Skip to content

Commit 3572c64

Browse files
committed
cmd/compile: keep pointer input arguments live throughout function
Introduce a KeepAlive op which makes sure that its argument is kept live until the KeepAlive. Use KeepAlive to mark pointer input arguments as live after each function call and at each return. We do this change only for pointer arguments. Those are the critical ones to handle because they might have finalizers. Doing compound arguments (slices, structs, ...) is more complicated because we would need to track field liveness individually (we do that for auto variables now, but inputs requires extra trickery). Turn off the automatic marking of args as live. That way, when args are explicitly nulled, plive will know that the original argument is dead. The KeepAlive op will be the eventual implementation of runtime.KeepAlive. Fixes #15277 Change-Id: I5f223e65d99c9f8342c03fbb1512c4d363e903e5 Reviewed-on: https://go-review.googlesource.com/22365 Reviewed-by: David Chase <[email protected]> Reviewed-by: Russ Cox <[email protected]>
1 parent d520226 commit 3572c64

File tree

10 files changed

+145
-34
lines changed

10 files changed

+145
-34
lines changed

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

+12
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,18 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
878878
gc.Gvarkill(v.Aux.(*gc.Node))
879879
case ssa.OpVarLive:
880880
gc.Gvarlive(v.Aux.(*gc.Node))
881+
case ssa.OpKeepAlive:
882+
if !v.Args[0].Type.IsPtrShaped() {
883+
v.Fatalf("keeping non-pointer alive %v", v.Args[0])
884+
}
885+
n, off := gc.AutoVar(v.Args[0])
886+
if n == nil {
887+
v.Fatalf("KeepLive with non-spilled value %s %s", v, v.Args[0])
888+
}
889+
if off != 0 {
890+
v.Fatalf("KeepLive with non-zero offset spill location %s:%d", n, off)
891+
}
892+
gc.Gvarlive(n)
881893
case ssa.OpAMD64LoweredNilCheck:
882894
// Optimization - if the subsequent block has a load or store
883895
// at the same address, we don't need to issue this instruction.

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

+3-18
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,9 @@ func progeffects(prog *obj.Prog, vars []*Node, uevar bvec, varkill bvec, avarini
569569
for i, node := range vars {
570570
switch node.Class &^ PHEAP {
571571
case PPARAM:
572-
bvset(uevar, int32(i))
572+
if !node.NotLiveAtEnd() {
573+
bvset(uevar, int32(i))
574+
}
573575

574576
// If the result had its address taken, it is being tracked
575577
// by the avarinit code, which does not use uevar.
@@ -980,23 +982,6 @@ func onebitlivepointermap(lv *Liveness, liveout bvec, vars []*Node, args bvec, l
980982
onebitwalktype1(node.Type, &xoffset, args)
981983
}
982984
}
983-
984-
// The node list only contains declared names.
985-
// If the receiver or arguments are unnamed, they will be omitted
986-
// from the list above. Preserve those values - even though they are unused -
987-
// in order to keep their addresses live for use in stack traces.
988-
thisargtype := lv.fn.Type.Recvs()
989-
990-
if thisargtype != nil {
991-
xoffset = 0
992-
onebitwalktype1(thisargtype, &xoffset, args)
993-
}
994-
995-
inargtype := lv.fn.Type.Params()
996-
if inargtype != nil {
997-
xoffset = 0
998-
onebitwalktype1(inargtype, &xoffset, args)
999-
}
1000985
}
1001986

1002987
// Construct a disembodied instruction.

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

+28-2
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ func buildssa(fn *Node) *ssa.Func {
161161
// the function.
162162
s.returns = append(s.returns, n)
163163
}
164+
if n.Class == PPARAM && s.canSSA(n) && n.Type.IsPtrShaped() {
165+
s.ptrargs = append(s.ptrargs, n)
166+
n.SetNotLiveAtEnd(true) // SSA takes care of this explicitly
167+
}
164168
case PAUTO | PHEAP:
165169
// TODO this looks wrong for PAUTO|PHEAP, no vardef, but also no definition
166170
aux := s.lookupSymbol(n, &ssa.AutoSymbol{Typ: n.Type, Node: n})
@@ -293,6 +297,10 @@ type state struct {
293297
// list of PPARAMOUT (return) variables. Does not include PPARAM|PHEAP vars.
294298
returns []*Node
295299

300+
// list of PPARAM SSA-able pointer-shaped args. We ensure these are live
301+
// throughout the function to help users avoid premature finalizers.
302+
ptrargs []*Node
303+
296304
cgoUnsafeArgs bool
297305
noWB bool
298306
WBLineno int32 // line number of first write barrier. 0=no write barriers
@@ -988,8 +996,7 @@ func (s *state) exit() *ssa.Block {
988996

989997
// Store SSAable PPARAMOUT variables back to stack locations.
990998
for _, n := range s.returns {
991-
aux := &ssa.ArgSymbol{Typ: n.Type, Node: n}
992-
addr := s.newValue1A(ssa.OpAddr, Ptrto(n.Type), aux, s.sp)
999+
addr := s.decladdrs[n]
9931000
val := s.variable(n, n.Type)
9941001
s.vars[&memVar] = s.newValue1A(ssa.OpVarDef, ssa.TypeMem, n, s.mem())
9951002
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, n.Type.Size(), addr, val, s.mem())
@@ -998,6 +1005,16 @@ func (s *state) exit() *ssa.Block {
9981005
// currently.
9991006
}
10001007

1008+
// Keep input pointer args live until the return. This is a bandaid
1009+
// fix for 1.7 for what will become in 1.8 explicit runtime.KeepAlive calls.
1010+
// For <= 1.7 we guarantee that pointer input arguments live to the end of
1011+
// the function to prevent premature (from the user's point of view)
1012+
// execution of finalizers. See issue 15277.
1013+
// TODO: remove for 1.8?
1014+
for _, n := range s.ptrargs {
1015+
s.vars[&memVar] = s.newValue2(ssa.OpKeepAlive, ssa.TypeMem, s.variable(n, n.Type), s.mem())
1016+
}
1017+
10011018
// Do actual return.
10021019
m := s.mem()
10031020
b := s.endBlock()
@@ -2648,6 +2665,10 @@ func (s *state) call(n *Node, k callKind) *ssa.Value {
26482665

26492666
// Start exit block, find address of result.
26502667
s.startBlock(bNext)
2668+
// Keep input pointer args live across calls. This is a bandaid until 1.8.
2669+
for _, n := range s.ptrargs {
2670+
s.vars[&memVar] = s.newValue2(ssa.OpKeepAlive, ssa.TypeMem, s.variable(n, n.Type), s.mem())
2671+
}
26512672
res := n.Left.Type.Results()
26522673
if res.NumFields() == 0 || k != callNormal {
26532674
// call has no return value. Continue with the next statement.
@@ -2997,6 +3018,11 @@ func (s *state) rtcall(fn *Node, returns bool, results []*Type, args ...*ssa.Val
29973018
b.AddEdgeTo(bNext)
29983019
s.startBlock(bNext)
29993020

3021+
// Keep input pointer args live across calls. This is a bandaid until 1.8.
3022+
for _, n := range s.ptrargs {
3023+
s.vars[&memVar] = s.newValue2(ssa.OpKeepAlive, ssa.TypeMem, s.variable(n, n.Type), s.mem())
3024+
}
3025+
30003026
// Load results
30013027
res := make([]*ssa.Value, len(results))
30023028
for i, t := range results {

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

+31-5
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,37 @@ type Node struct {
6868
Used bool
6969
Isddd bool // is the argument variadic
7070
Implicit bool
71-
Addrtaken bool // address taken, even if not moved to heap
72-
Assigned bool // is the variable ever assigned to
73-
Likely int8 // likeliness of if statement
74-
Hasbreak bool // has break statement
75-
hasVal int8 // +1 for Val, -1 for Opt, 0 for not yet set
71+
Addrtaken bool // address taken, even if not moved to heap
72+
Assigned bool // is the variable ever assigned to
73+
Likely int8 // likeliness of if statement
74+
hasVal int8 // +1 for Val, -1 for Opt, 0 for not yet set
75+
flags uint8 // TODO: store more bool fields in this flag field
76+
}
77+
78+
const (
79+
hasBreak = 1 << iota
80+
notLiveAtEnd
81+
)
82+
83+
func (n *Node) HasBreak() bool {
84+
return n.flags&hasBreak != 0
85+
}
86+
func (n *Node) SetHasBreak(b bool) {
87+
if b {
88+
n.flags |= hasBreak
89+
} else {
90+
n.flags &^= hasBreak
91+
}
92+
}
93+
func (n *Node) NotLiveAtEnd() bool {
94+
return n.flags&notLiveAtEnd != 0
95+
}
96+
func (n *Node) SetNotLiveAtEnd(b bool) {
97+
if b {
98+
n.flags |= notLiveAtEnd
99+
} else {
100+
n.flags &^= notLiveAtEnd
101+
}
76102
}
77103

78104
// Val returns the Val for the node.

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -3786,12 +3786,12 @@ func markbreak(n *Node, implicit *Node) {
37863786
case OBREAK:
37873787
if n.Left == nil {
37883788
if implicit != nil {
3789-
implicit.Hasbreak = true
3789+
implicit.SetHasBreak(true)
37903790
}
37913791
} else {
37923792
lab := n.Left.Sym.Label
37933793
if lab != nil {
3794-
lab.Def.Hasbreak = true
3794+
lab.Def.SetHasBreak(true)
37953795
}
37963796
}
37973797

@@ -3867,7 +3867,7 @@ func (n *Node) isterminating() bool {
38673867
if n.Left != nil {
38683868
return false
38693869
}
3870-
if n.Hasbreak {
3870+
if n.HasBreak() {
38713871
return false
38723872
}
38733873
return true
@@ -3876,7 +3876,7 @@ func (n *Node) isterminating() bool {
38763876
return n.Nbody.isterminating() && n.Rlist.isterminating()
38773877

38783878
case OSWITCH, OTYPESW, OSELECT:
3879-
if n.Hasbreak {
3879+
if n.HasBreak() {
38803880
return false
38813881
}
38823882
def := 0

src/cmd/compile/internal/ssa/gen/genericOps.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -382,9 +382,9 @@ var genericOps = []opData{
382382
{name: "ComplexImag", argLength: 1}, // imag(arg0)
383383

384384
// Strings
385-
{name: "StringMake", argLength: 2}, // arg0=ptr, arg1=len
386-
{name: "StringPtr", argLength: 1}, // ptr(arg0)
387-
{name: "StringLen", argLength: 1}, // len(arg0)
385+
{name: "StringMake", argLength: 2}, // arg0=ptr, arg1=len
386+
{name: "StringPtr", argLength: 1, typ: "BytePtr"}, // ptr(arg0)
387+
{name: "StringLen", argLength: 1, typ: "Int"}, // len(arg0)
388388

389389
// Interfaces
390390
{name: "IMake", argLength: 2}, // arg0=itab, arg1=data
@@ -407,14 +407,15 @@ var genericOps = []opData{
407407
{name: "LoadReg", argLength: 1},
408408

409409
// Used during ssa construction. Like Copy, but the arg has not been specified yet.
410-
{name: "FwdRef"},
410+
{name: "FwdRef", aux: "Sym"},
411411

412412
// Unknown value. Used for Values whose values don't matter because they are dead code.
413413
{name: "Unknown"},
414414

415415
{name: "VarDef", argLength: 1, aux: "Sym", typ: "Mem"}, // aux is a *gc.Node of a variable that is about to be initialized. arg0=mem, returns mem
416416
{name: "VarKill", argLength: 1, aux: "Sym"}, // aux is a *gc.Node of a variable that is known to be dead. arg0=mem, returns mem
417417
{name: "VarLive", argLength: 1, aux: "Sym"}, // aux is a *gc.Node of a variable that must be kept live. arg0=mem, returns mem
418+
{name: "KeepAlive", argLength: 2, typ: "Mem"}, // arg[0] is a value that must be kept alive until this mark. arg[1]=mem, returns mem
418419
}
419420

420421
// kind control successors implicit exit

src/cmd/compile/internal/ssa/lower.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func checkLower(f *Func) {
2121
continue // lowered
2222
}
2323
switch v.Op {
24-
case OpSP, OpSB, OpInitMem, OpArg, OpPhi, OpVarDef, OpVarKill, OpVarLive:
24+
case OpSP, OpSB, OpInitMem, OpArg, OpPhi, OpVarDef, OpVarKill, OpVarLive, OpKeepAlive:
2525
continue // ok not to lower
2626
}
2727
s := "not lowered: " + v.Op.String() + " " + v.Type.SimpleString()

src/cmd/compile/internal/ssa/opGen.go

+7
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,7 @@ const (
617617
OpVarDef
618618
OpVarKill
619619
OpVarLive
620+
OpKeepAlive
620621
)
621622

622623
var opcodeTable = [...]opInfo{
@@ -5357,6 +5358,7 @@ var opcodeTable = [...]opInfo{
53575358
},
53585359
{
53595360
name: "FwdRef",
5361+
auxType: auxSym,
53605362
argLen: 0,
53615363
generic: true,
53625364
},
@@ -5383,6 +5385,11 @@ var opcodeTable = [...]opInfo{
53835385
argLen: 1,
53845386
generic: true,
53855387
},
5388+
{
5389+
name: "KeepAlive",
5390+
argLen: 2,
5391+
generic: true,
5392+
},
53865393
}
53875394

53885395
func (o Op) Asm() obj.As { return opcodeTable[o].asm }

src/cmd/compile/internal/ssa/regalloc.go

+17
Original file line numberDiff line numberDiff line change
@@ -941,11 +941,28 @@ func (s *regAllocState) regalloc(f *Func) {
941941
s.advanceUses(v)
942942
continue
943943
}
944+
if v.Op == OpKeepAlive {
945+
// Make sure the argument to v is still live here.
946+
s.advanceUses(v)
947+
vi := &s.values[v.Args[0].ID]
948+
if vi.spillUsed {
949+
// Use the spill location.
950+
v.SetArg(0, vi.spill)
951+
b.Values = append(b.Values, v)
952+
} else {
953+
// No need to keep unspilled values live.
954+
// These are typically rematerializeable constants like nil,
955+
// or values of a variable that were modified since the last call.
956+
v.Args[0].Uses--
957+
}
958+
continue
959+
}
944960
regspec := opcodeTable[v.Op].reg
945961
if len(regspec.inputs) == 0 && len(regspec.outputs) == 0 {
946962
// No register allocation required (or none specified yet)
947963
s.freeRegs(regspec.clobbers)
948964
b.Values = append(b.Values, v)
965+
s.advanceUses(v)
949966
continue
950967
}
951968

test/fixedbugs/issue15277.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// run
2+
3+
// Copyright 2016 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package main
8+
9+
import "runtime"
10+
11+
type big [10 << 20]byte
12+
13+
func f(x *big, start int64) {
14+
if delta := inuse() - start; delta < 9<<20 {
15+
println("after alloc: expected delta at least 9MB, got: ", delta)
16+
}
17+
x = nil
18+
if delta := inuse() - start; delta > 1<<20 {
19+
println("after drop: expected delta below 1MB, got: ", delta)
20+
}
21+
x = new(big)
22+
if delta := inuse() - start; delta < 9<<20 {
23+
println("second alloc: expected delta at least 9MB, got: ", delta)
24+
}
25+
}
26+
27+
func main() {
28+
x := inuse()
29+
f(new(big), x)
30+
}
31+
32+
func inuse() int64 {
33+
runtime.GC()
34+
var st runtime.MemStats
35+
runtime.ReadMemStats(&st)
36+
return int64(st.Alloc)
37+
}

0 commit comments

Comments
 (0)