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