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