Skip to content
Open
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
25 changes: 25 additions & 0 deletions callback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
39 changes: 37 additions & 2 deletions syscall_sysv.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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,
Expand Down