v0.11.0-alpha.11 63002e0

struct_arm64.go RawHistoryFolder

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 := range numFields {
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 := range numFields {
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 range numABIFields(first.Type) {
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 := range numFields {
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 := range structInstance.NumField() {
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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close