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

@@ -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":