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