From c847932804467f511b0e123cf29b72dd0d509306 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Tue, 30 Mar 2021 17:55:22 -0400 Subject: [PATCH] runtime: replace reflectcall of defers with direct call With GOEXPERIMENT=regabidefer, all deferred functions take no arguments and have no results (their signature is always func()). Since the signature is fixed, we can replace all of the reflectcalls in the defer code with direct closure calls. For #40724. Change-Id: I3acd6742fe665610608a004c675f473b9d0e65ee Reviewed-on: https://go-review.googlesource.com/c/go/+/306010 Trust: Austin Clements Run-TryBot: Austin Clements Reviewed-by: Michael Knyszek Reviewed-by: Than McIntosh --- src/cmd/internal/objabi/funcid.go | 1 + src/runtime/panic.go | 83 +++++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/cmd/internal/objabi/funcid.go b/src/cmd/internal/objabi/funcid.go index 6e188e31bb..fa28609e4d 100644 --- a/src/cmd/internal/objabi/funcid.go +++ b/src/cmd/internal/objabi/funcid.go @@ -75,6 +75,7 @@ var funcIDs = map[string]FuncID{ "deferreturn": FuncID_wrapper, "runOpenDeferFrame": FuncID_wrapper, "reflectcallSave": FuncID_wrapper, + "deferCallSave": FuncID_wrapper, } // Get the function ID for the named function in the named file. diff --git a/src/runtime/panic.go b/src/runtime/panic.go index c265a5af79..bbf3ea473a 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -382,6 +382,19 @@ func deferArgs(d *_defer) unsafe.Pointer { return add(unsafe.Pointer(d), unsafe.Sizeof(*d)) } +// deferFunc returns d's deferred function. This is temporary while we +// support both modes of GOEXPERIMENT=regabidefer. Once we commit to +// that experiment, we should change the type of d.fn. +//go:nosplit +func deferFunc(d *_defer) func() { + if !experimentRegabiDefer { + throw("requires experimentRegabiDefer") + } + var fn func() + *(**funcval)(unsafe.Pointer(&fn)) = d.fn + return fn +} + var deferType *_type // type of _defer struct func init() { @@ -635,10 +648,15 @@ func Goexit() { addOneOpenDeferFrame(gp, 0, nil) } } else { - - // Save the pc/sp in reflectcallSave(), so we can "recover" back to this - // loop if necessary. - reflectcallSave(&p, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz)) + if experimentRegabiDefer { + // Save the pc/sp in deferCallSave(), so we can "recover" back to this + // loop if necessary. + deferCallSave(&p, deferFunc(d)) + } else { + // Save the pc/sp in reflectcallSave(), so we can "recover" back to this + // loop if necessary. + reflectcallSave(&p, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz)) + } } if p.aborted { // We had a recursive panic in the defer d we started, and @@ -860,7 +878,11 @@ func runOpenDeferFrame(gp *g, d *_defer) bool { deferBits = deferBits &^ (1 << i) *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset))) = deferBits p := d._panic - reflectcallSave(p, unsafe.Pointer(closure), deferArgs, argWidth) + if experimentRegabiDefer { + deferCallSave(p, deferFunc(d)) + } else { + reflectcallSave(p, unsafe.Pointer(closure), deferArgs, argWidth) + } if p != nil && p.aborted { break } @@ -880,17 +902,20 @@ func runOpenDeferFrame(gp *g, d *_defer) bool { // panic record. This allows the runtime to return to the Goexit defer processing // loop, in the unusual case where the Goexit may be bypassed by a successful // recover. +// +// This is marked as a wrapper by the compiler so it doesn't appear in +// tracebacks. func reflectcallSave(p *_panic, fn, arg unsafe.Pointer, argsize uint32) { + if experimentRegabiDefer { + throw("not allowed with experimentRegabiDefer") + } if p != nil { p.argp = unsafe.Pointer(getargp(0)) p.pc = getcallerpc() p.sp = unsafe.Pointer(getcallersp()) } - // Pass a dummy RegArgs for now since no function actually implements - // the register-based ABI. - // - // TODO(mknyszek): Implement this properly, setting up arguments in - // registers as necessary in the caller. + // Pass a dummy RegArgs since we'll only take this path if + // we're not using the register ABI. var regs abi.RegArgs reflectcall(nil, fn, arg, argsize, argsize, argsize, ®s) if p != nil { @@ -899,6 +924,29 @@ func reflectcallSave(p *_panic, fn, arg unsafe.Pointer, argsize uint32) { } } +// deferCallSave calls fn() after saving the caller's pc and sp in the +// panic record. This allows the runtime to return to the Goexit defer +// processing loop, in the unusual case where the Goexit may be +// bypassed by a successful recover. +// +// This is marked as a wrapper by the compiler so it doesn't appear in +// tracebacks. +func deferCallSave(p *_panic, fn func()) { + if !experimentRegabiDefer { + throw("only allowed with experimentRegabiDefer") + } + if p != nil { + p.argp = unsafe.Pointer(getargp(0)) + p.pc = getcallerpc() + p.sp = unsafe.Pointer(getcallersp()) + } + fn() + if p != nil { + p.pc = 0 + p.sp = unsafe.Pointer(nil) + } +} + // The implementation of the predeclared function panic. func gopanic(e interface{}) { gp := getg() @@ -970,7 +1018,7 @@ func gopanic(e interface{}) { // Mark defer as started, but keep on list, so that traceback // can find and update the defer's argument frame if stack growth - // or a garbage collection happens before reflectcall starts executing d.fn. + // or a garbage collection happens before executing d.fn. d.started = true // Record the panic that is running the defer. @@ -987,12 +1035,19 @@ func gopanic(e interface{}) { } else { p.argp = unsafe.Pointer(getargp(0)) - var regs abi.RegArgs - reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz), uint32(d.siz), ®s) + if experimentRegabiDefer { + fn := deferFunc(d) + fn() + } else { + // Pass a dummy RegArgs since we'll only take this path if + // we're not using the register ABI. + var regs abi.RegArgs + reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz), uint32(d.siz), ®s) + } } p.argp = nil - // reflectcall did not panic. Remove d. + // Deferred function did not panic. Remove d. if gp._defer != d { throw("bad defer entry in panic") }