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 391425ae. 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 <khr@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go
index 7bae8b4..8d06f1e 100644
--- a/src/cmd/compile/internal/gc/ssa.go
+++ b/src/cmd/compile/internal/gc/ssa.go
@@ -165,23 +165,15 @@
s.ptrargs = append(s.ptrargs, n)
n.SetNotLiveAtEnd(true) // SSA takes care of this explicitly
}
- case PAUTO | PHEAP:
- // TODO this looks wrong for PAUTO|PHEAP, no vardef, but also no definition
- aux := s.lookupSymbol(n, &ssa.AutoSymbol{Typ: n.Type, Node: n})
- s.decladdrs[n] = s.entryNewValue1A(ssa.OpAddr, Ptrto(n.Type), aux, s.sp)
- case PPARAM | PHEAP, PPARAMOUT | PHEAP:
- // This ends up wrong, have to do it at the PARAM node instead.
case PAUTO:
// processed at each use, to prevent Addr coming
// before the decl.
+ case PAUTOHEAP:
+ // moved to heap - already handled by frontend
case PFUNC:
// local function - already handled by frontend
default:
- str := ""
- if n.Class&PHEAP != 0 {
- str = ",heap"
- }
- s.Unimplementedf("local variable with class %s%s unimplemented", classnames[n.Class&^PHEAP], str)
+ s.Unimplementedf("local variable with class %s unimplemented", classnames[n.Class])
}
}
@@ -294,7 +286,7 @@
// list of FwdRef values.
fwdRefs []*ssa.Value
- // list of PPARAMOUT (return) variables. Does not include PPARAM|PHEAP vars.
+ // list of PPARAMOUT (return) variables.
returns []*Node
// list of PPARAM SSA-able pointer-shaped args. We ensure these are live
@@ -593,24 +585,9 @@
return
case ODCL:
- if n.Left.Class&PHEAP == 0 {
- return
+ if n.Left.Class == PAUTOHEAP {
+ Fatalf("DCL %v", n)
}
- if compiling_runtime {
- Fatalf("%v escapes to heap, not allowed in runtime.", n)
- }
-
- // TODO: the old pass hides the details of PHEAP
- // variables behind ONAME nodes. Figure out if it's better
- // to rewrite the tree and make the heapaddr construct explicit
- // or to keep this detail hidden behind the scenes.
- palloc := prealloc[n.Left]
- if palloc == nil {
- palloc = callnew(n.Left.Type)
- prealloc[n.Left] = palloc
- }
- r := s.expr(palloc)
- s.assign(n.Left.Name.Heapaddr, r, false, false, n.Lineno, 0)
case OLABEL:
sym := n.Left.Sym
@@ -1451,9 +1428,6 @@
case OCFUNC:
aux := s.lookupSymbol(n, &ssa.ExternSymbol{Typ: n.Type, Sym: n.Left.Sym})
return s.entryNewValue1A(ssa.OpAddr, n.Type, aux, s.sb)
- case OPARAM:
- addr := s.addr(n, false)
- return s.newValue2(ssa.OpLoad, n.Left.Type, addr, s.mem())
case ONAME:
if n.Class == PFUNC {
// "value" of a function is the address of the function's closure
@@ -2749,10 +2723,10 @@
// that cse works on their addresses
aux := s.lookupSymbol(n, &ssa.ArgSymbol{Typ: n.Type, Node: n})
return s.newValue1A(ssa.OpAddr, t, aux, s.sp)
- case PAUTO | PHEAP, PPARAM | PHEAP, PPARAMOUT | PHEAP, PPARAMREF:
+ case PPARAMREF:
return s.expr(n.Name.Heapaddr)
default:
- s.Unimplementedf("variable address class %v not implemented", n.Class)
+ s.Unimplementedf("variable address class %v not implemented", classnames[n.Class])
return nil
}
case OINDREG:
@@ -2795,17 +2769,6 @@
case OCLOSUREVAR:
return s.newValue1I(ssa.OpOffPtr, t, n.Xoffset,
s.entryNewValue0(ssa.OpGetClosurePtr, Ptrto(Types[TUINT8])))
- case OPARAM:
- p := n.Left
- if p.Op != ONAME || !(p.Class == PPARAM|PHEAP || p.Class == PPARAMOUT|PHEAP) {
- s.Fatalf("OPARAM not of ONAME,{PPARAM,PPARAMOUT}|PHEAP, instead %s", nodedump(p, 0))
- }
-
- // Recover original offset to address passed-in param value.
- original_p := *p
- original_p.Xoffset = n.Xoffset
- aux := &ssa.ArgSymbol{Typ: n.Type, Node: &original_p}
- return s.entryNewValue1A(ssa.OpAddr, t, aux, s.sp)
case OCONVNOP:
addr := s.addr(n.Left, bounded)
return s.newValue1(ssa.OpCopy, t, addr) // ensure that addr has the right type
@@ -2833,9 +2796,12 @@
if n.Addrtaken {
return false
}
- if n.Class&PHEAP != 0 {
+ if n.isParamHeapCopy() {
return false
}
+ if n.Class == PAUTOHEAP {
+ Fatalf("canSSA of PAUTOHEAP %v", n)
+ }
switch n.Class {
case PEXTERN, PPARAMREF:
// TODO: maybe treat PPARAMREF with an Arg-like op to read from closure?