diff options
author | Michael Anthony Knyszek <mknyszek@google.com> | 2021-04-10 00:05:07 +0000 |
---|---|---|
committer | Michael Knyszek <mknyszek@google.com> | 2021-04-14 19:54:26 +0000 |
commit | a89ace106f820a4f3b129c81ba0bcf0c48b5b7cd (patch) | |
tree | 57f8b82d0e3d9215334fc3cd939293c96e8a2014 /src/runtime/debug_test.go | |
parent | de7a87ef066d726eddcc47a018a2bc8fbd3793af (diff) | |
download | go-a89ace106f820a4f3b129c81ba0bcf0c48b5b7cd.tar.gz go-a89ace106f820a4f3b129c81ba0bcf0c48b5b7cd.zip |
runtime: update debug call protocol for register ABI
The debug call tests currently assume that the target Go function is
ABI0; this is clearly no longer true when we switch to the new ABI, so
make the tests set up argument register state in the debug call handler
and copy back results returned in registers.
A small snag in calling a Go function that follows the new ABI is that
the debug call protocol depends on the AX register being set to a
specific value as it bounces in and out of the handler, but this
register is part of the new register ABI, so results end up being
clobbered. Use R12 instead.
Next, the new desugaring behavior for "go" statements means that
newosproc1 must always call a function with no frame; if it takes any
arguments, it closes over them and they're passed in the context
register. Currently when debugCallWrap creates a new goroutine, it uses
newosproc1 directly and passes a non-zero-sized frame, so that needs to
be updated. To fix this, briefly use the g's param field which is
otherwise only used for channels to pass an explicitly allocated object
containing the "closed over" variables. While we could manually do the
desugaring ourselves (we cannot do so automatically because the Go
compiler prevents heap-allocated closures in the runtime), that bakes in
more ABI details in a place that really doesn't need to care about them.
Finally, there's an old bug here where the context register was set up
in CX, so technically closure calls never worked. Oops. It was otherwise
harmless for other types of calls before, but now CX is an argument
register, so now that interferes with regular calls, too.
For #40724.
Change-Id: I652c25ed56a25741bb04c24cfb603063c099edde
Reviewed-on: https://go-review.googlesource.com/c/go/+/309169
Trust: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Reviewed-by: Alessandro Arzilli <alessandro.arzilli@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Diffstat (limited to 'src/runtime/debug_test.go')
-rw-r--r-- | src/runtime/debug_test.go | 67 |
1 files changed, 47 insertions, 20 deletions
diff --git a/src/runtime/debug_test.go b/src/runtime/debug_test.go index 7f9e460303..f74383457f 100644 --- a/src/runtime/debug_test.go +++ b/src/runtime/debug_test.go @@ -9,17 +9,16 @@ // spends all of its time in the race runtime, which isn't a safe // point. -// TODO(register args): We skip this under GOEXPERIMENT=regabidefer -// because debugCallWrap passes a non-empty frame to newproc1, -// triggering a panic. - -//go:build amd64 && linux && !race && !goexperiment.regabidefer -// +build amd64,linux,!race,!goexperiment.regabidefer +//go:build amd64 && linux && !race +// +build amd64,linux,!race package runtime_test import ( "fmt" + "internal/abi" + "internal/goexperiment" + "math" "os" "regexp" "runtime" @@ -119,21 +118,49 @@ func TestDebugCall(t *testing.T) { g, after := startDebugCallWorker(t) defer after() + type stackArgs struct { + x0 int + x1 float64 + y0Ret int + y1Ret float64 + } + // Inject a call into the debugCallWorker goroutine and test // basic argument and result passing. - var args struct { - x int - yRet int + fn := func(x int, y float64) (y0Ret int, y1Ret float64) { + return x + 1, y + 1.0 } - fn := func(x int) (yRet int) { - return x + 1 + var args *stackArgs + var regs abi.RegArgs + intRegs := regs.Ints[:] + floatRegs := regs.Floats[:] + fval := float64(42.0) + if goexperiment.RegabiArgs { + intRegs[0] = 42 + floatRegs[0] = math.Float64bits(fval) + } else { + args = &stackArgs{ + x0: 42, + x1: 42.0, + } } - args.x = 42 - if _, err := runtime.InjectDebugCall(g, fn, &args, debugCallTKill, false); err != nil { + if _, err := runtime.InjectDebugCall(g, fn, ®s, args, debugCallTKill, false); err != nil { t.Fatal(err) } - if args.yRet != 43 { - t.Fatalf("want 43, got %d", args.yRet) + var result0 int + var result1 float64 + if goexperiment.RegabiArgs { + result0 = int(intRegs[0]) + result1 = math.Float64frombits(floatRegs[0]) + } else { + result0 = args.y0Ret + result1 = args.y1Ret + } + if result0 != 43 { + t.Errorf("want 43, got %d", result0) + } + if result1 != fval+1 { + t.Errorf("want 43, got %f", result1) } } @@ -158,7 +185,7 @@ func TestDebugCallLarge(t *testing.T) { args.in[i] = i want[i] = i + 1 } - if _, err := runtime.InjectDebugCall(g, fn, &args, debugCallTKill, false); err != nil { + if _, err := runtime.InjectDebugCall(g, fn, nil, &args, debugCallTKill, false); err != nil { t.Fatal(err) } if want != args.out { @@ -171,7 +198,7 @@ func TestDebugCallGC(t *testing.T) { defer after() // Inject a call that performs a GC. - if _, err := runtime.InjectDebugCall(g, runtime.GC, nil, debugCallTKill, false); err != nil { + if _, err := runtime.InjectDebugCall(g, runtime.GC, nil, nil, debugCallTKill, false); err != nil { t.Fatal(err) } } @@ -182,7 +209,7 @@ func TestDebugCallGrowStack(t *testing.T) { // Inject a call that grows the stack. debugCallWorker checks // for stack pointer breakage. - if _, err := runtime.InjectDebugCall(g, func() { growStack(nil) }, nil, debugCallTKill, false); err != nil { + if _, err := runtime.InjectDebugCall(g, func() { growStack(nil) }, nil, nil, debugCallTKill, false); err != nil { t.Fatal(err) } } @@ -218,7 +245,7 @@ func TestDebugCallUnsafePoint(t *testing.T) { runtime.Gosched() } - _, err := runtime.InjectDebugCall(g, func() {}, nil, debugCallTKill, true) + _, err := runtime.InjectDebugCall(g, func() {}, nil, nil, debugCallTKill, true) if msg := "call not at safe point"; err == nil || err.Error() != msg { t.Fatalf("want %q, got %s", msg, err) } @@ -242,7 +269,7 @@ func TestDebugCallPanic(t *testing.T) { }() g := <-ready - p, err := runtime.InjectDebugCall(g, func() { panic("test") }, nil, debugCallTKill, false) + p, err := runtime.InjectDebugCall(g, func() { panic("test") }, nil, nil, debugCallTKill, false) if err != nil { t.Fatal(err) } |