purego: fix cgo fallback build for linux/mips (#462)
Package purego failed to compile under CGO_ENABLED=1 for the Linux GOARCHs that lack a per-architecture struct file (mips, mipsle, mips64, mips64le). func.go and syscall.go reference helpers (addStruct, getStruct, shouldBundleStackArgs, collectStackArgs, bundleStackArgs, syscall_syscallN) that are only defined for the natively supported architectures, so the documented integer-only cgo fallback was broken for these targets. Add struct_other.go, providing those struct helpers as panicking stubs for architectures that have no dedicated struct_<arch>.go, and add the syscall_syscallN stub to syscall_cgo_linux.go. struct_386.go was already a pure stub, so it is removed and 386 now shares struct_other.go. Structs and floats remain unsupported, exactly as the README's "Cgo Fallback" section documents. Add a CI step that cross-compiles linux/mips with CGO_ENABLED=1 to guard against this regression. Closes #460 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- commit
- 12ee364b5d17bb94d0f5e22976c5a3a6e7c74a8f
- author
- Hajime Hoshi · · 2026-06-16 03:35 UTC
- committer
- GitHub · · 2026-06-16 03:35 UTC
- parent
- d339eb0
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
4 files · +59 -52
M .github/workflows/test.yml +8 -0
| @@ -83,6 +83,14 @@ jobs: | |||
| 83 | 83 | env GOOS=linux GOARCH=ppc64le go build -v ./... | |
| 84 | 84 | env GOOS=linux GOARCH=riscv64 go build -v ./... | |
| 85 | 85 | ||
| 86 | + | - name: go build (Linux mips, Cgo) | |
| 87 | + | # mips builds only via the Cgo fallback (CGO_ENABLED=1). See #460. | |
| 88 | + | if: runner.os == 'Linux' | |
| 89 | + | run: | | |
| 90 | + | sudo apt-get update | |
| 91 | + | sudo apt-get install -y gcc-mips-linux-gnu | |
| 92 | + | env GOOS=linux GOARCH=mips CGO_ENABLED=1 CC=mips-linux-gnu-gcc go build -v ./... | |
| 93 | + | ||
| 86 | 94 | - name: go build (plugin) | |
| 87 | 95 | if: runner.os == 'Linux' || runner.os == 'macOS' | |
| 88 | 96 | run: | |
| @@ -83,6 +83,14 @@ jobs: | |||||
| 83 | env GOOS=linux GOARCH=ppc64le go build -v ./... | 83 | env GOOS=linux GOARCH=ppc64le go build -v ./... | ||
| 84 | env GOOS=linux GOARCH=riscv64 go build -v ./... | 84 | env GOOS=linux GOARCH=riscv64 go build -v ./... | ||
| 85 | 85 | ||||
| 86 | + | - name: go build (Linux mips, Cgo) | |||
| 87 | + | # mips builds only via the Cgo fallback (CGO_ENABLED=1). See #460. | |||
| 88 | + | if: runner.os == 'Linux' | |||
| 89 | + | run: | | |||
| 90 | + | sudo apt-get update | |||
| 91 | + | sudo apt-get install -y gcc-mips-linux-gnu | |||
| 92 | + | env GOOS=linux GOARCH=mips CGO_ENABLED=1 CC=mips-linux-gnu-gcc go build -v ./... | |||
| 93 | + | ||||
| 86 | - name: go build (plugin) | 94 | - name: go build (plugin) | ||
| 87 | if: runner.os == 'Linux' || runner.os == 'macOS' | 95 | if: runner.os == 'Linux' || runner.os == 'macOS' | ||
| 88 | run: | 96 | run: | ||
D struct_386.go +0 -52
| @@ -1,52 +0,0 @@ | |||
| 1 | - | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | - | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |
| 3 | - | ||
| 4 | - | package purego | |
| 5 | - | ||
| 6 | - | import ( | |
| 7 | - | "reflect" | |
| 8 | - | "unsafe" | |
| 9 | - | ) | |
| 10 | - | ||
| 11 | - | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | |
| 12 | - | panic("purego: struct arguments are not supported") | |
| 13 | - | } | |
| 14 | - | ||
| 15 | - | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | |
| 16 | - | panic("purego: struct returns are not supported") | |
| 17 | - | } | |
| 18 | - | ||
| 19 | - | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | |
| 20 | - | panic("purego: placeRegisters not implemented on 386") | |
| 21 | - | } | |
| 22 | - | ||
| 23 | - | // shouldBundleStackArgs always returns false on 386 | |
| 24 | - | // since C-style stack argument bundling is only needed on Darwin ARM64. | |
| 25 | - | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |
| 26 | - | return false | |
| 27 | - | } | |
| 28 | - | ||
| 29 | - | // structFitsInRegisters is not used on 386. | |
| 30 | - | func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) { | |
| 31 | - | panic("purego: structFitsInRegisters should not be called on 386") | |
| 32 | - | } | |
| 33 | - | ||
| 34 | - | // collectStackArgs is not used on 386. | |
| 35 | - | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, | |
| 36 | - | keepAlive []any, addInt, addFloat, addStack func(uintptr), | |
| 37 | - | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { | |
| 38 | - | panic("purego: collectStackArgs should not be called on 386") | |
| 39 | - | } | |
| 40 | - | ||
| 41 | - | // bundleStackArgs is not used on 386. | |
| 42 | - | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |
| 43 | - | panic("purego: bundleStackArgs should not be called on 386") | |
| 44 | - | } | |
| 45 | - | ||
| 46 | - | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |
| 47 | - | panic("purego: struct callback arguments are not supported on 386") | |
| 48 | - | } | |
| 49 | - | ||
| 50 | - | func setStruct(a *callbackArgs, ret reflect.Value) { | |
| 51 | - | panic("purego: struct returns are not supported on 386") | |
| 52 | - | } | |
| @@ -1,52 +0,0 @@ | |||||
| 1 | - | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | - | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |||
| 3 | - | ||||
| 4 | - | package purego | |||
| 5 | - | ||||
| 6 | - | import ( | |||
| 7 | - | "reflect" | |||
| 8 | - | "unsafe" | |||
| 9 | - | ) | |||
| 10 | - | ||||
| 11 | - | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | |||
| 12 | - | panic("purego: struct arguments are not supported") | |||
| 13 | - | } | |||
| 14 | - | ||||
| 15 | - | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | |||
| 16 | - | panic("purego: struct returns are not supported") | |||
| 17 | - | } | |||
| 18 | - | ||||
| 19 | - | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | |||
| 20 | - | panic("purego: placeRegisters not implemented on 386") | |||
| 21 | - | } | |||
| 22 | - | ||||
| 23 | - | // shouldBundleStackArgs always returns false on 386 | |||
| 24 | - | // since C-style stack argument bundling is only needed on Darwin ARM64. | |||
| 25 | - | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |||
| 26 | - | return false | |||
| 27 | - | } | |||
| 28 | - | ||||
| 29 | - | // structFitsInRegisters is not used on 386. | |||
| 30 | - | func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (bool, int, int) { | |||
| 31 | - | panic("purego: structFitsInRegisters should not be called on 386") | |||
| 32 | - | } | |||
| 33 | - | ||||
| 34 | - | // collectStackArgs is not used on 386. | |||
| 35 | - | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, | |||
| 36 | - | keepAlive []any, addInt, addFloat, addStack func(uintptr), | |||
| 37 | - | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { | |||
| 38 | - | panic("purego: collectStackArgs should not be called on 386") | |||
| 39 | - | } | |||
| 40 | - | ||||
| 41 | - | // bundleStackArgs is not used on 386. | |||
| 42 | - | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||
| 43 | - | panic("purego: bundleStackArgs should not be called on 386") | |||
| 44 | - | } | |||
| 45 | - | ||||
| 46 | - | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |||
| 47 | - | panic("purego: struct callback arguments are not supported on 386") | |||
| 48 | - | } | |||
| 49 | - | ||||
| 50 | - | func setStruct(a *callbackArgs, ret reflect.Value) { | |||
| 51 | - | panic("purego: struct returns are not supported on 386") | |||
| 52 | - | } | |||
A struct_other.go +47 -0
| @@ -0,0 +1,47 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build !(amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x) | |
| 5 | + | ||
| 6 | + | package purego | |
| 7 | + | ||
| 8 | + | import ( | |
| 9 | + | "reflect" | |
| 10 | + | "unsafe" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // This file provides the struct-handling helpers for architectures that do | |
| 14 | + | // not support struct arguments or returns: 386, and any architecture reachable | |
| 15 | + | // only through the integer-only cgo fallback. Every struct operation panics. | |
| 16 | + | ||
| 17 | + | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | |
| 18 | + | panic("purego: struct arguments are not supported on this architecture") | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| 22 | + | panic("purego: struct returns are not supported on this architecture") | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | // shouldBundleStackArgs always returns false because C-style stack argument | |
| 26 | + | // bundling is only needed on Darwin ARM64. | |
| 27 | + | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |
| 28 | + | return false | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, | |
| 32 | + | keepAlive []any, addInt, addFloat, addStack func(uintptr), | |
| 33 | + | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { | |
| 34 | + | panic("purego: collectStackArgs should not be called on this architecture") | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |
| 38 | + | panic("purego: bundleStackArgs should not be called on this architecture") | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |
| 42 | + | panic("purego: struct callback arguments are not supported on this architecture") | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | func setStruct(a *callbackArgs, ret reflect.Value) { | |
| 46 | + | panic("purego: struct returns are not supported on this architecture") | |
| 47 | + | } | |
| @@ -0,0 +1,47 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build !(amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x) | |||
| 5 | + | ||||
| 6 | + | package purego | |||
| 7 | + | ||||
| 8 | + | import ( | |||
| 9 | + | "reflect" | |||
| 10 | + | "unsafe" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // This file provides the struct-handling helpers for architectures that do | |||
| 14 | + | // not support struct arguments or returns: 386, and any architecture reachable | |||
| 15 | + | // only through the integer-only cgo fallback. Every struct operation panics. | |||
| 16 | + | ||||
| 17 | + | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | |||
| 18 | + | panic("purego: struct arguments are not supported on this architecture") | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |||
| 22 | + | panic("purego: struct returns are not supported on this architecture") | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | // shouldBundleStackArgs always returns false because C-style stack argument | |||
| 26 | + | // bundling is only needed on Darwin ARM64. | |||
| 27 | + | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |||
| 28 | + | return false | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int, | |||
| 32 | + | keepAlive []any, addInt, addFloat, addStack func(uintptr), | |||
| 33 | + | pNumInts, pNumFloats, pNumStack *int) ([]reflect.Value, []any) { | |||
| 34 | + | panic("purego: collectStackArgs should not be called on this architecture") | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||
| 38 | + | panic("purego: bundleStackArgs should not be called on this architecture") | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | func getCallbackStruct(inType reflect.Type, frame unsafe.Pointer, floatsN *int, intsN *int, stackSlot *int, stackByteOffset *uintptr) reflect.Value { | |||
| 42 | + | panic("purego: struct callback arguments are not supported on this architecture") | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | func setStruct(a *callbackArgs, ret reflect.Value) { | |||
| 46 | + | panic("purego: struct returns are not supported on this architecture") | |||
| 47 | + | } | |||
M syscall_cgo_linux.go +4 -0
| @@ -14,3 +14,7 @@ var syscallXABI0 = uintptr(cgo.SyscallXABI0) | |||
| 14 | 14 | func NewCallback(_ any) uintptr { | |
| 15 | 15 | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64/s390x") | |
| 16 | 16 | } | |
| 17 | + | ||
| 18 | + | func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |
| 19 | + | panic("purego: syscall_syscallN is only supported on windows") | |
| 20 | + | } | |
| @@ -14,3 +14,7 @@ var syscallXABI0 = uintptr(cgo.SyscallXABI0) | |||||
| 14 | func NewCallback(_ any) uintptr { | 14 | func NewCallback(_ any) uintptr { | ||
| 15 | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64/s390x") | 15 | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64/s390x") | ||
| 16 | } | 16 | } | ||
| 17 | + | ||||
| 18 | + | func syscall_syscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||
| 19 | + | panic("purego: syscall_syscallN is only supported on windows") | |||
| 20 | + | } | |||