detached b1a632f

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 UnverifiedB5690EEEBB952194 The 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
4242
4343 - **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup>
4444 - **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>
4646 - **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>
4747 - **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup>
4848
@@ -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 @@
55
66 package purego
77
8+import "reflect"
9+
810 // MaxArgs re-exports maxArgs for external tests.
911 const MaxArgs = maxArgs
1012
1113 // 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)
1416 }
@@ -5,10 +5,12 @@
5 5
6 package purego6 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 = maxArgs11 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) {
199199 ensureStructSupported()
200200 outType := ty.Out(0)
201201 checkStructFieldsSupported(outType)
202- if structReturnInMemory(outType.Size()) {
202+ if structReturnInMemory(outType) {
203203 // A struct returned in memory is allocated by the caller and its
204204 // pointer is passed as a hidden first integer argument. When the
205205 // integer registers are already full, prepending it spills a
@@ -292,7 +292,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
292292 var arm64_r8 uintptr
293293 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
294294 outType := ty.Out(0)
295- if structReturnInMemory(outType.Size()) {
295+ if structReturnInMemory(outType) {
296296 // The caller allocates the return value and passes its pointer
297297 // as a hidden first integer argument.
298298 val := reflect.New(outType)
@@ -431,10 +431,14 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat
431431 addInt(0)
432432 }
433433 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.
436440 addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32)
437- } else {
441+ default:
438442 addFloat(uintptr(math.Float32bits(float32(v.Float()))))
439443 }
440444 case reflect.Float64:
@@ -510,9 +514,9 @@ func checkStructFieldsSupported(ty reflect.Type) {
510514 // a C function is unsupported on the current platform.
511515 func ensureStructSupported() {
512516 switch runtime.GOARCH {
513- case "amd64", "arm64", "loong64":
517+ case "amd64", "arm64", "loong64", "ppc64le":
514518 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")
516520 }
517521 switch runtime.GOOS {
518522 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 its203 // A struct returned in memory is allocated by the caller and its
204 // pointer is passed as a hidden first integer argument. When the204 // pointer is passed as a hidden first integer argument. When the
205 // integer registers are already full, prepending it spills a205 // integer registers are already full, prepending it spills a
@@ -292,7 +292,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
292 var arm64_r8 uintptr292 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 pointer296 // 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 register434+ 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) {
604604 // than sysargs holds and RegisterFunc must reject the registration.
605605 type bigStruct struct{ A, B, C uint64 } // larger than two eightbytes
606606
607- if !purego.StructReturnInMemory(reflect.TypeFor[bigStruct]().Size()) {
607+ if !purego.StructReturnInMemory(reflect.TypeFor[bigStruct]()) {
608608 t.Skipf("GOARCH=%s does not return large structs via a hidden integer argument", runtime.GOARCH)
609609 }
610610
@@ -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 eightbytes605 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 (
1313 // structReturnInMemory reports whether a struct return value of the given size
1414 // is returned through a caller-allocated hidden pointer passed as the first
1515 // 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()
1718 if size == 0 {
1819 return false
1920 }
@@ -38,7 +39,7 @@ func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) {
3839 switch {
3940 case outSize == 0:
4041 return reflect.New(outType).Elem()
41- case structReturnInMemory(outSize):
42+ case structReturnInMemory(outType):
4243 // Returned through the caller-allocated hidden pointer, which the
4344 // callee also returns in RAX.
4445 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 size13 // structReturnInMemory reports whether a struct return value of the given size
14 // is returned through a caller-allocated hidden pointer passed as the first14 // 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 false19 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 the43 // 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
3131 // structReturnInMemory always reports false on arm: an indirect struct return
3232 // is recovered from the pointer the callee leaves in a1 (see getStruct) rather
3333 // than through a caller-allocated hidden first argument.
34-func structReturnInMemory(size uintptr) bool {
34+func structReturnInMemory(reflect.Type) bool {
3535 return false
3636 }
3737
@@ -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 return31 // structReturnInMemory always reports false on arm: an indirect struct return
32 // is recovered from the pointer the callee leaves in a1 (see getStruct) rather32 // 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 false35 return false
36 }36 }
37 37
M struct_arm64.go +1 -1
@@ -17,7 +17,7 @@ import (
1717 // structReturnInMemory always reports false on arm64: a struct returned in
1818 // memory is passed through the dedicated indirect result register (R8), not as
1919 // an ordinary integer argument, so it is handled separately.
20-func structReturnInMemory(size uintptr) bool {
20+func structReturnInMemory(reflect.Type) bool {
2121 return false
2222 }
2323
@@ -17,7 +17,7 @@ import (
17 // structReturnInMemory always reports false on arm64: a struct returned in17 // structReturnInMemory always reports false on arm64: a struct returned in
18 // memory is passed through the dedicated indirect result register (R8), not as18 // 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 false21 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) {
7272 }
7373 }
7474
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
8080 }
8181
8282 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 size75+// structReturnInMemory reports whether a struct return value is returned through
76-// is returned through a caller-allocated hidden pointer passed as the first76+// 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 > maxRegAllocStructSize79+ 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 {
2222 panic("purego: struct returns are not supported on this architecture")
2323 }
2424
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 {
2929 return false
3030 }
3131
@@ -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 size25+// structReturnInMemory reports whether a struct return value is returned through
26-// is returned through a caller-allocated hidden pointer. Structs are unsupported26+// 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 false29 return false
30 }30 }
31 31
M struct_ppc64le.go +117 -95
@@ -4,84 +4,76 @@
44 package purego
55
66 import (
7+ "math"
78 "reflect"
89 "unsafe"
910 )
1011
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
1616 }
1717
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
4928 }
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)
5035 }
51-
52- return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem()
53-
5436 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})
5838 }
5939 }
6040
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
7047 }
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+ }
7456 }
57+ return leaves, true
58+}
7559
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
7770 }
7871
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 {
8473 size := v.Type().Size()
74+ if size == 0 {
75+ return keepAlive
76+ }
8577
8678 var ptr unsafe.Pointer
8779 if v.CanAddr() {
@@ -93,58 +85,88 @@ func placeSmallAggregatePPC64LE(
9385 keepAlive = append(keepAlive, tmp.Interface())
9486 }
9587
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+ }
10499 }
105100
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])))
115111 }
116112 }
117113
118114 return keepAlive
119115 }
120116
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
126152 }
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()
131154 }
132155
156+// shouldBundleStackArgs always returns false on ppc64le
157+// since C-style stack argument bundling is only needed on Darwin ARM64.
133158 func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
134- // PPC64LE does not bundle stack args
135159 return false
136160 }
137161
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")
146167 }
147168
169+// bundleStackArgs is not used on ppc64le.
148170 func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
149171 panic("purego: bundleStackArgs should not be called on ppc64le")
150172 }
@@ -4,84 +4,76 @@
4 package purego4 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 size12+// 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 first13+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 bytes26+ // Blank fields are padding, not counted members.
27- var buf [16]byte27+ 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 a137+ *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.Pointer78 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]byte88+ // 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 keepAlive114 return keepAlive
119 }115 }
120 116
121-// placeStack is a fallback for structs that are too large to fit in registers117+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 keepAlive121+
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 false159 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 (
88 "unsafe"
99 )
1010
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
1616 }
1717
1818 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 size11+// structReturnInMemory reports whether a struct return value is returned through
12-// is returned through a caller-allocated hidden pointer passed as the first12+// 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 > maxRegAllocStructSize15+ 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 (
88 "unsafe"
99 )
1010
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
1616 }
1717
1818 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 size11+// structReturnInMemory reports whether a struct return value is returned through
12-// is returned through a caller-allocated hidden pointer passed as the first12+// 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 > maxRegAllocStructSize15+ 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 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
33
4-//go:build (darwin || linux || windows) && (amd64 || arm64 || loong64)
4+//go:build (darwin || linux || windows) && (amd64 || arm64 || loong64 || ppc64le)
55
66 package purego_test
77
@@ -282,24 +282,32 @@ func TestRegisterFunc_structArgs(t *testing.T) {
282282 if ret := LargeFloatStructFn(LargeFloatStruct{1, 2, 3, 4, 5, -5}); ret != expectedDouble {
283283 t.Fatalf("LargeFloatStructFn returned %f wanted %f", ret, expectedFloat)
284284 }
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+ }
291295 }
292296 }
293297 {
294298 type Rect struct {
295299 x, y, w, h float64
296300 }
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+ }
303311 }
304312 var RectangleSubtract func(rect Rect) float64
305313 register(&RectangleSubtract, lib, "RectangleSubtract", func(rect Rect) float64 {
@@ -789,8 +797,10 @@ func TestRegisterFunc_structArgs(t *testing.T) {
789797 runtime.KeepAlive(a)
790798 runtime.KeepAlive(c)
791799 }
792- {
800+ if runtime.GOARCH != "ppc64le" {
793801 // 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.
794804 type IntLessThan16Bytes struct{ X, Y int64 }
795805 var fn func(int64, float64, IntLessThan16Bytes) IntLessThan16Bytes
796806 register(&fn, lib, "IdentityTwoInt64AfterPrims", func(x int64, y float64, s IntLessThan16Bytes) IntLessThan16Bytes {
@@ -1116,73 +1126,79 @@ func TestRegisterFunc_structReturns(t *testing.T) {
11161126 }
11171127 }
11181128
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+ }
11561145 }
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+ }
11631159 }
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+ }
11691172 }
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+ }
11771185 }
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)
11841201 }
1185- runtime.KeepAlive(ptr)
11861202 }
11871203 {
11881204 type Mixed5 struct {
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors2 // 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_test6 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) float64285+ 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.f287+ // 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 float64299 x, y, w, h float64
296 }300 }
297- var RectangleWithRegs func(a, b, c, d, e float64, rect Rect) float64301+ 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.h303+ // 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) float64312 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) IntLessThan16Bytes805 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 float321131+ // does not model for scalars, so a trailing integer arg is misplaced.
1122- B int321132+ // 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) Mixed11134+ {
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 float321143+ t.Fatalf("ReturnMixed1 returned %+v wanted %+v", ret, expected)
1134- B int321144+ }
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 float641149+ B int32
1161- B uint321150+ C float32
1162- C float321151+ 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) Mixed41160+ {
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 *int641176+ B uint32
1174- B int321177+ C float32
1175- C float321178+ }
1176- D int321179+ 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) Mixed51186+ {
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
107107 MOVD R3, syscallArgs_a1(R11)
108108 MOVD R4, syscallArgs_a2(R11)
109109
110- // Store float return values (F1-F4)
110+ // Store float return values F1-F8 (an HFA may span up to eight).
111111 FMOVD F1, syscallArgs_f1(R11)
112112 FMOVD F2, syscallArgs_f2(R11)
113113 FMOVD F3, syscallArgs_f3(R11)
114114 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)
115119
116120 // Epilogue: restore and return
117121 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 return120 // Epilogue: restore and return
117 MOVD LR_SAVE(R1), R12121 MOVD LR_SAVE(R1), R12
M syscall_unix.go +23 -4
@@ -6,6 +6,7 @@
66 package purego
77
88 import (
9+ "math"
910 "reflect"
1011 "runtime"
1112 "sync"
@@ -172,10 +173,13 @@ func callbackWrap(a *callbackArgs) {
172173 args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType)
173174 } else if stackFrame != nil {
174175 // 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":
176180 // s390x big-endian: sub-8-byte values are right-justified
177181 args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType)
178- } else {
182+ default:
179183 args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem()
180184 }
181185 stackSlot += slots
@@ -184,10 +188,13 @@ func callbackWrap(a *callbackArgs) {
184188 stackSlot += slots
185189 }
186190 } 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":
188195 // s390x big-endian: float32 is right-justified in 8-byte FPR slot
189196 args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType)
190- } else {
197+ default:
191198 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem()
192199 }
193200 }
@@ -287,6 +294,18 @@ func callbackArgFromStack(argsBase unsafe.Pointer, stackSlot int, stackByteOffse
287294 return reflect.NewAt(inType, ptr).Elem()
288295 }
289296
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+
290309 // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures.
291310 // On s390x:
292311 // - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size)
@@ -6,6 +6,7 @@
6 package purego6 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 stackFrame175 // 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-justified180 // 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 += slots185 stackSlot += slots
@@ -184,10 +188,13 @@ func callbackWrap(a *callbackArgs) {
184 stackSlot += slots188 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 slot195 // 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)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close