Skip to content

Commit c2ae5c7

Browse files
committed
cmd/compile, runtime: use PC of deferreturn for panic transfer
this removes the old conditional-on-register-value handshake from the deferproc/deferprocstack logic. The "line" for the recovery-exit frame itself (not the defers that it runs) is the closing brace of the function. Reduces code size slightly (e.g. go command is 0.2% smaller) Sample output showing effect of this change, also what sort of code it requires to observe the effect: ``` package main import "os" func main() { g(len(os.Args) - 1) // stack[0] } var gi int var pi *int = &gi //go:noinline func g(i int) { switch i { case 0: defer func() { println("g0", i) q() // stack[2] if i == 0 }() for j := *pi; j < 1; j++ { defer func() { println("recover0", recover().(string)) }() } default: for j := *pi; j < 1; j++ { defer func() { println("g1", i) q() // stack[2] if i == 1 }() } defer func() { println("recover1", recover().(string)) }() } p() } // stack[1] (deferreturn) //go:noinline func p() { panic("p()") } //go:noinline func q() { panic("q()") // stack[3] } /* Sample output for "./foo foo": recover1 p() g1 1 panic: q() goroutine 1 [running]: main.q() .../main.go:46 +0x2c main.g.func3() .../main.go:29 +0x48 main.g(0x1?) .../main.go:37 +0x68 main.main() .../main.go:6 +0x28 */ ``` Change-Id: Ie39ea62ecc244213500380ea06d44024cadc2317 Reviewed-on: https://go-review.googlesource.com/c/go/+/650795 Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 6adf08f commit c2ae5c7

File tree

29 files changed

+90
-299
lines changed

29 files changed

+90
-299
lines changed

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

+1-18
Original file line numberDiff line numberDiff line change
@@ -1441,24 +1441,7 @@ var nefJumps = [2][2]ssagen.IndexJump{
14411441

14421442
func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
14431443
switch b.Kind {
1444-
case ssa.BlockPlain:
1445-
if b.Succs[0].Block() != next {
1446-
p := s.Prog(obj.AJMP)
1447-
p.To.Type = obj.TYPE_BRANCH
1448-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()})
1449-
}
1450-
case ssa.BlockDefer:
1451-
// defer returns in rax:
1452-
// 0 if we should continue executing
1453-
// 1 if we should jump to deferreturn call
1454-
p := s.Prog(x86.ATESTL)
1455-
p.From.Type = obj.TYPE_REG
1456-
p.From.Reg = x86.REG_AX
1457-
p.To.Type = obj.TYPE_REG
1458-
p.To.Reg = x86.REG_AX
1459-
p = s.Prog(x86.AJNE)
1460-
p.To.Type = obj.TYPE_BRANCH
1461-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()})
1444+
case ssa.BlockPlain, ssa.BlockDefer:
14621445
if b.Succs[0].Block() != next {
14631446
p := s.Prog(obj.AJMP)
14641447
p.To.Type = obj.TYPE_BRANCH

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

+1-18
Original file line numberDiff line numberDiff line change
@@ -918,24 +918,7 @@ var gtJumps = [2][2]ssagen.IndexJump{
918918

919919
func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
920920
switch b.Kind {
921-
case ssa.BlockPlain:
922-
if b.Succs[0].Block() != next {
923-
p := s.Prog(obj.AJMP)
924-
p.To.Type = obj.TYPE_BRANCH
925-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()})
926-
}
927-
928-
case ssa.BlockDefer:
929-
// defer returns in R0:
930-
// 0 if we should continue executing
931-
// 1 if we should jump to deferreturn call
932-
p := s.Prog(arm.ACMP)
933-
p.From.Type = obj.TYPE_CONST
934-
p.From.Offset = 0
935-
p.Reg = arm.REG_R0
936-
p = s.Prog(arm.ABNE)
937-
p.To.Type = obj.TYPE_BRANCH
938-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()})
921+
case ssa.BlockPlain, ssa.BlockDefer:
939922
if b.Succs[0].Block() != next {
940923
p := s.Prog(obj.AJMP)
941924
p.To.Type = obj.TYPE_BRANCH

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

+1-18
Original file line numberDiff line numberDiff line change
@@ -1327,24 +1327,7 @@ var gtJumps = [2][2]ssagen.IndexJump{
13271327

13281328
func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
13291329
switch b.Kind {
1330-
case ssa.BlockPlain:
1331-
if b.Succs[0].Block() != next {
1332-
p := s.Prog(obj.AJMP)
1333-
p.To.Type = obj.TYPE_BRANCH
1334-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()})
1335-
}
1336-
1337-
case ssa.BlockDefer:
1338-
// defer returns in R0:
1339-
// 0 if we should continue executing
1340-
// 1 if we should jump to deferreturn call
1341-
p := s.Prog(arm64.ACMP)
1342-
p.From.Type = obj.TYPE_CONST
1343-
p.From.Offset = 0
1344-
p.Reg = arm64.REG_R0
1345-
p = s.Prog(arm64.ABNE)
1346-
p.To.Type = obj.TYPE_BRANCH
1347-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()})
1330+
case ssa.BlockPlain, ssa.BlockDefer:
13481331
if b.Succs[0].Block() != next {
13491332
p := s.Prog(obj.AJMP)
13501333
p.To.Type = obj.TYPE_BRANCH

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

+1-16
Original file line numberDiff line numberDiff line change
@@ -970,22 +970,7 @@ var blockJump = map[ssa.BlockKind]struct {
970970

971971
func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
972972
switch b.Kind {
973-
case ssa.BlockPlain:
974-
if b.Succs[0].Block() != next {
975-
p := s.Prog(obj.AJMP)
976-
p.To.Type = obj.TYPE_BRANCH
977-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()})
978-
}
979-
case ssa.BlockDefer:
980-
// defer returns in R19:
981-
// 0 if we should continue executing
982-
// 1 if we should jump to deferreturn call
983-
p := s.Prog(loong64.ABNE)
984-
p.From.Type = obj.TYPE_REG
985-
p.From.Reg = loong64.REGZERO
986-
p.Reg = loong64.REG_R19
987-
p.To.Type = obj.TYPE_BRANCH
988-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()})
973+
case ssa.BlockPlain, ssa.BlockDefer:
989974
if b.Succs[0].Block() != next {
990975
p := s.Prog(obj.AJMP)
991976
p.To.Type = obj.TYPE_BRANCH

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

+1-16
Original file line numberDiff line numberDiff line change
@@ -826,22 +826,7 @@ var blockJump = map[ssa.BlockKind]struct {
826826

827827
func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
828828
switch b.Kind {
829-
case ssa.BlockPlain:
830-
if b.Succs[0].Block() != next {
831-
p := s.Prog(obj.AJMP)
832-
p.To.Type = obj.TYPE_BRANCH
833-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()})
834-
}
835-
case ssa.BlockDefer:
836-
// defer returns in R1:
837-
// 0 if we should continue executing
838-
// 1 if we should jump to deferreturn call
839-
p := s.Prog(mips.ABNE)
840-
p.From.Type = obj.TYPE_REG
841-
p.From.Reg = mips.REGZERO
842-
p.Reg = mips.REG_R1
843-
p.To.Type = obj.TYPE_BRANCH
844-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()})
829+
case ssa.BlockPlain, ssa.BlockDefer:
845830
if b.Succs[0].Block() != next {
846831
p := s.Prog(obj.AJMP)
847832
p.To.Type = obj.TYPE_BRANCH

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

+1-16
Original file line numberDiff line numberDiff line change
@@ -835,22 +835,7 @@ var blockJump = map[ssa.BlockKind]struct {
835835

836836
func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
837837
switch b.Kind {
838-
case ssa.BlockPlain:
839-
if b.Succs[0].Block() != next {
840-
p := s.Prog(obj.AJMP)
841-
p.To.Type = obj.TYPE_BRANCH
842-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()})
843-
}
844-
case ssa.BlockDefer:
845-
// defer returns in R1:
846-
// 0 if we should continue executing
847-
// 1 if we should jump to deferreturn call
848-
p := s.Prog(mips.ABNE)
849-
p.From.Type = obj.TYPE_REG
850-
p.From.Reg = mips.REGZERO
851-
p.Reg = mips.REG_R1
852-
p.To.Type = obj.TYPE_BRANCH
853-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()})
838+
case ssa.BlockPlain, ssa.BlockDefer:
854839
if b.Succs[0].Block() != next {
855840
p := s.Prog(obj.AJMP)
856841
p.To.Type = obj.TYPE_BRANCH

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

+1-20
Original file line numberDiff line numberDiff line change
@@ -2003,26 +2003,7 @@ var blockJump = [...]struct {
20032003

20042004
func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
20052005
switch b.Kind {
2006-
case ssa.BlockDefer:
2007-
// defer returns in R3:
2008-
// 0 if we should continue executing
2009-
// 1 if we should jump to deferreturn call
2010-
p := s.Prog(ppc64.ACMP)
2011-
p.From.Type = obj.TYPE_REG
2012-
p.From.Reg = ppc64.REG_R3
2013-
p.To.Type = obj.TYPE_CONST
2014-
p.To.Offset = 0
2015-
2016-
p = s.Prog(ppc64.ABNE)
2017-
p.To.Type = obj.TYPE_BRANCH
2018-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()})
2019-
if b.Succs[0].Block() != next {
2020-
p := s.Prog(obj.AJMP)
2021-
p.To.Type = obj.TYPE_BRANCH
2022-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()})
2023-
}
2024-
2025-
case ssa.BlockPlain:
2006+
case ssa.BlockPlain, ssa.BlockDefer:
20262007
if b.Succs[0].Block() != next {
20272008
p := s.Prog(obj.AJMP)
20282009
p.To.Type = obj.TYPE_BRANCH

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

+1-16
Original file line numberDiff line numberDiff line change
@@ -802,22 +802,7 @@ func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
802802
s.SetPos(b.Pos)
803803

804804
switch b.Kind {
805-
case ssa.BlockDefer:
806-
// defer returns in A0:
807-
// 0 if we should continue executing
808-
// 1 if we should jump to deferreturn call
809-
p := s.Prog(riscv.ABNE)
810-
p.To.Type = obj.TYPE_BRANCH
811-
p.From.Type = obj.TYPE_REG
812-
p.From.Reg = riscv.REG_ZERO
813-
p.Reg = riscv.REG_A0
814-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[1].Block()})
815-
if b.Succs[0].Block() != next {
816-
p := s.Prog(obj.AJMP)
817-
p.To.Type = obj.TYPE_BRANCH
818-
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()})
819-
}
820-
case ssa.BlockPlain:
805+
case ssa.BlockPlain, ssa.BlockDefer:
821806
if b.Succs[0].Block() != next {
822807
p := s.Prog(obj.AJMP)
823808
p.To.Type = obj.TYPE_BRANCH

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

+1-14
Original file line numberDiff line numberDiff line change
@@ -887,26 +887,13 @@ func blockAsm(b *ssa.Block) obj.As {
887887
func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) {
888888
// Handle generic blocks first.
889889
switch b.Kind {
890-
case ssa.BlockPlain:
890+
case ssa.BlockPlain, ssa.BlockDefer:
891891
if b.Succs[0].Block() != next {
892892
p := s.Prog(s390x.ABR)
893893
p.To.Type = obj.TYPE_BRANCH
894894
s.Branches = append(s.Branches, ssagen.Branch{P: p, B: b.Succs[0].Block()})
895895
}
896896
return
897-
case ssa.BlockDefer:
898-
// defer returns in R3:
899-
// 0 if we should continue executing
900-
// 1 if we should jump to deferreturn call
901-
p := s.Br(s390x.ACIJ, b.Succs[1].Block())
902-
p.From.Type = obj.TYPE_CONST
903-
p.From.Offset = int64(s390x.NotEqual & s390x.NotUnordered) // unordered is not possible
904-
p.Reg = s390x.REG_R3
905-
p.AddRestSourceConst(0)
906-
if b.Succs[0].Block() != next {
907-
s.Br(s390x.ABR, b.Succs[0].Block())
908-
}
909-
return
910897
case ssa.BlockExit, ssa.BlockRetJmp:
911898
return
912899
case ssa.BlockRet:

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

+11-11
Original file line numberDiff line numberDiff line change
@@ -663,21 +663,21 @@ var genericOps = []opData{
663663
{name: "PrefetchCacheStreamed", argLength: 2, hasSideEffects: true}, // Do non-temporal or streamed prefetch arg0 to cache. arg0=addr, arg1=memory.
664664
}
665665

666-
// kind controls successors implicit exit
667-
// ----------------------------------------------------------
668-
// Exit [return mem] [] yes
669-
// Ret [return mem] [] yes
670-
// RetJmp [return mem] [] yes
671-
// Plain [] [next]
672-
// If [boolean Value] [then, else]
673-
// First [] [always, never]
674-
// Defer [mem] [nopanic, panic] (control opcode should be OpStaticCall to runtime.deferproc)
675-
// JumpTable [integer Value] [succ1,succ2,..]
666+
// kind controls successors implicit exit
667+
// ------------------------------------------------------------
668+
// Exit [return mem] [] yes
669+
// Ret [return mem] [] yes
670+
// RetJmp [return mem] [] yes
671+
// Plain [] [next]
672+
// If [boolean Value] [then, else]
673+
// First [] [always, never]
674+
// Defer [mem] [nopanic, recovery] (control opcode should be OpStaticCall to runtime.defer*)
675+
// JumpTable [integer Value] [succ1,succ2,..]
676676

677677
var genericBlocks = []blockData{
678678
{name: "Plain"}, // a single successor
679679
{name: "If", controls: 1}, // if Controls[0] goto Succs[0] else goto Succs[1]
680-
{name: "Defer", controls: 1}, // Succs[0]=defer queued, Succs[1]=defer recovered. Controls[0] is call op (of memory type)
680+
{name: "Defer", controls: 1}, // Succs[0]=defer queued, Succs[1]=defer recovery branch (jmp performed by runtime). Controls[0] is call op (of memory type).
681681
{name: "Ret", controls: 1}, // no successors, Controls[0] value is memory result
682682
{name: "RetJmp", controls: 1}, // no successors, Controls[0] value is a tail call
683683
{name: "Exit", controls: 1}, // no successors, Controls[0] value generates a panic

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ type Func struct {
4141
ABISelf *abi.ABIConfig // ABI for function being compiled
4242
ABIDefault *abi.ABIConfig // ABI for rtcall and other no-parsed-signature/pragma functions.
4343

44-
scheduled bool // Values in Blocks are in final order
45-
laidout bool // Blocks are ordered
46-
NoSplit bool // true if function is marked as nosplit. Used by schedule check pass.
47-
dumpFileSeq uint8 // the sequence numbers of dump file. (%s_%02d__%s.dump", funcname, dumpFileSeq, phaseName)
48-
IsPgoHot bool
49-
HasDeferRangeFunc bool // if true, needs a deferreturn so deferrangefunc can use it for recover() return PC
44+
scheduled bool // Values in Blocks are in final order
45+
laidout bool // Blocks are ordered
46+
NoSplit bool // true if function is marked as nosplit. Used by schedule check pass.
47+
dumpFileSeq uint8 // the sequence numbers of dump file. (%s_%02d__%s.dump", funcname, dumpFileSeq, phaseName)
48+
IsPgoHot bool
49+
DeferReturn *Block // avoid creating more than one deferreturn if there's multiple calls to deferproc-etc.
5050

5151
// when register allocation is done, maps value ids to locations
5252
RegAlloc []Location

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

+30-11
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ func buildssa(fn *ir.Func, worker int, isPgoHot bool) *ssa.Func {
410410
// Don't support open-coded defers for 386 ONLY when using shared
411411
// libraries, because there is extra code (added by rewriteToUseGot())
412412
// preceding the deferreturn/ret code that we don't track correctly.
413+
//
414+
// TODO this restriction can be removed given adjusted offset in computeDeferReturn in cmd/link/internal/ld/pcln.go
413415
s.hasOpenDefers = false
414416
}
415417
if s.hasOpenDefers && s.instrumentEnterExit {
@@ -2166,7 +2168,17 @@ func (s *state) exit() *ssa.Block {
21662168
}
21672169
s.openDeferExit()
21682170
} else {
2171+
// Shared deferreturn is assigned the "last" position in the function.
2172+
// The linker picks the first deferreturn call it sees, so this is
2173+
// the only sensible "shared" place.
2174+
// To not-share deferreturn, the protocol would need to be changed
2175+
// so that the call to deferproc-etc would receive the PC offset from
2176+
// the return PC, and the runtime would need to use that instead of
2177+
// the deferreturn retrieved from the pcln information.
2178+
// opendefers would remain a problem, however.
2179+
s.pushLine(s.curfn.Endlineno)
21692180
s.rtcall(ir.Syms.Deferreturn, true, nil)
2181+
s.popLine()
21702182
}
21712183
}
21722184

@@ -4411,6 +4423,8 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool, deferExt
44114423
s.Fatalf("go/defer call with arguments: %v", n)
44124424
}
44134425

4426+
isCallDeferRangeFunc := false
4427+
44144428
switch n.Op() {
44154429
case ir.OCALLFUNC:
44164430
if (k == callNormal || k == callTail) && fn.Op() == ir.ONAME && fn.(*ir.Name).Class == ir.PFUNC {
@@ -4434,7 +4448,7 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool, deferExt
44344448
}
44354449
}
44364450
if fn := n.Fun.Sym().Name; n.Fun.Sym().Pkg == ir.Pkgs.Runtime && fn == "deferrangefunc" {
4437-
s.f.HasDeferRangeFunc = true
4451+
isCallDeferRangeFunc = true
44384452
}
44394453
break
44404454
}
@@ -4596,17 +4610,20 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool, deferExt
45964610
}
45974611

45984612
// Finish block for defers
4599-
if k == callDefer || k == callDeferStack {
4613+
if k == callDefer || k == callDeferStack || isCallDeferRangeFunc {
46004614
b := s.endBlock()
46014615
b.Kind = ssa.BlockDefer
46024616
b.SetControl(call)
46034617
bNext := s.f.NewBlock(ssa.BlockPlain)
46044618
b.AddEdgeTo(bNext)
4605-
// Add recover edge to exit code.
4606-
r := s.f.NewBlock(ssa.BlockPlain)
4607-
s.startBlock(r)
4608-
s.exit()
4609-
b.AddEdgeTo(r)
4619+
r := s.f.DeferReturn // Share a single deferreturn among all defers
4620+
if r == nil {
4621+
r = s.f.NewBlock(ssa.BlockPlain)
4622+
s.startBlock(r)
4623+
s.exit()
4624+
s.f.DeferReturn = r
4625+
}
4626+
b.AddEdgeTo(r) // Add recover edge to exit code. This is a fake edge to keep the block live.
46104627
b.Likely = ssa.BranchLikely
46114628
s.startBlock(bNext)
46124629
}
@@ -6571,13 +6588,15 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
65716588
// nop (which will never execute) after the call.
65726589
Arch.Ginsnop(s.pp)
65736590
}
6574-
if openDeferInfo != nil || f.HasDeferRangeFunc {
6591+
if openDeferInfo != nil {
65756592
// When doing open-coded defers, generate a disconnected call to
65766593
// deferreturn and a return. This will be used to during panic
65776594
// recovery to unwind the stack and return back to the runtime.
6578-
//
6579-
// deferrangefunc needs to be sure that at least one of these exists;
6580-
// if all returns are dead-code eliminated, there might not be.
6595+
6596+
// Note that this exit code doesn't work if a return parameter
6597+
// is heap-allocated, but open defers aren't enabled in that case.
6598+
6599+
// TODO either make this handle heap-allocated return parameters or reuse the other-defers general-purpose code path.
65816600
s.pp.NextLive = s.livenessMap.DeferReturn
65826601
p := s.pp.Prog(obj.ACALL)
65836602
p.To.Type = obj.TYPE_MEM

0 commit comments

Comments
 (0)