Skip to content

Commit 8be94b8

Browse files
aclementsgopherbot
authored andcommitted
runtime: drop function context from traceback
Currently, gentraceback tracks the closure context of the outermost frame. This used to be important for "unstarted" calls to reflect function stubs, where "unstarted" calls are either deferred functions or the entry-point of a goroutine that hasn't run. Because reflect function stubs have a dynamic argument map, we have to reach into their closure context to fetch to map, and how to do this differs depending on whether the function has started. This was discovered in issue #25897. However, as part of the register ABI, "go" and "defer" were made much simpler, and any "go" or "defer" of a function that takes arguments or returns results gets wrapped in a closure that provides those arguments (and/or discards the results). Hence, we'll see that closure instead of a direct call to a reflect stub, and can get its static argument map without any trouble. The one case where we may still see an unstarted reflect stub is if the function takes no arguments and has no results, in which case the compiler can optimize away the wrapper closure. But in this case we know the argument map is empty: the compiler can apply this optimization precisely because the target function has no argument frame. As a result, we no longer need to track the closure context during traceback, so this CL drops all of that mechanism. We still have to be careful about the unstarted case because we can't reach into the function's locals frame to pull out its context (because it has no locals frame). We double-check that in this case we're at the function entry. I would prefer to do this with some in-code PCDATA annotations of where to find the dynamic argument map, but that's a lot of mechanism to introduce for just this. It might make sense to consider this along with #53609. Finally, we beef up the test for this so it more reliably forces the runtime down this path. It's fundamentally probabilistic, but this tweak makes it better. Scheduler testing hooks (#54475) would make it possible to write a reliable test for this. For #54466, but it's a nice clean-up all on its own. Change-Id: I16e4f2364ba2ea4b1fec1e27f971b06756e7b09f Reviewed-on: https://go-review.googlesource.com/c/go/+/424254 Run-TryBot: Austin Clements <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Auto-Submit: Austin Clements <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent 2efb579 commit 8be94b8

File tree

3 files changed

+57
-34
lines changed

3 files changed

+57
-34
lines changed

src/cmd/compile/internal/escape/call.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ func (e *escape) goDeferStmt(n *ir.GoDeferStmt) {
223223

224224
// If the function is already a zero argument/result function call,
225225
// just escape analyze it normally.
226+
//
227+
// Note that the runtime is aware of this optimization for
228+
// "go" statements that start in reflect.makeFuncStub or
229+
// reflect.methodValueCall.
226230
if call, ok := call.(*ir.CallExpr); ok && call.Op() == ir.OCALLFUNC {
227231
if sig := call.X.Type(); sig.NumParams()+sig.NumResults() == 0 {
228232
if clo, ok := call.X.(*ir.ClosureExpr); ok && n.Op() == ir.OGO {

src/runtime/traceback.go

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
5353
}
5454
level, _, _ := gotraceback()
5555

56-
var ctxt *funcval // Context pointer for unstarted goroutines. See issue #25897.
57-
5856
if pc0 == ^uintptr(0) && sp0 == ^uintptr(0) { // Signal to fetch saved values from gp.
5957
if gp.syscallsp != 0 {
6058
pc0 = gp.syscallpc
@@ -68,7 +66,6 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
6866
if usesLR {
6967
lr0 = gp.sched.lr
7068
}
71-
ctxt = (*funcval)(gp.sched.ctxt)
7269
}
7370
}
7471

@@ -297,10 +294,9 @@ func gentraceback(pc0, sp0, lr0 uintptr, gp *g, skip int, pcbuf *uintptr, max in
297294
var ok bool
298295
frame.arglen, frame.argmap, ok = getArgInfoFast(f, callback != nil)
299296
if !ok {
300-
frame.arglen, frame.argmap = getArgInfo(&frame, f, callback != nil, ctxt)
297+
frame.arglen, frame.argmap = getArgInfo(&frame, f, callback != nil)
301298
}
302299
}
303-
ctxt = nil // ctxt is only needed to get arg maps for the topmost frame
304300

305301
// Determine frame's 'continuation PC', where it can continue.
306302
// Normally this is the return address on the stack, but if sigpanic
@@ -683,40 +679,46 @@ func getArgInfoFast(f funcInfo, needArgMap bool) (arglen uintptr, argmap *bitvec
683679

684680
// getArgInfo returns the argument frame information for a call to f
685681
// with call frame frame.
686-
//
687-
// This is used for both actual calls with active stack frames and for
688-
// deferred calls or goroutines that are not yet executing. If this is an actual
689-
// call, ctxt must be nil (getArgInfo will retrieve what it needs from
690-
// the active stack frame). If this is a deferred call or unstarted goroutine,
691-
// ctxt must be the function object that was deferred or go'd.
692-
func getArgInfo(frame *stkframe, f funcInfo, needArgMap bool, ctxt *funcval) (arglen uintptr, argmap *bitvector) {
682+
func getArgInfo(frame *stkframe, f funcInfo, needArgMap bool) (arglen uintptr, argmap *bitvector) {
693683
arglen = uintptr(f.args)
694684
if needArgMap && f.args == _ArgsSizeUnknown {
695685
// Extract argument bitmaps for reflect stubs from the calls they made to reflect.
696686
switch funcname(f) {
697687
case "reflect.makeFuncStub", "reflect.methodValueCall":
698688
// These take a *reflect.methodValue as their
699-
// context register.
700-
var mv *reflectMethodValue
701-
var retValid bool
702-
if ctxt != nil {
703-
// This is not an actual call, but a
704-
// deferred call or an unstarted goroutine.
705-
// The function value is itself the *reflect.methodValue.
706-
mv = (*reflectMethodValue)(unsafe.Pointer(ctxt))
707-
} else {
708-
// This is a real call that took the
709-
// *reflect.methodValue as its context
710-
// register and immediately saved it
711-
// to 0(SP). Get the methodValue from
712-
// 0(SP).
713-
arg0 := frame.sp + sys.MinFrameSize
714-
mv = *(**reflectMethodValue)(unsafe.Pointer(arg0))
715-
// Figure out whether the return values are valid.
716-
// Reflect will update this value after it copies
717-
// in the return values.
718-
retValid = *(*bool)(unsafe.Pointer(arg0 + 4*goarch.PtrSize))
689+
// context register and immediately save it to 0(SP).
690+
// Get the methodValue from 0(SP).
691+
arg0 := frame.sp + sys.MinFrameSize
692+
693+
minSP := frame.fp
694+
if !usesLR {
695+
// The CALL itself pushes a word.
696+
// Undo that adjustment.
697+
minSP -= goarch.PtrSize
698+
}
699+
if arg0 >= minSP {
700+
// The function hasn't started yet.
701+
// This only happens if f was the
702+
// start function of a new goroutine
703+
// that hasn't run yet *and* f takes
704+
// no arguments and has no results
705+
// (otherwise it will get wrapped in a
706+
// closure). In this case, we can't
707+
// reach into its locals because it
708+
// doesn't have locals yet, but we
709+
// also know its argument map is
710+
// empty.
711+
if frame.pc != f.entry() {
712+
print("runtime: confused by ", funcname(f), ": no frame (sp=", hex(frame.sp), " fp=", hex(frame.fp), ") at entry+", hex(frame.pc-f.entry()), "\n")
713+
throw("reflect mismatch")
714+
}
715+
return 0, nil
719716
}
717+
mv := *(**reflectMethodValue)(unsafe.Pointer(arg0))
718+
// Figure out whether the return values are valid.
719+
// Reflect will update this value after it copies
720+
// in the return values.
721+
retValid := *(*bool)(unsafe.Pointer(arg0 + 4*goarch.PtrSize))
720722
if mv.fn != f.entry() {
721723
print("runtime: confused by ", funcname(f), "\n")
722724
throw("reflect mismatch")

test/fixedbugs/issue25897a.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,34 @@ const N = 100
1818

1919
func main() {
2020
runtime.GOMAXPROCS(1)
21+
// Run GC in a loop. This makes it more likely GC will catch
22+
// an unstarted goroutine then if we were to GC after kicking
23+
// everything off.
24+
go func() {
25+
for {
26+
runtime.GC()
27+
}
28+
}()
2129
c := make(chan bool, N)
2230
for i := 0; i < N; i++ {
31+
// Test both with an argument and without because this
32+
// affects whether the compiler needs to generate a
33+
// wrapper closure for the "go" statement.
2334
f := reflect.MakeFunc(reflect.TypeOf(((func(*int))(nil))),
2435
func(args []reflect.Value) []reflect.Value {
2536
c <- true
2637
return nil
2738
}).Interface().(func(*int))
2839
go f(nil)
40+
41+
g := reflect.MakeFunc(reflect.TypeOf(((func())(nil))),
42+
func(args []reflect.Value) []reflect.Value {
43+
c <- true
44+
return nil
45+
}).Interface().(func())
46+
go g()
2947
}
30-
runtime.GC()
31-
for i := 0; i < N; i++ {
48+
for i := 0; i < N*2; i++ {
3249
<-c
3350
}
3451
}

0 commit comments

Comments
 (0)