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}