Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 63 additions & 8 deletions func.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"math"
"reflect"
"runtime"
"strconv"
"sync"
"unsafe"

Expand All @@ -22,6 +21,15 @@
return new(syscall15Args)
}}

var structTypeCache sync.Map
var structInstancePool sync.Map // map[reflect.Type]*sync.Pool

// Pre-computed field names to avoid allocations
var fieldNames = [maxArgs]string{
"X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7",
"X8", "X9", "X10", "X11", "X12", "X13", "X14",
}

// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
// It panics if it can't find the name symbol.
func RegisterLibFunc(fptr any, handle uintptr, name string) {
Expand Down Expand Up @@ -283,26 +291,39 @@
}
if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" &&
(numInts >= numOfIntegerRegisters() || numFloats >= numOfFloatRegisters) && v.Kind() != reflect.Struct { // hit the stack
fields := make([]reflect.StructField, len(args[i:]))
fields := make([]reflect.StructField, 0, 8)

// Build type hash as we build fields (avoids string allocation)
var typeHash uintptr
for j, val := range args[i:] {
if val.Kind() == reflect.String {
ptr := strings.CString(val.String())
keepAlive = append(keepAlive, ptr)
val = reflect.ValueOf(ptr)
args[i+j] = val
}
fields[j] = reflect.StructField{
Name: "X" + strconv.Itoa(j),
Type: val.Type(),
}
valType := val.Type()
fields = append(fields, reflect.StructField{
Name: fieldNames[j],
Type: valType,
})
// Hash the type pointer for cache key (use interface value directly)
typeHash = typeHash*31 ^ uintptr((*[2]uintptr)(unsafe.Pointer(&valType))[1])
}
structType := reflect.StructOf(fields)
structInstance := reflect.New(structType).Elem()

var structType reflect.Type
if cached, ok := structTypeCache.Load(typeHash); ok {
structType = cached.(reflect.Type)
} else {
structType = reflect.StructOf(fields)
structTypeCache.Store(typeHash, structType)
}
structInstance := getPooledStructInstance(structType)
for j, val := range args[i:] {
structInstance.Field(j).Set(val)
}
placeRegisters(structInstance, addFloat, addInt)
returnPooledStructInstance(structType, structInstance)
break
}
keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
Expand Down Expand Up @@ -476,6 +497,40 @@
return (val + 7) &^ 7
}

func getPooledStructInstance(t reflect.Type) reflect.Value {
// Try to load existing pool first (fast path)
if poolInterface, ok := structInstancePool.Load(t); ok {
pool := poolInterface.(*sync.Pool)
ptr := pool.Get()
val := reflect.ValueOf(ptr).Elem()
return val
}

// Slow path: create new pool (only happens once per type)
newPool := &sync.Pool{
New: func() any {
return reflect.New(t).Interface()
},
}
poolInterface, _ := structInstancePool.LoadOrStore(t, newPool)
pool := poolInterface.(*sync.Pool)
ptr := pool.Get()
val := reflect.ValueOf(ptr).Elem()
return val
}

func returnPooledStructInstance(t reflect.Type, v reflect.Value) {
if poolInterface, ok := structInstancePool.Load(t); ok {
pool := poolInterface.(*sync.Pool)
// Zero all fields before returning to pool
for i := 0; i < v.NumField(); i++ {
v.Field(i).SetZero()

Check failure on line 527 in func.go

View workflow job for this annotation

GitHub Actions / Test with Go 1.18.x on ubuntu-24.04-arm

v.Field(i).SetZero undefined (type reflect.Value has no field or method SetZero)

Check failure on line 527 in func.go

View workflow job for this annotation

GitHub Actions / Test with Go 1.19.x on ubuntu-24.04-arm

v.Field(i).SetZero undefined (type reflect.Value has no field or method SetZero)

Check failure on line 527 in func.go

View workflow job for this annotation

GitHub Actions / Test with Go 1.19.x on macos-latest

v.Field(i).SetZero undefined (type reflect.Value has no field or method SetZero)
}
ptr := v.Addr().Interface()
pool.Put(ptr)
}
}

func numOfIntegerRegisters() int {
switch runtime.GOARCH {
case "arm64", "loong64":
Expand Down
2 changes: 2 additions & 0 deletions struct_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
} else {
addInt(uintptr(val))
}
val = 0
class = _NO_CLASS
}
switch f.Type().Kind() {
case reflect.Struct:
Expand Down
17 changes: 15 additions & 2 deletions struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,8 +373,9 @@ func TestRegisterFunc_structArgs(t *testing.T) {
}
var Array4CharsFn func(chars Array4Chars) int32
purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars")
if ret := Array4CharsFn(Array4Chars{a: [...]int8{100, -127, 4, -100}}); ret != expectedSigned {
t.Fatalf("Array4CharsFn returned %#x wanted %#x", ret, expectedSigned)
const expectedSum = 1 + 2 + 4 + 8
if ret := Array4CharsFn(Array4Chars{a: [...]int8{1, 2, 4, 8}}); ret != expectedSum {
t.Fatalf("Array4CharsFn returned %d wanted %d", ret, expectedSum)
}
}
{
Expand Down Expand Up @@ -486,6 +487,18 @@ func TestRegisterFunc_structArgs(t *testing.T) {
t.Fatalf("FloatAndBool(y: false) = %d, want 0", ret)
}
}
{
type FourInt32s struct {
f0, f1, f2, f3 int32
}
var FourInt32sFn func(FourInt32s) int32
purego.RegisterLibFunc(&FourInt32sFn, lib, "FourInt32s")
result := FourInt32sFn(FourInt32s{100, -127, 4, -100})
const want = 100 - 127 + 4 - 100
if result != want {
t.Fatalf("FourInt32s returned %d wanted %d", result, want)
}
}
}

func TestRegisterFunc_structReturns(t *testing.T) {
Expand Down
Loading
Loading