aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/cgocall.go
diff options
context:
space:
mode:
authorAustin Clements <austin@google.com>2020-10-01 17:22:38 -0400
committerAustin Clements <austin@google.com>2020-10-26 14:50:32 +0000
commit30c18878730434027dbefd343aad74963a1fdc48 (patch)
treefa134fee222fee13eca873820988d42cafd33b25 /src/runtime/cgocall.go
parent404899f6b56800c1d8e0521fc9ce0c856e459d94 (diff)
downloadgo-30c18878730434027dbefd343aad74963a1fdc48.tar.gz
go-30c18878730434027dbefd343aad74963a1fdc48.zip
runtime,cmd/cgo: simplify C -> Go call path
This redesigns the way calls work from C to exported Go functions. It removes several steps from the call path, makes cmd/cgo no longer sensitive to the Go calling convention, and eliminates the use of reflectcall from cgo. In order to avoid generating a large amount of FFI glue between the C and Go ABIs, the cgo tool has long depended on generating a C function that marshals the arguments into a struct, and then the actual ABI switch happens in functions with fixed signatures that simply take a pointer to this struct. In a way, this CL simply pushes this idea further. Currently, the cgo tool generates this argument struct in the exact layout of the Go stack frame and depends on reflectcall to unpack it into the appropriate Go call (even though it's actually reflectcall'ing a function generated by cgo). In this CL, we decouple this struct from the Go stack layout. Instead, cgo generates a Go function that takes the struct, unpacks it, and calls the exported function. Since this generated function has a generic signature (like the rest of the call path), we don't need reflectcall and can instead depend on the Go compiler itself to implement the call to the exported Go function. One complication is that syscall.NewCallback on Windows, which converts a Go function into a C function pointer, depends on cgocallback's current dynamic calling approach since the signatures of the callbacks aren't known statically. For this specific case, we continue to depend on reflectcall. Really, the current approach makes some overly simplistic assumptions about translating the C ABI to the Go ABI. Now we're at least in a much better position to do a proper ABI translation. For comparison, the current cgo call path looks like: GoF (generated C function) -> crosscall2 (in cgo/asm_*.s) -> _cgoexp_GoF (generated Go function) -> cgocallback (in asm_*.s) -> cgocallback_gofunc (in asm_*.s) -> cgocallbackg (in cgocall.go) -> cgocallbackg1 (in cgocall.go) -> reflectcall (in asm_*.s) -> _cgoexpwrap_GoF (generated Go function) -> p.GoF Now the call path looks like: GoF (generated C function) -> crosscall2 (in cgo/asm_*.s) -> cgocallback (in asm_*.s) -> cgocallbackg (in cgocall.go) -> cgocallbackg1 (in cgocall.go) -> _cgoexp_GoF (generated Go function) -> p.GoF Notably: 1. We combine _cgoexp_GoF and _cgoexpwrap_GoF and move the combined operation to the end of the sequence. This combined function also handles reflectcall's previous role. 2. We combined cgocallback and cgocallback_gofunc since the only purpose of having both was to convert a raw PC into a Go function value. We instead construct the Go function value in cgocallbackg1. 3. cgocallbackg1 no longer reaches backwards through the stack to get the arguments to cgocallback_gofunc. Instead, we just pass the arguments down. 4. Currently, we need an explicit msanwrite to mark the results struct as written because reflectcall doesn't do this. Now, the results are written by regular Go assignments, so the Go compiler generates the necessary MSAN annotations. This also means we no longer need to track the size of the arguments frame. Updates #40724, since now we don't need to teach cgo about the register ABI or change how it uses reflectcall. Change-Id: I7840489a2597962aeb670e0c1798a16a7359c94f Reviewed-on: https://go-review.googlesource.com/c/go/+/258938 Trust: Austin Clements <austin@google.com> Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Cherry Zhang <cherryyz@google.com>
Diffstat (limited to 'src/runtime/cgocall.go')
-rw-r--r--src/runtime/cgocall.go140
1 files changed, 39 insertions, 101 deletions
diff --git a/src/runtime/cgocall.go b/src/runtime/cgocall.go
index 7ab42a0ed0..9bca279318 100644
--- a/src/runtime/cgocall.go
+++ b/src/runtime/cgocall.go
@@ -35,31 +35,25 @@
// cgo writes a gcc-compiled function named GoF (not p.GoF, since gcc doesn't
// know about packages). The gcc-compiled C function f calls GoF.
//
-// GoF calls crosscall2(_cgoexp_GoF, frame, framesize, ctxt).
-// Crosscall2 (in cgo/asm_$GOARCH.s) is a four-argument adapter from
-// the gcc function call ABI to the gc function call ABI.
-// It is called from gcc to call gc functions. In this case it calls
-// _cgoexp_GoF(frame, framesize), still running on m.g0's stack
-// and outside the $GOMAXPROCS limit. Thus, this code cannot yet
-// call arbitrary Go code directly and must be careful not to allocate
-// memory or use up m.g0's stack.
+// GoF initializes "frame", a structure containing all of its
+// arguments and slots for p.GoF's results. It calls
+// crosscall2(_cgoexp_GoF, frame, framesize, ctxt) using the gcc ABI.
//
-// _cgoexp_GoF (generated by cmd/cgo) calls
-// runtime.cgocallback(funcPC(p.GoF), frame, framesize, ctxt).
-// (The reason for having _cgoexp_GoF instead of writing a crosscall3
-// to make this call directly is that _cgoexp_GoF, because it is compiled
-// with gc instead of gcc, can refer to dotted names like
-// runtime.cgocallback and p.GoF.)
+// crosscall2 (in cgo/asm_$GOARCH.s) is a four-argument adapter from
+// the gcc function call ABI to the gc function call ABI. At this
+// point we're in the Go runtime, but we're still running on m.g0's
+// stack and outside the $GOMAXPROCS limit. crosscall2 calls
+// runtime.cgocallback(_cgoexp_GoF, frame, ctxt) using the gc ABI.
+// (crosscall2's framesize argument is no longer used, but there's one
+// case where SWIG calls crosscall2 directly and expects to pass this
+// argument. See _cgo_panic.)
//
-// runtime.cgocallback (in asm_$GOARCH.s) turns the raw PC of p.GoF
-// into a Go function value and calls runtime.cgocallback_gofunc.
-//
-// runtime.cgocallback_gofunc (in asm_$GOARCH.s) switches from m.g0's
-// stack to the original g (m.curg)'s stack, on which it calls
-// runtime.cgocallbackg(p.GoF, frame, framesize).
-// As part of the stack switch, runtime.cgocallback saves the current
-// SP as m.g0.sched.sp, so that any use of m.g0's stack during the
-// execution of the callback will be done below the existing stack frames.
+// runtime.cgocallback (in asm_$GOARCH.s) switches from m.g0's stack
+// to the original g (m.curg)'s stack, on which it calls
+// runtime.cgocallbackg(_cgoexp_GoF, frame, ctxt). As part of the
+// stack switch, runtime.cgocallback saves the current SP as
+// m.g0.sched.sp, so that any use of m.g0's stack during the execution
+// of the callback will be done below the existing stack frames.
// Before overwriting m.g0.sched.sp, it pushes the old value on the
// m.g0 stack, so that it can be restored later.
//
@@ -67,19 +61,26 @@
// stack (not an m.g0 stack). First it calls runtime.exitsyscall, which will
// block until the $GOMAXPROCS limit allows running this goroutine.
// Once exitsyscall has returned, it is safe to do things like call the memory
-// allocator or invoke the Go callback function p.GoF. runtime.cgocallbackg
+// allocator or invoke the Go callback function. runtime.cgocallbackg
// first defers a function to unwind m.g0.sched.sp, so that if p.GoF
// panics, m.g0.sched.sp will be restored to its old value: the m.g0 stack
// and the m.curg stack will be unwound in lock step.
-// Then it calls p.GoF. Finally it pops but does not execute the deferred
-// function, calls runtime.entersyscall, and returns to runtime.cgocallback.
+// Then it calls _cgoexp_GoF(frame).
+//
+// _cgoexp_GoF, which was generated by cmd/cgo, unpacks the arguments
+// from frame, calls p.GoF, writes the results back to frame, and
+// returns. Now we start unwinding this whole process.
+//
+// runtime.cgocallbackg pops but does not execute the deferred
+// function to unwind m.g0.sched.sp, calls runtime.entersyscall, and
+// returns to runtime.cgocallback.
//
// After it regains control, runtime.cgocallback switches back to
// m.g0's stack (the pointer is still in m.g0.sched.sp), restores the old
-// m.g0.sched.sp value from the stack, and returns to _cgoexp_GoF.
+// m.g0.sched.sp value from the stack, and returns to crosscall2.
//
-// _cgoexp_GoF immediately returns to crosscall2, which restores the
-// callee-save registers for gcc and returns to GoF, which returns to f.
+// crosscall2 restores the callee-save registers for gcc and returns
+// to GoF, which unpacks any result values and returns to f.
package runtime
@@ -196,7 +197,7 @@ func cgocall(fn, arg unsafe.Pointer) int32 {
// Call from C back to Go.
//go:nosplit
-func cgocallbackg(ctxt uintptr) {
+func cgocallbackg(fn, frame unsafe.Pointer, ctxt uintptr) {
gp := getg()
if gp != gp.m.curg {
println("runtime: bad g in cgocallback")
@@ -224,7 +225,7 @@ func cgocallbackg(ctxt uintptr) {
osPreemptExtExit(gp.m)
- cgocallbackg1(ctxt)
+ cgocallbackg1(fn, frame, ctxt)
// At this point unlockOSThread has been called.
// The following code must not change to a different m.
@@ -239,7 +240,7 @@ func cgocallbackg(ctxt uintptr) {
gp.m.syscall = syscall
}
-func cgocallbackg1(ctxt uintptr) {
+func cgocallbackg1(fn, frame unsafe.Pointer, ctxt uintptr) {
gp := getg()
if gp.m.needextram || atomic.Load(&extraMWaiters) > 0 {
gp.m.needextram = false
@@ -283,79 +284,16 @@ func cgocallbackg1(ctxt uintptr) {
raceacquire(unsafe.Pointer(&racecgosync))
}
- type args struct {
- fn *funcval
- arg unsafe.Pointer
- argsize uintptr
- }
- var cb *args
-
- // Location of callback arguments depends on stack frame layout
- // and size of stack frame of cgocallback_gofunc.
- sp := gp.m.g0.sched.sp
- switch GOARCH {
- default:
- throw("cgocallbackg is unimplemented on arch")
- case "arm":
- // On arm, stack frame is two words and there's a saved LR between
- // SP and the stack frame and between the stack frame and the arguments.
- cb = (*args)(unsafe.Pointer(sp + 4*sys.PtrSize))
- case "arm64":
- // On arm64, stack frame is four words and there's a saved LR between
- // SP and the stack frame and between the stack frame and the arguments.
- // Additional two words (16-byte alignment) are for saving FP.
- cb = (*args)(unsafe.Pointer(sp + 7*sys.PtrSize))
- case "amd64":
- // On amd64, stack frame is two words, plus caller PC and BP.
- cb = (*args)(unsafe.Pointer(sp + 4*sys.PtrSize))
- case "386":
- // On 386, stack frame is three words, plus caller PC.
- cb = (*args)(unsafe.Pointer(sp + 4*sys.PtrSize))
- case "ppc64", "ppc64le", "s390x":
- // On ppc64 and s390x, the callback arguments are in the arguments area of
- // cgocallback's stack frame. The stack looks like this:
- // +--------------------+------------------------------+
- // | | ... |
- // | cgoexp_$fn +------------------------------+
- // | | fixed frame area |
- // +--------------------+------------------------------+
- // | | arguments area |
- // | cgocallback +------------------------------+ <- sp + 2*minFrameSize + 2*ptrSize
- // | | fixed frame area |
- // +--------------------+------------------------------+ <- sp + minFrameSize + 2*ptrSize
- // | | local variables (2 pointers) |
- // | cgocallback_gofunc +------------------------------+ <- sp + minFrameSize
- // | | fixed frame area |
- // +--------------------+------------------------------+ <- sp
- cb = (*args)(unsafe.Pointer(sp + 2*sys.MinFrameSize + 2*sys.PtrSize))
- case "mips64", "mips64le":
- // On mips64x, stack frame is two words and there's a saved LR between
- // SP and the stack frame and between the stack frame and the arguments.
- cb = (*args)(unsafe.Pointer(sp + 4*sys.PtrSize))
- case "mips", "mipsle":
- // On mipsx, stack frame is two words and there's a saved LR between
- // SP and the stack frame and between the stack frame and the arguments.
- cb = (*args)(unsafe.Pointer(sp + 4*sys.PtrSize))
- }
-
- // Invoke callback.
- // NOTE(rsc): passing nil for argtype means that the copying of the
- // results back into cb.arg happens without any corresponding write barriers.
- // For cgo, cb.arg points into a C stack frame and therefore doesn't
- // hold any pointers that the GC can find anyway - the write barrier
- // would be a no-op.
- reflectcall(nil, unsafe.Pointer(cb.fn), cb.arg, uint32(cb.argsize), 0)
+ // Invoke callback. This function is generated by cmd/cgo and
+ // will unpack the argument frame and call the Go function.
+ var cb func(frame unsafe.Pointer)
+ cbFV := funcval{uintptr(fn)}
+ *(*unsafe.Pointer)(unsafe.Pointer(&cb)) = noescape(unsafe.Pointer(&cbFV))
+ cb(frame)
if raceenabled {
racereleasemerge(unsafe.Pointer(&racecgosync))
}
- if msanenabled {
- // Tell msan that we wrote to the entire argument block.
- // This tells msan that we set the results.
- // Since we have already called the function it doesn't
- // matter that we are writing to the non-result parameters.
- msanwrite(cb.arg, cb.argsize)
- }
// Do not unwind m->g0->sched.sp.
// Our caller, cgocallback, will do that.