diff options
author | Austin Clements <austin@google.com> | 2020-10-01 17:22:38 -0400 |
---|---|---|
committer | Austin Clements <austin@google.com> | 2020-10-26 14:50:32 +0000 |
commit | 30c18878730434027dbefd343aad74963a1fdc48 (patch) | |
tree | fa134fee222fee13eca873820988d42cafd33b25 /src/runtime/syscall_windows.go | |
parent | 404899f6b56800c1d8e0521fc9ce0c856e459d94 (diff) | |
download | go-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/syscall_windows.go')
-rw-r--r-- | src/runtime/syscall_windows.go | 42 |
1 files changed, 38 insertions, 4 deletions
diff --git a/src/runtime/syscall_windows.go b/src/runtime/syscall_windows.go index 0e2fcfb02d..ff43e7cbed 100644 --- a/src/runtime/syscall_windows.go +++ b/src/runtime/syscall_windows.go @@ -5,6 +5,7 @@ package runtime import ( + "runtime/internal/sys" "unsafe" ) @@ -22,10 +23,7 @@ func (c *wincallbackcontext) setCleanstack(cleanstack bool) { c.cleanstack = cleanstack } -var ( - cbs callbacks - cbctxts **wincallbackcontext = &cbs.ctxt[0] // to simplify access to cbs.ctxt in sys_windows_*.s -) +var cbs callbacks func callbackasm() @@ -53,6 +51,8 @@ func callbackasmAddr(i int) uintptr { return funcPC(callbackasm) + uintptr(i*entrySize) } +const callbackMaxArgs = 64 + //go:linkname compileCallback syscall.compileCallback func compileCallback(fn eface, cleanstack bool) (code uintptr) { if fn._type == nil || (fn._type.kind&kindMask) != kindFunc { @@ -66,6 +66,9 @@ func compileCallback(fn eface, cleanstack bool) (code uintptr) { if ft.out()[0].size != uintptrSize { panic("compileCallback: expected function with one uintptr-sized result") } + if len(ft.in()) > callbackMaxArgs { + panic("compileCallback: too many function arguments") + } argsize := uintptr(0) for _, t := range ft.in() { if t.size > uintptrSize { @@ -106,6 +109,37 @@ func compileCallback(fn eface, cleanstack bool) (code uintptr) { return r } +type callbackArgs struct { + index uintptr + args *uintptr // Arguments in stdcall/cdecl convention, with registers spilled + // Below are out-args from callbackWrap + result uintptr + retPop uintptr // For 386 cdecl, how many bytes to pop on return +} + +// callbackWrap is called by callbackasm to invoke a registered C callback. +func callbackWrap(a *callbackArgs) { + c := cbs.ctxt[a.index] + a.retPop = c.restorestack + + // Convert from stdcall to Go ABI. We assume the stack layout + // is the same, and we just need to make room for the result. + // + // TODO: This isn't a good assumption. For example, a function + // that takes two uint16 arguments will be laid out + // differently by the stdcall and Go ABIs. We should implement + // proper ABI conversion. + var frame [callbackMaxArgs + 1]uintptr + memmove(unsafe.Pointer(&frame), unsafe.Pointer(a.args), c.argsize) + + // Even though this is copying back results, we can pass a nil + // type because those results must not require write barriers. + reflectcall(nil, c.gobody, noescape(unsafe.Pointer(&frame)), sys.PtrSize+uint32(c.argsize), uint32(c.argsize)) + + // Extract the result. + a.result = frame[c.argsize/sys.PtrSize] +} + const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800 // When available, this function will use LoadLibraryEx with the filename |