Skip to content

Commit ddc6b64

Browse files
committed
cmd/compile: fix defer/deferreturn
Make sure we do any just-before-return cleanup on all paths out of a function, including when recovering. Each exit path should include deferreturn (if there are any defers) and then the exit code (e.g. copying heap-escaping return values back to the stack). Introduce a Defer SSA block type which has two outgoing edges - one the fallthrough edge (the defer was queued successfully) and one which immediately returns (the defer had a successful recover() call and normal execution should resume at the return point). Fixes #14725 Change-Id: Iad035c9fd25ef8b7a74dafbd7461cf04833d981f Reviewed-on: https://go-review.googlesource.com/20486 Reviewed-by: David Chase <[email protected]>
1 parent 9c8f549 commit ddc6b64

File tree

9 files changed

+124
-58
lines changed

9 files changed

+124
-58
lines changed

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

+44-52
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,9 @@ func buildssa(fn *Node) *ssa.Func {
177177

178178
// fallthrough to exit
179179
if s.curBlock != nil {
180-
s.stmts(s.exitCode)
181-
m := s.mem()
182-
b := s.endBlock()
183-
b.Line = fn.Func.Endlineno
184-
b.Kind = ssa.BlockRet
185-
b.Control = m
180+
s.pushLine(fn.Func.Endlineno)
181+
s.exit()
182+
s.popLine()
186183
}
187184

188185
// Check that we used all labels
@@ -904,6 +901,10 @@ func (s *state) stmt(n *Node) {
904901
// It returns a BlockRet block that ends the control flow. Its control value
905902
// will be set to the final memory state.
906903
func (s *state) exit() *ssa.Block {
904+
if hasdefer {
905+
s.rtcall(Deferreturn, true, nil)
906+
}
907+
907908
// Run exit code. Typically, this code copies heap-allocated PPARAMOUT
908909
// variables back to the stack.
909910
s.stmts(s.exitCode)
@@ -2402,6 +2403,15 @@ func (s *state) call(n *Node, k callKind) *ssa.Value {
24022403
b.Kind = ssa.BlockCall
24032404
b.Control = call
24042405
b.AddEdgeTo(bNext)
2406+
if k == callDefer {
2407+
// Add recover edge to exit code.
2408+
b.Kind = ssa.BlockDefer
2409+
r := s.f.NewBlock(ssa.BlockPlain)
2410+
s.startBlock(r)
2411+
s.exit()
2412+
b.AddEdgeTo(r)
2413+
b.Likely = ssa.BranchLikely
2414+
}
24052415

24062416
// Start exit block, find address of result.
24072417
s.startBlock(bNext)
@@ -3622,12 +3632,6 @@ type genState struct {
36223632

36233633
// bstart remembers where each block starts (indexed by block ID)
36243634
bstart []*obj.Prog
3625-
3626-
// deferBranches remembers all the defer branches we've seen.
3627-
deferBranches []*obj.Prog
3628-
3629-
// deferTarget remembers the (last) deferreturn call site.
3630-
deferTarget *obj.Prog
36313635
}
36323636

36333637
// genssa appends entries to ptxt for each instruction in f.
@@ -3690,15 +3694,6 @@ func genssa(f *ssa.Func, ptxt *obj.Prog, gcargs, gclocals *Sym) {
36903694
for _, br := range s.branches {
36913695
br.p.To.Val = s.bstart[br.b.ID]
36923696
}
3693-
if s.deferBranches != nil && s.deferTarget == nil {
3694-
// This can happen when the function has a defer but
3695-
// no return (because it has an infinite loop).
3696-
s.deferReturn()
3697-
Prog(obj.ARET)
3698-
}
3699-
for _, p := range s.deferBranches {
3700-
p.To.Val = s.deferTarget
3701-
}
37023697

37033698
if logProgs {
37043699
for p := ptxt; p != nil; p = p.Link {
@@ -4529,6 +4524,17 @@ func (s *genState) genValue(v *ssa.Value) {
45294524
q.To.Reg = r
45304525
}
45314526
case ssa.OpAMD64CALLstatic:
4527+
if v.Aux.(*Sym) == Deferreturn.Sym {
4528+
// Deferred calls will appear to be returning to
4529+
// the CALL deferreturn(SB) that we are about to emit.
4530+
// However, the stack trace code will show the line
4531+
// of the instruction byte before the return PC.
4532+
// To avoid that being an unrelated instruction,
4533+
// insert an actual hardware NOP that will have the right line number.
4534+
// This is different from obj.ANOP, which is a virtual no-op
4535+
// that doesn't make it into the instruction stream.
4536+
Thearch.Ginsnop()
4537+
}
45324538
p := Prog(obj.ACALL)
45334539
p.To.Type = obj.TYPE_MEM
45344540
p.To.Name = obj.NAME_EXTERN
@@ -4551,17 +4557,6 @@ func (s *genState) genValue(v *ssa.Value) {
45514557
if Maxarg < v.AuxInt {
45524558
Maxarg = v.AuxInt
45534559
}
4554-
// defer returns in rax:
4555-
// 0 if we should continue executing
4556-
// 1 if we should jump to deferreturn call
4557-
p = Prog(x86.ATESTL)
4558-
p.From.Type = obj.TYPE_REG
4559-
p.From.Reg = x86.REG_AX
4560-
p.To.Type = obj.TYPE_REG
4561-
p.To.Reg = x86.REG_AX
4562-
p = Prog(x86.AJNE)
4563-
p.To.Type = obj.TYPE_BRANCH
4564-
s.deferBranches = append(s.deferBranches, p)
45654560
case ssa.OpAMD64CALLgo:
45664561
p := Prog(obj.ACALL)
45674562
p.To.Type = obj.TYPE_MEM
@@ -4835,12 +4830,26 @@ func (s *genState) genBlock(b, next *ssa.Block) {
48354830
p.To.Type = obj.TYPE_BRANCH
48364831
s.branches = append(s.branches, branch{p, b.Succs[0]})
48374832
}
4833+
case ssa.BlockDefer:
4834+
// defer returns in rax:
4835+
// 0 if we should continue executing
4836+
// 1 if we should jump to deferreturn call
4837+
p := Prog(x86.ATESTL)
4838+
p.From.Type = obj.TYPE_REG
4839+
p.From.Reg = x86.REG_AX
4840+
p.To.Type = obj.TYPE_REG
4841+
p.To.Reg = x86.REG_AX
4842+
p = Prog(x86.AJNE)
4843+
p.To.Type = obj.TYPE_BRANCH
4844+
s.branches = append(s.branches, branch{p, b.Succs[1]})
4845+
if b.Succs[0] != next {
4846+
p := Prog(obj.AJMP)
4847+
p.To.Type = obj.TYPE_BRANCH
4848+
s.branches = append(s.branches, branch{p, b.Succs[0]})
4849+
}
48384850
case ssa.BlockExit:
48394851
Prog(obj.AUNDEF) // tell plive.go that we never reach here
48404852
case ssa.BlockRet:
4841-
if hasdefer {
4842-
s.deferReturn()
4843-
}
48444853
Prog(obj.ARET)
48454854
case ssa.BlockRetJmp:
48464855
p := Prog(obj.AJMP)
@@ -4899,23 +4908,6 @@ func (s *genState) genBlock(b, next *ssa.Block) {
48994908
}
49004909
}
49014910

4902-
func (s *genState) deferReturn() {
4903-
// Deferred calls will appear to be returning to
4904-
// the CALL deferreturn(SB) that we are about to emit.
4905-
// However, the stack trace code will show the line
4906-
// of the instruction byte before the return PC.
4907-
// To avoid that being an unrelated instruction,
4908-
// insert an actual hardware NOP that will have the right line number.
4909-
// This is different from obj.ANOP, which is a virtual no-op
4910-
// that doesn't make it into the instruction stream.
4911-
s.deferTarget = Pc
4912-
Thearch.Ginsnop()
4913-
p := Prog(obj.ACALL)
4914-
p.To.Type = obj.TYPE_MEM
4915-
p.To.Name = obj.NAME_EXTERN
4916-
p.To.Sym = Linksym(Deferreturn.Sym)
4917-
}
4918-
49194911
// addAux adds the offset in the aux fields (AuxInt and Aux) of v to a.
49204912
func addAux(a *obj.Addr, v *ssa.Value) {
49214913
addAux2(a, v, v.AuxInt)

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

+10
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,16 @@ func checkFunc(f *Func) {
125125
if !b.Control.Type.IsMemory() {
126126
f.Fatalf("call block %s has non-memory control value %s", b, b.Control.LongString())
127127
}
128+
case BlockDefer:
129+
if len(b.Succs) != 2 {
130+
f.Fatalf("defer block %s len(Succs)==%d, want 2", b, len(b.Succs))
131+
}
132+
if b.Control == nil {
133+
f.Fatalf("defer block %s has no control value", b)
134+
}
135+
if !b.Control.Type.IsMemory() {
136+
f.Fatalf("defer block %s has non-memory control value %s", b, b.Control.LongString())
137+
}
128138
case BlockCheck:
129139
if len(b.Succs) != 1 {
130140
f.Fatalf("check block %s len(Succs)==%d, want 1", b, len(b.Succs))

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

+4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ func flagalloc(f *Func) {
5858
if v != nil && v.Type.IsFlags() && end[b.ID] != v {
5959
end[b.ID] = nil
6060
}
61+
if b.Kind == BlockDefer {
62+
// Defer blocks internally use/clobber the flags value.
63+
end[b.ID] = nil
64+
}
6165
}
6266

6367
// Add flag recomputations where they are needed.

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

+1
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ var genericBlocks = []blockData{
401401
{name: "Plain"}, // a single successor
402402
{name: "If"}, // 2 successors, if control goto Succs[0] else goto Succs[1]
403403
{name: "Call"}, // 1 successor, control is call op (of memory type)
404+
{name: "Defer"}, // 2 successors, Succs[0]=defer queued, Succs[1]=defer recovered. control is call op (of memory type)
404405
{name: "Check"}, // 1 successor, control is nilcheck op (of void type)
405406
{name: "Ret"}, // no successors, control value is memory result
406407
{name: "RetJmp"}, // no successors, jumps to b.Aux.(*gc.Sym)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func likelyadjust(f *Func) {
100100
// Calls. TODO not all calls are equal, names give useful clues.
101101
// Any name-based heuristics are only relative to other calls,
102102
// and less influential than inferences from loop structure.
103-
case BlockCall:
103+
case BlockCall, BlockDefer:
104104
local[b.ID] = blCALL
105105
certain[b.ID] = max8(blCALL, certain[b.Succs[0].ID])
106106

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

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
BlockPlain
3030
BlockIf
3131
BlockCall
32+
BlockDefer
3233
BlockCheck
3334
BlockRet
3435
BlockRetJmp
@@ -58,6 +59,7 @@ var blockString = [...]string{
5859
BlockPlain: "Plain",
5960
BlockIf: "If",
6061
BlockCall: "Call",
62+
BlockDefer: "Defer",
6163
BlockCheck: "Check",
6264
BlockRet: "Ret",
6365
BlockRetJmp: "RetJmp",

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ func phiopt(f *Func) {
2626
}
2727

2828
pb0, b0 := b, b.Preds[0]
29-
for b0.Kind != BlockIf && len(b0.Preds) == 1 {
29+
for len(b0.Succs) == 1 && len(b0.Preds) == 1 {
3030
pb0, b0 = b0, b0.Preds[0]
3131
}
3232
if b0.Kind != BlockIf {
3333
continue
3434
}
3535
pb1, b1 := b, b.Preds[1]
36-
for b1.Kind != BlockIf && len(b1.Preds) == 1 {
36+
for len(b1.Succs) == 1 && len(b1.Preds) == 1 {
3737
pb1, b1 = b1, b1.Preds[0]
3838
}
3939
if b1 != b0 {

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ func (s *regAllocState) regalloc(f *Func) {
585585
// Walk backwards through the block doing liveness analysis.
586586
liveSet.clear()
587587
d := int32(len(b.Values))
588-
if b.Kind == BlockCall {
588+
if b.Kind == BlockCall || b.Kind == BlockDefer {
589589
d += unlikelyDistance
590590
}
591591
for _, e := range s.live[b.ID] {
@@ -988,7 +988,7 @@ func (s *regAllocState) regalloc(f *Func) {
988988
continue
989989
}
990990
for {
991-
if p.Kind == BlockCall {
991+
if p.Kind == BlockCall || p.Kind == BlockDefer {
992992
goto badloop
993993
}
994994
if p == top {
@@ -1607,7 +1607,7 @@ func (s *regAllocState) computeLive() {
16071607
// to beginning-of-block distance.
16081608
live.clear()
16091609
d := int32(len(b.Values))
1610-
if b.Kind == BlockCall {
1610+
if b.Kind == BlockCall || b.Kind == BlockDefer {
16111611
// Because we keep no values in registers across a call,
16121612
// make every use past a call very far away.
16131613
d += unlikelyDistance

test/fixedbugs/issue14725.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 "fmt"
10+
11+
func f1() (x int) {
12+
for {
13+
defer func() {
14+
recover()
15+
x = 1
16+
}()
17+
panic(nil)
18+
}
19+
}
20+
21+
var sink *int
22+
23+
func f2() (x int) {
24+
sink = &x
25+
defer func() {
26+
recover()
27+
x = 1
28+
}()
29+
panic(nil)
30+
}
31+
32+
func f3(b bool) (x int) {
33+
sink = &x
34+
defer func() {
35+
recover()
36+
x = 1
37+
}()
38+
if b {
39+
panic(nil)
40+
}
41+
return
42+
}
43+
44+
func main() {
45+
if x := f1(); x != 1 {
46+
panic(fmt.Sprintf("f1 returned %d, wanted 1", x))
47+
}
48+
if x := f2(); x != 1 {
49+
panic(fmt.Sprintf("f2 returned %d, wanted 1", x))
50+
}
51+
if x := f3(true); x != 1 {
52+
panic(fmt.Sprintf("f3(true) returned %d, wanted 1", x))
53+
}
54+
if x := f3(false); x != 1 {
55+
panic(fmt.Sprintf("f3(false) returned %d, wanted 1", x))
56+
}
57+
}

0 commit comments

Comments
 (0)