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