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 isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
20 r1 = syscall.f1
21 if numFields == 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 isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
29 switch numFields {
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 isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 {
46 switch numFields {
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 []any) []any {
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() > numOfFloatRegisters {
77 *numFloats = numOfFloatRegisters
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 align := byte(f.Type().Align()*8 - 1)
111 shift = (shift + align) &^ align
112 if shift >= 64 {
113 shift = 0
114 flushed = true
115 if class == _FLOAT {
116 addFloat(uintptr(val))
117 } else {
118 addInt(uintptr(val))
119 }
120 }
121 switch f.Type().Kind() {
122 case reflect.Struct:
123 place(f)
124 case reflect.Bool:
125 if f.Bool() {
126 val |= 1
127 }
128 shift += 8
129 class |= _INT
130 case reflect.Uint8:
131 val |= f.Uint() << shift
132 shift += 8
133 class |= _INT
134 case reflect.Uint16:
135 val |= f.Uint() << shift
136 shift += 16
137 class |= _INT
138 case reflect.Uint32:
139 val |= f.Uint() << shift
140 shift += 32
141 class |= _INT
142 case reflect.Uint64, reflect.Uint, reflect.Uintptr:
143 addInt(uintptr(f.Uint()))
144 shift = 0
145 flushed = true
146 class = _NO_CLASS
147 case reflect.Int8:
148 val |= uint64(f.Int()&0xFF) << shift
149 shift += 8
150 class |= _INT
151 case reflect.Int16:
152 val |= uint64(f.Int()&0xFFFF) << shift
153 shift += 16
154 class |= _INT
155 case reflect.Int32:
156 val |= uint64(f.Int()&0xFFFF_FFFF) << shift
157 shift += 32
158 class |= _INT
159 case reflect.Int64, reflect.Int:
160 addInt(uintptr(f.Int()))
161 shift = 0
162 flushed = true
163 class = _NO_CLASS
164 case reflect.Float32:
165 if class == _FLOAT {
166 addFloat(uintptr(val))
167 val = 0
168 shift = 0
169 }
170 val |= uint64(math.Float32bits(float32(f.Float()))) << shift
171 shift += 32
172 class |= _FLOAT
173 case reflect.Float64:
174 addFloat(uintptr(math.Float64bits(float64(f.Float()))))
175 shift = 0
176 flushed = true
177 class = _NO_CLASS
178 case reflect.Ptr:
179 addInt(f.Pointer())
180 shift = 0
181 flushed = true
182 class = _NO_CLASS
183 case reflect.Array:
184 place(f)
185 default:
186 panic("purego: unsupported kind " + f.Kind().String())
187 }
188 }
189 }
190 place(v)
191 if !flushed {
192 if class == _FLOAT {
193 addFloat(uintptr(val))
194 } else {
195 addInt(uintptr(val))
196 }
197 }
198}
199
200func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any {
201 // Struct is too big to be placed in registers.
202 // Copy to heap and place the pointer in register
203 ptrStruct := reflect.New(v.Type())
204 ptrStruct.Elem().Set(v)
205 ptr := ptrStruct.Elem().Addr().UnsafePointer()
206 keepAlive = append(keepAlive, ptr)
207 addInt(uintptr(ptr))
208 return keepAlive
209}
210
211// isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a
212// Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]).
213// This type of struct will be placed more compactly than the individual fields.
214//
215// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
216func isHFA(t reflect.Type) bool {
217 // round up struct size to nearest 8 see section B.4
218 structSize := roundUpTo8(t.Size())
219 if structSize == 0 || t.NumField() > 4 {
220 return false
221 }
222 first := t.Field(0)
223 switch first.Type.Kind() {
224 case reflect.Float32, reflect.Float64:
225 firstKind := first.Type.Kind()
226 for i := 0; i < t.NumField(); i++ {
227 if t.Field(i).Type.Kind() != firstKind {
228 return false
229 }
230 }
231 return true
232 case reflect.Array:
233 switch first.Type.Elem().Kind() {
234 case reflect.Float32, reflect.Float64:
235 return true
236 default:
237 return false
238 }
239 case reflect.Struct:
240 for i := 0; i < first.Type.NumField(); i++ {
241 if !isHFA(first.Type) {
242 return false
243 }
244 }
245 return true
246 default:
247 return false
248 }
249}
250
251// isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type
252// and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]).
253// A short vector is a machine type that is composed of repeated instances of one fundamental integral or
254// floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]).
255// This type of struct will be placed more compactly than the individual fields.
256//
257// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
258func isHVA(t reflect.Type) bool {
259 // round up struct size to nearest 8 see section B.4
260 structSize := roundUpTo8(t.Size())
261 if structSize == 0 || (structSize != 8 && structSize != 16) {
262 return false
263 }
264 first := t.Field(0)
265 switch first.Type.Kind() {
266 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
267 firstKind := first.Type.Kind()
268 for i := 0; i < t.NumField(); i++ {
269 if t.Field(i).Type.Kind() != firstKind {
270 return false
271 }
272 }
273 return true
274 case reflect.Array:
275 switch first.Type.Elem().Kind() {
276 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
277 return true
278 default:
279 return false
280 }
281 default:
282 return false
283 }
284}