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 Unverified
B5690EEEBB952194The 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 | |||
| 42 | 42 | ||
| 43 | 43 | - **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup> | |
| 44 | 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 | 46 | - **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup> | |
| 47 | 47 | - **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup> | |
| 48 | 48 | ||
| @@ -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) { | |||
| 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 | 505 | 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") | |
| 506 | 510 | } | |
| 507 | 511 | switch runtime.GOOS { | |
| 508 | 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 | 532 | // isDarwin is true on platforms that use Apple's calling convention. | |
| 515 | 533 | // iOS (GOOS=ios) shares it with macOS (GOOS=darwin). | |
| 516 | 534 | 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 @@ | |||
| 4 | 4 | package purego | |
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | - | "math" | |
| 8 | 7 | "reflect" | |
| 9 | 8 | "unsafe" | |
| 10 | 9 | ) | |
| 11 | 10 | ||
| 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 | |
| 23 | 31 | } | |
| 32 | + | loong64Flatten(f.Type, base+f.Offset, leaves) | |
| 24 | 33 | } | |
| 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) | |
| 42 | 38 | } | |
| 43 | - | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |
| 44 | 39 | 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. | |
| 47 | 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 ( | |
| 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 | + | } | |
| 56 | 128 | ||
| 57 | 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 | 132 | 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 { | |
| 63 | - | placeRegisters(v, addFloat, addInt) | |
| 138 | + | var ptr unsafe.Pointer | |
| 139 | + | if v.CanAddr() { | |
| 140 | + | ptr = v.Addr().UnsafePointer() | |
| 64 | 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)) { | |
| 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))) | |
| 166 | 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) | |
| 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)) | |
| 178 | 194 | } | |
| 179 | 195 | } | |
| 180 | 196 | ||
| @@ -4,177 +4,193 @@ | |||||
| 4 | package purego | 4 | 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.a1 | 17 | + | size uintptr |
| 19 | - | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | 18 | + | } |
| 20 | - | r1 = syscall.f1 | 19 | + | |
| 21 | - | if numFields == 2 { | 20 | + | // loong64Flatten appends the scalar leaves of t to leaves, offsetting each member |
| 22 | - | r1 = syscall.f2<<32 | syscall.f1 | 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 | |||
| 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.a2 | 36 | + | 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 above | 40 | + | k := t.Kind() |
| 46 | - | // weird pointer dereference to circumvent go vet | 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. | |||
| 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 = 0b00 | 117 | + | switch size { |
| 53 | - | _FLOAT = 0b01 | 118 | + | case 1: |
| 54 | - | _INT = 0b11 | 119 | + | *(*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 keepAlive | 132 | 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 uint64 | 149 | + | for _, l := range leaves { |
| 72 | - | var shift byte | 150 | + | src := unsafe.Add(ptr, l.offset) |
| 73 | - | var flushed bool | 151 | + | switch { |
| 74 | - | class := _NO_CLASS | 152 | + | 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 int | 155 | + | 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 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // 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 | 6 | package purego_test | |
| 7 | 7 | ||
| @@ -83,6 +83,9 @@ func TestRegisterFunc_structArgs(t *testing.T) { | |||
| 83 | 83 | // not support struct arguments or returns. | |
| 84 | 84 | continue | |
| 85 | 85 | } | |
| 86 | + | if imp.usesCallbacks && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" { | |
| 87 | + | continue | |
| 88 | + | } | |
| 86 | 89 | t.Run(imp.name, func(t *testing.T) { | |
| 87 | 90 | register := imp.register | |
| 88 | 91 | { | |
| @@ -908,6 +911,9 @@ func TestRegisterFunc_structReturns(t *testing.T) { | |||
| 908 | 911 | // not support struct arguments or returns. | |
| 909 | 912 | continue | |
| 910 | 913 | } | |
| 914 | + | if imp.usesCallbacks && runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" { | |
| 915 | + | continue | |
| 916 | + | } | |
| 911 | 917 | t.Run(imp.name, func(t *testing.T) { | |
| 912 | 918 | register := imp.register | |
| 913 | 919 | { | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors | 2 | // 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_test | 6 | 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 | continue | 84 | 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.register | 90 | 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 | continue | 912 | 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.register | 918 | register := imp.register | ||
| 913 | { | 919 | { | ||
M syscall_unix.go +6 -2
| @@ -64,7 +64,7 @@ func compileCallback(fn any) uintptr { | |||
| 64 | 64 | if i == 0 && in.AssignableTo(reflect.TypeFor[CDecl]()) { | |
| 65 | 65 | continue | |
| 66 | 66 | } | |
| 67 | - | ensureStructSupported() | |
| 67 | + | ensureCallbackStructSupported() | |
| 68 | 68 | checkStructFieldsSupported(in) | |
| 69 | 69 | continue | |
| 70 | 70 | case reflect.Interface, reflect.Func, reflect.Slice, | |
| @@ -77,9 +77,13 @@ output: | |||
| 77 | 77 | switch { | |
| 78 | 78 | case ty.NumOut() == 1: | |
| 79 | 79 | switch ty.Out(0).Kind() { | |
| 80 | + | case reflect.Struct: | |
| 81 | + | ensureCallbackStructSupported() | |
| 82 | + | checkStructFieldsSupported(ty.Out(0)) | |
| 83 | + | break output | |
| 80 | 84 | case reflect.Pointer, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | |
| 81 | 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 | 87 | break output | |
| 84 | 88 | } | |
| 85 | 89 | 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 | continue | 65 | continue | ||
| 66 | } | 66 | } | ||
| 67 | - | ensureStructSupported() | 67 | + | ensureCallbackStructSupported() |
| 68 | checkStructFieldsSupported(in) | 68 | checkStructFieldsSupported(in) | ||
| 69 | continue | 69 | 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 output | 87 | break output | ||
| 84 | } | 88 | } | ||
| 85 | panic("purego: unsupported return type: " + ty.String()) | 89 | panic("purego: unsupported return type: " + ty.String()) | ||