v0.11.0-alpha.8 d476432

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

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