purego: support structs on ppc64le (#475)
Pass and return structs by value when calling C functions under the ELFv2 ABI: a homogeneous floating-point aggregate is passed and returned in the floating-point registers whatever its size, any other aggregate occupies successive doublewords in the general-purpose registers and the parameter save area, and a non-homogeneous aggregate larger than two doublewords is returned through a caller-allocated buffer. Scalar float32 arguments are passed in double-precision format, matching the FMOVD-based convention already used for float32 struct members and float returns; reading float32 callback arguments does the inverse conversion. Widen structReturnInMemory to take the return type rather than its size, so ppc64le can keep a homogeneous floating-point aggregate in registers regardless of size while every other architecture keeps its size-only rule. Struct arguments and returns in callbacks remain unsupported, as on the other non-amd64/arm64 architectures. Aggregates that need more than eight floating-point argument registers, and functions taking mixed scalar floating-point and integer arguments, are not yet handled; the latter needs the ELFv2 general-purpose register shadowing that purego does not model for scalar arguments. Updates #474 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- commit
- b1a632f0cddd68b6258bc67f30e6fa40de4a82e8
- author
- Hajime Hoshi · · 2026-07-15 03:35 UTC
- committer
- GitHub · · 2026-07-15 03:35 UTC
- parent
- ca3432a
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown - refs
- tag: v0.11.0-alpha.7
15 files · +278 -210
M README.md +1 -1
| @@ -42,7 +42,7 @@ Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs o | |||
| 42 | 42 | ||
| 43 | 43 | - **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup> | |
| 44 | 44 | - **FreeBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup> | |
| 45 | - | - **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>2</sup>, ppc64le<sup>3</sup>, riscv64<sup>3</sup>, s390x<sup>3, 5</sup> | |
| 45 | + | - **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>2</sup>, ppc64le<sup>2</sup>, riscv64<sup>3</sup>, s390x<sup>3, 5</sup> | |
| 46 | 46 | - **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup> | |
| 47 | 47 | - **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup> | |
| 48 | 48 | ||
| @@ -42,7 +42,7 @@ Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs o | |||||
| 42 | 42 | ||||
| 43 | - **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup> | 43 | - **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup> | ||
| 44 | - **FreeBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup> | 44 | - **FreeBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup> | ||
| 45 | - | - **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>2</sup>, ppc64le<sup>3</sup>, riscv64<sup>3</sup>, s390x<sup>3, 5</sup> | 45 | + | - **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>2</sup>, ppc64le<sup>2</sup>, riscv64<sup>3</sup>, s390x<sup>3, 5</sup> |
| 46 | - **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup> | 46 | - **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup> | ||
| 47 | - **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup> | 47 | - **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup> | ||
| 48 | 48 | ||||
M export_test.go +4 -2
| @@ -5,10 +5,12 @@ | |||
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| 8 | + | import "reflect" | |
| 9 | + | ||
| 8 | 10 | // MaxArgs re-exports maxArgs for external tests. | |
| 9 | 11 | const MaxArgs = maxArgs | |
| 10 | 12 | ||
| 11 | 13 | // StructReturnInMemory re-exports structReturnInMemory for external tests. | |
| 12 | - | func StructReturnInMemory(size uintptr) bool { | |
| 13 | - | return structReturnInMemory(size) | |
| 14 | + | func StructReturnInMemory(outType reflect.Type) bool { | |
| 15 | + | return structReturnInMemory(outType) | |
| 14 | 16 | } | |
| @@ -5,10 +5,12 @@ | |||||
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| 8 | + | import "reflect" | |||
| 9 | + | ||||
| 8 | // MaxArgs re-exports maxArgs for external tests. | 10 | // MaxArgs re-exports maxArgs for external tests. | ||
| 9 | const MaxArgs = maxArgs | 11 | const MaxArgs = maxArgs | ||
| 10 | 12 | ||||
| 11 | // StructReturnInMemory re-exports structReturnInMemory for external tests. | 13 | // StructReturnInMemory re-exports structReturnInMemory for external tests. | ||
| 12 | - | func StructReturnInMemory(size uintptr) bool { | 14 | + | func StructReturnInMemory(outType reflect.Type) bool { |
| 13 | - | return structReturnInMemory(size) | 15 | + | return structReturnInMemory(outType) |
| 14 | } | 16 | } | ||
M func.go +11 -7
| @@ -199,7 +199,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 199 | 199 | ensureStructSupported() | |
| 200 | 200 | outType := ty.Out(0) | |
| 201 | 201 | checkStructFieldsSupported(outType) | |
| 202 | - | if structReturnInMemory(outType.Size()) { | |
| 202 | + | if structReturnInMemory(outType) { | |
| 203 | 203 | // A struct returned in memory is allocated by the caller and its | |
| 204 | 204 | // pointer is passed as a hidden first integer argument. When the | |
| 205 | 205 | // integer registers are already full, prepending it spills a | |
| @@ -292,7 +292,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 292 | 292 | var arm64_r8 uintptr | |
| 293 | 293 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 294 | 294 | outType := ty.Out(0) | |
| 295 | - | if structReturnInMemory(outType.Size()) { | |
| 295 | + | if structReturnInMemory(outType) { | |
| 296 | 296 | // The caller allocates the return value and passes its pointer | |
| 297 | 297 | // as a hidden first integer argument. | |
| 298 | 298 | val := reflect.New(outType) | |
| @@ -431,10 +431,14 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||
| 431 | 431 | addInt(0) | |
| 432 | 432 | } | |
| 433 | 433 | case reflect.Float32: | |
| 434 | - | // On S390X big-endian, float32 goes in upper 32 bits of 64-bit FP register | |
| 435 | - | if runtime.GOARCH == "s390x" { | |
| 434 | + | switch runtime.GOARCH { | |
| 435 | + | case "ppc64le": | |
| 436 | + | // A single-precision argument occupies a floating-point register in double format on Power. | |
| 437 | + | addFloat(uintptr(math.Float64bits(v.Float()))) | |
| 438 | + | case "s390x": | |
| 439 | + | // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register. | |
| 436 | 440 | addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) | |
| 437 | - | } else { | |
| 441 | + | default: | |
| 438 | 442 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | |
| 439 | 443 | } | |
| 440 | 444 | case reflect.Float64: | |
| @@ -510,9 +514,9 @@ func checkStructFieldsSupported(ty reflect.Type) { | |||
| 510 | 514 | // a C function is unsupported on the current platform. | |
| 511 | 515 | func ensureStructSupported() { | |
| 512 | 516 | switch runtime.GOARCH { | |
| 513 | - | case "amd64", "arm64", "loong64": | |
| 517 | + | case "amd64", "arm64", "loong64", "ppc64le": | |
| 514 | 518 | default: | |
| 515 | - | panic("purego: struct arguments/returns are only supported on amd64, arm64, and loong64") | |
| 519 | + | panic("purego: struct arguments/returns are only supported on amd64, arm64, loong64, and ppc64le") | |
| 516 | 520 | } | |
| 517 | 521 | switch runtime.GOOS { | |
| 518 | 522 | case "android", "darwin", "ios", "linux", "windows": | |
| @@ -199,7 +199,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 199 | ensureStructSupported() | 199 | ensureStructSupported() | ||
| 200 | outType := ty.Out(0) | 200 | outType := ty.Out(0) | ||
| 201 | checkStructFieldsSupported(outType) | 201 | checkStructFieldsSupported(outType) | ||
| 202 | - | if structReturnInMemory(outType.Size()) { | 202 | + | if structReturnInMemory(outType) { |
| 203 | // A struct returned in memory is allocated by the caller and its | 203 | // A struct returned in memory is allocated by the caller and its | ||
| 204 | // pointer is passed as a hidden first integer argument. When the | 204 | // pointer is passed as a hidden first integer argument. When the | ||
| 205 | // integer registers are already full, prepending it spills a | 205 | // integer registers are already full, prepending it spills a | ||
| @@ -292,7 +292,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 292 | var arm64_r8 uintptr | 292 | var arm64_r8 uintptr | ||
| 293 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | 293 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | ||
| 294 | outType := ty.Out(0) | 294 | outType := ty.Out(0) | ||
| 295 | - | if structReturnInMemory(outType.Size()) { | 295 | + | if structReturnInMemory(outType) { |
| 296 | // The caller allocates the return value and passes its pointer | 296 | // The caller allocates the return value and passes its pointer | ||
| 297 | // as a hidden first integer argument. | 297 | // as a hidden first integer argument. | ||
| 298 | val := reflect.New(outType) | 298 | val := reflect.New(outType) | ||
| @@ -431,10 +431,14 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||||
| 431 | addInt(0) | 431 | addInt(0) | ||
| 432 | } | 432 | } | ||
| 433 | case reflect.Float32: | 433 | case reflect.Float32: | ||
| 434 | - | // On S390X big-endian, float32 goes in upper 32 bits of 64-bit FP register | 434 | + | switch runtime.GOARCH { |
| 435 | - | if runtime.GOARCH == "s390x" { | 435 | + | case "ppc64le": |
| 436 | + | // A single-precision argument occupies a floating-point register in double format on Power. | |||
| 437 | + | addFloat(uintptr(math.Float64bits(v.Float()))) | |||
| 438 | + | case "s390x": | |||
| 439 | + | // S390X big-endian: float32 goes in the upper 32 bits of the 64-bit FP register. | |||
| 436 | addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) | 440 | addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) | ||
| 437 | - | } else { | 441 | + | default: |
| 438 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | 442 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | ||
| 439 | } | 443 | } | ||
| 440 | case reflect.Float64: | 444 | case reflect.Float64: | ||
| @@ -510,9 +514,9 @@ func checkStructFieldsSupported(ty reflect.Type) { | |||||
| 510 | // a C function is unsupported on the current platform. | 514 | // a C function is unsupported on the current platform. | ||
| 511 | func ensureStructSupported() { | 515 | func ensureStructSupported() { | ||
| 512 | switch runtime.GOARCH { | 516 | switch runtime.GOARCH { | ||
| 513 | - | case "amd64", "arm64", "loong64": | 517 | + | case "amd64", "arm64", "loong64", "ppc64le": |
| 514 | default: | 518 | default: | ||
| 515 | - | panic("purego: struct arguments/returns are only supported on amd64, arm64, and loong64") | 519 | + | panic("purego: struct arguments/returns are only supported on amd64, arm64, loong64, and ppc64le") |
| 516 | } | 520 | } | ||
| 517 | switch runtime.GOOS { | 521 | switch runtime.GOOS { | ||
| 518 | case "android", "darwin", "ios", "linux", "windows": | 522 | case "android", "darwin", "ios", "linux", "windows": | ||
M func_test.go +1 -1
| @@ -604,7 +604,7 @@ func TestABI_StructReturnHiddenPointer(t *testing.T) { | |||
| 604 | 604 | // than sysargs holds and RegisterFunc must reject the registration. | |
| 605 | 605 | type bigStruct struct{ A, B, C uint64 } // larger than two eightbytes | |
| 606 | 606 | ||
| 607 | - | if !purego.StructReturnInMemory(reflect.TypeFor[bigStruct]().Size()) { | |
| 607 | + | if !purego.StructReturnInMemory(reflect.TypeFor[bigStruct]()) { | |
| 608 | 608 | t.Skipf("GOARCH=%s does not return large structs via a hidden integer argument", runtime.GOARCH) | |
| 609 | 609 | } | |
| 610 | 610 | ||
| @@ -604,7 +604,7 @@ func TestABI_StructReturnHiddenPointer(t *testing.T) { | |||||
| 604 | // than sysargs holds and RegisterFunc must reject the registration. | 604 | // than sysargs holds and RegisterFunc must reject the registration. | ||
| 605 | type bigStruct struct{ A, B, C uint64 } // larger than two eightbytes | 605 | type bigStruct struct{ A, B, C uint64 } // larger than two eightbytes | ||
| 606 | 606 | ||||
| 607 | - | if !purego.StructReturnInMemory(reflect.TypeFor[bigStruct]().Size()) { | 607 | + | if !purego.StructReturnInMemory(reflect.TypeFor[bigStruct]()) { |
| 608 | t.Skipf("GOARCH=%s does not return large structs via a hidden integer argument", runtime.GOARCH) | 608 | t.Skipf("GOARCH=%s does not return large structs via a hidden integer argument", runtime.GOARCH) | ||
| 609 | } | 609 | } | ||
| 610 | 610 | ||||
M struct_amd64.go +3 -2
| @@ -13,7 +13,8 @@ import ( | |||
| 13 | 13 | // structReturnInMemory reports whether a struct return value of the given size | |
| 14 | 14 | // is returned through a caller-allocated hidden pointer passed as the first | |
| 15 | 15 | // integer argument (true) rather than in registers (false). | |
| 16 | - | func structReturnInMemory(size uintptr) bool { | |
| 16 | + | func structReturnInMemory(outType reflect.Type) bool { | |
| 17 | + | size := outType.Size() | |
| 17 | 18 | if size == 0 { | |
| 18 | 19 | return false | |
| 19 | 20 | } | |
| @@ -38,7 +39,7 @@ func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | |||
| 38 | 39 | switch { | |
| 39 | 40 | case outSize == 0: | |
| 40 | 41 | return reflect.New(outType).Elem() | |
| 41 | - | case structReturnInMemory(outSize): | |
| 42 | + | case structReturnInMemory(outType): | |
| 42 | 43 | // Returned through the caller-allocated hidden pointer, which the | |
| 43 | 44 | // callee also returns in RAX. | |
| 44 | 45 | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | |
| @@ -13,7 +13,8 @@ import ( | |||||
| 13 | // structReturnInMemory reports whether a struct return value of the given size | 13 | // structReturnInMemory reports whether a struct return value of the given size | ||
| 14 | // is returned through a caller-allocated hidden pointer passed as the first | 14 | // is returned through a caller-allocated hidden pointer passed as the first | ||
| 15 | // integer argument (true) rather than in registers (false). | 15 | // integer argument (true) rather than in registers (false). | ||
| 16 | - | func structReturnInMemory(size uintptr) bool { | 16 | + | func structReturnInMemory(outType reflect.Type) bool { |
| 17 | + | size := outType.Size() | |||
| 17 | if size == 0 { | 18 | if size == 0 { | ||
| 18 | return false | 19 | return false | ||
| 19 | } | 20 | } | ||
| @@ -38,7 +39,7 @@ func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | |||||
| 38 | switch { | 39 | switch { | ||
| 39 | case outSize == 0: | 40 | case outSize == 0: | ||
| 40 | return reflect.New(outType).Elem() | 41 | return reflect.New(outType).Elem() | ||
| 41 | - | case structReturnInMemory(outSize): | 42 | + | case structReturnInMemory(outType): |
| 42 | // Returned through the caller-allocated hidden pointer, which the | 43 | // Returned through the caller-allocated hidden pointer, which the | ||
| 43 | // callee also returns in RAX. | 44 | // callee also returns in RAX. | ||
| 44 | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | 45 | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | ||
M struct_arm.go +1 -1
| @@ -31,7 +31,7 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||
| 31 | 31 | // structReturnInMemory always reports false on arm: an indirect struct return | |
| 32 | 32 | // is recovered from the pointer the callee leaves in a1 (see getStruct) rather | |
| 33 | 33 | // than through a caller-allocated hidden first argument. | |
| 34 | - | func structReturnInMemory(size uintptr) bool { | |
| 34 | + | func structReturnInMemory(reflect.Type) bool { | |
| 35 | 35 | return false | |
| 36 | 36 | } | |
| 37 | 37 | ||
| @@ -31,7 +31,7 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||||
| 31 | // structReturnInMemory always reports false on arm: an indirect struct return | 31 | // structReturnInMemory always reports false on arm: an indirect struct return | ||
| 32 | // is recovered from the pointer the callee leaves in a1 (see getStruct) rather | 32 | // is recovered from the pointer the callee leaves in a1 (see getStruct) rather | ||
| 33 | // than through a caller-allocated hidden first argument. | 33 | // than through a caller-allocated hidden first argument. | ||
| 34 | - | func structReturnInMemory(size uintptr) bool { | 34 | + | func structReturnInMemory(reflect.Type) bool { |
| 35 | return false | 35 | return false | ||
| 36 | } | 36 | } | ||
| 37 | 37 | ||||
M struct_arm64.go +1 -1
| @@ -17,7 +17,7 @@ import ( | |||
| 17 | 17 | // structReturnInMemory always reports false on arm64: a struct returned in | |
| 18 | 18 | // memory is passed through the dedicated indirect result register (R8), not as | |
| 19 | 19 | // an ordinary integer argument, so it is handled separately. | |
| 20 | - | func structReturnInMemory(size uintptr) bool { | |
| 20 | + | func structReturnInMemory(reflect.Type) bool { | |
| 21 | 21 | return false | |
| 22 | 22 | } | |
| 23 | 23 | ||
| @@ -17,7 +17,7 @@ import ( | |||||
| 17 | // structReturnInMemory always reports false on arm64: a struct returned in | 17 | // structReturnInMemory always reports false on arm64: a struct returned in | ||
| 18 | // memory is passed through the dedicated indirect result register (R8), not as | 18 | // memory is passed through the dedicated indirect result register (R8), not as | ||
| 19 | // an ordinary integer argument, so it is handled separately. | 19 | // an ordinary integer argument, so it is handled separately. | ||
| 20 | - | func structReturnInMemory(size uintptr) bool { | 20 | + | func structReturnInMemory(reflect.Type) bool { |
| 21 | return false | 21 | return false | ||
| 22 | } | 22 | } | ||
| 23 | 23 | ||||
M struct_loong64.go +5 -5
| @@ -72,11 +72,11 @@ func loong64Classify(t reflect.Type) (leaves []loong64Leaf, useFP bool) { | |||
| 72 | 72 | } | |
| 73 | 73 | } | |
| 74 | 74 | ||
| 75 | - | // structReturnInMemory reports whether a struct return value of the given size | |
| 76 | - | // is returned through a caller-allocated hidden pointer passed as the first | |
| 77 | - | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |
| 78 | - | func structReturnInMemory(size uintptr) bool { | |
| 79 | - | return size > maxRegAllocStructSize | |
| 75 | + | // structReturnInMemory reports whether a struct return value is returned through | |
| 76 | + | // a caller-allocated hidden pointer passed as the first integer argument. | |
| 77 | + | // Aggregates larger than two eightbytes are returned in memory. | |
| 78 | + | func structReturnInMemory(outType reflect.Type) bool { | |
| 79 | + | return outType.Size() > maxRegAllocStructSize | |
| 80 | 80 | } | |
| 81 | 81 | ||
| 82 | 82 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| @@ -72,11 +72,11 @@ func loong64Classify(t reflect.Type) (leaves []loong64Leaf, useFP bool) { | |||||
| 72 | } | 72 | } | ||
| 73 | } | 73 | } | ||
| 74 | 74 | ||||
| 75 | - | // structReturnInMemory reports whether a struct return value of the given size | 75 | + | // structReturnInMemory reports whether a struct return value is returned through |
| 76 | - | // is returned through a caller-allocated hidden pointer passed as the first | 76 | + | // a caller-allocated hidden pointer passed as the first integer argument. |
| 77 | - | // integer argument. Aggregates larger than two eightbytes are returned in memory. | 77 | + | // Aggregates larger than two eightbytes are returned in memory. |
| 78 | - | func structReturnInMemory(size uintptr) bool { | 78 | + | func structReturnInMemory(outType reflect.Type) bool { |
| 79 | - | return size > maxRegAllocStructSize | 79 | + | return outType.Size() > maxRegAllocStructSize |
| 80 | } | 80 | } | ||
| 81 | 81 | ||||
| 82 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | 82 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | ||
M struct_other.go +4 -4
| @@ -22,10 +22,10 @@ func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |||
| 22 | 22 | panic("purego: struct returns are not supported on this architecture") | |
| 23 | 23 | } | |
| 24 | 24 | ||
| 25 | - | // structReturnInMemory reports whether a struct return value of the given size | |
| 26 | - | // is returned through a caller-allocated hidden pointer. Structs are unsupported | |
| 27 | - | // on this architecture, so it always reports false. | |
| 28 | - | func structReturnInMemory(size uintptr) bool { | |
| 25 | + | // structReturnInMemory reports whether a struct return value is returned through | |
| 26 | + | // a caller-allocated hidden pointer. Structs are unsupported on this | |
| 27 | + | // architecture, so it always reports false. | |
| 28 | + | func structReturnInMemory(reflect.Type) bool { | |
| 29 | 29 | return false | |
| 30 | 30 | } | |
| 31 | 31 | ||
| @@ -22,10 +22,10 @@ func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |||||
| 22 | panic("purego: struct returns are not supported on this architecture") | 22 | panic("purego: struct returns are not supported on this architecture") | ||
| 23 | } | 23 | } | ||
| 24 | 24 | ||||
| 25 | - | // structReturnInMemory reports whether a struct return value of the given size | 25 | + | // structReturnInMemory reports whether a struct return value is returned through |
| 26 | - | // is returned through a caller-allocated hidden pointer. Structs are unsupported | 26 | + | // a caller-allocated hidden pointer. Structs are unsupported on this |
| 27 | - | // on this architecture, so it always reports false. | 27 | + | // architecture, so it always reports false. |
| 28 | - | func structReturnInMemory(size uintptr) bool { | 28 | + | func structReturnInMemory(reflect.Type) bool { |
| 29 | return false | 29 | return false | ||
| 30 | } | 30 | } | ||
| 31 | 31 | ||||
M struct_ppc64le.go +117 -95
| @@ -4,84 +4,76 @@ | |||
| 4 | 4 | package purego | |
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | + | "math" | |
| 7 | 8 | "reflect" | |
| 8 | 9 | "unsafe" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| 11 | - | // structReturnInMemory reports whether a struct return value of the given size | |
| 12 | - | // is returned through a caller-allocated hidden pointer passed as the first | |
| 13 | - | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |
| 14 | - | func structReturnInMemory(size uintptr) bool { | |
| 15 | - | return size > maxRegAllocStructSize | |
| 12 | + | // ppc64leLeaf is a scalar member of a flattened aggregate at byte offset offset. | |
| 13 | + | type ppc64leLeaf struct { | |
| 14 | + | kind reflect.Kind | |
| 15 | + | offset uintptr | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | - | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| 19 | - | outSize := outType.Size() | |
| 20 | - | ||
| 21 | - | switch { | |
| 22 | - | case outSize == 0: | |
| 23 | - | return reflect.New(outType).Elem() | |
| 24 | - | ||
| 25 | - | case outSize <= 16: | |
| 26 | - | // Reconstruct from registers by copying raw bytes | |
| 27 | - | var buf [16]byte | |
| 28 | - | ||
| 29 | - | // Integer registers | |
| 30 | - | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.a1 | |
| 31 | - | if outSize > 8 { | |
| 32 | - | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.a2 | |
| 33 | - | } | |
| 34 | - | ||
| 35 | - | // Homogeneous float aggregates override integer regs | |
| 36 | - | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 37 | - | if outType.Field(0).Type.Kind() == reflect.Float32 { | |
| 38 | - | // float32 values in FP regs | |
| 39 | - | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | |
| 40 | - | for i := 0; i < numFields; i++ { | |
| 41 | - | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) | |
| 42 | - | } | |
| 43 | - | } else { | |
| 44 | - | // float64: whole register value is valid | |
| 45 | - | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.f1 | |
| 46 | - | if outSize > 8 { | |
| 47 | - | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.f2 | |
| 48 | - | } | |
| 18 | + | // ppc64leFlatten appends the scalar leaves of t to leaves, expanding nested | |
| 19 | + | // structs and arrays. | |
| 20 | + | func ppc64leFlatten(t reflect.Type, base uintptr, leaves *[]ppc64leLeaf) { | |
| 21 | + | switch t.Kind() { | |
| 22 | + | case reflect.Struct: | |
| 23 | + | for i := range t.NumField() { | |
| 24 | + | f := t.Field(i) | |
| 25 | + | if f.Name == "_" { | |
| 26 | + | // Blank fields are padding, not counted members. | |
| 27 | + | continue | |
| 49 | 28 | } | |
| 29 | + | ppc64leFlatten(f.Type, base+f.Offset, leaves) | |
| 30 | + | } | |
| 31 | + | case reflect.Array: | |
| 32 | + | elem := t.Elem() | |
| 33 | + | for i := range t.Len() { | |
| 34 | + | ppc64leFlatten(elem, base+uintptr(i)*elem.Size(), leaves) | |
| 50 | 35 | } | |
| 51 | - | ||
| 52 | - | return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem() | |
| 53 | - | ||
| 54 | 36 | default: | |
| 55 | - | // Returned indirectly via pointer in a1 | |
| 56 | - | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)) | |
| 57 | - | return reflect.NewAt(outType, ptr).Elem() | |
| 37 | + | *leaves = append(*leaves, ppc64leLeaf{kind: t.Kind(), offset: base}) | |
| 58 | 38 | } | |
| 59 | 39 | } | |
| 60 | 40 | ||
| 61 | - | func addStruct( | |
| 62 | - | v reflect.Value, | |
| 63 | - | numInts, numFloats, numStack *int, | |
| 64 | - | addInt, addFloat, addStack func(uintptr), | |
| 65 | - | keepAlive []any, | |
| 66 | - | ) []any { | |
| 67 | - | size := v.Type().Size() | |
| 68 | - | if size == 0 { | |
| 69 | - | return keepAlive | |
| 41 | + | // ppc64leClassifyHFA reports whether t is a homogeneous floating-point | |
| 42 | + | // aggregate: one to eight members all of the same floating-point type. | |
| 43 | + | func ppc64leClassifyHFA(t reflect.Type) (leaves []ppc64leLeaf, isHFA bool) { | |
| 44 | + | ppc64leFlatten(t, 0, &leaves) | |
| 45 | + | if len(leaves) == 0 || len(leaves) > 8 { | |
| 46 | + | return leaves, false | |
| 70 | 47 | } | |
| 71 | - | ||
| 72 | - | if size <= 16 { | |
| 73 | - | return placeSmallAggregatePPC64LE(v, addFloat, addInt, keepAlive) | |
| 48 | + | first := leaves[0].kind | |
| 49 | + | if first != reflect.Float32 && first != reflect.Float64 { | |
| 50 | + | return leaves, false | |
| 51 | + | } | |
| 52 | + | for _, l := range leaves { | |
| 53 | + | if l.kind != first { | |
| 54 | + | return leaves, false | |
| 55 | + | } | |
| 74 | 56 | } | |
| 57 | + | return leaves, true | |
| 58 | + | } | |
| 75 | 59 | ||
| 76 | - | return placeStack(v, keepAlive, addInt) | |
| 60 | + | // structReturnInMemory reports whether a struct return value is returned through | |
| 61 | + | // a caller-allocated hidden pointer rather than in registers. A homogeneous | |
| 62 | + | // floating-point aggregate is returned in the floating-point registers whatever | |
| 63 | + | // its size. | |
| 64 | + | func structReturnInMemory(outType reflect.Type) bool { | |
| 65 | + | if outType.Size() <= maxRegAllocStructSize { | |
| 66 | + | return false | |
| 67 | + | } | |
| 68 | + | _, isHFA := ppc64leClassifyHFA(outType) | |
| 69 | + | return !isHFA | |
| 77 | 70 | } | |
| 78 | 71 | ||
| 79 | - | func placeSmallAggregatePPC64LE( | |
| 80 | - | v reflect.Value, | |
| 81 | - | addFloat, addInt func(uintptr), | |
| 82 | - | keepAlive []any, | |
| 83 | - | ) []any { | |
| 72 | + | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | |
| 84 | 73 | size := v.Type().Size() | |
| 74 | + | if size == 0 { | |
| 75 | + | return keepAlive | |
| 76 | + | } | |
| 85 | 77 | ||
| 86 | 78 | var ptr unsafe.Pointer | |
| 87 | 79 | if v.CanAddr() { | |
| @@ -93,58 +85,88 @@ func placeSmallAggregatePPC64LE( | |||
| 93 | 85 | keepAlive = append(keepAlive, tmp.Interface()) | |
| 94 | 86 | } | |
| 95 | 87 | ||
| 96 | - | var buf [16]byte | |
| 97 | - | src := unsafe.Slice((*byte)(ptr), size) | |
| 98 | - | copy(buf[:], src) | |
| 99 | - | ||
| 100 | - | w0 := *(*uintptr)(unsafe.Pointer(&buf[0])) | |
| 101 | - | w1 := uintptr(0) | |
| 102 | - | if size > 8 { | |
| 103 | - | w1 = *(*uintptr)(unsafe.Pointer(&buf[8])) | |
| 88 | + | // An HFA passes each member in its own floating-point register. | |
| 89 | + | if leaves, isHFA := ppc64leClassifyHFA(v.Type()); isHFA { | |
| 90 | + | for _, l := range leaves { | |
| 91 | + | src := unsafe.Add(ptr, l.offset) | |
| 92 | + | if l.kind == reflect.Float32 { | |
| 93 | + | // Single precision occupies the register in double format. | |
| 94 | + | addFloat(uintptr(math.Float64bits(float64(*(*float32)(src))))) | |
| 95 | + | } else { | |
| 96 | + | addFloat(uintptr(*(*uint64)(src))) | |
| 97 | + | } | |
| 98 | + | } | |
| 104 | 99 | } | |
| 105 | 100 | ||
| 106 | - | if isFloats, _ := isAllSameFloat(v.Type()); isFloats { | |
| 107 | - | addFloat(w0) | |
| 108 | - | if size > 8 { | |
| 109 | - | addFloat(w1) | |
| 110 | - | } | |
| 111 | - | } else { | |
| 112 | - | addInt(w0) | |
| 113 | - | if size > 8 { | |
| 114 | - | addInt(w1) | |
| 101 | + | // Every aggregate also occupies its size rounded up to doublewords in the | |
| 102 | + | // GPRs r3-r10 / parameter save area: shadow (skipped) for an HFA, the value | |
| 103 | + | // otherwise. | |
| 104 | + | for off := uintptr(0); off < size; off += 8 { | |
| 105 | + | if remaining := size - off; remaining >= 8 { | |
| 106 | + | addInt(*(*uintptr)(unsafe.Add(ptr, off))) | |
| 107 | + | } else { | |
| 108 | + | var word [8]byte | |
| 109 | + | copy(word[:], unsafe.Slice((*byte)(unsafe.Add(ptr, off)), int(remaining))) | |
| 110 | + | addInt(*(*uintptr)(unsafe.Pointer(&word[0]))) | |
| 115 | 111 | } | |
| 116 | 112 | } | |
| 117 | 113 | ||
| 118 | 114 | return keepAlive | |
| 119 | 115 | } | |
| 120 | 116 | ||
| 121 | - | // placeStack is a fallback for structs that are too large to fit in registers | |
| 122 | - | func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | |
| 123 | - | if v.CanAddr() { | |
| 124 | - | addInt(v.Addr().Pointer()) | |
| 125 | - | return keepAlive | |
| 117 | + | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| 118 | + | if outType.Size() == 0 { | |
| 119 | + | return reflect.New(outType).Elem() | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | // An HFA returns each member in its own floating-point register. | |
| 123 | + | if leaves, isHFA := ppc64leClassifyHFA(outType); isHFA { | |
| 124 | + | floatRegs := [8]uintptr{ | |
| 125 | + | syscall.f1, syscall.f2, syscall.f3, syscall.f4, | |
| 126 | + | syscall.f5, syscall.f6, syscall.f7, syscall.f8, | |
| 127 | + | } | |
| 128 | + | ret := reflect.New(outType) | |
| 129 | + | base := ret.UnsafePointer() | |
| 130 | + | for i, l := range leaves { | |
| 131 | + | dst := unsafe.Add(base, l.offset) | |
| 132 | + | if l.kind == reflect.Float32 { | |
| 133 | + | // The register holds the value in double format. | |
| 134 | + | *(*float32)(dst) = float32(math.Float64frombits(uint64(floatRegs[i]))) | |
| 135 | + | } else { | |
| 136 | + | *(*uint64)(dst) = uint64(floatRegs[i]) | |
| 137 | + | } | |
| 138 | + | } | |
| 139 | + | return ret.Elem() | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | if outType.Size() > 16 { | |
| 143 | + | // Returned via a caller-allocated buffer whose pointer comes back in r3. | |
| 144 | + | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | // Otherwise returned in r3 and r4. | |
| 148 | + | var buf [16]byte | |
| 149 | + | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.a1 | |
| 150 | + | if outType.Size() > 8 { | |
| 151 | + | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.a2 | |
| 126 | 152 | } | |
| 127 | - | ptr := reflect.New(v.Type()) | |
| 128 | - | ptr.Elem().Set(v) | |
| 129 | - | addInt(ptr.Pointer()) | |
| 130 | - | return append(keepAlive, ptr.Interface()) | |
| 153 | + | return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem() | |
| 131 | 154 | } | |
| 132 | 155 | ||
| 156 | + | // shouldBundleStackArgs always returns false on ppc64le | |
| 157 | + | // since C-style stack argument bundling is only needed on Darwin ARM64. | |
| 133 | 158 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |
| 134 | - | // PPC64LE does not bundle stack args | |
| 135 | 159 | return false | |
| 136 | 160 | } | |
| 137 | 161 | ||
| 138 | - | func collectStackArgs( | |
| 139 | - | args []reflect.Value, | |
| 140 | - | i, numInts, numFloats int, | |
| 141 | - | keepAlive []any, | |
| 142 | - | addInt, addFloat, addStack func(uintptr), | |
| 143 | - | numIntsPtr, numFloatsPtr, numStackPtr *int, | |
| 144 | - | ) ([]reflect.Value, []any) { | |
| 145 | - | return nil, keepAlive | |
| 162 | + | // collectStackArgs is not used on ppc64le. | |
| 163 | + | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, | |
| 164 | + | keepAlive []any, addInt, addFloat, addStack func(uintptr), | |
| 165 | + | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { | |
| 166 | + | panic("purego: collectStackArgs should not be called on ppc64le") | |
| 146 | 167 | } | |
| 147 | 168 | ||
| 169 | + | // bundleStackArgs is not used on ppc64le. | |
| 148 | 170 | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |
| 149 | 171 | panic("purego: bundleStackArgs should not be called on ppc64le") | |
| 150 | 172 | } | |
| @@ -4,84 +4,76 @@ | |||||
| 4 | package purego | 4 | package purego | ||
| 5 | 5 | ||||
| 6 | import ( | 6 | import ( | ||
| 7 | + | "math" | |||
| 7 | "reflect" | 8 | "reflect" | ||
| 8 | "unsafe" | 9 | "unsafe" | ||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| 11 | - | // structReturnInMemory reports whether a struct return value of the given size | 12 | + | // ppc64leLeaf is a scalar member of a flattened aggregate at byte offset offset. |
| 12 | - | // is returned through a caller-allocated hidden pointer passed as the first | 13 | + | type ppc64leLeaf struct { |
| 13 | - | // integer argument. Aggregates larger than two eightbytes are returned in memory. | 14 | + | kind reflect.Kind |
| 14 | - | func structReturnInMemory(size uintptr) bool { | 15 | + | offset uintptr |
| 15 | - | return size > maxRegAllocStructSize | |||
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | - | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | 18 | + | // ppc64leFlatten appends the scalar leaves of t to leaves, expanding nested |
| 19 | - | outSize := outType.Size() | 19 | + | // structs and arrays. |
| 20 | - | 20 | + | func ppc64leFlatten(t reflect.Type, base uintptr, leaves *[]ppc64leLeaf) { | |
| 21 | - | switch { | 21 | + | switch t.Kind() { |
| 22 | - | case outSize == 0: | 22 | + | case reflect.Struct: |
| 23 | - | return reflect.New(outType).Elem() | 23 | + | for i := range t.NumField() { |
| 24 | - | 24 | + | f := t.Field(i) | |
| 25 | - | case outSize <= 16: | 25 | + | if f.Name == "_" { |
| 26 | - | // Reconstruct from registers by copying raw bytes | 26 | + | // Blank fields are padding, not counted members. |
| 27 | - | var buf [16]byte | 27 | + | continue |
| 28 | - | ||||
| 29 | - | // Integer registers | |||
| 30 | - | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.a1 | |||
| 31 | - | if outSize > 8 { | |||
| 32 | - | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.a2 | |||
| 33 | - | } | |||
| 34 | - | ||||
| 35 | - | // Homogeneous float aggregates override integer regs | |||
| 36 | - | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |||
| 37 | - | if outType.Field(0).Type.Kind() == reflect.Float32 { | |||
| 38 | - | // float32 values in FP regs | |||
| 39 | - | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | |||
| 40 | - | for i := 0; i < numFields; i++ { | |||
| 41 | - | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) | |||
| 42 | - | } | |||
| 43 | - | } else { | |||
| 44 | - | // float64: whole register value is valid | |||
| 45 | - | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.f1 | |||
| 46 | - | if outSize > 8 { | |||
| 47 | - | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.f2 | |||
| 48 | - | } | |||
| 49 | } | 28 | } | ||
| 29 | + | ppc64leFlatten(f.Type, base+f.Offset, leaves) | |||
| 30 | + | } | |||
| 31 | + | case reflect.Array: | |||
| 32 | + | elem := t.Elem() | |||
| 33 | + | for i := range t.Len() { | |||
| 34 | + | ppc64leFlatten(elem, base+uintptr(i)*elem.Size(), leaves) | |||
| 50 | } | 35 | } | ||
| 51 | - | ||||
| 52 | - | return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem() | |||
| 53 | - | ||||
| 54 | default: | 36 | default: | ||
| 55 | - | // Returned indirectly via pointer in a1 | 37 | + | *leaves = append(*leaves, ppc64leLeaf{kind: t.Kind(), offset: base}) |
| 56 | - | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)) | |||
| 57 | - | return reflect.NewAt(outType, ptr).Elem() | |||
| 58 | } | 38 | } | ||
| 59 | } | 39 | } | ||
| 60 | 40 | ||||
| 61 | - | func addStruct( | 41 | + | // ppc64leClassifyHFA reports whether t is a homogeneous floating-point |
| 62 | - | v reflect.Value, | 42 | + | // aggregate: one to eight members all of the same floating-point type. |
| 63 | - | numInts, numFloats, numStack *int, | 43 | + | func ppc64leClassifyHFA(t reflect.Type) (leaves []ppc64leLeaf, isHFA bool) { |
| 64 | - | addInt, addFloat, addStack func(uintptr), | 44 | + | ppc64leFlatten(t, 0, &leaves) |
| 65 | - | keepAlive []any, | 45 | + | if len(leaves) == 0 || len(leaves) > 8 { |
| 66 | - | ) []any { | 46 | + | return leaves, false |
| 67 | - | size := v.Type().Size() | |||
| 68 | - | if size == 0 { | |||
| 69 | - | return keepAlive | |||
| 70 | } | 47 | } | ||
| 71 | - | 48 | + | first := leaves[0].kind | |
| 72 | - | if size <= 16 { | 49 | + | if first != reflect.Float32 && first != reflect.Float64 { |
| 73 | - | return placeSmallAggregatePPC64LE(v, addFloat, addInt, keepAlive) | 50 | + | return leaves, false |
| 51 | + | } | |||
| 52 | + | for _, l := range leaves { | |||
| 53 | + | if l.kind != first { | |||
| 54 | + | return leaves, false | |||
| 55 | + | } | |||
| 74 | } | 56 | } | ||
| 57 | + | return leaves, true | |||
| 58 | + | } | |||
| 75 | 59 | ||||
| 76 | - | return placeStack(v, keepAlive, addInt) | 60 | + | // structReturnInMemory reports whether a struct return value is returned through |
| 61 | + | // a caller-allocated hidden pointer rather than in registers. A homogeneous | |||
| 62 | + | // floating-point aggregate is returned in the floating-point registers whatever | |||
| 63 | + | // its size. | |||
| 64 | + | func structReturnInMemory(outType reflect.Type) bool { | |||
| 65 | + | if outType.Size() <= maxRegAllocStructSize { | |||
| 66 | + | return false | |||
| 67 | + | } | |||
| 68 | + | _, isHFA := ppc64leClassifyHFA(outType) | |||
| 69 | + | return !isHFA | |||
| 77 | } | 70 | } | ||
| 78 | 71 | ||||
| 79 | - | func placeSmallAggregatePPC64LE( | 72 | + | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { |
| 80 | - | v reflect.Value, | |||
| 81 | - | addFloat, addInt func(uintptr), | |||
| 82 | - | keepAlive []any, | |||
| 83 | - | ) []any { | |||
| 84 | size := v.Type().Size() | 73 | size := v.Type().Size() | ||
| 74 | + | if size == 0 { | |||
| 75 | + | return keepAlive | |||
| 76 | + | } | |||
| 85 | 77 | ||||
| 86 | var ptr unsafe.Pointer | 78 | var ptr unsafe.Pointer | ||
| 87 | if v.CanAddr() { | 79 | if v.CanAddr() { | ||
| @@ -93,58 +85,88 @@ func placeSmallAggregatePPC64LE( | |||||
| 93 | keepAlive = append(keepAlive, tmp.Interface()) | 85 | keepAlive = append(keepAlive, tmp.Interface()) | ||
| 94 | } | 86 | } | ||
| 95 | 87 | ||||
| 96 | - | var buf [16]byte | 88 | + | // An HFA passes each member in its own floating-point register. |
| 97 | - | src := unsafe.Slice((*byte)(ptr), size) | 89 | + | if leaves, isHFA := ppc64leClassifyHFA(v.Type()); isHFA { |
| 98 | - | copy(buf[:], src) | 90 | + | for _, l := range leaves { |
| 99 | - | 91 | + | src := unsafe.Add(ptr, l.offset) | |
| 100 | - | w0 := *(*uintptr)(unsafe.Pointer(&buf[0])) | 92 | + | if l.kind == reflect.Float32 { |
| 101 | - | w1 := uintptr(0) | 93 | + | // Single precision occupies the register in double format. |
| 102 | - | if size > 8 { | 94 | + | addFloat(uintptr(math.Float64bits(float64(*(*float32)(src))))) |
| 103 | - | w1 = *(*uintptr)(unsafe.Pointer(&buf[8])) | 95 | + | } else { |
| 96 | + | addFloat(uintptr(*(*uint64)(src))) | |||
| 97 | + | } | |||
| 98 | + | } | |||
| 104 | } | 99 | } | ||
| 105 | 100 | ||||
| 106 | - | if isFloats, _ := isAllSameFloat(v.Type()); isFloats { | 101 | + | // Every aggregate also occupies its size rounded up to doublewords in the |
| 107 | - | addFloat(w0) | 102 | + | // GPRs r3-r10 / parameter save area: shadow (skipped) for an HFA, the value |
| 108 | - | if size > 8 { | 103 | + | // otherwise. |
| 109 | - | addFloat(w1) | 104 | + | for off := uintptr(0); off < size; off += 8 { |
| 110 | - | } | 105 | + | if remaining := size - off; remaining >= 8 { |
| 111 | - | } else { | 106 | + | addInt(*(*uintptr)(unsafe.Add(ptr, off))) |
| 112 | - | addInt(w0) | 107 | + | } else { |
| 113 | - | if size > 8 { | 108 | + | var word [8]byte |
| 114 | - | addInt(w1) | 109 | + | copy(word[:], unsafe.Slice((*byte)(unsafe.Add(ptr, off)), int(remaining))) |
| 110 | + | addInt(*(*uintptr)(unsafe.Pointer(&word[0]))) | |||
| 115 | } | 111 | } | ||
| 116 | } | 112 | } | ||
| 117 | 113 | ||||
| 118 | return keepAlive | 114 | return keepAlive | ||
| 119 | } | 115 | } | ||
| 120 | 116 | ||||
| 121 | - | // placeStack is a fallback for structs that are too large to fit in registers | 117 | + | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { |
| 122 | - | func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | 118 | + | if outType.Size() == 0 { |
| 123 | - | if v.CanAddr() { | 119 | + | return reflect.New(outType).Elem() |
| 124 | - | addInt(v.Addr().Pointer()) | 120 | + | } |
| 125 | - | return keepAlive | 121 | + | |
| 122 | + | // An HFA returns each member in its own floating-point register. | |||
| 123 | + | if leaves, isHFA := ppc64leClassifyHFA(outType); isHFA { | |||
| 124 | + | floatRegs := [8]uintptr{ | |||
| 125 | + | syscall.f1, syscall.f2, syscall.f3, syscall.f4, | |||
| 126 | + | syscall.f5, syscall.f6, syscall.f7, syscall.f8, | |||
| 127 | + | } | |||
| 128 | + | ret := reflect.New(outType) | |||
| 129 | + | base := ret.UnsafePointer() | |||
| 130 | + | for i, l := range leaves { | |||
| 131 | + | dst := unsafe.Add(base, l.offset) | |||
| 132 | + | if l.kind == reflect.Float32 { | |||
| 133 | + | // The register holds the value in double format. | |||
| 134 | + | *(*float32)(dst) = float32(math.Float64frombits(uint64(floatRegs[i]))) | |||
| 135 | + | } else { | |||
| 136 | + | *(*uint64)(dst) = uint64(floatRegs[i]) | |||
| 137 | + | } | |||
| 138 | + | } | |||
| 139 | + | return ret.Elem() | |||
| 140 | + | } | |||
| 141 | + | ||||
| 142 | + | if outType.Size() > 16 { | |||
| 143 | + | // Returned via a caller-allocated buffer whose pointer comes back in r3. | |||
| 144 | + | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | |||
| 145 | + | } | |||
| 146 | + | ||||
| 147 | + | // Otherwise returned in r3 and r4. | |||
| 148 | + | var buf [16]byte | |||
| 149 | + | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.a1 | |||
| 150 | + | if outType.Size() > 8 { | |||
| 151 | + | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.a2 | |||
| 126 | } | 152 | } | ||
| 127 | - | ptr := reflect.New(v.Type()) | 153 | + | return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem() |
| 128 | - | ptr.Elem().Set(v) | |||
| 129 | - | addInt(ptr.Pointer()) | |||
| 130 | - | return append(keepAlive, ptr.Interface()) | |||
| 131 | } | 154 | } | ||
| 132 | 155 | ||||
| 156 | + | // shouldBundleStackArgs always returns false on ppc64le | |||
| 157 | + | // since C-style stack argument bundling is only needed on Darwin ARM64. | |||
| 133 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | 158 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | ||
| 134 | - | // PPC64LE does not bundle stack args | |||
| 135 | return false | 159 | return false | ||
| 136 | } | 160 | } | ||
| 137 | 161 | ||||
| 138 | - | func collectStackArgs( | 162 | + | // collectStackArgs is not used on ppc64le. |
| 139 | - | args []reflect.Value, | 163 | + | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, |
| 140 | - | i, numInts, numFloats int, | 164 | + | keepAlive []any, addInt, addFloat, addStack func(uintptr), |
| 141 | - | keepAlive []any, | 165 | + | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { |
| 142 | - | addInt, addFloat, addStack func(uintptr), | 166 | + | panic("purego: collectStackArgs should not be called on ppc64le") |
| 143 | - | numIntsPtr, numFloatsPtr, numStackPtr *int, | |||
| 144 | - | ) ([]reflect.Value, []any) { | |||
| 145 | - | return nil, keepAlive | |||
| 146 | } | 167 | } | ||
| 147 | 168 | ||||
| 169 | + | // bundleStackArgs is not used on ppc64le. | |||
| 148 | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | 170 | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | ||
| 149 | panic("purego: bundleStackArgs should not be called on ppc64le") | 171 | panic("purego: bundleStackArgs should not be called on ppc64le") | ||
| 150 | } | 172 | } | ||
M struct_riscv64.go +5 -5
| @@ -8,11 +8,11 @@ import ( | |||
| 8 | 8 | "unsafe" | |
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | - | // structReturnInMemory reports whether a struct return value of the given size | |
| 12 | - | // is returned through a caller-allocated hidden pointer passed as the first | |
| 13 | - | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |
| 14 | - | func structReturnInMemory(size uintptr) bool { | |
| 15 | - | return size > maxRegAllocStructSize | |
| 11 | + | // structReturnInMemory reports whether a struct return value is returned through | |
| 12 | + | // a caller-allocated hidden pointer passed as the first integer argument. | |
| 13 | + | // Aggregates larger than two eightbytes are returned in memory. | |
| 14 | + | func structReturnInMemory(outType reflect.Type) bool { | |
| 15 | + | return outType.Size() > maxRegAllocStructSize | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| @@ -8,11 +8,11 @@ import ( | |||||
| 8 | "unsafe" | 8 | "unsafe" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | - | // structReturnInMemory reports whether a struct return value of the given size | 11 | + | // structReturnInMemory reports whether a struct return value is returned through |
| 12 | - | // is returned through a caller-allocated hidden pointer passed as the first | 12 | + | // a caller-allocated hidden pointer passed as the first integer argument. |
| 13 | - | // integer argument. Aggregates larger than two eightbytes are returned in memory. | 13 | + | // Aggregates larger than two eightbytes are returned in memory. |
| 14 | - | func structReturnInMemory(size uintptr) bool { | 14 | + | func structReturnInMemory(outType reflect.Type) bool { |
| 15 | - | return size > maxRegAllocStructSize | 15 | + | return outType.Size() > maxRegAllocStructSize |
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | ||
M struct_s390x.go +5 -5
| @@ -8,11 +8,11 @@ import ( | |||
| 8 | 8 | "unsafe" | |
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | - | // structReturnInMemory reports whether a struct return value of the given size | |
| 12 | - | // is returned through a caller-allocated hidden pointer passed as the first | |
| 13 | - | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |
| 14 | - | func structReturnInMemory(size uintptr) bool { | |
| 15 | - | return size > maxRegAllocStructSize | |
| 11 | + | // structReturnInMemory reports whether a struct return value is returned through | |
| 12 | + | // a caller-allocated hidden pointer passed as the first integer argument. | |
| 13 | + | // Aggregates larger than two eightbytes are returned in memory. | |
| 14 | + | func structReturnInMemory(outType reflect.Type) bool { | |
| 15 | + | return outType.Size() > maxRegAllocStructSize | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| @@ -8,11 +8,11 @@ import ( | |||||
| 8 | "unsafe" | 8 | "unsafe" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | - | // structReturnInMemory reports whether a struct return value of the given size | 11 | + | // structReturnInMemory reports whether a struct return value is returned through |
| 12 | - | // is returned through a caller-allocated hidden pointer passed as the first | 12 | + | // a caller-allocated hidden pointer passed as the first integer argument. |
| 13 | - | // integer argument. Aggregates larger than two eightbytes are returned in memory. | 13 | + | // Aggregates larger than two eightbytes are returned in memory. |
| 14 | - | func structReturnInMemory(size uintptr) bool { | 14 | + | func structReturnInMemory(outType reflect.Type) bool { |
| 15 | - | return size > maxRegAllocStructSize | 15 | + | return outType.Size() > maxRegAllocStructSize |
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | ||
M struct_test.go +92 -76
| @@ -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 || linux || windows) && (amd64 || arm64 || loong64) | |
| 4 | + | //go:build (darwin || linux || windows) && (amd64 || arm64 || loong64 || ppc64le) | |
| 5 | 5 | ||
| 6 | 6 | package purego_test | |
| 7 | 7 | ||
| @@ -282,24 +282,32 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 282 | 282 | if ret := LargeFloatStructFn(LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble { | |
| 283 | 283 | t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat) | |
| 284 | 284 | } | |
| 285 | - | var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64 | |
| 286 | - | register(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs", func(a, b, c float64, s LargeFloatStruct) float64 { | |
| 287 | - | return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f | |
| 288 | - | }) | |
| 289 | - | if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble { | |
| 290 | - | t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat) | |
| 285 | + | if runtime.GOARCH != "ppc64le" { | |
| 286 | + | // Needs 9 float argument registers (3 doubles + a 6-double HFA); | |
| 287 | + | // ppc64le wires 8. | |
| 288 | + | var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64 | |
| 289 | + | register(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs", func(a, b, c float64, s LargeFloatStruct) float64 { | |
| 290 | + | return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f | |
| 291 | + | }) | |
| 292 | + | if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble { | |
| 293 | + | t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat) | |
| 294 | + | } | |
| 291 | 295 | } | |
| 292 | 296 | } | |
| 293 | 297 | { | |
| 294 | 298 | type Rect struct { | |
| 295 | 299 | x, y, w, h float64 | |
| 296 | 300 | } | |
| 297 | - | var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64 | |
| 298 | - | register(&RectangleWithRegs, lib, "RectangleWithRegs", func(a, b, c, d, e float64, rect Rect) float64 { | |
| 299 | - | return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h | |
| 300 | - | }) | |
| 301 | - | if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{1, 2, 3, -4}); ret != expectedDouble { | |
| 302 | - | t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble) | |
| 301 | + | if runtime.GOARCH != "ppc64le" { | |
| 302 | + | // Needs 9 float argument registers (5 doubles + a 4-double HFA); | |
| 303 | + | // ppc64le wires 8. | |
| 304 | + | var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64 | |
| 305 | + | register(&RectangleWithRegs, lib, "RectangleWithRegs", func(a, b, c, d, e float64, rect Rect) float64 { | |
| 306 | + | return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h | |
| 307 | + | }) | |
| 308 | + | if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{1, 2, 3, -4}); ret != expectedDouble { | |
| 309 | + | t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble) | |
| 310 | + | } | |
| 303 | 311 | } | |
| 304 | 312 | var RectangleSubtract func(rect Rect) float64 | |
| 305 | 313 | register(&RectangleSubtract, lib, "RectangleSubtract", func(rect Rect) float64 { | |
| @@ -789,8 +797,10 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 789 | 797 | runtime.KeepAlive(a) | |
| 790 | 798 | runtime.KeepAlive(c) | |
| 791 | 799 | } | |
| 792 | - | { | |
| 800 | + | if runtime.GOARCH != "ppc64le" { | |
| 793 | 801 | // Struct arg after some primitive args: register offset correctness. | |
| 802 | + | // ppc64le is skipped: a float argument shadows a GPR, which purego | |
| 803 | + | // does not model for scalars, so the trailing struct is misplaced. | |
| 794 | 804 | type IntLessThan16Bytes struct{ X, Y int64 } | |
| 795 | 805 | var fn func(int64, float64, IntLessThan16Bytes) IntLessThan16Bytes | |
| 796 | 806 | register(&fn, lib, "IdentityTwoInt64AfterPrims", func(x int64, y float64, s IntLessThan16Bytes) IntLessThan16Bytes { | |
| @@ -1116,73 +1126,79 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 1116 | 1126 | } | |
| 1117 | 1127 | } | |
| 1118 | 1128 | ||
| 1119 | - | { | |
| 1120 | - | type Mixed1 struct { | |
| 1121 | - | A float32 | |
| 1122 | - | B int32 | |
| 1123 | - | } | |
| 1124 | - | var ReturnMixed1 func(a float32, b int32) Mixed1 | |
| 1125 | - | register(&ReturnMixed1, lib, "ReturnMixed1") | |
| 1126 | - | expected := Mixed1{1, 2} | |
| 1127 | - | if ret := ReturnMixed1(1, 2); ret != expected { | |
| 1128 | - | t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected) | |
| 1129 | - | } | |
| 1130 | - | } | |
| 1131 | - | { | |
| 1132 | - | type Mixed2 struct { | |
| 1133 | - | A float32 | |
| 1134 | - | B int32 | |
| 1135 | - | C float32 | |
| 1136 | - | D int32 | |
| 1137 | - | } | |
| 1138 | - | var ReturnMixed2 func(a float32, b int32, c float32, d int32) Mixed2 | |
| 1139 | - | register(&ReturnMixed2, lib, "ReturnMixed2") | |
| 1140 | - | expected := Mixed2{1, 2, 3, 4} | |
| 1141 | - | if ret := ReturnMixed2(1, 2, 3, 4); ret != expected { | |
| 1142 | - | t.Fatalf("ReturnMixed2 returned %+v wanted %+v", ret, expected) | |
| 1143 | - | } | |
| 1144 | - | } | |
| 1145 | - | { | |
| 1146 | - | type Mixed3 struct { | |
| 1147 | - | A float32 | |
| 1148 | - | B uint32 | |
| 1149 | - | C float64 | |
| 1150 | - | } | |
| 1151 | - | var ReturnMixed3 func(a float32, b uint32, c float64) Mixed3 | |
| 1152 | - | register(&ReturnMixed3, lib, "ReturnMixed3") | |
| 1153 | - | expected := Mixed3{1, 2, 3} | |
| 1154 | - | if ret := ReturnMixed3(1, 2, 3); ret != expected { | |
| 1155 | - | t.Fatalf("ReturnMixed3 returned %+v wanted %+v", ret, expected) | |
| 1129 | + | if runtime.GOARCH != "ppc64le" { | |
| 1130 | + | // ppc64le is skipped: a float argument shadows a GPR, which purego | |
| 1131 | + | // does not model for scalars, so a trailing integer arg is misplaced. | |
| 1132 | + | // TODO: Support mixed scalar floating-point and integer arguments on | |
| 1133 | + | // ppc64le by modeling the ELFv2 general-purpose register shadowing. | |
| 1134 | + | { | |
| 1135 | + | type Mixed1 struct { | |
| 1136 | + | A float32 | |
| 1137 | + | B int32 | |
| 1138 | + | } | |
| 1139 | + | var ReturnMixed1 func(a float32, b int32) Mixed1 | |
| 1140 | + | register(&ReturnMixed1, lib, "ReturnMixed1") | |
| 1141 | + | expected := Mixed1{1, 2} | |
| 1142 | + | if ret := ReturnMixed1(1, 2); ret != expected { | |
| 1143 | + | t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected) | |
| 1144 | + | } | |
| 1156 | 1145 | } | |
| 1157 | - | } | |
| 1158 | - | { | |
| 1159 | - | type Mixed4 struct { | |
| 1160 | - | A float64 | |
| 1161 | - | B uint32 | |
| 1162 | - | C float32 | |
| 1146 | + | { | |
| 1147 | + | type Mixed2 struct { | |
| 1148 | + | A float32 | |
| 1149 | + | B int32 | |
| 1150 | + | C float32 | |
| 1151 | + | D int32 | |
| 1152 | + | } | |
| 1153 | + | var ReturnMixed2 func(a float32, b int32, c float32, d int32) Mixed2 | |
| 1154 | + | register(&ReturnMixed2, lib, "ReturnMixed2") | |
| 1155 | + | expected := Mixed2{1, 2, 3, 4} | |
| 1156 | + | if ret := ReturnMixed2(1, 2, 3, 4); ret != expected { | |
| 1157 | + | t.Fatalf("ReturnMixed2 returned %+v wanted %+v", ret, expected) | |
| 1158 | + | } | |
| 1163 | 1159 | } | |
| 1164 | - | var ReturnMixed4 func(a float64, b uint32, c float32) Mixed4 | |
| 1165 | - | register(&ReturnMixed4, lib, "ReturnMixed4") | |
| 1166 | - | expected := Mixed4{1, 2, 3} | |
| 1167 | - | if ret := ReturnMixed4(1, 2, 3); ret != expected { | |
| 1168 | - | t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected) | |
| 1160 | + | { | |
| 1161 | + | type Mixed3 struct { | |
| 1162 | + | A float32 | |
| 1163 | + | B uint32 | |
| 1164 | + | C float64 | |
| 1165 | + | } | |
| 1166 | + | var ReturnMixed3 func(a float32, b uint32, c float64) Mixed3 | |
| 1167 | + | register(&ReturnMixed3, lib, "ReturnMixed3") | |
| 1168 | + | expected := Mixed3{1, 2, 3} | |
| 1169 | + | if ret := ReturnMixed3(1, 2, 3); ret != expected { | |
| 1170 | + | t.Fatalf("ReturnMixed3 returned %+v wanted %+v", ret, expected) | |
| 1171 | + | } | |
| 1169 | 1172 | } | |
| 1170 | - | } | |
| 1171 | - | { | |
| 1172 | - | type Mixed5 struct { | |
| 1173 | - | A *int64 | |
| 1174 | - | B int32 | |
| 1175 | - | C float32 | |
| 1176 | - | D int32 | |
| 1173 | + | { | |
| 1174 | + | type Mixed4 struct { | |
| 1175 | + | A float64 | |
| 1176 | + | B uint32 | |
| 1177 | + | C float32 | |
| 1178 | + | } | |
| 1179 | + | var ReturnMixed4 func(a float64, b uint32, c float32) Mixed4 | |
| 1180 | + | register(&ReturnMixed4, lib, "ReturnMixed4") | |
| 1181 | + | expected := Mixed4{1, 2, 3} | |
| 1182 | + | if ret := ReturnMixed4(1, 2, 3); ret != expected { | |
| 1183 | + | t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected) | |
| 1184 | + | } | |
| 1177 | 1185 | } | |
| 1178 | - | var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed5 | |
| 1179 | - | register(&ReturnMixed5, lib, "ReturnMixed5") | |
| 1180 | - | ptr := new(int64) | |
| 1181 | - | expected := Mixed5{ptr, 1, 7.2, 9} | |
| 1182 | - | if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected { | |
| 1183 | - | t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected) | |
| 1186 | + | { | |
| 1187 | + | type Mixed5 struct { | |
| 1188 | + | A *int64 | |
| 1189 | + | B int32 | |
| 1190 | + | C float32 | |
| 1191 | + | D int32 | |
| 1192 | + | } | |
| 1193 | + | var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed5 | |
| 1194 | + | register(&ReturnMixed5, lib, "ReturnMixed5") | |
| 1195 | + | ptr := new(int64) | |
| 1196 | + | expected := Mixed5{ptr, 1, 7.2, 9} | |
| 1197 | + | if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected { | |
| 1198 | + | t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected) | |
| 1199 | + | } | |
| 1200 | + | runtime.KeepAlive(ptr) | |
| 1184 | 1201 | } | |
| 1185 | - | runtime.KeepAlive(ptr) | |
| 1186 | 1202 | } | |
| 1187 | 1203 | { | |
| 1188 | 1204 | type Mixed5 struct { | |
| @@ -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 || linux || windows) && (amd64 || arm64 || loong64) | 4 | + | //go:build (darwin || linux || windows) && (amd64 || arm64 || loong64 || ppc64le) |
| 5 | 5 | ||||
| 6 | package purego_test | 6 | package purego_test | ||
| 7 | 7 | ||||
| @@ -282,24 +282,32 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 282 | if ret := LargeFloatStructFn(LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble { | 282 | if ret := LargeFloatStructFn(LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble { | ||
| 283 | t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat) | 283 | t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat) | ||
| 284 | } | 284 | } | ||
| 285 | - | var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64 | 285 | + | if runtime.GOARCH != "ppc64le" { |
| 286 | - | register(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs", func(a, b, c float64, s LargeFloatStruct) float64 { | 286 | + | // Needs 9 float argument registers (3 doubles + a 6-double HFA); |
| 287 | - | return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f | 287 | + | // ppc64le wires 8. |
| 288 | - | }) | 288 | + | var LargeFloatStructWithRegs func(a, b, c float64, s LargeFloatStruct) float64 |
| 289 | - | if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble { | 289 | + | register(&LargeFloatStructWithRegs, lib, "LargeFloatStructWithRegs", func(a, b, c float64, s LargeFloatStruct) float64 { |
| 290 | - | t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat) | 290 | + | return a + b + c + s.a + s.b + s.c + s.d + s.e + s.f |
| 291 | + | }) | |||
| 292 | + | if ret := LargeFloatStructWithRegs(1, -1, 0, LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble { | |||
| 293 | + | t.Fatalf("LargeFloatStructWithRegs returned %f wanted %f", ret, expectedFloat) | |||
| 294 | + | } | |||
| 291 | } | 295 | } | ||
| 292 | } | 296 | } | ||
| 293 | { | 297 | { | ||
| 294 | type Rect struct { | 298 | type Rect struct { | ||
| 295 | x, y, w, h float64 | 299 | x, y, w, h float64 | ||
| 296 | } | 300 | } | ||
| 297 | - | var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64 | 301 | + | if runtime.GOARCH != "ppc64le" { |
| 298 | - | register(&RectangleWithRegs, lib, "RectangleWithRegs", func(a, b, c, d, e float64, rect Rect) float64 { | 302 | + | // Needs 9 float argument registers (5 doubles + a 4-double HFA); |
| 299 | - | return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h | 303 | + | // ppc64le wires 8. |
| 300 | - | }) | 304 | + | var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64 |
| 301 | - | if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{1, 2, 3, -4}); ret != expectedDouble { | 305 | + | register(&RectangleWithRegs, lib, "RectangleWithRegs", func(a, b, c, d, e float64, rect Rect) float64 { |
| 302 | - | t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble) | 306 | + | return a + b + c + d + e + rect.x + rect.y + rect.w + rect.h |
| 307 | + | }) | |||
| 308 | + | if ret := RectangleWithRegs(1, 2, 3, 4, -2, Rect{1, 2, 3, -4}); ret != expectedDouble { | |||
| 309 | + | t.Fatalf("RectangleWithRegs returned %f wanted %f", ret, expectedDouble) | |||
| 310 | + | } | |||
| 303 | } | 311 | } | ||
| 304 | var RectangleSubtract func(rect Rect) float64 | 312 | var RectangleSubtract func(rect Rect) float64 | ||
| 305 | register(&RectangleSubtract, lib, "RectangleSubtract", func(rect Rect) float64 { | 313 | register(&RectangleSubtract, lib, "RectangleSubtract", func(rect Rect) float64 { | ||
| @@ -789,8 +797,10 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||||
| 789 | runtime.KeepAlive(a) | 797 | runtime.KeepAlive(a) | ||
| 790 | runtime.KeepAlive(c) | 798 | runtime.KeepAlive(c) | ||
| 791 | } | 799 | } | ||
| 792 | - | { | 800 | + | if runtime.GOARCH != "ppc64le" { |
| 793 | // Struct arg after some primitive args: register offset correctness. | 801 | // Struct arg after some primitive args: register offset correctness. | ||
| 802 | + | // ppc64le is skipped: a float argument shadows a GPR, which purego | |||
| 803 | + | // does not model for scalars, so the trailing struct is misplaced. | |||
| 794 | type IntLessThan16Bytes struct{ X, Y int64 } | 804 | type IntLessThan16Bytes struct{ X, Y int64 } | ||
| 795 | var fn func(int64, float64, IntLessThan16Bytes) IntLessThan16Bytes | 805 | var fn func(int64, float64, IntLessThan16Bytes) IntLessThan16Bytes | ||
| 796 | register(&fn, lib, "IdentityTwoInt64AfterPrims", func(x int64, y float64, s IntLessThan16Bytes) IntLessThan16Bytes { | 806 | register(&fn, lib, "IdentityTwoInt64AfterPrims", func(x int64, y float64, s IntLessThan16Bytes) IntLessThan16Bytes { | ||
| @@ -1116,73 +1126,79 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||||
| 1116 | } | 1126 | } | ||
| 1117 | } | 1127 | } | ||
| 1118 | 1128 | ||||
| 1119 | - | { | 1129 | + | if runtime.GOARCH != "ppc64le" { |
| 1120 | - | type Mixed1 struct { | 1130 | + | // ppc64le is skipped: a float argument shadows a GPR, which purego |
| 1121 | - | A float32 | 1131 | + | // does not model for scalars, so a trailing integer arg is misplaced. |
| 1122 | - | B int32 | 1132 | + | // TODO: Support mixed scalar floating-point and integer arguments on |
| 1123 | - | } | 1133 | + | // ppc64le by modeling the ELFv2 general-purpose register shadowing. |
| 1124 | - | var ReturnMixed1 func(a float32, b int32) Mixed1 | 1134 | + | { |
| 1125 | - | register(&ReturnMixed1, lib, "ReturnMixed1") | 1135 | + | type Mixed1 struct { |
| 1126 | - | expected := Mixed1{1, 2} | 1136 | + | A float32 |
| 1127 | - | if ret := ReturnMixed1(1, 2); ret != expected { | 1137 | + | B int32 |
| 1128 | - | t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected) | 1138 | + | } |
| 1129 | - | } | 1139 | + | var ReturnMixed1 func(a float32, b int32) Mixed1 |
| 1130 | - | } | 1140 | + | register(&ReturnMixed1, lib, "ReturnMixed1") |
| 1131 | - | { | 1141 | + | expected := Mixed1{1, 2} |
| 1132 | - | type Mixed2 struct { | 1142 | + | if ret := ReturnMixed1(1, 2); ret != expected { |
| 1133 | - | A float32 | 1143 | + | t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected) |
| 1134 | - | B int32 | 1144 | + | } |
| 1135 | - | C float32 | |||
| 1136 | - | D int32 | |||
| 1137 | - | } | |||
| 1138 | - | var ReturnMixed2 func(a float32, b int32, c float32, d int32) Mixed2 | |||
| 1139 | - | register(&ReturnMixed2, lib, "ReturnMixed2") | |||
| 1140 | - | expected := Mixed2{1, 2, 3, 4} | |||
| 1141 | - | if ret := ReturnMixed2(1, 2, 3, 4); ret != expected { | |||
| 1142 | - | t.Fatalf("ReturnMixed2 returned %+v wanted %+v", ret, expected) | |||
| 1143 | - | } | |||
| 1144 | - | } | |||
| 1145 | - | { | |||
| 1146 | - | type Mixed3 struct { | |||
| 1147 | - | A float32 | |||
| 1148 | - | B uint32 | |||
| 1149 | - | C float64 | |||
| 1150 | - | } | |||
| 1151 | - | var ReturnMixed3 func(a float32, b uint32, c float64) Mixed3 | |||
| 1152 | - | register(&ReturnMixed3, lib, "ReturnMixed3") | |||
| 1153 | - | expected := Mixed3{1, 2, 3} | |||
| 1154 | - | if ret := ReturnMixed3(1, 2, 3); ret != expected { | |||
| 1155 | - | t.Fatalf("ReturnMixed3 returned %+v wanted %+v", ret, expected) | |||
| 1156 | } | 1145 | } | ||
| 1157 | - | } | 1146 | + | { |
| 1158 | - | { | 1147 | + | type Mixed2 struct { |
| 1159 | - | type Mixed4 struct { | 1148 | + | A float32 |
| 1160 | - | A float64 | 1149 | + | B int32 |
| 1161 | - | B uint32 | 1150 | + | C float32 |
| 1162 | - | C float32 | 1151 | + | D int32 |
| 1152 | + | } | |||
| 1153 | + | var ReturnMixed2 func(a float32, b int32, c float32, d int32) Mixed2 | |||
| 1154 | + | register(&ReturnMixed2, lib, "ReturnMixed2") | |||
| 1155 | + | expected := Mixed2{1, 2, 3, 4} | |||
| 1156 | + | if ret := ReturnMixed2(1, 2, 3, 4); ret != expected { | |||
| 1157 | + | t.Fatalf("ReturnMixed2 returned %+v wanted %+v", ret, expected) | |||
| 1158 | + | } | |||
| 1163 | } | 1159 | } | ||
| 1164 | - | var ReturnMixed4 func(a float64, b uint32, c float32) Mixed4 | 1160 | + | { |
| 1165 | - | register(&ReturnMixed4, lib, "ReturnMixed4") | 1161 | + | type Mixed3 struct { |
| 1166 | - | expected := Mixed4{1, 2, 3} | 1162 | + | A float32 |
| 1167 | - | if ret := ReturnMixed4(1, 2, 3); ret != expected { | 1163 | + | B uint32 |
| 1168 | - | t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected) | 1164 | + | C float64 |
| 1165 | + | } | |||
| 1166 | + | var ReturnMixed3 func(a float32, b uint32, c float64) Mixed3 | |||
| 1167 | + | register(&ReturnMixed3, lib, "ReturnMixed3") | |||
| 1168 | + | expected := Mixed3{1, 2, 3} | |||
| 1169 | + | if ret := ReturnMixed3(1, 2, 3); ret != expected { | |||
| 1170 | + | t.Fatalf("ReturnMixed3 returned %+v wanted %+v", ret, expected) | |||
| 1171 | + | } | |||
| 1169 | } | 1172 | } | ||
| 1170 | - | } | 1173 | + | { |
| 1171 | - | { | 1174 | + | type Mixed4 struct { |
| 1172 | - | type Mixed5 struct { | 1175 | + | A float64 |
| 1173 | - | A *int64 | 1176 | + | B uint32 |
| 1174 | - | B int32 | 1177 | + | C float32 |
| 1175 | - | C float32 | 1178 | + | } |
| 1176 | - | D int32 | 1179 | + | var ReturnMixed4 func(a float64, b uint32, c float32) Mixed4 |
| 1180 | + | register(&ReturnMixed4, lib, "ReturnMixed4") | |||
| 1181 | + | expected := Mixed4{1, 2, 3} | |||
| 1182 | + | if ret := ReturnMixed4(1, 2, 3); ret != expected { | |||
| 1183 | + | t.Fatalf("ReturnMixed4 returned %+v wanted %+v", ret, expected) | |||
| 1184 | + | } | |||
| 1177 | } | 1185 | } | ||
| 1178 | - | var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed5 | 1186 | + | { |
| 1179 | - | register(&ReturnMixed5, lib, "ReturnMixed5") | 1187 | + | type Mixed5 struct { |
| 1180 | - | ptr := new(int64) | 1188 | + | A *int64 |
| 1181 | - | expected := Mixed5{ptr, 1, 7.2, 9} | 1189 | + | B int32 |
| 1182 | - | if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected { | 1190 | + | C float32 |
| 1183 | - | t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected) | 1191 | + | D int32 |
| 1192 | + | } | |||
| 1193 | + | var ReturnMixed5 func(a *int64, b int32, c float32, d int32) Mixed5 | |||
| 1194 | + | register(&ReturnMixed5, lib, "ReturnMixed5") | |||
| 1195 | + | ptr := new(int64) | |||
| 1196 | + | expected := Mixed5{ptr, 1, 7.2, 9} | |||
| 1197 | + | if ret := ReturnMixed5(ptr, 1, 7.2, 9); ret != expected { | |||
| 1198 | + | t.Fatalf("ReturnMixed5 returned %+v wanted %+v", ret, expected) | |||
| 1199 | + | } | |||
| 1200 | + | runtime.KeepAlive(ptr) | |||
| 1184 | } | 1201 | } | ||
| 1185 | - | runtime.KeepAlive(ptr) | |||
| 1186 | } | 1202 | } | ||
| 1187 | { | 1203 | { | ||
| 1188 | type Mixed5 struct { | 1204 | type Mixed5 struct { | ||
M sys_ppc64le.s +5 -1
| @@ -107,11 +107,15 @@ TEXT syscallX(SB), NOSPLIT, $0 | |||
| 107 | 107 | MOVD R3, syscallArgs_a1(R11) | |
| 108 | 108 | MOVD R4, syscallArgs_a2(R11) | |
| 109 | 109 | ||
| 110 | - | // Store float return values (F1-F4) | |
| 110 | + | // Store float return values F1-F8 (an HFA may span up to eight). | |
| 111 | 111 | FMOVD F1, syscallArgs_f1(R11) | |
| 112 | 112 | FMOVD F2, syscallArgs_f2(R11) | |
| 113 | 113 | FMOVD F3, syscallArgs_f3(R11) | |
| 114 | 114 | FMOVD F4, syscallArgs_f4(R11) | |
| 115 | + | FMOVD F5, syscallArgs_f5(R11) | |
| 116 | + | FMOVD F6, syscallArgs_f6(R11) | |
| 117 | + | FMOVD F7, syscallArgs_f7(R11) | |
| 118 | + | FMOVD F8, syscallArgs_f8(R11) | |
| 115 | 119 | ||
| 116 | 120 | // Epilogue: restore and return | |
| 117 | 121 | MOVD LR_SAVE(R1), R12 | |
| @@ -107,11 +107,15 @@ TEXT syscallX(SB), NOSPLIT, $0 | |||||
| 107 | MOVD R3, syscallArgs_a1(R11) | 107 | MOVD R3, syscallArgs_a1(R11) | ||
| 108 | MOVD R4, syscallArgs_a2(R11) | 108 | MOVD R4, syscallArgs_a2(R11) | ||
| 109 | 109 | ||||
| 110 | - | // Store float return values (F1-F4) | 110 | + | // Store float return values F1-F8 (an HFA may span up to eight). |
| 111 | FMOVD F1, syscallArgs_f1(R11) | 111 | FMOVD F1, syscallArgs_f1(R11) | ||
| 112 | FMOVD F2, syscallArgs_f2(R11) | 112 | FMOVD F2, syscallArgs_f2(R11) | ||
| 113 | FMOVD F3, syscallArgs_f3(R11) | 113 | FMOVD F3, syscallArgs_f3(R11) | ||
| 114 | FMOVD F4, syscallArgs_f4(R11) | 114 | FMOVD F4, syscallArgs_f4(R11) | ||
| 115 | + | FMOVD F5, syscallArgs_f5(R11) | |||
| 116 | + | FMOVD F6, syscallArgs_f6(R11) | |||
| 117 | + | FMOVD F7, syscallArgs_f7(R11) | |||
| 118 | + | FMOVD F8, syscallArgs_f8(R11) | |||
| 115 | 119 | ||||
| 116 | // Epilogue: restore and return | 120 | // Epilogue: restore and return | ||
| 117 | MOVD LR_SAVE(R1), R12 | 121 | MOVD LR_SAVE(R1), R12 | ||
M syscall_unix.go +23 -4
| @@ -6,6 +6,7 @@ | |||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| 8 | 8 | import ( | |
| 9 | + | "math" | |
| 9 | 10 | "reflect" | |
| 10 | 11 | "runtime" | |
| 11 | 12 | "sync" | |
| @@ -172,10 +173,13 @@ func callbackWrap(a *callbackArgs) { | |||
| 172 | 173 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | |
| 173 | 174 | } else if stackFrame != nil { | |
| 174 | 175 | // ppc64le/s390x: stack args are in separate stackFrame | |
| 175 | - | if runtime.GOARCH == "s390x" { | |
| 176 | + | switch runtime.GOARCH { | |
| 177 | + | case "ppc64le": | |
| 178 | + | args[i] = callbackFloatFromDoubleSlot(unsafe.Pointer(&stackFrame[stackSlot]), inType) | |
| 179 | + | case "s390x": | |
| 176 | 180 | // s390x big-endian: sub-8-byte values are right-justified | |
| 177 | 181 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType) | |
| 178 | - | } else { | |
| 182 | + | default: | |
| 179 | 183 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |
| 180 | 184 | } | |
| 181 | 185 | stackSlot += slots | |
| @@ -184,10 +188,13 @@ func callbackWrap(a *callbackArgs) { | |||
| 184 | 188 | stackSlot += slots | |
| 185 | 189 | } | |
| 186 | 190 | } else { | |
| 187 | - | if runtime.GOARCH == "s390x" { | |
| 191 | + | switch runtime.GOARCH { | |
| 192 | + | case "ppc64le": | |
| 193 | + | args[i] = callbackFloatFromDoubleSlot(unsafe.Pointer(&frame[floatsN]), inType) | |
| 194 | + | case "s390x": | |
| 188 | 195 | // s390x big-endian: float32 is right-justified in 8-byte FPR slot | |
| 189 | 196 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType) | |
| 190 | - | } else { | |
| 197 | + | default: | |
| 191 | 198 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | |
| 192 | 199 | } | |
| 193 | 200 | } | |
| @@ -287,6 +294,18 @@ func callbackArgFromStack(argsBase unsafe.Pointer, stackSlot int, stackByteOffse | |||
| 287 | 294 | return reflect.NewAt(inType, ptr).Elem() | |
| 288 | 295 | } | |
| 289 | 296 | ||
| 297 | + | // callbackFloatFromDoubleSlot reads a floating-point callback argument from an | |
| 298 | + | // 8-byte register or stack slot on ppc64le, where a single-precision value is | |
| 299 | + | // held in double-precision format. | |
| 300 | + | func callbackFloatFromDoubleSlot(slotPtr unsafe.Pointer, inType reflect.Type) reflect.Value { | |
| 301 | + | if inType.Kind() != reflect.Float32 { | |
| 302 | + | return reflect.NewAt(inType, slotPtr).Elem() | |
| 303 | + | } | |
| 304 | + | v := reflect.New(inType).Elem() | |
| 305 | + | v.SetFloat(math.Float64frombits(*(*uint64)(slotPtr))) | |
| 306 | + | return v | |
| 307 | + | } | |
| 308 | + | ||
| 290 | 309 | // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures. | |
| 291 | 310 | // On s390x: | |
| 292 | 311 | // - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size) | |
| @@ -6,6 +6,7 @@ | |||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| 8 | import ( | 8 | import ( | ||
| 9 | + | "math" | |||
| 9 | "reflect" | 10 | "reflect" | ||
| 10 | "runtime" | 11 | "runtime" | ||
| 11 | "sync" | 12 | "sync" | ||
| @@ -172,10 +173,13 @@ func callbackWrap(a *callbackArgs) { | |||||
| 172 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | 173 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | ||
| 173 | } else if stackFrame != nil { | 174 | } else if stackFrame != nil { | ||
| 174 | // ppc64le/s390x: stack args are in separate stackFrame | 175 | // ppc64le/s390x: stack args are in separate stackFrame | ||
| 175 | - | if runtime.GOARCH == "s390x" { | 176 | + | switch runtime.GOARCH { |
| 177 | + | case "ppc64le": | |||
| 178 | + | args[i] = callbackFloatFromDoubleSlot(unsafe.Pointer(&stackFrame[stackSlot]), inType) | |||
| 179 | + | case "s390x": | |||
| 176 | // s390x big-endian: sub-8-byte values are right-justified | 180 | // s390x big-endian: sub-8-byte values are right-justified | ||
| 177 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType) | 181 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType) | ||
| 178 | - | } else { | 182 | + | default: |
| 179 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | 183 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | ||
| 180 | } | 184 | } | ||
| 181 | stackSlot += slots | 185 | stackSlot += slots | ||
| @@ -184,10 +188,13 @@ func callbackWrap(a *callbackArgs) { | |||||
| 184 | stackSlot += slots | 188 | stackSlot += slots | ||
| 185 | } | 189 | } | ||
| 186 | } else { | 190 | } else { | ||
| 187 | - | if runtime.GOARCH == "s390x" { | 191 | + | switch runtime.GOARCH { |
| 192 | + | case "ppc64le": | |||
| 193 | + | args[i] = callbackFloatFromDoubleSlot(unsafe.Pointer(&frame[floatsN]), inType) | |||
| 194 | + | case "s390x": | |||
| 188 | // s390x big-endian: float32 is right-justified in 8-byte FPR slot | 195 | // s390x big-endian: float32 is right-justified in 8-byte FPR slot | ||
| 189 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType) | 196 | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType) | ||
| 190 | - | } else { | 197 | + | default: |
| 191 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | 198 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | ||
| 192 | } | 199 | } | ||
| 193 | } | 200 | } | ||
| @@ -287,6 +294,18 @@ func callbackArgFromStack(argsBase unsafe.Pointer, stackSlot int, stackByteOffse | |||||
| 287 | return reflect.NewAt(inType, ptr).Elem() | 294 | return reflect.NewAt(inType, ptr).Elem() | ||
| 288 | } | 295 | } | ||
| 289 | 296 | ||||
| 297 | + | // callbackFloatFromDoubleSlot reads a floating-point callback argument from an | |||
| 298 | + | // 8-byte register or stack slot on ppc64le, where a single-precision value is | |||
| 299 | + | // held in double-precision format. | |||
| 300 | + | func callbackFloatFromDoubleSlot(slotPtr unsafe.Pointer, inType reflect.Type) reflect.Value { | |||
| 301 | + | if inType.Kind() != reflect.Float32 { | |||
| 302 | + | return reflect.NewAt(inType, slotPtr).Elem() | |||
| 303 | + | } | |||
| 304 | + | v := reflect.New(inType).Elem() | |||
| 305 | + | v.SetFloat(math.Float64frombits(*(*uint64)(slotPtr))) | |||
| 306 | + | return v | |||
| 307 | + | } | |||
| 308 | + | ||||
| 290 | // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures. | 309 | // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures. | ||
| 291 | // On s390x: | 310 | // On s390x: | ||
| 292 | // - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size) | 311 | // - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size) | ||