Skip to content

Commit 198e2b1

Browse files
committed
purego: extend struct argument support to Linux amd64/arm64
Expands struct argument and return value support from Darwin-only to both Darwin and Linux on amd64 and arm64 architectures. This change enables purego to handle struct marshalling on additional platforms while maintaining the existing safety checks for unsupported architectures. Updates platform validation logic to check both GOOS and GOARCH, ensuring struct support is only enabled on tested platform combinations. The build constraints for struct tests are updated accordingly to include Linux. Adds test coverage for pointer wrapper structs to validate proper handling of single and multiple pointer fields within structs, ensuring correct register allocation and marshalling across the expanded platform support.
1 parent e49e529 commit 198e2b1

File tree

5 files changed

+86
-12
lines changed

5 files changed

+86
-12
lines changed

func.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) {
6060
// int64 <=> int64_t
6161
// float32 <=> float
6262
// float64 <=> double
63-
// struct <=> struct (WIP - darwin only)
63+
// struct <=> struct (darwin amd64/arm64, linux amd64/arm64)
6464
// func <=> C function
6565
// unsafe.Pointer, *T <=> void*
6666
// []T => void*
@@ -169,8 +169,16 @@ func RegisterFunc(fptr any, cfn uintptr) {
169169
stack++
170170
}
171171
case reflect.Struct:
172-
if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") {
173-
panic("purego: struct arguments are only supported on darwin amd64 & arm64")
172+
// Struct arguments are supported on:
173+
// - darwin amd64 & arm64
174+
// - linux amd64 & arm64
175+
switch runtime.GOARCH {
176+
case "amd64", "arm64":
177+
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
178+
panic("purego: struct arguments are only supported on darwin and linux")
179+
}
180+
default:
181+
panic("purego: struct arguments are only supported on amd64 and arm64")
174182
}
175183
if arg.Size() == 0 {
176184
continue
@@ -190,8 +198,16 @@ func RegisterFunc(fptr any, cfn uintptr) {
190198
}
191199
}
192200
if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
193-
if runtime.GOOS != "darwin" {
194-
panic("purego: struct return values only supported on darwin arm64 & amd64")
201+
// Struct return values are supported on:
202+
// - darwin amd64 & arm64
203+
// - linux amd64 & arm64
204+
switch runtime.GOARCH {
205+
case "amd64", "arm64":
206+
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
207+
panic("purego: struct return values are only supported on darwin and linux")
208+
}
209+
default:
210+
panic("purego: struct return values are only supported on amd64 and arm64")
195211
}
196212
outType := ty.Out(0)
197213
checkStructFieldsSupported(outType)

struct_amd64.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,10 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
169169
}
170170
shift += 8
171171
class |= _INTEGER
172-
case reflect.Pointer:
173-
ok = false
174-
return
172+
case reflect.Pointer, reflect.UnsafePointer:
173+
val = uint64(f.Pointer())
174+
shift = 64
175+
class = _INTEGER
175176
case reflect.Int8:
176177
val |= uint64(f.Int()&0xFF) << shift
177178
shift += 8
@@ -241,7 +242,7 @@ func placeStack(v reflect.Value, addStack func(uintptr)) {
241242
for i := 0; i < v.Type().NumField(); i++ {
242243
f := v.Field(i)
243244
switch f.Kind() {
244-
case reflect.Pointer:
245+
case reflect.Pointer, reflect.UnsafePointer:
245246
addStack(f.Pointer())
246247
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
247248
addStack(uintptr(f.Int()))

struct_arm64.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
175175
shift = 0
176176
flushed = true
177177
class = _NO_CLASS
178-
case reflect.Ptr:
178+
case reflect.Ptr, reflect.UnsafePointer:
179179
addInt(f.Pointer())
180180
shift = 0
181181
flushed = true

struct_test.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
33

4-
//go:build darwin && (arm64 || amd64)
4+
//go:build (darwin && (arm64 || amd64)) || (linux && (arm64 || amd64))
55

66
package purego_test
77

@@ -498,6 +498,44 @@ func TestRegisterFunc_structArgs(t *testing.T) {
498498
t.Fatalf("FourInt32s returned %q wanted %q", result, want)
499499
}
500500
}
501+
{
502+
// Simple pointer wrapper struct - tests single pointer field
503+
type PointerWrapper struct {
504+
ctx unsafe.Pointer
505+
}
506+
var ExtractPointer func(wrapper PointerWrapper) uintptr
507+
purego.RegisterLibFunc(&ExtractPointer, lib, "ExtractPointer")
508+
509+
// Use actual allocated memory to satisfy checkptr
510+
testValue := new(int)
511+
expectedPtr := uintptr(unsafe.Pointer(testValue))
512+
result := ExtractPointer(PointerWrapper{ctx: unsafe.Pointer(testValue)})
513+
if result != expectedPtr {
514+
t.Fatalf("ExtractPointer returned %#x wanted %#x", result, expectedPtr)
515+
}
516+
runtime.KeepAlive(testValue)
517+
}
518+
{
519+
// Two pointer struct - tests register allocation for multiple pointer fields
520+
type TwoPointers struct {
521+
ptr1, ptr2 unsafe.Pointer
522+
}
523+
var AddPointers func(wrapper TwoPointers) uintptr
524+
purego.RegisterLibFunc(&AddPointers, lib, "AddPointers")
525+
526+
// Use actual allocated memory to satisfy checkptr
527+
val1 := new(int)
528+
val2 := new(int)
529+
ptr1 := uintptr(unsafe.Pointer(val1))
530+
ptr2 := uintptr(unsafe.Pointer(val2))
531+
expected := ptr1 + ptr2
532+
result := AddPointers(TwoPointers{unsafe.Pointer(val1), unsafe.Pointer(val2)})
533+
if result != expected {
534+
t.Fatalf("AddPointers returned %#x wanted %#x", result, expected)
535+
}
536+
runtime.KeepAlive(val1)
537+
runtime.KeepAlive(val2)
538+
}
501539
}
502540

503541
func TestRegisterFunc_structReturns(t *testing.T) {

testdata/structtest/struct_test.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ unsigned int Array2UnsignedShorts(struct Array2UnsignedShort a) {
278278
}
279279

280280
struct Array4Chars {
281-
char a[4];
281+
int8_t a[4];
282282
};
283283

284284
int Array4Chars(struct Array4Chars a) {
@@ -377,3 +377,22 @@ char* FourInt32s(struct FourInt32s s) {
377377
snprintf(result, 64, "%d:%d:%d:%d", s.f0, s.f1, s.f2, s.f3);
378378
return result;
379379
}
380+
381+
// Simple pointer wrapper struct - tests single pointer field
382+
struct PointerWrapper {
383+
void* ctx;
384+
};
385+
386+
uintptr_t ExtractPointer(struct PointerWrapper wrapper) {
387+
return (uintptr_t)wrapper.ctx;
388+
}
389+
390+
// Two pointer struct - tests register allocation for multiple pointer fields
391+
struct TwoPointers {
392+
void* ptr1;
393+
void* ptr2;
394+
};
395+
396+
uintptr_t AddPointers(struct TwoPointers wrapper) {
397+
return (uintptr_t)wrapper.ptr1 + (uintptr_t)wrapper.ptr2;
398+
}

0 commit comments

Comments
 (0)