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 "strconv"
11 stdstrings "strings"
12 "unsafe"
13
14 "github.com/ebitengine/purego/internal/strings"
15)
16
17// structReturnInMemory always reports false on arm64: a struct returned in
18// memory is passed through the dedicated indirect result register (R8), not as
19// an ordinary integer argument, so it is handled separately.
20func structReturnInMemory(reflect.Type) bool {
21 return false
22}
23
24func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) {
25 outSize := outType.Size()
26 switch {
27 case outSize == 0:
28 return reflect.New(outType).Elem()
29 case outSize <= 8:
30 r1 := syscall.a1
31 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
32 r1 = syscall.f1
33 if numFields == 2 {
34 r1 = syscall.f2<<32 | syscall.f1&math.MaxUint32
35 }
36 }
37 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
38 case outSize <= 16:
39 r1, r2 := syscall.a1, syscall.a2
40 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
41 switch numFields {
42 case 4:
43 r1 = syscall.f2<<32 | syscall.f1&math.MaxUint32
44 r2 = syscall.f4<<32 | syscall.f3&math.MaxUint32
45 case 3:
46 r1 = syscall.f2<<32 | syscall.f1&math.MaxUint32
47 r2 = syscall.f3
48 case 2:
49 r1 = syscall.f1
50 r2 = syscall.f2
51 default:
52 panic("not reached")
53 }
54 }
55 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
56 default:
57 if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 {
58 switch numFields {
59 case 4:
60 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
61 case 3:
62 return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c uintptr }{syscall.f1, syscall.f2, syscall.f3})).Elem()
63 default:
64 panic("not reached")
65 }
66 }
67 // create struct from the Go pointer created in arm64_r8
68 // weird pointer dereference to circumvent go vet
69 return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.arm64_r8))).Elem()
70 }
71}
72
73// https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
74const (
75 _NO_CLASS = 0b00
76 _FLOAT = 0b01
77 _INT = 0b11
78)
79
80func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
81 if v.Type().Size() == 0 {
82 return keepAlive
83 }
84
85 if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
86 // if this doesn't fit entirely in registers then
87 // each element goes onto the stack
88 if hfa && *numFloats+numABIFields(v.Type()) > numOfFloatRegisters() {
89 *numFloats = numOfFloatRegisters()
90 } else if hva && *numInts+numABIFields(v.Type()) > numOfIntegerRegisters() {
91 *numInts = numOfIntegerRegisters()
92 }
93
94 placeRegisters(v, addFloat, addInt)
95 } else {
96 keepAlive = placeStack(v, keepAlive, addInt)
97 }
98 return keepAlive // the struct was allocated so don't panic
99}
100
101func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
102 if isDarwin {
103 placeRegistersDarwin(v, addFloat, addInt)
104 return
105 }
106 placeRegistersArm64(v, addFloat, addInt)
107}
108
109func placeRegistersArm64(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
110 var val uint64
111 var shift byte
112 var flushed bool
113 class := _NO_CLASS
114 var place func(v reflect.Value)
115 place = func(v reflect.Value) {
116 var numFields int
117 if v.Kind() == reflect.Struct {
118 numFields = v.Type().NumField()
119 } else {
120 numFields = v.Type().Len()
121 }
122 for k := 0; k < numFields; k++ {
123 if v.Kind() == reflect.Struct && !isABIField(v.Type().Field(k)) {
124 continue
125 }
126 flushed = false
127 var f reflect.Value
128 if v.Kind() == reflect.Struct {
129 f = v.Field(k)
130 } else {
131 f = v.Index(k)
132 }
133 align := byte(f.Type().Align()*8 - 1)
134 shift = (shift + align) &^ align
135 if shift >= 64 {
136 shift = 0
137 flushed = true
138 if class == _FLOAT {
139 addFloat(uintptr(val))
140 } else {
141 addInt(uintptr(val))
142 }
143 val = 0
144 class = _NO_CLASS
145 }
146 switch f.Type().Kind() {
147 case reflect.Struct:
148 place(f)
149 case reflect.Bool:
150 if f.Bool() {
151 val |= 1 << shift
152 }
153 shift += 8
154 class |= _INT
155 case reflect.Uint8:
156 val |= f.Uint() << shift
157 shift += 8
158 class |= _INT
159 case reflect.Uint16:
160 val |= f.Uint() << shift
161 shift += 16
162 class |= _INT
163 case reflect.Uint32:
164 val |= f.Uint() << shift
165 shift += 32
166 class |= _INT
167 case reflect.Uint64, reflect.Uint, reflect.Uintptr:
168 addInt(uintptr(f.Uint()))
169 shift = 0
170 flushed = true
171 class = _NO_CLASS
172 case reflect.Int8:
173 val |= uint64(f.Int()&0xFF) << shift
174 shift += 8
175 class |= _INT
176 case reflect.Int16:
177 val |= uint64(f.Int()&0xFFFF) << shift
178 shift += 16
179 class |= _INT
180 case reflect.Int32:
181 val |= uint64(f.Int()&0xFFFF_FFFF) << shift
182 shift += 32
183 class |= _INT
184 case reflect.Int64, reflect.Int:
185 addInt(uintptr(f.Int()))
186 shift = 0
187 flushed = true
188 class = _NO_CLASS
189 case reflect.Float32:
190 if class == _FLOAT {
191 addFloat(uintptr(val))
192 val = 0
193 shift = 0
194 }
195 val |= uint64(math.Float32bits(float32(f.Float()))) << shift
196 shift += 32
197 class |= _FLOAT
198 case reflect.Float64:
199 addFloat(uintptr(math.Float64bits(float64(f.Float()))))
200 shift = 0
201 flushed = true
202 class = _NO_CLASS
203 case reflect.Pointer, reflect.UnsafePointer:
204 addInt(f.Pointer())
205 shift = 0
206 flushed = true
207 class = _NO_CLASS
208 case reflect.Array:
209 place(f)
210 default:
211 panic("purego: unsupported kind " + f.Kind().String())
212 }
213 }
214 }
215 place(v)
216 if !flushed {
217 if class == _FLOAT {
218 addFloat(uintptr(val))
219 } else {
220 addInt(uintptr(val))
221 }
222 }
223}
224
225func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any {
226 // Struct is too big to be placed in registers.
227 // Copy to heap and place the pointer in register
228 ptrStruct := reflect.New(v.Type())
229 ptrStruct.Elem().Set(v)
230 ptr := ptrStruct.Elem().Addr().UnsafePointer()
231 keepAlive = append(keepAlive, ptr)
232 addInt(uintptr(ptr))
233 return keepAlive
234}
235
236// isHFA reports a Homogeneous Floating-point Aggregate (HFA) which is a Fundamental Data Type that is a
237// Floating-Point type and at most four uniquely addressable members (5.9.5.1 in [Arm64 Calling Convention]).
238// This type of struct will be placed more compactly than the individual fields.
239//
240// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
241func isHFA(t reflect.Type) bool {
242 // round up struct size to nearest 8 see section B.4
243 structSize := roundUpTo8(t.Size())
244 numFields := numABIFields(t)
245 if structSize == 0 || numFields > 4 {
246 return false
247 }
248 first := abiField(t, 0)
249 switch first.Type.Kind() {
250 case reflect.Float32, reflect.Float64:
251 firstKind := first.Type.Kind()
252 for i := 0; i < numFields; i++ {
253 if abiField(t, i).Type.Kind() != firstKind {
254 return false
255 }
256 }
257 return true
258 case reflect.Array:
259 switch first.Type.Elem().Kind() {
260 case reflect.Float32, reflect.Float64:
261 return true
262 default:
263 return false
264 }
265 case reflect.Struct:
266 for i := 0; i < numABIFields(first.Type); i++ {
267 if !isHFA(first.Type) {
268 return false
269 }
270 }
271 return true
272 default:
273 return false
274 }
275}
276
277// isHVA reports a Homogeneous Aggregate with a Fundamental Data Type that is a Short-Vector type
278// and at most four uniquely addressable members (5.9.5.2 in [Arm64 Calling Convention]).
279// A short vector is a machine type that is composed of repeated instances of one fundamental integral or
280// floating-point type. It may be 8 or 16 bytes in total size (5.4 in [Arm64 Calling Convention]).
281// This type of struct will be placed more compactly than the individual fields.
282//
283// [Arm64 Calling Convention]: https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst
284func isHVA(t reflect.Type) bool {
285 // round up struct size to nearest 8 see section B.4
286 structSize := roundUpTo8(t.Size())
287 if structSize == 0 || (structSize != 8 && structSize != 16) {
288 return false
289 }
290 numFields := numABIFields(t)
291 first := abiField(t, 0)
292 switch first.Type.Kind() {
293 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
294 firstKind := first.Type.Kind()
295 for i := 0; i < numFields; i++ {
296 if abiField(t, i).Type.Kind() != firstKind {
297 return false
298 }
299 }
300 return true
301 case reflect.Array:
302 switch first.Type.Elem().Kind() {
303 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Int8, reflect.Int16, reflect.Int32:
304 return true
305 default:
306 return false
307 }
308 default:
309 return false
310 }
311}
312
313// copyStruct8ByteChunks copies struct memory in 8-byte chunks to the provided callback.
314// This is used for Darwin ARM64's byte-level packing of non-HFA/HVA structs.
315func copyStruct8ByteChunks(ptr unsafe.Pointer, size uintptr, addChunk func(uintptr)) {
316 if !isDarwin {
317 panic("purego: should only be called on darwin")
318 }
319 for offset := uintptr(0); offset < size; offset += 8 {
320 var chunk uintptr
321 remaining := size - offset
322 if remaining >= 8 {
323 chunk = *(*uintptr)(unsafe.Add(ptr, offset))
324 } else {
325 // Read byte-by-byte to avoid reading beyond allocation
326 for i := range remaining {
327 b := *(*byte)(unsafe.Add(ptr, offset+i))
328 chunk |= uintptr(b) << (i * 8)
329 }
330 }
331 addChunk(chunk)
332 }
333}
334
335// placeRegisters implements Darwin ARM64 calling convention for struct arguments.
336//
337// For HFA/HVA structs, each element must go in a separate register (or stack slot for elements
338// that don't fit in registers). We use placeRegistersArm64 for this.
339//
340// For non-HFA/HVA structs, Darwin uses byte-level packing. We copy the struct memory in
341// 8-byte chunks, which works correctly for both register and stack placement.
342func placeRegistersDarwin(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
343 if !isDarwin {
344 panic("purego: placeRegistersDarwin should only be called on darwin")
345 }
346 // Check if this is an HFA/HVA
347 hfa := isHFA(v.Type())
348 hva := isHVA(v.Type())
349
350 // For HFA/HVA structs, use the standard ARM64 logic which places each element separately
351 if hfa || hva {
352 placeRegistersArm64(v, addFloat, addInt)
353 return
354 }
355
356 // For non-HFA/HVA structs, use byte-level copying
357 // If the value is not addressable, create an addressable copy
358 if !v.CanAddr() {
359 addressable := reflect.New(v.Type()).Elem()
360 addressable.Set(v)
361 v = addressable
362 }
363 ptr := unsafe.Pointer(v.Addr().Pointer())
364 size := v.Type().Size()
365 copyStruct8ByteChunks(ptr, size, addInt)
366}
367
368// shouldBundleStackArgs determines if we need to start C-style packing for
369// Darwin ARM64 stack arguments. This happens when registers are exhausted.
370func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
371 if !isDarwin {
372 return false
373 }
374
375 kind := v.Kind()
376 isFloat := kind == reflect.Float32 || kind == reflect.Float64
377 isInt := !isFloat && kind != reflect.Struct
378 primitiveOnStack :=
379 (isInt && numInts >= numOfIntegerRegisters()) ||
380 (isFloat && numFloats >= numOfFloatRegisters())
381 if primitiveOnStack {
382 return true
383 }
384 if kind != reflect.Struct {
385 return false
386 }
387 hfa := isHFA(v.Type())
388 hva := isHVA(v.Type())
389 size := v.Type().Size()
390 eligible := hfa || hva || size <= 16
391 if !eligible {
392 return false
393 }
394
395 if hfa {
396 need := numABIFields(v.Type())
397 return numFloats+need > numOfFloatRegisters()
398 }
399
400 if hva {
401 need := numABIFields(v.Type())
402 return numInts+need > numOfIntegerRegisters()
403 }
404
405 slotsNeeded := int((size + align8ByteMask) / align8ByteSize)
406 return numInts+slotsNeeded > numOfIntegerRegisters()
407}
408
409// structFitsInRegisters determines if a struct can still fit in remaining
410// registers, used during stack argument bundling to decide if a struct
411// should go through normal register allocation or be bundled with stack args.
412func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {
413 if !isDarwin {
414 panic("purego: structFitsInRegisters should only be called on darwin")
415 }
416 hfa := isHFA(val.Type())
417 hva := isHVA(val.Type())
418 size := val.Type().Size()
419
420 if hfa {
421 // HFA: check if elements fit in float registers
422 if numFields := numABIFields(val.Type()); tempNumFloats+numFields <= numOfFloatRegisters() {
423 return true, tempNumInts, tempNumFloats + numFields
424 }
425 } else if hva {
426 // HVA: check if elements fit in int registers
427 if numFields := numABIFields(val.Type()); tempNumInts+numFields <= numOfIntegerRegisters() {
428 return true, tempNumInts + numFields, tempNumFloats
429 }
430 } else if size <= 16 {
431 // Non-HFA/HVA small structs use int registers for byte-packing
432 slotsNeeded := int((size + align8ByteMask) / align8ByteSize)
433 if tempNumInts+slotsNeeded <= numOfIntegerRegisters() {
434 return true, tempNumInts + slotsNeeded, tempNumFloats
435 }
436 }
437
438 return false, tempNumInts, tempNumFloats
439}
440
441// collectStackArgs separates remaining arguments into those that fit in registers vs those that go on stack.
442// It returns the stack arguments and processes register arguments through addValue.
443func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
444 keepAlive []any, addInt, addFloat, addStack func(uintptr),
445 pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
446 if !isDarwin {
447 panic("purego: collectStackArgs should only be called on darwin")
448 }
449
450 var stackArgs []reflect.Value
451 tempNumInts := numInts
452 tempNumFloats := numFloats
453
454 for j, val := range args[startIdx:] {
455 // Determine if this argument goes to register or stack
456 var fitsInRegister bool
457 var newNumInts, newNumFloats int
458
459 if val.Kind() == reflect.Struct {
460 // Check if struct still fits in remaining registers
461 fitsInRegister, newNumInts, newNumFloats = structFitsInRegisters(val, tempNumInts, tempNumFloats)
462 } else {
463 // Primitive argument
464 isFloat := val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64
465 if isFloat {
466 fitsInRegister = tempNumFloats < numOfFloatRegisters()
467 newNumFloats = tempNumFloats + 1
468 newNumInts = tempNumInts
469 } else {
470 fitsInRegister = tempNumInts < numOfIntegerRegisters()
471 newNumInts = tempNumInts + 1
472 newNumFloats = tempNumFloats
473 }
474 }
475
476 if fitsInRegister {
477 // Process through normal register allocation
478 tempNumInts = newNumInts
479 tempNumFloats = newNumFloats
480 keepAlive = addValue(val, keepAlive, addInt, addFloat, addStack, pNumInts, pNumFloats, pNumStack)
481 } else {
482 // Convert strings to C strings before bundling
483 if val.Kind() == reflect.String {
484 ptr := strings.CString(val.String())
485 keepAlive = append(keepAlive, ptr)
486 val = reflect.ValueOf(ptr)
487 args[startIdx+j] = val
488 }
489 stackArgs = append(stackArgs, val)
490 }
491 }
492
493 return stackArgs, keepAlive
494}
495
496const (
497 paddingFieldPrefix = "Pad"
498)
499
500// bundleStackArgs bundles remaining arguments for Darwin ARM64 C-style stack packing.
501// It creates a packed struct with proper alignment and copies it to the stack in 8-byte chunks.
502func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
503 if !isDarwin {
504 panic("purego: bundleStackArgs should only be called on darwin")
505 }
506 if len(stackArgs) == 0 {
507 return
508 }
509
510 // Build struct fields with proper C alignment and padding
511 var fields []reflect.StructField
512 currentOffset := uintptr(0)
513 fieldIndex := 0
514
515 for j, val := range stackArgs {
516 valSize := val.Type().Size()
517 valAlign := val.Type().Align()
518
519 // ARM64 requires 8-byte alignment for 8-byte or larger structs
520 if val.Kind() == reflect.Struct && valSize >= 8 {
521 valAlign = 8
522 }
523
524 // Add padding field if needed for alignment
525 if currentOffset%uintptr(valAlign) != 0 {
526 paddingNeeded := uintptr(valAlign) - (currentOffset % uintptr(valAlign))
527 fields = append(fields, reflect.StructField{
528 Name: paddingFieldPrefix + strconv.Itoa(fieldIndex),
529 Type: reflect.ArrayOf(int(paddingNeeded), reflect.TypeFor[byte]()),
530 })
531 currentOffset += paddingNeeded
532 fieldIndex++
533 }
534
535 fields = append(fields, reflect.StructField{
536 Name: "X" + strconv.Itoa(j),
537 Type: val.Type(),
538 })
539 currentOffset += valSize
540 fieldIndex++
541 }
542
543 // Create and populate the packed struct
544 structType := reflect.StructOf(fields)
545 structInstance := reflect.New(structType).Elem()
546
547 // Set values (skip padding fields)
548 argIndex := 0
549 for j := 0; j < structInstance.NumField(); j++ {
550 fieldName := structType.Field(j).Name
551 if stdstrings.HasPrefix(fieldName, paddingFieldPrefix) {
552 continue
553 }
554 structInstance.Field(j).Set(stackArgs[argIndex])
555 argIndex++
556 }
557
558 ptr := unsafe.Pointer(structInstance.Addr().Pointer())
559 size := structType.Size()
560 copyStruct8ByteChunks(ptr, size, addStack)
561}
562
563// getCallbackStruct reads a struct argument from the callback frame on arm64.
564// It mirrors the AAPCS64 rules used by addStruct for the Go→C path.
565//
566// getCallbackStruct is only used on Unix. On Windows, callbacks are handled by
567// the runtime's own callback mechanism, so this function is compiled but unused.
568//
569// AAPCS64 struct argument passing rules:
570// - HFA (Homogeneous Float Aggregate): each float field in a separate float register
571// - Non-HFA ≤ 16 bytes: packed into 1–2 integer registers
572// - > 16 bytes (not HFA): passed by pointer in integer register
573// - Register overflow: struct goes on the stack
574// - Darwin ARM64: byte-level packing on the stack
575func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
576 switch runtime.GOOS {
577 case "android", "darwin", "freebsd", "ios", "linux", "netbsd":
578 default:
579 panic("purego: getCallbackStruct is not supported on " + runtime.GOOS)
580 }
581
582 f := (*[callbackMaxFrame]uintptr)(frame)
583
584 if isHFA(inType) {
585 _, numFloatFields := isAllSameFloat(inType)
586 if *floatsN+numFloatFields <= numOfFloatRegisters() {
587 return readHFAFromRegisters(inType, f, floatsN, numFloatFields)
588 }
589 // Not enough float registers: entire struct goes on stack.
590 // Exhaust float registers per AAPCS64.
591 *floatsN = numOfFloatRegisters()
592 return readStructFromStackArm64(inType, f, frame, stackSlot, stackByteOffset)
593 }
594
595 if size := inType.Size(); size <= 16 {
596 // Non-HFA composite ≤ 16 bytes: packed into 1–2 integer registers.
597 numSlots := int((size + 7) / 8)
598 if *intsN+numSlots <= numOfIntegerRegisters() {
599 pos := numOfFloatRegisters() + *intsN
600 var r1, r2 uintptr
601 r1 = f[pos]
602 *intsN++
603 if numSlots == 2 {
604 r2 = f[pos+1]
605 *intsN++
606 }
607 if numSlots == 1 {
608 return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
609 }
610 return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
611 }
612 // Not enough int registers: goes on stack.
613 return readStructFromStackArm64(inType, f, frame, stackSlot, stackByteOffset)
614 }
615
616 // > 16 bytes, not HFA: passed by pointer in integer register.
617 if *intsN < numOfIntegerRegisters() {
618 ptr := f[numOfFloatRegisters()+*intsN]
619 *intsN++
620 return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem()
621 }
622
623 // Pointer on stack (rare: all integer registers exhausted).
624 if isDarwin {
625 ptrVal := callbackArgFromStack(frame, *stackSlot, stackByteOffset, reflect.TypeFor[uintptr]())
626 ptr := uintptr(ptrVal.Uint())
627 return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem()
628 }
629 ptr := f[*stackSlot]
630 *stackSlot++
631 return reflect.NewAt(inType, *(*unsafe.Pointer)(unsafe.Pointer(&ptr))).Elem()
632}
633
634// readHFAFromRegisters reads an HFA (Homogeneous Float Aggregate) struct from
635// float registers. Each float field occupies one float register slot.
636// For float32, the value is in the low 32 bits of the register.
637func readHFAFromRegisters(inType reflect.Type, f *[callbackMaxFrame]uintptr, floatsN *int, numFields int) reflect.Value {
638 size := inType.Size()
639
640 // Determine the element type
641 root := abiField(inType, 0).Type
642 for root.Kind() == reflect.Struct {
643 root = abiField(root, 0).Type
644 }
645 isFloat32 := root.Kind() == reflect.Float32
646
647 if isFloat32 {
648 // Each float32 is in a separate register (low 32 bits).
649 // Pack pairs into uintptrs to match the struct memory layout.
650 switch {
651 case size <= 8:
652 var r1 uintptr
653 switch numFields {
654 case 1:
655 r1 = f[*floatsN]
656 case 2:
657 r1 = f[*floatsN+1]<<32 | f[*floatsN]&math.MaxUint32
658 }
659 *floatsN += numFields
660 return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
661 case size <= 16:
662 var r1, r2 uintptr
663 switch numFields {
664 case 3:
665 r1 = f[*floatsN+1]<<32 | f[*floatsN]&math.MaxUint32
666 r2 = f[*floatsN+2]
667 case 4:
668 r1 = f[*floatsN+1]<<32 | f[*floatsN]&math.MaxUint32
669 r2 = f[*floatsN+3]<<32 | f[*floatsN+2]&math.MaxUint32
670 }
671 *floatsN += numFields
672 return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
673 default:
674 panic("not reached")
675 }
676 }
677
678 // float64: each field occupies one full register.
679 switch numFields {
680 case 1:
681 r1 := f[*floatsN]
682 *floatsN++
683 return reflect.NewAt(inType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
684 case 2:
685 r1, r2 := f[*floatsN], f[*floatsN+1]
686 *floatsN += 2
687 return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
688 case 3:
689 r1, r2, r3 := f[*floatsN], f[*floatsN+1], f[*floatsN+2]
690 *floatsN += 3
691 return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b, c uintptr }{r1, r2, r3})).Elem()
692 case 4:
693 r1, r2, r3, r4 := f[*floatsN], f[*floatsN+1], f[*floatsN+2], f[*floatsN+3]
694 *floatsN += 4
695 return reflect.NewAt(inType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{r1, r2, r3, r4})).Elem()
696 default:
697 panic("purego: HFA with more than 4 fields is not supported")
698 }
699}
700
701// readStructFromStackArm64 reads a struct argument from the stack area of the
702// callback frame. On Darwin ARM64, arguments are byte-packed on the stack.
703// On Linux ARM64, arguments are 8-byte aligned.
704func readStructFromStackArm64(inType reflect.Type, f *[callbackMaxFrame]uintptr, frame unsafe.Pointer, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
705 if isDarwin {
706 return callbackArgFromStack(frame, *stackSlot, stackByteOffset, inType)
707 }
708 // Linux ARM64: 8-byte aligned slots.
709 numSlots := int((inType.Size() + 7) / 8)
710 v := reflect.NewAt(inType, unsafe.Pointer(&f[*stackSlot])).Elem()
711 *stackSlot += numSlots
712 return v
713}
714
715func setStruct(a *callbackArgs, ret reflect.Value) {
716 outSize := ret.Type().Size()
717 switch {
718 case outSize == 0:
719 return
720 case outSize <= 8:
721 reflect.NewAt(ret.Type(), unsafe.Pointer(&a.result)).Elem().Set(ret)
722 if isAllFloats, numFields := isAllSameFloat(ret.Type()); isAllFloats && numFields == 2 {
723 a.result[1] = a.result[0] >> 32 // expanding two float32s into a.result[0] and a.result[1]
724 a.result[0] &= math.MaxUint32 // clear the top bits since they contain the second argument
725 }
726 return
727 case outSize <= 16:
728 reflect.NewAt(ret.Type(), unsafe.Pointer(&a.result)).Elem().Set(ret)
729 if isAllFloats, numFields := isAllSameFloat(ret.Type()); isAllFloats {
730 switch numFields {
731 case 4:
732 a.result[3] = a.result[1] >> 32
733 a.result[2] = a.result[1] & math.MaxUint32
734 a.result[1] = a.result[0] >> 32
735 a.result[0] &= math.MaxUint32
736 case 3:
737 a.result[2] = a.result[1] & math.MaxUint32
738 a.result[1] = a.result[0] >> 32
739 a.result[0] &= math.MaxUint32
740 case 2:
741 // two float64s are already in a.result[0] and a.result[1]
742 default:
743 panic("not reached")
744 }
745 }
746 return
747 default:
748 if isAllFloats, numFields := isAllSameFloat(ret.Type()); isAllFloats && numFields <= 4 {
749 reflect.NewAt(ret.Type(), unsafe.Pointer(&a.result)).Elem().Set(ret)
750 return
751 }
752 // The caller passed the address to place the return struct,
753 // so copy the Go struct into the provided memory.
754 reflect.NewAt(ret.Type(), *(*unsafe.Pointer)(unsafe.Pointer(&a.result[0]))).Elem().Set(ret)
755 return
756 }
757}