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 if isAllFloats(outType) {
19 // 2 float32s or 1 float64s are return in the float register
20 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem()
21 }
22 // up to 8 bytes is returned in RAX
23 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
24 case outSize <= 16:
25 r1, r2 := syscall.a1, syscall.a2
26 if isAllFloats(outType) {
27 r1 = syscall.f1
28 r2 = syscall.f2
29 } else {
30 // check first 8 bytes if it's floats
31 hasFirstFloat := false
32 f1 := outType.Field(0).Type
33 if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 {
34 r1 = syscall.f1
35 hasFirstFloat = true
36 }
37
38 // find index of the field that starts the second 8 bytes
39 var i int
40 for i = 0; i < outType.NumField(); i++ {
41 if outType.Field(i).Offset == 8 {
42 break
43 }
44 }
45
46 // check last 8 bytes if they are floats
47 f1 = outType.Field(i).Type
48 if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() {
49 r2 = syscall.f1
50 } else if hasFirstFloat {
51 // if the first field was a float then that means the second integer field
52 // comes from the first integer register
53 r2 = syscall.a1
54 }
55 }
56 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
57 default:
58 // create struct from the Go pointer created above
59 // weird pointer dereference to circumvent go vet
60 return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
61 }
62}
63
64func isAllFloats(ty reflect.Type) bool {
65 for i := 0; i < ty.NumField(); i++ {
66 f := ty.Field(i)
67 switch f.Type.Kind() {
68 case reflect.Float64, reflect.Float32:
69 default:
70 return false
71 }
72 }
73 return true
74}
75
76// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
77// https://gitlab.com/x86-psABIs/x86-64-ABI
78// Class determines where the 8 byte value goes.
79// Higher value classes win over lower value classes
80const (
81 _NO_CLASS = 0b0000
82 _SSE = 0b0001
83 _X87 = 0b0011 // long double not used in Go
84 _INTEGER = 0b0111
85 _MEMORY = 0b1111
86)
87
88func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} {
89 if v.Type().Size() == 0 {
90 return keepAlive
91 }
92
93 // if greater than 64 bytes place on stack
94 if v.Type().Size() > 8*8 {
95 placeStack(v, addStack)
96 return keepAlive
97 }
98 var (
99 savedNumFloats = *numFloats
100 savedNumInts = *numInts
101 savedNumStack = *numStack
102 )
103 placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt)
104 if placeOnStack {
105 // reset any values placed in registers
106 *numFloats = savedNumFloats
107 *numInts = savedNumInts
108 *numStack = savedNumStack
109 placeStack(v, addStack)
110 }
111 return keepAlive
112}
113
114func postMerger(t reflect.Type) bool {
115 // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other
116 // eightbyte isn’t SSEUP, the whole argument is passed in memory.
117 if t.Kind() != reflect.Struct {
118 return false
119 }
120 if t.Size() <= 2*8 {
121 return false
122 }
123 first := getFirst(t).Kind()
124 if first != reflect.Float32 && first != reflect.Float64 {
125 return false
126 }
127 return true
128}
129
130func getFirst(t reflect.Type) reflect.Type {
131 first := t.Field(0).Type
132 if first.Kind() == reflect.Struct {
133 return getFirst(first)
134 }
135 return first
136}
137
138func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
139 ok = true
140 var val uint64
141 var shift byte // # of bits to shift
142 var flushed bool
143 class := _NO_CLASS
144 flushIfNeeded := func() {
145 if flushed {
146 return
147 }
148 flushed = true
149 if class == _SSE {
150 addFloat(uintptr(val))
151 } else {
152 addInt(uintptr(val))
153 }
154 val = 0
155 shift = 0
156 class = _NO_CLASS
157 }
158 var place func(v reflect.Value)
159 place = func(v reflect.Value) {
160 var numFields int
161 if v.Kind() == reflect.Struct {
162 numFields = v.Type().NumField()
163 } else {
164 numFields = v.Type().Len()
165 }
166
167 for i := 0; i < numFields; i++ {
168 flushed = false
169 var f reflect.Value
170 if v.Kind() == reflect.Struct {
171 f = v.Field(i)
172 } else {
173 f = v.Index(i)
174 }
175 switch f.Kind() {
176 case reflect.Struct:
177 place(f)
178 case reflect.Bool:
179 if f.Bool() {
180 val |= 1
181 }
182 shift += 8
183 class |= _INTEGER
184 case reflect.Pointer:
185 ok = false
186 return
187 case reflect.Int8:
188 val |= uint64(f.Int()&0xFF) << shift
189 shift += 8
190 class |= _INTEGER
191 case reflect.Int16:
192 val |= uint64(f.Int()&0xFFFF) << shift
193 shift += 16
194 class |= _INTEGER
195 case reflect.Int32:
196 val |= uint64(f.Int()&0xFFFF_FFFF) << shift
197 shift += 32
198 class |= _INTEGER
199 case reflect.Int64:
200 val = uint64(f.Int())
201 shift = 64
202 class = _INTEGER
203 case reflect.Uint8:
204 val |= f.Uint() << shift
205 shift += 8
206 class |= _INTEGER
207 case reflect.Uint16:
208 val |= f.Uint() << shift
209 shift += 16
210 class |= _INTEGER
211 case reflect.Uint32:
212 val |= f.Uint() << shift
213 shift += 32
214 class |= _INTEGER
215 case reflect.Uint64:
216 val = f.Uint()
217 shift = 64
218 class = _INTEGER
219 case reflect.Float32:
220 val |= uint64(math.Float32bits(float32(f.Float()))) << shift
221 shift += 32
222 class |= _SSE
223 case reflect.Float64:
224 if v.Type().Size() > 16 {
225 ok = false
226 return
227 }
228 val = uint64(math.Float64bits(f.Float()))
229 shift = 64
230 class = _SSE
231 case reflect.Array:
232 place(f)
233 default:
234 panic("purego: unsupported kind " + f.Kind().String())
235 }
236
237 if shift == 64 {
238 flushIfNeeded()
239 } else if shift > 64 {
240 // Should never happen, but may if we forget to reset shift after flush (or forget to flush),
241 // better fall apart here, than corrupt arguments.
242 panic("purego: tryPlaceRegisters shift > 64")
243 }
244 }
245 }
246
247 place(v)
248 flushIfNeeded()
249 return ok
250}
251
252func placeStack(v reflect.Value, addStack func(uintptr)) {
253 for i := 0; i < v.Type().NumField(); i++ {
254 f := v.Field(i)
255 switch f.Kind() {
256 case reflect.Pointer:
257 addStack(f.Pointer())
258 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
259 addStack(uintptr(f.Int()))
260 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
261 addStack(uintptr(f.Uint()))
262 case reflect.Float32:
263 addStack(uintptr(math.Float32bits(float32(f.Float()))))
264 case reflect.Float64:
265 addStack(uintptr(math.Float64bits(f.Float())))
266 case reflect.Struct:
267 placeStack(f, addStack)
268 default:
269 panic("purego: unsupported kind " + f.Kind().String())
270 }
271 }
272}