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