v0.11.0-bnema.3 2dfd053

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/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}

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