detached 5f49e7c

purego: support structs on loong64 (#473)

Updates #474

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
commit
5f49e7c4932236f91b58b89323a50401a2ab47f1
author
Hajime Hoshi · · 2026-07-07 03:33 UTC
committer
GitHub · · 2026-07-07 03:33 UTC
parent
d60df56
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
5 files · +197 -153
M README.md +1 -1
@@ -42,7 +42,7 @@ Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs o
4242
4343 - **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup>
4444 - **FreeBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>
45-- **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>3</sup>, ppc64le<sup>3</sup>, riscv64<sup>3</sup>, s390x<sup>3, 5</sup>
45+- **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>2</sup>, ppc64le<sup>3</sup>, riscv64<sup>3</sup>, s390x<sup>3, 5</sup>
4646 - **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>
4747 - **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup>
4848
@@ -42,7 +42,7 @@ Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs o
42 42
43 - **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup>43 - **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup>
44 - **FreeBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>44 - **FreeBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>
45-- **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>3</sup>, ppc64le<sup>3</sup>, riscv64<sup>3</sup>, s390x<sup>3, 5</sup>45+- **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>2</sup>, ppc64le<sup>3</sup>, riscv64<sup>3</sup>, s390x<sup>3, 5</sup>
46 - **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>46 - **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>
47 - **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup>47 - **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup>
48 48
M func.go +20 -2
@@ -500,9 +500,13 @@ func checkStructFieldsSupported(ty reflect.Type) {
500500 }
501501 }
502502
503+// ensureStructSupported panics if passing or returning structs through a call to
504+// a C function is unsupported on the current platform.
503505 func ensureStructSupported() {
504- if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
505- panic("purego: struct arguments/returns are only supported on amd64 and arm64")
506+ switch runtime.GOARCH {
507+ case "amd64", "arm64", "loong64":
508+ default:
509+ panic("purego: struct arguments/returns are only supported on amd64, arm64, and loong64")
506510 }
507511 switch runtime.GOOS {
508512 case "android", "darwin", "ios", "linux", "windows":
@@ -511,6 +515,20 @@ func ensureStructSupported() {
511515 }
512516 }
513517
518+// ensureCallbackStructSupported panics if passing or returning structs through a
519+// callback is unsupported on the current platform. Callbacks support structs on
520+// fewer architectures than a direct call to a C function.
521+func ensureCallbackStructSupported() {
522+ if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
523+ panic("purego: struct arguments/returns in callbacks are only supported on amd64 and arm64")
524+ }
525+ switch runtime.GOOS {
526+ case "android", "darwin", "ios", "linux", "windows":
527+ default:
528+ panic("purego: struct arguments/returns in callbacks are only supported on android, darwin, ios, linux, and windows")
529+ }
530+}
531+
514532 // isDarwin is true on platforms that use Apple's calling convention.
515533 // iOS (GOOS=ios) shares it with macOS (GOOS=darwin).
516534 const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios"
@@ -500,9 +500,13 @@ func checkStructFieldsSupported(ty reflect.Type) {
500 }500 }
501 }501 }
502 502
503+// ensureStructSupported panics if passing or returning structs through a call to
504+// a C function is unsupported on the current platform.
503 func ensureStructSupported() {505 func ensureStructSupported() {
504- if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {506+ switch runtime.GOARCH {
505- panic("purego: struct arguments/returns are only supported on amd64 and arm64")507+ case "amd64", "arm64", "loong64":
508+ default:
509+ panic("purego: struct arguments/returns are only supported on amd64, arm64, and loong64")
506 }510 }
507 switch runtime.GOOS {511 switch runtime.GOOS {
508 case "android", "darwin", "ios", "linux", "windows":512 case "android", "darwin", "ios", "linux", "windows":
@@ -511,6 +515,20 @@ func ensureStructSupported() {
511 }515 }
512 }516 }
513 517
518+// ensureCallbackStructSupported panics if passing or returning structs through a
519+// callback is unsupported on the current platform. Callbacks support structs on
520+// fewer architectures than a direct call to a C function.
521+func ensureCallbackStructSupported() {
522+ if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
523+ panic("purego: struct arguments/returns in callbacks are only supported on amd64 and arm64")
524+ }
525+ switch runtime.GOOS {
526+ case "android", "darwin", "ios", "linux", "windows":
527+ default:
528+ panic("purego: struct arguments/returns in callbacks are only supported on android, darwin, ios, linux, and windows")
529+ }
530+}
531+
514 // isDarwin is true on platforms that use Apple's calling convention.532 // isDarwin is true on platforms that use Apple's calling convention.
515 // iOS (GOOS=ios) shares it with macOS (GOOS=darwin).533 // iOS (GOOS=ios) shares it with macOS (GOOS=darwin).
516 const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios"534 const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios"
M struct_loong64.go +163 -147
@@ -4,177 +4,193 @@
44 package purego
55
66 import (
7- "math"
87 "reflect"
98 "unsafe"
109 )
1110
12-func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) {
13- outSize := outType.Size()
14- switch {
15- case outSize == 0:
16- return reflect.New(outType).Elem()
17- case outSize <= 8:
18- r1 := syscall.a1
19- if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
20- r1 = syscall.f1
21- if numFields == 2 {
22- r1 = syscall.f2<<32 | syscall.f1
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.
13+type 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.
22+func 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
2331 }
32+ loong64Flatten(f.Type, base+f.Offset, leaves)
2433 }
25- return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
26- case outSize <= 16:
27- r1, r2 := syscall.a1, syscall.a2
28- if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
29- switch numFields {
30- case 4:
31- r1 = syscall.f2<<32 | syscall.f1
32- r2 = syscall.f4<<32 | syscall.f3
33- case 3:
34- r1 = syscall.f2<<32 | syscall.f1
35- r2 = syscall.f3
36- case 2:
37- r1 = syscall.f1
38- r2 = syscall.f2
39- default:
40- panic("not reached")
41- }
34+ case reflect.Array:
35+ elem := t.Elem()
36+ for i := range t.Len() {
37+ loong64Flatten(elem, base+uintptr(i)*elem.Size(), leaves)
4238 }
43- return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
4439 default:
45- // create struct from the Go pointer created above
46- // weird pointer dereference to circumvent go vet
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.
55+func 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+func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value {
76+ outSize := outType.Size()
77+ if outSize == 0 {
78+ return reflect.New(outType).Elem()
79+ }
80+ if outSize > 16 {
81+ // Returned indirectly through a pointer in the first integer register.
4782 return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
4883 }
84+
85+ var buf [16]byte
86+ base := unsafe.Pointer(&buf[0])
87+ if leaves, useFP := loong64Classify(outType); useFP {
88+ floatRegs := [2]uintptr{syscall.f1, syscall.f2}
89+ intRegs := [2]uintptr{syscall.a1, syscall.a2}
90+ var fi, ii int
91+ for _, l := range leaves {
92+ dst := unsafe.Add(base, l.offset)
93+ if !l.isFloat {
94+ loong64StoreInt(dst, l.size, intRegs[ii])
95+ ii++
96+ continue
97+ }
98+ r := floatRegs[fi]
99+ fi++
100+ if l.kind == reflect.Float32 {
101+ // A single-precision value is NaN-boxed in the register.
102+ *(*uint32)(dst) = uint32(r)
103+ } else {
104+ *(*uint64)(dst) = uint64(r)
105+ }
106+ }
107+ } else {
108+ *(*uintptr)(base) = syscall.a1
109+ if outSize > 8 {
110+ *(*uintptr)(unsafe.Add(base, 8)) = syscall.a2
111+ }
112+ }
113+ return reflect.NewAt(outType, base).Elem()
49114 }
50115
51-const (
52- _NO_CLASS = 0b00
53- _FLOAT = 0b01
54- _INT = 0b11
55-)
116+func loong64StoreInt(dst unsafe.Pointer, size uintptr, r uintptr) {
117+ switch size {
118+ case 1:
119+ *(*uint8)(dst) = uint8(r)
120+ case 2:
121+ *(*uint16)(dst) = uint16(r)
122+ case 4:
123+ *(*uint32)(dst) = uint32(r)
124+ default:
125+ *(*uint64)(dst) = uint64(r)
126+ }
127+}
56128
57129 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
58- if v.Type().Size() == 0 {
130+ size := v.Type().Size()
131+ if size == 0 {
59132 return keepAlive
60133 }
134+ if size > 16 {
135+ return placeStack(v, keepAlive, addInt)
136+ }
61137
62- if size := v.Type().Size(); size <= 16 {
63- placeRegisters(v, addFloat, addInt)
138+ var ptr unsafe.Pointer
139+ if v.CanAddr() {
140+ ptr = v.Addr().UnsafePointer()
64141 } else {
65- keepAlive = placeStack(v, keepAlive, addInt)
142+ tmp := reflect.New(v.Type())
143+ tmp.Elem().Set(v)
144+ ptr = tmp.UnsafePointer()
145+ keepAlive = append(keepAlive, tmp.Interface())
66146 }
67- return keepAlive // the struct was allocated so don't panic
68-}
69147
70-func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
71- var val uint64
72- var shift byte
73- var flushed bool
74- class := _NO_CLASS
75- var place func(v reflect.Value)
76- place = func(v reflect.Value) {
77- var numFields int
78- if v.Kind() == reflect.Struct {
79- numFields = v.Type().NumField()
80- } else {
81- numFields = v.Type().Len()
82- }
83- for k := 0; k < numFields; k++ {
84- flushed = false
85- var f reflect.Value
86- if v.Kind() == reflect.Struct {
87- f = v.Field(k)
88- } else {
89- f = v.Index(k)
90- }
91- align := byte(f.Type().Align()*8 - 1)
92- shift = (shift + align) &^ align
93- if shift >= 64 {
94- shift = 0
95- flushed = true
96- if class == _FLOAT {
97- addFloat(uintptr(val))
98- } else {
99- addInt(uintptr(val))
100- }
101- }
102- switch f.Type().Kind() {
103- case reflect.Struct:
104- place(f)
105- case reflect.Bool:
106- if f.Bool() {
107- val |= 1 << shift
108- }
109- shift += 8
110- class |= _INT
111- case reflect.Uint8:
112- val |= f.Uint() << shift
113- shift += 8
114- class |= _INT
115- case reflect.Uint16:
116- val |= f.Uint() << shift
117- shift += 16
118- class |= _INT
119- case reflect.Uint32:
120- val |= f.Uint() << shift
121- shift += 32
122- class |= _INT
123- case reflect.Uint64, reflect.Uint, reflect.Uintptr:
124- addInt(uintptr(f.Uint()))
125- shift = 0
126- flushed = true
127- class = _NO_CLASS
128- case reflect.Int8:
129- val |= uint64(f.Int()&0xFF) << shift
130- shift += 8
131- class |= _INT
132- case reflect.Int16:
133- val |= uint64(f.Int()&0xFFFF) << shift
134- shift += 16
135- class |= _INT
136- case reflect.Int32:
137- val |= uint64(f.Int()&0xFFFF_FFFF) << shift
138- shift += 32
139- class |= _INT
140- case reflect.Int64, reflect.Int:
141- addInt(uintptr(f.Int()))
142- shift = 0
143- flushed = true
144- class = _NO_CLASS
145- case reflect.Float32:
146- if class == _FLOAT {
147- addFloat(uintptr(val))
148- val = 0
149- shift = 0
150- }
151- val |= uint64(math.Float32bits(float32(f.Float()))) << shift
152- shift += 32
153- class |= _FLOAT
154- case reflect.Float64:
155- addFloat(uintptr(math.Float64bits(float64(f.Float()))))
156- shift = 0
157- flushed = true
158- class = _NO_CLASS
159- case reflect.Ptr, reflect.UnsafePointer:
160- addInt(f.Pointer())
161- shift = 0
162- flushed = true
163- class = _NO_CLASS
164- case reflect.Array:
165- place(f)
148+ if leaves, useFP := loong64Classify(v.Type()); useFP {
149+ for _, l := range leaves {
150+ src := unsafe.Add(ptr, l.offset)
151+ switch {
152+ case l.isFloat && l.kind == reflect.Float32:
153+ // NaN-box the single-precision value in the 64-bit FP register.
154+ addFloat(uintptr(*(*uint32)(src)) | 0xFFFFFFFF_00000000)
155+ case l.isFloat:
156+ addFloat(uintptr(*(*uint64)(src)))
166157 default:
167- panic("purego: unsupported kind " + f.Kind().String())
158+ addInt(loong64LoadInt(src, l))
168159 }
169160 }
161+ return keepAlive
170162 }
171- place(v)
172- if !flushed {
173- if class == _FLOAT {
174- addFloat(uintptr(val))
175- } else {
176- addInt(uintptr(val))
177- }
163+
164+ // Integer calling convention: pass the raw aggregate in one or two GARs.
165+ var words [16]byte
166+ copy(words[:], unsafe.Slice((*byte)(ptr), size))
167+ addInt(*(*uintptr)(unsafe.Pointer(&words[0])))
168+ if size > 8 {
169+ addInt(*(*uintptr)(unsafe.Pointer(&words[8])))
170+ }
171+ return keepAlive
172+}
173+
174+// loong64LoadInt reads an integer leaf into a register value, sign-extending
175+// signed members and zero-extending the rest.
176+func loong64LoadInt(src unsafe.Pointer, l loong64Leaf) uintptr {
177+ switch l.kind {
178+ case reflect.Int8:
179+ return uintptr(int64(*(*int8)(src)))
180+ case reflect.Int16:
181+ return uintptr(int64(*(*int16)(src)))
182+ case reflect.Int32:
183+ return uintptr(int64(*(*int32)(src)))
184+ case reflect.Int, reflect.Int64:
185+ return uintptr(*(*int64)(src))
186+ case reflect.Bool, reflect.Uint8:
187+ return uintptr(*(*uint8)(src))
188+ case reflect.Uint16:
189+ return uintptr(*(*uint16)(src))
190+ case reflect.Uint32:
191+ return uintptr(*(*uint32)(src))
192+ default:
193+ return uintptr(*(*uint64)(src))
178194 }
179195 }
180196
@@ -4,177 +4,193 @@
4 package purego4 package purego
5 5
6 import (6 import (
7- "math"
8 "reflect"7 "reflect"
9 "unsafe"8 "unsafe"
10 )9 )
11 10
12-func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) {11+// loong64Leaf is a scalar member of an aggregate after flattening nested structs
13- outSize := outType.Size()12+// and arrays. offset is the byte offset of the member within the aggregate.
14- switch {13+type loong64Leaf struct {
15- case outSize == 0:14+ isFloat bool
16- return reflect.New(outType).Elem()15+ kind reflect.Kind
17- case outSize <= 8:16+ offset uintptr
18- r1 := syscall.a117+ size uintptr
19- if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {18+}
20- r1 = syscall.f119+
21- if numFields == 2 {20+// loong64Flatten appends the scalar leaves of t to leaves, offsetting each member
22- r1 = syscall.f2<<32 | syscall.f121+// by base. Nested structs and arrays are expanded into their members.
22+func 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
23 }31 }
32+ loong64Flatten(f.Type, base+f.Offset, leaves)
24 }33 }
25- return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()34+ case reflect.Array:
26- case outSize <= 16:35+ elem := t.Elem()
27- r1, r2 := syscall.a1, syscall.a236+ for i := range t.Len() {
28- if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {37+ loong64Flatten(elem, base+uintptr(i)*elem.Size(), leaves)
29- switch numFields {
30- case 4:
31- r1 = syscall.f2<<32 | syscall.f1
32- r2 = syscall.f4<<32 | syscall.f3
33- case 3:
34- r1 = syscall.f2<<32 | syscall.f1
35- r2 = syscall.f3
36- case 2:
37- r1 = syscall.f1
38- r2 = syscall.f2
39- default:
40- panic("not reached")
41- }
42 }38 }
43- return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
44 default:39 default:
45- // create struct from the Go pointer created above40+ k := t.Kind()
46- // weird pointer dereference to circumvent go vet41+ *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.
55+func 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+func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value {
76+ outSize := outType.Size()
77+ if outSize == 0 {
78+ return reflect.New(outType).Elem()
79+ }
80+ if outSize > 16 {
81+ // Returned indirectly through a pointer in the first integer register.
47 return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()82 return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
48 }83 }
84+
85+ var buf [16]byte
86+ base := unsafe.Pointer(&buf[0])
87+ if leaves, useFP := loong64Classify(outType); useFP {
88+ floatRegs := [2]uintptr{syscall.f1, syscall.f2}
89+ intRegs := [2]uintptr{syscall.a1, syscall.a2}
90+ var fi, ii int
91+ for _, l := range leaves {
92+ dst := unsafe.Add(base, l.offset)
93+ if !l.isFloat {
94+ loong64StoreInt(dst, l.size, intRegs[ii])
95+ ii++
96+ continue
97+ }
98+ r := floatRegs[fi]
99+ fi++
100+ if l.kind == reflect.Float32 {
101+ // A single-precision value is NaN-boxed in the register.
102+ *(*uint32)(dst) = uint32(r)
103+ } else {
104+ *(*uint64)(dst) = uint64(r)
105+ }
106+ }
107+ } else {
108+ *(*uintptr)(base) = syscall.a1
109+ if outSize > 8 {
110+ *(*uintptr)(unsafe.Add(base, 8)) = syscall.a2
111+ }
112+ }
113+ return reflect.NewAt(outType, base).Elem()
49 }114 }
50 115
51-const (116+func loong64StoreInt(dst unsafe.Pointer, size uintptr, r uintptr) {
52- _NO_CLASS = 0b00117+ switch size {
53- _FLOAT = 0b01118+ case 1:
54- _INT = 0b11119+ *(*uint8)(dst) = uint8(r)
55-)120+ case 2:
121+ *(*uint16)(dst) = uint16(r)
122+ case 4:
123+ *(*uint32)(dst) = uint32(r)
124+ default:
125+ *(*uint64)(dst) = uint64(r)
126+ }
127+}
56 128
57 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {129 func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
58- if v.Type().Size() == 0 {130+ size := v.Type().Size()
131+ if size == 0 {
59 return keepAlive132 return keepAlive
60 }133 }
134+ if size > 16 {
135+ return placeStack(v, keepAlive, addInt)
136+ }
61 137
62- if size := v.Type().Size(); size <= 16 {138+ var ptr unsafe.Pointer
63- placeRegisters(v, addFloat, addInt)139+ if v.CanAddr() {
140+ ptr = v.Addr().UnsafePointer()
64 } else {141 } else {
65- keepAlive = placeStack(v, keepAlive, addInt)142+ tmp := reflect.New(v.Type())
143+ tmp.Elem().Set(v)
144+ ptr = tmp.UnsafePointer()
145+ keepAlive = append(keepAlive, tmp.Interface())
66 }146 }
67- return keepAlive // the struct was allocated so don't panic
68-}
69 147
70-func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {148+ if leaves, useFP := loong64Classify(v.Type()); useFP {
71- var val uint64149+ for _, l := range leaves {
72- var shift byte150+ src := unsafe.Add(ptr, l.offset)
73- var flushed bool151+ switch {
74- class := _NO_CLASS152+ case l.isFloat && l.kind == reflect.Float32:
75- var place func(v reflect.Value)153+ // NaN-box the single-precision value in the 64-bit FP register.
76- place = func(v reflect.Value) {154+ addFloat(uintptr(*(*uint32)(src)) | 0xFFFFFFFF_00000000)
77- var numFields int155+ case l.isFloat:
78- if v.Kind() == reflect.Struct {156+ addFloat(uintptr(*(*uint64)(src)))
79- numFields = v.Type().NumField()
80- } else {
81- numFields = v.Type().Len()
82- }
83- for k := 0; k < numFields; k++ {
84- flushed = false
85- var f reflect.Value
86- if v.Kind() == reflect.Struct {
87- f = v.Field(k)
88- } else {
89- f = v.Index(k)
90- }
91- align := byte(f.Type().Align()*8 - 1)
92- shift = (shift + align) &^ align
93- if shift >= 64 {
94- shift = 0
95- flushed = true
96- if class == _FLOAT {
97- addFloat(uintptr(val))
98- } else {
99- addInt(uintptr(val))
100- }
101- }
102- switch f.Type().Kind() {
103- case reflect.Struct:
104- place(f)
105- case reflect.Bool:
106- if f.Bool() {
107- val |= 1 << shift
108- }
109- shift += 8
110- class |= _INT
111- case reflect.Uint8:
112- val |= f.Uint() << shift
113- shift += 8
114- class |= _INT
115- case reflect.Uint16:
116- val |= f.Uint() << shift
117- shift += 16
118- class |= _INT
119- case reflect.Uint32:
120- val |= f.Uint() << shift
121- shift += 32
122- class |= _INT
123- case reflect.Uint64, reflect.Uint, reflect.Uintptr:
124- addInt(uintptr(f.Uint()))
125- shift = 0
126- flushed = true
127- class = _NO_CLASS
128- case reflect.Int8:
129- val |= uint64(f.Int()&0xFF) << shift
130- shift += 8
131- class |= _INT
132- case reflect.Int16:
133- val |= uint64(f.Int()&0xFFFF) << shift
134- shift += 16
135- class |= _INT
136- case reflect.Int32:
137- val |= uint64(f.Int()&0xFFFF_FFFF) << shift
138- shift += 32
139- class |= _INT
140- case reflect.Int64, reflect.Int:
141- addInt(uintptr(f.Int()))
142- shift = 0
143- flushed = true
144- class = _NO_CLASS
145- case reflect.Float32:
146- if class == _FLOAT {
147- addFloat(uintptr(val))
148- val = 0
149- shift = 0
150- }
151- val |= uint64(math.Float32bits(float32(f.Float()))) << shift
152- shift += 32
153- class |= _FLOAT
154- case reflect.Float64:
155- addFloat(uintptr(math.Float64bits(float64(f.Float()))))
156- shift = 0
157- flushed = true
158- class = _NO_CLASS
159- case reflect.Ptr, reflect.UnsafePointer:
160- addInt(f.Pointer())
161- shift = 0
162- flushed = true
163- class = _NO_CLASS
164- case reflect.Array:
165- place(f)
166 default:157 default:
167- panic("purego: unsupported kind " + f.Kind().String())158+ addInt(loong64LoadInt(src, l))
168 }159 }
169 }160 }
161+ return keepAlive
170 }162 }
171- place(v)163+
172- if !flushed {164+ // Integer calling convention: pass the raw aggregate in one or two GARs.
173- if class == _FLOAT {165+ var words [16]byte
174- addFloat(uintptr(val))166+ copy(words[:], unsafe.Slice((*byte)(ptr), size))
175- } else {167+ addInt(*(*uintptr)(unsafe.Pointer(&words[0])))
176- addInt(uintptr(val))168+ if size > 8 {
177- }169+ addInt(*(*uintptr)(unsafe.Pointer(&words[8])))
170+ }
171+ return keepAlive
172+}
173+
174+// loong64LoadInt reads an integer leaf into a register value, sign-extending
175+// signed members and zero-extending the rest.
176+func loong64LoadInt(src unsafe.Pointer, l loong64Leaf) uintptr {
177+ switch l.kind {
178+ case reflect.Int8:
179+ return uintptr(int64(*(*int8)(src)))
180+ case reflect.Int16:
181+ return uintptr(int64(*(*int16)(src)))
182+ case reflect.Int32:
183+ return uintptr(int64(*(*int32)(src)))
184+ case reflect.Int, reflect.Int64:
185+ return uintptr(*(*int64)(src))
186+ case reflect.Bool, reflect.Uint8:
187+ return uintptr(*(*uint8)(src))
188+ case reflect.Uint16:
189+ return uintptr(*(*uint16)(src))
190+ case reflect.Uint32:
191+ return uintptr(*(*uint32)(src))
192+ default:
193+ return uintptr(*(*uint64)(src))
178 }194 }
179 }195 }
180 196
M struct_test.go +7 -1
@@ -1,7 +1,7 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
33
4-//go:build (darwin || linux || windows) && (amd64 || arm64)
4+//go:build (darwin || linux || windows) && (amd64 || arm64 || loong64)
55
66 package purego_test
77
@@ -83,6 +83,9 @@ func TestRegisterFunc_structArgs(t *testing.T) {
8383 // not support struct arguments or returns.
8484 continue
8585 }
86+ if imp.usesCallbacks && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
87+ continue
88+ }
8689 t.Run(imp.name, func(t *testing.T) {
8790 register := imp.register
8891 {
@@ -908,6 +911,9 @@ func TestRegisterFunc_structReturns(t *testing.T) {
908911 // not support struct arguments or returns.
909912 continue
910913 }
914+ if imp.usesCallbacks && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
915+ continue
916+ }
911917 t.Run(imp.name, func(t *testing.T) {
912918 register := imp.register
913919 {
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3 3
4-//go:build (darwin || linux || windows) && (amd64 || arm64)4+//go:build (darwin || linux || windows) && (amd64 || arm64 || loong64)
5 5
6 package purego_test6 package purego_test
7 7
@@ -83,6 +83,9 @@ func TestRegisterFunc_structArgs(t *testing.T) {
83 // not support struct arguments or returns.83 // not support struct arguments or returns.
84 continue84 continue
85 }85 }
86+ if imp.usesCallbacks && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
87+ continue
88+ }
86 t.Run(imp.name, func(t *testing.T) {89 t.Run(imp.name, func(t *testing.T) {
87 register := imp.register90 register := imp.register
88 {91 {
@@ -908,6 +911,9 @@ func TestRegisterFunc_structReturns(t *testing.T) {
908 // not support struct arguments or returns.911 // not support struct arguments or returns.
909 continue912 continue
910 }913 }
914+ if imp.usesCallbacks && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
915+ continue
916+ }
911 t.Run(imp.name, func(t *testing.T) {917 t.Run(imp.name, func(t *testing.T) {
912 register := imp.register918 register := imp.register
913 {919 {
M syscall_unix.go +6 -2
@@ -64,7 +64,7 @@ func compileCallback(fn any) uintptr {
6464 if i == 0 && in.AssignableTo(reflect.TypeFor[CDecl]()) {
6565 continue
6666 }
67- ensureStructSupported()
67+ ensureCallbackStructSupported()
6868 checkStructFieldsSupported(in)
6969 continue
7070 case reflect.Interface, reflect.Func, reflect.Slice,
@@ -77,9 +77,13 @@ output:
7777 switch {
7878 case ty.NumOut() == 1:
7979 switch ty.Out(0).Kind() {
80+ case reflect.Struct:
81+ ensureCallbackStructSupported()
82+ checkStructFieldsSupported(ty.Out(0))
83+ break output
8084 case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
8185 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
82- reflect.Bool, reflect.UnsafePointer, reflect.Struct:
86+ reflect.Bool, reflect.UnsafePointer:
8387 break output
8488 }
8589 panic("purego: unsupported return type: " + ty.String())
@@ -64,7 +64,7 @@ func compileCallback(fn any) uintptr {
64 if i == 0 && in.AssignableTo(reflect.TypeFor[CDecl]()) {64 if i == 0 && in.AssignableTo(reflect.TypeFor[CDecl]()) {
65 continue65 continue
66 }66 }
67- ensureStructSupported()67+ ensureCallbackStructSupported()
68 checkStructFieldsSupported(in)68 checkStructFieldsSupported(in)
69 continue69 continue
70 case reflect.Interface, reflect.Func, reflect.Slice,70 case reflect.Interface, reflect.Func, reflect.Slice,
@@ -77,9 +77,13 @@ output:
77 switch {77 switch {
78 case ty.NumOut() == 1:78 case ty.NumOut() == 1:
79 switch ty.Out(0).Kind() {79 switch ty.Out(0).Kind() {
80+ case reflect.Struct:
81+ ensureCallbackStructSupported()
82+ checkStructFieldsSupported(ty.Out(0))
83+ break output
80 case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,84 case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
81 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,85 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
82- reflect.Bool, reflect.UnsafePointer, reflect.Struct:86+ reflect.Bool, reflect.UnsafePointer:
83 break output87 break output
84 }88 }
85 panic("purego: unsupported return type: " + ty.String())89 panic("purego: unsupported return type: " + ty.String())

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