Skip to content

Commit ca4089a

Browse files
committed
cmd/compile: args no longer live until end-of-function
We're dropping this behavior in favor of runtime.KeepAlive. Implement runtime.KeepAlive as an intrinsic. Update #15843 Change-Id: Ib60225bd30d6770ece1c3c7d1339a06aa25b1cbc Reviewed-on: https://go-review.googlesource.com/28310 Run-TryBot: Keith Randall <khr@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: David Chase <drchase@google.com>
1 parent faf611a commit ca4089a

File tree

8 files changed

+25
-69
lines changed

8 files changed

+25
-69
lines changed

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,6 @@ func moveToHeap(n *Node) {
133133
stackcopy.Xoffset = n.Xoffset
134134
stackcopy.Class = n.Class
135135
stackcopy.Name.Heapaddr = heapaddr
136-
if n.Class == PPARAM {
137-
stackcopy.SetNotLiveAtEnd(true)
138-
}
139136
if n.Class == PPARAMOUT {
140137
// Make sure the pointer to the heap copy is kept live throughout the function.
141138
// The function could panic at any point, and then a defer could recover.

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

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -543,31 +543,16 @@ func progeffects(prog *obj.Prog, vars []*Node, uevar bvec, varkill bvec, avarini
543543
bvresetall(avarinit)
544544

545545
if prog.As == obj.ARET {
546-
// Return instructions implicitly read all the arguments. For
547-
// the sake of correctness, out arguments must be read. For the
548-
// sake of backtrace quality, we read in arguments as well.
549-
//
550-
// A return instruction with a p.to is a tail return, which brings
551-
// the stack pointer back up (if it ever went down) and then jumps
552-
// to a new function entirely. That form of instruction must read
553-
// all the parameters for correctness, and similarly it must not
554-
// read the out arguments - they won't be set until the new
555-
// function runs.
546+
// Return instructions read all of the out arguments.
556547
for i, node := range vars {
557548
switch node.Class {
558-
case PPARAM:
559-
if !node.NotLiveAtEnd() {
560-
bvset(uevar, int32(i))
561-
}
562-
563-
// If the result had its address taken, it is being tracked
549+
// If the result had its address taken, it is being tracked
564550
// by the avarinit code, which does not use uevar.
565551
// If we added it to uevar too, we'd not see any kill
566552
// and decide that the variable was live entry, which it is not.
567553
// So only use uevar in the non-addrtaken case.
568-
// The p.to.type == thearch.D_NONE limits the bvset to
569-
// non-tail-call return instructions; see note above
570-
// the for loop for details.
554+
// The p.to.type == obj.TYPE_NONE limits the bvset to
555+
// non-tail-call return instructions; see note below for details.
571556
case PPARAMOUT:
572557
if !node.Addrtaken && prog.To.Type == obj.TYPE_NONE {
573558
bvset(uevar, int32(i))
@@ -577,6 +562,12 @@ func progeffects(prog *obj.Prog, vars []*Node, uevar bvec, varkill bvec, avarini
577562

578563
return
579564
}
565+
// A return instruction with a p.to is a tail return, which brings
566+
// the stack pointer back up (if it ever went down) and then jumps
567+
// to a new function entirely. That form of instruction must read
568+
// all the parameters for correctness, and similarly it must not
569+
// read the out arguments - they won't be set until the new
570+
// function runs.
580571
if prog.As == obj.AJMP && prog.To.Type == obj.TYPE_MEM && prog.To.Name == obj.NAME_EXTERN {
581572
// This is a tail call. Ensure the arguments are still alive.
582573
// See issue 16016.

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

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,6 @@ func buildssa(fn *Node) *ssa.Func {
102102
// the function.
103103
s.returns = append(s.returns, n)
104104
}
105-
if n.Class == PPARAM && s.canSSA(n) && n.Type.IsPtrShaped() {
106-
s.ptrargs = append(s.ptrargs, n)
107-
n.SetNotLiveAtEnd(true) // SSA takes care of this explicitly
108-
}
109105
case PAUTO:
110106
// processed at each use, to prevent Addr coming
111107
// before the decl.
@@ -230,10 +226,6 @@ type state struct {
230226
// list of PPARAMOUT (return) variables.
231227
returns []*Node
232228

233-
// list of PPARAM SSA-able pointer-shaped args. We ensure these are live
234-
// throughout the function to help users avoid premature finalizers.
235-
ptrargs []*Node
236-
237229
cgoUnsafeArgs bool
238230
noWB bool
239231
WBLineno int32 // line number of first write barrier. 0=no write barriers
@@ -945,16 +937,6 @@ func (s *state) exit() *ssa.Block {
945937
// currently.
946938
}
947939

948-
// Keep input pointer args live until the return. This is a bandaid
949-
// fix for 1.7 for what will become in 1.8 explicit runtime.KeepAlive calls.
950-
// For <= 1.7 we guarantee that pointer input arguments live to the end of
951-
// the function to prevent premature (from the user's point of view)
952-
// execution of finalizers. See issue 15277.
953-
// TODO: remove for 1.8?
954-
for _, n := range s.ptrargs {
955-
s.vars[&memVar] = s.newValue2(ssa.OpKeepAlive, ssa.TypeMem, s.variable(n, n.Type), s.mem())
956-
}
957-
958940
// Do actual return.
959941
m := s.mem()
960942
b := s.endBlock()
@@ -2528,6 +2510,11 @@ func intrinsicInit() {
25282510
len := s.newValue1(ssa.OpSliceLen, Types[TINT], slice)
25292511
return s.newValue2(ssa.OpStringMake, n.Type, ptr, len)
25302512
})),
2513+
intrinsicKey{"runtime", "KeepAlive"}: func(s *state, n *Node) *ssa.Value {
2514+
data := s.newValue1(ssa.OpIData, ptrto(Types[TUINT8]), s.intrinsicFirstArg(n))
2515+
s.vars[&memVar] = s.newValue2(ssa.OpKeepAlive, ssa.TypeMem, data, s.mem())
2516+
return nil
2517+
},
25312518

25322519
/******** runtime/internal/sys ********/
25332520
intrinsicKey{"runtime/internal/sys", "Ctz32"}: enableOnArch(func(s *state, n *Node) *ssa.Value {
@@ -2892,11 +2879,6 @@ func (s *state) call(n *Node, k callKind) *ssa.Value {
28922879
s.startBlock(bNext)
28932880
}
28942881

2895-
// Keep input pointer args live across calls. This is a bandaid until 1.8.
2896-
for _, n := range s.ptrargs {
2897-
s.vars[&memVar] = s.newValue2(ssa.OpKeepAlive, ssa.TypeMem, s.variable(n, n.Type), s.mem())
2898-
}
2899-
// Find address of result.
29002882
res := n.Left.Type.Results()
29012883
if res.NumFields() == 0 || k != callNormal {
29022884
// call has no return value. Continue with the next statement.
@@ -3243,11 +3225,6 @@ func (s *state) rtcall(fn *Node, returns bool, results []*Type, args ...*ssa.Val
32433225
return nil
32443226
}
32453227

3246-
// Keep input pointer args live across calls. This is a bandaid until 1.8.
3247-
for _, n := range s.ptrargs {
3248-
s.vars[&memVar] = s.newValue2(ssa.OpKeepAlive, ssa.TypeMem, s.variable(n, n.Type), s.mem())
3249-
}
3250-
32513228
// Load results
32523229
res := make([]*ssa.Value, len(results))
32533230
for i, t := range results {

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ type Node struct {
7777

7878
const (
7979
hasBreak = 1 << iota
80-
notLiveAtEnd
8180
isClosureVar
8281
isOutputParamHeapAddr
8382
noInline // used internally by inliner to indicate that a function call should not be inlined; set for OCALLFUNC and OCALLMETH only
@@ -93,16 +92,6 @@ func (n *Node) SetHasBreak(b bool) {
9392
n.flags &^= hasBreak
9493
}
9594
}
96-
func (n *Node) NotLiveAtEnd() bool {
97-
return n.flags&notLiveAtEnd != 0
98-
}
99-
func (n *Node) SetNotLiveAtEnd(b bool) {
100-
if b {
101-
n.flags |= notLiveAtEnd
102-
} else {
103-
n.flags &^= notLiveAtEnd
104-
}
105-
}
10695
func (n *Node) isClosureVar() bool {
10796
return n.flags&isClosureVar != 0
10897
}

test/fixedbugs/issue15277.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ func f(x *big, start int64) {
1515
if delta := inuse() - start; delta < 9<<20 {
1616
println("after alloc: expected delta at least 9MB, got: ", delta)
1717
}
18+
runtime.KeepAlive(x)
1819
x = nil
1920
if delta := inuse() - start; delta > 1<<20 {
2021
println("after drop: expected delta below 1MB, got: ", delta)
@@ -23,6 +24,7 @@ func f(x *big, start int64) {
2324
if delta := inuse() - start; delta < 9<<20 {
2425
println("second alloc: expected delta at least 9MB, got: ", delta)
2526
}
27+
runtime.KeepAlive(x)
2628
}
2729

2830
func main() {

test/fixedbugs/issue15747.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ type T struct{ M string }
1717

1818
var b bool
1919

20-
func f1(q *Q, xx []byte) interface{} { // ERROR "live at entry to f1: q xx" "live at call to newobject: q xx" "live at call to writebarrierptr: q &xx"
20+
func f1(q *Q, xx []byte) interface{} { // ERROR "live at entry to f1: xx" "live at call to newobject: xx" "live at call to writebarrierptr: &xx"
2121
// xx was copied from the stack to the heap on the previous line:
2222
// xx was live for the first two prints but then it switched to &xx
2323
// being live. We should not see plain xx again.
2424
if b {
25-
global = &xx // ERROR "live at call to writebarrierptr: q &xx[^x]*$"
25+
global = &xx // ERROR "live at call to writebarrierptr: &xx[^x]*$"
2626
}
27-
xx, _, err := f2(xx, 5) // ERROR "live at call to newobject: q( d)? &xx( odata.ptr)?" "live at call to writebarrierptr: q (e|err.data err.type)$"
27+
xx, _, err := f2(xx, 5) // ERROR "live at call to newobject:( d)? &xx( odata.ptr)?" "live at call to writebarrierptr: (e|err.data err.type)$"
2828
if err != nil {
2929
return err
3030
}

test/live.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -651,8 +651,8 @@ func good40() {
651651
}
652652

653653
func ddd1(x, y *int) { // ERROR "live at entry to ddd1: x y$"
654-
ddd2(x, y) // ERROR "live at call to ddd2: x y autotmp_[0-9]+$"
655-
printnl() // ERROR "live at call to printnl: x y$"
654+
ddd2(x, y) // ERROR "live at call to ddd2: autotmp_[0-9]+$"
655+
printnl()
656656
// Note: no autotmp live at printnl. See issue 16996.
657657
}
658658
func ddd2(a ...*int) { // ERROR "live at entry to ddd2: a$"

test/uintptrescapes2.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ func F1(a uintptr) {} // ERROR "escaping uintptr"
1818

1919
//go:uintptrescapes
2020
//go:noinline
21-
func F2(a ...uintptr) {} // ERROR "escaping ...uintptr" "live at entry" "a does not escape"
21+
func F2(a ...uintptr) {} // ERROR "escaping ...uintptr" "a does not escape"
2222

2323
func G() {
24-
var t int // ERROR "moved to heap"
24+
var t int // ERROR "moved to heap"
2525
F1(uintptr(unsafe.Pointer(&t))) // ERROR "live at call to F1: autotmp" "&t escapes to heap"
2626
}
2727

2828
func H() {
29-
var v int // ERROR "moved to heap"
29+
var v int // ERROR "moved to heap"
3030
F2(0, 1, uintptr(unsafe.Pointer(&v)), 2) // ERROR "live at call to newobject: autotmp" "live at call to F2: autotmp" "escapes to heap"
3131
}

0 commit comments

Comments
 (0)