Skip to content

Commit b6dc3e6

Browse files
committed
cmd/compile: fix liveness computation for heap-escaped parameters
The liveness computation of parameters generally was never correct, but forcing all parameters to be live throughout the function covered up that problem. The new SSA back end is too clever: even though it currently keeps the parameter values live throughout the function, it may find optimizations that mean the current values are not written back to the original parameter stack slots immediately or ever (for example if a parameter is set to nil, SSA constant propagation may replace all later uses of the parameter with a constant nil, eliminating the need to write the nil value back to the stack slot), so the liveness code must now track the actual operations on the stack slots, exposing these problems. One small problem in the handling of arguments is that nodarg can return ONAME PPARAM nodes with adjusted offsets, so that there are actually multiple *Node pointers for the same parameter in the instruction stream. This might be possible to correct, but not in this CL. For now, we fix this by using n.Orig instead of n when considering PPARAM and PPARAMOUT nodes. The major problem in the handling of arguments is general confusion in the liveness code about the meaning of PPARAM|PHEAP and PPARAMOUT|PHEAP nodes, especially as contrasted with PAUTO|PHEAP. The difference between these two is that when a local variable "moves" to the heap, it's really just allocated there to start with; in contrast, when an argument moves to the heap, the actual data has to be copied there from the stack at the beginning of the function, and when a result "moves" to the heap the value in the heap has to be copied back to the stack when the function returns This general confusion is also present in the SSA back end. The PHEAP bit worked decently when I first introduced it 7 years ago (!) in 391425a. The back end did nothing sophisticated, and in particular there was no analysis at all: no escape analysis, no liveness analysis, and certainly no SSA back end. But the complications caused in the various downstream consumers suggest that this should be a detail kept mainly in the front end. This CL therefore eliminates both the PHEAP bit and even the idea of "heap variables" from the back ends. First, it replaces the PPARAM|PHEAP, PPARAMOUT|PHEAP, and PAUTO|PHEAP variable classes with the single PAUTOHEAP, a pseudo-class indicating a variable maintained on the heap and available by indirecting a local variable kept on the stack (a plain PAUTO). Second, walkexpr replaces all references to PAUTOHEAP variables with indirections of the corresponding PAUTO variable. The back ends and the liveness code now just see plain indirected variables. This may actually produce better code, but the real goal here is to eliminate these little-used and somewhat suspect code paths in the back end analyses. The OPARAM node type goes away too. A followup CL will do the same to PPARAMREF. I'm not sure that the back ends (SSA in particular) are handling those right either, and with the framework established in this CL that change is trivial and the result clearly more correct. Fixes #15747. Change-Id: I2770b1ce3cbc93981bfc7166be66a9da12013d74 Reviewed-on: https://go-review.googlesource.com/23393 Reviewed-by: Keith Randall <[email protected]> Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 99d29d5 commit b6dc3e6

24 files changed

+332
-221
lines changed

src/cmd/compile/internal/amd64/gsubr.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func ginscmp(op gc.Op, t *gc.Type, n1, n2 *gc.Node, likely int) *obj.Prog {
116116
base = n1.Left
117117
}
118118

119-
if base.Op == gc.ONAME && base.Class&gc.PHEAP == 0 || n1.Op == gc.OINDREG {
119+
if base.Op == gc.ONAME && base.Class != gc.PAUTOHEAP || n1.Op == gc.OINDREG {
120120
r1 = *n1
121121
} else {
122122
gc.Regalloc(&r1, t, n1)
@@ -229,6 +229,8 @@ func gmove(f *gc.Node, t *gc.Node) {
229229

230230
switch uint32(ft)<<16 | uint32(tt) {
231231
default:
232+
gc.Dump("f", f)
233+
gc.Dump("t", t)
232234
gc.Fatalf("gmove %v -> %v", gc.Tconv(f.Type, gc.FmtLong), gc.Tconv(t.Type, gc.FmtLong))
233235

234236
/*

src/cmd/compile/internal/gc/align.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,15 @@ func widstruct(errtype *Type, t *Type, o int64, flag int) int64 {
5555
}
5656
f.Offset = o
5757
if f.Nname != nil {
58-
// this same stackparam logic is in addrescapes
59-
// in typecheck.go. usually addrescapes runs after
60-
// widstruct, in which case we could drop this,
58+
// addrescapes has similar code to update these offsets.
59+
// Usually addrescapes runs after widstruct,
60+
// in which case we could drop this,
6161
// but function closure functions are the exception.
62-
if f.Nname.Name.Param.Stackparam != nil {
63-
f.Nname.Name.Param.Stackparam.Xoffset = o
62+
// NOTE(rsc): This comment may be stale.
63+
// It's possible the ordering has changed and this is
64+
// now the common case. I'm not sure.
65+
if f.Nname.Name.Param.Stackcopy != nil {
66+
f.Nname.Name.Param.Stackcopy.Xoffset = o
6467
f.Nname.Xoffset = 0
6568
} else {
6669
f.Nname.Xoffset = o

src/cmd/compile/internal/gc/bexport.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,8 +1408,8 @@ func (p *exporter) stmt(n *Node) {
14081408
switch op := n.Op; op {
14091409
case ODCL:
14101410
p.op(ODCL)
1411-
switch n.Left.Class &^ PHEAP {
1412-
case PPARAM, PPARAMOUT, PAUTO:
1411+
switch n.Left.Class {
1412+
case PPARAM, PPARAMOUT, PAUTO, PAUTOHEAP:
14131413
// TODO(gri) when is this not PAUTO?
14141414
// Also, originally this didn't look like
14151415
// the default case. Investigate.

src/cmd/compile/internal/gc/cgen.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ func cgen_wb(n, res *Node, wb bool) {
519519
ODOTPTR,
520520
OINDEX,
521521
OIND,
522-
ONAME: // PHEAP or PPARAMREF var
522+
ONAME: // PPARAMREF var
523523
var n1 Node
524524
Igen(n, &n1, res)
525525

@@ -1579,7 +1579,7 @@ func Agen(n *Node, res *Node) {
15791579
}
15801580

15811581
// should only get here for heap vars or paramref
1582-
if n.Class&PHEAP == 0 && n.Class != PPARAMREF {
1582+
if n.Class != PPARAMREF {
15831583
Dump("bad agen", n)
15841584
Fatalf("agen: bad ONAME class %#x", n.Class)
15851585
}
@@ -1646,7 +1646,7 @@ func Igen(n *Node, a *Node, res *Node) {
16461646

16471647
switch n.Op {
16481648
case ONAME:
1649-
if (n.Class&PHEAP != 0) || n.Class == PPARAMREF {
1649+
if n.Class == PPARAMREF {
16501650
break
16511651
}
16521652
*a = *n

src/cmd/compile/internal/gc/cplx.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ func Complexgen(n *Node, res *Node) {
405405
ODOTPTR,
406406
OINDEX,
407407
OIND,
408-
ONAME, // PHEAP or PPARAMREF var
408+
ONAME, // PPARAMREF var
409409
OCALLFUNC,
410410
OCALLMETH,
411411
OCALLINTER:

src/cmd/compile/internal/gc/esc.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,6 @@ func escassign(e *EscState, dst, src *Node, step *EscStep) {
10681068
OIND, // dst = *x
10691069
ODOTPTR, // dst = (*x).f
10701070
ONAME,
1071-
OPARAM,
10721071
ODDDARG,
10731072
OPTRLIT,
10741073
OARRAYLIT,
@@ -1835,20 +1834,20 @@ func escwalkBody(e *EscState, level Level, dst *Node, src *Node, step *EscStep,
18351834
}
18361835
if leaks {
18371836
src.Esc = EscHeap
1838-
addrescapes(src.Left)
18391837
if Debug['m'] != 0 && osrcesc != src.Esc {
18401838
p := src
18411839
if p.Left.Op == OCLOSURE {
18421840
p = p.Left // merely to satisfy error messages in tests
18431841
}
18441842
if Debug['m'] > 2 {
1845-
Warnl(src.Lineno, "%v escapes to heap, level=%v, dst.eld=%v, src.eld=%v",
1846-
Nconv(p, FmtShort), level, dstE.Escloopdepth, modSrcLoopdepth)
1843+
Warnl(src.Lineno, "%v escapes to heap, level=%v, dst=%v dst.eld=%v, src.eld=%v",
1844+
Nconv(p, FmtShort), level, dst, dstE.Escloopdepth, modSrcLoopdepth)
18471845
} else {
18481846
Warnl(src.Lineno, "%v escapes to heap", Nconv(p, FmtShort))
18491847
step.describe(src)
18501848
}
18511849
}
1850+
addrescapes(src.Left)
18521851
escwalkBody(e, level.dec(), dst, src.Left, e.stepWalk(dst, src.Left, why, step), modSrcLoopdepth)
18531852
extraloopdepth = modSrcLoopdepth // passes to recursive case, seems likely a no-op
18541853
} else {

src/cmd/compile/internal/gc/export.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func reexportdep(n *Node) {
121121
//print("reexportdep %+hN\n", n);
122122
switch n.Op {
123123
case ONAME:
124-
switch n.Class &^ PHEAP {
124+
switch n.Class {
125125
// methods will be printed along with their type
126126
// nodes for T.Method expressions
127127
case PFUNC:

src/cmd/compile/internal/gc/fmt.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ var classnames = []string{
218218
"Pxxx",
219219
"PEXTERN",
220220
"PAUTO",
221+
"PAUTOHEAP",
221222
"PPARAM",
222223
"PPARAMOUT",
223224
"PPARAMREF",
@@ -251,14 +252,10 @@ func jconv(n *Node, flag FmtFlag) string {
251252
}
252253

253254
if n.Class != 0 {
254-
s := ""
255-
if n.Class&PHEAP != 0 {
256-
s = ",heap"
257-
}
258-
if int(n.Class&^PHEAP) < len(classnames) {
259-
fmt.Fprintf(&buf, " class(%s%s)", classnames[n.Class&^PHEAP], s)
255+
if int(n.Class) < len(classnames) {
256+
fmt.Fprintf(&buf, " class(%s)", classnames[n.Class])
260257
} else {
261-
fmt.Fprintf(&buf, " class(%d?%s)", n.Class&^PHEAP, s)
258+
fmt.Fprintf(&buf, " class(%d?)", n.Class)
262259
}
263260
}
264261

@@ -798,8 +795,8 @@ func stmtfmt(n *Node) string {
798795
switch n.Op {
799796
case ODCL:
800797
if fmtmode == FExp {
801-
switch n.Left.Class &^ PHEAP {
802-
case PPARAM, PPARAMOUT, PAUTO:
798+
switch n.Left.Class {
799+
case PPARAM, PPARAMOUT, PAUTO, PAUTOHEAP:
803800
f += fmt.Sprintf("var %v %v", n.Left, n.Left.Type)
804801
goto ret
805802
}

src/cmd/compile/internal/gc/gen.go

Lines changed: 136 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -43,52 +43,39 @@ func addrescapes(n *Node) {
4343
break
4444
}
4545

46-
switch n.Class {
47-
case PPARAMREF:
46+
// A PPARAMREF is a closure reference.
47+
// Mark the thing it refers to as escaping.
48+
if n.Class == PPARAMREF {
4849
addrescapes(n.Name.Defn)
50+
break
51+
}
4952

50-
// if func param, need separate temporary
51-
// to hold heap pointer.
52-
// the function type has already been checked
53-
// (we're in the function body)
54-
// so the param already has a valid xoffset.
55-
56-
// expression to refer to stack copy
57-
case PPARAM, PPARAMOUT:
58-
n.Name.Param.Stackparam = Nod(OPARAM, n, nil)
59-
60-
n.Name.Param.Stackparam.Type = n.Type
61-
n.Name.Param.Stackparam.Addable = true
62-
if n.Xoffset == BADWIDTH {
63-
Fatalf("addrescapes before param assignment")
64-
}
65-
n.Name.Param.Stackparam.Xoffset = n.Xoffset
66-
fallthrough
67-
68-
case PAUTO:
69-
n.Class |= PHEAP
70-
71-
n.Addable = false
72-
n.Ullman = 2
73-
n.Xoffset = 0
74-
75-
// create stack variable to hold pointer to heap
76-
oldfn := Curfn
53+
if n.Class != PPARAM && n.Class != PPARAMOUT && n.Class != PAUTO {
54+
break
55+
}
7756

78-
Curfn = n.Name.Curfn
79-
if Curfn.Func.Closure != nil && Curfn.Op == OCLOSURE {
80-
Curfn = Curfn.Func.Closure
81-
}
82-
n.Name.Heapaddr = temp(Ptrto(n.Type))
83-
buf := fmt.Sprintf("&%v", n.Sym)
84-
n.Name.Heapaddr.Sym = Lookup(buf)
85-
n.Name.Heapaddr.Orig.Sym = n.Name.Heapaddr.Sym
86-
n.Esc = EscHeap
87-
if Debug['m'] != 0 {
88-
fmt.Printf("%v: moved to heap: %v\n", n.Line(), n)
89-
}
90-
Curfn = oldfn
57+
// This is a plain parameter or local variable that needs to move to the heap,
58+
// but possibly for the function outside the one we're compiling.
59+
// That is, if we have:
60+
//
61+
// func f(x int) {
62+
// func() {
63+
// global = &x
64+
// }
65+
// }
66+
//
67+
// then we're analyzing the inner closure but we need to move x to the
68+
// heap in f, not in the inner closure. Flip over to f before calling moveToHeap.
69+
oldfn := Curfn
70+
Curfn = n.Name.Curfn
71+
if Curfn.Func.Closure != nil && Curfn.Op == OCLOSURE {
72+
Curfn = Curfn.Func.Closure
9173
}
74+
ln := lineno
75+
lineno = Curfn.Lineno
76+
moveToHeap(n)
77+
Curfn = oldfn
78+
lineno = ln
9279

9380
case OIND, ODOTPTR:
9481
break
@@ -105,6 +92,110 @@ func addrescapes(n *Node) {
10592
}
10693
}
10794

95+
// isParamStackCopy reports whether this is the on-stack copy of a
96+
// function parameter that moved to the heap.
97+
func (n *Node) isParamStackCopy() bool {
98+
return n.Op == ONAME && (n.Class == PPARAM || n.Class == PPARAMOUT) && n.Name.Heapaddr != nil
99+
}
100+
101+
// isParamHeapCopy reports whether this is the on-heap copy of
102+
// a function parameter that moved to the heap.
103+
func (n *Node) isParamHeapCopy() bool {
104+
return n.Op == ONAME && n.Class == PAUTOHEAP && n.Name.Param.Stackcopy != nil
105+
}
106+
107+
// paramClass reports the parameter class (PPARAM or PPARAMOUT)
108+
// of the node, which may be an unmoved on-stack parameter
109+
// or the on-heap or on-stack copy of a parameter that moved to the heap.
110+
// If the node is not a parameter, paramClass returns Pxxx.
111+
func (n *Node) paramClass() Class {
112+
if n.Op != ONAME {
113+
return Pxxx
114+
}
115+
if n.Class == PPARAM || n.Class == PPARAMOUT {
116+
return n.Class
117+
}
118+
if n.isParamHeapCopy() {
119+
return n.Name.Param.Stackcopy.Class
120+
}
121+
return Pxxx
122+
}
123+
124+
// moveToHeap records the parameter or local variable n as moved to the heap.
125+
func moveToHeap(n *Node) {
126+
if Debug['r'] != 0 {
127+
Dump("MOVE", n)
128+
}
129+
if compiling_runtime {
130+
Yyerror("%v escapes to heap, not allowed in runtime.", n)
131+
}
132+
if n.Class == PAUTOHEAP {
133+
Dump("n", n)
134+
Fatalf("double move to heap")
135+
}
136+
137+
// Allocate a local stack variable to hold the pointer to the heap copy.
138+
// temp will add it to the function declaration list automatically.
139+
heapaddr := temp(Ptrto(n.Type))
140+
heapaddr.Sym = Lookup("&" + n.Sym.Name)
141+
heapaddr.Orig.Sym = heapaddr.Sym
142+
143+
// Parameters have a local stack copy used at function start/end
144+
// in addition to the copy in the heap that may live longer than
145+
// the function.
146+
if n.Class == PPARAM || n.Class == PPARAMOUT {
147+
if n.Xoffset == BADWIDTH {
148+
Fatalf("addrescapes before param assignment")
149+
}
150+
151+
// We rewrite n below to be a heap variable (indirection of heapaddr).
152+
// Preserve a copy so we can still write code referring to the original,
153+
// and substitute that copy into the function declaration list
154+
// so that analyses of the local (on-stack) variables use it.
155+
stackcopy := Nod(ONAME, nil, nil)
156+
stackcopy.Sym = n.Sym
157+
stackcopy.Type = n.Type
158+
stackcopy.Xoffset = n.Xoffset
159+
stackcopy.Class = n.Class
160+
stackcopy.Name.Heapaddr = heapaddr
161+
if n.Class == PPARAM {
162+
stackcopy.SetNotLiveAtEnd(true)
163+
}
164+
n.Name.Param.Stackcopy = stackcopy
165+
166+
// Substitute the stackcopy into the function variable list so that
167+
// liveness and other analyses use the underlying stack slot
168+
// and not the now-pseudo-variable n.
169+
found := false
170+
for i, d := range Curfn.Func.Dcl {
171+
if d == n {
172+
Curfn.Func.Dcl[i] = stackcopy
173+
found = true
174+
break
175+
}
176+
// Parameters are before locals, so can stop early.
177+
// This limits the search even in functions with many local variables.
178+
if d.Class == PAUTO {
179+
break
180+
}
181+
}
182+
if !found {
183+
Fatalf("cannot find %v in local variable list", n)
184+
}
185+
Curfn.Func.Dcl = append(Curfn.Func.Dcl, n)
186+
}
187+
188+
// Modify n in place so that uses of n now mean indirection of the heapaddr.
189+
n.Class = PAUTOHEAP
190+
n.Ullman = 2
191+
n.Xoffset = 0
192+
n.Name.Heapaddr = heapaddr
193+
n.Esc = EscHeap
194+
if Debug['m'] != 0 {
195+
fmt.Printf("%v: moved to heap: %v\n", n.Line(), n)
196+
}
197+
}
198+
108199
func clearlabels() {
109200
for _, l := range labellist {
110201
l.Sym.Label = nil
@@ -243,16 +334,9 @@ func cgen_dcl(n *Node) {
243334
Fatalf("cgen_dcl")
244335
}
245336

246-
if n.Class&PHEAP == 0 {
247-
return
337+
if n.Class == PAUTOHEAP {
338+
Fatalf("cgen_dcl %v", n)
248339
}
249-
if compiling_runtime {
250-
Yyerror("%v escapes to heap, not allowed in runtime.", n)
251-
}
252-
if prealloc[n] == nil {
253-
prealloc[n] = callnew(n.Type)
254-
}
255-
Cgen_as(n.Name.Heapaddr, prealloc[n])
256340
}
257341

258342
// generate discard of value
@@ -263,7 +347,7 @@ func cgen_discard(nr *Node) {
263347

264348
switch nr.Op {
265349
case ONAME:
266-
if nr.Class&PHEAP == 0 && nr.Class != PEXTERN && nr.Class != PFUNC && nr.Class != PPARAMREF {
350+
if nr.Class != PAUTOHEAP && nr.Class != PEXTERN && nr.Class != PFUNC && nr.Class != PPARAMREF {
267351
gused(nr)
268352
}
269353

@@ -908,11 +992,6 @@ func Cgen_as_wb(nl, nr *Node, wb bool) {
908992
}
909993

910994
if nr == nil || iszero(nr) {
911-
// heaps should already be clear
912-
if nr == nil && (nl.Class&PHEAP != 0) {
913-
return
914-
}
915-
916995
tl := nl.Type
917996
if tl == nil {
918997
return

0 commit comments

Comments
 (0)