diff --git a/callback_test.go b/callback_test.go index e373c02a..bc7be0b1 100644 --- a/callback_test.go +++ b/callback_test.go @@ -411,3 +411,28 @@ func TestCallbackFloat32StackPacking(t *testing.T) { t.Errorf("callCallback12Float32() = %d, want %d", got, want) } } + +func TestNewCallbackStructArg(t *testing.T) { + type Object struct{ Ptr unsafe.Pointer } + type Exception struct{ Object } + + var got Exception + cb := purego.NewCallback(func(e Exception) { got = e }) + if cb == 0 { + t.Fatal("NewCallback returned 0") + } + + if runtime.GOOS != "darwin" { + t.Skipf("RegisterFunc struct args not supported on %s/%s", runtime.GOOS, runtime.GOARCH) + } + + var marker byte + sentinel := unsafe.Pointer(&marker) + var fn func(Exception) + purego.RegisterFunc(&fn, cb) + fn(Exception{Object{sentinel}}) + + if got.Ptr != sentinel { + t.Errorf("struct callback arg: got %v, want %v", got.Ptr, sentinel) + } +} diff --git a/syscall_sysv.go b/syscall_sysv.go index e35b32e7..e8b26dec 100644 --- a/syscall_sysv.go +++ b/syscall_sysv.go @@ -76,6 +76,9 @@ func compileCallback(fn any) uintptr { if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) { continue } + if in.Size() <= ptrSize && isPointerLikeStruct(in) { + continue + } fallthrough case reflect.Interface, reflect.Func, reflect.Slice, reflect.Chan, reflect.Complex64, reflect.Complex128, @@ -187,8 +190,23 @@ func callbackWrap(a *callbackArgs) { } floatsN += slots case reflect.Struct: - // This is the CDecl field - args[i] = reflect.Zero(inType) + if inType.AssignableTo(reflect.TypeOf(CDecl{})) { + args[i] = reflect.Zero(inType) + continue + } + slots = int((inType.Size() + ptrSize - 1) / ptrSize) + if intsN+slots > numOfIntegerRegisters() { + if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { + args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) + } else { + args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() + stackSlot += slots + } + } else { + pos := intsN + numOfFloatRegisters() + args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() + } + intsN += slots default: slots = int((inType.Size() + ptrSize - 1) / ptrSize) if intsN+slots > numOfIntegerRegisters() { @@ -288,6 +306,23 @@ func callbackArgFromSlotBigEndian(slotPtr unsafe.Pointer, inType reflect.Type) r return reflect.NewAt(inType, ptr).Elem() } +// isPointerLikeStruct reports whether t is a struct whose leaf fields +// total exactly one pointer-sized value. +func isPointerLikeStruct(t reflect.Type) bool { + return flattenStructSize(t) == ptrSize +} + +func flattenStructSize(t reflect.Type) uintptr { + if t.Kind() != reflect.Struct { + return t.Size() + } + var total uintptr + for i := 0; i < t.NumField(); i++ { + total += flattenStructSize(t.Field(i).Type) + } + return total +} + // callbackasmAddr returns address of runtime.callbackasm // function adjusted by i. // On x86 and amd64, runtime.callbackasm is a series of CALL instructions,