mirror of
				https://github.com/golang/go.git
				synced 2025-10-26 14:24:14 +00:00 
			
		
		
		
	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 <austin@google.com> Reviewed-by: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
		
							parent
							
								
									cbafcc55e8
								
							
						
					
					
						commit
						9a8372f8bd
					
				
					 16 changed files with 375 additions and 334 deletions
				
			
		|  | @ -4,9 +4,9 @@ package gc | ||||||
| 
 | 
 | ||||||
| import "strconv" | 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 { | func (i Op) String() string { | ||||||
| 	if i >= Op(len(_Op_index)-1) { | 	if i >= Op(len(_Op_index)-1) { | ||||||
|  |  | ||||||
|  | @ -78,6 +78,10 @@ import ( | ||||||
| // that its argument is certainly dead, for use when the liveness analysis | // that its argument is certainly dead, for use when the liveness analysis | ||||||
| // would not otherwise be able to deduce that fact. | // 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. | // BlockEffects summarizes the liveness effects on an SSA block. | ||||||
| type BlockEffects struct { | type BlockEffects struct { | ||||||
| 	// Computed during Liveness.prologue using only the content of | 	// 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) | 	//	uevar: upward exposed variables (used before set in block) | ||||||
| 	//	varkill: killed variables (set in block) | 	//	varkill: killed variables (set in block) | ||||||
| 	//	avarinit: addrtaken variables set or used (proof of initialization) | 	uevar   varRegVec | ||||||
| 	uevar    varRegVec | 	varkill varRegVec | ||||||
| 	varkill  varRegVec |  | ||||||
| 	avarinit bvec |  | ||||||
| 
 | 
 | ||||||
| 	// Computed during Liveness.solve using control flow information: | 	// Computed during Liveness.solve using control flow information: | ||||||
| 	// | 	// | ||||||
| 	//	livein: variables live at block entry | 	//	livein: variables live at block entry | ||||||
| 	//	liveout: variables live at block exit | 	//	liveout: variables live at block exit | ||||||
| 	//	avarinitany: addrtaken variables possibly initialized at block exit | 	livein  varRegVec | ||||||
| 	//		(initialized in block or at exit from any predecessor block) | 	liveout varRegVec | ||||||
| 	//	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 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // A collection of global state used by liveness analysis. | // A collection of global state used by liveness analysis. | ||||||
|  | @ -186,10 +182,9 @@ func (idx LivenessIndex) Valid() bool { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type progeffectscache struct { | type progeffectscache struct { | ||||||
| 	textavarinit []int32 | 	retuevar    []int32 | ||||||
| 	retuevar     []int32 | 	tailuevar   []int32 | ||||||
| 	tailuevar    []int32 | 	initialized bool | ||||||
| 	initialized  bool |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // varRegVec contains liveness bitmaps for variables and registers. | // 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 | 			// all the parameters for correctness, and similarly it must not | ||||||
| 			// read the out arguments - they won't be set until the new | 			// read the out arguments - they won't be set until the new | ||||||
| 			// function runs. | 			// function runs. | ||||||
| 
 |  | ||||||
| 			lv.cache.tailuevar = append(lv.cache.tailuevar, int32(i)) | 			lv.cache.tailuevar = append(lv.cache.tailuevar, int32(i)) | ||||||
| 
 | 
 | ||||||
| 			if node.Addrtaken() { |  | ||||||
| 				lv.cache.textavarinit = append(lv.cache.textavarinit, int32(i)) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 		case PPARAMOUT: | 		case PPARAMOUT: | ||||||
| 			// If the result had its address taken, it is being tracked | 			// All results are live at every return point. | ||||||
| 			// by the avarinit code, which does not use uevar. | 			// Note that this point is after escaping return values | ||||||
| 			// If we added it to uevar too, we'd not see any kill | 			// are copied back to the stack using their PAUTOHEAP references. | ||||||
| 			// and decide that the variable was live entry, which it is not. | 			lv.cache.retuevar = append(lv.cache.retuevar, int32(i)) | ||||||
| 			// 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)) |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -291,21 +275,13 @@ func (lv *Liveness) initcache() { | ||||||
| // | // | ||||||
| // The possible flags are: | // The possible flags are: | ||||||
| //	uevar - used by the instruction | //	uevar - used by the instruction | ||||||
| //	varkill - killed by the instruction | //	varkill - killed by the instruction (set) | ||||||
| //		for variables without address taken, means variable was set | // A kill happens after the use (for an instruction that updates a value, for example). | ||||||
| //		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. |  | ||||||
| type liveEffect int | type liveEffect int | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	uevar liveEffect = 1 << iota | 	uevar liveEffect = 1 << iota | ||||||
| 	varkill | 	varkill | ||||||
| 	avarinit |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // valueEffects returns the index of a variable in lv.vars and the | // 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 | 	var effect liveEffect | ||||||
| 	if n.Addrtaken() { | 	// Read is a read, obviously. | ||||||
| 		if v.Op != ssa.OpVarKill { | 	// | ||||||
| 			effect |= avarinit | 	// 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 v.Op == ssa.OpVarDef || v.Op == ssa.OpVarKill { | 	if e&(ssa.SymRead|ssa.SymAddr) != 0 { | ||||||
| 			effect |= varkill | 		effect |= uevar | ||||||
| 		} | 	} | ||||||
| 	} else { | 	if e&ssa.SymWrite != 0 && (!isfat(n.Type) || v.Op == ssa.OpVarDef) { | ||||||
| 		// Read is a read, obviously. | 		effect |= varkill | ||||||
| 		// 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 |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if effect == 0 { | 	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.varkill = varRegVec{vars: bulk.next()} | ||||||
| 		be.livein = varRegVec{vars: bulk.next()} | 		be.livein = varRegVec{vars: bulk.next()} | ||||||
| 		be.liveout = 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()) | 	lv.livenessMap.reset(lv.f.NumValues()) | ||||||
| 
 | 
 | ||||||
|  | @ -869,19 +830,6 @@ func (lv *Liveness) prologue() { | ||||||
| 			} | 			} | ||||||
| 			be.uevar.regs |= regUevar | 			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)) | 	nvars := int32(len(lv.vars)) | ||||||
| 	newlivein := varRegVec{vars: bvalloc(nvars)} | 	newlivein := varRegVec{vars: bvalloc(nvars)} | ||||||
| 	newliveout := varRegVec{vars: bvalloc(nvars)} | 	newliveout := varRegVec{vars: bvalloc(nvars)} | ||||||
| 	any := bvalloc(nvars) |  | ||||||
| 	all := bvalloc(nvars) |  | ||||||
| 
 | 
 | ||||||
| 	// Push avarinitall, avarinitany forward. | 	// Walk blocks in postorder ordering. This improves convergence. | ||||||
| 	// 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. |  | ||||||
| 	po := lv.f.Postorder() | 	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 | 	// Iterate through the blocks in reverse round-robin fashion. A work | ||||||
| 	// queue might be slightly faster. As is, the number of iterations is | 	// queue might be slightly faster. As is, the number of iterations is | ||||||
| 	// so low that it hardly seems to be worth the complexity. | 	// so low that it hardly seems to be worth the complexity. | ||||||
|  | @ -957,7 +864,7 @@ func (lv *Liveness) solve() { | ||||||
| 					newliveout.vars.Set(pos) | 					newliveout.vars.Set(pos) | ||||||
| 				} | 				} | ||||||
| 			case ssa.BlockExit: | 			case ssa.BlockExit: | ||||||
| 				// nothing to do | 				// panic exit - nothing to do | ||||||
| 			default: | 			default: | ||||||
| 				// A variable is live on output from this block | 				// A variable is live on output from this block | ||||||
| 				// if it is live on input to some successor. | 				// 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 | 			// 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. | 			// not set by the code in this block. | ||||||
| 			// | 			// | ||||||
| 			// in[b] = uevar[b] \cup (out[b] \setminus varkill[b]) | 			// in[b] = uevar[b] \cup (out[b] \setminus varkill[b]) | ||||||
|  | @ -990,8 +897,6 @@ func (lv *Liveness) solve() { | ||||||
| func (lv *Liveness) epilogue() { | func (lv *Liveness) epilogue() { | ||||||
| 	nvars := int32(len(lv.vars)) | 	nvars := int32(len(lv.vars)) | ||||||
| 	liveout := varRegVec{vars: bvalloc(nvars)} | 	liveout := varRegVec{vars: bvalloc(nvars)} | ||||||
| 	any := bvalloc(nvars) |  | ||||||
| 	all := bvalloc(nvars) |  | ||||||
| 	livedefer := bvalloc(nvars) // always-live variables | 	livedefer := bvalloc(nvars) // always-live variables | ||||||
| 
 | 
 | ||||||
| 	// If there is a defer (that could recover), then all output | 	// If there is a defer (that could recover), then all output | ||||||
|  | @ -1017,6 +922,9 @@ func (lv *Liveness) epilogue() { | ||||||
| 				livedefer.Set(int32(i)) | 				livedefer.Set(int32(i)) | ||||||
| 			} | 			} | ||||||
| 			if n.IsOutputParamHeapAddr() { | 			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) | 				n.Name.SetNeedzero(true) | ||||||
| 				livedefer.Set(int32(i)) | 				livedefer.Set(int32(i)) | ||||||
| 			} | 			} | ||||||
|  | @ -1033,9 +941,6 @@ func (lv *Liveness) epilogue() { | ||||||
| 	{ | 	{ | ||||||
| 		// Reserve an entry for function entry. | 		// Reserve an entry for function entry. | ||||||
| 		live := bvalloc(nvars) | 		live := bvalloc(nvars) | ||||||
| 		for _, pos := range lv.cache.textavarinit { |  | ||||||
| 			live.Set(pos) |  | ||||||
| 		} |  | ||||||
| 		lv.livevars = append(lv.livevars, varRegVec{vars: live}) | 		lv.livevars = append(lv.livevars, varRegVec{vars: live}) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -1043,53 +948,14 @@ func (lv *Liveness) epilogue() { | ||||||
| 		be := lv.blockEffects(b) | 		be := lv.blockEffects(b) | ||||||
| 		firstBitmapIndex := len(lv.livevars) | 		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 | 		// Walk forward through the basic block instructions and | ||||||
| 		// allocate liveness maps for those instructions that need them. | 		// allocate liveness maps for those instructions that need them. | ||||||
| 		// Seed the maps with information about the addrtaken variables. |  | ||||||
| 		for _, v := range b.Values { | 		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) { | 			if !lv.issafepoint(v) { | ||||||
| 				continue | 				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 := bvalloc(nvars) | ||||||
| 			live.Copy(any) |  | ||||||
| 			lv.livevars = append(lv.livevars, varRegVec{vars: live}) | 			lv.livevars = append(lv.livevars, varRegVec{vars: live}) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -1128,6 +994,17 @@ func (lv *Liveness) epilogue() { | ||||||
| 				Fatalf("bad index for entry point: %v", index) | 				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. | 			// Record live variables. | ||||||
| 			live := &lv.livevars[index] | 			live := &lv.livevars[index] | ||||||
| 			live.Or(*live, liveout) | 			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) | 	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 | // Compact coalesces identical bitmaps from lv.livevars into the sets | ||||||
| // lv.stackMapSet and lv.regMaps. | // lv.stackMapSet and lv.regMaps. | ||||||
| // | // | ||||||
|  | @ -1559,7 +1414,6 @@ func (lv *Liveness) printDebug() { | ||||||
| 			printed = false | 			printed = false | ||||||
| 			printed = lv.printeffect(printed, "uevar", pos, effect&uevar != 0, regUevar) | 			printed = lv.printeffect(printed, "uevar", pos, effect&uevar != 0, regUevar) | ||||||
| 			printed = lv.printeffect(printed, "varkill", pos, effect&varkill != 0, regKill) | 			printed = lv.printeffect(printed, "varkill", pos, effect&varkill != 0, regKill) | ||||||
| 			printed = lv.printeffect(printed, "avarinit", pos, effect&avarinit != 0, 0) |  | ||||||
| 			if printed { | 			if printed { | ||||||
| 				fmt.Printf("\n") | 				fmt.Printf("\n") | ||||||
| 			} | 			} | ||||||
|  | @ -1596,9 +1450,6 @@ func (lv *Liveness) printDebug() { | ||||||
| 		printed = false | 		printed = false | ||||||
| 		printed = lv.printbvec(printed, "varkill", be.varkill) | 		printed = lv.printbvec(printed, "varkill", be.varkill) | ||||||
| 		printed = lv.printbvec(printed, "liveout", be.liveout) | 		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 { | 		if printed { | ||||||
| 			fmt.Printf("\n") | 			fmt.Printf("\n") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -839,6 +839,10 @@ func slicelit(ctxt initContext, n *Node, var_ *Node, init *Nodes) { | ||||||
| 			a = nod(OAS, x, nil) | 			a = nod(OAS, x, nil) | ||||||
| 			a = typecheck(a, Etop) | 			a = typecheck(a, Etop) | ||||||
| 			init.Append(a) // zero new temp | 			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) | 		a = nod(OADDR, x, nil) | ||||||
|  | @ -849,6 +853,8 @@ func slicelit(ctxt initContext, n *Node, var_ *Node, init *Nodes) { | ||||||
| 			a = typecheck(a, Etop) | 			a = typecheck(a, Etop) | ||||||
| 			init.Append(a) // zero new temp | 			init.Append(a) // zero new temp | ||||||
| 			a = a.Left | 			a = a.Left | ||||||
|  | 		} else { | ||||||
|  | 			init.Append(nod(OVARDEF, a, nil)) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		a = nod(OADDR, a, nil) | 		a = nod(OADDR, a, nil) | ||||||
|  |  | ||||||
|  | @ -755,8 +755,8 @@ func (s *state) stmtList(l Nodes) { | ||||||
| 
 | 
 | ||||||
| // stmt converts the statement n to SSA and adds it to s. | // stmt converts the statement n to SSA and adds it to s. | ||||||
| func (s *state) stmt(n *Node) { | func (s *state) stmt(n *Node) { | ||||||
| 	if !(n.Op == OVARKILL || n.Op == OVARLIVE) { | 	if !(n.Op == OVARKILL || n.Op == OVARLIVE || n.Op == OVARDEF) { | ||||||
| 		// OVARKILL and OVARLIVE are invisible to the programmer, so we don't use their line numbers to avoid confusion in debugging. | 		// 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) | 		s.pushLine(n.Pos) | ||||||
| 		defer s.popLine() | 		defer s.popLine() | ||||||
| 	} | 	} | ||||||
|  | @ -1170,6 +1170,10 @@ func (s *state) stmt(n *Node) { | ||||||
| 		} | 		} | ||||||
| 		s.startBlock(bEnd) | 		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: | 	case OVARKILL: | ||||||
| 		// Insert a varkill op to record that a variable is no longer live. | 		// 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 | 		// 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.Type = obj.TYPE_MEM | ||||||
| 	p.To.Name = obj.NAME_EXTERN | 	p.To.Name = obj.NAME_EXTERN | ||||||
| 	p.To.Sym = x | 	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. | // genssa appends entries to pp for each instruction in f. | ||||||
|  | @ -5056,24 +5066,8 @@ func genssa(f *ssa.Func, pp *Progs) { | ||||||
| 			case ssa.OpGetG: | 			case ssa.OpGetG: | ||||||
| 				// nothing to do when there's a g register, | 				// nothing to do when there's a g register, | ||||||
| 				// and checkLower complains if there's not | 				// 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 | 				// 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: | 			case ssa.OpPhi: | ||||||
| 				CheckLoweredPhi(v) | 				CheckLoweredPhi(v) | ||||||
| 			case ssa.OpConvert: | 			case ssa.OpConvert: | ||||||
|  |  | ||||||
|  | @ -739,6 +739,7 @@ const ( | ||||||
| 	OCLOSUREVAR // variable reference at beginning of closure function | 	OCLOSUREVAR // variable reference at beginning of closure function | ||||||
| 	OCFUNC      // reference to c function pointer (not go func value) | 	OCFUNC      // reference to c function pointer (not go func value) | ||||||
| 	OCHECKNIL   // emit code to ensure pointer/interface not nil | 	OCHECKNIL   // emit code to ensure pointer/interface not nil | ||||||
|  | 	OVARDEF     // variable is about to be fully initialized | ||||||
| 	OVARKILL    // variable is dead | 	OVARKILL    // variable is dead | ||||||
| 	OVARLIVE    // variable is alive | 	OVARLIVE    // variable is alive | ||||||
| 	OINDREGSP   // offset plus indirect of REGSP, such as 8(SP). | 	OINDREGSP   // offset plus indirect of REGSP, such as 8(SP). | ||||||
|  |  | ||||||
|  | @ -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: "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: "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 | 	// TODO: what's the difference betweeen VarLive and KeepAlive? | ||||||
| 	{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 | 	{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 | 	// Ops for breaking 64-bit operations on 32-bit architectures | ||||||
| 	{name: "Int64Make", argLength: 2, typ: "UInt64"}, // arg0=hi, arg1=lo | 	{name: "Int64Make", argLength: 2, typ: "UInt64"}, // arg0=hi, arg1=lo | ||||||
|  |  | ||||||
|  | @ -232,6 +232,11 @@ func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool { | ||||||
| 	gp.param = nil | 	gp.param = nil | ||||||
| 	c.sendq.enqueue(mysg) | 	c.sendq.enqueue(mysg) | ||||||
| 	goparkunlock(&c.lock, waitReasonChanSend, traceEvGoBlockSend, 3) | 	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. | 	// someone woke us up. | ||||||
| 	if mysg != gp.waiting { | 	if mysg != gp.waiting { | ||||||
|  |  | ||||||
|  | @ -35,14 +35,46 @@ func TestGCInfo(t *testing.T) { | ||||||
| 	verifyGCInfo(t, "data eface", &dataEface, infoEface) | 	verifyGCInfo(t, "data eface", &dataEface, infoEface) | ||||||
| 	verifyGCInfo(t, "data iface", &dataIface, infoIface) | 	verifyGCInfo(t, "data iface", &dataIface, infoIface) | ||||||
| 
 | 
 | ||||||
| 	verifyGCInfo(t, "stack Ptr", new(Ptr), infoPtr) | 	{ | ||||||
| 	verifyGCInfo(t, "stack ScalarPtr", new(ScalarPtr), infoScalarPtr) | 		var x Ptr | ||||||
| 	verifyGCInfo(t, "stack PtrScalar", new(PtrScalar), infoPtrScalar) | 		verifyGCInfo(t, "stack Ptr", &x, infoPtr) | ||||||
| 	verifyGCInfo(t, "stack BigStruct", new(BigStruct), infoBigStruct()) | 		runtime.KeepAlive(x) | ||||||
| 	verifyGCInfo(t, "stack string", new(string), infoString) | 	} | ||||||
| 	verifyGCInfo(t, "stack slice", new([]string), infoSlice) | 	{ | ||||||
| 	verifyGCInfo(t, "stack eface", new(interface{}), infoEface) | 		var x ScalarPtr | ||||||
| 	verifyGCInfo(t, "stack iface", new(Iface), infoIface) | 		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++ { | 	for i := 0; i < 10; i++ { | ||||||
| 		verifyGCInfo(t, "heap Ptr", escape(new(Ptr)), trimDead(padDead(infoPtr))) | 		verifyGCInfo(t, "heap Ptr", escape(new(Ptr)), trimDead(padDead(infoPtr))) | ||||||
|  |  | ||||||
|  | @ -1994,7 +1994,9 @@ func reflect_gcbits(x interface{}) []byte { | ||||||
| 	return ret | 	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) { | func getgcmask(ep interface{}) (mask []byte) { | ||||||
| 	e := *efaceOf(&ep) | 	e := *efaceOf(&ep) | ||||||
| 	p := e.data | 	p := e.data | ||||||
|  | @ -2051,11 +2053,6 @@ func getgcmask(ep interface{}) (mask []byte) { | ||||||
| 		_g_ := getg() | 		_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) | 		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() { | 		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) | 			locals, _, _ := getStackMap(&frame, nil, false) | ||||||
| 			if locals.n == 0 { | 			if locals.n == 0 { | ||||||
| 				return | 				return | ||||||
|  |  | ||||||
|  | @ -15,10 +15,10 @@ type T struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func f(a T) { // ERROR "live at entry to f: a" | func f(a T) { // ERROR "live at entry to f: a" | ||||||
| 	var e interface{} | 	var e interface{} // ERROR "stack object e interface \{\}$" | ||||||
| 	func() { // ERROR "live at entry to f.func1: a &e" | 	func() {          // ERROR "live at entry to f.func1: a &e" | ||||||
| 		e = a.s // ERROR "live at call to convT2Estring: a &e" | 		e = a.s // ERROR "live at call to convT2Estring: &e" "stack object a T$" | ||||||
| 	}() // ERROR "live at call to f.func1: e$" | 	}() | ||||||
| 	// Before the fix, both a and e were live at the previous line. | 	// Before the fix, both a and e were live at the previous line. | ||||||
| 	_ = e | 	_ = e | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										175
									
								
								test/live.go
									
										
									
									
									
								
							
							
						
						
									
										175
									
								
								test/live.go
									
										
									
									
									
								
							|  | @ -32,9 +32,9 @@ func printbytepointer(*byte) | ||||||
| func printint(int) | func printint(int) | ||||||
| 
 | 
 | ||||||
| func f1() { | func f1() { | ||||||
| 	var x *int | 	var x *int       // ERROR "stack object x \*int$" | ||||||
| 	printpointer(&x) // ERROR "live at call to printpointer: x$" |  | ||||||
| 	printpointer(&x) // ERROR "live at call to printpointer: x$" | 	printpointer(&x) // ERROR "live at call to printpointer: x$" | ||||||
|  | 	printpointer(&x) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func f2(b bool) { | func f2(b bool) { | ||||||
|  | @ -42,9 +42,9 @@ func f2(b bool) { | ||||||
| 		printint(0) // nothing live here | 		printint(0) // nothing live here | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	var x *int | 	var x *int       // ERROR "stack object x \*int$" | ||||||
| 	printpointer(&x) // ERROR "live at call to printpointer: x$" |  | ||||||
| 	printpointer(&x) // ERROR "live at call to printpointer: x$" | 	printpointer(&x) // ERROR "live at call to printpointer: x$" | ||||||
|  | 	printpointer(&x) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func f3(b1, b2 bool) { | func f3(b1, b2 bool) { | ||||||
|  | @ -60,15 +60,15 @@ func f3(b1, b2 bool) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if b2 { | 	if b2 { | ||||||
| 		var x *int | 		var x *int       // ERROR "stack object x \*int$" | ||||||
| 		printpointer(&x) // ERROR "live at call to printpointer: x$" |  | ||||||
| 		printpointer(&x) // ERROR "live at call to printpointer: x$" | 		printpointer(&x) // ERROR "live at call to printpointer: x$" | ||||||
|  | 		printpointer(&x) | ||||||
| 	} else { | 	} else { | ||||||
| 		var y *int | 		var y *int       // ERROR "stack object y \*int$" | ||||||
| 		printpointer(&y) // ERROR "live at call to printpointer: y$" |  | ||||||
| 		printpointer(&y) // ERROR "live at call to printpointer: y$" | 		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 | // 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 | 		return | ||||||
| 	} | 	} | ||||||
| 	var z **int | 	var z **int | ||||||
| 	x := new(int) | 	x := new(int) // ERROR "stack object x \*int$" | ||||||
| 	*x = 42 | 	*x = 42 | ||||||
| 	z = &x | 	z = &x | ||||||
| 	printint(**z) // ERROR "live at call to printint: 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) { | func f5(b1 bool) { | ||||||
| 	var z **int | 	var z **int | ||||||
| 	if b1 { | 	if b1 { | ||||||
| 		x := new(int) | 		x := new(int) // ERROR "stack object x \*int$" | ||||||
| 		*x = 42 | 		*x = 42 | ||||||
| 		z = &x | 		z = &x | ||||||
| 	} else { | 	} else { | ||||||
| 		y := new(int) | 		y := new(int) // ERROR "stack object y \*int$" | ||||||
| 		*y = 54 | 		*y = 54 | ||||||
| 		z = &y | 		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: _". | // 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". | // 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 | ||||||
| 	x = "hello" | 	x = "hello" | ||||||
| 	return | 	return | ||||||
|  | @ -141,7 +141,7 @@ var i9 interface{} | ||||||
| func f9() bool { | func f9() bool { | ||||||
| 	g8() | 	g8() | ||||||
| 	x := i9 | 	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 | 	i9 = y                  // make y escape so the line above has to call convT2E | ||||||
| 	return x != y | 	return x != y | ||||||
| } | } | ||||||
|  | @ -163,7 +163,7 @@ var b bool | ||||||
| 
 | 
 | ||||||
| // this used to have a spurious "live at entry to f11a: ~r0" | // this used to have a spurious "live at entry to f11a: ~r0" | ||||||
| func f11a() *int { | func f11a() *int { | ||||||
| 	select { // ERROR "live at call to selectgo: .autotmp_[0-9]+$" | 	select { // ERROR "stack object .autotmp_[0-9]+ \[2\]struct" | ||||||
| 	case <-c: | 	case <-c: | ||||||
| 		return nil | 		return nil | ||||||
| 	case <-c: | 	case <-c: | ||||||
|  | @ -178,7 +178,7 @@ func f11b() *int { | ||||||
| 		// get to the bottom of the function. | 		// get to the bottom of the function. | ||||||
| 		// This used to have a spurious "live at call to printint: p". | 		// This used to have a spurious "live at call to printint: p". | ||||||
| 		printint(1) // nothing live here! | 		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: | 		case <-c: | ||||||
| 			return nil | 			return nil | ||||||
| 		case <-c: | 		case <-c: | ||||||
|  | @ -198,7 +198,7 @@ func f11c() *int { | ||||||
| 		// Unlike previous, the cases in this select fall through, | 		// Unlike previous, the cases in this select fall through, | ||||||
| 		// so we can get to the println, so p is not dead. | 		// so we can get to the println, so p is not dead. | ||||||
| 		printint(1) // ERROR "live at call to printint: p$" | 		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: | ||||||
| 		case <-c: | 		case <-c: | ||||||
| 		} | 		} | ||||||
|  | @ -233,8 +233,8 @@ func h13(string, string) string | ||||||
| // more incorrectly placed VARDEF. | // more incorrectly placed VARDEF. | ||||||
| 
 | 
 | ||||||
| func f14() { | func f14() { | ||||||
| 	x := g14() | 	x := g14() // ERROR "stack object x string$" | ||||||
| 	printstringpointer(&x) // ERROR "live at call to printstringpointer: x$" | 	printstringpointer(&x) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func g14() string | func g14() string | ||||||
|  | @ -254,10 +254,10 @@ func iface() interface{} | ||||||
| 
 | 
 | ||||||
| func f16() { | func f16() { | ||||||
| 	if b { | 	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 "stack object .autotmp_[0-9]+ interface \{\}$" | ||||||
| 	delete(mi, iface()) // ERROR "live at call to mapdelete: .autotmp_[0-9]+$" | 	delete(mi, iface()) // ERROR "stack object .autotmp_[0-9]+ interface \{\}$" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var m2s map[string]*byte | var m2s map[string]*byte | ||||||
|  | @ -300,10 +300,10 @@ func f18() { | ||||||
| 	// temporary introduced by orderexpr. | 	// temporary introduced by orderexpr. | ||||||
| 	var z *byte | 	var z *byte | ||||||
| 	if b { | 	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 "stack object .autotmp_[0-9]+ \[2\]string$" | ||||||
| 	z = m2[g18()] // ERROR "live at call to mapaccess1: .autotmp_[0-9]+$" | 	z = m2[g18()] // ERROR "stack object .autotmp_[0-9]+ \[2\]string$" | ||||||
| 	printbytepointer(z) | 	printbytepointer(z) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -317,30 +317,30 @@ func f19() { | ||||||
| 	var z *byte | 	var z *byte | ||||||
| 
 | 
 | ||||||
| 	if b { | 	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 "stack object .autotmp_[0-9]+ \*byte$" | ||||||
| 	z = <-ch // ERROR "live at call to chanrecv1: .autotmp_[0-9]+$" | 	z = <-ch // ERROR "stack object .autotmp_[0-9]+ \*byte$" "live at call to chanrecv1: .autotmp_[0-9]+$" | ||||||
| 	printbytepointer(z) | 	printbytepointer(z) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func f20() { | func f20() { | ||||||
| 	// src temporary for channel send | 	// src temporary for channel send | ||||||
| 	if b { | 	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 "stack object .autotmp_[0-9]+ \*byte$" | ||||||
| 	ch <- byteptr() // ERROR "live at call to chansend1: .autotmp_[0-9]+$" | 	ch <- byteptr() // ERROR "stack object .autotmp_[0-9]+ \*byte$" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func f21() { | func f21() { | ||||||
| 	// key temporary for mapaccess using array literal key. | 	// key temporary for mapaccess using array literal key. | ||||||
| 	var z *byte | 	var z *byte | ||||||
| 	if b { | 	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 "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 "stack object .autotmp_[0-9]+ \[2\]string$" | ||||||
| 	printbytepointer(z) | 	printbytepointer(z) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -349,10 +349,10 @@ func f23() { | ||||||
| 	var z *byte | 	var z *byte | ||||||
| 	var ok bool | 	var ok bool | ||||||
| 	if b { | 	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 "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 "stack object .autotmp_[0-9]+ \[2\]string$" | ||||||
| 	printbytepointer(z) | 	printbytepointer(z) | ||||||
| 	print(ok) | 	print(ok) | ||||||
| } | } | ||||||
|  | @ -361,10 +361,10 @@ func f24() { | ||||||
| 	// key temporary for map access using array literal key. | 	// key temporary for map access using array literal key. | ||||||
| 	// value temporary too. | 	// value temporary too. | ||||||
| 	if b { | 	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 "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 "stack object .autotmp_[0-9]+ \[2\]string$" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // defer should not cause spurious ambiguously live variables | // defer should not cause spurious ambiguously live variables | ||||||
|  | @ -387,10 +387,10 @@ func g25() | ||||||
| 
 | 
 | ||||||
| func f26(b bool) { | func f26(b bool) { | ||||||
| 	if b { | 	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 "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 "stack object .autotmp_[0-9]+ \[3\]interface \{\}$" | ||||||
| 	printnl() | 	printnl() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -402,10 +402,10 @@ func print26(...interface{}) | ||||||
| func f27(b bool) { | func f27(b bool) { | ||||||
| 	x := 0 | 	x := 0 | ||||||
| 	if b { | 	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 "stack object .autotmp_[0-9]+ struct \{" | ||||||
| 	call27(func() { x++ }) // ERROR "live at call to call27: .autotmp_[0-9]+$" | 	call27(func() { x++ }) // ERROR "stack object .autotmp_[0-9]+ struct \{" | ||||||
| 	printnl() | 	printnl() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -414,11 +414,11 @@ func f27(b bool) { | ||||||
| func f27defer(b bool) { | func f27defer(b bool) { | ||||||
| 	x := 0 | 	x := 0 | ||||||
| 	if b { | 	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]+$" | 	defer call27(func() { x++ }) // ERROR "stack object .autotmp_[0-9]+ struct \{" | ||||||
| 	printnl()                    // ERROR "live at call to printnl: .autotmp_[0-9]+ .autotmp_[0-9]+$" | 	printnl() | ||||||
| } // ERROR "live at call to deferreturn: .autotmp_[0-9]+ .autotmp_[0-9]+$" | } | ||||||
| 
 | 
 | ||||||
| // and newproc (go) escapes to the heap | // 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) { | func f28(b bool) { | ||||||
| 	if b { | 	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 "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 "stack object .autotmp_[0-9]+ \[10\]string$" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // map iterator should die on end of range loop | // map iterator should die on end of range loop | ||||||
| 
 | 
 | ||||||
| func f29(b bool) { | func f29(b bool) { | ||||||
| 	if b { | 	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]+$" | 			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]+$" | 		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]+$" | 		printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -473,20 +473,19 @@ type pstruct struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func f30(b bool) { | func f30(b bool) { | ||||||
| 	// two live temps during printintpointer(p): | 	// live temp during printintpointer(p): | ||||||
| 	// in the copy of p.intp and |  | ||||||
| 	// the internal iterator pointer if a pointer to pstruct in pstructarr | 	// the internal iterator pointer if a pointer to pstruct in pstructarr | ||||||
| 	// can not be easily computed by strength reduction. | 	// can not be easily computed by strength reduction. | ||||||
| 	if b { | 	if b { | ||||||
| 		for _, p := range pstructarr { | 		for _, p := range pstructarr { // ERROR "stack object .autotmp_[0-9]+ \[10\]pstruct$" | ||||||
| 			printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+ .autotmp_[0-9]+$" | 			printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+$" | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	for _, p := range pstructarr { | 	for _, p := range pstructarr { // ERROR "stack object .autotmp_[0-9]+ \[10\]pstruct$" | ||||||
| 		printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+ .autotmp_[0-9]+$" | 		printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+$" | ||||||
| 	} | 	} | ||||||
| 	for _, p := range pstructarr { | 	for _, p := range pstructarr { // ERROR "stack object .autotmp_[0-9]+ \[10\]pstruct$" | ||||||
| 		printintpointer(p.intp) // ERROR "live at call to printintpointer: .autotmp_[0-9]+ .autotmp_[0-9]+$" | 		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) { | func f31(b1, b2, b3 bool) { | ||||||
| 	if b1 { | 	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 { | 	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 { | 	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) | 	print(b3) | ||||||
| } | } | ||||||
|  | @ -520,10 +519,10 @@ var t32 T32 | ||||||
| 
 | 
 | ||||||
| func f32(b bool) { | func f32(b bool) { | ||||||
| 	if b { | 	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 "stack object .autotmp_[0-9]+ struct \{" | ||||||
| 	call32(t32.Inc) // ERROR "live at call to call32: .autotmp_[0-9]+$" | 	call32(t32.Inc) // ERROR "stack object .autotmp_[0-9]+ struct \{" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| //go:noescape | //go:noescape | ||||||
|  | @ -535,7 +534,7 @@ func call32(func()) | ||||||
| var m33 map[interface{}]int | var m33 map[interface{}]int | ||||||
| 
 | 
 | ||||||
| func f33() { | 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() | 		printnl() | ||||||
| 		return | 		return | ||||||
| 	} else { | 	} else { | ||||||
|  | @ -545,7 +544,7 @@ func f33() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func f34() { | 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() | 		printnl() | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -553,7 +552,8 @@ func f34() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func f35() { | 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() | 		printnl() | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -561,7 +561,8 @@ func f35() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func f36() { | 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() | 		printnl() | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -569,7 +570,9 @@ func f36() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func f37() { | 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() | 		printnl() | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -589,14 +592,14 @@ func f38(b bool) { | ||||||
| 	// we care that the println lines have no live variables | 	// we care that the println lines have no live variables | ||||||
| 	// and therefore no output. | 	// and therefore no output. | ||||||
| 	if b { | 	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(): | 		case <-fc38(): | ||||||
| 			printnl() | 			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() | 			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() | 			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() | ||||||
| 		} | 		} | ||||||
| 		printnl() | 		printnl() | ||||||
|  | @ -655,15 +658,17 @@ func bad40() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func good40() { | func good40() { | ||||||
| 	ret := T40{} | 	ret := T40{}              // ERROR "stack object ret T40$" | ||||||
| 	ret.m = make(map[int]int) // ERROR "live at call to fastrand: .autotmp_[0-9]+ ret$" | 	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 | 	t := &ret | ||||||
| 	printnl() // ERROR "live at call to printnl: .autotmp_[0-9]+ ret$" | 	printnl() // ERROR "live at call to printnl: ret$" | ||||||
| 	useT40(t) // ERROR "live at call to useT40: .autotmp_[0-9]+ 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$" | 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() | 	printnl() | ||||||
| 	// Note: no .?autotmp live at printnl.  See issue 16996. | 	// Note: no .?autotmp live at printnl.  See issue 16996. | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -10,7 +10,6 @@ | ||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| // issue 8142: lost 'addrtaken' bit on inlined variables. | // issue 8142: lost 'addrtaken' bit on inlined variables. | ||||||
| // no inlining in this test, so just checking that non-inlined works. |  | ||||||
| 
 | 
 | ||||||
| func printnl() | func printnl() | ||||||
| 
 | 
 | ||||||
|  | @ -28,15 +27,15 @@ func newT40() *T40 { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func bad40() { | func bad40() { | ||||||
| 	t := newT40() // ERROR "live at call to makemap: .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: .autotmp_[0-9]+ ret$" | 	printnl()     // ERROR "live at call to printnl: ret$" | ||||||
| 	useT40(t)     // ERROR "live at call to useT40: .autotmp_[0-9]+ ret$" | 	useT40(t) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func good40() { | func good40() { | ||||||
| 	ret := T40{} | 	ret := T40{}                  // ERROR "stack object ret T40$" | ||||||
| 	ret.m = make(map[int]int, 42) // ERROR "live at call to makemap: .autotmp_[0-9]+ ret$" | 	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 | 	t := &ret | ||||||
| 	printnl() // ERROR "live at call to printnl: .autotmp_[0-9]+ ret$" | 	printnl() // ERROR "live at call to printnl: ret$" | ||||||
| 	useT40(t) // ERROR "live at call to useT40: .autotmp_[0-9]+ ret$" | 	useT40(t) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -19,22 +19,22 @@ func f(uintptr) // ERROR "f assuming arg#1 is unsafe uintptr" | ||||||
| 
 | 
 | ||||||
| func g() { | func g() { | ||||||
| 	var t int | 	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() { | func h() { | ||||||
| 	var v int | 	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() { | func i() { | ||||||
| 	var t int | 	var t int | ||||||
| 	p := unsafe.Pointer(&t) // ERROR "i &t does not escape" | 	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() { | func j() { | ||||||
| 	var v int | 	var v int | ||||||
| 	p := unsafe.Pointer(&v)              // ERROR "j &v does not escape" | 	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$" | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										57
									
								
								test/stackobj.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								test/stackobj.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
|  | } | ||||||
							
								
								
									
										93
									
								
								test/stackobj3.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								test/stackobj3.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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() | ||||||
|  | } | ||||||
|  | @ -30,14 +30,14 @@ func F4(...uintptr) {} // ERROR "escaping ...uintptr" | ||||||
| 
 | 
 | ||||||
| func G() { | func G() { | ||||||
| 	var t int                        // ERROR "moved to heap" | 	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" | 	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() { | func H() { | ||||||
| 	var v int                                 // ERROR "moved to heap" | 	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" | 	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$" | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Keith Randall
						Keith Randall