v0.12.0-alpha.1 9f4a5c8

struct_loong64.go RawHistoryFolder

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
3
4package purego
5
6import (
7	"reflect"
8	"unsafe"
9)
10
11// loong64Leaf is a scalar member of an aggregate after flattening nested structs
12// and arrays. offset is the byte offset of the member within the aggregate.
13type loong64Leaf struct {
14	isFloat bool
15	kind    reflect.Kind
16	offset  uintptr
17	size    uintptr
18}
19
20// loong64Flatten appends the scalar leaves of t to leaves, offsetting each member
21// by base. Nested structs and arrays are expanded into their members.
22func loong64Flatten(t reflect.Type, base uintptr, leaves *[]loong64Leaf) {
23	switch t.Kind() {
24	case reflect.Struct:
25		for i := range t.NumField() {
26			f := t.Field(i)
27			if f.Name == "_" {
28				// Blank fields are explicit padding to match the C layout, not
29				// members that the calling convention counts.
30				continue
31			}
32			loong64Flatten(f.Type, base+f.Offset, leaves)
33		}
34	case reflect.Array:
35		elem := t.Elem()
36		for i := range t.Len() {
37			loong64Flatten(elem, base+uintptr(i)*elem.Size(), leaves)
38		}
39	default:
40		k := t.Kind()
41		*leaves = append(*leaves, loong64Leaf{
42			isFloat: k == reflect.Float32 || k == reflect.Float64,
43			kind:    k,
44			offset:  base,
45			size:    t.Size(),
46		})
47	}
48}
49
50// loong64Classify flattens t and reports whether it is passed and returned through
51// the floating-point calling convention. Under the LoongArch hard-float ABI an
52// aggregate uses FP registers only when, after flattening, it has one or two
53// floating-point members and nothing else, or exactly one floating-point member
54// together with one integer member.
55func loong64Classify(t reflect.Type) (leaves []loong64Leaf, useFP bool) {
56	loong64Flatten(t, 0, &leaves)
57	var floats, ints int
58	for _, l := range leaves {
59		if l.isFloat {
60			floats++
61		} else {
62			ints++
63		}
64	}
65	switch {
66	case ints == 0 && (floats == 1 || floats == 2):
67		return leaves, true
68	case ints == 1 && floats == 1:
69		return leaves, true
70	default:
71		return leaves, false
72	}
73}
74
75// structReturnInMemory reports whether a struct return value is returned through
76// a caller-allocated hidden pointer passed as the first integer argument.
77// Aggregates larger than two eightbytes are returned in memory.
78func structReturnInMemory(outType reflect.Type) bool {
79	return outType.Size() > maxRegAllocStructSize
80}
81
82func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value {
83	outSize := outType.Size()
84	if outSize == 0 {
85		return reflect.New(outType).Elem()
86	}
87	if outSize > 16 {
88		// Returned indirectly through a pointer in the first integer register.
89		return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
90	}
91
92	var buf [16]byte
93	base := unsafe.Pointer(&buf[0])
94	if leaves, useFP := loong64Classify(outType); useFP {
95		floatRegs := [2]uintptr{syscall.f1, syscall.f2}
96		intRegs := [2]uintptr{syscall.a1, syscall.a2}
97		var fi, ii int
98		for _, l := range leaves {
99			dst := unsafe.Add(base, l.offset)
100			if !l.isFloat {
101				loong64StoreInt(dst, l.size, intRegs[ii])
102				ii++
103				continue
104			}
105			r := floatRegs[fi]
106			fi++
107			if l.kind == reflect.Float32 {
108				// A single-precision value is NaN-boxed in the register.
109				*(*uint32)(dst) = uint32(r)
110			} else {
111				*(*uint64)(dst) = uint64(r)
112			}
113		}
114	} else {
115		*(*uintptr)(base) = syscall.a1
116		if outSize > 8 {
117			*(*uintptr)(unsafe.Add(base, 8)) = syscall.a2
118		}
119	}
120	return reflect.NewAt(outType, base).Elem()
121}
122
123func loong64StoreInt(dst unsafe.Pointer, size uintptr, r uintptr) {
124	switch size {
125	case 1:
126		*(*uint8)(dst) = uint8(r)
127	case 2:
128		*(*uint16)(dst) = uint16(r)
129	case 4:
130		*(*uint32)(dst) = uint32(r)
131	default:
132		*(*uint64)(dst) = uint64(r)
133	}
134}
135
136func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
137	size := v.Type().Size()
138	if size == 0 {
139		return keepAlive
140	}
141	if size > 16 {
142		return placeStack(v, keepAlive, addInt)
143	}
144
145	var ptr unsafe.Pointer
146	if v.CanAddr() {
147		ptr = v.Addr().UnsafePointer()
148	} else {
149		tmp := reflect.New(v.Type())
150		tmp.Elem().Set(v)
151		ptr = tmp.UnsafePointer()
152		keepAlive = append(keepAlive, tmp.Interface())
153	}
154
155	if leaves, useFP := loong64Classify(v.Type()); useFP {
156		for _, l := range leaves {
157			src := unsafe.Add(ptr, l.offset)
158			switch {
159			case l.isFloat && l.kind == reflect.Float32:
160				// NaN-box the single-precision value in the 64-bit FP register.
161				addFloat(uintptr(*(*uint32)(src)) | 0xFFFFFFFF_00000000)
162			case l.isFloat:
163				addFloat(uintptr(*(*uint64)(src)))
164			default:
165				addInt(loong64LoadInt(src, l))
166			}
167		}
168		return keepAlive
169	}
170
171	// Integer calling convention: pass the raw aggregate in one or two GARs.
172	var words [16]byte
173	copy(words[:], unsafe.Slice((*byte)(ptr), size))
174	addInt(*(*uintptr)(unsafe.Pointer(&words[0])))
175	if size > 8 {
176		addInt(*(*uintptr)(unsafe.Pointer(&words[8])))
177	}
178	return keepAlive
179}
180
181// loong64LoadInt reads an integer leaf into a register value, sign-extending
182// signed members and zero-extending the rest.
183func loong64LoadInt(src unsafe.Pointer, l loong64Leaf) uintptr {
184	switch l.kind {
185	case reflect.Int8:
186		return uintptr(int64(*(*int8)(src)))
187	case reflect.Int16:
188		return uintptr(int64(*(*int16)(src)))
189	case reflect.Int32:
190		return uintptr(int64(*(*int32)(src)))
191	case reflect.Int, reflect.Int64:
192		return uintptr(*(*int64)(src))
193	case reflect.Bool, reflect.Uint8:
194		return uintptr(*(*uint8)(src))
195	case reflect.Uint16:
196		return uintptr(*(*uint16)(src))
197	case reflect.Uint32:
198		return uintptr(*(*uint32)(src))
199	default:
200		return uintptr(*(*uint64)(src))
201	}
202}
203
204func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any {
205	// Struct is too big to be placed in registers.
206	// Copy to heap and place the pointer in register
207	ptrStruct := reflect.New(v.Type())
208	ptrStruct.Elem().Set(v)
209	ptr := ptrStruct.Elem().Addr().UnsafePointer()
210	keepAlive = append(keepAlive, ptr)
211	addInt(uintptr(ptr))
212	return keepAlive
213}
214
215// shouldBundleStackArgs always returns false on loong64
216// since C-style stack argument bundling is only needed on Darwin ARM64.
217func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
218	return false
219}
220
221// structFitsInRegisters is not used on loong64.
222func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) {
223	panic("purego: structFitsInRegisters should not be called on loong64")
224}
225
226// collectStackArgs is not used on loong64.
227func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int,
228	keepAlive []any, addInt, addFloat, addStack func(uintptr),
229	pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) {
230	panic("purego: collectStackArgs should not be called on loong64")
231}
232
233// bundleStackArgs is not used on loong64.
234func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) {
235	panic("purego: bundleStackArgs should not be called on loong64")
236}
237
238func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value {
239	panic("purego: struct callback arguments are not supported on loong64")
240}
241
242func setStruct(a *callbackArgs, ret reflect.Value) {
243	panic("purego: struct returns are not supported on loong64")
244}

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