From e23c16363917ffd7249f4b9d084da9e5d6f91740 Mon Sep 17 00:00:00 2001 From: jwijenbergh Date: Fri, 22 Sep 2023 14:24:29 +0200 Subject: [PATCH] All: Support float arguments with CGO_ENABLED=1 on Linux --- callback_float_test.go | 90 ------------------------------------------ callback_test.go | 81 ++++++++++++++++++++++++++++++++++++- dlfcn_nocgo_linux.go | 2 +- dlfcn_stubs.s | 2 +- func.go | 3 -- sys_amd64.s | 71 ++++++++++++++++++++++++++++++++- sys_arm64.s | 2 +- sys_unix_amd64.s | 78 ------------------------------------ syscall_cgo_linux.go | 13 +++++- syscall_other.go | 22 ----------- syscall_sysv.go | 13 ++++++ 11 files changed, 178 insertions(+), 199 deletions(-) delete mode 100644 callback_float_test.go delete mode 100644 sys_unix_amd64.s delete mode 100644 syscall_other.go diff --git a/callback_float_test.go b/callback_float_test.go deleted file mode 100644 index f6ccae68..00000000 --- a/callback_float_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 The Ebitengine Authors - -//go:build darwin || (!cgo && linux) - -package purego_test -import ( - "testing" - - "github.com/ebitengine/purego" -) - -func TestNewCallbackFloat64(t *testing.T) { - // This tests the maximum number of arguments a function to NewCallback can take - const ( - expectCbTotal = -3 - expectedCbTotalF = float64(36) - ) - var cbTotal int - var cbTotalF float64 - imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int, - f1, f2, f3, f4, f5, f6, f7, f8 float64) { - cbTotal = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 - cbTotalF = f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 - }) - var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int, - f1, f2, f3, f4, f5, f6, f7, f8 float64) - purego.RegisterFunc(&fn, imp) - fn(1, 2, -3, 4, -5, 6, -7, 8, -9, - 1, 2, 3, 4, 5, 6, 7, 8) - - if cbTotal != expectCbTotal { - t.Errorf("cbTotal not correct got %d but wanted %d", cbTotal, expectCbTotal) - } - if cbTotalF != expectedCbTotalF { - t.Errorf("cbTotalF not correct got %f but wanted %f", cbTotalF, expectedCbTotalF) - } -} - -func TestNewCallbackFloat32(t *testing.T) { - // This tests the maximum number of float32 arguments a function to NewCallback can take - const ( - expectCbTotal = 6 - expectedCbTotalF = float32(45) - ) - var cbTotal int - var cbTotalF float32 - imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8 int, - f1, f2, f3, f4, f5, f6, f7, f8, f9 float32) { - cbTotal = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 - cbTotalF = f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9 - }) - var fn func(a1, a2, a3, a4, a5, a6, a7, a8 int, - f1, f2, f3, f4, f5, f6, f7, f8, f9 float32) - purego.RegisterFunc(&fn, imp) - fn(1, 2, -3, 4, -5, 6, -7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, 9) - - if cbTotal != expectCbTotal { - t.Errorf("cbTotal not correct got %d but wanted %d", cbTotal, expectCbTotal) - } - if cbTotalF != expectedCbTotalF { - t.Errorf("cbTotalF not correct got %f but wanted %f", cbTotalF, expectedCbTotalF) - } -} - -func TestNewCallbackFloat32AndFloat64(t *testing.T) { - // This tests that calling a function with a mix of float32 and float64 arguments works - const ( - expectedCbTotalF32 = float32(30) - expectedCbTotalF64 = float64(15) - ) - var cbTotalF32 float32 - var cbTotalF64 float64 - imp := purego.NewCallback(func(f1, f2, f3 float32, f4, f5, f6 float64, f7, f8, f9 float32) { - cbTotalF32 = f1 + f2 + f3 + f7 + f8 + f9 - cbTotalF64 = f4 + f5 + f6 - - }) - var fn func(f1, f2, f3 float32, f4, f5, f6 float64, f7, f8, f9 float32) - purego.RegisterFunc(&fn, imp) - fn(1, 2, 3, 4, 5, 6, 7, 8, 9) - - if cbTotalF32 != expectedCbTotalF32 { - t.Errorf("cbTotalF32 not correct got %f but wanted %f", cbTotalF32, expectedCbTotalF32) - } - if cbTotalF64 != expectedCbTotalF64 { - t.Errorf("cbTotalF64 not correct got %f but wanted %f", cbTotalF64, expectedCbTotalF64) - } -} diff --git a/callback_test.go b/callback_test.go index 28bcf1da..4ab54530 100644 --- a/callback_test.go +++ b/callback_test.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 The Ebitengine Authors -//go:build darwin || linux +//go:build darwin || (linux && (!cgo || arm64 || amd64)) package purego_test @@ -48,3 +48,82 @@ func TestCallGoFromSharedLib(t *testing.T) { } } } + +func TestNewCallbackFloat64(t *testing.T) { + // This tests the maximum number of arguments a function to NewCallback can take + const ( + expectCbTotal = -3 + expectedCbTotalF = float64(36) + ) + var cbTotal int + var cbTotalF float64 + imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int, + f1, f2, f3, f4, f5, f6, f7, f8 float64) { + cbTotal = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + cbTotalF = f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + }) + var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int, + f1, f2, f3, f4, f5, f6, f7, f8 float64) + purego.RegisterFunc(&fn, imp) + fn(1, 2, -3, 4, -5, 6, -7, 8, -9, + 1, 2, 3, 4, 5, 6, 7, 8) + + if cbTotal != expectCbTotal { + t.Errorf("cbTotal not correct got %d but wanted %d", cbTotal, expectCbTotal) + } + if cbTotalF != expectedCbTotalF { + t.Errorf("cbTotalF not correct got %f but wanted %f", cbTotalF, expectedCbTotalF) + } +} + +func TestNewCallbackFloat32(t *testing.T) { + // This tests the maximum number of float32 arguments a function to NewCallback can take + const ( + expectCbTotal = 6 + expectedCbTotalF = float32(45) + ) + var cbTotal int + var cbTotalF float32 + imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8 int, + f1, f2, f3, f4, f5, f6, f7, f8, f9 float32) { + cbTotal = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + cbTotalF = f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9 + }) + var fn func(a1, a2, a3, a4, a5, a6, a7, a8 int, + f1, f2, f3, f4, f5, f6, f7, f8, f9 float32) + purego.RegisterFunc(&fn, imp) + fn(1, 2, -3, 4, -5, 6, -7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, 9) + + if cbTotal != expectCbTotal { + t.Errorf("cbTotal not correct got %d but wanted %d", cbTotal, expectCbTotal) + } + if cbTotalF != expectedCbTotalF { + t.Errorf("cbTotalF not correct got %f but wanted %f", cbTotalF, expectedCbTotalF) + } +} + +func TestNewCallbackFloat32AndFloat64(t *testing.T) { + // This tests that calling a function with a mix of float32 and float64 arguments works + const ( + expectedCbTotalF32 = float32(30) + expectedCbTotalF64 = float64(15) + ) + var cbTotalF32 float32 + var cbTotalF64 float64 + imp := purego.NewCallback(func(f1, f2, f3 float32, f4, f5, f6 float64, f7, f8, f9 float32) { + cbTotalF32 = f1 + f2 + f3 + f7 + f8 + f9 + cbTotalF64 = f4 + f5 + f6 + + }) + var fn func(f1, f2, f3 float32, f4, f5, f6 float64, f7, f8, f9 float32) + purego.RegisterFunc(&fn, imp) + fn(1, 2, 3, 4, 5, 6, 7, 8, 9) + + if cbTotalF32 != expectedCbTotalF32 { + t.Errorf("cbTotalF32 not correct got %f but wanted %f", cbTotalF32, expectedCbTotalF32) + } + if cbTotalF64 != expectedCbTotalF64 { + t.Errorf("cbTotalF64 not correct got %f but wanted %f", cbTotalF64, expectedCbTotalF64) + } +} diff --git a/dlfcn_nocgo_linux.go b/dlfcn_nocgo_linux.go index 2c8009fd..be366c14 100644 --- a/dlfcn_nocgo_linux.go +++ b/dlfcn_nocgo_linux.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors -//go:build !cgo +//go:build !cgo || arm64 || amd64 package purego diff --git a/dlfcn_stubs.s b/dlfcn_stubs.s index d8c6eea3..a4b71f69 100644 --- a/dlfcn_stubs.s +++ b/dlfcn_stubs.s @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors -//go:build darwin || freebsd || (linux && !cgo) +//go:build darwin || freebsd || (linux && (!cgo || arm64 || amd64)) #include "textflag.h" diff --git a/func.go b/func.go index 00eef6a2..5ce3b0fd 100644 --- a/func.go +++ b/func.go @@ -62,9 +62,6 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) { // This means that using arg ...interface{} is like a cast to the function with the arguments inside arg. // This is not the same as C variadic. // -// There is one limitation when using RegisterFunc on Linux: There is no support for float32 and float64 arguments/return values with CGO_ENABLED=1. -// Linux otherwise has the same feature parity as Darwin. -// // # Memory // // In general it is not possible for purego to guarantee the lifetimes of objects returned or received from diff --git a/sys_amd64.s b/sys_amd64.s index 0afa3865..9b13a800 100644 --- a/sys_amd64.s +++ b/sys_amd64.s @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors -//go:build darwin || freebsd || (!cgo && linux) +//go:build darwin || freebsd || linux #include "textflag.h" #include "abi_amd64.h" @@ -72,3 +72,72 @@ TEXT syscall9X(SB), NOSPLIT|NOFRAME, $0 MOVQ BP, SP POPQ BP RET + +TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 + // remove return address from stack, we are not returning to callbackasm, but to its caller. + MOVQ 0(SP), AX + ADDQ $8, SP + + MOVQ 0(SP), R10 // get the return SP so that we can align register args with stack args + + // make space for first six int and 8 float arguments below the frame + ADJSP $14*8, SP + MOVSD X0, (1*8)(SP) + MOVSD X1, (2*8)(SP) + MOVSD X2, (3*8)(SP) + MOVSD X3, (4*8)(SP) + MOVSD X4, (5*8)(SP) + MOVSD X5, (6*8)(SP) + MOVSD X6, (7*8)(SP) + MOVSD X7, (8*8)(SP) + MOVQ DI, (9*8)(SP) + MOVQ SI, (10*8)(SP) + MOVQ DX, (11*8)(SP) + MOVQ CX, (12*8)(SP) + MOVQ R8, (13*8)(SP) + MOVQ R9, (14*8)(SP) + LEAQ 8(SP), R8 // R8 = address of args vector + + MOVQ R10, 0(SP) // push the stack pointer below registers + + // determine index into runtime·cbs table + MOVQ $callbackasm(SB), DX + SUBQ DX, AX + MOVQ $0, DX + MOVQ $5, CX // divide by 5 because each call instruction in ·callbacks is 5 bytes long + DIVL CX + SUBQ $1, AX // subtract 1 because return PC is to the next slot + + // Switch from the host ABI to the Go ABI. + PUSH_REGS_HOST_TO_ABI0() + + // Create a struct callbackArgs on our stack to be passed as + // the "frame" to cgocallback and on to callbackWrap. + // $24 to make enough room for the arguments to runtime.cgocallback + SUBQ $(24+callbackArgs__size), SP + MOVQ AX, (24+callbackArgs_index)(SP) // callback index + MOVQ R8, (24+callbackArgs_args)(SP) // address of args vector + MOVQ $0, (24+callbackArgs_result)(SP) // result + LEAQ 24(SP), AX // take the address of callbackArgs + + // Call cgocallback, which will call callbackWrap(frame). + MOVQ ·callbackWrap_call(SB), DI // Get the ABIInternal function pointer + MOVQ (DI), DI // without by using a closure. + MOVQ AX, SI // frame (address of callbackArgs) + MOVQ $0, CX // context + + CALL crosscall2(SB) // runtime.cgocallback(fn, frame, ctxt uintptr) + + // Get callback result. + MOVQ (24+callbackArgs_result)(SP), AX + ADDQ $(24+callbackArgs__size), SP // remove callbackArgs struct + + POP_REGS_HOST_TO_ABI0() + + MOVQ 0(SP), R10 // get the SP back + + ADJSP $-14*8, SP // remove arguments + + MOVQ R10, 0(SP) + + RET diff --git a/sys_arm64.s b/sys_arm64.s index 914ebb4e..82d1fe00 100644 --- a/sys_arm64.s +++ b/sys_arm64.s @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors -//go:build darwin || freebsd || (!cgo && linux) || windows +//go:build darwin || freebsd || linux || windows #include "textflag.h" #include "go_asm.h" diff --git a/sys_unix_amd64.s b/sys_unix_amd64.s deleted file mode 100644 index 25c40550..00000000 --- a/sys_unix_amd64.s +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 The Ebitengine Authors - -//go:build darwin || freebsd || linux - -#include "textflag.h" -#include "abi_amd64.h" -#include "go_asm.h" -#include "funcdata.h" - -TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 - // remove return address from stack, we are not returning to callbackasm, but to its caller. - MOVQ 0(SP), AX - ADDQ $8, SP - - MOVQ 0(SP), R10 // get the return SP so that we can align register args with stack args - - // make space for first six int and 8 float arguments below the frame - ADJSP $14*8, SP - MOVSD X0, (1*8)(SP) - MOVSD X1, (2*8)(SP) - MOVSD X2, (3*8)(SP) - MOVSD X3, (4*8)(SP) - MOVSD X4, (5*8)(SP) - MOVSD X5, (6*8)(SP) - MOVSD X6, (7*8)(SP) - MOVSD X7, (8*8)(SP) - MOVQ DI, (9*8)(SP) - MOVQ SI, (10*8)(SP) - MOVQ DX, (11*8)(SP) - MOVQ CX, (12*8)(SP) - MOVQ R8, (13*8)(SP) - MOVQ R9, (14*8)(SP) - LEAQ 8(SP), R8 // R8 = address of args vector - - MOVQ R10, 0(SP) // push the stack pointer below registers - - // determine index into runtime·cbs table - MOVQ $callbackasm(SB), DX - SUBQ DX, AX - MOVQ $0, DX - MOVQ $5, CX // divide by 5 because each call instruction in ·callbacks is 5 bytes long - DIVL CX - SUBQ $1, AX // subtract 1 because return PC is to the next slot - - // Switch from the host ABI to the Go ABI. - PUSH_REGS_HOST_TO_ABI0() - - // Create a struct callbackArgs on our stack to be passed as - // the "frame" to cgocallback and on to callbackWrap. - // $24 to make enough room for the arguments to runtime.cgocallback - SUBQ $(24+callbackArgs__size), SP - MOVQ AX, (24+callbackArgs_index)(SP) // callback index - MOVQ R8, (24+callbackArgs_args)(SP) // address of args vector - MOVQ $0, (24+callbackArgs_result)(SP) // result - LEAQ 24(SP), AX // take the address of callbackArgs - - // Call cgocallback, which will call callbackWrap(frame). - MOVQ ·callbackWrap_call(SB), DI // Get the ABIInternal function pointer - MOVQ (DI), DI // without by using a closure. - MOVQ AX, SI // frame (address of callbackArgs) - MOVQ $0, CX // context - - CALL crosscall2(SB) // runtime.cgocallback(fn, frame, ctxt uintptr) - - // Get callback result. - MOVQ (24+callbackArgs_result)(SP), AX - ADDQ $(24+callbackArgs__size), SP // remove callbackArgs struct - - POP_REGS_HOST_TO_ABI0() - - MOVQ 0(SP), R10 // get the SP back - - ADJSP $-14*8, SP // remove arguments - - MOVQ R10, 0(SP) - - RET diff --git a/syscall_cgo_linux.go b/syscall_cgo_linux.go index 4eea15ad..ff117db9 100644 --- a/syscall_cgo_linux.go +++ b/syscall_cgo_linux.go @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors -//go:build cgo +//go:build cgo && !(amd64 || arm64) package purego @@ -13,7 +13,18 @@ import ( var syscall9XABI0 = uintptr(cgo.Syscall9XABI0) +// this is only here to make the assembly files happy :) +type syscall9Args struct { + fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr + f1, f2, f3, f4, f5, f6, f7, f8 uintptr + r1, r2, err uintptr +} + //go:nosplit func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) { return cgo.Syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9) } + +func NewCallback(_ interface{}) uintptr { + panic("purego: NewCallback on Linux is only supported on arm64/amd64") +} diff --git a/syscall_other.go b/syscall_other.go deleted file mode 100644 index 7084fd82..00000000 --- a/syscall_other.go +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 The Ebitengine Authors - -//go:build darwin || freebsd || (!cgo && linux && (amd64 || arm64)) - -package purego - -import "unsafe" - -var syscall9XABI0 uintptr - -//go:nosplit -func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) { - args := syscall9Args{ - fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, - a1, a2, a3, a4, a5, a6, a7, a8, - r1, r2, err, - } - runtime_cgocall(syscall9XABI0, unsafe.Pointer(&args)) - return args.r1, args.r2, args.err -} - diff --git a/syscall_sysv.go b/syscall_sysv.go index 33fe0c41..c21e0dca 100644 --- a/syscall_sysv.go +++ b/syscall_sysv.go @@ -12,12 +12,25 @@ import ( "unsafe" ) +var syscall9XABI0 uintptr + type syscall9Args struct { fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr f1, f2, f3, f4, f5, f6, f7, f8 uintptr r1, r2, err uintptr } +//go:nosplit +func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) { + args := syscall9Args{ + fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, + a1, a2, a3, a4, a5, a6, a7, a8, + r1, r2, err, + } + runtime_cgocall(syscall9XABI0, unsafe.Pointer(&args)) + return args.r1, args.r2, args.err +} + // NewCallback converts a Go function to a function pointer conforming to the C calling convention. // This is useful when interoperating with C code requiring callbacks. The argument is expected to be a // function with zero or one uintptr-sized result. The function must not have arguments with size larger than the size