summaryrefslogtreecommitdiff
path: root/src/cmd/8g/reg.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/8g/reg.c')
-rw-r--r--src/cmd/8g/reg.c78
1 files changed, 76 insertions, 2 deletions
diff --git a/src/cmd/8g/reg.c b/src/cmd/8g/reg.c
index 98edfd9a9..af3e834c9 100644
--- a/src/cmd/8g/reg.c
+++ b/src/cmd/8g/reg.c
@@ -159,8 +159,12 @@ regopt(Prog *firstp)
* find use and set of variables
*/
g = flowstart(firstp, sizeof(Reg));
- if(g == nil)
+ if(g == nil) {
+ for(i=0; i<nvar; i++)
+ var[i].node->opt = nil;
return;
+ }
+
firstr = (Reg*)g->start;
for(r = firstr; r != R; r = (Reg*)r->f.link) {
@@ -368,6 +372,8 @@ brk:
/*
* free aux structures. peep allocates new ones.
*/
+ for(i=0; i<nvar; i++)
+ var[i].node->opt = nil;
flowend(g);
firstr = R;
@@ -631,6 +637,13 @@ mkvar(Reg *r, Adr *a)
v->width = w;
v->addr = flag; // funny punning
v->node = node;
+
+ // node->opt is the head of a linked list
+ // of Vars within the given Node, so that
+ // we can start at a Var and find all the other
+ // Vars in the same Go variable.
+ v->nextinnode = node->opt;
+ node->opt = v;
if(debug['R'])
print("bit=%2d et=%2E w=%d+%d %#N %D flag=%d\n", i, et, o, w, node, a, v->addr);
@@ -644,6 +657,24 @@ mkvar(Reg *r, Adr *a)
for(z=0; z<BITS; z++)
params.b[z] |= bit.b[z];
+ // Treat values with their address taken as live at calls,
+ // because the garbage collector's liveness analysis in ../gc/plive.c does.
+ // These must be consistent or else we will elide stores and the garbage
+ // collector will see uninitialized data.
+ // The typical case where our own analysis is out of sync is when the
+ // node appears to have its address taken but that code doesn't actually
+ // get generated and therefore doesn't show up as an address being
+ // taken when we analyze the instruction stream.
+ // One instance of this case is when a closure uses the same name as
+ // an outer variable for one of its own variables declared with :=.
+ // The parser flags the outer variable as possibly shared, and therefore
+ // sets addrtaken, even though it ends up not being actually shared.
+ // If we were better about _ elision, _ = &x would suffice too.
+ // The broader := in a closure problem is mentioned in a comment in
+ // closure.c:/^typecheckclosure and dcl.c:/^oldname.
+ if(node->addrtaken)
+ setaddrs(bit);
+
return bit;
none:
@@ -654,7 +685,8 @@ void
prop(Reg *r, Bits ref, Bits cal)
{
Reg *r1, *r2;
- int z;
+ int z, i, j;
+ Var *v, *v1;
for(r1 = r; r1 != R; r1 = (Reg*)r1->f.p1) {
for(z=0; z<BITS; z++) {
@@ -677,6 +709,48 @@ prop(Reg *r, Bits ref, Bits cal)
cal.b[z] |= ref.b[z] | externs.b[z];
ref.b[z] = 0;
}
+
+ // cal.b is the current approximation of what's live across the call.
+ // Every bit in cal.b is a single stack word. For each such word,
+ // find all the other tracked stack words in the same Go variable
+ // (struct/slice/string/interface) and mark them live too.
+ // This is necessary because the liveness analysis for the garbage
+ // collector works at variable granularity, not at word granularity.
+ // It is fundamental for slice/string/interface: the garbage collector
+ // needs the whole value, not just some of the words, in order to
+ // interpret the other bits correctly. Specifically, slice needs a consistent
+ // ptr and cap, string needs a consistent ptr and len, and interface
+ // needs a consistent type word and data word.
+ for(z=0; z<BITS; z++) {
+ if(cal.b[z] == 0)
+ continue;
+ for(i=0; i<32; i++) {
+ if(z*32+i >= nvar || ((cal.b[z]>>i)&1) == 0)
+ continue;
+ v = var+z*32+i;
+ if(v->node->opt == nil) // v represents fixed register, not Go variable
+ continue;
+
+ // v->node->opt is the head of a linked list of Vars
+ // corresponding to tracked words from the Go variable v->node.
+ // Walk the list and set all the bits.
+ // For a large struct this could end up being quadratic:
+ // after the first setting, the outer loop (for z, i) would see a 1 bit
+ // for all of the remaining words in the struct, and for each such
+ // word would go through and turn on all the bits again.
+ // To avoid the quadratic behavior, we only turn on the bits if
+ // v is the head of the list or if the head's bit is not yet turned on.
+ // This will set the bits at most twice, keeping the overall loop linear.
+ v1 = v->node->opt;
+ j = v1 - var;
+ if(v == v1 || ((cal.b[j/32]>>(j&31))&1) == 0) {
+ for(; v1 != nil; v1 = v1->nextinnode) {
+ j = v1 - var;
+ cal.b[j/32] |= 1<<(j&31);
+ }
+ }
+ }
+ }
break;
case ATEXT: