mirror of
https://github.com/golang/go
synced 2024-11-23 08:20:05 -07:00
cmd/gc: handle non-escaping address-taken variables better
This CL makes the bitmaps a little more precise about variables that have their address taken but for which the address does not escape to the heap, so that the variables are kept in the stack frame rather than allocated on the heap. The code before this CL handled these variables by treating every return statement as using every such variable and depending on liveness analysis to essentially treat the variable as live during the entire function. That approach has false positives and (worse) false negatives. That is, it's both sloppy and buggy: func f(b1, b2 bool) { // x live here! (sloppy) if b2 { print(0) // x live here! (sloppy) return } var z **int x := new(int) *x = 42 z = &x print(**z) // x live here (conservative) if b2 { print(1) // x live here (conservative) return } for { print(**z) // x not live here (buggy) } } The first two liveness annotations (marked sloppy) are clearly wrong: x cannot be live if it has not yet been declared. The last liveness annotation (marked buggy) is also wrong: x is live here as *z, but because there is no return statement reachable from this point in the code, the analysis treats x as dead. This CL changes the liveness calculation to mark such variables live exactly at points in the code reachable from the variable declaration. This keeps the conservative decisions but fixes the sloppy and buggy ones. The CL also detects ambiguously live variables, those that are being marked live but may not actually have been initialized, such as in this example: func f(b1 bool) { var z **int if b1 { x := new(int) *x = 42 z = &x } else { y := new(int) *y = 54 z = &y } print(**z) // x, y live here (conservative) } Since the print statement is reachable from the declaration of x, x must conservatively be marked live. The same goes for y. Although both x and y are marked live at the print statement, clearly only one of them has been initialized. They are both "ambiguously live". These ambiguously live variables cause problems for garbage collection: the collector cannot ignore them but also cannot depend on them to be initialized to valid pointer values. Ambiguously live variables do not come up too often in real code, but recent changes to the way map and interface runtime functions are invoked has created a large number of ambiguously live compiler-generated temporary variables. The next CL will adjust the analysis to understand these temporaries better, to make ambiguously live variables fairly rare. Once ambiguously live variables are rare enough, another CL will introduce code at the beginning of a function to zero those slots on the stack. At that point the garbage collector and the stack copying routines will be able to depend on the guarantee that if a slot is marked as live in a liveness bitmap, it is initialized. R=khr CC=golang-codereviews, iant https://golang.org/cl/51810043
This commit is contained in:
parent
fbfb9430dc
commit
ca9975a45e
@ -135,6 +135,18 @@ bvor(Bvec *dst, Bvec *src1, Bvec *src2)
|
||||
dst->b[w] = src1->b[w] | src2->b[w];
|
||||
}
|
||||
|
||||
/* intersection */
|
||||
void
|
||||
bvand(Bvec *dst, Bvec *src1, Bvec *src2)
|
||||
{
|
||||
int32 i, w;
|
||||
|
||||
if(dst->n != src1->n || dst->n != src2->n)
|
||||
fatal("bvor: lengths %d, %d, and %d are not equal", dst->n, src1->n, src2->n);
|
||||
for(i = 0, w = 0; i < dst->n; i += WORDBITS, w++)
|
||||
dst->b[w] = src1->b[w] & src2->b[w];
|
||||
}
|
||||
|
||||
void
|
||||
bvprint(Bvec *bv)
|
||||
{
|
||||
|
@ -1036,6 +1036,7 @@ int bvget(Bvec *bv, int32 i);
|
||||
int bvisempty(Bvec *bv);
|
||||
void bvnot(Bvec *bv);
|
||||
void bvor(Bvec *dst, Bvec *src1, Bvec *src2);
|
||||
void bvand(Bvec *dst, Bvec *src1, Bvec *src2);
|
||||
void bvprint(Bvec *bv);
|
||||
void bvreset(Bvec *bv, int32 i);
|
||||
void bvresetall(Bvec *bv);
|
||||
|
@ -293,7 +293,8 @@ cmpstackvar(Node *a, Node *b)
|
||||
return +1;
|
||||
if(a->type->width > b->type->width)
|
||||
return -1;
|
||||
return 0;
|
||||
|
||||
return strcmp(a->sym->name, b->sym->name);
|
||||
}
|
||||
|
||||
// TODO(lvd) find out where the PAUTO/OLITERAL nodes come from.
|
||||
|
@ -2,6 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Garbage collector liveness bitmap generation.
|
||||
|
||||
// The command line flag -live causes this code to print debug information.
|
||||
// The levels are:
|
||||
//
|
||||
// -live (aka -live=1): print liveness lists as code warnings at safe points
|
||||
// -live=2: print an assembly listing with liveness annotations
|
||||
// -live=3: print information during each computation phase (much chattier)
|
||||
//
|
||||
// Each level includes the earlier output as well.
|
||||
|
||||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include "gg.h"
|
||||
@ -73,29 +84,48 @@ struct Liveness {
|
||||
Array *vars;
|
||||
|
||||
// A list of basic blocks that are overlayed on the instruction list.
|
||||
// The blocks are roughly in the same order as the instructions
|
||||
// in the function (first block has TEXT instruction, and so on).
|
||||
Array *cfg;
|
||||
|
||||
// Summary sets of block effects. The upward exposed variables and
|
||||
// variables killed sets are computed during the dataflow prologue. The
|
||||
// live in and live out are solved for and serialized in the epilogue.
|
||||
// Summary sets of block effects.
|
||||
// The Bvec** is indexed by bb->rpo to yield a single Bvec*.
|
||||
// That bit vector is indexed by variable number (same as lv->vars).
|
||||
//
|
||||
// Computed during livenessprologue using only the content of
|
||||
// individual blocks:
|
||||
//
|
||||
// uevar: upward exposed variables (used before set in block)
|
||||
// varkill: killed variables (set in block)
|
||||
// avarinit: addrtaken variables set or used (proof of initialization)
|
||||
//
|
||||
// Computed during livenesssolve using control flow information:
|
||||
//
|
||||
// livein: variables live at block entry
|
||||
// liveout: variables live at block exit
|
||||
// avarinitany: addrtaken variables possibly initialized at block exit
|
||||
// (initialized in block or at exit from any predecessor block)
|
||||
// avarinitall: addrtaken variables certainly initialized at block exit
|
||||
// (initialized in block or at exit from all predecessor blocks)
|
||||
Bvec **uevar;
|
||||
Bvec **varkill;
|
||||
Bvec **livein;
|
||||
Bvec **liveout;
|
||||
Bvec **avarinit;
|
||||
Bvec **avarinitany;
|
||||
Bvec **avarinitall;
|
||||
|
||||
// An array with a bit vector for each safe point tracking live pointers
|
||||
// in the arguments and locals area.
|
||||
// in the arguments and locals area, indexed by bb->rpo.
|
||||
Array *argslivepointers;
|
||||
Array *livepointers;
|
||||
|
||||
// An array with a bit vector for each safe point tracking dead values
|
||||
// pointers in the arguments and locals area.
|
||||
// pointers in the arguments and locals area, indexed by bb->rpo.
|
||||
Array *argsdeadvalues;
|
||||
Array *deadvalues;
|
||||
};
|
||||
|
||||
static int printnoise = 0;
|
||||
|
||||
static void*
|
||||
xmalloc(uintptr size)
|
||||
{
|
||||
@ -600,10 +630,20 @@ isfunny(Node *node)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Computes the upward exposure and kill effects of an instruction on a set of
|
||||
// Computes the effects of an instruction on a set of
|
||||
// variables. The vars argument is an array of Node*s.
|
||||
//
|
||||
// The output vectors give bits for variables:
|
||||
// uevar - used by this instruction
|
||||
// varkill - set by this instruction
|
||||
// avarinit - initialized or referred to by this instruction,
|
||||
// only for variables with address taken but not escaping to heap
|
||||
//
|
||||
// The avarinit output serves as a signal that the data has been
|
||||
// initialized, because any use of a variable must come after its
|
||||
// initialization.
|
||||
static void
|
||||
progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
|
||||
progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill, Bvec *avarinit)
|
||||
{
|
||||
ProgInfo info;
|
||||
Adr *from;
|
||||
@ -614,6 +654,8 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
|
||||
|
||||
bvresetall(uevar);
|
||||
bvresetall(varkill);
|
||||
bvresetall(avarinit);
|
||||
|
||||
proginfo(&info, prog);
|
||||
if(prog->as == ARET) {
|
||||
// Return instructions implicitly read all the arguments. For
|
||||
@ -626,13 +668,6 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
|
||||
case PPARAMOUT:
|
||||
bvset(uevar, i);
|
||||
break;
|
||||
case PAUTO:
|
||||
// Because the lifetime of stack variables
|
||||
// that have their address taken is undecidable,
|
||||
// we conservatively assume their lifetime
|
||||
// extends to the return as well.
|
||||
if(isfat(node->type) || node->addrtaken)
|
||||
bvset(uevar, i);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@ -659,6 +694,8 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
|
||||
pos = arrayindexof(vars, from->node);
|
||||
if(pos == -1)
|
||||
goto Next;
|
||||
if(from->node->addrtaken)
|
||||
bvset(avarinit, pos);
|
||||
if(info.flags & (LeftRead | LeftAddr))
|
||||
bvset(uevar, pos);
|
||||
if(info.flags & LeftWrite)
|
||||
@ -678,6 +715,8 @@ Next:
|
||||
pos = arrayindexof(vars, to->node);
|
||||
if(pos == -1)
|
||||
goto Next1;
|
||||
if(to->node->addrtaken)
|
||||
bvset(avarinit, pos);
|
||||
if(info.flags & (RightRead | RightAddr))
|
||||
bvset(uevar, pos);
|
||||
if(info.flags & RightWrite)
|
||||
@ -711,6 +750,9 @@ newliveness(Node *fn, Prog *ptxt, Array *cfg, Array *vars, int computedead)
|
||||
result->varkill = xmalloc(sizeof(Bvec*) * nblocks);
|
||||
result->livein = xmalloc(sizeof(Bvec*) * nblocks);
|
||||
result->liveout = xmalloc(sizeof(Bvec*) * nblocks);
|
||||
result->avarinit = xmalloc(sizeof(Bvec*) * nblocks);
|
||||
result->avarinitany = xmalloc(sizeof(Bvec*) * nblocks);
|
||||
result->avarinitall = xmalloc(sizeof(Bvec*) * nblocks);
|
||||
|
||||
nvars = arraylength(vars);
|
||||
for(i = 0; i < nblocks; i++) {
|
||||
@ -718,6 +760,9 @@ newliveness(Node *fn, Prog *ptxt, Array *cfg, Array *vars, int computedead)
|
||||
result->varkill[i] = bvalloc(nvars);
|
||||
result->livein[i] = bvalloc(nvars);
|
||||
result->liveout[i] = bvalloc(nvars);
|
||||
result->avarinit[i] = bvalloc(nvars);
|
||||
result->avarinitany[i] = bvalloc(nvars);
|
||||
result->avarinitall[i] = bvalloc(nvars);
|
||||
}
|
||||
|
||||
result->livepointers = arraynew(0, sizeof(Bvec*));
|
||||
@ -764,24 +809,32 @@ freeliveness(Liveness *lv)
|
||||
free(lv->varkill[i]);
|
||||
free(lv->livein[i]);
|
||||
free(lv->liveout[i]);
|
||||
free(lv->avarinit[i]);
|
||||
free(lv->avarinitany[i]);
|
||||
free(lv->avarinitall[i]);
|
||||
}
|
||||
|
||||
free(lv->uevar);
|
||||
free(lv->varkill);
|
||||
free(lv->livein);
|
||||
free(lv->liveout);
|
||||
free(lv->avarinit);
|
||||
free(lv->avarinitany);
|
||||
free(lv->avarinitall);
|
||||
|
||||
free(lv);
|
||||
}
|
||||
|
||||
static void
|
||||
printeffects(Prog *p, Bvec *uevar, Bvec *varkill)
|
||||
printeffects(Prog *p, Bvec *uevar, Bvec *varkill, Bvec *avarinit)
|
||||
{
|
||||
print("effects of %P", p);
|
||||
print("\nuevar: ");
|
||||
bvprint(uevar);
|
||||
print("\nvarkill: ");
|
||||
bvprint(varkill);
|
||||
print("\navarinit: ");
|
||||
bvprint(avarinit);
|
||||
print("\n");
|
||||
}
|
||||
|
||||
@ -844,6 +897,9 @@ livenessprintblock(Liveness *lv, BasicBlock *bb)
|
||||
printvars("\tvarkill", lv->varkill[bb->rpo], lv->vars);
|
||||
printvars("\tlivein", lv->livein[bb->rpo], lv->vars);
|
||||
printvars("\tliveout", lv->liveout[bb->rpo], lv->vars);
|
||||
printvars("\tavarinit", lv->avarinit[bb->rpo], lv->vars);
|
||||
printvars("\tavarinitany", lv->avarinitany[bb->rpo], lv->vars);
|
||||
printvars("\tavarinitall", lv->avarinitall[bb->rpo], lv->vars);
|
||||
|
||||
print("\tprog:\n");
|
||||
for(prog = bb->first;; prog = prog->link) {
|
||||
@ -1118,10 +1174,11 @@ twobitlivepointermap(Liveness *lv, Bvec *liveout, Array *vars, Bvec *args, Bvec
|
||||
break;
|
||||
}
|
||||
}
|
||||
// In various and obscure circumstances, such as methods with an unused
|
||||
// receiver, the this argument and in arguments are omitted from the
|
||||
// node list. We must explicitly preserve these values to ensure that
|
||||
// the addresses printed in backtraces are valid.
|
||||
|
||||
// The node list only contains declared names.
|
||||
// If the receiver or arguments are unnamed, they will be omitted
|
||||
// from the list above. Preserve those values - even though they are unused -
|
||||
// in order to keep their addresses live for use in stack traces.
|
||||
thisargtype = getthisx(lv->fn->type);
|
||||
if(thisargtype != nil) {
|
||||
xoffset = 0;
|
||||
@ -1199,8 +1256,7 @@ unlinkedprog(int as)
|
||||
static Prog*
|
||||
newpcdataprog(Prog *prog, int32 index)
|
||||
{
|
||||
Node from;
|
||||
Node to;
|
||||
Node from, to;
|
||||
Prog *pcdata;
|
||||
|
||||
nodconst(&from, types[TINT32], PCDATA_StackMapIndex);
|
||||
@ -1227,8 +1283,7 @@ static void
|
||||
livenessprologue(Liveness *lv)
|
||||
{
|
||||
BasicBlock *bb;
|
||||
Bvec *uevar;
|
||||
Bvec *varkill;
|
||||
Bvec *uevar, *varkill, *avarinit;
|
||||
Prog *prog;
|
||||
int32 i;
|
||||
int32 nvars;
|
||||
@ -1236,39 +1291,86 @@ livenessprologue(Liveness *lv)
|
||||
nvars = arraylength(lv->vars);
|
||||
uevar = bvalloc(nvars);
|
||||
varkill = bvalloc(nvars);
|
||||
avarinit = bvalloc(nvars);
|
||||
for(i = 0; i < arraylength(lv->cfg); i++) {
|
||||
bb = *(BasicBlock**)arrayget(lv->cfg, i);
|
||||
// Walk the block instructions backward and update the block
|
||||
// effects with the each prog effects.
|
||||
for(prog = bb->last; prog != nil; prog = prog->opt) {
|
||||
progeffects(prog, lv->vars, uevar, varkill);
|
||||
if(0) printeffects(prog, uevar, varkill);
|
||||
progeffects(prog, lv->vars, uevar, varkill, avarinit);
|
||||
if(debuglive >= 3)
|
||||
printeffects(prog, uevar, varkill, avarinit);
|
||||
bvor(lv->varkill[i], lv->varkill[i], varkill);
|
||||
bvandnot(lv->uevar[i], lv->uevar[i], varkill);
|
||||
bvor(lv->uevar[i], lv->uevar[i], uevar);
|
||||
bvor(lv->uevar[i], lv->uevar[i], uevar);
|
||||
bvor(lv->avarinit[i], lv->avarinit[i], avarinit);
|
||||
}
|
||||
}
|
||||
free(uevar);
|
||||
free(varkill);
|
||||
free(avarinit);
|
||||
}
|
||||
|
||||
// Solve the liveness dataflow equations.
|
||||
static void
|
||||
livenesssolve(Liveness *lv)
|
||||
{
|
||||
BasicBlock *bb;
|
||||
BasicBlock *succ;
|
||||
Bvec *newlivein;
|
||||
Bvec *newliveout;
|
||||
int32 rpo;
|
||||
int32 i;
|
||||
int32 j;
|
||||
int change;
|
||||
BasicBlock *bb, *succ, *pred;
|
||||
Bvec *newlivein, *newliveout, *any, *all;
|
||||
int32 rpo, i, j, change;
|
||||
|
||||
// These temporary bitvectors exist to avoid successive allocations and
|
||||
// frees within the loop.
|
||||
newlivein = bvalloc(arraylength(lv->vars));
|
||||
newliveout = bvalloc(arraylength(lv->vars));
|
||||
any = bvalloc(arraylength(lv->vars));
|
||||
all = bvalloc(arraylength(lv->vars));
|
||||
|
||||
// Push avarinitall, avarinitany forward.
|
||||
// avarinitall says the addressed var is initialized along all paths reaching the block exit.
|
||||
// avarinitany says the addressed var is initialized along some path reaching the block exit.
|
||||
for(i = 0; i < arraylength(lv->cfg); i++) {
|
||||
bb = *(BasicBlock**)arrayget(lv->cfg, i);
|
||||
rpo = bb->rpo;
|
||||
if(i == 0)
|
||||
bvcopy(lv->avarinitall[rpo], lv->avarinit[rpo]);
|
||||
else {
|
||||
bvresetall(lv->avarinitall[rpo]);
|
||||
bvnot(lv->avarinitall[rpo]);
|
||||
}
|
||||
bvcopy(lv->avarinitany[rpo], lv->avarinit[rpo]);
|
||||
}
|
||||
|
||||
change = 1;
|
||||
while(change != 0) {
|
||||
change = 0;
|
||||
for(i = 0; i < arraylength(lv->cfg); i++) {
|
||||
bb = *(BasicBlock**)arrayget(lv->cfg, i);
|
||||
rpo = bb->rpo;
|
||||
bvresetall(any);
|
||||
bvresetall(all);
|
||||
for(j = 0; j < arraylength(bb->pred); j++) {
|
||||
pred = *(BasicBlock**)arrayget(bb->pred, j);
|
||||
if(j == 0) {
|
||||
bvcopy(any, lv->avarinitany[pred->rpo]);
|
||||
bvcopy(all, lv->avarinitall[pred->rpo]);
|
||||
} else {
|
||||
bvor(any, any, lv->avarinitany[pred->rpo]);
|
||||
bvand(all, all, lv->avarinitall[pred->rpo]);
|
||||
}
|
||||
}
|
||||
bvor(any, any, lv->avarinit[rpo]);
|
||||
bvor(all, all, lv->avarinit[rpo]);
|
||||
if(bvcmp(any, lv->avarinitany[rpo])) {
|
||||
change = 1;
|
||||
bvcopy(lv->avarinitany[rpo], any);
|
||||
}
|
||||
if(bvcmp(all, lv->avarinitall[rpo])) {
|
||||
change = 1;
|
||||
bvcopy(lv->avarinitall[rpo], all);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through the blocks in reverse round-robin fashion. A work
|
||||
// queue might be slightly faster. As is, the number of iterations is
|
||||
@ -1279,6 +1381,9 @@ livenesssolve(Liveness *lv)
|
||||
// Walk blocks in the general direction of propagation. This
|
||||
// improves convergence.
|
||||
for(i = arraylength(lv->cfg) - 1; i >= 0; i--) {
|
||||
// A variable is live on output from this block
|
||||
// if it is live on input to some successor.
|
||||
//
|
||||
// out[b] = \bigcup_{s \in succ[b]} in[s]
|
||||
bb = *(BasicBlock**)arrayget(lv->cfg, i);
|
||||
rpo = bb->rpo;
|
||||
@ -1291,6 +1396,11 @@ livenesssolve(Liveness *lv)
|
||||
change = 1;
|
||||
bvcopy(lv->liveout[rpo], newliveout);
|
||||
}
|
||||
|
||||
// A variable is live on input to this block
|
||||
// if it is live on output from this block and
|
||||
// not set by the code in this block.
|
||||
//
|
||||
// in[b] = uevar[b] \cup (out[b] \setminus varkill[b])
|
||||
bvandnot(newlivein, lv->liveout[rpo], lv->varkill[rpo]);
|
||||
bvor(lv->livein[rpo], newlivein, lv->uevar[rpo]);
|
||||
@ -1299,6 +1409,31 @@ livenesssolve(Liveness *lv)
|
||||
|
||||
free(newlivein);
|
||||
free(newliveout);
|
||||
free(any);
|
||||
free(all);
|
||||
}
|
||||
|
||||
// This function is slow but it is only used for generating debug prints.
|
||||
// Check whether n is marked live in args/locals.
|
||||
static int
|
||||
islive(Node *n, Bvec *args, Bvec *locals)
|
||||
{
|
||||
int i;
|
||||
|
||||
switch(n->class) {
|
||||
case PPARAM:
|
||||
case PPARAMOUT:
|
||||
for(i = 0; i < n->type->width/widthptr*BitsPerPointer; i++)
|
||||
if(bvget(args, n->xoffset/widthptr*BitsPerPointer + i))
|
||||
return 1;
|
||||
break;
|
||||
case PAUTO:
|
||||
for(i = 0; i < n->type->width/widthptr*BitsPerPointer; i++)
|
||||
if(bvget(locals, (n->xoffset + stkptrsize)/widthptr*BitsPerPointer + i))
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Visits all instructions in a basic block and computes a bit vector of live
|
||||
@ -1306,10 +1441,11 @@ livenesssolve(Liveness *lv)
|
||||
static void
|
||||
livenessepilogue(Liveness *lv)
|
||||
{
|
||||
BasicBlock *bb;
|
||||
Bvec *livein, *liveout, *uevar, *varkill, *args, *locals;
|
||||
BasicBlock *bb, *pred;
|
||||
Bvec *livein, *liveout, *uevar, *varkill, *args, *locals, *avarinit, *any, *all;
|
||||
Node *n;
|
||||
Prog *p, *next;
|
||||
int32 i, j, nmsg, nvars, pos;
|
||||
int32 i, j, numlive, startmsg, nmsg, nvars, pos;
|
||||
char **msg;
|
||||
Fmt fmt;
|
||||
|
||||
@ -1318,38 +1454,97 @@ livenessepilogue(Liveness *lv)
|
||||
liveout = bvalloc(nvars);
|
||||
uevar = bvalloc(nvars);
|
||||
varkill = bvalloc(nvars);
|
||||
avarinit = bvalloc(nvars);
|
||||
any = bvalloc(nvars);
|
||||
all = bvalloc(nvars);
|
||||
msg = nil;
|
||||
nmsg = 0;
|
||||
startmsg = 0;
|
||||
|
||||
for(i = 0; i < arraylength(lv->cfg); i++) {
|
||||
bb = *(BasicBlock**)arrayget(lv->cfg, i);
|
||||
bvcopy(livein, lv->liveout[bb->rpo]);
|
||||
// Walk forward through the basic block instructions and
|
||||
// allocate and empty map for those instructions that need them
|
||||
for(p = bb->last; p != nil; p = p->opt) {
|
||||
if(!issafepoint(p))
|
||||
continue;
|
||||
|
||||
// Allocate a bit vector for each class and facet of
|
||||
// value we are tracking.
|
||||
|
||||
// Live stuff first.
|
||||
args = bvalloc(argswords() * BitsPerPointer);
|
||||
arrayadd(lv->argslivepointers, &args);
|
||||
locals = bvalloc(localswords() * BitsPerPointer);
|
||||
arrayadd(lv->livepointers, &locals);
|
||||
|
||||
// Dead stuff second.
|
||||
if(lv->deadvalues != nil) {
|
||||
args = bvalloc(argswords() * BitsPerPointer);
|
||||
arrayadd(lv->argsdeadvalues, &args);
|
||||
locals = bvalloc(localswords() * BitsPerPointer);
|
||||
arrayadd(lv->deadvalues, &locals);
|
||||
|
||||
// Compute avarinitany and avarinitall for entry to block.
|
||||
// This duplicates information known during livenesssolve
|
||||
// but avoids storing two more vectors for each block.
|
||||
bvresetall(any);
|
||||
bvresetall(all);
|
||||
for(j = 0; j < arraylength(bb->pred); j++) {
|
||||
pred = *(BasicBlock**)arrayget(bb->pred, j);
|
||||
if(j == 0) {
|
||||
bvcopy(any, lv->avarinitany[pred->rpo]);
|
||||
bvcopy(all, lv->avarinitall[pred->rpo]);
|
||||
} else {
|
||||
bvor(any, any, lv->avarinitany[pred->rpo]);
|
||||
bvand(all, all, lv->avarinitall[pred->rpo]);
|
||||
}
|
||||
}
|
||||
|
||||
// Walk forward through the basic block instructions and
|
||||
// allocate liveness maps for those instructions that need them.
|
||||
// Seed the maps with information about the addrtaken variables.
|
||||
for(p = bb->first;; p = p->link) {
|
||||
progeffects(p, lv->vars, uevar, varkill, avarinit);
|
||||
bvor(any, any, avarinit);
|
||||
bvor(all, all, avarinit);
|
||||
|
||||
if(issafepoint(p)) {
|
||||
if(debuglive >= 3) {
|
||||
// Diagnose ambiguously live variables (any &^ all).
|
||||
// livein and liveout are dead here and used as temporaries.
|
||||
bvresetall(livein);
|
||||
bvandnot(liveout, any, all);
|
||||
if(bvcmp(livein, liveout) != 0) {
|
||||
for(pos = 0; pos < liveout->n; pos++) {
|
||||
if(bvget(liveout, pos)) {
|
||||
n = *(Node**)arrayget(lv->vars, pos);
|
||||
if(!n->diag && strncmp(n->sym->name, "autotmp_", 8) != 0) {
|
||||
n->diag = 1;
|
||||
warnl(p->lineno, "%N: %lN is ambiguously live", curfn->nname, n);
|
||||
}
|
||||
}
|
||||
bvset(all, pos); // silence future warnings in this block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate a bit vector for each class and facet of
|
||||
// value we are tracking.
|
||||
|
||||
// Live stuff first.
|
||||
args = bvalloc(argswords() * BitsPerPointer);
|
||||
arrayadd(lv->argslivepointers, &args);
|
||||
locals = bvalloc(localswords() * BitsPerPointer);
|
||||
arrayadd(lv->livepointers, &locals);
|
||||
|
||||
if(debuglive >= 3) {
|
||||
print("%P\n", p);
|
||||
printvars("avarinitany", any, lv->vars);
|
||||
}
|
||||
|
||||
// Record any values with an "address taken" reaching
|
||||
// this code position as live. Must do now instead of below
|
||||
// because the any/all calculation requires walking forward
|
||||
// over the block (as this loop does), while the liveout
|
||||
// requires walking backward (as the next loop does).
|
||||
twobitlivepointermap(lv, any, lv->vars, args, locals);
|
||||
|
||||
// Dead stuff second.
|
||||
if(lv->deadvalues != nil) {
|
||||
args = bvalloc(argswords() * BitsPerPointer);
|
||||
arrayadd(lv->argsdeadvalues, &args);
|
||||
locals = bvalloc(localswords() * BitsPerPointer);
|
||||
arrayadd(lv->deadvalues, &locals);
|
||||
}
|
||||
}
|
||||
|
||||
if(p == bb->last)
|
||||
break;
|
||||
}
|
||||
|
||||
if(debuglive) {
|
||||
if(debuglive >= 1 && strcmp(curfn->nname->sym->name, "init") != 0) {
|
||||
nmsg = arraylength(lv->livepointers);
|
||||
startmsg = nmsg;
|
||||
msg = xmalloc(nmsg*sizeof msg[0]);
|
||||
for(j=0; j<nmsg; j++)
|
||||
msg[j] = nil;
|
||||
@ -1363,14 +1558,15 @@ livenessepilogue(Liveness *lv)
|
||||
fatal("livenessepilogue");
|
||||
}
|
||||
|
||||
bvcopy(livein, lv->liveout[bb->rpo]);
|
||||
for(p = bb->last; p != nil; p = next) {
|
||||
next = p->opt; // splicebefore modifies p->opt
|
||||
// Propagate liveness information
|
||||
progeffects(p, lv->vars, uevar, varkill);
|
||||
progeffects(p, lv->vars, uevar, varkill, avarinit);
|
||||
bvcopy(liveout, livein);
|
||||
bvandnot(livein, liveout, varkill);
|
||||
bvor(livein, livein, uevar);
|
||||
if(printnoise){
|
||||
if(debuglive >= 3 && issafepoint(p)){
|
||||
print("%P\n", p);
|
||||
printvars("uevar", uevar, lv->vars);
|
||||
printvars("varkill", varkill, lv->vars);
|
||||
@ -1381,25 +1577,37 @@ livenessepilogue(Liveness *lv)
|
||||
// Found an interesting instruction, record the
|
||||
// corresponding liveness information.
|
||||
|
||||
if(debuglive) {
|
||||
fmtstrinit(&fmt);
|
||||
fmtprint(&fmt, "%L: live at ", p->lineno);
|
||||
if(p->as == ACALL)
|
||||
fmtprint(&fmt, "CALL %lS:", p->to.sym);
|
||||
else
|
||||
fmtprint(&fmt, "TEXT %lS:", p->from.sym);
|
||||
for(j = 0; j < arraylength(lv->vars); j++)
|
||||
if(bvget(liveout, j))
|
||||
fmtprint(&fmt, " %N", *(Node**)arrayget(lv->vars, j));
|
||||
fmtprint(&fmt, "\n");
|
||||
msg[pos] = fmtstrflush(&fmt);
|
||||
}
|
||||
|
||||
// Record live pointers.
|
||||
args = *(Bvec**)arrayget(lv->argslivepointers, pos);
|
||||
locals = *(Bvec**)arrayget(lv->livepointers, pos);
|
||||
twobitlivepointermap(lv, liveout, lv->vars, args, locals);
|
||||
|
||||
// Show live pointer bitmaps.
|
||||
// We're interpreting the args and locals bitmap instead of liveout so that we
|
||||
// include the bits added by the avarinit logic in the
|
||||
// previous loop.
|
||||
if(debuglive >= 1) {
|
||||
fmtstrinit(&fmt);
|
||||
fmtprint(&fmt, "%L: live at ", p->lineno);
|
||||
if(p->as == ACALL)
|
||||
fmtprint(&fmt, "call to %s:", p->to.node->sym->name);
|
||||
else
|
||||
fmtprint(&fmt, "entry to %s:", p->from.node->sym->name);
|
||||
numlive = 0;
|
||||
for(j = 0; j < arraylength(lv->vars); j++) {
|
||||
n = *(Node**)arrayget(lv->vars, j);
|
||||
if(islive(n, args, locals)) {
|
||||
fmtprint(&fmt, " %N", n);
|
||||
numlive++;
|
||||
}
|
||||
}
|
||||
fmtprint(&fmt, "\n");
|
||||
if(numlive == 0) // squelch message
|
||||
free(fmtstrflush(&fmt));
|
||||
else
|
||||
msg[--startmsg] = fmtstrflush(&fmt);
|
||||
}
|
||||
|
||||
// Record dead values.
|
||||
if(lv->deadvalues != nil) {
|
||||
args = *(Bvec**)arrayget(lv->argsdeadvalues, pos);
|
||||
@ -1411,17 +1619,12 @@ livenessepilogue(Liveness *lv)
|
||||
// The TEXT instruction annotation is implicit.
|
||||
if(p->as == ACALL) {
|
||||
if(isdeferreturn(p)) {
|
||||
// Because this runtime call
|
||||
// modifies its return address
|
||||
// to return back to itself,
|
||||
// emitting a PCDATA before the
|
||||
// call instruction will result
|
||||
// in an off by one error during
|
||||
// a stack walk. Fortunately,
|
||||
// the compiler inserts a no-op
|
||||
// instruction before this call
|
||||
// so we can reliably anchor the
|
||||
// PCDATA to that instruction.
|
||||
// runtime.deferreturn modifies its return address to return
|
||||
// back to the CALL, not to the subsequent instruction.
|
||||
// Because the return comes back one instruction early,
|
||||
// the PCDATA must begin one instruction early too.
|
||||
// The instruction before a call to deferreturn is always a
|
||||
// no-op, to keep PC-specific data unambiguous.
|
||||
splicebefore(lv, bb, newpcdataprog(p->opt, pos), p->opt);
|
||||
} else {
|
||||
splicebefore(lv, bb, newpcdataprog(p, pos), p);
|
||||
@ -1431,13 +1634,14 @@ livenessepilogue(Liveness *lv)
|
||||
pos--;
|
||||
}
|
||||
}
|
||||
if(debuglive) {
|
||||
for(j=0; j<nmsg; j++)
|
||||
if(debuglive >= 1) {
|
||||
for(j=startmsg; j<nmsg; j++)
|
||||
if(msg[j] != nil)
|
||||
print("%s", msg[j]);
|
||||
free(msg);
|
||||
msg = nil;
|
||||
nmsg = 0;
|
||||
startmsg = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1445,6 +1649,130 @@ livenessepilogue(Liveness *lv)
|
||||
free(liveout);
|
||||
free(uevar);
|
||||
free(varkill);
|
||||
free(avarinit);
|
||||
free(any);
|
||||
free(all);
|
||||
}
|
||||
|
||||
static int
|
||||
printbitset(int printed, char *name, Array *vars, Bvec *bits)
|
||||
{
|
||||
int i, started;
|
||||
Node *n;
|
||||
|
||||
started = 0;
|
||||
for(i=0; i<arraylength(vars); i++) {
|
||||
if(!bvget(bits, i))
|
||||
continue;
|
||||
if(!started) {
|
||||
if(!printed) {
|
||||
printed = 1;
|
||||
print("\t");
|
||||
} else
|
||||
print(" ");
|
||||
started = 1;
|
||||
printed = 1;
|
||||
print("%s=", name);
|
||||
} else {
|
||||
print(",");
|
||||
}
|
||||
n = *(Node**)arrayget(vars, i);
|
||||
print("%s", n->sym->name);
|
||||
}
|
||||
return printed;
|
||||
}
|
||||
|
||||
// Prints the computed liveness information and inputs, for debugging.
|
||||
// This format synthesizes the information used during the multiple passes
|
||||
// into a single presentation.
|
||||
static void
|
||||
livenessprintdebug(Liveness *lv)
|
||||
{
|
||||
int i, j, printed, nsafe;
|
||||
BasicBlock *bb;
|
||||
Prog *p;
|
||||
Bvec *uevar, *varkill, *avarinit, *args, *locals;
|
||||
Node *n;
|
||||
|
||||
print("liveness: %s\n", curfn->nname->sym->name);
|
||||
|
||||
uevar = bvalloc(arraylength(lv->vars));
|
||||
varkill = bvalloc(arraylength(lv->vars));
|
||||
avarinit = bvalloc(arraylength(lv->vars));
|
||||
|
||||
nsafe = 0;
|
||||
for(i = 0; i < arraylength(lv->cfg); i++) {
|
||||
if(i > 0)
|
||||
print("\n");
|
||||
bb = *(BasicBlock**)arrayget(lv->cfg, i);
|
||||
|
||||
// bb#0 pred=1,2 succ=3,4
|
||||
print("bb#%d pred=", i);
|
||||
for(j = 0; j < arraylength(bb->pred); j++) {
|
||||
if(j > 0)
|
||||
print(",");
|
||||
print("%d", (*(BasicBlock**)arrayget(bb->pred, j))->rpo);
|
||||
}
|
||||
print(" succ=");
|
||||
for(j = 0; j < arraylength(bb->succ); j++) {
|
||||
if(j > 0)
|
||||
print(",");
|
||||
print("%d", (*(BasicBlock**)arrayget(bb->succ, j))->rpo);
|
||||
}
|
||||
print("\n");
|
||||
|
||||
// initial settings
|
||||
printed = 0;
|
||||
printed = printbitset(printed, "uevar", lv->vars, lv->uevar[bb->rpo]);
|
||||
printed = printbitset(printed, "livein", lv->vars, lv->livein[bb->rpo]);
|
||||
if(printed)
|
||||
print("\n");
|
||||
|
||||
// program listing, with individual effects listed
|
||||
for(p = bb->first;; p = p->link) {
|
||||
print("%P\n", p);
|
||||
progeffects(p, lv->vars, uevar, varkill, avarinit);
|
||||
printed = 0;
|
||||
printed = printbitset(printed, "uevar", lv->vars, uevar);
|
||||
printed = printbitset(printed, "varkill", lv->vars, varkill);
|
||||
printed = printbitset(printed, "avarinit", lv->vars, avarinit);
|
||||
if(printed)
|
||||
print("\n");
|
||||
if(issafepoint(p)) {
|
||||
args = *(Bvec**)arrayget(lv->argslivepointers, nsafe);
|
||||
locals = *(Bvec**)arrayget(lv->livepointers, nsafe);
|
||||
nsafe++;
|
||||
print("\tlive=");
|
||||
printed = 0;
|
||||
for(j = 0; j < arraylength(lv->vars); j++) {
|
||||
n = *(Node**)arrayget(lv->vars, j);
|
||||
if(islive(n, args, locals)) {
|
||||
if(printed++)
|
||||
print(",");
|
||||
print("%N", n);
|
||||
}
|
||||
}
|
||||
print("\n");
|
||||
}
|
||||
if(p == bb->last)
|
||||
break;
|
||||
}
|
||||
|
||||
// bb bitsets
|
||||
print("end\n");
|
||||
printed = printbitset(printed, "varkill", lv->vars, lv->varkill[bb->rpo]);
|
||||
printed = printbitset(printed, "liveout", lv->vars, lv->liveout[bb->rpo]);
|
||||
printed = printbitset(printed, "avarinit", lv->vars, lv->avarinit[bb->rpo]);
|
||||
printed = printbitset(printed, "avarinitany", lv->vars, lv->avarinitany[bb->rpo]);
|
||||
printed = printbitset(printed, "avarinitall", lv->vars, lv->avarinitall[bb->rpo]);
|
||||
if(printed)
|
||||
print("\n");
|
||||
}
|
||||
print("\n");
|
||||
|
||||
free(uevar);
|
||||
free(varkill);
|
||||
free(avarinit);
|
||||
}
|
||||
|
||||
// Dumps an array of bitmaps to a symbol as a sequence of uint32 values. The
|
||||
@ -1456,14 +1784,8 @@ static void
|
||||
twobitwritesymbol(Array *arr, Sym *sym, Bvec *check)
|
||||
{
|
||||
Bvec *bv;
|
||||
int off;
|
||||
uint32 bit;
|
||||
uint32 word;
|
||||
uint32 checkword;
|
||||
int32 i;
|
||||
int32 j;
|
||||
int32 len;
|
||||
int32 pos;
|
||||
int off, i, j, len, pos;
|
||||
uint32 bit, word, checkword;
|
||||
|
||||
len = arraylength(arr);
|
||||
// Dump the length of the bitmap array.
|
||||
@ -1478,8 +1800,7 @@ twobitwritesymbol(Array *arr, Sym *sym, Bvec *check)
|
||||
word = bv->b[j/32];
|
||||
checkword = check->b[j/32];
|
||||
if(word != checkword) {
|
||||
// Found a mismatched word, find
|
||||
// the mismatched bit.
|
||||
// Found a mismatched word; find the mismatched bit.
|
||||
for(pos = 0; pos < 32; pos++) {
|
||||
bit = 1 << pos;
|
||||
if((word & bit) && !(checkword & bit)) {
|
||||
@ -1516,26 +1837,40 @@ printprog(Prog *p)
|
||||
void
|
||||
liveness(Node *fn, Prog *firstp, Sym *argssym, Sym *livesym, Sym *deadsym)
|
||||
{
|
||||
Array *cfg;
|
||||
Array *vars;
|
||||
Array *cfg, *vars;
|
||||
Liveness *lv;
|
||||
int debugdelta;
|
||||
|
||||
if(0) print("curfn->nname->sym->name is %s\n", curfn->nname->sym->name);
|
||||
if(0) printprog(firstp);
|
||||
// Change name to dump debugging information only for a specific function.
|
||||
debugdelta = 0;
|
||||
if(strcmp(curfn->nname->sym->name, "!") == 0)
|
||||
debugdelta = 2;
|
||||
|
||||
debuglive += debugdelta;
|
||||
if(debuglive >= 3) {
|
||||
print("liveness: %s\n", curfn->nname->sym->name);
|
||||
printprog(firstp);
|
||||
}
|
||||
checkptxt(fn, firstp);
|
||||
|
||||
// Construct the global liveness state.
|
||||
cfg = newcfg(firstp);
|
||||
if(0) printcfg(cfg);
|
||||
if(debuglive >= 3)
|
||||
printcfg(cfg);
|
||||
vars = getvariables(fn, deadsym != nil);
|
||||
lv = newliveness(fn, firstp, cfg, vars, deadsym != nil);
|
||||
|
||||
// Run the dataflow framework.
|
||||
livenessprologue(lv);
|
||||
if(0) livenessprintcfg(lv);
|
||||
if(debuglive >= 3)
|
||||
livenessprintcfg(lv);
|
||||
livenesssolve(lv);
|
||||
if(0) livenessprintcfg(lv);
|
||||
if(debuglive >= 3)
|
||||
livenessprintcfg(lv);
|
||||
livenessepilogue(lv);
|
||||
|
||||
if(debuglive >= 2)
|
||||
livenessprintdebug(lv);
|
||||
|
||||
// Emit the live pointer map data structures
|
||||
twobitwritesymbol(lv->livepointers, livesym, nil);
|
||||
@ -1549,4 +1884,6 @@ liveness(Node *fn, Prog *firstp, Sym *argssym, Sym *livesym, Sym *deadsym)
|
||||
freeliveness(lv);
|
||||
arrayfree(vars);
|
||||
freecfg(cfg);
|
||||
|
||||
debuglive -= debugdelta;
|
||||
}
|
||||
|
81
test/live.go
Normal file
81
test/live.go
Normal file
@ -0,0 +1,81 @@
|
||||
// errorcheck -0 -l -live
|
||||
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
func f1() {
|
||||
var x *int
|
||||
print(&x) // ERROR "live at call to printpointer: x$"
|
||||
print(&x) // ERROR "live at call to printpointer: x$"
|
||||
}
|
||||
|
||||
func f2(b bool) {
|
||||
if b {
|
||||
print(0) // nothing live here
|
||||
return
|
||||
}
|
||||
var x *int
|
||||
print(&x) // ERROR "live at call to printpointer: x$"
|
||||
print(&x) // ERROR "live at call to printpointer: x$"
|
||||
}
|
||||
|
||||
func f3(b bool) {
|
||||
print(0)
|
||||
if b == false {
|
||||
print(0) // nothing live here
|
||||
return
|
||||
}
|
||||
|
||||
if b {
|
||||
var x *int
|
||||
print(&x) // ERROR "live at call to printpointer: x$"
|
||||
print(&x) // ERROR "live at call to printpointer: x$"
|
||||
} else {
|
||||
var y *int
|
||||
print(&y) // ERROR "live at call to printpointer: y$"
|
||||
print(&y) // ERROR "live at call to printpointer: y$"
|
||||
}
|
||||
print(0) // ERROR "live at call to printint: x y$"
|
||||
}
|
||||
|
||||
// The old algorithm treated x as live on all code that
|
||||
// could flow to a return statement, so it included the
|
||||
// function entry and code above the declaration of x
|
||||
// but would not include an indirect use of x in an infinite loop.
|
||||
// Check that these cases are handled correctly.
|
||||
|
||||
func f4(b1, b2 bool) { // x not live here
|
||||
if b2 {
|
||||
print(0) // x not live here
|
||||
return
|
||||
}
|
||||
var z **int
|
||||
x := new(int)
|
||||
*x = 42
|
||||
z = &x
|
||||
print(**z) // ERROR "live at call to printint: x z$"
|
||||
if b2 {
|
||||
print(1) // ERROR "live at call to printint: x$"
|
||||
return
|
||||
}
|
||||
for {
|
||||
print(**z) // ERROR "live at call to printint: x z$"
|
||||
}
|
||||
}
|
||||
|
||||
func f5(b1 bool) {
|
||||
var z **int
|
||||
if b1 {
|
||||
x := new(int)
|
||||
*x = 42
|
||||
z = &x
|
||||
} else {
|
||||
y := new(int)
|
||||
*y = 54
|
||||
z = &y
|
||||
}
|
||||
print(**z) // ERROR "live at call to printint: x y$"
|
||||
}
|
Loading…
Reference in New Issue
Block a user