From 9a8372f8bd5a39d2476bfa9247407b51f9193b9e Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Fri, 7 Sep 2018 14:55:09 -0700 Subject: [PATCH] cmd/compile,runtime: remove ambiguously live logic The previous CL introduced stack objects. This CL removes the old ambiguously live liveness analysis. After this CL we're relying on stack objects exclusively. Update a bunch of liveness tests to reflect the new world. Fixes #22350 Change-Id: I739b26e015882231011ce6bc1a7f426049e59f31 Reviewed-on: https://go-review.googlesource.com/c/134156 Reviewed-by: Austin Clements Reviewed-by: Cherry Zhang --- src/cmd/compile/internal/gc/op_string.go | 4 +- src/cmd/compile/internal/gc/plive.go | 235 ++++-------------- src/cmd/compile/internal/gc/sinit.go | 6 + src/cmd/compile/internal/gc/ssa.go | 32 +-- src/cmd/compile/internal/gc/syntax.go | 1 + .../compile/internal/ssa/gen/genericOps.go | 5 +- src/runtime/chan.go | 5 + src/runtime/gcinfo_test.go | 48 +++- src/runtime/mbitmap.go | 9 +- test/fixedbugs/issue20250.go | 8 +- test/live.go | 175 ++++++------- test/live2.go | 15 +- test/live_syscall.go | 8 +- test/stackobj.go | 57 +++++ test/stackobj3.go | 93 +++++++ test/uintptrescapes2.go | 8 +- 16 files changed, 375 insertions(+), 334 deletions(-) create mode 100644 test/stackobj.go create mode 100644 test/stackobj3.go diff --git a/src/cmd/compile/internal/gc/op_string.go b/src/cmd/compile/internal/gc/op_string.go index 857234e45d..90b95d769f 100644 --- a/src/cmd/compile/internal/gc/op_string.go +++ b/src/cmd/compile/internal/gc/op_string.go @@ -4,9 +4,9 @@ package gc import "strconv" -const _Op_name = "XXXNAMENONAMETYPEPACKLITERALADDSUBORXORADDSTRADDRANDANDAPPENDARRAYBYTESTRARRAYBYTESTRTMPARRAYRUNESTRSTRARRAYBYTESTRARRAYBYTETMPSTRARRAYRUNEASAS2AS2FUNCAS2RECVAS2MAPRAS2DOTTYPEASOPCALLCALLFUNCCALLMETHCALLINTERCALLPARTCAPCLOSECLOSURECMPIFACECMPSTRCOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVNOPCOPYDCLDCLFUNCDCLFIELDDCLCONSTDCLTYPEDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTINDINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMULDIVMODLSHRSHANDANDNOTNEWNOTCOMPLUSMINUSORORPANICPRINTPRINTNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRRECOVERRECVRUNESTRSELRECVSELRECV2IOTAREALIMAGCOMPLEXALIGNOFOFFSETOFSIZEOFBLOCKBREAKCASEXCASECONTINUEDEFEREMPTYFALLFORFORUNTILGOTOIFLABELPROCRANGERETURNSELECTSWITCHTYPESWTCHANTMAPTSTRUCTTINTERTFUNCTARRAYDDDDDDARGINLCALLEFACEITABIDATASPTRCLOSUREVARCFUNCCHECKNILVARKILLVARLIVEINDREGSPRETJMPGETGEND" +const _Op_name = "XXXNAMENONAMETYPEPACKLITERALADDSUBORXORADDSTRADDRANDANDAPPENDARRAYBYTESTRARRAYBYTESTRTMPARRAYRUNESTRSTRARRAYBYTESTRARRAYBYTETMPSTRARRAYRUNEASAS2AS2FUNCAS2RECVAS2MAPRAS2DOTTYPEASOPCALLCALLFUNCCALLMETHCALLINTERCALLPARTCAPCLOSECLOSURECMPIFACECMPSTRCOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVNOPCOPYDCLDCLFUNCDCLFIELDDCLCONSTDCLTYPEDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTINDINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMULDIVMODLSHRSHANDANDNOTNEWNOTCOMPLUSMINUSORORPANICPRINTPRINTNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRRECOVERRECVRUNESTRSELRECVSELRECV2IOTAREALIMAGCOMPLEXALIGNOFOFFSETOFSIZEOFBLOCKBREAKCASEXCASECONTINUEDEFEREMPTYFALLFORFORUNTILGOTOIFLABELPROCRANGERETURNSELECTSWITCHTYPESWTCHANTMAPTSTRUCTTINTERTFUNCTARRAYDDDDDDARGINLCALLEFACEITABIDATASPTRCLOSUREVARCFUNCCHECKNILVARDEFVARKILLVARLIVEINDREGSPRETJMPGETGEND" -var _Op_index = [...]uint16{0, 3, 7, 13, 17, 21, 28, 31, 34, 36, 39, 45, 49, 55, 61, 73, 88, 100, 112, 127, 139, 141, 144, 151, 158, 165, 175, 179, 183, 191, 199, 208, 216, 219, 224, 231, 239, 245, 252, 258, 267, 275, 283, 289, 293, 302, 309, 313, 316, 323, 331, 339, 346, 352, 355, 361, 368, 376, 380, 387, 395, 397, 399, 401, 403, 405, 407, 410, 415, 423, 426, 435, 438, 442, 450, 457, 466, 469, 472, 475, 478, 481, 484, 490, 493, 496, 499, 503, 508, 512, 517, 522, 528, 533, 537, 542, 550, 558, 564, 573, 580, 584, 591, 598, 606, 610, 614, 618, 625, 632, 640, 646, 651, 656, 660, 665, 673, 678, 683, 687, 690, 698, 702, 704, 709, 713, 718, 724, 730, 736, 742, 747, 751, 758, 764, 769, 775, 778, 784, 791, 796, 800, 805, 809, 819, 824, 832, 839, 846, 854, 860, 864, 867} +var _Op_index = [...]uint16{0, 3, 7, 13, 17, 21, 28, 31, 34, 36, 39, 45, 49, 55, 61, 73, 88, 100, 112, 127, 139, 141, 144, 151, 158, 165, 175, 179, 183, 191, 199, 208, 216, 219, 224, 231, 239, 245, 252, 258, 267, 275, 283, 289, 293, 302, 309, 313, 316, 323, 331, 339, 346, 352, 355, 361, 368, 376, 380, 387, 395, 397, 399, 401, 403, 405, 407, 410, 415, 423, 426, 435, 438, 442, 450, 457, 466, 469, 472, 475, 478, 481, 484, 490, 493, 496, 499, 503, 508, 512, 517, 522, 528, 533, 537, 542, 550, 558, 564, 573, 580, 584, 591, 598, 606, 610, 614, 618, 625, 632, 640, 646, 651, 656, 660, 665, 673, 678, 683, 687, 690, 698, 702, 704, 709, 713, 718, 724, 730, 736, 742, 747, 751, 758, 764, 769, 775, 778, 784, 791, 796, 800, 805, 809, 819, 824, 832, 838, 845, 852, 860, 866, 870, 873} func (i Op) String() string { if i >= Op(len(_Op_index)-1) { diff --git a/src/cmd/compile/internal/gc/plive.go b/src/cmd/compile/internal/gc/plive.go index e070a5cd1a..563b425db7 100644 --- a/src/cmd/compile/internal/gc/plive.go +++ b/src/cmd/compile/internal/gc/plive.go @@ -78,6 +78,10 @@ import ( // that its argument is certainly dead, for use when the liveness analysis // would not otherwise be able to deduce that fact. +// TODO: get rid of OpVarKill here. It's useful for stack frame allocation +// so the compiler can allocate two temps to the same location. Here it's now +// useless, since the implementation of stack objects. + // BlockEffects summarizes the liveness effects on an SSA block. type BlockEffects struct { // Computed during Liveness.prologue using only the content of @@ -85,23 +89,15 @@ type BlockEffects struct { // // uevar: upward exposed variables (used before set in block) // varkill: killed variables (set in block) - // avarinit: addrtaken variables set or used (proof of initialization) - uevar varRegVec - varkill varRegVec - avarinit bvec + uevar varRegVec + varkill varRegVec // Computed during Liveness.solve 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) - livein varRegVec - liveout varRegVec - avarinitany bvec - avarinitall bvec + livein varRegVec + liveout varRegVec } // A collection of global state used by liveness analysis. @@ -186,10 +182,9 @@ func (idx LivenessIndex) Valid() bool { } type progeffectscache struct { - textavarinit []int32 - retuevar []int32 - tailuevar []int32 - initialized bool + retuevar []int32 + tailuevar []int32 + initialized bool } // varRegVec contains liveness bitmaps for variables and registers. @@ -264,24 +259,13 @@ func (lv *Liveness) initcache() { // all the parameters for correctness, and similarly it must not // read the out arguments - they won't be set until the new // function runs. - lv.cache.tailuevar = append(lv.cache.tailuevar, int32(i)) - if node.Addrtaken() { - lv.cache.textavarinit = append(lv.cache.textavarinit, int32(i)) - } - case PPARAMOUT: - // If the result had its address taken, it is being tracked - // by the avarinit code, which does not use uevar. - // If we added it to uevar too, we'd not see any kill - // and decide that the variable was live entry, which it is not. - // So only use uevar in the non-addrtaken case. - // The p.to.type == obj.TYPE_NONE limits the bvset to - // non-tail-call return instructions; see note below for details. - if !node.Addrtaken() { - lv.cache.retuevar = append(lv.cache.retuevar, int32(i)) - } + // All results are live at every return point. + // Note that this point is after escaping return values + // are copied back to the stack using their PAUTOHEAP references. + lv.cache.retuevar = append(lv.cache.retuevar, int32(i)) } } } @@ -291,21 +275,13 @@ func (lv *Liveness) initcache() { // // The possible flags are: // uevar - used by the instruction -// varkill - killed by the instruction -// for variables without address taken, means variable was set -// for variables with address taken, means variable was marked dead -// avarinit - initialized or referred to by the 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. +// varkill - killed by the instruction (set) +// A kill happens after the use (for an instruction that updates a value, for example). type liveEffect int const ( uevar liveEffect = 1 << iota varkill - avarinit ) // valueEffects returns the index of a variable in lv.vars and the @@ -329,27 +305,15 @@ func (lv *Liveness) valueEffects(v *ssa.Value) (int32, liveEffect) { } var effect liveEffect - if n.Addrtaken() { - if v.Op != ssa.OpVarKill { - effect |= avarinit - } - if v.Op == ssa.OpVarDef || v.Op == ssa.OpVarKill { - effect |= varkill - } - } else { - // Read is a read, obviously. - // Addr by itself is also implicitly a read. - // - // Addr|Write means that the address is being taken - // but only so that the instruction can write to the value. - // It is not a read. - - if e&ssa.SymRead != 0 || e&(ssa.SymAddr|ssa.SymWrite) == ssa.SymAddr { - effect |= uevar - } - if e&ssa.SymWrite != 0 && (!isfat(n.Type) || v.Op == ssa.OpVarDef) { - effect |= varkill - } + // Read is a read, obviously. + // + // Addr is a read also, as any subseqent holder of the pointer must be able + // to see all the values (including initialization) written so far. + if e&(ssa.SymRead|ssa.SymAddr) != 0 { + effect |= uevar + } + if e&ssa.SymWrite != 0 && (!isfat(n.Type) || v.Op == ssa.OpVarDef) { + effect |= varkill } if effect == 0 { @@ -545,9 +509,6 @@ func newliveness(fn *Node, f *ssa.Func, vars []*Node, idx map[*Node]int32, stkpt be.varkill = varRegVec{vars: bulk.next()} be.livein = varRegVec{vars: bulk.next()} be.liveout = varRegVec{vars: bulk.next()} - be.avarinit = bulk.next() - be.avarinitany = bulk.next() - be.avarinitall = bulk.next() } lv.livenessMap.reset(lv.f.NumValues()) @@ -869,19 +830,6 @@ func (lv *Liveness) prologue() { } be.uevar.regs |= regUevar } - - // Walk the block instructions forward to update avarinit bits. - // avarinit describes the effect at the end of the block, not the beginning. - for _, val := range b.Values { - pos, e := lv.valueEffects(val) - // No need for regEffects because registers never appear in avarinit. - if e&varkill != 0 { - be.avarinit.Unset(pos) - } - if e&avarinit != 0 { - be.avarinit.Set(pos) - } - } } } @@ -892,51 +840,10 @@ func (lv *Liveness) solve() { nvars := int32(len(lv.vars)) newlivein := varRegVec{vars: bvalloc(nvars)} newliveout := varRegVec{vars: bvalloc(nvars)} - any := bvalloc(nvars) - all := bvalloc(nvars) - // 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 _, b := range lv.f.Blocks { - be := lv.blockEffects(b) - if b == lv.f.Entry { - be.avarinitall.Copy(be.avarinit) - } else { - be.avarinitall.Clear() - be.avarinitall.Not() - } - be.avarinitany.Copy(be.avarinit) - } - - // Walk blocks in the general direction of propagation (RPO - // for avarinit{any,all}, and PO for live{in,out}). This - // improves convergence. + // Walk blocks in postorder ordering. This improves convergence. po := lv.f.Postorder() - for change := true; change; { - change = false - for i := len(po) - 1; i >= 0; i-- { - b := po[i] - be := lv.blockEffects(b) - lv.avarinitanyall(b, any, all) - - any.AndNot(any, be.varkill.vars) - all.AndNot(all, be.varkill.vars) - any.Or(any, be.avarinit) - all.Or(all, be.avarinit) - if !any.Eq(be.avarinitany) { - change = true - be.avarinitany.Copy(any) - } - - if !all.Eq(be.avarinitall) { - change = true - be.avarinitall.Copy(all) - } - } - } - // Iterate through the blocks in reverse round-robin fashion. A work // queue might be slightly faster. As is, the number of iterations is // so low that it hardly seems to be worth the complexity. @@ -957,7 +864,7 @@ func (lv *Liveness) solve() { newliveout.vars.Set(pos) } case ssa.BlockExit: - // nothing to do + // panic exit - nothing to do default: // A variable is live on output from this block // if it is live on input to some successor. @@ -975,7 +882,7 @@ func (lv *Liveness) solve() { } // A variable is live on input to this block - // if it is live on output from this block and + // if it is used by this block, or 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]) @@ -990,8 +897,6 @@ func (lv *Liveness) solve() { func (lv *Liveness) epilogue() { nvars := int32(len(lv.vars)) liveout := varRegVec{vars: bvalloc(nvars)} - any := bvalloc(nvars) - all := bvalloc(nvars) livedefer := bvalloc(nvars) // always-live variables // If there is a defer (that could recover), then all output @@ -1017,6 +922,9 @@ func (lv *Liveness) epilogue() { livedefer.Set(int32(i)) } if n.IsOutputParamHeapAddr() { + // This variable will be overwritten early in the function + // prologue (from the result of a mallocgc) but we need to + // zero it in case that malloc causes a stack scan. n.Name.SetNeedzero(true) livedefer.Set(int32(i)) } @@ -1033,9 +941,6 @@ func (lv *Liveness) epilogue() { { // Reserve an entry for function entry. live := bvalloc(nvars) - for _, pos := range lv.cache.textavarinit { - live.Set(pos) - } lv.livevars = append(lv.livevars, varRegVec{vars: live}) } @@ -1043,53 +948,14 @@ func (lv *Liveness) epilogue() { be := lv.blockEffects(b) firstBitmapIndex := len(lv.livevars) - // Compute avarinitany and avarinitall for entry to block. - // This duplicates information known during Liveness.solve - // but avoids storing two more vectors for each block. - lv.avarinitanyall(b, any, all) - // 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 _, v := range b.Values { - pos, e := lv.valueEffects(v) - // No need for regEffects because registers never appear in avarinit. - if e&varkill != 0 { - any.Unset(pos) - all.Unset(pos) - } - if e&avarinit != 0 { - any.Set(pos) - all.Set(pos) - } - if !lv.issafepoint(v) { continue } - // Annotate ambiguously live variables so that they can - // be zeroed at function entry and at VARKILL points. - // liveout is dead here and used as a temporary. - liveout.vars.AndNot(any, all) - if !liveout.vars.IsEmpty() { - for pos := int32(0); pos < liveout.vars.n; pos++ { - if !liveout.vars.Get(pos) { - continue - } - all.Set(pos) // silence future warnings in this block - n := lv.vars[pos] - if !n.Name.Needzero() { - n.Name.SetNeedzero(true) - if debuglive >= 1 { - Warnl(v.Pos, "%v: %L is ambiguously live", lv.fn.Func.Nname, n) - } - } - } - } - - // Live stuff first. live := bvalloc(nvars) - live.Copy(any) lv.livevars = append(lv.livevars, varRegVec{vars: live}) } @@ -1128,6 +994,17 @@ func (lv *Liveness) epilogue() { Fatalf("bad index for entry point: %v", index) } + // Check to make sure only input variables are live. + for i, n := range lv.vars { + if !liveout.vars.Get(int32(i)) { + continue + } + if n.Class() == PPARAM { + continue // ok + } + Fatalf("bad live variable at entry of %v: %L", lv.fn.Func.Nname, n) + } + // Record live variables. live := &lv.livevars[index] live.Or(*live, liveout) @@ -1330,28 +1207,6 @@ func clobberPtr(b *ssa.Block, v *Node, offset int64) { b.NewValue0IA(src.NoXPos, ssa.OpClobber, types.TypeVoid, offset, v) } -func (lv *Liveness) avarinitanyall(b *ssa.Block, any, all bvec) { - if len(b.Preds) == 0 { - any.Clear() - all.Clear() - for _, pos := range lv.cache.textavarinit { - any.Set(pos) - all.Set(pos) - } - return - } - - be := lv.blockEffects(b.Preds[0].Block()) - any.Copy(be.avarinitany) - all.Copy(be.avarinitall) - - for _, pred := range b.Preds[1:] { - be := lv.blockEffects(pred.Block()) - any.Or(any, be.avarinitany) - all.And(all, be.avarinitall) - } -} - // Compact coalesces identical bitmaps from lv.livevars into the sets // lv.stackMapSet and lv.regMaps. // @@ -1559,7 +1414,6 @@ func (lv *Liveness) printDebug() { printed = false printed = lv.printeffect(printed, "uevar", pos, effect&uevar != 0, regUevar) printed = lv.printeffect(printed, "varkill", pos, effect&varkill != 0, regKill) - printed = lv.printeffect(printed, "avarinit", pos, effect&avarinit != 0, 0) if printed { fmt.Printf("\n") } @@ -1596,9 +1450,6 @@ func (lv *Liveness) printDebug() { printed = false printed = lv.printbvec(printed, "varkill", be.varkill) printed = lv.printbvec(printed, "liveout", be.liveout) - printed = lv.printbvec(printed, "avarinit", varRegVec{vars: be.avarinit}) - printed = lv.printbvec(printed, "avarinitany", varRegVec{vars: be.avarinitany}) - printed = lv.printbvec(printed, "avarinitall", varRegVec{vars: be.avarinitall}) if printed { fmt.Printf("\n") } diff --git a/src/cmd/compile/internal/gc/sinit.go b/src/cmd/compile/internal/gc/sinit.go index f76b02828f..9d1114fa43 100644 --- a/src/cmd/compile/internal/gc/sinit.go +++ b/src/cmd/compile/internal/gc/sinit.go @@ -839,6 +839,10 @@ func slicelit(ctxt initContext, n *Node, var_ *Node, init *Nodes) { a = nod(OAS, x, nil) a = typecheck(a, Etop) init.Append(a) // zero new temp + } else { + // Declare that we're about to initialize all of x. + // (Which happens at the *vauto = vstat below.) + init.Append(nod(OVARDEF, x, nil)) } a = nod(OADDR, x, nil) @@ -849,6 +853,8 @@ func slicelit(ctxt initContext, n *Node, var_ *Node, init *Nodes) { a = typecheck(a, Etop) init.Append(a) // zero new temp a = a.Left + } else { + init.Append(nod(OVARDEF, a, nil)) } a = nod(OADDR, a, nil) diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 663042754e..138ce08fec 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -755,8 +755,8 @@ func (s *state) stmtList(l Nodes) { // stmt converts the statement n to SSA and adds it to s. func (s *state) stmt(n *Node) { - if !(n.Op == OVARKILL || n.Op == OVARLIVE) { - // OVARKILL and OVARLIVE are invisible to the programmer, so we don't use their line numbers to avoid confusion in debugging. + if !(n.Op == OVARKILL || n.Op == OVARLIVE || n.Op == OVARDEF) { + // OVARKILL, OVARLIVE, and OVARDEF are invisible to the programmer, so we don't use their line numbers to avoid confusion in debugging. s.pushLine(n.Pos) defer s.popLine() } @@ -1170,6 +1170,10 @@ func (s *state) stmt(n *Node) { } s.startBlock(bEnd) + case OVARDEF: + if !s.canSSA(n.Left) { + s.vars[&memVar] = s.newValue1Apos(ssa.OpVarDef, types.TypeMem, n.Left, s.mem(), false) + } case OVARKILL: // Insert a varkill op to record that a variable is no longer live. // We only care about liveness info at call sites, so putting the @@ -4977,6 +4981,12 @@ func emitStackObjects(e *ssafn, pp *Progs) { p.To.Type = obj.TYPE_MEM p.To.Name = obj.NAME_EXTERN p.To.Sym = x + + if debuglive != 0 { + for _, v := range vars { + Warnl(v.Pos, "stack object %v %s", v, v.Type.String()) + } + } } // genssa appends entries to pp for each instruction in f. @@ -5056,24 +5066,8 @@ func genssa(f *ssa.Func, pp *Progs) { case ssa.OpGetG: // nothing to do when there's a g register, // and checkLower complains if there's not - case ssa.OpVarDef, ssa.OpVarLive, ssa.OpKeepAlive: + case ssa.OpVarDef, ssa.OpVarLive, ssa.OpKeepAlive, ssa.OpVarKill: // nothing to do; already used by liveness - case ssa.OpVarKill: - // Zero variable if it is ambiguously live. - // After the VARKILL anything this variable references - // might be collected. If it were to become live again later, - // the GC will see references to already-collected objects. - // See issue 20029. - n := v.Aux.(*Node) - if n.Name.Needzero() { - if n.Class() != PAUTO { - v.Fatalf("zero of variable which isn't PAUTO %v", n) - } - if n.Type.Size()%int64(Widthptr) != 0 { - v.Fatalf("zero of variable not a multiple of ptr size %v", n) - } - thearch.ZeroAuto(s.pp, n) - } case ssa.OpPhi: CheckLoweredPhi(v) case ssa.OpConvert: diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index df23b83f29..eb2ab6b916 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -739,6 +739,7 @@ const ( OCLOSUREVAR // variable reference at beginning of closure function OCFUNC // reference to c function pointer (not go func value) OCHECKNIL // emit code to ensure pointer/interface not nil + OVARDEF // variable is about to be fully initialized OVARKILL // variable is dead OVARLIVE // variable is alive OINDREGSP // offset plus indirect of REGSP, such as 8(SP). diff --git a/src/cmd/compile/internal/ssa/gen/genericOps.go b/src/cmd/compile/internal/ssa/gen/genericOps.go index 7292012d26..ee9c6fa0f6 100644 --- a/src/cmd/compile/internal/ssa/gen/genericOps.go +++ b/src/cmd/compile/internal/ssa/gen/genericOps.go @@ -470,8 +470,9 @@ var genericOps = []opData{ {name: "VarDef", argLength: 1, aux: "Sym", typ: "Mem", symEffect: "None", zeroWidth: true}, // aux is a *gc.Node of a variable that is about to be initialized. arg0=mem, returns mem {name: "VarKill", argLength: 1, aux: "Sym", symEffect: "None"}, // aux is a *gc.Node of a variable that is known to be dead. arg0=mem, returns mem - {name: "VarLive", argLength: 1, aux: "Sym", symEffect: "Read", zeroWidth: true}, // aux is a *gc.Node of a variable that must be kept live. arg0=mem, returns mem - {name: "KeepAlive", argLength: 2, typ: "Mem", zeroWidth: true}, // arg[0] is a value that must be kept alive until this mark. arg[1]=mem, returns mem + // TODO: what's the difference betweeen VarLive and KeepAlive? + {name: "VarLive", argLength: 1, aux: "Sym", symEffect: "Read", zeroWidth: true}, // aux is a *gc.Node of a variable that must be kept live. arg0=mem, returns mem + {name: "KeepAlive", argLength: 2, typ: "Mem", zeroWidth: true}, // arg[0] is a value that must be kept alive until this mark. arg[1]=mem, returns mem // Ops for breaking 64-bit operations on 32-bit architectures {name: "Int64Make", argLength: 2, typ: "UInt64"}, // arg0=hi, arg1=lo diff --git a/src/runtime/chan.go b/src/runtime/chan.go index a4ee51ca39..5cf0b86f58 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -232,6 +232,11 @@ func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { gp.param = nil c.sendq.enqueue(mysg) goparkunlock(&c.lock, waitReasonChanSend, traceEvGoBlockSend, 3) + // Ensure the value being sent is kept alive until the + // receiver copies it out. The sudog has a pointer to the + // stack object, but sudogs aren't considered as roots of the + // stack tracer. + KeepAlive(ep) // someone woke us up. if mysg != gp.waiting { diff --git a/src/runtime/gcinfo_test.go b/src/runtime/gcinfo_test.go index 7dd1a5607c..0741f6361c 100644 --- a/src/runtime/gcinfo_test.go +++ b/src/runtime/gcinfo_test.go @@ -35,14 +35,46 @@ func TestGCInfo(t *testing.T) { verifyGCInfo(t, "data eface", &dataEface, infoEface) verifyGCInfo(t, "data iface", &dataIface, infoIface) - verifyGCInfo(t, "stack Ptr", new(Ptr), infoPtr) - verifyGCInfo(t, "stack ScalarPtr", new(ScalarPtr), infoScalarPtr) - verifyGCInfo(t, "stack PtrScalar", new(PtrScalar), infoPtrScalar) - verifyGCInfo(t, "stack BigStruct", new(BigStruct), infoBigStruct()) - verifyGCInfo(t, "stack string", new(string), infoString) - verifyGCInfo(t, "stack slice", new([]string), infoSlice) - verifyGCInfo(t, "stack eface", new(interface{}), infoEface) - verifyGCInfo(t, "stack iface", new(Iface), infoIface) + { + var x Ptr + verifyGCInfo(t, "stack Ptr", &x, infoPtr) + runtime.KeepAlive(x) + } + { + var x ScalarPtr + verifyGCInfo(t, "stack ScalarPtr", &x, infoScalarPtr) + runtime.KeepAlive(x) + } + { + var x PtrScalar + verifyGCInfo(t, "stack PtrScalar", &x, infoPtrScalar) + runtime.KeepAlive(x) + } + { + var x BigStruct + verifyGCInfo(t, "stack BigStruct", &x, infoBigStruct()) + runtime.KeepAlive(x) + } + { + var x string + verifyGCInfo(t, "stack string", &x, infoString) + runtime.KeepAlive(x) + } + { + var x []string + verifyGCInfo(t, "stack slice", &x, infoSlice) + runtime.KeepAlive(x) + } + { + var x interface{} + verifyGCInfo(t, "stack eface", &x, infoEface) + runtime.KeepAlive(x) + } + { + var x Iface + verifyGCInfo(t, "stack iface", &x, infoIface) + runtime.KeepAlive(x) + } for i := 0; i < 10; i++ { verifyGCInfo(t, "heap Ptr", escape(new(Ptr)), trimDead(padDead(infoPtr))) diff --git a/src/runtime/mbitmap.go b/src/runtime/mbitmap.go index 5301e692e0..87fa027b4e 100644 --- a/src/runtime/mbitmap.go +++ b/src/runtime/mbitmap.go @@ -1994,7 +1994,9 @@ func reflect_gcbits(x interface{}) []byte { return ret } -// Returns GC type info for object p for testing. +// Returns GC type info for the pointer stored in ep for testing. +// If ep points to the stack, only static live information will be returned +// (i.e. not for objects which are only dynamically live stack objects). func getgcmask(ep interface{}) (mask []byte) { e := *efaceOf(&ep) p := e.data @@ -2051,11 +2053,6 @@ func getgcmask(ep interface{}) (mask []byte) { _g_ := getg() gentraceback(_g_.m.curg.sched.pc, _g_.m.curg.sched.sp, 0, _g_.m.curg, 0, nil, 1000, getgcmaskcb, noescape(unsafe.Pointer(&frame)), 0) if frame.fn.valid() { - // TODO: once stack objects are enabled (and their pointers - // are no longer described by the stack pointermap directly), - // tests using this will probably need fixing. We might need - // to loop through the stackobjects and if we're inside one, - // use the pointermap from that object. locals, _, _ := getStackMap(&frame, nil, false) if locals.n == 0 { return diff --git a/test/fixedbugs/issue20250.go b/test/fixedbugs/issue20250.go index 6fc861a8dc..47879385d2 100644 --- a/test/fixedbugs/issue20250.go +++ b/test/fixedbugs/issue20250.go @@ -15,10 +15,10 @@ type T struct { } func f(a T) { // ERROR "live at entry to f: a" - var e interface{} - func() { // ERROR "live at entry to f.func1: a &e" - e = a.s // ERROR "live at call to convT2Estring: a &e" - }() // ERROR "live at call to f.func1: e$" + var e interface{} // ERROR "stack object e interface \{\}$" + func() { // ERROR "live at entry to f.func1: a &e" + e = a.s // ERROR "live at call to convT2Estring: &e" "stack object a T$" + }() // Before the fix, both a and e were live at the previous line. _ = e } diff --git a/test/live.go b/test/live.go index 13bdc4aae1..679562d9bf 100644 --- a/test/live.go +++ b/test/live.go @@ -32,9 +32,9 @@ func printbytepointer(*byte) func printint(int) func f1() { - var x *int - printpointer(&x) // ERROR "live at call to printpointer: x$" + var x *int // ERROR "stack object x \*int$" printpointer(&x) // ERROR "live at call to printpointer: x$" + printpointer(&x) } func f2(b bool) { @@ -42,9 +42,9 @@ func f2(b bool) { printint(0) // nothing live here return } - var x *int - printpointer(&x) // ERROR "live at call to printpointer: x$" + var x *int // ERROR "stack object x \*int$" printpointer(&x) // ERROR "live at call to printpointer: x$" + printpointer(&x) } func f3(b1, b2 bool) { @@ -60,15 +60,15 @@ func f3(b1, b2 bool) { } if b2 { - var x *int - printpointer(&x) // ERROR "live at call to printpointer: x$" + var x *int // ERROR "stack object x \*int$" printpointer(&x) // ERROR "live at call to printpointer: x$" + printpointer(&x) } else { - var y *int - printpointer(&y) // ERROR "live at call to printpointer: y$" + var y *int // ERROR "stack object y \*int$" printpointer(&y) // ERROR "live at call to printpointer: y$" + printpointer(&y) } - printint(0) // ERROR "f3: x \(type \*int\) is ambiguously live$" "f3: y \(type \*int\) is ambiguously live$" "live at call to printint: x y$" + printint(0) // nothing is live here } // The old algorithm treated x as live on all code that @@ -83,7 +83,7 @@ func f4(b1, b2 bool) { // x not live here return } var z **int - x := new(int) + x := new(int) // ERROR "stack object x \*int$" *x = 42 z = &x printint(**z) // ERROR "live at call to printint: x$" @@ -99,15 +99,15 @@ func f4(b1, b2 bool) { // x not live here func f5(b1 bool) { var z **int if b1 { - x := new(int) + x := new(int) // ERROR "stack object x \*int$" *x = 42 z = &x } else { - y := new(int) + y := new(int) // ERROR "stack object y \*int$" *y = 54 z = &y } - printint(**z) // ERROR "f5: x \(type \*int\) is ambiguously live$" "f5: y \(type \*int\) is ambiguously live$" "live at call to printint: x y$" + printint(**z) // nothing live here } // confusion about the _ result used to cause spurious "live at entry to f6: _". @@ -119,7 +119,7 @@ func f6() (_, y string) { // confusion about addressed results used to cause "live at entry to f7: x". -func f7() (x string) { +func f7() (x string) { // ERROR "stack object x string" _ = &x x = "hello" return @@ -141,7 +141,7 @@ var i9 interface{} func f9() bool { g8() x := i9 - y := interface{}(str()) // ERROR "live at call to convT2Estring: .autotmp_[0-9]+ x.data$" "live at call to str: x.data$" + y := interface{}(str()) // ERROR "live at call to convT2Estring: x.data$" "live at call to str: x.data$" "stack object .autotmp_[0-9]+ string$" i9 = y // make y escape so the line above has to call convT2E return x != y } @@ -163,7 +163,7 @@ var b bool // this used to have a spurious "live at entry to f11a: ~r0" func f11a() *int { - select { // ERROR "live at call to selectgo: .autotmp_[0-9]+$" + select { // ERROR "stack object .autotmp_[0-9]+ \[2\]struct" case <-c: return nil case <-c: @@ -178,7 +178,7 @@ func f11b() *int { // get to the bottom of the function. // This used to have a spurious "live at call to printint: p". printint(1) // nothing live here! - select { // ERROR "live at call to selectgo: .autotmp_[0-9]+$" + select { // ERROR "stack object .autotmp_[0-9]+ \[2\]struct" case <-c: return nil case <-c: @@ -198,7 +198,7 @@ func f11c() *int { // Unlike previous, the cases in this select fall through, // so we can get to the println, so p is not dead. printint(1) // ERROR "live at call to printint: p$" - select { // ERROR "live at call to selectgo: .autotmp_[0-9]+ p$" + select { // ERROR "live at call to selectgo: p$" "stack object .autotmp_[0-9]+ \[2\]struct" case <-c: case <-c: } @@ -233,8 +233,8 @@ func h13(string, string) string // more incorrectly placed VARDEF. func f14() { - x := g14() - printstringpointer(&x) // ERROR "live at call to printstringpointer: x$" + x := g14() // ERROR "stack object x string$" + printstringpointer(&x) } func g14() string @@ -254,10 +254,10 @@ func iface() interface{} func f16() { if b { - delete(mi, iface()) // ERROR "live at call to mapdelete: .autotmp_[0-9]+$" + delete(mi, iface()) // ERROR "stack object .autotmp_[0-9]+ interface \{\}$" } - delete(mi, iface()) // ERROR "live at call to mapdelete: .autotmp_[0-9]+$" - delete(mi, iface()) // ERROR "live at call to mapdelete: .autotmp_[0-9]+$" + delete(mi, iface()) // ERROR "stack object .autotmp_[0-9]+ interface \{\}$" + delete(mi, iface()) // ERROR "stack object .autotmp_[0-9]+ interface \{\}$" } var m2s map[string]*byte @@ -300,10 +300,10 @@ func f18() { // temporary introduced by orderexpr. var z *byte if b { - z = m2[g18()] // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" + z = m2[g18()] // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" } - z = m2[g18()] // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" - z = m2[g18()] // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" + z = m2[g18()] // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" + z = m2[g18()] // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" printbytepointer(z) } @@ -317,30 +317,30 @@ func f19() { var z *byte if b { - z = <-ch // ERROR "live at call to chanrecv1: .autotmp_[0-9]+$" + z = <-ch // ERROR "stack object .autotmp_[0-9]+ \*byte$" } - z = <-ch // ERROR "live at call to chanrecv1: .autotmp_[0-9]+$" - z = <-ch // ERROR "live at call to chanrecv1: .autotmp_[0-9]+$" + z = <-ch // ERROR "stack object .autotmp_[0-9]+ \*byte$" + z = <-ch // ERROR "stack object .autotmp_[0-9]+ \*byte$" "live at call to chanrecv1: .autotmp_[0-9]+$" printbytepointer(z) } func f20() { // src temporary for channel send if b { - ch <- byteptr() // ERROR "live at call to chansend1: .autotmp_[0-9]+$" + ch <- byteptr() // ERROR "stack object .autotmp_[0-9]+ \*byte$" } - ch <- byteptr() // ERROR "live at call to chansend1: .autotmp_[0-9]+$" - ch <- byteptr() // ERROR "live at call to chansend1: .autotmp_[0-9]+$" + ch <- byteptr() // ERROR "stack object .autotmp_[0-9]+ \*byte$" + ch <- byteptr() // ERROR "stack object .autotmp_[0-9]+ \*byte$" } func f21() { // key temporary for mapaccess using array literal key. var z *byte if b { - z = m2[[2]string{"x", "y"}] // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" + z = m2[[2]string{"x", "y"}] // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" } - z = m2[[2]string{"x", "y"}] // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" - z = m2[[2]string{"x", "y"}] // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" + z = m2[[2]string{"x", "y"}] // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" + z = m2[[2]string{"x", "y"}] // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" printbytepointer(z) } @@ -349,10 +349,10 @@ func f23() { var z *byte var ok bool if b { - z, ok = m2[[2]string{"x", "y"}] // ERROR "live at call to mapaccess2: .autotmp_[0-9]+$" + z, ok = m2[[2]string{"x", "y"}] // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" } - z, ok = m2[[2]string{"x", "y"}] // ERROR "live at call to mapaccess2: .autotmp_[0-9]+$" - z, ok = m2[[2]string{"x", "y"}] // ERROR "live at call to mapaccess2: .autotmp_[0-9]+$" + z, ok = m2[[2]string{"x", "y"}] // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" + z, ok = m2[[2]string{"x", "y"}] // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" printbytepointer(z) print(ok) } @@ -361,10 +361,10 @@ func f24() { // key temporary for map access using array literal key. // value temporary too. if b { - m2[[2]string{"x", "y"}] = nil // ERROR "live at call to mapassign: .autotmp_[0-9]+$" + m2[[2]string{"x", "y"}] = nil // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" } - m2[[2]string{"x", "y"}] = nil // ERROR "live at call to mapassign: .autotmp_[0-9]+$" - m2[[2]string{"x", "y"}] = nil // ERROR "live at call to mapassign: .autotmp_[0-9]+$" + m2[[2]string{"x", "y"}] = nil // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" + m2[[2]string{"x", "y"}] = nil // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" } // defer should not cause spurious ambiguously live variables @@ -387,10 +387,10 @@ func g25() func f26(b bool) { if b { - print26((*int)(nil), (*int)(nil), (*int)(nil)) // ERROR "live at call to print26: .autotmp_[0-9]+$" + print26((*int)(nil), (*int)(nil), (*int)(nil)) // ERROR "stack object .autotmp_[0-9]+ \[3\]interface \{\}$" } - print26((*int)(nil), (*int)(nil), (*int)(nil)) // ERROR "live at call to print26: .autotmp_[0-9]+$" - print26((*int)(nil), (*int)(nil), (*int)(nil)) // ERROR "live at call to print26: .autotmp_[0-9]+$" + print26((*int)(nil), (*int)(nil), (*int)(nil)) // ERROR "stack object .autotmp_[0-9]+ \[3\]interface \{\}$" + print26((*int)(nil), (*int)(nil), (*int)(nil)) // ERROR "stack object .autotmp_[0-9]+ \[3\]interface \{\}$" printnl() } @@ -402,10 +402,10 @@ func print26(...interface{}) func f27(b bool) { x := 0 if b { - call27(func() { x++ }) // ERROR "live at call to call27: .autotmp_[0-9]+$" + call27(func() { x++ }) // ERROR "stack object .autotmp_[0-9]+ struct \{" } - 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 "stack object .autotmp_[0-9]+ struct \{" + call27(func() { x++ }) // ERROR "stack object .autotmp_[0-9]+ struct \{" printnl() } @@ -414,11 +414,11 @@ func f27(b bool) { 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 "stack object .autotmp_[0-9]+ struct \{" } - defer call27(func() { x++ }) // ERROR "f27defer: .autotmp_[0-9]+ \(type struct { F uintptr; x \*int }\) is ambiguously live$" "live at call to deferproc: .autotmp_[0-9]+ .autotmp_[0-9]+$" "live at call to deferreturn: .autotmp_[0-9]+ .autotmp_[0-9]+$" - printnl() // ERROR "live at call to printnl: .autotmp_[0-9]+ .autotmp_[0-9]+$" -} // ERROR "live at call to deferreturn: .autotmp_[0-9]+ .autotmp_[0-9]+$" + defer call27(func() { x++ }) // ERROR "stack object .autotmp_[0-9]+ struct \{" + printnl() +} // and newproc (go) escapes to the heap @@ -440,24 +440,24 @@ var s1, s2, s3, s4, s5, s6, s7, s8, s9, s10 string func f28(b bool) { if b { - printstring(s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10) // ERROR "live at call to concatstrings: .autotmp_[0-9]+$" "live at call to printstring: .autotmp_[0-9]+$" + printstring(s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10) // ERROR "stack object .autotmp_[0-9]+ \[10\]string$" } - printstring(s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10) // ERROR "live at call to concatstrings: .autotmp_[0-9]+$" "live at call to printstring: .autotmp_[0-9]+$" - printstring(s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10) // ERROR "live at call to concatstrings: .autotmp_[0-9]+$" "live at call to printstring: .autotmp_[0-9]+$" + printstring(s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10) // ERROR "stack object .autotmp_[0-9]+ \[10\]string$" + printstring(s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10) // ERROR "stack object .autotmp_[0-9]+ \[10\]string$" } // map iterator should die on end of range loop func f29(b bool) { if b { - for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" + for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ map.iter\[string\]int$" printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$" } } - for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" + for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ map.iter\[string\]int$" printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$" } - for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" + for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ map.iter\[string\]int$" printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$" } } @@ -473,20 +473,19 @@ type pstruct struct { } func f30(b bool) { - // two live temps during printintpointer(p): - // in the copy of p.intp and + // live temp during printintpointer(p): // the internal iterator pointer if a pointer to pstruct in pstructarr // can not be easily computed by strength reduction. if b { - for _, p := range pstructarr { - printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+ .autotmp_[0-9]+$" + for _, p := range pstructarr { // ERROR "stack object .autotmp_[0-9]+ \[10\]pstruct$" + printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+$" } } - for _, p := range pstructarr { - printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+ .autotmp_[0-9]+$" + for _, p := range pstructarr { // ERROR "stack object .autotmp_[0-9]+ \[10\]pstruct$" + printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+$" } - for _, p := range pstructarr { - printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+ .autotmp_[0-9]+$" + for _, p := range pstructarr { // ERROR "stack object .autotmp_[0-9]+ \[10\]pstruct$" + printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+$" } } @@ -494,13 +493,13 @@ func f30(b bool) { func f31(b1, b2, b3 bool) { if b1 { - g31(str()) // ERROR "live at call to convT2Estring: .autotmp_[0-9]+$" "live at call to g31: .autotmp_[0-9]+$" + g31(str()) // ERROR "stack object .autotmp_[0-9]+ string$" } if b2 { - h31(str()) // ERROR "live at call to convT2Estring: .autotmp_[0-9]+ .autotmp_[0-9]+$" "live at call to h31: .autotmp_[0-9]+$" "live at call to newobject: .autotmp_[0-9]+$" + h31(str()) // ERROR "live at call to convT2Estring: .autotmp_[0-9]+$" "live at call to newobject: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ string$" } if b3 { - panic(str()) // ERROR "live at call to convT2Estring: .autotmp_[0-9]+$" "live at call to gopanic: .autotmp_[0-9]+$" + panic(str()) // ERROR "stack object .autotmp_[0-9]+ string$" } print(b3) } @@ -520,10 +519,10 @@ var t32 T32 func f32(b bool) { if b { - call32(t32.Inc) // ERROR "live at call to call32: .autotmp_[0-9]+$" + call32(t32.Inc) // ERROR "stack object .autotmp_[0-9]+ struct \{" } - call32(t32.Inc) // ERROR "live at call to call32: .autotmp_[0-9]+$" - call32(t32.Inc) // ERROR "live at call to call32: .autotmp_[0-9]+$" + call32(t32.Inc) // ERROR "stack object .autotmp_[0-9]+ struct \{" + call32(t32.Inc) // ERROR "stack object .autotmp_[0-9]+ struct \{" } //go:noescape @@ -535,7 +534,7 @@ func call32(func()) var m33 map[interface{}]int func f33() { - if m33[byteptr()] == 0 { // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" + if m33[byteptr()] == 0 { // ERROR "stack object .autotmp_[0-9]+ interface \{\}$" printnl() return } else { @@ -545,7 +544,7 @@ func f33() { } func f34() { - if m33[byteptr()] == 0 { // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" + if m33[byteptr()] == 0 { // ERROR "stack object .autotmp_[0-9]+ interface \{\}$" printnl() return } @@ -553,7 +552,8 @@ func f34() { } func f35() { - if m33[byteptr()] == 0 && m33[byteptr()] == 0 { // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" "f35: .autotmp_[0-9]+ \(type interface \{\}\) is ambiguously live$" + if m33[byteptr()] == 0 && // ERROR "stack object .autotmp_[0-9]+ interface \{\}" + m33[byteptr()] == 0 { // ERROR "stack object .autotmp_[0-9]+ interface \{\}" printnl() return } @@ -561,7 +561,8 @@ func f35() { } func f36() { - if m33[byteptr()] == 0 || m33[byteptr()] == 0 { // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" "f36: .autotmp_[0-9]+ \(type interface \{\}\) is ambiguously live$" + if m33[byteptr()] == 0 || // ERROR "stack object .autotmp_[0-9]+ interface \{\}" + m33[byteptr()] == 0 { // ERROR "stack object .autotmp_[0-9]+ interface \{\}" printnl() return } @@ -569,7 +570,9 @@ func f36() { } func f37() { - if (m33[byteptr()] == 0 || m33[byteptr()] == 0) && m33[byteptr()] == 0 { // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" "f37: .autotmp_[0-9]+ \(type interface \{\}\) is ambiguously live$" + if (m33[byteptr()] == 0 || // ERROR "stack object .autotmp_[0-9]+ interface \{\}" + m33[byteptr()] == 0) && // ERROR "stack object .autotmp_[0-9]+ interface \{\}" + m33[byteptr()] == 0 { // ERROR "stack object .autotmp_[0-9]+ interface \{\}" printnl() return } @@ -589,14 +592,14 @@ func f38(b bool) { // we care that the println lines have no live variables // and therefore no output. if b { - select { // ERROR "live at call to selectgo:( .autotmp_[0-9]+)+$" + select { // ERROR "live at call to selectgo:( .autotmp_[0-9]+)+$" "stack object .autotmp_[0-9]+ \[4\]struct \{" case <-fc38(): printnl() - case fc38() <- *fi38(1): // ERROR "live at call to fc38:( .autotmp_[0-9]+)+$" "live at call to fi38:( .autotmp_[0-9]+)+$" + case fc38() <- *fi38(1): // ERROR "live at call to fc38:( .autotmp_[0-9]+)+$" "live at call to fi38:( .autotmp_[0-9]+)+$" "stack object .autotmp_[0-9]+ string$" printnl() - case *fi38(2) = <-fc38(): // ERROR "live at call to fc38:( .autotmp_[0-9]+)+$" "live at call to fi38:( .autotmp_[0-9]+)+$" + case *fi38(2) = <-fc38(): // ERROR "live at call to fc38:( .autotmp_[0-9]+)+$" "live at call to fi38:( .autotmp_[0-9]+)+$" "stack object .autotmp_[0-9]+ string$" printnl() - case *fi38(3), *fb38() = <-fc38(): // ERROR "live at call to fb38:( .autotmp_[0-9]+)+$" "live at call to fc38:( .autotmp_[0-9]+)+$" "live at call to fi38:( .autotmp_[0-9]+)+$" + case *fi38(3), *fb38() = <-fc38(): // ERROR "stack object .autotmp_[0-9]+ string$" "live at call to fc38:( .autotmp_[0-9]+)+$" "live at call to fi38:( .autotmp_[0-9]+)+$" printnl() } printnl() @@ -655,15 +658,17 @@ func bad40() { } func good40() { - ret := T40{} - ret.m = make(map[int]int) // ERROR "live at call to fastrand: .autotmp_[0-9]+ ret$" + ret := T40{} // ERROR "stack object ret T40$" + ret.m = make(map[int]int) // ERROR "live at call to fastrand: .autotmp_[0-9]+ ret$" "stack object .autotmp_[0-9]+ map.hdr\[int\]int$" t := &ret - printnl() // ERROR "live at call to printnl: .autotmp_[0-9]+ ret$" - useT40(t) // ERROR "live at call to useT40: .autotmp_[0-9]+ ret$" + printnl() // ERROR "live at call to printnl: ret$" + // Note: ret is live at the printnl because the compiler moves &ret + // from before the printnl to after. + useT40(t) } func ddd1(x, y *int) { // ERROR "live at entry to ddd1: x y$" - ddd2(x, y) // ERROR "live at call to ddd2: .autotmp_[0-9]+$" + ddd2(x, y) // ERROR "stack object .autotmp_[0-9]+ \[2\]\*int$" printnl() // Note: no .?autotmp live at printnl. See issue 16996. } diff --git a/test/live2.go b/test/live2.go index cc1b0b7acf..cea312f075 100644 --- a/test/live2.go +++ b/test/live2.go @@ -10,7 +10,6 @@ package main // issue 8142: lost 'addrtaken' bit on inlined variables. -// no inlining in this test, so just checking that non-inlined works. func printnl() @@ -28,15 +27,15 @@ func newT40() *T40 { } func bad40() { - t := newT40() // ERROR "live at call to makemap: .autotmp_[0-9]+ ret$" - printnl() // ERROR "live at call to printnl: .autotmp_[0-9]+ ret$" - useT40(t) // ERROR "live at call to useT40: .autotmp_[0-9]+ ret$" + t := newT40() // ERROR "live at call to makemap: ret$" "stack object ret T40$" "stack object .autotmp_[0-9]+ map.hdr\[int\]int$" + printnl() // ERROR "live at call to printnl: ret$" + useT40(t) } func good40() { - ret := T40{} - ret.m = make(map[int]int, 42) // ERROR "live at call to makemap: .autotmp_[0-9]+ ret$" + ret := T40{} // ERROR "stack object ret T40$" + ret.m = make(map[int]int, 42) // ERROR "live at call to makemap: ret$" "stack object .autotmp_[0-9]+ map.hdr\[int\]int$" t := &ret - printnl() // ERROR "live at call to printnl: .autotmp_[0-9]+ ret$" - useT40(t) // ERROR "live at call to useT40: .autotmp_[0-9]+ ret$" + printnl() // ERROR "live at call to printnl: ret$" + useT40(t) } diff --git a/test/live_syscall.go b/test/live_syscall.go index 65a161c028..b7b85bcabf 100644 --- a/test/live_syscall.go +++ b/test/live_syscall.go @@ -19,22 +19,22 @@ func f(uintptr) // ERROR "f assuming arg#1 is unsafe uintptr" func g() { var t int - f(uintptr(unsafe.Pointer(&t))) // ERROR "live at call to f: .?autotmp" "g &t does not escape" + f(uintptr(unsafe.Pointer(&t))) // ERROR "live at call to f: .?autotmp" "g &t does not escape" "stack object .autotmp_[0-9]+ unsafe.Pointer$" } func h() { var v int - syscall.Syscall(0, 1, uintptr(unsafe.Pointer(&v)), 2) // ERROR "live at call to Syscall: .?autotmp" "h &v does not escape" + syscall.Syscall(0, 1, uintptr(unsafe.Pointer(&v)), 2) // ERROR "live at call to Syscall: .?autotmp" "h &v does not escape" "stack object .autotmp_[0-9]+ unsafe.Pointer$" } func i() { var t int p := unsafe.Pointer(&t) // ERROR "i &t does not escape" - f(uintptr(p)) // ERROR "live at call to f: .?autotmp" + f(uintptr(p)) // ERROR "live at call to f: .?autotmp" "stack object .autotmp_[0-9]+ unsafe.Pointer$" } func j() { var v int p := unsafe.Pointer(&v) // ERROR "j &v does not escape" - syscall.Syscall(0, 1, uintptr(p), 2) // ERROR "live at call to Syscall: .?autotmp" + syscall.Syscall(0, 1, uintptr(p), 2) // ERROR "live at call to Syscall: .?autotmp" "stack object .autotmp_[0-9]+ unsafe.Pointer$" } diff --git a/test/stackobj.go b/test/stackobj.go new file mode 100644 index 0000000000..b6af4bd8f3 --- /dev/null +++ b/test/stackobj.go @@ -0,0 +1,57 @@ +// run + +// Copyright 2018 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 + +import ( + "fmt" + "runtime" +) + +type HeapObj [8]int64 + +type StkObj struct { + h *HeapObj +} + +var n int +var c int = -1 + +func gc() { + // encourage heap object to be collected, and have its finalizer run. + runtime.GC() + runtime.GC() + runtime.GC() + n++ +} + +func main() { + f() + gc() // prior to stack objects, heap object is not collected until here + if c < 0 { + panic("heap object never collected") + } + if c != 1 { + panic(fmt.Sprintf("expected collection at phase 1, got phase %d", c)) + } +} + +func f() { + var s StkObj + s.h = new(HeapObj) + runtime.SetFinalizer(s.h, func(h *HeapObj) { + // Remember at what phase the heap object was collected. + c = n + }) + g(&s) + gc() +} + +func g(s *StkObj) { + gc() // heap object is still live here + runtime.KeepAlive(s) + gc() // heap object should be collected here +} diff --git a/test/stackobj3.go b/test/stackobj3.go new file mode 100644 index 0000000000..8bb8fb3270 --- /dev/null +++ b/test/stackobj3.go @@ -0,0 +1,93 @@ +// run + +// Copyright 2018 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. + +// This test makes sure that ambiguously live arguments work correctly. + +package main + +import ( + "runtime" +) + +type HeapObj [8]int64 + +type StkObj struct { + h *HeapObj +} + +var n int +var c int = -1 + +func gc() { + // encourage heap object to be collected, and have its finalizer run. + runtime.GC() + runtime.GC() + runtime.GC() + n++ +} + +var null StkObj + +var sink *HeapObj + +//go:noinline +func use(p *StkObj) { +} + +//go:noinline +func f(s StkObj, b bool) { + var p *StkObj + if b { + p = &s + } else { + p = &null + } + // use is required here to prevent the conditional + // code above from being executed after the first gc() call. + use(p) + // If b==false, h should be collected here. + gc() // 0 + sink = p.h + gc() // 1 + sink = nil + // If b==true, h should be collected here. + gc() // 2 +} + +func fTrue() { + var s StkObj + s.h = new(HeapObj) + c = -1 + n = 0 + runtime.SetFinalizer(s.h, func(h *HeapObj) { + // Remember at what phase the heap object was collected. + c = n + }) + f(s, true) + if c != 2 { + panic("bad liveness") + } +} + +func fFalse() { + var s StkObj + s.h = new(HeapObj) + c = -1 + n = 0 + runtime.SetFinalizer(s.h, func(h *HeapObj) { + // Remember at what phase the heap object was collected. + c = n + }) + f(s, false) + if c != 0 { + panic("bad liveness") + } +} + +func main() { + fTrue() + fFalse() +} diff --git a/test/uintptrescapes2.go b/test/uintptrescapes2.go index c94bc148c8..e7b5d721f5 100644 --- a/test/uintptrescapes2.go +++ b/test/uintptrescapes2.go @@ -30,14 +30,14 @@ func F4(...uintptr) {} // ERROR "escaping ...uintptr" func G() { var t int // ERROR "moved to heap" - F1(uintptr(unsafe.Pointer(&t))) // ERROR "live at call to F1: .?autotmp" "&t escapes to heap" + F1(uintptr(unsafe.Pointer(&t))) // ERROR "live at call to F1: .?autotmp" "&t escapes to heap" "stack object .autotmp_[0-9]+ unsafe.Pointer$" var t2 int // ERROR "moved to heap" - F3(uintptr(unsafe.Pointer(&t2))) // ERROR "live at call to F3: .?autotmp" "&t2 escapes to heap" + F3(uintptr(unsafe.Pointer(&t2))) // ERROR "live at call to F3: .?autotmp" "&t2 escapes to heap" "stack object .autotmp_[0-9]+ unsafe.Pointer$" } func H() { var v int // ERROR "moved to heap" - F2(0, 1, uintptr(unsafe.Pointer(&v)), 2) // ERROR "live at call to newobject: .?autotmp" "live at call to F2: .?autotmp" "escapes to heap" + F2(0, 1, uintptr(unsafe.Pointer(&v)), 2) // ERROR "live at call to newobject: .?autotmp" "live at call to F2: .?autotmp" "escapes to heap" "stack object .autotmp_[0-9]+ unsafe.Pointer$" var v2 int // ERROR "moved to heap" - F4(0, 1, uintptr(unsafe.Pointer(&v2)), 2) // ERROR "live at call to newobject: .?autotmp" "live at call to F4: .?autotmp" "escapes to heap" + F4(0, 1, uintptr(unsafe.Pointer(&v2)), 2) // ERROR "live at call to newobject: .?autotmp" "live at call to F4: .?autotmp" "escapes to heap" "stack object .autotmp_[0-9]+ unsafe.Pointer$" }