diff --git a/README.md b/README.md
index 3239ab50..cbbf5c98 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs o
- **Android**: 3861,3, arm1,3
- **FreeBSD**: amd643,4, arm643,4
-- **Linux**: 3863, arm3, loong643, ppc64le3, riscv643, s390x3, 5
+- **Linux**: 3863, arm3, loong642, ppc64le3, riscv643, s390x3, 5
- **NetBSD**: amd643,4, arm643,4
- **Windows**: 3863,6, arm3,6,7
diff --git a/func.go b/func.go
index 8d3a6f7e..3ba44bbc 100644
--- a/func.go
+++ b/func.go
@@ -500,9 +500,13 @@ func checkStructFieldsSupported(ty reflect.Type) {
}
}
+// ensureStructSupported panics if passing or returning structs through a call to
+// a C function is unsupported on the current platform.
func ensureStructSupported() {
- if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
- panic("purego: struct arguments/returns are only supported on amd64 and arm64")
+ switch runtime.GOARCH {
+ case "amd64", "arm64", "loong64":
+ default:
+ panic("purego: struct arguments/returns are only supported on amd64, arm64, and loong64")
}
switch runtime.GOOS {
case "android", "darwin", "ios", "linux", "windows":
@@ -511,6 +515,20 @@ func ensureStructSupported() {
}
}
+// ensureCallbackStructSupported panics if passing or returning structs through a
+// callback is unsupported on the current platform. Callbacks support structs on
+// fewer architectures than a direct call to a C function.
+func ensureCallbackStructSupported() {
+ if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
+ panic("purego: struct arguments/returns in callbacks are only supported on amd64 and arm64")
+ }
+ switch runtime.GOOS {
+ case "android", "darwin", "ios", "linux", "windows":
+ default:
+ panic("purego: struct arguments/returns in callbacks are only supported on android, darwin, ios, linux, and windows")
+ }
+}
+
// isDarwin is true on platforms that use Apple's calling convention.
// iOS (GOOS=ios) shares it with macOS (GOOS=darwin).
const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios"
diff --git a/struct_loong64.go b/struct_loong64.go
index 7a8a7c7c..ad425441 100644
--- a/struct_loong64.go
+++ b/struct_loong64.go
@@ -4,177 +4,193 @@
package purego
import (
- "math"
"reflect"
"unsafe"
)
-func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) {
- outSize := outType.Size()
- switch {
- case outSize == 0:
- return reflect.New(outType).Elem()
- case outSize <= 8:
- r1 := syscall.a1
- if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
- r1 = syscall.f1
- if numFields == 2 {
- r1 = syscall.f2<<32 | syscall.f1
+// loong64Leaf is a scalar member of an aggregate after flattening nested structs
+// and arrays. offset is the byte offset of the member within the aggregate.
+type loong64Leaf struct {
+ isFloat bool
+ kind reflect.Kind
+ offset uintptr
+ size uintptr
+}
+
+// loong64Flatten appends the scalar leaves of t to leaves, offsetting each member
+// by base. Nested structs and arrays are expanded into their members.
+func loong64Flatten(t reflect.Type, base uintptr, leaves *[]loong64Leaf) {
+ switch t.Kind() {
+ case reflect.Struct:
+ for i := range t.NumField() {
+ f := t.Field(i)
+ if f.Name == "_" {
+ // Blank fields are explicit padding to match the C layout, not
+ // members that the calling convention counts.
+ continue
}
+ loong64Flatten(f.Type, base+f.Offset, leaves)
}
- return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
- case outSize <= 16:
- r1, r2 := syscall.a1, syscall.a2
- if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
- switch numFields {
- case 4:
- r1 = syscall.f2<<32 | syscall.f1
- r2 = syscall.f4<<32 | syscall.f3
- case 3:
- r1 = syscall.f2<<32 | syscall.f1
- r2 = syscall.f3
- case 2:
- r1 = syscall.f1
- r2 = syscall.f2
- default:
- panic("not reached")
- }
+ case reflect.Array:
+ elem := t.Elem()
+ for i := range t.Len() {
+ loong64Flatten(elem, base+uintptr(i)*elem.Size(), leaves)
}
- return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
default:
- // create struct from the Go pointer created above
- // weird pointer dereference to circumvent go vet
+ k := t.Kind()
+ *leaves = append(*leaves, loong64Leaf{
+ isFloat: k == reflect.Float32 || k == reflect.Float64,
+ kind: k,
+ offset: base,
+ size: t.Size(),
+ })
+ }
+}
+
+// loong64Classify flattens t and reports whether it is passed and returned through
+// the floating-point calling convention. Under the LoongArch hard-float ABI an
+// aggregate uses FP registers only when, after flattening, it has one or two
+// floating-point members and nothing else, or exactly one floating-point member
+// together with one integer member.
+func loong64Classify(t reflect.Type) (leaves []loong64Leaf, useFP bool) {
+ loong64Flatten(t, 0, &leaves)
+ var floats, ints int
+ for _, l := range leaves {
+ if l.isFloat {
+ floats++
+ } else {
+ ints++
+ }
+ }
+ switch {
+ case ints == 0 && (floats == 1 || floats == 2):
+ return leaves, true
+ case ints == 1 && floats == 1:
+ return leaves, true
+ default:
+ return leaves, false
+ }
+}
+
+func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value {
+ outSize := outType.Size()
+ if outSize == 0 {
+ return reflect.New(outType).Elem()
+ }
+ if outSize > 16 {
+ // Returned indirectly through a pointer in the first integer register.
return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
}
+
+ var buf [16]byte
+ base := unsafe.Pointer(&buf[0])
+ if leaves, useFP := loong64Classify(outType); useFP {
+ floatRegs := [2]uintptr{syscall.f1, syscall.f2}
+ intRegs := [2]uintptr{syscall.a1, syscall.a2}
+ var fi, ii int
+ for _, l := range leaves {
+ dst := unsafe.Add(base, l.offset)
+ if !l.isFloat {
+ loong64StoreInt(dst, l.size, intRegs[ii])
+ ii++
+ continue
+ }
+ r := floatRegs[fi]
+ fi++
+ if l.kind == reflect.Float32 {
+ // A single-precision value is NaN-boxed in the register.
+ *(*uint32)(dst) = uint32(r)
+ } else {
+ *(*uint64)(dst) = uint64(r)
+ }
+ }
+ } else {
+ *(*uintptr)(base) = syscall.a1
+ if outSize > 8 {
+ *(*uintptr)(unsafe.Add(base, 8)) = syscall.a2
+ }
+ }
+ return reflect.NewAt(outType, base).Elem()
}
-const (
- _NO_CLASS = 0b00
- _FLOAT = 0b01
- _INT = 0b11
-)
+func loong64StoreInt(dst unsafe.Pointer, size uintptr, r uintptr) {
+ switch size {
+ case 1:
+ *(*uint8)(dst) = uint8(r)
+ case 2:
+ *(*uint16)(dst) = uint16(r)
+ case 4:
+ *(*uint32)(dst) = uint32(r)
+ default:
+ *(*uint64)(dst) = uint64(r)
+ }
+}
func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
- if v.Type().Size() == 0 {
+ size := v.Type().Size()
+ if size == 0 {
return keepAlive
}
+ if size > 16 {
+ return placeStack(v, keepAlive, addInt)
+ }
- if size := v.Type().Size(); size <= 16 {
- placeRegisters(v, addFloat, addInt)
+ var ptr unsafe.Pointer
+ if v.CanAddr() {
+ ptr = v.Addr().UnsafePointer()
} else {
- keepAlive = placeStack(v, keepAlive, addInt)
+ tmp := reflect.New(v.Type())
+ tmp.Elem().Set(v)
+ ptr = tmp.UnsafePointer()
+ keepAlive = append(keepAlive, tmp.Interface())
}
- return keepAlive // the struct was allocated so don't panic
-}
-func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
- var val uint64
- var shift byte
- var flushed bool
- class := _NO_CLASS
- var place func(v reflect.Value)
- place = func(v reflect.Value) {
- var numFields int
- if v.Kind() == reflect.Struct {
- numFields = v.Type().NumField()
- } else {
- numFields = v.Type().Len()
- }
- for k := 0; k < numFields; k++ {
- flushed = false
- var f reflect.Value
- if v.Kind() == reflect.Struct {
- f = v.Field(k)
- } else {
- f = v.Index(k)
- }
- align := byte(f.Type().Align()*8 - 1)
- shift = (shift + align) &^ align
- if shift >= 64 {
- shift = 0
- flushed = true
- if class == _FLOAT {
- addFloat(uintptr(val))
- } else {
- addInt(uintptr(val))
- }
- }
- switch f.Type().Kind() {
- case reflect.Struct:
- place(f)
- case reflect.Bool:
- if f.Bool() {
- val |= 1 << shift
- }
- shift += 8
- class |= _INT
- case reflect.Uint8:
- val |= f.Uint() << shift
- shift += 8
- class |= _INT
- case reflect.Uint16:
- val |= f.Uint() << shift
- shift += 16
- class |= _INT
- case reflect.Uint32:
- val |= f.Uint() << shift
- shift += 32
- class |= _INT
- case reflect.Uint64, reflect.Uint, reflect.Uintptr:
- addInt(uintptr(f.Uint()))
- shift = 0
- flushed = true
- class = _NO_CLASS
- case reflect.Int8:
- val |= uint64(f.Int()&0xFF) << shift
- shift += 8
- class |= _INT
- case reflect.Int16:
- val |= uint64(f.Int()&0xFFFF) << shift
- shift += 16
- class |= _INT
- case reflect.Int32:
- val |= uint64(f.Int()&0xFFFF_FFFF) << shift
- shift += 32
- class |= _INT
- case reflect.Int64, reflect.Int:
- addInt(uintptr(f.Int()))
- shift = 0
- flushed = true
- class = _NO_CLASS
- case reflect.Float32:
- if class == _FLOAT {
- addFloat(uintptr(val))
- val = 0
- shift = 0
- }
- val |= uint64(math.Float32bits(float32(f.Float()))) << shift
- shift += 32
- class |= _FLOAT
- case reflect.Float64:
- addFloat(uintptr(math.Float64bits(float64(f.Float()))))
- shift = 0
- flushed = true
- class = _NO_CLASS
- case reflect.Ptr, reflect.UnsafePointer:
- addInt(f.Pointer())
- shift = 0
- flushed = true
- class = _NO_CLASS
- case reflect.Array:
- place(f)
+ if leaves, useFP := loong64Classify(v.Type()); useFP {
+ for _, l := range leaves {
+ src := unsafe.Add(ptr, l.offset)
+ switch {
+ case l.isFloat && l.kind == reflect.Float32:
+ // NaN-box the single-precision value in the 64-bit FP register.
+ addFloat(uintptr(*(*uint32)(src)) | 0xFFFFFFFF_00000000)
+ case l.isFloat:
+ addFloat(uintptr(*(*uint64)(src)))
default:
- panic("purego: unsupported kind " + f.Kind().String())
+ addInt(loong64LoadInt(src, l))
}
}
+ return keepAlive
}
- place(v)
- if !flushed {
- if class == _FLOAT {
- addFloat(uintptr(val))
- } else {
- addInt(uintptr(val))
- }
+
+ // Integer calling convention: pass the raw aggregate in one or two GARs.
+ var words [16]byte
+ copy(words[:], unsafe.Slice((*byte)(ptr), size))
+ addInt(*(*uintptr)(unsafe.Pointer(&words[0])))
+ if size > 8 {
+ addInt(*(*uintptr)(unsafe.Pointer(&words[8])))
+ }
+ return keepAlive
+}
+
+// loong64LoadInt reads an integer leaf into a register value, sign-extending
+// signed members and zero-extending the rest.
+func loong64LoadInt(src unsafe.Pointer, l loong64Leaf) uintptr {
+ switch l.kind {
+ case reflect.Int8:
+ return uintptr(int64(*(*int8)(src)))
+ case reflect.Int16:
+ return uintptr(int64(*(*int16)(src)))
+ case reflect.Int32:
+ return uintptr(int64(*(*int32)(src)))
+ case reflect.Int, reflect.Int64:
+ return uintptr(*(*int64)(src))
+ case reflect.Bool, reflect.Uint8:
+ return uintptr(*(*uint8)(src))
+ case reflect.Uint16:
+ return uintptr(*(*uint16)(src))
+ case reflect.Uint32:
+ return uintptr(*(*uint32)(src))
+ default:
+ return uintptr(*(*uint64)(src))
}
}
diff --git a/struct_test.go b/struct_test.go
index 14ed0584..98c91845 100644
--- a/struct_test.go
+++ b/struct_test.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
-//go:build (darwin || linux || windows) && (amd64 || arm64)
+//go:build (darwin || linux || windows) && (amd64 || arm64 || loong64)
package purego_test
@@ -83,6 +83,9 @@ func TestRegisterFunc_structArgs(t *testing.T) {
// not support struct arguments or returns.
continue
}
+ if imp.usesCallbacks && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
+ continue
+ }
t.Run(imp.name, func(t *testing.T) {
register := imp.register
{
@@ -908,6 +911,9 @@ func TestRegisterFunc_structReturns(t *testing.T) {
// not support struct arguments or returns.
continue
}
+ if imp.usesCallbacks && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
+ continue
+ }
t.Run(imp.name, func(t *testing.T) {
register := imp.register
{
diff --git a/syscall_unix.go b/syscall_unix.go
index 7a7f9e7e..19b96ca5 100644
--- a/syscall_unix.go
+++ b/syscall_unix.go
@@ -64,7 +64,7 @@ func compileCallback(fn any) uintptr {
if i == 0 && in.AssignableTo(reflect.TypeFor[CDecl]()) {
continue
}
- ensureStructSupported()
+ ensureCallbackStructSupported()
checkStructFieldsSupported(in)
continue
case reflect.Interface, reflect.Func, reflect.Slice,
@@ -77,9 +77,13 @@ output:
switch {
case ty.NumOut() == 1:
switch ty.Out(0).Kind() {
+ case reflect.Struct:
+ ensureCallbackStructSupported()
+ checkStructFieldsSupported(ty.Out(0))
+ break output
case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
- reflect.Bool, reflect.UnsafePointer, reflect.Struct:
+ reflect.Bool, reflect.UnsafePointer:
break output
}
panic("purego: unsupported return type: " + ty.String())