1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3
4package purego
5
6import (
7 "math"
8 "reflect"
9 "runtime"
10 "unsafe"
11)
12
13func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
14 outSize := outType.Size()
15 switch {
16 case outSize == 0:
17 return reflect.New(outType).Elem()
18 case outSize <= 8:
19 if isAllFloats(outType) {
20 // 2 float32s or 1 float64s are return in the float register
21 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.f1})).Elem()
22 }
23 // up to 8 bytes is returned in RAX
24 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem()
25 case outSize <= 16:
26 r1, r2 := syscall.a1, syscall.a2
27 if isAllFloats(outType) {
28 r1 = syscall.f1
29 r2 = syscall.f2
30 } else {
31 // check first 8 bytes if it's floats
32 hasFirstFloat := false
33 f1 := outType.Field(0).Type
34 if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && outType.Field(1).Type.Kind() == reflect.Float32 {
35 r1 = syscall.f1
36 hasFirstFloat = true
37 }
38
39 // find index of the field that starts the second 8 bytes
40 var i int
41 for i = 0; i < outType.NumField(); i++ {
42 if outType.Field(i).Offset == 8 {
43 break
44 }
45 }
46
47 // check last 8 bytes if they are floats
48 f1 = outType.Field(i).Type
49 if f1.Kind() == reflect.Float64 || f1.Kind() == reflect.Float32 && i+1 == outType.NumField() {
50 r2 = syscall.f1
51 } else if hasFirstFloat {
52 // if the first field was a float then that means the second integer field
53 // comes from the first integer register
54 r2 = syscall.a1
55 }
56 }
57 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
58 default:
59 // create struct from the Go pointer created above
60 // weird pointer dereference to circumvent go vet
61 return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
62 }
63}
64
65func isAllFloats(ty reflect.Type) bool {
66 for i := 0; i < ty.NumField(); i++ {
67 f := ty.Field(i)
68 switch f.Type.Kind() {
69 case reflect.Float64, reflect.Float32:
70 default:
71 return false
72 }
73 }
74 return true
75}
76
77// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
78// https://gitlab.com/x86-psABIs/x86-64-ABI
79// Class determines where the 8 byte value goes.
80// Higher value classes win over lower value classes
81const (
82 _NO_CLASS = 0b0000
83 _SSE = 0b0001
84 _X87 = 0b0011 // long double not used in Go
85 _INTEGER = 0b0111
86 _MEMORY = 0b1111
87)
88
89func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
90 if v.Type().Size() == 0 {
91 return keepAlive
92 }
93
94 // if greater than 64 bytes place on stack
95 if v.Type().Size() > 8*8 {
96 placeStack(v, addStack)
97 return keepAlive
98 }
99 var (
100 savedNumFloats = *numFloats
101 savedNumInts = *numInts
102 savedNumStack = *numStack
103 )
104 placeOnStack := postMerger(v.Type()) || !tryPlaceRegister(v, addFloat, addInt)
105 if placeOnStack {
106 // reset any values placed in registers
107 *numFloats = savedNumFloats
108 *numInts = savedNumInts
109 *numStack = savedNumStack
110 placeStack(v, addStack)
111 }
112 return keepAlive
113}
114
115func postMerger(t reflect.Type) (passInMemory bool) {
116 // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isn’t SSE or any other
117 // eightbyte isn’t SSEUP, the whole argument is passed in memory.
118 if t.Kind() != reflect.Struct {
119 return false
120 }
121 if t.Size() <= 2*8 {
122 return false
123 }
124 return true // Go does not have an SSE/SSEUP type so this is always true
125}
126
127func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
128 ok = true
129 var val uint64
130 var shift byte // # of bits to shift
131 var flushed bool
132 class := _NO_CLASS
133 flushIfNeeded := func() {
134 if flushed {
135 return
136 }
137 flushed = true
138 if class == _SSE {
139 addFloat(uintptr(val))
140 } else {
141 addInt(uintptr(val))
142 }
143 val = 0
144 shift = 0
145 class = _NO_CLASS
146 }
147 var place func(v reflect.Value)
148 place = func(v reflect.Value) {
149 var numFields int
150 if v.Kind() == reflect.Struct {
151 numFields = v.Type().NumField()
152 } else {
153 numFields = v.Type().Len()
154 }
155
156 for i := 0; i < numFields; i++ {
157 flushed = false
158 var f reflect.Value
159 if v.Kind() == reflect.Struct {
160 f = v.Field(i)
161 } else {
162 f = v.Index(i)
163 }
164 switch f.Kind() {
165 case reflect.Struct:
166 place(f)
167 case reflect.Bool:
168 if f.Bool() {
169 val |= 1 << shift
170 }
171 shift += 8
172 class |= _INTEGER
173 case reflect.Pointer, reflect.UnsafePointer:
174 val = uint64(f.Pointer())
175 shift = 64
176 class = _INTEGER
177 case reflect.Int8:
178 val |= uint64(f.Int()&0xFF) << shift
179 shift += 8
180 class |= _INTEGER
181 case reflect.Int16:
182 val |= uint64(f.Int()&0xFFFF) << shift
183 shift += 16
184 class |= _INTEGER
185 case reflect.Int32:
186 val |= uint64(f.Int()&0xFFFF_FFFF) << shift
187 shift += 32
188 class |= _INTEGER
189 case reflect.Int64, reflect.Int:
190 val = uint64(f.Int())
191 shift = 64
192 class = _INTEGER
193 case reflect.Uint8:
194 val |= f.Uint() << shift
195 shift += 8
196 class |= _INTEGER
197 case reflect.Uint16:
198 val |= f.Uint() << shift
199 shift += 16
200 class |= _INTEGER
201 case reflect.Uint32:
202 val |= f.Uint() << shift
203 shift += 32
204 class |= _INTEGER
205 case reflect.Uint64, reflect.Uint, reflect.Uintptr:
206 val = f.Uint()
207 shift = 64
208 class = _INTEGER
209 case reflect.Float32:
210 val |= uint64(math.Float32bits(float32(f.Float()))) << shift
211 shift += 32
212 class |= _SSE
213 case reflect.Float64:
214 if v.Type().Size() > 16 {
215 ok = false
216 return
217 }
218 val = uint64(math.Float64bits(f.Float()))
219 shift = 64
220 class = _SSE
221 case reflect.Array:
222 place(f)
223 default:
224 panic("purego: unsupported kind " + f.Kind().String())
225 }
226
227 if shift == 64 {
228 flushIfNeeded()
229 } else if shift > 64 {
230 // Should never happen, but may if we forget to reset shift after flush (or forget to flush),
231 // better fall apart here, than corrupt arguments.
232 panic("purego: tryPlaceRegisters shift > 64")
233 }
234 }
235 }
236
237 place(v)
238 flushIfNeeded()
239 return ok
240}
241
242func placeStack(v reflect.Value, addStack func(uintptr)) {
243 // Copy the struct as a contiguous block of memory in eightbyte (8-byte)
244 // chunks. The x86-64 ABI requires structs passed on the stack to be
245 // laid out exactly as in memory, including padding and field packing
246 // within eightbytes. Decomposing field-by-field would place each field
247 // as a separate stack slot, breaking structs with mixed-type fields
248 // that share an eightbyte (e.g. int32 + float32).
249 if !v.CanAddr() {
250 tmp := reflect.New(v.Type()).Elem()
251 tmp.Set(v)
252 v = tmp
253 }
254 ptr := v.Addr().UnsafePointer()
255 size := v.Type().Size()
256 for off := uintptr(0); off < size; off += 8 {
257 chunk := *(*uintptr)(unsafe.Add(ptr, off))
258 addStack(chunk)
259 }
260}
261
262func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
263 panic("purego: placeRegisters not implemented on amd64")
264}
265
266// shouldBundleStackArgs always returns false on non-Darwin platforms
267// since C-style stack argument bundling is only needed on Darwin ARM64.
268func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
269 return false
270}
271
272// structFitsInRegisters is not used on amd64.
273func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {
274 panic("purego: structFitsInRegisters should not be called on amd64")
275}
276
277// collectStackArgs is not used on amd64.
278func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
279 keepAlive []any, addInt, addFloat, addStack func(uintptr),
280 pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
281 panic("purego: collectStackArgs should not be called on amd64")
282}
283
284// bundleStackArgs is not used on amd64.
285func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
286 panic("purego: bundleStackArgs should not be called on amd64")
287}
288
289// getCallbackStruct reads a struct argument from the callback frame on amd64.
290// It mirrors the SysV AMD64 ABI rules used by addStruct for the Go→C path.
291//
292// getCallbackStruct is only used on Unix. On Windows, callbacks are handled by
293// the runtime's own callback mechanism, so this function is compiled but unused.
294//
295// SysV AMD64 struct argument passing rules:
296// - Struct > 16 bytes (postMerger): passed on the stack as raw bytes
297// - Struct ≤ 16 bytes: classify each eightbyte (INTEGER or SSE),
298// read from the appropriate register class
299// - If not enough registers for all eightbytes: entire struct goes on the stack
300func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
301 switch runtime.GOOS {
302 case "darwin", "freebsd", "linux", "netbsd":
303 default:
304 panic("purego: getCallbackStruct is not supported on " + runtime.GOOS)
305 }
306
307 f := (*[callbackMaxFrame]uintptr)(frame)
308 size := inType.Size()
309
310 // Structs > 16 bytes are passed on the stack as raw bytes (SysV ABI MEMORY class).
311 if postMerger(inType) {
312 numSlots := int((size + 7) / 8)
313 v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem()
314 *stackSlot += numSlots
315 return v
316 }
317
318 // Struct ≤ 16 bytes: classify each eightbyte and read from the appropriate register.
319 numEightbytes := int((size + 7) / 8)
320
321 // Count how many integer and SSE registers this struct needs.
322 var needInts, needFloats int
323 for i := 0; i < numEightbytes; i++ {
324 class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)
325 if class == _SSE {
326 needFloats++
327 } else {
328 needInts++
329 }
330 }
331
332 // If not enough registers for all eightbytes, the entire struct goes on the stack.
333 if *intsN+needInts > numOfIntegerRegisters() || *floatsN+needFloats > numOfFloatRegisters() {
334 v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem()
335 *stackSlot += numEightbytes
336 return v
337 }
338
339 // Read each eightbyte from its appropriate register class.
340 var r1, r2 uintptr
341 for i := 0; i < numEightbytes; i++ {
342 class := classifyEightbyte(inType, uintptr(i)*8, uintptr(i)*8+8)
343 if class == _SSE {
344 if i == 0 {
345 r1 = f[*floatsN]
346 } else {
347 r2 = f[*floatsN]
348 }
349 *floatsN++
350 } else {
351 if i == 0 {
352 r1 = f[numOfFloatRegisters()+*intsN]
353 } else {
354 r2 = f[numOfFloatRegisters()+*intsN]
355 }
356 *intsN++
357 }
358 }
359
360 if numEightbytes == 1 {
361 return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
362 }
363 return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
364}
365
366func setStruct(a *callbackArgs, ret reflect.Value) {
367 outSize := ret.Type().Size()
368 switch {
369 case outSize == 0:
370 return
371 case outSize <= 16:
372 // Copy the struct's raw bytes (including padding) into a buffer.
373 var buf [2]uintptr
374 reflect.NewAt(ret.Type(), unsafe.Pointer(&buf[0])).Elem().Set(ret)
375 // Classify each eightbyte by the SysV ABI rules (§3.2.3, rule 4d
376 // of https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf).
377 // INTEGER wins over SSE for mixed eightbytes. Place INTEGER eightbytes in
378 // result[0]/result[1] (AX/DX) and SSE eightbytes in result[2]/result[3]
379 // (XMM0/XMM1).
380 // Assign each eightbyte to the next available register of the
381 // appropriate class. The ABI counts integer (AX, DX) and SSE
382 // (XMM0, XMM1) return registers independently.
383 var numInts int
384 var numFloats int
385 for i := 0; i < 2 && uintptr(i)*8 < outSize; i++ {
386 class := classifyEightbyte(ret.Type(), uintptr(i)*8, uintptr(i)*8+8)
387 if class == _SSE {
388 switch numFloats {
389 case 0:
390 a.result[2] = buf[i]
391 case 1:
392 a.result[3] = buf[i]
393 }
394 numFloats++
395 } else {
396 switch numInts {
397 case 0:
398 a.result[0] = buf[i]
399 case 1:
400 a.result[1] = buf[i]
401 }
402 numInts++
403 }
404 }
405 default:
406 // Structs > 16 bytes are returned by hidden pointer.
407 // a.result[0] contains the pointer passed by the caller in RDI.
408 // Write the struct through this pointer.
409 reflect.NewAt(ret.Type(), *(*unsafe.Pointer)(unsafe.Pointer(&a.result[0]))).Elem().Set(ret)
410 }
411}
412
413// classifyEightbyte returns the SysV ABI class for the byte range [start, end)
414// within a type, by examining all scalar fields that overlap that range.
415func classifyEightbyte(t reflect.Type, start, end uintptr) int {
416 return doClassifyEightbyte(t, 0, start, end)
417}
418
419func doClassifyEightbyte(t reflect.Type, base, start, end uintptr) int {
420 switch t.Kind() {
421 case reflect.Struct:
422 class := _NO_CLASS
423 for i := 0; i < t.NumField(); i++ {
424 f := t.Field(i)
425 class |= doClassifyEightbyte(f.Type, base+f.Offset, start, end)
426 }
427 return class
428 case reflect.Array:
429 class := _NO_CLASS
430 elemSize := t.Elem().Size()
431 for i := 0; i < t.Len(); i++ {
432 class |= doClassifyEightbyte(t.Elem(), base+uintptr(i)*elemSize, start, end)
433 }
434 return class
435 default:
436 fStart := base
437 fEnd := base + t.Size()
438 if fStart >= end || fEnd <= start {
439 return _NO_CLASS
440 }
441 switch t.Kind() {
442 case reflect.Float32, reflect.Float64:
443 return _SSE
444 default:
445 return _INTEGER
446 }
447 }
448}