purego: extend struct argument support to Linux amd64/arm64 (#361)
This change removes the Darwin-only restriction on struct arguments and enables struct-by-value support on Linux for both amd64 and arm64 architectures. Closes #236 Closes #412
- commit
- 1a5155f4f6ffe0cd9807167734bc7d1684de32d2
- author
- Travis Cline · · 2026-02-12 04:10 UTC
- committer
- GitHub · · 2026-02-12 04:10 UTC
- parent
- 071d22a
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
7 files · +186 -35
M func.go +14 -8
| @@ -64,7 +64,7 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) { | |||
| 64 | 64 | // int64 <=> int64_t | |
| 65 | 65 | // float32 <=> float | |
| 66 | 66 | // float64 <=> double | |
| 67 | - | // struct <=> struct (WIP - darwin only) | |
| 67 | + | // struct <=> struct (darwin amd64/arm64, linux amd64/arm64) | |
| 68 | 68 | // func <=> C function | |
| 69 | 69 | // unsafe.Pointer, *T <=> void* | |
| 70 | 70 | // []T => void* | |
| @@ -173,9 +173,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 173 | 173 | stack++ | |
| 174 | 174 | } | |
| 175 | 175 | case reflect.Struct: | |
| 176 | - | if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") { | |
| 177 | - | panic("purego: struct arguments are only supported on darwin amd64 & arm64") | |
| 178 | - | } | |
| 176 | + | ensureStructSupportedForRegisterFunc() | |
| 179 | 177 | if arg.Size() == 0 { | |
| 180 | 178 | continue | |
| 181 | 179 | } | |
| @@ -194,9 +192,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 194 | 192 | } | |
| 195 | 193 | } | |
| 196 | 194 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 197 | - | if runtime.GOOS != "darwin" { | |
| 198 | - | panic("purego: struct return values only supported on darwin arm64 & amd64") | |
| 199 | - | } | |
| 195 | + | ensureStructSupportedForRegisterFunc() | |
| 200 | 196 | outType := ty.Out(0) | |
| 201 | 197 | checkStructFieldsSupported(outType) | |
| 202 | 198 | if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { | |
| @@ -477,13 +473,23 @@ func checkStructFieldsSupported(ty reflect.Type) { | |||
| 477 | 473 | switch f.Kind() { | |
| 478 | 474 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | |
| 479 | 475 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, | |
| 480 | - | reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32: | |
| 476 | + | reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32, | |
| 477 | + | reflect.Bool: | |
| 481 | 478 | default: | |
| 482 | 479 | panic(fmt.Sprintf("purego: struct field type %s is not supported", f)) | |
| 483 | 480 | } | |
| 484 | 481 | } | |
| 485 | 482 | } | |
| 486 | 483 | ||
| 484 | + | func ensureStructSupportedForRegisterFunc() { | |
| 485 | + | if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" { | |
| 486 | + | panic("purego: struct arguments are only supported on amd64 and arm64") | |
| 487 | + | } | |
| 488 | + | if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { | |
| 489 | + | panic("purego: struct arguments are only supported on darwin and linux") | |
| 490 | + | } | |
| 491 | + | } | |
| 492 | + | ||
| 487 | 493 | func roundUpTo8(val uintptr) uintptr { | |
| 488 | 494 | return (val + align8ByteMask) &^ align8ByteMask | |
| 489 | 495 | } | |
| @@ -64,7 +64,7 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) { | |||||
| 64 | // int64 <=> int64_t | 64 | // int64 <=> int64_t | ||
| 65 | // float32 <=> float | 65 | // float32 <=> float | ||
| 66 | // float64 <=> double | 66 | // float64 <=> double | ||
| 67 | - | // struct <=> struct (WIP - darwin only) | 67 | + | // struct <=> struct (darwin amd64/arm64, linux amd64/arm64) |
| 68 | // func <=> C function | 68 | // func <=> C function | ||
| 69 | // unsafe.Pointer, *T <=> void* | 69 | // unsafe.Pointer, *T <=> void* | ||
| 70 | // []T => void* | 70 | // []T => void* | ||
| @@ -173,9 +173,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 173 | stack++ | 173 | stack++ | ||
| 174 | } | 174 | } | ||
| 175 | case reflect.Struct: | 175 | case reflect.Struct: | ||
| 176 | - | if runtime.GOOS != "darwin" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64") { | 176 | + | ensureStructSupportedForRegisterFunc() |
| 177 | - | panic("purego: struct arguments are only supported on darwin amd64 & arm64") | |||
| 178 | - | } | |||
| 179 | if arg.Size() == 0 { | 177 | if arg.Size() == 0 { | ||
| 180 | continue | 178 | continue | ||
| 181 | } | 179 | } | ||
| @@ -194,9 +192,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 194 | } | 192 | } | ||
| 195 | } | 193 | } | ||
| 196 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | 194 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | ||
| 197 | - | if runtime.GOOS != "darwin" { | 195 | + | ensureStructSupportedForRegisterFunc() |
| 198 | - | panic("purego: struct return values only supported on darwin arm64 & amd64") | |||
| 199 | - | } | |||
| 200 | outType := ty.Out(0) | 196 | outType := ty.Out(0) | ||
| 201 | checkStructFieldsSupported(outType) | 197 | checkStructFieldsSupported(outType) | ||
| 202 | if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { | 198 | if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { | ||
| @@ -477,13 +473,23 @@ func checkStructFieldsSupported(ty reflect.Type) { | |||||
| 477 | switch f.Kind() { | 473 | switch f.Kind() { | ||
| 478 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | 474 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | ||
| 479 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, | 475 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, | ||
| 480 | - | reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32: | 476 | + | reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Float64, reflect.Float32, |
| 477 | + | reflect.Bool: | |||
| 481 | default: | 478 | default: | ||
| 482 | panic(fmt.Sprintf("purego: struct field type %s is not supported", f)) | 479 | panic(fmt.Sprintf("purego: struct field type %s is not supported", f)) | ||
| 483 | } | 480 | } | ||
| 484 | } | 481 | } | ||
| 485 | } | 482 | } | ||
| 486 | 483 | ||||
| 484 | + | func ensureStructSupportedForRegisterFunc() { | |||
| 485 | + | if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" { | |||
| 486 | + | panic("purego: struct arguments are only supported on amd64 and arm64") | |||
| 487 | + | } | |||
| 488 | + | if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { | |||
| 489 | + | panic("purego: struct arguments are only supported on darwin and linux") | |||
| 490 | + | } | |||
| 491 | + | } | |||
| 492 | + | ||||
| 487 | func roundUpTo8(val uintptr) uintptr { | 493 | func roundUpTo8(val uintptr) uintptr { | ||
| 488 | return (val + align8ByteMask) &^ align8ByteMask | 494 | return (val + align8ByteMask) &^ align8ByteMask | ||
| 489 | } | 495 | } | ||
M struct_amd64.go +20 -21
| @@ -169,9 +169,10 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp | |||
| 169 | 169 | } | |
| 170 | 170 | shift += 8 | |
| 171 | 171 | 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 | |
| 175 | 176 | case reflect.Int8: | |
| 176 | 177 | val |= uint64(f.Int()&0xFF) << shift | |
| 177 | 178 | shift += 8 | |
| @@ -238,24 +239,22 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp | |||
| 238 | 239 | } | |
| 239 | 240 | ||
| 240 | 241 | func placeStack(v reflect.Value, addStack func(uintptr)) { | |
| 241 | - | for i := 0; i < v.Type().NumField(); i++ { | |
| 242 | - | f := v.Field(i) | |
| 243 | - | switch f.Kind() { | |
| 244 | - | case reflect.Pointer: | |
| 245 | - | addStack(f.Pointer()) | |
| 246 | - | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| 247 | - | addStack(uintptr(f.Int())) | |
| 248 | - | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | |
| 249 | - | addStack(uintptr(f.Uint())) | |
| 250 | - | case reflect.Float32: | |
| 251 | - | addStack(uintptr(math.Float32bits(float32(f.Float())))) | |
| 252 | - | case reflect.Float64: | |
| 253 | - | addStack(uintptr(math.Float64bits(f.Float()))) | |
| 254 | - | case reflect.Struct: | |
| 255 | - | placeStack(f, addStack) | |
| 256 | - | default: | |
| 257 | - | panic("purego: unsupported kind " + f.Kind().String()) | |
| 258 | - | } | |
| 242 | + | // Copy the struct as a contiguous block of memory in eightbyte (8-byte) | |
| 243 | + | // chunks. The x86-64 ABI requires structs passed on the stack to be | |
| 244 | + | // laid out exactly as in memory, including padding and field packing | |
| 245 | + | // within eightbytes. Decomposing field-by-field would place each field | |
| 246 | + | // as a separate stack slot, breaking structs with mixed-type fields | |
| 247 | + | // that share an eightbyte (e.g. int32 + float32). | |
| 248 | + | if !v.CanAddr() { | |
| 249 | + | tmp := reflect.New(v.Type()).Elem() | |
| 250 | + | tmp.Set(v) | |
| 251 | + | v = tmp | |
| 252 | + | } | |
| 253 | + | ptr := v.Addr().UnsafePointer() | |
| 254 | + | size := v.Type().Size() | |
| 255 | + | for off := uintptr(0); off < size; off += 8 { | |
| 256 | + | chunk := *(*uintptr)(unsafe.Add(ptr, off)) | |
| 257 | + | addStack(chunk) | |
| 259 | 258 | } | |
| 260 | 259 | } | |
| 261 | 260 | ||
| @@ -169,9 +169,10 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp | |||||
| 169 | } | 169 | } | ||
| 170 | shift += 8 | 170 | shift += 8 | ||
| 171 | class |= _INTEGER | 171 | class |= _INTEGER | ||
| 172 | - | case reflect.Pointer: | 172 | + | case reflect.Pointer, reflect.UnsafePointer: |
| 173 | - | ok = false | 173 | + | val = uint64(f.Pointer()) |
| 174 | - | return | 174 | + | shift = 64 |
| 175 | + | class = _INTEGER | |||
| 175 | case reflect.Int8: | 176 | case reflect.Int8: | ||
| 176 | val |= uint64(f.Int()&0xFF) << shift | 177 | val |= uint64(f.Int()&0xFF) << shift | ||
| 177 | shift += 8 | 178 | shift += 8 | ||
| @@ -238,24 +239,22 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp | |||||
| 238 | } | 239 | } | ||
| 239 | 240 | ||||
| 240 | func placeStack(v reflect.Value, addStack func(uintptr)) { | 241 | func placeStack(v reflect.Value, addStack func(uintptr)) { | ||
| 241 | - | for i := 0; i < v.Type().NumField(); i++ { | 242 | + | // Copy the struct as a contiguous block of memory in eightbyte (8-byte) |
| 242 | - | f := v.Field(i) | 243 | + | // chunks. The x86-64 ABI requires structs passed on the stack to be |
| 243 | - | switch f.Kind() { | 244 | + | // laid out exactly as in memory, including padding and field packing |
| 244 | - | case reflect.Pointer: | 245 | + | // within eightbytes. Decomposing field-by-field would place each field |
| 245 | - | addStack(f.Pointer()) | 246 | + | // as a separate stack slot, breaking structs with mixed-type fields |
| 246 | - | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | 247 | + | // that share an eightbyte (e.g. int32 + float32). |
| 247 | - | addStack(uintptr(f.Int())) | 248 | + | if !v.CanAddr() { |
| 248 | - | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | 249 | + | tmp := reflect.New(v.Type()).Elem() |
| 249 | - | addStack(uintptr(f.Uint())) | 250 | + | tmp.Set(v) |
| 250 | - | case reflect.Float32: | 251 | + | v = tmp |
| 251 | - | addStack(uintptr(math.Float32bits(float32(f.Float())))) | 252 | + | } |
| 252 | - | case reflect.Float64: | 253 | + | ptr := v.Addr().UnsafePointer() |
| 253 | - | addStack(uintptr(math.Float64bits(f.Float()))) | 254 | + | size := v.Type().Size() |
| 254 | - | case reflect.Struct: | 255 | + | for off := uintptr(0); off < size; off += 8 { |
| 255 | - | placeStack(f, addStack) | 256 | + | chunk := *(*uintptr)(unsafe.Add(ptr, off)) |
| 256 | - | default: | 257 | + | addStack(chunk) |
| 257 | - | panic("purego: unsupported kind " + f.Kind().String()) | |||
| 258 | - | } | |||
| 259 | } | 258 | } | ||
| 260 | } | 259 | } | ||
| 261 | 260 | ||||
M struct_arm64.go +1 -1
| @@ -190,7 +190,7 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui | |||
| 190 | 190 | shift = 0 | |
| 191 | 191 | flushed = true | |
| 192 | 192 | class = _NO_CLASS | |
| 193 | - | case reflect.Ptr: | |
| 193 | + | case reflect.Ptr, reflect.UnsafePointer: | |
| 194 | 194 | addInt(f.Pointer()) | |
| 195 | 195 | shift = 0 | |
| 196 | 196 | flushed = true | |
| @@ -190,7 +190,7 @@ func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(ui | |||||
| 190 | shift = 0 | 190 | shift = 0 | ||
| 191 | flushed = true | 191 | flushed = true | ||
| 192 | class = _NO_CLASS | 192 | class = _NO_CLASS | ||
| 193 | - | case reflect.Ptr: | 193 | + | case reflect.Ptr, reflect.UnsafePointer: |
| 194 | addInt(f.Pointer()) | 194 | addInt(f.Pointer()) | ||
| 195 | shift = 0 | 195 | shift = 0 | ||
| 196 | flushed = true | 196 | flushed = true | ||
M struct_loong64.go +1 -1
| @@ -156,7 +156,7 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr | |||
| 156 | 156 | shift = 0 | |
| 157 | 157 | flushed = true | |
| 158 | 158 | class = _NO_CLASS | |
| 159 | - | case reflect.Ptr: | |
| 159 | + | case reflect.Ptr, reflect.UnsafePointer: | |
| 160 | 160 | addInt(f.Pointer()) | |
| 161 | 161 | shift = 0 | |
| 162 | 162 | flushed = true | |
| @@ -156,7 +156,7 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr | |||||
| 156 | shift = 0 | 156 | shift = 0 | ||
| 157 | flushed = true | 157 | flushed = true | ||
| 158 | class = _NO_CLASS | 158 | class = _NO_CLASS | ||
| 159 | - | case reflect.Ptr: | 159 | + | case reflect.Ptr, reflect.UnsafePointer: |
| 160 | addInt(f.Pointer()) | 160 | addInt(f.Pointer()) | ||
| 161 | shift = 0 | 161 | shift = 0 | ||
| 162 | flushed = true | 162 | flushed = true | ||
M struct_test.go +93 -3
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build darwin && (arm64 || amd64) | |
| 4 | + | //go:build (darwin || linux) && (amd64 || arm64) | |
| 5 | 5 | ||
| 6 | 6 | package purego_test | |
| 7 | 7 | ||
| @@ -373,8 +373,8 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 373 | 373 | } | |
| 374 | 374 | var Array4CharsFn func(chars Array4Chars) int32 | |
| 375 | 375 | purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars") | |
| 376 | - | const expectedSum = 1 + 2 + 4 + 8 | |
| 377 | - | if ret := Array4CharsFn(Array4Chars{a: [...]int8{1, 2, 4, 8}}); ret != expectedSum { | |
| 376 | + | const expectedSum = 10 + 20 + 30 + 63 | |
| 377 | + | if ret := Array4CharsFn(Array4Chars{a: [...]int8{10, 20, 30, 63}}); ret != expectedSum { | |
| 378 | 378 | t.Fatalf("Array4CharsFn returned %d wanted %d", ret, expectedSum) | |
| 379 | 379 | } | |
| 380 | 380 | } | |
| @@ -499,6 +499,37 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 499 | 499 | t.Fatalf("FourInt32s returned %d wanted %d", result, want) | |
| 500 | 500 | } | |
| 501 | 501 | } | |
| 502 | + | { | |
| 503 | + | type PointerWrapper struct { | |
| 504 | + | ctx unsafe.Pointer | |
| 505 | + | } | |
| 506 | + | var ExtractPointer func(wrapper PointerWrapper) uintptr | |
| 507 | + | purego.RegisterLibFunc(&ExtractPointer, lib, "ExtractPointer") | |
| 508 | + | ||
| 509 | + | var v int | |
| 510 | + | ptr := unsafe.Pointer(&v) | |
| 511 | + | expected := uintptr(ptr) | |
| 512 | + | result := ExtractPointer(PointerWrapper{ctx: ptr}) | |
| 513 | + | if result != expected { | |
| 514 | + | t.Fatalf("ExtractPointer returned %#x wanted %#x", result, expected) | |
| 515 | + | } | |
| 516 | + | } | |
| 517 | + | { | |
| 518 | + | type TwoPointers struct { | |
| 519 | + | ptr1, ptr2 unsafe.Pointer | |
| 520 | + | } | |
| 521 | + | var AddPointers func(wrapper TwoPointers) uintptr | |
| 522 | + | purego.RegisterLibFunc(&AddPointers, lib, "AddPointers") | |
| 523 | + | ||
| 524 | + | var v1, v2 int | |
| 525 | + | ptr1 := unsafe.Pointer(&v1) | |
| 526 | + | ptr2 := unsafe.Pointer(&v2) | |
| 527 | + | expected := uintptr(ptr1) + uintptr(ptr2) | |
| 528 | + | result := AddPointers(TwoPointers{ptr1, ptr2}) | |
| 529 | + | if result != expected { | |
| 530 | + | t.Fatalf("AddPointers returned %#x wanted %#x", result, expected) | |
| 531 | + | } | |
| 532 | + | } | |
| 502 | 533 | } | |
| 503 | 534 | ||
| 504 | 535 | func TestRegisterFunc_structReturns(t *testing.T) { | |
| @@ -757,6 +788,65 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 757 | 788 | t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected) | |
| 758 | 789 | } | |
| 759 | 790 | } | |
| 791 | + | { | |
| 792 | + | type Mixed5 struct { | |
| 793 | + | a *int64 | |
| 794 | + | b int32 | |
| 795 | + | c float32 | |
| 796 | + | d int32 | |
| 797 | + | } | |
| 798 | + | var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed5 | |
| 799 | + | purego.RegisterLibFunc(&ReturnMixed5, lib, "ReturnMixed5") | |
| 800 | + | ptr := new(int64) | |
| 801 | + | expected := Mixed5{ptr, 1, 7.2, 9} | |
| 802 | + | if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected { | |
| 803 | + | t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected) | |
| 804 | + | } | |
| 805 | + | runtime.KeepAlive(ptr) | |
| 806 | + | } | |
| 807 | + | { | |
| 808 | + | type Mixed5 struct { | |
| 809 | + | a *int64 | |
| 810 | + | b int32 | |
| 811 | + | c float32 | |
| 812 | + | d int32 | |
| 813 | + | } | |
| 814 | + | var IdentityMixed5 func(m Mixed5) Mixed5 | |
| 815 | + | purego.RegisterLibFunc(&IdentityMixed5, lib, "IdentityMixed5") | |
| 816 | + | ptr := new(int64) | |
| 817 | + | expected := Mixed5{ptr, 1, 7.2, 9} | |
| 818 | + | if ret := IdentityMixed5(expected); ret != expected { | |
| 819 | + | t.Fatalf("IdentityMixed5 returned %+v wanted %+v", ret, expected) | |
| 820 | + | } | |
| 821 | + | runtime.KeepAlive(ptr) | |
| 822 | + | } | |
| 823 | + | { | |
| 824 | + | type SmallBool struct { | |
| 825 | + | a bool | |
| 826 | + | b int32 | |
| 827 | + | c int64 | |
| 828 | + | } | |
| 829 | + | var ReturnSmallBool func(a bool, b int32, c int64) SmallBool | |
| 830 | + | purego.RegisterLibFunc(&ReturnSmallBool, lib, "ReturnSmallBool") | |
| 831 | + | expected := SmallBool{true, 42, 123456789} | |
| 832 | + | if ret := ReturnSmallBool(true, 42, 123456789); ret != expected { | |
| 833 | + | t.Fatalf("ReturnSmallBool returned %+v wanted %+v", ret, expected) | |
| 834 | + | } | |
| 835 | + | } | |
| 836 | + | { | |
| 837 | + | type LargeBool struct { | |
| 838 | + | a bool | |
| 839 | + | b int32 | |
| 840 | + | c int64 | |
| 841 | + | d int64 | |
| 842 | + | } | |
| 843 | + | var ReturnLargeBool func(a bool, b int32, c int64, d int64) LargeBool | |
| 844 | + | purego.RegisterLibFunc(&ReturnLargeBool, lib, "ReturnLargeBool") | |
| 845 | + | expected := LargeBool{false, -99, 987654321, 111222333444} | |
| 846 | + | if ret := ReturnLargeBool(false, -99, 987654321, 111222333444); ret != expected { | |
| 847 | + | t.Fatalf("ReturnLargeBool returned %+v wanted %+v", ret, expected) | |
| 848 | + | } | |
| 849 | + | } | |
| 760 | 850 | { | |
| 761 | 851 | type Ptr1 struct { | |
| 762 | 852 | a *int64 | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build darwin && (arm64 || amd64) | 4 | + | //go:build (darwin || linux) && (amd64 || arm64) |
| 5 | 5 | ||||
| 6 | package purego_test | 6 | package purego_test | ||
| 7 | 7 | ||||
| @@ -373,8 +373,8 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 373 | } | 373 | } | ||
| 374 | var Array4CharsFn func(chars Array4Chars) int32 | 374 | var Array4CharsFn func(chars Array4Chars) int32 | ||
| 375 | purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars") | 375 | purego.RegisterLibFunc(&Array4CharsFn, lib, "Array4Chars") | ||
| 376 | - | const expectedSum = 1 + 2 + 4 + 8 | 376 | + | const expectedSum = 10 + 20 + 30 + 63 |
| 377 | - | if ret := Array4CharsFn(Array4Chars{a: [...]int8{1, 2, 4, 8}}); ret != expectedSum { | 377 | + | if ret := Array4CharsFn(Array4Chars{a: [...]int8{10, 20, 30, 63}}); ret != expectedSum { |
| 378 | t.Fatalf("Array4CharsFn returned %d wanted %d", ret, expectedSum) | 378 | t.Fatalf("Array4CharsFn returned %d wanted %d", ret, expectedSum) | ||
| 379 | } | 379 | } | ||
| 380 | } | 380 | } | ||
| @@ -499,6 +499,37 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 499 | t.Fatalf("FourInt32s returned %d wanted %d", result, want) | 499 | t.Fatalf("FourInt32s returned %d wanted %d", result, want) | ||
| 500 | } | 500 | } | ||
| 501 | } | 501 | } | ||
| 502 | + | { | |||
| 503 | + | type PointerWrapper struct { | |||
| 504 | + | ctx unsafe.Pointer | |||
| 505 | + | } | |||
| 506 | + | var ExtractPointer func(wrapper PointerWrapper) uintptr | |||
| 507 | + | purego.RegisterLibFunc(&ExtractPointer, lib, "ExtractPointer") | |||
| 508 | + | ||||
| 509 | + | var v int | |||
| 510 | + | ptr := unsafe.Pointer(&v) | |||
| 511 | + | expected := uintptr(ptr) | |||
| 512 | + | result := ExtractPointer(PointerWrapper{ctx: ptr}) | |||
| 513 | + | if result != expected { | |||
| 514 | + | t.Fatalf("ExtractPointer returned %#x wanted %#x", result, expected) | |||
| 515 | + | } | |||
| 516 | + | } | |||
| 517 | + | { | |||
| 518 | + | type TwoPointers struct { | |||
| 519 | + | ptr1, ptr2 unsafe.Pointer | |||
| 520 | + | } | |||
| 521 | + | var AddPointers func(wrapper TwoPointers) uintptr | |||
| 522 | + | purego.RegisterLibFunc(&AddPointers, lib, "AddPointers") | |||
| 523 | + | ||||
| 524 | + | var v1, v2 int | |||
| 525 | + | ptr1 := unsafe.Pointer(&v1) | |||
| 526 | + | ptr2 := unsafe.Pointer(&v2) | |||
| 527 | + | expected := uintptr(ptr1) + uintptr(ptr2) | |||
| 528 | + | result := AddPointers(TwoPointers{ptr1, ptr2}) | |||
| 529 | + | if result != expected { | |||
| 530 | + | t.Fatalf("AddPointers returned %#x wanted %#x", result, expected) | |||
| 531 | + | } | |||
| 532 | + | } | |||
| 502 | } | 533 | } | ||
| 503 | 534 | ||||
| 504 | func TestRegisterFunc_structReturns(t *testing.T) { | 535 | func TestRegisterFunc_structReturns(t *testing.T) { | ||
| @@ -757,6 +788,65 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 757 | t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected) | 788 | t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected) | ||
| 758 | } | 789 | } | ||
| 759 | } | 790 | } | ||
| 791 | + | { | |||
| 792 | + | type Mixed5 struct { | |||
| 793 | + | a *int64 | |||
| 794 | + | b int32 | |||
| 795 | + | c float32 | |||
| 796 | + | d int32 | |||
| 797 | + | } | |||
| 798 | + | var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed5 | |||
| 799 | + | purego.RegisterLibFunc(&ReturnMixed5, lib, "ReturnMixed5") | |||
| 800 | + | ptr := new(int64) | |||
| 801 | + | expected := Mixed5{ptr, 1, 7.2, 9} | |||
| 802 | + | if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected { | |||
| 803 | + | t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected) | |||
| 804 | + | } | |||
| 805 | + | runtime.KeepAlive(ptr) | |||
| 806 | + | } | |||
| 807 | + | { | |||
| 808 | + | type Mixed5 struct { | |||
| 809 | + | a *int64 | |||
| 810 | + | b int32 | |||
| 811 | + | c float32 | |||
| 812 | + | d int32 | |||
| 813 | + | } | |||
| 814 | + | var IdentityMixed5 func(m Mixed5) Mixed5 | |||
| 815 | + | purego.RegisterLibFunc(&IdentityMixed5, lib, "IdentityMixed5") | |||
| 816 | + | ptr := new(int64) | |||
| 817 | + | expected := Mixed5{ptr, 1, 7.2, 9} | |||
| 818 | + | if ret := IdentityMixed5(expected); ret != expected { | |||
| 819 | + | t.Fatalf("IdentityMixed5 returned %+v wanted %+v", ret, expected) | |||
| 820 | + | } | |||
| 821 | + | runtime.KeepAlive(ptr) | |||
| 822 | + | } | |||
| 823 | + | { | |||
| 824 | + | type SmallBool struct { | |||
| 825 | + | a bool | |||
| 826 | + | b int32 | |||
| 827 | + | c int64 | |||
| 828 | + | } | |||
| 829 | + | var ReturnSmallBool func(a bool, b int32, c int64) SmallBool | |||
| 830 | + | purego.RegisterLibFunc(&ReturnSmallBool, lib, "ReturnSmallBool") | |||
| 831 | + | expected := SmallBool{true, 42, 123456789} | |||
| 832 | + | if ret := ReturnSmallBool(true, 42, 123456789); ret != expected { | |||
| 833 | + | t.Fatalf("ReturnSmallBool returned %+v wanted %+v", ret, expected) | |||
| 834 | + | } | |||
| 835 | + | } | |||
| 836 | + | { | |||
| 837 | + | type LargeBool struct { | |||
| 838 | + | a bool | |||
| 839 | + | b int32 | |||
| 840 | + | c int64 | |||
| 841 | + | d int64 | |||
| 842 | + | } | |||
| 843 | + | var ReturnLargeBool func(a bool, b int32, c int64, d int64) LargeBool | |||
| 844 | + | purego.RegisterLibFunc(&ReturnLargeBool, lib, "ReturnLargeBool") | |||
| 845 | + | expected := LargeBool{false, -99, 987654321, 111222333444} | |||
| 846 | + | if ret := ReturnLargeBool(false, -99, 987654321, 111222333444); ret != expected { | |||
| 847 | + | t.Fatalf("ReturnLargeBool returned %+v wanted %+v", ret, expected) | |||
| 848 | + | } | |||
| 849 | + | } | |||
| 760 | { | 850 | { | ||
| 761 | type Ptr1 struct { | 851 | type Ptr1 struct { | ||
| 762 | a *int64 | 852 | a *int64 | ||
M testdata/structtest/struct_test.c +18 -1
| @@ -280,7 +280,7 @@ unsigned int Array2UnsignedShorts(struct Array2UnsignedShort a) { | |||
| 280 | 280 | } | |
| 281 | 281 | ||
| 282 | 282 | struct Array4Chars { | |
| 283 | - | char a[4]; | |
| 283 | + | signed char a[4]; | |
| 284 | 284 | }; | |
| 285 | 285 | ||
| 286 | 286 | int Array4Chars(struct Array4Chars a) { | |
| @@ -374,3 +374,20 @@ struct FourInt32s { | |||
| 374 | 374 | int32_t FourInt32s(struct FourInt32s s) { | |
| 375 | 375 | return s.f0 + s.f1 + s.f2 + s.f3; | |
| 376 | 376 | } | |
| 377 | + | ||
| 378 | + | struct PointerWrapper { | |
| 379 | + | void* ctx; | |
| 380 | + | }; | |
| 381 | + | ||
| 382 | + | uintptr_t ExtractPointer(struct PointerWrapper wrapper) { | |
| 383 | + | return (uintptr_t)wrapper.ctx; | |
| 384 | + | } | |
| 385 | + | ||
| 386 | + | struct TwoPointers { | |
| 387 | + | void* ptr1; | |
| 388 | + | void* ptr2; | |
| 389 | + | }; | |
| 390 | + | ||
| 391 | + | uintptr_t AddPointers(struct TwoPointers wrapper) { | |
| 392 | + | return (uintptr_t)wrapper.ptr1 + (uintptr_t)wrapper.ptr2; | |
| 393 | + | } | |
| @@ -280,7 +280,7 @@ unsigned int Array2UnsignedShorts(struct Array2UnsignedShort a) { | |||||
| 280 | } | 280 | } | ||
| 281 | 281 | ||||
| 282 | struct Array4Chars { | 282 | struct Array4Chars { | ||
| 283 | - | char a[4]; | 283 | + | signed char a[4]; |
| 284 | }; | 284 | }; | ||
| 285 | 285 | ||||
| 286 | int Array4Chars(struct Array4Chars a) { | 286 | int Array4Chars(struct Array4Chars a) { | ||
| @@ -374,3 +374,20 @@ struct FourInt32s { | |||||
| 374 | int32_t FourInt32s(struct FourInt32s s) { | 374 | int32_t FourInt32s(struct FourInt32s s) { | ||
| 375 | return s.f0 + s.f1 + s.f2 + s.f3; | 375 | return s.f0 + s.f1 + s.f2 + s.f3; | ||
| 376 | } | 376 | } | ||
| 377 | + | ||||
| 378 | + | struct PointerWrapper { | |||
| 379 | + | void* ctx; | |||
| 380 | + | }; | |||
| 381 | + | ||||
| 382 | + | uintptr_t ExtractPointer(struct PointerWrapper wrapper) { | |||
| 383 | + | return (uintptr_t)wrapper.ctx; | |||
| 384 | + | } | |||
| 385 | + | ||||
| 386 | + | struct TwoPointers { | |||
| 387 | + | void* ptr1; | |||
| 388 | + | void* ptr2; | |||
| 389 | + | }; | |||
| 390 | + | ||||
| 391 | + | uintptr_t AddPointers(struct TwoPointers wrapper) { | |||
| 392 | + | return (uintptr_t)wrapper.ptr1 + (uintptr_t)wrapper.ptr2; | |||
| 393 | + | } | |||
M testdata/structtest/structreturn_test.c +39 -0
| @@ -227,6 +227,45 @@ struct Mixed4 ReturnMixed4(double a, uint32_t b, float c) { | |||
| 227 | 227 | return s; | |
| 228 | 228 | } | |
| 229 | 229 | ||
| 230 | + | struct Mixed5{ | |
| 231 | + | int64_t *a; | |
| 232 | + | int32_t b; | |
| 233 | + | float c; | |
| 234 | + | int32_t d; | |
| 235 | + | }; | |
| 236 | + | ||
| 237 | + | struct Mixed5 ReturnMixed5(int64_t *a, int32_t b, float c, int32_t d) { | |
| 238 | + | struct Mixed5 s = {a, b, c, d}; | |
| 239 | + | return s; | |
| 240 | + | } | |
| 241 | + | ||
| 242 | + | struct Mixed5 IdentityMixed5(struct Mixed5 m) { | |
| 243 | + | return m; | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | struct SmallBool{ | |
| 247 | + | _Bool a; | |
| 248 | + | int32_t b; | |
| 249 | + | int64_t c; | |
| 250 | + | }; | |
| 251 | + | ||
| 252 | + | struct SmallBool ReturnSmallBool(_Bool a, int32_t b, int64_t c) { | |
| 253 | + | struct SmallBool s = {a, b, c}; | |
| 254 | + | return s; | |
| 255 | + | } | |
| 256 | + | ||
| 257 | + | struct LargeBool{ | |
| 258 | + | _Bool a; | |
| 259 | + | int32_t b; | |
| 260 | + | int64_t c; | |
| 261 | + | int64_t d; | |
| 262 | + | }; | |
| 263 | + | ||
| 264 | + | struct LargeBool ReturnLargeBool(_Bool a, int32_t b, int64_t c, int64_t d) { | |
| 265 | + | struct LargeBool s = {a, b, c, d}; | |
| 266 | + | return s; | |
| 267 | + | } | |
| 268 | + | ||
| 230 | 269 | struct Ptr1{ | |
| 231 | 270 | int64_t *a; | |
| 232 | 271 | void *b; | |
| @@ -227,6 +227,45 @@ struct Mixed4 ReturnMixed4(double a, uint32_t b, float c) { | |||||
| 227 | return s; | 227 | return s; | ||
| 228 | } | 228 | } | ||
| 229 | 229 | ||||
| 230 | + | struct Mixed5{ | |||
| 231 | + | int64_t *a; | |||
| 232 | + | int32_t b; | |||
| 233 | + | float c; | |||
| 234 | + | int32_t d; | |||
| 235 | + | }; | |||
| 236 | + | ||||
| 237 | + | struct Mixed5 ReturnMixed5(int64_t *a, int32_t b, float c, int32_t d) { | |||
| 238 | + | struct Mixed5 s = {a, b, c, d}; | |||
| 239 | + | return s; | |||
| 240 | + | } | |||
| 241 | + | ||||
| 242 | + | struct Mixed5 IdentityMixed5(struct Mixed5 m) { | |||
| 243 | + | return m; | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | struct SmallBool{ | |||
| 247 | + | _Bool a; | |||
| 248 | + | int32_t b; | |||
| 249 | + | int64_t c; | |||
| 250 | + | }; | |||
| 251 | + | ||||
| 252 | + | struct SmallBool ReturnSmallBool(_Bool a, int32_t b, int64_t c) { | |||
| 253 | + | struct SmallBool s = {a, b, c}; | |||
| 254 | + | return s; | |||
| 255 | + | } | |||
| 256 | + | ||||
| 257 | + | struct LargeBool{ | |||
| 258 | + | _Bool a; | |||
| 259 | + | int32_t b; | |||
| 260 | + | int64_t c; | |||
| 261 | + | int64_t d; | |||
| 262 | + | }; | |||
| 263 | + | ||||
| 264 | + | struct LargeBool ReturnLargeBool(_Bool a, int32_t b, int64_t c, int64_t d) { | |||
| 265 | + | struct LargeBool s = {a, b, c, d}; | |||
| 266 | + | return s; | |||
| 267 | + | } | |||
| 268 | + | ||||
| 230 | struct Ptr1{ | 269 | struct Ptr1{ | ||
| 231 | int64_t *a; | 270 | int64_t *a; | ||
| 232 | void *b; | 271 | void *b; | ||