1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3
4package purego
5
6import (
7 "math"
8 "reflect"
9 "unsafe"
10)
11
12func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
13 outSize := outType.Size()
14 switch {
15 case outSize == 0:
16 return reflect.New(outType).Elem()
17 case outSize <= 8:
18 r1 := syscall.a1
19 if isAllSameFloat(outType) {
20 r1 = syscall.f1
21 if outType.NumField() == 2 {
22 r1 = syscall.f2<<32 | syscall.f1
23 }
24 }
25 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
26 case outSize <= 16:
27 r1, r2 := syscall.a1, syscall.a2
28 if isAllSameFloat(outType) {
29 switch outType.NumField() {
30 case 4:
31 r1 = syscall.f2<<32 | syscall.f1
32 r2 = syscall.f4<<32 | syscall.f3
33 case 3:
34 r1 = syscall.f2<<32 | syscall.f1
35 r2 = syscall.f3
36 case 2:
37 r1 = syscall.f1
38 r2 = syscall.f2
39 default:
40 panic("unreachable")
41 }
42 }
43 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
44 default:
45 if isAllSameFloat(outType) && outType.NumField() <= 4 {
46 switch outType.NumField() {
47 case 4:
48 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
49 case 3:
50 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem()
51 default:
52 panic("unreachable")
53 }
54 }
55 // create struct from the Go pointer created in arm64_r8
56 // weird pointer dereference to circumvent go vet
57 return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.arm64_r8))).Elem()
58 }
59}
60
61// https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
62const (
63 _NO_CLASS = 0b00
64 _FLOAT = 0b01
65 _INT = 0b11
66)
67
68func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
69 if v.Type().Size() == 0 {
70 return keepAlive
71 }
72
73 if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
74 // if this doesn't fit entirely in registers then
75 // each element goes onto the stack
76 if hfa && *numFloats+v.NumField() > numOfFloats {
77 *numFloats = numOfFloats
78 } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
79 *numInts = numOfIntegerRegisters()
80 }
81
82 placeRegisters(v, addFloat, addInt)
83 } else {
84 keepAlive = placeStack(v, keepAlive, addInt)
85 }
86 return keepAlive // the struct was allocated so don't panic
87}
88
89func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
90 var val uint64
91 var shift byte
92 var flushed bool
93 class := _NO_CLASS
94 var place func(v reflect.Value)
95 place = func(v reflect.Value) {
96 var numFields int
97 if v.Kind() == reflect.Struct {
98 numFields = v.Type().NumField()
99 } else {
100 numFields = v.Type().Len()
101 }
102 for k := 0; k < numFields; k++ {
103 flushed = false
104 var f reflect.Value
105 if v.Kind() == reflect.Struct {
106 f = v.Field(k)
107 } else {
108 f = v.Index(k)
109 }
110 if shift >= 64 {
111 shift = 0
112 flushed = true
113 if class == _FLOAT {
114 addFloat(uintptr(val))
115 } else {
116 addInt(uintptr(val))
117 }
118 }
119 switch f.Type().Kind() {
120 case reflect.Struct:
121 place(f)
122 case reflect.Bool:
123 if f.Bool() {
124 val |= 1
125 }
126 shift += 8
127 class |= _INT
128 case reflect.Uint8:
129 val |= f.Uint() << shift
130 shift += 8
131 class |= _INT
132 case reflect.Uint16:
133 val |= f.Uint() << shift
134 shift += 16
135 class |= _INT
136 case reflect.Uint32:
137 val |= f.Uint() << shift
138 shift += 32
139 class |= _INT
140 case reflect.Uint64:
141 addInt(uintptr(f.Uint()))
142 shift = 0
143 flushed = true
144 case reflect.Int8:
145 val |= uint64(f.Int()&0xFF) << shift
146 shift += 8
147 class |= _INT
148 case reflect.Int16:
149 val |= uint64(f.Int()&0xFFFF) << shift
150 shift += 16
151 class |= _INT
152 case reflect.Int32:
153 val |= uint64(f.Int()&0xFFFF_FFFF) << shift
154 shift += 32
155 class |= _INT
156 case reflect.Int64:
157 addInt(uintptr(f.Int()))
158 shift = 0
159 flushed = true
160 case reflect.Float32:
161 if class == _FLOAT {
162 addFloat(uintptr(val))
163 val = 0
164 shift = 0
165 }
166 val |= uint64(math.Float32bits(float32(f.Float()))) << shift
167 shift += 32
168 class |= _FLOAT
169 case reflect.Float64:
170 addFloat(uintptr(math.Float64bits(float64(f.Float()))))
171 shift = 0
172 flushed = true
173 case reflect.Array:
174 place(f)
175 default:
176 panic("purego: unsupported kind " + f.Kind().String())
177 }
178 }
179 }
180 place(v)
181 if !flushed {
182 if class == _FLOAT {
183 addFloat(uintptr(val))
184 } else {
185 addInt(uintptr(val))
186 }
187 }
188}
189
190func placeStack(v reflect.Value, keepAlive []interface{}, addInt func(uintptr)) []interface{} {
191 // Struct is too big to be placed in registers.
192 // Copy to heap and place the pointer in register
193 ptrStruct := reflect.New(v.Type())
194 ptrStruct.Elem().Set(v)
195 ptr := ptrStruct.Elem().Addr().UnsafePointer()
196 keepAlive = append(keepAlive, ptr)
197 addInt(uintptr(ptr))
198 return keepAlive
199}
200
201// isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a
202// Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]).
203// This type of struct will be placed more compactly than the individual fields.
204//
205// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
206func isHFA(t reflect.Type) bool {
207 // round up struct size to nearest 8 see section B.4
208 structSize := roundUpTo8(t.Size())
209 if structSize == 0 || t.NumField() > 4 {
210 return false
211 }
212 first := t.Field(0)
213 switch first.Type.Kind() {
214 case reflect.Float32, reflect.Float64:
215 firstKind := first.Type.Kind()
216 for i := 0; i < t.NumField(); i++ {
217 if t.Field(i).Type.Kind() != firstKind {
218 return false
219 }
220 }
221 return true
222 case reflect.Array:
223 switch first.Type.Elem().Kind() {
224 case reflect.Float32, reflect.Float64:
225 return true
226 default:
227 return false
228 }
229 case reflect.Struct:
230 for i := 0; i < first.Type.NumField(); i++ {
231 if !isHFA(first.Type) {
232 return false
233 }
234 }
235 return true
236 default:
237 return false
238 }
239}
240
241// isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type
242// and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]).
243// A short vector is a machine type that is composed of repeated instances of one fundamental integral or
244// floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]).
245// This type of struct will be placed more compactly than the individual fields.
246//
247// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
248func isHVA(t reflect.Type) bool {
249 // round up struct size to nearest 8 see section B.4
250 structSize := roundUpTo8(t.Size())
251 if structSize == 0 || (structSize != 8 && structSize != 16) {
252 return false
253 }
254 first := t.Field(0)
255 switch first.Type.Kind() {
256 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
257 firstKind := first.Type.Kind()
258 for i := 0; i < t.NumField(); i++ {
259 if t.Field(i).Type.Kind() != firstKind {
260 return false
261 }
262 }
263 return true
264 case reflect.Array:
265 switch first.Type.Elem().Kind() {
266 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
267 return true
268 default:
269 return false
270 }
271 default:
272 return false
273 }
274}