refactor: update go mod library (#210)

refactor: update go mod library and refactor dev logic

Co-authored-by: wencaiwulue <895703375@qq.com>
This commit is contained in:
naison
2024-04-04 12:04:02 +08:00
committed by GitHub
parent 91b3a2fbdf
commit 87166494c0
1336 changed files with 51428 additions and 57404 deletions

View File

@@ -3,6 +3,10 @@
A library for calling C functions from Go without Cgo.
> This is beta software so expect bugs and potentially API breaking changes
> but each release will be tagged to avoid breaking people's code.
> Bug reports are encouraged.
## Motivation
The [Ebitengine](https://github.com/hajimehoshi/ebiten) game engine was ported to use only Go on Windows. This enabled
@@ -16,6 +20,18 @@ born to bring that same vision to the other platforms supported by Ebitengine.
- **Smaller Binaries**: Using Cgo generates a C wrapper function for each C function called. Purego doesn't!
- **Dynamic Linking**: Load symbols at runtime and use it as a plugin system.
- **Foreign Function Interface**: Call into other languages that are compiled into shared objects.
- **Cgo Fallback**: Works even with CGO_ENABLED=1 so incremental porting is possible.
This also means unsupported GOARCHs (freebsd/riscv64, linux/mips, etc.) will still work
except for float arguments and return values.
## Supported Platforms
- **FreeBSD**: amd64, arm64
- **Linux**: amd64, arm64
- **macOS / iOS**: amd64, arm64
- **Windows**: 386*, amd64, arm*, arm64
`*` These architectures only support SyscallN and NewCallback
## Example
@@ -55,6 +71,11 @@ func main() {
Then to run: `CGO_ENABLED=0 go run main.go`
## Questions
If you have questions about how to incorporate purego in your project or want to discuss
how it works join the [Discord](https://discord.com/channels/842049801528016967/1123106378731487345)!
### External Code
Purego uses code that originates from the Go runtime. These files are under the BSD-3
@@ -71,4 +92,5 @@ This is a list of the copied files:
* `internal/fakecgo/setenv.go` from package `runtime/cgo`
* `internal/fakecgo/freebsd.go` from package `runtime/cgo`
The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of `#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636))
The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of
`#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636))

View File

@@ -12,4 +12,8 @@ package purego
// which will import this package automatically. Normally this isn't an issue since it
// usually isn't possible to call into C without using that import. However, with purego
// it is since we don't use `import "C"`!
import _ "runtime/cgo"
import (
_ "runtime/cgo"
_ "github.com/ebitengine/purego/internal/cgo"
)

View File

@@ -17,3 +17,8 @@ const (
//go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlopen dlopen "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib"

View File

@@ -11,8 +11,3 @@ const (
RTLD_LOCAL = 0x00000 // All symbols are not made available for relocation processing by other modules.
RTLD_GLOBAL = 0x00100 // All symbols are available for relocation processing of other modules.
)
//go:cgo_import_dynamic purego_dlopen dlopen "libc.so.7"
//go:cgo_import_dynamic purego_dlsym dlsym "libc.so.7"
//go:cgo_import_dynamic purego_dlerror dlerror "libc.so.7"
//go:cgo_import_dynamic purego_dlclose dlclose "libc.so.7"

View File

@@ -0,0 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package purego
//go:cgo_import_dynamic purego_dlopen dlopen "libc.so.7"
//go:cgo_import_dynamic purego_dlsym dlsym "libc.so.7"
//go:cgo_import_dynamic purego_dlerror dlerror "libc.so.7"
//go:cgo_import_dynamic purego_dlclose dlclose "libc.so.7"

View File

@@ -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 || !cgo && (freebsd || linux)
#include "textflag.h"

View File

@@ -6,6 +6,7 @@
package purego
import (
"fmt"
"math"
"reflect"
"runtime"
@@ -50,9 +51,9 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
// int16 <=> int16_t
// int32 <=> int32_t
// int64 <=> int64_t
// float32 <=> float (WIP)
// float64 <=> double (WIP)
// struct <=> struct (WIP)
// float32 <=> float
// float64 <=> double
// struct <=> struct (WIP - darwin only)
// func <=> C function
// unsafe.Pointer, *T <=> void*
// []T => void*
@@ -62,10 +63,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 are some limitations when using RegisterFunc on Linux. First, there is no support for function arguments.
// Second, float32 and float64 arguments and return values do not work when CGO_ENABLED=1. Otherwise, Linux
// 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
@@ -85,6 +82,12 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
// using unsafe.Slice. Doing this means that it becomes the responsibility of the caller to care about the lifetime
// of the pointer
//
// # Structs
//
// Purego can handle the most common structs that have fields of builtin types like int8, uint16, float32, etc. However,
// it does not support aligning fields properly. It is therefore the responsibility of the caller to ensure
// that all padding is added to the Go struct to match the C one. See `BoolStructFn` in struct_test.go for an example.
//
// # Example
//
// All functions below call this C function:
@@ -132,15 +135,47 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
stack++
}
case reflect.Float32, reflect.Float64:
if is32bit {
panic("purego: floats only supported on 64bit platforms")
}
if floats < numOfFloats {
floats++
} else {
stack++
}
case reflect.Struct:
if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
panic("purego: struct arguments are only supported on darwin amd64 & arm64")
}
if arg.Size() == 0 {
continue
}
addInt := func(u uintptr) {
ints++
}
addFloat := func(u uintptr) {
floats++
}
addStack := func(u uintptr) {
stack++
}
_ = addStruct(reflect.New(arg).Elem(), &ints, &floats, &stack, addInt, addFloat, addStack, nil)
default:
panic("purego: unsupported kind " + arg.Kind().String())
}
}
if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
if runtime.GOOS != "darwin" {
panic("purego: struct return values only supported on darwin arm64 & amd64")
}
outType := ty.Out(0)
checkStructFieldsSupported(outType)
if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
// on amd64 if struct is bigger than 16 bytes allocate the return struct
// and pass it in as a hidden first argument.
ints++
}
}
sizeOfStack := maxArgs - numOfIntegerRegisters()
if stack > sizeOfStack {
panic("purego: too many arguments")
@@ -207,6 +242,21 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
runtime.KeepAlive(keepAlive)
runtime.KeepAlive(args)
}()
var syscall syscall15Args
if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
outType := ty.Out(0)
if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
val := reflect.New(outType)
keepAlive = append(keepAlive, val)
addInt(val.Pointer())
} else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
if !isAllSameFloat(outType) || outType.NumField() > 4 {
val := reflect.New(outType)
keepAlive = append(keepAlive, val)
syscall.arm64_r8 = val.Pointer()
}
}
}
for _, v := range args {
switch v.Kind() {
case reflect.String:
@@ -232,25 +282,29 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
addFloat(uintptr(math.Float32bits(float32(v.Float()))))
case reflect.Float64:
addFloat(uintptr(math.Float64bits(v.Float())))
case reflect.Struct:
keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
default:
panic("purego: unsupported kind: " + v.Kind().String())
}
}
// TODO: support structs
var r1, r2 uintptr
if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
// Use the normal arm64 calling convention even on Windows
syscall := syscall9Args{
syscall = syscall15Args{
cfn,
sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8],
sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
sysargs[12], sysargs[13], sysargs[14],
floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
0, 0, 0,
syscall.arm64_r8,
}
runtime_cgocall(syscall9XABI0, unsafe.Pointer(&syscall))
r1, r2 = syscall.r1, syscall.r2
runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))
} else {
// This is a fallback for amd64, 386, and arm. Note this may not support floats
r1, r2, _ = syscall_syscall9X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[6], sysargs[7], sysargs[8])
// This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
sysargs[12], sysargs[13], sysargs[14])
syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
}
if ty.NumOut() == 0 {
return nil
@@ -259,31 +313,33 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
v := reflect.New(outType).Elem()
switch outType.Kind() {
case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
v.SetUint(uint64(r1))
v.SetUint(uint64(syscall.a1))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
v.SetInt(int64(r1))
v.SetInt(int64(syscall.a1))
case reflect.Bool:
v.SetBool(byte(r1) != 0)
v.SetBool(byte(syscall.a1) != 0)
case reflect.UnsafePointer:
// We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&r1)))
v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
case reflect.Ptr:
// It is safe to have the address of r1 not escape because it is immediately dereferenced with .Elem()
v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&r1))).Elem()
// It is safe to have the address of syscall.r1 not escape because it is immediately dereferenced with .Elem()
v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&syscall.a1))).Elem()
case reflect.Func:
// wrap this C function in a nicely typed Go function
v = reflect.New(outType)
RegisterFunc(v.Interface(), r1)
RegisterFunc(v.Interface(), syscall.a1)
case reflect.String:
v.SetString(strings.GoString(r1))
v.SetString(strings.GoString(syscall.a1))
case reflect.Float32:
// NOTE: r2 is only the floating return value on 64bit platforms.
// On 32bit platforms r2 is the upper part of a 64bit return.
v.SetFloat(float64(math.Float32frombits(uint32(r2))))
// NOTE: syscall.r2 is only the floating return value on 64bit platforms.
// On 32bit platforms syscall.r2 is the upper part of a 64bit return.
v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
case reflect.Float64:
// NOTE: r2 is only the floating return value on 64bit platforms.
// On 32bit platforms r2 is the upper part of a 64bit return.
v.SetFloat(math.Float64frombits(uint64(r2)))
// NOTE: syscall.r2 is only the floating return value on 64bit platforms.
// On 32bit platforms syscall.r2 is the upper part of a 64bit return.
v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
case reflect.Struct:
v = getStruct(outType, syscall)
default:
panic("purego: unsupported return kind: " + outType.Kind().String())
}
@@ -292,6 +348,50 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
fn.Set(v)
}
// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
// if it is bigger than this than enough space must be allocated on the heap and then passed into
// the function as the first parameter on amd64 or in R8 on arm64.
const maxRegAllocStructSize = 16
func isAllSameFloat(ty reflect.Type) bool {
first := ty.Field(0).Type.Kind()
if first != reflect.Float32 && first != reflect.Float64 {
return false
}
for i := 0; i < ty.NumField(); i++ {
f := ty.Field(i)
if f.Type.Kind() != first {
return false
}
}
return true
}
func checkStructFieldsSupported(ty reflect.Type) {
for i := 0; i < ty.NumField(); i++ {
f := ty.Field(i).Type
if f.Kind() == reflect.Array {
f = f.Elem()
} else if f.Kind() == reflect.Struct {
checkStructFieldsSupported(f)
continue
}
switch f.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32:
default:
panic(fmt.Sprintf("purego: struct field type %s is not supported", f))
}
}
}
const is32bit = unsafe.Sizeof(uintptr(0)) == 4
func roundUpTo8(val uintptr) uintptr {
return (val + 7) &^ 7
}
func numOfIntegerRegisters() int {
switch runtime.GOARCH {
case "arm64":

View File

@@ -0,0 +1,22 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
//go:build freebsd || linux
package cgo
/*
#cgo LDFLAGS: -ldl
#include <dlfcn.h>
*/
import "C"
// all that is needed is to assign each dl function because then its
// symbol will then be made available to the linker and linked to inside dlfcn.go
var (
_ = C.dlopen
_ = C.dlsym
_ = C.dlerror
_ = C.dlclose
)

View File

@@ -0,0 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
package cgo
// Empty so that importing this package doesn't cause issue for certain platforms.

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build freebsd || linux
//go:build freebsd || (linux && !(arm64 || amd64))
package cgo
@@ -16,18 +16,21 @@ package cgo
#include <errno.h>
#include <assert.h>
typedef struct syscall9Args {
typedef struct syscall15Args {
uintptr_t fn;
uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9;
uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15;
uintptr_t f1, f2, f3, f4, f5, f6, f7, f8;
uintptr_t r1, r2, err;
} syscall9Args;
} syscall15Args;
void syscall9(struct syscall9Args *args) {
void syscall15(struct syscall15Args *args) {
assert((args->f1|args->f2|args->f3|args->f4|args->f5|args->f6|args->f7|args->f8) == 0);
uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, uintptr_t a7, uintptr_t a8, uintptr_t a9);
uintptr_t (*func_name)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6,
uintptr_t a7, uintptr_t a8, uintptr_t a9, uintptr_t a10, uintptr_t a11, uintptr_t a12,
uintptr_t a13, uintptr_t a14, uintptr_t a15);
*(void**)(&func_name) = (void*)(args->fn);
uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9);
uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9,
args->a10,args->a11,args->a12,args->a13,args->a14,args->a15);
args->r1 = r1;
args->err = errno;
}
@@ -36,23 +39,17 @@ void syscall9(struct syscall9Args *args) {
import "C"
import "unsafe"
// assign purego.syscall9XABI0 to the C version of this function.
var Syscall9XABI0 = unsafe.Pointer(C.syscall9)
// all that is needed is to assign each dl function because then its
// symbol will then be made available to the linker and linked to inside dlfcn.go
var (
_ = C.dlopen
_ = C.dlsym
_ = C.dlerror
_ = C.dlclose
)
// assign purego.syscall15XABI0 to the C version of this function.
var Syscall15XABI0 = unsafe.Pointer(C.syscall15)
//go:nosplit
func Syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {
args := C.syscall9Args{C.uintptr_t(fn), C.uintptr_t(a1), C.uintptr_t(a2), C.uintptr_t(a3),
func Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
args := C.syscall15Args{
C.uintptr_t(fn), C.uintptr_t(a1), C.uintptr_t(a2), C.uintptr_t(a3),
C.uintptr_t(a4), C.uintptr_t(a5), C.uintptr_t(a6),
C.uintptr_t(a7), C.uintptr_t(a8), C.uintptr_t(a9), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
C.syscall9(&args)
C.uintptr_t(a7), C.uintptr_t(a8), C.uintptr_t(a9), C.uintptr_t(a10), C.uintptr_t(a11), C.uintptr_t(a12),
C.uintptr_t(a13), C.uintptr_t(a14), C.uintptr_t(a15), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}
C.syscall15(&args)
return uintptr(args.r1), uintptr(args.r2), uintptr(args.err)
}

View File

@@ -10,7 +10,7 @@
// Saves C callee-saved registers and calls cgocallback with three arguments.
// fn is the PC of a func(a unsafe.Pointer) function.
// This signature is known to SWIG, so we can't change it.
TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0-0
TEXT crosscall2(SB), NOSPLIT, $0-0
PUSH_REGS_HOST_TO_ABI0()
// Make room for arguments to cgocallback.

272
vendor/github.com/ebitengine/purego/struct_amd64.go generated vendored Normal file
View File

@@ -0,0 +1,272 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
package purego
import (
"math"
"reflect"
"unsafe"
)
func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
outSize := outType.Size()
switch {
case outSize == 0:
return reflect.New(outType).Elem()
case outSize <= 8:
if isAllFloats(outType) {
// 2 float32s or 1 float64s are return in the float register
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem()
}
// up to 8 bytes is returned in RAX
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
case outSize <= 16:
r1, r2 := syscall.a1, syscall.a2
if isAllFloats(outType) {
r1 = syscall.f1
r2 = syscall.f2
} else {
// check first 8 bytes if it's floats
hasFirstFloat := false
f1 := outType.Field(0).Type
if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 {
r1 = syscall.f1
hasFirstFloat = true
}
// find index of the field that starts the second 8 bytes
var i int
for i = 0; i < outType.NumField(); i++ {
if outType.Field(i).Offset == 8 {
break
}
}
// check last 8 bytes if they are floats
f1 = outType.Field(i).Type
if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() {
r2 = syscall.f1
} else if hasFirstFloat {
// if the first field was a float then that means the second integer field
// comes from the first integer register
r2 = syscall.a1
}
}
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
default:
// create struct from the Go pointer created above
// weird pointer dereference to circumvent go vet
return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
}
}
func isAllFloats(ty reflect.Type) bool {
for i := 0; i < ty.NumField(); i++ {
f := ty.Field(i)
switch f.Type.Kind() {
case reflect.Float64, reflect.Float32:
default:
return false
}
}
return true
}
// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
// https://gitlab.com/x86-psABIs/x86-64-ABI
// Class determines where the 8 byte value goes.
// Higher value classes win over lower value classes
const (
_NO_CLASS = 0b0000
_SSE = 0b0001
_X87 = 0b0011 // long double not used in Go
_INTEGER = 0b0111
_MEMORY = 0b1111
)
func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
if v.Type().Size() == 0 {
return keepAlive
}
// if greater than 64 bytes place on stack
if v.Type().Size() > 8*8 {
placeStack(v, addStack)
return keepAlive
}
var (
savedNumFloats = *numFloats
savedNumInts = *numInts
savedNumStack = *numStack
)
placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt)
if placeOnStack {
// reset any values placed in registers
*numFloats = savedNumFloats
*numInts = savedNumInts
*numStack = savedNumStack
placeStack(v, addStack)
}
return keepAlive
}
func postMerger(t reflect.Type) bool {
// (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isnt SSE or any other
// eightbyte isnt SSEUP, the whole argument is passed in memory.
if t.Kind() != reflect.Struct {
return false
}
if t.Size() <= 2*8 {
return false
}
first := getFirst(t).Kind()
if first != reflect.Float32 && first != reflect.Float64 {
return false
}
return true
}
func getFirst(t reflect.Type) reflect.Type {
first := t.Field(0).Type
if first.Kind() == reflect.Struct {
return getFirst(first)
}
return first
}
func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
ok = true
var val uint64
var shift byte // # of bits to shift
var flushed bool
class := _NO_CLASS
flushIfNeeded := func() {
if flushed {
return
}
flushed = true
if class == _SSE {
addFloat(uintptr(val))
} else {
addInt(uintptr(val))
}
val = 0
shift = 0
class = _NO_CLASS
}
var place func(v reflect.Value)
place = func(v reflect.Value) {
var numFields int
if v.Kind() == reflect.Struct {
numFields = v.Type().NumField()
} else {
numFields = v.Type().Len()
}
for i := 0; i < numFields; i++ {
flushed = false
var f reflect.Value
if v.Kind() == reflect.Struct {
f = v.Field(i)
} else {
f = v.Index(i)
}
switch f.Kind() {
case reflect.Struct:
place(f)
case reflect.Bool:
if f.Bool() {
val |= 1
}
shift += 8
class |= _INTEGER
case reflect.Pointer:
ok = false
return
case reflect.Int8:
val |= uint64(f.Int()&0xFF) << shift
shift += 8
class |= _INTEGER
case reflect.Int16:
val |= uint64(f.Int()&0xFFFF) << shift
shift += 16
class |= _INTEGER
case reflect.Int32:
val |= uint64(f.Int()&0xFFFF_FFFF) << shift
shift += 32
class |= _INTEGER
case reflect.Int64:
val = uint64(f.Int())
shift = 64
class = _INTEGER
case reflect.Uint8:
val |= f.Uint() << shift
shift += 8
class |= _INTEGER
case reflect.Uint16:
val |= f.Uint() << shift
shift += 16
class |= _INTEGER
case reflect.Uint32:
val |= f.Uint() << shift
shift += 32
class |= _INTEGER
case reflect.Uint64:
val = f.Uint()
shift = 64
class = _INTEGER
case reflect.Float32:
val |= uint64(math.Float32bits(float32(f.Float()))) << shift
shift += 32
class |= _SSE
case reflect.Float64:
if v.Type().Size() > 16 {
ok = false
return
}
val = uint64(math.Float64bits(f.Float()))
shift = 64
class = _SSE
case reflect.Array:
place(f)
default:
panic("purego: unsupported kind " + f.Kind().String())
}
if shift == 64 {
flushIfNeeded()
} else if shift > 64 {
// Should never happen, but may if we forget to reset shift after flush (or forget to flush),
// better fall apart here, than corrupt arguments.
panic("purego: tryPlaceRegisters shift > 64")
}
}
}
place(v)
flushIfNeeded()
return ok
}
func placeStack(v reflect.Value, addStack func(uintptr)) {
for i := 0; i < v.Type().NumField(); i++ {
f := v.Field(i)
switch f.Kind() {
case reflect.Pointer:
addStack(f.Pointer())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
addStack(uintptr(f.Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
addStack(uintptr(f.Uint()))
case reflect.Float32:
addStack(uintptr(math.Float32bits(float32(f.Float()))))
case reflect.Float64:
addStack(uintptr(math.Float64bits(f.Float())))
case reflect.Struct:
placeStack(f, addStack)
default:
panic("purego: unsupported kind " + f.Kind().String())
}
}
}

274
vendor/github.com/ebitengine/purego/struct_arm64.go generated vendored Normal file
View File

@@ -0,0 +1,274 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
package purego
import (
"math"
"reflect"
"unsafe"
)
func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
outSize := outType.Size()
switch {
case outSize == 0:
return reflect.New(outType).Elem()
case outSize <= 8:
r1 := syscall.a1
if isAllSameFloat(outType) {
r1 = syscall.f1
if outType.NumField() == 2 {
r1 = syscall.f2<<32 | syscall.f1
}
}
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
case outSize <= 16:
r1, r2 := syscall.a1, syscall.a2
if isAllSameFloat(outType) {
switch outType.NumField() {
case 4:
r1 = syscall.f2<<32 | syscall.f1
r2 = syscall.f4<<32 | syscall.f3
case 3:
r1 = syscall.f2<<32 | syscall.f1
r2 = syscall.f3
case 2:
r1 = syscall.f1
r2 = syscall.f2
default:
panic("unreachable")
}
}
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
default:
if isAllSameFloat(outType) && outType.NumField() <= 4 {
switch outType.NumField() {
case 4:
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
case 3:
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem()
default:
panic("unreachable")
}
}
// create struct from the Go pointer created in arm64_r8
// weird pointer dereference to circumvent go vet
return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.arm64_r8))).Elem()
}
}
// https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
const (
_NO_CLASS = 0b00
_FLOAT = 0b01
_INT = 0b11
)
func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
if v.Type().Size() == 0 {
return keepAlive
}
if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
// if this doesn't fit entirely in registers then
// each element goes onto the stack
if hfa && *numFloats+v.NumField() > numOfFloats {
*numFloats = numOfFloats
} else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
*numInts = numOfIntegerRegisters()
}
placeRegisters(v, addFloat, addInt)
} else {
keepAlive = placeStack(v, keepAlive, addInt)
}
return keepAlive // the struct was allocated so don't panic
}
func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
var val uint64
var shift byte
var flushed bool
class := _NO_CLASS
var place func(v reflect.Value)
place = func(v reflect.Value) {
var numFields int
if v.Kind() == reflect.Struct {
numFields = v.Type().NumField()
} else {
numFields = v.Type().Len()
}
for k := 0; k < numFields; k++ {
flushed = false
var f reflect.Value
if v.Kind() == reflect.Struct {
f = v.Field(k)
} else {
f = v.Index(k)
}
if shift >= 64 {
shift = 0
flushed = true
if class == _FLOAT {
addFloat(uintptr(val))
} else {
addInt(uintptr(val))
}
}
switch f.Type().Kind() {
case reflect.Struct:
place(f)
case reflect.Bool:
if f.Bool() {
val |= 1
}
shift += 8
class |= _INT
case reflect.Uint8:
val |= f.Uint() << shift
shift += 8
class |= _INT
case reflect.Uint16:
val |= f.Uint() << shift
shift += 16
class |= _INT
case reflect.Uint32:
val |= f.Uint() << shift
shift += 32
class |= _INT
case reflect.Uint64:
addInt(uintptr(f.Uint()))
shift = 0
flushed = true
case reflect.Int8:
val |= uint64(f.Int()&0xFF) << shift
shift += 8
class |= _INT
case reflect.Int16:
val |= uint64(f.Int()&0xFFFF) << shift
shift += 16
class |= _INT
case reflect.Int32:
val |= uint64(f.Int()&0xFFFF_FFFF) << shift
shift += 32
class |= _INT
case reflect.Int64:
addInt(uintptr(f.Int()))
shift = 0
flushed = true
case reflect.Float32:
if class == _FLOAT {
addFloat(uintptr(val))
val = 0
shift = 0
}
val |= uint64(math.Float32bits(float32(f.Float()))) << shift
shift += 32
class |= _FLOAT
case reflect.Float64:
addFloat(uintptr(math.Float64bits(float64(f.Float()))))
shift = 0
flushed = true
case reflect.Array:
place(f)
default:
panic("purego: unsupported kind " + f.Kind().String())
}
}
}
place(v)
if !flushed {
if class == _FLOAT {
addFloat(uintptr(val))
} else {
addInt(uintptr(val))
}
}
}
func placeStack(v reflect.Value, keepAlive []interface{}, addInt func(uintptr)) []interface{} {
// Struct is too big to be placed in registers.
// Copy to heap and place the pointer in register
ptrStruct := reflect.New(v.Type())
ptrStruct.Elem().Set(v)
ptr := ptrStruct.Elem().Addr().UnsafePointer()
keepAlive = append(keepAlive, ptr)
addInt(uintptr(ptr))
return keepAlive
}
// isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a
// Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]).
// This type of struct will be placed more compactly than the individual fields.
//
// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
func isHFA(t reflect.Type) bool {
// round up struct size to nearest 8 see section B.4
structSize := roundUpTo8(t.Size())
if structSize == 0 || t.NumField() > 4 {
return false
}
first := t.Field(0)
switch first.Type.Kind() {
case reflect.Float32, reflect.Float64:
firstKind := first.Type.Kind()
for i := 0; i < t.NumField(); i++ {
if t.Field(i).Type.Kind() != firstKind {
return false
}
}
return true
case reflect.Array:
switch first.Type.Elem().Kind() {
case reflect.Float32, reflect.Float64:
return true
default:
return false
}
case reflect.Struct:
for i := 0; i < first.Type.NumField(); i++ {
if !isHFA(first.Type) {
return false
}
}
return true
default:
return false
}
}
// isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type
// and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]).
// A short vector is a machine type that is composed of repeated instances of one fundamental integral or
// floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]).
// This type of struct will be placed more compactly than the individual fields.
//
// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
func isHVA(t reflect.Type) bool {
// round up struct size to nearest 8 see section B.4
structSize := roundUpTo8(t.Size())
if structSize == 0 || (structSize != 8 && structSize != 16) {
return false
}
first := t.Field(0)
switch first.Type.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
firstKind := first.Type.Kind()
for i := 0; i < t.NumField(); i++ {
if t.Field(i).Type.Kind() != firstKind {
return false
}
}
return true
case reflect.Array:
switch first.Type.Elem().Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
return true
default:
return false
}
default:
return false
}
}

16
vendor/github.com/ebitengine/purego/struct_other.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
//go:build !amd64 && !arm64
package purego
import "reflect"
func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
panic("purego: struct arguments are not supported")
}
func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
panic("purego: struct returns are not supported")
}

View File

@@ -1,15 +1,18 @@
// 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"
#include "go_asm.h"
#include "funcdata.h"
// syscall9X calls a function in libc on behalf of the syscall package.
// syscall9X takes a pointer to a struct like:
#define STACK_SIZE 80
#define PTR_ADDRESS (STACK_SIZE - 8)
// syscall15X calls a function in libc on behalf of the syscall package.
// syscall15X takes a pointer to a struct like:
// struct {
// fn uintptr
// a1 uintptr
@@ -21,64 +24,83 @@
// a7 uintptr
// a8 uintptr
// a9 uintptr
// a10 uintptr
// a11 uintptr
// a12 uintptr
// a13 uintptr
// a14 uintptr
// a15 uintptr
// r1 uintptr
// r2 uintptr
// err uintptr
// }
// syscall9X must be called on the g0 stack with the
// syscall15X must be called on the g0 stack with the
// C calling convention (use libcCall).
GLOBL ·syscall9XABI0(SB), NOPTR|RODATA, $8
DATA ·syscall9XABI0(SB)/8, $syscall9X(SB)
TEXT syscall9X(SB), NOSPLIT|NOFRAME, $0
GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8
DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)
TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0
PUSHQ BP
MOVQ SP, BP
SUBQ $32, SP
MOVQ DI, 24(BP) // save the pointer
SUBQ $STACK_SIZE, SP
MOVQ DI, PTR_ADDRESS(BP) // save the pointer
MOVQ DI, R11
MOVQ syscall9Args_f1(DI), X0 // f1
MOVQ syscall9Args_f2(DI), X1 // f2
MOVQ syscall9Args_f3(DI), X2 // f3
MOVQ syscall9Args_f4(DI), X3 // f4
MOVQ syscall9Args_f5(DI), X4 // f5
MOVQ syscall9Args_f6(DI), X5 // f6
MOVQ syscall9Args_f7(DI), X6 // f7
MOVQ syscall9Args_f8(DI), X7 // f8
MOVQ syscall15Args_f1(R11), X0 // f1
MOVQ syscall15Args_f2(R11), X1 // f2
MOVQ syscall15Args_f3(R11), X2 // f3
MOVQ syscall15Args_f4(R11), X3 // f4
MOVQ syscall15Args_f5(R11), X4 // f5
MOVQ syscall15Args_f6(R11), X5 // f6
MOVQ syscall15Args_f7(R11), X6 // f7
MOVQ syscall15Args_f8(R11), X7 // f8
MOVQ syscall9Args_fn(DI), R10 // fn
MOVQ syscall9Args_a2(DI), SI // a2
MOVQ syscall9Args_a3(DI), DX // a3
MOVQ syscall9Args_a4(DI), CX // a4
MOVQ syscall9Args_a5(DI), R8 // a5
MOVQ syscall9Args_a6(DI), R9 // a6
MOVQ syscall9Args_a7(DI), R11 // a7
MOVQ syscall9Args_a8(DI), R12 // a8
MOVQ syscall9Args_a9(DI), R13 // a9
MOVQ syscall9Args_a1(DI), DI // a1
MOVQ syscall15Args_a1(R11), DI // a1
MOVQ syscall15Args_a2(R11), SI // a2
MOVQ syscall15Args_a3(R11), DX // a3
MOVQ syscall15Args_a4(R11), CX // a4
MOVQ syscall15Args_a5(R11), R8 // a5
MOVQ syscall15Args_a6(R11), R9 // a6
// push the remaining paramters onto the stack
MOVQ R11, 0(SP) // push a7
MOVQ R12, 8(SP) // push a8
MOVQ R13, 16(SP) // push a9
XORL AX, AX // vararg: say "no float args"
MOVQ syscall15Args_a7(R11), R12
MOVQ R12, 0(SP) // push a7
MOVQ syscall15Args_a8(R11), R12
MOVQ R12, 8(SP) // push a8
MOVQ syscall15Args_a9(R11), R12
MOVQ R12, 16(SP) // push a9
MOVQ syscall15Args_a10(R11), R12
MOVQ R12, 24(SP) // push a10
MOVQ syscall15Args_a11(R11), R12
MOVQ R12, 32(SP) // push a11
MOVQ syscall15Args_a12(R11), R12
MOVQ R12, 40(SP) // push a12
MOVQ syscall15Args_a13(R11), R12
MOVQ R12, 48(SP) // push a13
MOVQ syscall15Args_a14(R11), R12
MOVQ R12, 56(SP) // push a14
MOVQ syscall15Args_a15(R11), R12
MOVQ R12, 64(SP) // push a15
XORL AX, AX // vararg: say "no float args"
MOVQ syscall15Args_fn(R11), R10 // fn
CALL R10
MOVQ 24(BP), DI // get the pointer back
MOVQ AX, syscall9Args_r1(DI) // r1
MOVQ X0, syscall9Args_r2(DI) // r2
MOVQ PTR_ADDRESS(BP), DI // get the pointer back
MOVQ AX, syscall15Args_a1(DI) // r1
MOVQ DX, syscall15Args_a2(DI) // r3
MOVQ X0, syscall15Args_f1(DI) // f1
MOVQ X1, syscall15Args_f2(DI) // f2
XORL AX, AX // no error (it's ignored anyway)
ADDQ $32, SP
XORL AX, AX // no error (it's ignored anyway)
ADDQ $STACK_SIZE, SP
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
MOVQ 0(SP), AX // save the return address to calculate the cb index
MOVQ 8(SP), R10 // get the return SP so that we can align register args with stack args
ADDQ $8, SP // remove return address from stack, we are not returning to callbackasm, but to its caller.
// make space for first six int and 8 float arguments below the frame
ADJSP $14*8, SP
@@ -98,7 +120,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
MOVQ R9, (14*8)(SP)
LEAQ 8(SP), R8 // R8 = address of args vector
MOVQ R10, 0(SP) // push the stack pointer below registers
PUSHQ R10 // push the stack pointer below registers
// determine index into runtime·cbs table
MOVQ $callbackasm(SB), DX
@@ -134,8 +156,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
POP_REGS_HOST_TO_ABI0()
MOVQ 0(SP), R10 // get the SP back
POPQ R10 // get the SP back
ADJSP $-14*8, SP // remove arguments
MOVQ R10, 0(SP)

View File

@@ -1,14 +1,17 @@
// 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"
#include "funcdata.h"
// syscall9X calls a function in libc on behalf of the syscall package.
// syscall9X takes a pointer to a struct like:
#define STACK_SIZE 64
#define PTR_ADDRESS (STACK_SIZE - 8)
// syscall15X calls a function in libc on behalf of the syscall package.
// syscall15X takes a pointer to a struct like:
// struct {
// fn uintptr
// a1 uintptr
@@ -20,44 +23,70 @@
// a7 uintptr
// a8 uintptr
// a9 uintptr
// a10 uintptr
// a11 uintptr
// a12 uintptr
// a13 uintptr
// a14 uintptr
// a15 uintptr
// r1 uintptr
// r2 uintptr
// err uintptr
// }
// syscall9X must be called on the g0 stack with the
// syscall15X must be called on the g0 stack with the
// C calling convention (use libcCall).
GLOBL ·syscall9XABI0(SB), NOPTR|RODATA, $8
DATA ·syscall9XABI0(SB)/8, $syscall9X(SB)
TEXT syscall9X(SB), NOSPLIT, $0
SUB $16, RSP // push structure pointer
MOVD R0, 8(RSP)
GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8
DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)
TEXT syscall15X(SB), NOSPLIT, $0
SUB $STACK_SIZE, RSP // push structure pointer
MOVD R0, PTR_ADDRESS(RSP)
MOVD R0, R9
FMOVD syscall9Args_f1(R0), F0 // f1
FMOVD syscall9Args_f2(R0), F1 // f2
FMOVD syscall9Args_f3(R0), F2 // f3
FMOVD syscall9Args_f4(R0), F3 // f4
FMOVD syscall9Args_f5(R0), F4 // f5
FMOVD syscall9Args_f6(R0), F5 // f6
FMOVD syscall9Args_f7(R0), F6 // f7
FMOVD syscall9Args_f8(R0), F7 // f8
FMOVD syscall15Args_f1(R9), F0 // f1
FMOVD syscall15Args_f2(R9), F1 // f2
FMOVD syscall15Args_f3(R9), F2 // f3
FMOVD syscall15Args_f4(R9), F3 // f4
FMOVD syscall15Args_f5(R9), F4 // f5
FMOVD syscall15Args_f6(R9), F5 // f6
FMOVD syscall15Args_f7(R9), F6 // f7
FMOVD syscall15Args_f8(R9), F7 // f8
MOVD syscall9Args_fn(R0), R12 // fn
MOVD syscall9Args_a2(R0), R1 // a2
MOVD syscall9Args_a3(R0), R2 // a3
MOVD syscall9Args_a4(R0), R3 // a4
MOVD syscall9Args_a5(R0), R4 // a5
MOVD syscall9Args_a6(R0), R5 // a6
MOVD syscall9Args_a7(R0), R6 // a7
MOVD syscall9Args_a8(R0), R7 // a8
MOVD syscall9Args_a9(R0), R8 // a9
MOVD syscall9Args_a1(R0), R0 // a1
MOVD syscall15Args_a1(R9), R0 // a1
MOVD syscall15Args_a2(R9), R1 // a2
MOVD syscall15Args_a3(R9), R2 // a3
MOVD syscall15Args_a4(R9), R3 // a4
MOVD syscall15Args_a5(R9), R4 // a5
MOVD syscall15Args_a6(R9), R5 // a6
MOVD syscall15Args_a7(R9), R6 // a7
MOVD syscall15Args_a8(R9), R7 // a8
MOVD syscall15Args_arm64_r8(R9), R8 // r8
MOVD R8, (RSP) // push a9 onto stack
MOVD syscall15Args_a9(R9), R10
MOVD R10, 0(RSP) // push a9 onto stack
MOVD syscall15Args_a10(R9), R10
MOVD R10, 8(RSP) // push a10 onto stack
MOVD syscall15Args_a11(R9), R10
MOVD R10, 16(RSP) // push a11 onto stack
MOVD syscall15Args_a12(R9), R10
MOVD R10, 24(RSP) // push a12 onto stack
MOVD syscall15Args_a13(R9), R10
MOVD R10, 32(RSP) // push a13 onto stack
MOVD syscall15Args_a14(R9), R10
MOVD R10, 40(RSP) // push a14 onto stack
MOVD syscall15Args_a15(R9), R10
MOVD R10, 48(RSP) // push a15 onto stack
BL (R12)
MOVD syscall15Args_fn(R9), R10 // fn
BL (R10)
MOVD PTR_ADDRESS(RSP), R2 // pop structure pointer
ADD $STACK_SIZE, RSP
MOVD R0, syscall15Args_a1(R2) // save r1
MOVD R1, syscall15Args_a2(R2) // save r3
FMOVD F0, syscall15Args_f1(R2) // save f0
FMOVD F1, syscall15Args_f2(R2) // save f1
FMOVD F2, syscall15Args_f3(R2) // save f2
FMOVD F3, syscall15Args_f4(R2) // save f3
MOVD 8(RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, syscall9Args_r1(R2) // save r1
FMOVD F0, syscall9Args_r2(R2) // save r2
RET

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || (!cgo && linux)
//go:build darwin || freebsd || linux
#include "textflag.h"
#include "go_asm.h"

View File

@@ -6,7 +6,7 @@
package purego
const (
maxArgs = 9
maxArgs = 15
numOfFloats = 8 // arm64 and amd64 both have 8 float registers
)
@@ -36,5 +36,5 @@ func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) {
// add padding so there is no out-of-bounds slicing
var tmp [maxArgs]uintptr
copy(tmp[:], args)
return syscall_syscall9X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8])
return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14])
}

View File

@@ -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
@@ -11,20 +11,21 @@ import (
"github.com/ebitengine/purego/internal/cgo"
)
var syscall9XABI0 = uintptr(cgo.Syscall9XABI0)
var syscall15XABI0 = uintptr(cgo.Syscall15XABI0)
// 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
type syscall15Args struct {
fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
f1, f2, f3, f4, f5, f6, f7, f8 uintptr
r1, r2, err uintptr
arm64_r8 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 syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
return cgo.Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)
}
func NewCallback(_ interface{}) uintptr {
panic("purego: NewCallback not supported")
panic("purego: NewCallback on Linux is only supported on amd64/arm64")
}

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || (!cgo && linux && (amd64 || arm64))
//go:build darwin || freebsd || (linux && (amd64 || arm64))
package purego
@@ -12,23 +12,23 @@ import (
"unsafe"
)
var syscall9XABI0 uintptr
var syscall15XABI0 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
type syscall15Args struct {
fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
f1, f2, f3, f4, f5, f6, f7, f8 uintptr
arm64_r8 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,
func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
args := syscall15Args{
fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15,
a1, a2, a3, a4, a5, a6, a7, a8,
r1, r2, err,
0,
}
runtime_cgocall(syscall9XABI0, unsafe.Pointer(&args))
return args.r1, args.r2, args.err
runtime_cgocall(syscall15XABI0, unsafe.Pointer(&args))
return args.a1, args.a2, 0
}
// NewCallback converts a Go function to a function pointer conforming to the C calling convention.
@@ -37,12 +37,7 @@ func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2,
// of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated
// for these callbacks is never released. At least 2000 callbacks can always be created. Although this function
// provides similar functionality to windows.NewCallback it is distinct.
//
// NOTE: Linux is currently not supported and will panic if called.
func NewCallback(fn interface{}) uintptr {
if runtime.GOOS == "linux" {
panic("purego: NewCallback not supported")
}
return compileCallback(fn)
}

View File

@@ -10,16 +10,16 @@ import (
"golang.org/x/sys/windows"
)
var syscall9XABI0 uintptr
var syscall15XABI0 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
type syscall15Args struct {
fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
f1, f2, f3, f4, f5, f6, f7, f8 uintptr
arm64_r8 uintptr
}
func syscall_syscall9X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) {
r1, r2, errno := syscall.Syscall9(fn, 9, a1, a2, a3, a4, a5, a6, a7, a8, a9)
func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
r1, r2, errno := syscall.Syscall15(fn, 15, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)
return r1, r2, uintptr(errno)
}

View File

@@ -1,6 +1,6 @@
// Code generated by wincallback.go using 'go generate'. DO NOT EDIT.
//go:build darwin || freebsd || (!cgo && linux)
//go:build darwin || freebsd || linux
// runtime·callbackasm is called by external code to
// execute Go implemented callback function. It is not

View File

@@ -1,6 +1,6 @@
// Code generated by wincallback.go using 'go generate'. DO NOT EDIT.
//go:build darwin || freebsd || (!cgo && linux)
//go:build darwin || freebsd || linux
// External code calls into callbackasm at an offset corresponding
// to the callback index. Callbackasm is a table of MOV and B instructions.