Commit dad61637 authored by Dan Scales's avatar Dan Scales

cmd/compile, cmd/link, runtime: make defers low-cost through inline code and extra funcdata

Generate inline code at defer time to save the args of defer calls to unique
(autotmp) stack slots, and generate inline code at exit time to check which defer
calls were made and make the associated function/method/interface calls. We
remember that a particular defer statement was reached by storing in the deferBits
variable (always stored on the stack). At exit time, we check the bits of the
deferBits variable to determine which defer function calls to make (in reverse
order). These low-cost defers are only used for functions where no defers
appear in loops. In addition, we don't do these low-cost defers if there are too
many defer statements or too many exits in a function (to limit code increase).

When a function uses open-coded defers, we produce extra
FUNCDATA_OpenCodedDeferInfo information that specifies the number of defers, and
for each defer, the stack slots where the closure and associated args have been
stored. The funcdata also includes the location of the deferBits variable.
Therefore, for panics, we can use this funcdata to determine exactly which defers
are active, and call the appropriate functions/methods/closures with the correct
arguments for each active defer.

In order to unwind the stack correctly after a recover(), we need to add an extra
code segment to functions with open-coded defers that simply calls deferreturn()
and returns. This segment is not reachable by the normal function, but is returned
to by the runtime during recovery. We set the liveness information of this
deferreturn() to be the same as the liveness at the first function call during the
last defer exit code (so all return values and all stack slots needed by the defer
calls will be live).

I needed to increase the stackguard constant from 880 to 896, because of a small
amount of new code in deferreturn().

The -N flag disables open-coded defers. '-d defer' prints out the kind of defer
being used at each defer statement (heap-allocated, stack-allocated, or
open-coded).

Cost of defer statement  [ go test -run NONE -bench BenchmarkDefer$ runtime ]
  With normal (stack-allocated) defers only:         35.4  ns/op
  With open-coded defers:                             5.6  ns/op
  Cost of function call alone (remove defer keyword): 4.4  ns/op

Text size increase (including funcdata) for go cmd without/with open-coded defers:  0.09%

The average size increase (including funcdata) for only the functions that use
open-coded defers is 1.1%.

The cost of a panic followed by a recover got noticeably slower, since panic
processing now requires a scan of the stack for open-coded defer frames. This scan
is required, even if no frames are using open-coded defers:

Cost of panic and recover [ go test -run NONE -bench BenchmarkPanicRecover runtime ]
  Without open-coded defers:        62.0 ns/op
  With open-coded defers:           255  ns/op

A CGO Go-to-C-to-Go benchmark got noticeably faster because of open-coded defers:

CGO Go-to-C-to-Go benchmark [cd misc/cgo/test; go test -run NONE -bench BenchmarkCGoCallback ]
  Without open-coded defers:        443 ns/op
  With open-coded defers:           347 ns/op

Updates #14939 (defer performance)
Updates #34481 (design doc)

Change-Id: I51a389860b9676cfa1b84722f5fb84d3c4ee9e28
Reviewed-on: https://go-review.googlesource.com/c/go/+/190098Reviewed-by: default avatarAustin Clements <austin@google.com>
parent e94475ea
...@@ -371,6 +371,7 @@ func (e *Escape) stmt(n *Node) { ...@@ -371,6 +371,7 @@ func (e *Escape) stmt(n *Node) {
e.stmts(n.Right.Ninit) e.stmts(n.Right.Ninit)
e.call(e.addrs(n.List), n.Right, nil) e.call(e.addrs(n.List), n.Right, nil)
case ORETURN: case ORETURN:
e.curfn.Func.numReturns++
results := e.curfn.Type.Results().FieldSlice() results := e.curfn.Type.Results().FieldSlice()
for i, v := range n.List.Slice() { for i, v := range n.List.Slice() {
e.assign(asNode(results[i].Nname), v, "return", n) e.assign(asNode(results[i].Nname), v, "return", n)
...@@ -378,6 +379,16 @@ func (e *Escape) stmt(n *Node) { ...@@ -378,6 +379,16 @@ func (e *Escape) stmt(n *Node) {
case OCALLFUNC, OCALLMETH, OCALLINTER, OCLOSE, OCOPY, ODELETE, OPANIC, OPRINT, OPRINTN, ORECOVER: case OCALLFUNC, OCALLMETH, OCALLINTER, OCLOSE, OCOPY, ODELETE, OPANIC, OPRINT, OPRINTN, ORECOVER:
e.call(nil, n, nil) e.call(nil, n, nil)
case OGO, ODEFER: case OGO, ODEFER:
if n.Op == ODEFER {
e.curfn.Func.SetHasDefer(true)
e.curfn.Func.numDefers++
if e.curfn.Func.numDefers > maxOpenDefers {
// Don't allow open defers if there are more than
// 8 defers in the function, since we use a single
// byte to record active defers.
e.curfn.Func.SetOpenCodedDeferDisallowed(true)
}
}
e.stmts(n.Left.Ninit) e.stmts(n.Left.Ninit)
e.call(nil, n.Left, n) e.call(nil, n.Left, n)
...@@ -872,8 +883,13 @@ func (e *Escape) augmentParamHole(k EscHole, where *Node) EscHole { ...@@ -872,8 +883,13 @@ func (e *Escape) augmentParamHole(k EscHole, where *Node) EscHole {
// non-transient location to avoid arguments from being // non-transient location to avoid arguments from being
// transiently allocated. // transiently allocated.
if where.Op == ODEFER && e.loopDepth == 1 { if where.Op == ODEFER && e.loopDepth == 1 {
where.Esc = EscNever // force stack allocation of defer record (see ssa.go) // force stack allocation of defer record, unless open-coded
// defers are used (see ssa.go)
where.Esc = EscNever
return e.later(k) return e.later(k)
} else if where.Op == ODEFER {
// If any defer occurs in a loop, open-coded defers cannot be used
e.curfn.Func.SetOpenCodedDeferDisallowed(true)
} }
return e.heapHole() return e.heapHole()
......
...@@ -52,6 +52,7 @@ var ( ...@@ -52,6 +52,7 @@ var (
Debug_typecheckinl int Debug_typecheckinl int
Debug_gendwarfinl int Debug_gendwarfinl int
Debug_softfloat int Debug_softfloat int
Debug_defer int
) )
// Debug arguments. // Debug arguments.
...@@ -81,6 +82,7 @@ var debugtab = []struct { ...@@ -81,6 +82,7 @@ var debugtab = []struct {
{"typecheckinl", "eager typechecking of inline function bodies", &Debug_typecheckinl}, {"typecheckinl", "eager typechecking of inline function bodies", &Debug_typecheckinl},
{"dwarfinl", "print information about DWARF inlined function creation", &Debug_gendwarfinl}, {"dwarfinl", "print information about DWARF inlined function creation", &Debug_gendwarfinl},
{"softfloat", "force compiler to emit soft-float code", &Debug_softfloat}, {"softfloat", "force compiler to emit soft-float code", &Debug_softfloat},
{"defer", "print information about defer compilation", &Debug_defer},
} }
const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>] const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]
......
...@@ -294,6 +294,9 @@ func addGCLocals() { ...@@ -294,6 +294,9 @@ func addGCLocals() {
} }
ggloblsym(x, int32(len(x.P)), attr) ggloblsym(x, int32(len(x.P)), attr)
} }
if x := s.Func.OpenCodedDeferInfo; x != nil {
ggloblsym(x, int32(len(x.P)), obj.RODATA|obj.DUPOK)
}
} }
} }
......
...@@ -338,6 +338,7 @@ func deferstruct(stksize int64) *types.Type { ...@@ -338,6 +338,7 @@ func deferstruct(stksize int64) *types.Type {
makefield("siz", types.Types[TUINT32]), makefield("siz", types.Types[TUINT32]),
makefield("started", types.Types[TBOOL]), makefield("started", types.Types[TBOOL]),
makefield("heap", types.Types[TBOOL]), makefield("heap", types.Types[TBOOL]),
makefield("openDefer", types.Types[TBOOL]),
makefield("sp", types.Types[TUINTPTR]), makefield("sp", types.Types[TUINTPTR]),
makefield("pc", types.Types[TUINTPTR]), makefield("pc", types.Types[TUINTPTR]),
// Note: the types here don't really matter. Defer structures // Note: the types here don't really matter. Defer structures
...@@ -346,6 +347,9 @@ func deferstruct(stksize int64) *types.Type { ...@@ -346,6 +347,9 @@ func deferstruct(stksize int64) *types.Type {
makefield("fn", types.Types[TUINTPTR]), makefield("fn", types.Types[TUINTPTR]),
makefield("_panic", types.Types[TUINTPTR]), makefield("_panic", types.Types[TUINTPTR]),
makefield("link", types.Types[TUINTPTR]), makefield("link", types.Types[TUINTPTR]),
makefield("framepc", types.Types[TUINTPTR]),
makefield("varp", types.Types[TUINTPTR]),
makefield("fd", types.Types[TUINTPTR]),
makefield("args", argtype), makefield("args", argtype),
} }
......
...@@ -20,7 +20,7 @@ func TestSizeof(t *testing.T) { ...@@ -20,7 +20,7 @@ func TestSizeof(t *testing.T) {
_32bit uintptr // size on 32bit platforms _32bit uintptr // size on 32bit platforms
_64bit uintptr // size on 64bit platforms _64bit uintptr // size on 64bit platforms
}{ }{
{Func{}, 116, 208}, {Func{}, 124, 224},
{Name{}, 32, 56}, {Name{}, 32, 56},
{Param{}, 24, 48}, {Param{}, 24, 48},
{Node{}, 76, 128}, {Node{}, 76, 128},
......
This diff is collapsed.
...@@ -492,6 +492,8 @@ type Func struct { ...@@ -492,6 +492,8 @@ type Func struct {
Pragma syntax.Pragma // go:xxx function annotations Pragma syntax.Pragma // go:xxx function annotations
flags bitset16 flags bitset16
numDefers int // number of defer calls in the function
numReturns int // number of explicit returns in the function
// nwbrCalls records the LSyms of functions called by this // nwbrCalls records the LSyms of functions called by this
// function for go:nowritebarrierrec analysis. Only filled in // function for go:nowritebarrierrec analysis. Only filled in
...@@ -532,6 +534,7 @@ const ( ...@@ -532,6 +534,7 @@ const (
funcInlinabilityChecked // inliner has already determined whether the function is inlinable funcInlinabilityChecked // inliner has already determined whether the function is inlinable
funcExportInline // include inline body in export data funcExportInline // include inline body in export data
funcInstrumentBody // add race/msan instrumentation during SSA construction funcInstrumentBody // add race/msan instrumentation during SSA construction
funcOpenCodedDeferDisallowed // can't do open-coded defers
) )
func (f *Func) Dupok() bool { return f.flags&funcDupok != 0 } func (f *Func) Dupok() bool { return f.flags&funcDupok != 0 }
...@@ -544,6 +547,7 @@ func (f *Func) NilCheckDisabled() bool { return f.flags&funcNilCheckDisabled ...@@ -544,6 +547,7 @@ func (f *Func) NilCheckDisabled() bool { return f.flags&funcNilCheckDisabled
func (f *Func) InlinabilityChecked() bool { return f.flags&funcInlinabilityChecked != 0 } func (f *Func) InlinabilityChecked() bool { return f.flags&funcInlinabilityChecked != 0 }
func (f *Func) ExportInline() bool { return f.flags&funcExportInline != 0 } func (f *Func) ExportInline() bool { return f.flags&funcExportInline != 0 }
func (f *Func) InstrumentBody() bool { return f.flags&funcInstrumentBody != 0 } func (f *Func) InstrumentBody() bool { return f.flags&funcInstrumentBody != 0 }
func (f *Func) OpenCodedDeferDisallowed() bool { return f.flags&funcOpenCodedDeferDisallowed != 0 }
func (f *Func) SetDupok(b bool) { f.flags.set(funcDupok, b) } func (f *Func) SetDupok(b bool) { f.flags.set(funcDupok, b) }
func (f *Func) SetWrapper(b bool) { f.flags.set(funcWrapper, b) } func (f *Func) SetWrapper(b bool) { f.flags.set(funcWrapper, b) }
...@@ -555,6 +559,7 @@ func (f *Func) SetNilCheckDisabled(b bool) { f.flags.set(funcNilCheckDisabled ...@@ -555,6 +559,7 @@ func (f *Func) SetNilCheckDisabled(b bool) { f.flags.set(funcNilCheckDisabled
func (f *Func) SetInlinabilityChecked(b bool) { f.flags.set(funcInlinabilityChecked, b) } func (f *Func) SetInlinabilityChecked(b bool) { f.flags.set(funcInlinabilityChecked, b) }
func (f *Func) SetExportInline(b bool) { f.flags.set(funcExportInline, b) } func (f *Func) SetExportInline(b bool) { f.flags.set(funcExportInline, b) }
func (f *Func) SetInstrumentBody(b bool) { f.flags.set(funcInstrumentBody, b) } func (f *Func) SetInstrumentBody(b bool) { f.flags.set(funcInstrumentBody, b) }
func (f *Func) SetOpenCodedDeferDisallowed(b bool) { f.flags.set(funcOpenCodedDeferDisallowed, b) }
func (f *Func) setWBPos(pos src.XPos) { func (f *Func) setWBPos(pos src.XPos) {
if Debug_wb != 0 { if Debug_wb != 0 {
......
...@@ -213,7 +213,6 @@ func walkstmt(n *Node) *Node { ...@@ -213,7 +213,6 @@ func walkstmt(n *Node) *Node {
yyerror("case statement out of place") yyerror("case statement out of place")
case ODEFER: case ODEFER:
Curfn.Func.SetHasDefer(true)
fallthrough fallthrough
case OGO: case OGO:
switch n.Left.Op { switch n.Left.Op {
......
...@@ -170,6 +170,11 @@ func elimDeadAutosGeneric(f *Func) { ...@@ -170,6 +170,11 @@ func elimDeadAutosGeneric(f *Func) {
return return
case OpVarLive: case OpVarLive:
// Don't delete the auto if it needs to be kept alive. // Don't delete the auto if it needs to be kept alive.
// We depend on this check to keep the autotmp stack slots
// for open-coded defers from being removed (since they
// may not be used by the inline code, but will be used by
// panic processing).
n, ok := v.Aux.(GCNode) n, ok := v.Aux.(GCNode)
if !ok || n.StorageClass() != ClassAuto { if !ok || n.StorageClass() != ClassAuto {
return return
......
...@@ -32,6 +32,14 @@ type Func struct { ...@@ -32,6 +32,14 @@ type Func struct {
Type *types.Type // type signature of the function. Type *types.Type // type signature of the function.
Blocks []*Block // unordered set of all basic blocks (note: not indexable by ID) Blocks []*Block // unordered set of all basic blocks (note: not indexable by ID)
Entry *Block // the entry basic block Entry *Block // the entry basic block
// If we are using open-coded defers, this is the first call to a deferred
// function in the final defer exit sequence that we generated. This call
// should be after all defer statements, and will have all args, etc. of
// all defer calls as live. The liveness info of this call will be used
// for the deferreturn/ret segment generated for functions with open-coded
// defers.
LastDeferExit *Value
bid idAlloc // block ID allocator bid idAlloc // block ID allocator
vid idAlloc // value ID allocator vid idAlloc // value ID allocator
......
...@@ -409,6 +409,7 @@ type FuncInfo struct { ...@@ -409,6 +409,7 @@ type FuncInfo struct {
GCLocals *LSym GCLocals *LSym
GCRegs *LSym GCRegs *LSym
StackObjects *LSym StackObjects *LSym
OpenCodedDeferInfo *LSym
} }
type InlMark struct { type InlMark struct {
......
...@@ -20,6 +20,7 @@ const ( ...@@ -20,6 +20,7 @@ const (
FUNCDATA_RegPointerMaps = 2 FUNCDATA_RegPointerMaps = 2
FUNCDATA_StackObjects = 3 FUNCDATA_StackObjects = 3
FUNCDATA_InlTree = 4 FUNCDATA_InlTree = 4
FUNCDATA_OpenCodedDeferInfo = 5
// ArgsSizeUnknown is set in Func.argsize to mark all functions // ArgsSizeUnknown is set in Func.argsize to mark all functions
// whose argument size is unknown (C vararg functions, and // whose argument size is unknown (C vararg functions, and
......
...@@ -85,6 +85,12 @@ func GetFuncID(name, file string) FuncID { ...@@ -85,6 +85,12 @@ func GetFuncID(name, file string) FuncID {
return FuncID_panicwrap return FuncID_panicwrap
case "runtime.handleAsyncEvent": case "runtime.handleAsyncEvent":
return FuncID_handleAsyncEvent return FuncID_handleAsyncEvent
case "runtime.deferreturn":
// Don't show in the call stack (used when invoking defer functions)
return FuncID_wrapper
case "runtime.runOpenDeferFrame":
// Don't show in the call stack (used when invoking defer functions)
return FuncID_wrapper
} }
if file == "<autogenerated>" { if file == "<autogenerated>" {
return FuncID_wrapper return FuncID_wrapper
......
...@@ -18,7 +18,7 @@ const ( ...@@ -18,7 +18,7 @@ const (
) )
// Initialize StackGuard and StackLimit according to target system. // Initialize StackGuard and StackLimit according to target system.
var StackGuard = 880*stackGuardMultiplier() + StackSystem var StackGuard = 896*stackGuardMultiplier() + StackSystem
var StackLimit = StackGuard - StackSystem - StackSmall var StackLimit = StackGuard - StackSystem - StackSmall
// stackGuardMultiplier returns a multiplier to apply to the default // stackGuardMultiplier returns a multiplier to apply to the default
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"cmd/internal/sys" "cmd/internal/sys"
"cmd/link/internal/sym" "cmd/link/internal/sym"
"encoding/binary" "encoding/binary"
"fmt"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
...@@ -255,13 +256,23 @@ func (ctxt *Link) pclntab() { ...@@ -255,13 +256,23 @@ func (ctxt *Link) pclntab() {
} }
if r.Type.IsDirectJump() && r.Sym != nil && r.Sym.Name == "runtime.deferreturn" { if r.Type.IsDirectJump() && r.Sym != nil && r.Sym.Name == "runtime.deferreturn" {
if ctxt.Arch.Family == sys.Wasm { if ctxt.Arch.Family == sys.Wasm {
deferreturn = lastWasmAddr deferreturn = lastWasmAddr - 1
} else { } else {
// Note: the relocation target is in the call instruction, but // Note: the relocation target is in the call instruction, but
// is not necessarily the whole instruction (for instance, on // is not necessarily the whole instruction (for instance, on
// x86 the relocation applies to bytes [1:5] of the 5 byte call // x86 the relocation applies to bytes [1:5] of the 5 byte call
// instruction). // instruction).
deferreturn = uint32(r.Off) deferreturn = uint32(r.Off)
switch ctxt.Arch.Family {
case sys.AMD64, sys.I386, sys.MIPS, sys.MIPS64, sys.RISCV64:
deferreturn--
case sys.PPC64, sys.ARM, sys.ARM64:
// no change
case sys.S390X:
deferreturn -= 2
default:
panic(fmt.Sprint("Unhandled architecture:", ctxt.Arch.Family))
}
} }
break // only need one break // only need one
} }
......
...@@ -498,7 +498,8 @@ func (ctxt *Link) symtab() { ...@@ -498,7 +498,8 @@ func (ctxt *Link) symtab() {
case strings.HasPrefix(s.Name, "gcargs."), case strings.HasPrefix(s.Name, "gcargs."),
strings.HasPrefix(s.Name, "gclocals."), strings.HasPrefix(s.Name, "gclocals."),
strings.HasPrefix(s.Name, "gclocals·"), strings.HasPrefix(s.Name, "gclocals·"),
strings.HasPrefix(s.Name, "inltree."): strings.HasPrefix(s.Name, "inltree."),
strings.HasSuffix(s.Name, ".opendefer"):
s.Type = sym.SGOFUNC s.Type = sym.SGOFUNC
s.Attr |= sym.AttrNotInSymbolTable s.Attr |= sym.AttrNotInSymbolTable
s.Outer = symgofunc s.Outer = symgofunc
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package runtime_test package runtime_test
import ( import (
"internal/race"
"reflect" "reflect"
"runtime" "runtime"
"strings" "strings"
...@@ -12,19 +13,19 @@ import ( ...@@ -12,19 +13,19 @@ import (
) )
func f1(pan bool) []uintptr { func f1(pan bool) []uintptr {
return f2(pan) // line 15 return f2(pan) // line 16
} }
func f2(pan bool) []uintptr { func f2(pan bool) []uintptr {
return f3(pan) // line 19 return f3(pan) // line 20
} }
func f3(pan bool) []uintptr { func f3(pan bool) []uintptr {
if pan { if pan {
panic("f3") // line 24 panic("f3") // line 25
} }
ret := make([]uintptr, 20) ret := make([]uintptr, 20)
return ret[:runtime.Callers(0, ret)] // line 27 return ret[:runtime.Callers(0, ret)] // line 28
} }
func testCallers(t *testing.T, pcs []uintptr, pan bool) { func testCallers(t *testing.T, pcs []uintptr, pan bool) {
...@@ -48,16 +49,16 @@ func testCallers(t *testing.T, pcs []uintptr, pan bool) { ...@@ -48,16 +49,16 @@ func testCallers(t *testing.T, pcs []uintptr, pan bool) {
var f3Line int var f3Line int
if pan { if pan {
f3Line = 24 f3Line = 25
} else { } else {
f3Line = 27 f3Line = 28
} }
want := []struct { want := []struct {
name string name string
line int line int
}{ }{
{"f1", 15}, {"f1", 16},
{"f2", 19}, {"f2", 20},
{"f3", f3Line}, {"f3", f3Line},
} }
for _, w := range want { for _, w := range want {
...@@ -188,3 +189,36 @@ func TestCallersDivZeroPanic(t *testing.T) { ...@@ -188,3 +189,36 @@ func TestCallersDivZeroPanic(t *testing.T) {
t.Fatal("did not see divide-by-sizer panic") t.Fatal("did not see divide-by-sizer panic")
} }
} }
// This test will have a slightly different callstack if non-open-coded defers are
// called (e.g. if race checks enabled), because of a difference in the way the
// defer function is invoked.
func TestCallersDeferNilFuncPanic(t *testing.T) {
if race.Enabled {
t.Skip("skipping TestCallersDeferNilFuncPanic under race detector")
}
// Make sure we don't have any extra frames on the stack (due to
// open-coded defer processing)
state := 1
want := []string{"runtime.Callers", "runtime_test.TestCallersDeferNilFuncPanic.func1",
"runtime.gopanic", "runtime.panicmem", "runtime.sigpanic",
"runtime_test.TestCallersDeferNilFuncPanic"}
defer func() {
if r := recover(); r == nil {
t.Fatal("did not panic")
}
pcs := make([]uintptr, 20)
pcs = pcs[:runtime.Callers(0, pcs)]
testCallersEqual(t, pcs, want)
if state == 1 {
t.Fatal("nil defer func panicked at defer time rather than function exit time")
}
}()
var f func()
defer f()
// Use the value of 'state' to make sure nil defer func f causes panic at
// function exit, rather than at the defer statement.
state = 2
}
...@@ -15,11 +15,11 @@ import ( ...@@ -15,11 +15,11 @@ import (
// unconditional panic (hence no return from the function) // unconditional panic (hence no return from the function)
func TestUnconditionalPanic(t *testing.T) { func TestUnconditionalPanic(t *testing.T) {
defer func() { defer func() {
if recover() == nil { if recover() != "testUnconditional" {
t.Fatal("expected unconditional panic") t.Fatal("expected unconditional panic")
} }
}() }()
panic("panic should be recovered") panic("testUnconditional")
} }
var glob int = 3 var glob int = 3
...@@ -30,7 +30,7 @@ func TestOpenAndNonOpenDefers(t *testing.T) { ...@@ -30,7 +30,7 @@ func TestOpenAndNonOpenDefers(t *testing.T) {
for { for {
// Non-open defer because in a loop // Non-open defer because in a loop
defer func(n int) { defer func(n int) {
if recover() == nil { if recover() != "testNonOpenDefer" {
t.Fatal("expected testNonOpen panic") t.Fatal("expected testNonOpen panic")
} }
}(3) }(3)
...@@ -45,7 +45,7 @@ func TestOpenAndNonOpenDefers(t *testing.T) { ...@@ -45,7 +45,7 @@ func TestOpenAndNonOpenDefers(t *testing.T) {
//go:noinline //go:noinline
func testOpen(t *testing.T, arg int) { func testOpen(t *testing.T, arg int) {
defer func(n int) { defer func(n int) {
if recover() == nil { if recover() != "testOpenDefer" {
t.Fatal("expected testOpen panic") t.Fatal("expected testOpen panic")
} }
}(4) }(4)
...@@ -61,7 +61,7 @@ func TestNonOpenAndOpenDefers(t *testing.T) { ...@@ -61,7 +61,7 @@ func TestNonOpenAndOpenDefers(t *testing.T) {
for { for {
// Non-open defer because in a loop // Non-open defer because in a loop
defer func(n int) { defer func(n int) {
if recover() == nil { if recover() != "testNonOpenDefer" {
t.Fatal("expected testNonOpen panic") t.Fatal("expected testNonOpen panic")
} }
}(3) }(3)
...@@ -80,7 +80,7 @@ func TestConditionalDefers(t *testing.T) { ...@@ -80,7 +80,7 @@ func TestConditionalDefers(t *testing.T) {
list = make([]int, 0, 10) list = make([]int, 0, 10)
defer func() { defer func() {
if recover() == nil { if recover() != "testConditional" {
t.Fatal("expected panic") t.Fatal("expected panic")
} }
want := []int{4, 2, 1} want := []int{4, 2, 1}
...@@ -106,7 +106,7 @@ func testConditionalDefers(n int) { ...@@ -106,7 +106,7 @@ func testConditionalDefers(n int) {
defer doappend(4) defer doappend(4)
} }
} }
panic("test") panic("testConditional")
} }
// Test that there is no compile-time or run-time error if an open-coded defer // Test that there is no compile-time or run-time error if an open-coded defer
...@@ -174,3 +174,52 @@ func TestRecoverMatching(t *testing.T) { ...@@ -174,3 +174,52 @@ func TestRecoverMatching(t *testing.T) {
}() }()
panic("panic1") panic("panic1")
} }
type nonSSAable [128]byte
type bigStruct struct {
x, y, z, w, p, q int64
}
func mknonSSAable() nonSSAable {
globint1++
return nonSSAable{0, 0, 0, 0, 5}
}
var globint1, globint2 int
//go:noinline
func sideeffect(n int64) int64 {
globint2++
return n
}
// Test that nonSSAable arguments to defer are handled correctly and only evaluated once.
func TestNonSSAableArgs(t *testing.T) {
globint1 = 0
globint2 = 0
var save1 byte
var save2 int64
defer func() {
if globint1 != 1 {
t.Fatal(fmt.Sprintf("globint1: wanted: 1, got %v", globint1))
}
if save1 != 5 {
t.Fatal(fmt.Sprintf("save1: wanted: 5, got %v", save1))
}
if globint2 != 1 {
t.Fatal(fmt.Sprintf("globint2: wanted: 1, got %v", globint2))
}
if save2 != 2 {
t.Fatal(fmt.Sprintf("save2: wanted: 2, got %v", save2))
}
}()
defer func(n nonSSAable) {
save1 = n[4]
}(mknonSSAable())
defer func(b bigStruct) {
save2 = b.y
}(bigStruct{1, 2, 3, 4, 5, sideeffect(6)})
}
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#define FUNCDATA_RegPointerMaps 2 #define FUNCDATA_RegPointerMaps 2
#define FUNCDATA_StackObjects 3 #define FUNCDATA_StackObjects 3
#define FUNCDATA_InlTree 4 #define FUNCDATA_InlTree 4
#define FUNCDATA_OpenCodedDeferInfo 5 /* info for func with open-coded defers */
// Pseudo-assembly statements. // Pseudo-assembly statements.
......
This diff is collapsed.
...@@ -701,7 +701,7 @@ type _func struct { ...@@ -701,7 +701,7 @@ type _func struct {
nameoff int32 // function name nameoff int32 // function name
args int32 // in/out args size args int32 // in/out args size
deferreturn uint32 // offset of a deferreturn block from entry, if any. deferreturn uint32 // offset of start of a deferreturn call instruction from entry, if any.
pcsp int32 pcsp int32
pcfile int32 pcfile int32
...@@ -774,7 +774,7 @@ func extendRandom(r []byte, n int) { ...@@ -774,7 +774,7 @@ func extendRandom(r []byte, n int) {
} }
// A _defer holds an entry on the list of deferred calls. // A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in freedefer. // If you add a field here, add code to clear it in freedefer and deferProcStack
// This struct must match the code in cmd/compile/internal/gc/reflect.go:deferstruct // This struct must match the code in cmd/compile/internal/gc/reflect.go:deferstruct
// and cmd/compile/internal/gc/ssa.go:(*state).call. // and cmd/compile/internal/gc/ssa.go:(*state).call.
// Some defers will be allocated on the stack and some on the heap. // Some defers will be allocated on the stack and some on the heap.
...@@ -785,11 +785,27 @@ type _defer struct { ...@@ -785,11 +785,27 @@ type _defer struct {
siz int32 // includes both arguments and results siz int32 // includes both arguments and results
started bool started bool
heap bool heap bool
// openDefer indicates that this _defer is for a frame with open-coded
// defers. We have only one defer record for the entire frame (which may
// currently have 0, 1, or more defers active).
openDefer bool
sp uintptr // sp at time of defer sp uintptr // sp at time of defer
pc uintptr pc uintptr // pc at time of defer
fn *funcval fn *funcval
_panic *_panic // panic that is running defer _panic *_panic // panic that is running defer
link *_defer link *_defer
// If openDefer is true, the fields below record values about the stack
// frame and associated function that has the open-coded defer(s). sp
// above will be the sp for the frame, and pc will be address of the
// deferreturn call in the function.
fd unsafe.Pointer // funcdata for the function associated with the frame
varp uintptr // value of varp for the stack frame
// framepc is the current pc associated with the stack frame. Together,
// with sp above (which is the sp associated with the stack frame),
// framepc/sp can be used as pc/sp pair to continue a stack trace via
// gentraceback().
framepc uintptr
} }
// A _panic holds information about an active panic. // A _panic holds information about an active panic.
......
...@@ -89,7 +89,7 @@ func BenchmarkDefer(b *testing.B) { ...@@ -89,7 +89,7 @@ func BenchmarkDefer(b *testing.B) {
} }
func defer1() { func defer1() {
defer func(x, y, z int) { func(x, y, z int) {
if recover() != nil || x != 1 || y != 2 || z != 3 { if recover() != nil || x != 1 || y != 2 || z != 3 {
panic("bad recover") panic("bad recover")
} }
......
...@@ -91,7 +91,7 @@ const ( ...@@ -91,7 +91,7 @@ const (
// The stack guard is a pointer this many bytes above the // The stack guard is a pointer this many bytes above the
// bottom of the stack. // bottom of the stack.
_StackGuard = 880*sys.StackGuardMultiplier + _StackSystem _StackGuard = 896*sys.StackGuardMultiplier + _StackSystem
// After a stack split check the SP is allowed to be this // After a stack split check the SP is allowed to be this
// many bytes below the stack guard. This saves an instruction // many bytes below the stack guard. This saves an instruction
...@@ -736,6 +736,8 @@ func adjustdefers(gp *g, adjinfo *adjustinfo) { ...@@ -736,6 +736,8 @@ func adjustdefers(gp *g, adjinfo *adjustinfo) {
adjustpointer(adjinfo, unsafe.Pointer(&d.sp)) adjustpointer(adjinfo, unsafe.Pointer(&d.sp))
adjustpointer(adjinfo, unsafe.Pointer(&d._panic)) adjustpointer(adjinfo, unsafe.Pointer(&d._panic))
adjustpointer(adjinfo, unsafe.Pointer(&d.link)) adjustpointer(adjinfo, unsafe.Pointer(&d.link))
adjustpointer(adjinfo, unsafe.Pointer(&d.varp))
adjustpointer(adjinfo, unsafe.Pointer(&d.fd))
} }
// Adjust defer argument blocks the same way we adjust active stack frames. // Adjust defer argument blocks the same way we adjust active stack frames.
......
...@@ -221,6 +221,7 @@ const ( ...@@ -221,6 +221,7 @@ const (
_FUNCDATA_RegPointerMaps = 2 _FUNCDATA_RegPointerMaps = 2
_FUNCDATA_StackObjects = 3 _FUNCDATA_StackObjects = 3
_FUNCDATA_InlTree = 4 _FUNCDATA_InlTree = 4
_FUNCDATA_OpenCodedDeferInfo = 5
_ArgsSizeUnknown = -0x80000000 _ArgsSizeUnknown = -0x80000000
) )
......
// errorcheck -0 -l -d=defer
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// check that open-coded defers are used in expected situations
package main
import "fmt"
var glob = 3
func f1() {
for i := 0; i < 10; i++ {
fmt.Println("loop")
}
defer func() { // ERROR "open-coded defer in function f1"
fmt.Println("defer")
}()
}
func f2() {
for {
defer func() { // ERROR "heap-allocated defer in function f2"
fmt.Println("defer1")
}()
if glob > 2 {
break
}
}
defer func() { // ERROR "stack-allocated defer in function f2"
fmt.Println("defer2")
}()
}
func f3() {
defer func() { // ERROR "stack-allocated defer in function f3"
fmt.Println("defer2")
}()
for {
defer func() { // ERROR "heap-allocated defer in function f3"
fmt.Println("defer1")
}()
if glob > 2 {
break
}
}
}
func f4() {
defer func() { // ERROR "open-coded defer in function f4"
fmt.Println("defer")
}()
label:
fmt.Println("goto loop")
if glob > 2 {
goto label
}
}
func f5() {
label:
fmt.Println("goto loop")
defer func() { // ERROR "heap-allocated defer in function f5"
fmt.Println("defer")
}()
if glob > 2 {
goto label
}
}
func f6() {
label:
fmt.Println("goto loop")
if glob > 2 {
goto label
}
// The current analysis doesn't end a backward goto loop, so this defer is
// considered to be inside a loop
defer func() { // ERROR "heap-allocated defer in function f6"
fmt.Println("defer")
}()
}
...@@ -367,16 +367,19 @@ func f24() { ...@@ -367,16 +367,19 @@ func f24() {
m2[[2]string{"x", "y"}] = nil m2[[2]string{"x", "y"}] = nil
} }
// defer should not cause spurious ambiguously live variables // Non-open-coded defers should not cause autotmps. (Open-coded defers do create extra autotmps).
func f25(b bool) { func f25(b bool) {
for i := 0; i < 2; i++ {
// Put in loop to make sure defer is not open-coded
defer g25() defer g25()
}
if b { if b {
return return
} }
var x string var x string
x = g14() x = g14()
printstring(x) printstring(x)
return
} }
func g25() func g25()
...@@ -417,7 +420,8 @@ func f27defer(b bool) { ...@@ -417,7 +420,8 @@ func f27defer(b bool) {
defer call27(func() { x++ }) // ERROR "stack object .autotmp_[0-9]+ struct \{" defer call27(func() { x++ }) // ERROR "stack object .autotmp_[0-9]+ struct \{"
} }
defer call27(func() { x++ }) // ERROR "stack object .autotmp_[0-9]+ struct \{" defer call27(func() { x++ }) // ERROR "stack object .autotmp_[0-9]+ struct \{"
printnl() printnl() // ERROR "live at call to printnl: .autotmp_[0-9]+ .autotmp_[0-9]+"
return // ERROR "live at call to call27: .autotmp_[0-9]+"
} }
// and newproc (go) escapes to the heap // and newproc (go) escapes to the heap
...@@ -687,12 +691,12 @@ type R struct{ *T } // ERRORAUTO "live at entry to \(\*R\)\.Foo: \.this ptr" "li ...@@ -687,12 +691,12 @@ type R struct{ *T } // ERRORAUTO "live at entry to \(\*R\)\.Foo: \.this ptr" "li
// In particular, at printint r must be live. // In particular, at printint r must be live.
func f41(p, q *int) (r *int) { // ERROR "live at entry to f41: p q$" func f41(p, q *int) (r *int) { // ERROR "live at entry to f41: p q$"
r = p r = p
defer func() { // ERROR "live at call to deferprocStack: q r$" "live at call to deferreturn: r$" defer func() {
recover() recover()
}() }()
printint(0) // ERROR "live at call to printint: q r$" printint(0) // ERROR "live at call to printint: q r .autotmp_[0-9]+$"
r = q r = q
return // ERROR "live at call to deferreturn: r$" return // ERROR "live at call to f41.func1: r .autotmp_[0-9]+$"
} }
func f42() { func f42() {
......
...@@ -309,17 +309,17 @@ TestCases: ...@@ -309,17 +309,17 @@ TestCases:
name := m[1] name := m[1]
size, _ := strconv.Atoi(m[2]) size, _ := strconv.Atoi(m[2])
// The limit was originally 128 but is now 752 (880-128). // The limit was originally 128 but is now 768 (896-128).
// Instead of rewriting the test cases above, adjust // Instead of rewriting the test cases above, adjust
// the first stack frame to use up the extra bytes. // the first stack frame to use up the extra bytes.
if i == 0 { if i == 0 {
size += (880 - 128) - 128 size += (896 - 128) - 128
// Noopt builds have a larger stackguard. // Noopt builds have a larger stackguard.
// See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier // See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
// This increase is included in objabi.StackGuard // This increase is included in objabi.StackGuard
for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") { for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
if s == "-N" { if s == "-N" {
size += 880 size += 896
} }
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment