1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3
4package purego
5
6import (
7 "math"
8 "reflect"
9 "unsafe"
10)
11
12// ppc64leLeaf is a scalar member of a flattened aggregate at byte offset offset.
13type ppc64leLeaf struct {
14 kind reflect.Kind
15 offset uintptr
16}
17
18// ppc64leFlatten appends the scalar leaves of t to leaves, expanding nested
19// structs and arrays.
20func 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
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)
35 }
36 default:
37 *leaves = append(*leaves, ppc64leLeaf{kind: t.Kind(), offset: base})
38 }
39}
40
41// ppc64leClassifyHFA reports whether t is a homogeneous floating-point
42// aggregate: one to eight members all of the same floating-point type.
43func 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
47 }
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 }
56 }
57 return leaves, true
58}
59
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.
64func structReturnInMemory(outType reflect.Type) bool {
65 if outType.Size() <= maxRegAllocStructSize {
66 return false
67 }
68 _, isHFA := ppc64leClassifyHFA(outType)
69 return !isHFA
70}
71
72func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
73 size := v.Type().Size()
74 if size == 0 {
75 return keepAlive
76 }
77
78 var ptr unsafe.Pointer
79 if v.CanAddr() {
80 ptr = v.Addr().UnsafePointer()
81 } else {
82 tmp := reflect.New(v.Type())
83 tmp.Elem().Set(v)
84 ptr = tmp.UnsafePointer()
85 keepAlive = append(keepAlive, tmp.Interface())
86 }
87
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 }
99 }
100
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])))
111 }
112 }
113
114 return keepAlive
115}
116
117func 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
152 }
153 return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem()
154}
155
156// shouldBundleStackArgs always returns false on ppc64le
157// since C-style stack argument bundling is only needed on Darwin ARM64.
158func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
159 return false
160}
161
162// collectStackArgs is not used on ppc64le.
163func 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")
167}
168
169// bundleStackArgs is not used on ppc64le.
170func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
171 panic("purego: bundleStackArgs should not be called on ppc64le")
172}
173
174func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
175 panic("purego: struct callback arguments are not supported on ppc64le")
176}
177
178func setStruct(a *callbackArgs, ret reflect.Value) {
179 panic("purego: struct returns are not supported on ppc64le")
180}