mirror of
https://github.com/golang/go
synced 2024-11-02 13:21:55 +00:00
cmd/gc, runtime: make GODEBUG=gcdead=1 mode work with liveness
Trying to make GODEBUG=gcdead=1 work with liveness and in particular ambiguously live variables. 1. In the liveness computation, mark all ambiguously live variables as live for the entire function, except the entry. They are zeroed directly after entry, and we need them not to be poisoned thereafter. 2. In the liveness computation, compute liveness (and deadness) for all parameters, not just pointer-containing parameters. Otherwise gcdead poisons untracked scalar parameters and results. 3. Fix liveness debugging print for -live=2 to use correct bitmaps. (Was not updated for compaction during compaction CL.) 4. Correct varkill during map literal initialization. Was killing the map itself instead of the inserted value temp. 5. Disable aggressive varkill cleanup for call arguments if the call appears in a defer or go statement. 6. In the garbage collector, avoid bug scanning empty strings. An empty string is two zeros. The multiword code only looked at the first zero and then interpreted the next two bits in the bitmap as an ordinary word bitmap. For a string the bits are 11 00, so if a live string was zero length with a 0 base pointer, the poisoning code treated the length as an ordinary word with code 00, meaning it needed poisoning, turning the string into a poison-length string with base pointer 0. By the same logic I believe that a live nil slice (bits 11 01 00) will have its cap poisoned. Always scan full multiword struct. 7. In the runtime, treat both poison words (PoisonGC and PoisonStack) as invalid pointers that warrant crashes. Manual testing as follows: - Create a script called gcdead on your PATH containing: #!/bin/bash GODEBUG=gcdead=1 GOGC=10 GOTRACEBACK=2 exec "$@" - Now you can build a test and then run 'gcdead ./foo.test'. - More importantly, you can run 'go test -short -exec gcdead std' to run all the tests. Fixes #7676. While here, enable the precise scanning of slices, since that was disabled due to bugs like these. That now works, both with and without gcdead. Fixes #7549. LGTM=khr R=khr CC=golang-codereviews https://golang.org/cl/83410044
This commit is contained in:
parent
ebe5f203bf
commit
28f1868fed
7 changed files with 135 additions and 74 deletions
|
@ -185,12 +185,12 @@ visitcode(Node *n, uint32 min)
|
||||||
typedef struct EscState EscState;
|
typedef struct EscState EscState;
|
||||||
|
|
||||||
static void escfunc(EscState*, Node *func);
|
static void escfunc(EscState*, Node *func);
|
||||||
static void esclist(EscState*, NodeList *l);
|
static void esclist(EscState*, NodeList *l, Node *up);
|
||||||
static void esc(EscState*, Node *n);
|
static void esc(EscState*, Node *n, Node *up);
|
||||||
static void escloopdepthlist(EscState*, NodeList *l);
|
static void escloopdepthlist(EscState*, NodeList *l);
|
||||||
static void escloopdepth(EscState*, Node *n);
|
static void escloopdepth(EscState*, Node *n);
|
||||||
static void escassign(EscState*, Node *dst, Node *src);
|
static void escassign(EscState*, Node *dst, Node *src);
|
||||||
static void esccall(EscState*, Node*);
|
static void esccall(EscState*, Node*, Node *up);
|
||||||
static void escflows(EscState*, Node *dst, Node *src);
|
static void escflows(EscState*, Node *dst, Node *src);
|
||||||
static void escflood(EscState*, Node *dst);
|
static void escflood(EscState*, Node *dst);
|
||||||
static void escwalk(EscState*, int level, Node *dst, Node *src);
|
static void escwalk(EscState*, int level, Node *dst, Node *src);
|
||||||
|
@ -347,7 +347,7 @@ escfunc(EscState *e, Node *func)
|
||||||
escflows(e, &e->theSink, ll->n);
|
escflows(e, &e->theSink, ll->n);
|
||||||
|
|
||||||
escloopdepthlist(e, curfn->nbody);
|
escloopdepthlist(e, curfn->nbody);
|
||||||
esclist(e, curfn->nbody);
|
esclist(e, curfn->nbody, curfn);
|
||||||
curfn = savefn;
|
curfn = savefn;
|
||||||
e->loopdepth = saveld;
|
e->loopdepth = saveld;
|
||||||
}
|
}
|
||||||
|
@ -405,14 +405,14 @@ escloopdepth(EscState *e, Node *n)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
esclist(EscState *e, NodeList *l)
|
esclist(EscState *e, NodeList *l, Node *up)
|
||||||
{
|
{
|
||||||
for(; l; l=l->next)
|
for(; l; l=l->next)
|
||||||
esc(e, l->n);
|
esc(e, l->n, up);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
esc(EscState *e, Node *n)
|
esc(EscState *e, Node *n, Node *up)
|
||||||
{
|
{
|
||||||
int lno;
|
int lno;
|
||||||
NodeList *ll, *lr;
|
NodeList *ll, *lr;
|
||||||
|
@ -424,19 +424,19 @@ esc(EscState *e, Node *n)
|
||||||
lno = setlineno(n);
|
lno = setlineno(n);
|
||||||
|
|
||||||
// ninit logically runs at a different loopdepth than the rest of the for loop.
|
// ninit logically runs at a different loopdepth than the rest of the for loop.
|
||||||
esclist(e, n->ninit);
|
esclist(e, n->ninit, n);
|
||||||
|
|
||||||
if(n->op == OFOR || n->op == ORANGE)
|
if(n->op == OFOR || n->op == ORANGE)
|
||||||
e->loopdepth++;
|
e->loopdepth++;
|
||||||
|
|
||||||
esc(e, n->left);
|
esc(e, n->left, n);
|
||||||
esc(e, n->right);
|
esc(e, n->right, n);
|
||||||
esc(e, n->ntest);
|
esc(e, n->ntest, n);
|
||||||
esc(e, n->nincr);
|
esc(e, n->nincr, n);
|
||||||
esclist(e, n->nbody);
|
esclist(e, n->nbody, n);
|
||||||
esclist(e, n->nelse);
|
esclist(e, n->nelse, n);
|
||||||
esclist(e, n->list);
|
esclist(e, n->list, n);
|
||||||
esclist(e, n->rlist);
|
esclist(e, n->rlist, n);
|
||||||
|
|
||||||
if(n->op == OFOR || n->op == ORANGE)
|
if(n->op == OFOR || n->op == ORANGE)
|
||||||
e->loopdepth--;
|
e->loopdepth--;
|
||||||
|
@ -522,7 +522,7 @@ esc(EscState *e, Node *n)
|
||||||
case OCALLMETH:
|
case OCALLMETH:
|
||||||
case OCALLFUNC:
|
case OCALLFUNC:
|
||||||
case OCALLINTER:
|
case OCALLINTER:
|
||||||
esccall(e, n);
|
esccall(e, n, up);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OAS2FUNC: // x,y = f()
|
case OAS2FUNC: // x,y = f()
|
||||||
|
@ -843,7 +843,7 @@ escassignfromtag(EscState *e, Strlit *note, NodeList *dsts, Node *src)
|
||||||
// different for methods vs plain functions and for imported vs
|
// different for methods vs plain functions and for imported vs
|
||||||
// this-package
|
// this-package
|
||||||
static void
|
static void
|
||||||
esccall(EscState *e, Node *n)
|
esccall(EscState *e, Node *n, Node *up)
|
||||||
{
|
{
|
||||||
NodeList *ll, *lr;
|
NodeList *ll, *lr;
|
||||||
Node *a, *fn, *src;
|
Node *a, *fn, *src;
|
||||||
|
@ -965,7 +965,7 @@ esccall(EscState *e, Node *n)
|
||||||
n->right = src;
|
n->right = src;
|
||||||
}
|
}
|
||||||
if(haspointers(t->type)) {
|
if(haspointers(t->type)) {
|
||||||
if(escassignfromtag(e, t->note, n->escretval, src) == EscNone) {
|
if(escassignfromtag(e, t->note, n->escretval, src) == EscNone && up->op != ODEFER && up->op != OPROC) {
|
||||||
a = src;
|
a = src;
|
||||||
while(a->op == OCONVNOP)
|
while(a->op == OCONVNOP)
|
||||||
a = a->left;
|
a = a->left;
|
||||||
|
|
|
@ -69,6 +69,9 @@ struct BasicBlock {
|
||||||
// State to denote whether the block has been visited during a
|
// State to denote whether the block has been visited during a
|
||||||
// traversal.
|
// traversal.
|
||||||
int mark;
|
int mark;
|
||||||
|
|
||||||
|
// For use during livenessepilogue.
|
||||||
|
int lastbitmapindex;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A collection of global state used by liveness analysis.
|
// A collection of global state used by liveness analysis.
|
||||||
|
@ -273,11 +276,20 @@ getvariables(Node *fn)
|
||||||
result = arraynew(0, sizeof(Node*));
|
result = arraynew(0, sizeof(Node*));
|
||||||
for(ll = fn->dcl; ll != nil; ll = ll->next) {
|
for(ll = fn->dcl; ll != nil; ll = ll->next) {
|
||||||
if(ll->n->op == ONAME) {
|
if(ll->n->op == ONAME) {
|
||||||
|
// In order for GODEBUG=gcdead=1 to work, each bitmap needs
|
||||||
|
// to contain information about all variables covered by the bitmap.
|
||||||
|
// For local variables, the bitmap only covers the stkptrsize
|
||||||
|
// bytes in the frame where variables containing pointers live.
|
||||||
|
// For arguments and results, the bitmap covers all variables,
|
||||||
|
// so we must include all the variables, even the ones without
|
||||||
|
// pointers.
|
||||||
switch(ll->n->class) {
|
switch(ll->n->class) {
|
||||||
case PAUTO:
|
case PAUTO:
|
||||||
|
if(haspointers(ll->n->type))
|
||||||
|
arrayadd(result, &ll->n);
|
||||||
|
break;
|
||||||
case PPARAM:
|
case PPARAM:
|
||||||
case PPARAMOUT:
|
case PPARAMOUT:
|
||||||
if(haspointers(ll->n->type))
|
|
||||||
arrayadd(result, &ll->n);
|
arrayadd(result, &ll->n);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1101,18 +1113,9 @@ twobitwalktype1(Type *t, vlong *xoffset, Bvec *bv)
|
||||||
// struct { byte *array; uintgo len; uintgo cap; }
|
// struct { byte *array; uintgo len; uintgo cap; }
|
||||||
if((*xoffset & (widthptr-1)) != 0)
|
if((*xoffset & (widthptr-1)) != 0)
|
||||||
fatal("twobitwalktype1: invalid TARRAY alignment, %T", t);
|
fatal("twobitwalktype1: invalid TARRAY alignment, %T", t);
|
||||||
if(0) {
|
|
||||||
bvset(bv, (*xoffset / widthptr) * BitsPerPointer + 0);
|
bvset(bv, (*xoffset / widthptr) * BitsPerPointer + 0);
|
||||||
bvset(bv, (*xoffset / widthptr) * BitsPerPointer + 1);
|
bvset(bv, (*xoffset / widthptr) * BitsPerPointer + 1);
|
||||||
bvset(bv, (*xoffset / widthptr) * BitsPerPointer + 2); // 3:1 = multiword/slice
|
bvset(bv, (*xoffset / widthptr) * BitsPerPointer + 2); // 3:1 = multiword/slice
|
||||||
} else {
|
|
||||||
// Until bug 7564 is fixed, we consider a slice as
|
|
||||||
// a separate pointer and integer.
|
|
||||||
bvset(bv, (*xoffset / widthptr) * BitsPerPointer + 1); // 2 = live ptr
|
|
||||||
bvset(bv, (*xoffset / widthptr) * BitsPerPointer + 2); // 1 = live scalar
|
|
||||||
}
|
|
||||||
// mark capacity as live
|
|
||||||
bvset(bv, (*xoffset / widthptr) * BitsPerPointer + 4); // 1 = live scalar
|
|
||||||
*xoffset += t->width;
|
*xoffset += t->width;
|
||||||
} else
|
} else
|
||||||
for(i = 0; i < t->bound; i++)
|
for(i = 0; i < t->bound; i++)
|
||||||
|
@ -1412,10 +1415,11 @@ static void
|
||||||
livenessepilogue(Liveness *lv)
|
livenessepilogue(Liveness *lv)
|
||||||
{
|
{
|
||||||
BasicBlock *bb, *pred;
|
BasicBlock *bb, *pred;
|
||||||
Bvec *livein, *liveout, *uevar, *varkill, *args, *locals, *avarinit, *any, *all;
|
Bvec *ambig, *livein, *liveout, *uevar, *varkill, *args, *locals, *avarinit, *any, *all;
|
||||||
Node *n;
|
Node *n;
|
||||||
Prog *p, *next;
|
Prog *p, *next;
|
||||||
int32 i, j, numlive, startmsg, nmsg, nvars, pos;
|
int32 i, j, numlive, startmsg, nmsg, nvars, pos;
|
||||||
|
int64 xoffset;
|
||||||
char **msg;
|
char **msg;
|
||||||
Fmt fmt;
|
Fmt fmt;
|
||||||
|
|
||||||
|
@ -1427,6 +1431,7 @@ livenessepilogue(Liveness *lv)
|
||||||
avarinit = bvalloc(nvars);
|
avarinit = bvalloc(nvars);
|
||||||
any = bvalloc(nvars);
|
any = bvalloc(nvars);
|
||||||
all = bvalloc(nvars);
|
all = bvalloc(nvars);
|
||||||
|
ambig = bvalloc(localswords() * BitsPerPointer);
|
||||||
msg = nil;
|
msg = nil;
|
||||||
nmsg = 0;
|
nmsg = 0;
|
||||||
startmsg = 0;
|
startmsg = 0;
|
||||||
|
@ -1471,14 +1476,17 @@ livenessepilogue(Liveness *lv)
|
||||||
bvandnot(liveout, any, all);
|
bvandnot(liveout, any, all);
|
||||||
if(!bvisempty(liveout)) {
|
if(!bvisempty(liveout)) {
|
||||||
for(pos = 0; pos < liveout->n; pos++) {
|
for(pos = 0; pos < liveout->n; pos++) {
|
||||||
bvset(all, pos); // silence future warnings in this block
|
|
||||||
if(!bvget(liveout, pos))
|
if(!bvget(liveout, pos))
|
||||||
continue;
|
continue;
|
||||||
|
bvset(all, pos); // silence future warnings in this block
|
||||||
n = *(Node**)arrayget(lv->vars, pos);
|
n = *(Node**)arrayget(lv->vars, pos);
|
||||||
if(!n->needzero) {
|
if(!n->needzero) {
|
||||||
n->needzero = 1;
|
n->needzero = 1;
|
||||||
if(debuglive >= 1)
|
if(debuglive >= 1)
|
||||||
warnl(p->lineno, "%N: %lN is ambiguously live", curfn->nname, n);
|
warnl(p->lineno, "%N: %lN is ambiguously live", curfn->nname, n);
|
||||||
|
// Record in 'ambiguous' bitmap.
|
||||||
|
xoffset = n->xoffset + stkptrsize;
|
||||||
|
twobitwalktype1(n->type, &xoffset, ambig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1509,6 +1517,11 @@ livenessepilogue(Liveness *lv)
|
||||||
if(p == bb->last)
|
if(p == bb->last)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
bb->lastbitmapindex = arraylength(lv->livepointers) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < arraylength(lv->cfg); i++) {
|
||||||
|
bb = *(BasicBlock**)arrayget(lv->cfg, i);
|
||||||
|
|
||||||
if(debuglive >= 1 && strcmp(curfn->nname->sym->name, "init") != 0 && curfn->nname->sym->name[0] != '.') {
|
if(debuglive >= 1 && strcmp(curfn->nname->sym->name, "init") != 0 && curfn->nname->sym->name[0] != '.') {
|
||||||
nmsg = arraylength(lv->livepointers);
|
nmsg = arraylength(lv->livepointers);
|
||||||
|
@ -1519,7 +1532,7 @@ livenessepilogue(Liveness *lv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// walk backward, emit pcdata and populate the maps
|
// walk backward, emit pcdata and populate the maps
|
||||||
pos = arraylength(lv->livepointers) - 1;
|
pos = bb->lastbitmapindex;
|
||||||
if(pos < 0) {
|
if(pos < 0) {
|
||||||
// the first block we encounter should have the ATEXT so
|
// the first block we encounter should have the ATEXT so
|
||||||
// at no point should pos ever be less than zero.
|
// at no point should pos ever be less than zero.
|
||||||
|
@ -1563,6 +1576,12 @@ livenessepilogue(Liveness *lv)
|
||||||
locals = *(Bvec**)arrayget(lv->livepointers, pos);
|
locals = *(Bvec**)arrayget(lv->livepointers, pos);
|
||||||
twobitlivepointermap(lv, liveout, lv->vars, args, locals);
|
twobitlivepointermap(lv, liveout, lv->vars, args, locals);
|
||||||
|
|
||||||
|
// Ambiguously live variables are zeroed immediately after
|
||||||
|
// function entry. Mark them live for all the non-entry bitmaps
|
||||||
|
// so that GODEBUG=gcdead=1 mode does not poison them.
|
||||||
|
if(p->as == ACALL)
|
||||||
|
bvor(locals, locals, ambig);
|
||||||
|
|
||||||
// Show live pointer bitmaps.
|
// Show live pointer bitmaps.
|
||||||
// We're interpreting the args and locals bitmap instead of liveout so that we
|
// We're interpreting the args and locals bitmap instead of liveout so that we
|
||||||
// include the bits added by the avarinit logic in the
|
// include the bits added by the avarinit logic in the
|
||||||
|
@ -1628,6 +1647,7 @@ livenessepilogue(Liveness *lv)
|
||||||
free(avarinit);
|
free(avarinit);
|
||||||
free(any);
|
free(any);
|
||||||
free(all);
|
free(all);
|
||||||
|
free(ambig);
|
||||||
|
|
||||||
flusherrors();
|
flusherrors();
|
||||||
}
|
}
|
||||||
|
@ -1772,7 +1792,7 @@ printbitset(int printed, char *name, Array *vars, Bvec *bits)
|
||||||
static void
|
static void
|
||||||
livenessprintdebug(Liveness *lv)
|
livenessprintdebug(Liveness *lv)
|
||||||
{
|
{
|
||||||
int i, j, printed, nsafe;
|
int i, j, pcdata, printed;
|
||||||
BasicBlock *bb;
|
BasicBlock *bb;
|
||||||
Prog *p;
|
Prog *p;
|
||||||
Bvec *uevar, *varkill, *avarinit, *args, *locals;
|
Bvec *uevar, *varkill, *avarinit, *args, *locals;
|
||||||
|
@ -1784,7 +1804,7 @@ livenessprintdebug(Liveness *lv)
|
||||||
varkill = bvalloc(arraylength(lv->vars));
|
varkill = bvalloc(arraylength(lv->vars));
|
||||||
avarinit = bvalloc(arraylength(lv->vars));
|
avarinit = bvalloc(arraylength(lv->vars));
|
||||||
|
|
||||||
nsafe = 0;
|
pcdata = 0;
|
||||||
for(i = 0; i < arraylength(lv->cfg); i++) {
|
for(i = 0; i < arraylength(lv->cfg); i++) {
|
||||||
if(i > 0)
|
if(i > 0)
|
||||||
print("\n");
|
print("\n");
|
||||||
|
@ -1815,6 +1835,8 @@ livenessprintdebug(Liveness *lv)
|
||||||
// program listing, with individual effects listed
|
// program listing, with individual effects listed
|
||||||
for(p = bb->first;; p = p->link) {
|
for(p = bb->first;; p = p->link) {
|
||||||
print("%P\n", p);
|
print("%P\n", p);
|
||||||
|
if(p->as == APCDATA && p->from.offset == PCDATA_StackMapIndex)
|
||||||
|
pcdata = p->to.offset;
|
||||||
progeffects(p, lv->vars, uevar, varkill, avarinit);
|
progeffects(p, lv->vars, uevar, varkill, avarinit);
|
||||||
printed = 0;
|
printed = 0;
|
||||||
printed = printbitset(printed, "uevar", lv->vars, uevar);
|
printed = printbitset(printed, "uevar", lv->vars, uevar);
|
||||||
|
@ -1823,9 +1845,8 @@ livenessprintdebug(Liveness *lv)
|
||||||
if(printed)
|
if(printed)
|
||||||
print("\n");
|
print("\n");
|
||||||
if(issafepoint(p)) {
|
if(issafepoint(p)) {
|
||||||
args = *(Bvec**)arrayget(lv->argslivepointers, nsafe);
|
args = *(Bvec**)arrayget(lv->argslivepointers, pcdata);
|
||||||
locals = *(Bvec**)arrayget(lv->livepointers, nsafe);
|
locals = *(Bvec**)arrayget(lv->livepointers, pcdata);
|
||||||
nsafe++;
|
|
||||||
print("\tlive=");
|
print("\tlive=");
|
||||||
printed = 0;
|
printed = 0;
|
||||||
for(j = 0; j < arraylength(lv->vars); j++) {
|
for(j = 0; j < arraylength(lv->vars); j++) {
|
||||||
|
|
|
@ -1014,7 +1014,7 @@ ctxt = 0;
|
||||||
a = nod(OVARKILL, key, N);
|
a = nod(OVARKILL, key, N);
|
||||||
typecheck(&a, Etop);
|
typecheck(&a, Etop);
|
||||||
*init = list(*init, a);
|
*init = list(*init, a);
|
||||||
a = nod(OVARKILL, var, N);
|
a = nod(OVARKILL, val, N);
|
||||||
typecheck(&a, Etop);
|
typecheck(&a, Etop);
|
||||||
*init = list(*init, a);
|
*init = list(*init, a);
|
||||||
}
|
}
|
||||||
|
|
|
@ -637,4 +637,5 @@ void runtime·memorydump(void);
|
||||||
int32 runtime·setgcpercent(int32);
|
int32 runtime·setgcpercent(int32);
|
||||||
|
|
||||||
// Value we use to mark dead pointers when GODEBUG=gcdead=1.
|
// Value we use to mark dead pointers when GODEBUG=gcdead=1.
|
||||||
#define PoisonPtr ((uintptr)0x6969696969696969LL)
|
#define PoisonGC ((uintptr)0xf969696969696969ULL)
|
||||||
|
#define PoisonStack ((uintptr)0x6868686868686868ULL)
|
||||||
|
|
|
@ -1455,14 +1455,14 @@ scanbitvector(Func *f, bool precise, byte *scanp, BitVector *bv, bool afterprolo
|
||||||
switch(bits) {
|
switch(bits) {
|
||||||
case BitsDead:
|
case BitsDead:
|
||||||
if(runtime·debug.gcdead)
|
if(runtime·debug.gcdead)
|
||||||
*(uintptr*)scanp = PoisonPtr;
|
*(uintptr*)scanp = PoisonGC;
|
||||||
break;
|
break;
|
||||||
case BitsScalar:
|
case BitsScalar:
|
||||||
break;
|
break;
|
||||||
case BitsPointer:
|
case BitsPointer:
|
||||||
p = *(byte**)scanp;
|
p = *(byte**)scanp;
|
||||||
if(p != nil) {
|
if(p != nil) {
|
||||||
if(precise && (p < (byte*)PageSize || (uintptr)p == PoisonPtr)) {
|
if(precise && (p < (byte*)PageSize || (uintptr)p == PoisonGC || (uintptr)p == PoisonStack)) {
|
||||||
// Looks like a junk value in a pointer slot.
|
// Looks like a junk value in a pointer slot.
|
||||||
// Liveness analysis wrong?
|
// Liveness analysis wrong?
|
||||||
m->traceback = 2;
|
m->traceback = 2;
|
||||||
|
@ -1473,8 +1473,7 @@ scanbitvector(Func *f, bool precise, byte *scanp, BitVector *bv, bool afterprolo
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BitsMultiWord:
|
case BitsMultiWord:
|
||||||
p = *(byte**)scanp;
|
p = scanp;
|
||||||
if(p != nil) {
|
|
||||||
word >>= BitsPerPointer;
|
word >>= BitsPerPointer;
|
||||||
scanp += PtrSize;
|
scanp += PtrSize;
|
||||||
i--;
|
i--;
|
||||||
|
@ -1490,25 +1489,38 @@ scanbitvector(Func *f, bool precise, byte *scanp, BitVector *bv, bool afterprolo
|
||||||
}
|
}
|
||||||
switch(word & 3) {
|
switch(word & 3) {
|
||||||
case BitsString:
|
case BitsString:
|
||||||
if(((String*)(scanp - PtrSize))->len != 0)
|
if(((String*)p)->len != 0)
|
||||||
markonly(p);
|
markonly(((String*)p)->str);
|
||||||
break;
|
break;
|
||||||
case BitsSlice:
|
case BitsSlice:
|
||||||
if(((Slice*)(scanp - PtrSize))->cap < ((Slice*)(scanp - PtrSize))->len) {
|
word >>= BitsPerPointer;
|
||||||
|
scanp += PtrSize;
|
||||||
|
i--;
|
||||||
|
if(i == 0) {
|
||||||
|
// Get next chunk of bits
|
||||||
|
remptrs -= 32;
|
||||||
|
word = *wordp++;
|
||||||
|
if(remptrs < 32)
|
||||||
|
i = remptrs;
|
||||||
|
else
|
||||||
|
i = 32;
|
||||||
|
i /= BitsPerPointer;
|
||||||
|
}
|
||||||
|
if(((Slice*)p)->cap < ((Slice*)p)->len) {
|
||||||
m->traceback = 2;
|
m->traceback = 2;
|
||||||
runtime·printf("bad slice in frame %s at %p: %p/%p/%p\n", runtime·funcname(f), scanp, ((byte**)scanp)[0], ((byte**)scanp)[1], ((byte**)scanp)[2]);
|
runtime·printf("bad slice in frame %s at %p: %p/%p/%p\n", runtime·funcname(f), p, ((byte**)p)[0], ((byte**)p)[1], ((byte**)p)[2]);
|
||||||
runtime·throw("slice capacity smaller than length");
|
runtime·throw("slice capacity smaller than length");
|
||||||
}
|
}
|
||||||
if(((Slice*)(scanp - PtrSize))->cap != 0)
|
if(((Slice*)p)->cap != 0)
|
||||||
enqueue1(wbufp, (Obj){scanp - PtrSize, PtrSize, 0});
|
enqueue1(wbufp, (Obj){p, PtrSize, 0});
|
||||||
break;
|
break;
|
||||||
case BitsIface:
|
case BitsIface:
|
||||||
case BitsEface:
|
case BitsEface:
|
||||||
scaninterfacedata(word & 3, scanp - PtrSize, afterprologue, wbufp);
|
if(*(byte**)p != nil)
|
||||||
|
scaninterfacedata(word & 3, p, afterprologue, wbufp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
word >>= BitsPerPointer;
|
word >>= BitsPerPointer;
|
||||||
scanp += PtrSize;
|
scanp += PtrSize;
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,17 +356,17 @@ adjustpointers(byte **scanp, BitVector *bv, AdjustInfo *adjinfo, Func *f)
|
||||||
switch(bv->data[i / (32 / BitsPerPointer)] >> (i * BitsPerPointer & 31) & 3) {
|
switch(bv->data[i / (32 / BitsPerPointer)] >> (i * BitsPerPointer & 31) & 3) {
|
||||||
case BitsDead:
|
case BitsDead:
|
||||||
if(runtime·debug.gcdead)
|
if(runtime·debug.gcdead)
|
||||||
scanp[i] = (byte*)0x6868686868686868LL;
|
scanp[i] = (byte*)PoisonStack;
|
||||||
break;
|
break;
|
||||||
case BitsScalar:
|
case BitsScalar:
|
||||||
break;
|
break;
|
||||||
case BitsPointer:
|
case BitsPointer:
|
||||||
p = scanp[i];
|
p = scanp[i];
|
||||||
if(f != nil && (byte*)0 < p && (p < (byte*)PageSize || (uintptr)p == PoisonPtr)) {
|
if(f != nil && (byte*)0 < p && (p < (byte*)PageSize || (uintptr)p == PoisonGC || (uintptr)p == PoisonStack)) {
|
||||||
// Looks like a junk value in a pointer slot.
|
// Looks like a junk value in a pointer slot.
|
||||||
// Live analysis wrong?
|
// Live analysis wrong?
|
||||||
m->traceback = 2;
|
m->traceback = 2;
|
||||||
runtime·printf("%p: %p %s\n", &scanp[i], p, runtime·funcname(f));
|
runtime·printf("runtime: bad pointer in frame %s at %p: %p\n", runtime·funcname(f), &scanp[i], p);
|
||||||
runtime·throw("bad pointer!");
|
runtime·throw("bad pointer!");
|
||||||
}
|
}
|
||||||
if(minp <= p && p < maxp) {
|
if(minp <= p && p < maxp) {
|
||||||
|
|
39
test/live.go
39
test/live.go
|
@ -23,20 +23,24 @@ func f2(b bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func f3(b bool) {
|
func f3(b bool) {
|
||||||
print(0)
|
// Because x and y are ambiguously live, they appear
|
||||||
|
// live throughout the function, to avoid being poisoned
|
||||||
|
// in GODEBUG=gcdead=1 mode.
|
||||||
|
|
||||||
|
print(0) // ERROR "live at call to printint: x y$"
|
||||||
if b == false {
|
if b == false {
|
||||||
print(0) // nothing live here
|
print(0) // ERROR "live at call to printint: x y$"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if b {
|
if b {
|
||||||
var x *int
|
var x *int
|
||||||
print(&x) // ERROR "live at call to printpointer: x$"
|
print(&x) // ERROR "live at call to printpointer: x y$"
|
||||||
print(&x) // ERROR "live at call to printpointer: x$"
|
print(&x) // ERROR "live at call to printpointer: x y$"
|
||||||
} else {
|
} else {
|
||||||
var y *int
|
var y *int
|
||||||
print(&y) // ERROR "live at call to printpointer: y$"
|
print(&y) // ERROR "live at call to printpointer: x y$"
|
||||||
print(&y) // ERROR "live at call to printpointer: y$"
|
print(&y) // ERROR "live at call to printpointer: x y$"
|
||||||
}
|
}
|
||||||
print(0) // ERROR "live at call to printint: x y$" "x \(type \*int\) is ambiguously live" "y \(type \*int\) is ambiguously live"
|
print(0) // ERROR "live at call to printint: x y$" "x \(type \*int\) is ambiguously live" "y \(type \*int\) is ambiguously live"
|
||||||
}
|
}
|
||||||
|
@ -371,6 +375,29 @@ func f27(b bool) {
|
||||||
}
|
}
|
||||||
call27(func() {x++}) // ERROR "live at call to call27: autotmp_[0-9]+$"
|
call27(func() {x++}) // ERROR "live at call to call27: autotmp_[0-9]+$"
|
||||||
call27(func() {x++}) // ERROR "live at call to call27: autotmp_[0-9]+$"
|
call27(func() {x++}) // ERROR "live at call to call27: autotmp_[0-9]+$"
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
// but defer does escape to later execution in the function
|
||||||
|
|
||||||
|
func f27defer(b bool) {
|
||||||
|
x := 0
|
||||||
|
if b {
|
||||||
|
defer call27(func() {x++}) // ERROR "live at call to deferproc: autotmp_[0-9]+$" "live at call to deferreturn: autotmp_[0-9]+$"
|
||||||
|
}
|
||||||
|
defer call27(func() {x++}) // ERROR "live at call to deferproc: autotmp_[0-9]+ autotmp_[0-9]+$" "live at call to deferreturn: autotmp_[0-9]+ autotmp_[0-9]+$" "ambiguously live"
|
||||||
|
println() // ERROR "live at call to printnl: autotmp_[0-9]+ autotmp_[0-9]+$"
|
||||||
|
} // ERROR "live at call to deferreturn: autotmp_[0-9]+ autotmp_[0-9]+$"
|
||||||
|
|
||||||
|
// and newproc (go) escapes to the heap
|
||||||
|
|
||||||
|
func f27go(b bool) {
|
||||||
|
x := 0
|
||||||
|
if b {
|
||||||
|
go call27(func() {x++}) // ERROR "live at call to new: &x" "live at call to newproc: &x$"
|
||||||
|
}
|
||||||
|
go call27(func() {x++}) // ERROR "live at call to new: &x"
|
||||||
|
println()
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:noescape
|
//go:noescape
|
||||||
|
|
Loading…
Reference in a new issue