diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c10b6dbf..5c334cac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest, windows-11-arm] - go: ['1.25.x', '1.26.x'] + go: ['1.25.x', '1.26.x', '1.27.0-rc.1'] name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} defaults: @@ -75,13 +75,13 @@ jobs: - name: go build (Linux minor architectures) # Test them only on the latest Go to reduce CI time. - # s390x cannot be tested here as it requires Cgo. - if: startsWith(matrix.go, '1.26.') + if: startsWith(matrix.go, '1.27.') run: | # Check cross-compiling Linux binaries for minor architectures. env GOOS=linux GOARCH=loong64 go build -v ./... env GOOS=linux GOARCH=ppc64le go build -v ./... env GOOS=linux GOARCH=riscv64 go build -v ./... + env GOOS=linux GOARCH=s390x go build -v ./... - name: go build (Linux mips, Cgo) # mips builds only via the Cgo fallback (CGO_ENABLED=1). See #460. @@ -191,7 +191,7 @@ jobs: minor-arches: strategy: matrix: - go: ['1.25.x', '1.26.x'] + go: ['1.25.x', '1.26.x', '1.27.0-rc.1'] name: Test with Go ${{ matrix.go }} on Linux minor architectures runs-on: ubuntu-latest defaults: @@ -278,6 +278,11 @@ jobs: run: | go env -w CC=s390x-linux-gnu-gcc go env -w CXX=s390x-linux-gnu-g++ + # CGO_ENABLED=0 is only supported on Go 1.27+ for s390x. + if [[ "${{ matrix.go }}" != 1.25.* && "${{ matrix.go }}" != 1.26.* ]]; then + env GOOS=linux GOARCH=s390x CGO_ENABLED=0 go test -c -o=purego-test-nocgo . + env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 + fi env GOOS=linux GOARCH=s390x CGO_ENABLED=1 go test -c -o=purego-test-cgo . env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 go env -u CC @@ -326,7 +331,7 @@ jobs: strategy: fail-fast: false matrix: - go: ['1.25.x', '1.26.x'] + go: ['1.25.x', '1.26.x', '1.27.0-rc.1'] name: Test with Go ${{ matrix.go }} on Android amd64 runs-on: ubuntu-latest defaults: diff --git a/README.md b/README.md index d7e904d7..3239ab50 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ 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, s390x1,3 +- **Linux**: 3863, arm3, loong643, ppc64le3, riscv643, s390x3, 5 - **NetBSD**: amd643,4, arm643,4 -- **Windows**: 3863,5, arm3,5,6 +- **Windows**: 3863,6, arm3,6,7 #### Support Notes @@ -52,8 +52,9 @@ Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs o 2. These architectures support passing structs by value as arguments and return values when calling C functions, but not in callbacks created with `NewCallback` 3. These architectures do not support passing structs by value as arguments or return values 4. These architectures require the special flag `-gcflags="github.com/ebitengine/purego/internal/fakecgo=-std"` to compile with CGO_ENABLED=0 -5. These architectures only support `SyscallN` and `NewCallback` -6. These architectures are no longer supported as of Go 1.26 +5. These architectures require CGO_ENABLED=1 to compile in versions before Go 1.27, but will be supported without Cgo in Go 1.27 and later +6. These architectures only support `SyscallN` and `NewCallback` +7. These architectures are no longer supported as of Go 1.26 ## Example diff --git a/internal/fakecgo/trampolines_linux_s390x.s b/internal/fakecgo/trampolines_linux_s390x.s new file mode 100644 index 00000000..2ef29dbb --- /dev/null +++ b/internal/fakecgo/trampolines_linux_s390x.s @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2022 The Ebitengine Authors + +//go:build go1.27 && !cgo + +#include "textflag.h" + +TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT|NOFRAME, $0 + MOVD ·x_cgo_purego_setegid_call(SB), R1 + MOVD (R1), R1 + BR R1 + +TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT|NOFRAME, $0 + MOVD ·x_cgo_purego_seteuid_call(SB), R1 + MOVD (R1), R1 + BR R1 + +TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT|NOFRAME, $0 + MOVD ·x_cgo_purego_setgid_call(SB), R1 + MOVD (R1), R1 + BR R1 + +TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT|NOFRAME, $0 + MOVD ·x_cgo_purego_setregid_call(SB), R1 + MOVD (R1), R1 + BR R1 + +TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT|NOFRAME, $0 + MOVD ·x_cgo_purego_setresgid_call(SB), R1 + MOVD (R1), R1 + BR R1 + +TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT|NOFRAME, $0 + MOVD ·x_cgo_purego_setresuid_call(SB), R1 + MOVD (R1), R1 + BR R1 + +TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT|NOFRAME, $0 + MOVD ·x_cgo_purego_setreuid_call(SB), R1 + MOVD (R1), R1 + BR R1 + +TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT|NOFRAME, $0 + MOVD ·x_cgo_purego_setuid_call(SB), R1 + MOVD (R1), R1 + BR R1 + +TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT|NOFRAME, $0 + MOVD ·x_cgo_purego_setgroups_call(SB), R1 + MOVD (R1), R1 + BR R1 diff --git a/internal/fakecgo/trampolines_s390x.s b/internal/fakecgo/trampolines_s390x.s new file mode 100644 index 00000000..258c4a33 --- /dev/null +++ b/internal/fakecgo/trampolines_s390x.s @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2026 The Ebitengine Authors + +//go:build go1.27 && !cgo && linux + +#include "textflag.h" +#include "go_asm.h" + +// these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions. +// Note that C arguments are passed in R2-R6, which matches Go ABIInternal for the first five arguments. +// R1 is used as a temporary register. + +TEXT x_cgo_init_trampoline(SB), NOSPLIT|NOFRAME, $0-0 + MOVD R15, R1 + SUB $192, R15 + MOVD R1, 0(R15) // backchain + MOVD R14, 160(R15) // save R14 + MOVD R9, 168(R15) // save R9 (Go runtime needs this preserved) + + MOVD ·x_cgo_init_call(SB), R1 + MOVD (R1), R1 + BL R1 + + MOVD 168(R15), R9 + MOVD 160(R15), R14 + ADD $192, R15 + BR R14 + +TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT|NOFRAME, $0-0 + MOVD R15, R1 + SUB $176, R15 + MOVD R1, 0(R15) // backchain + MOVD R14, 152(R15) // save R14 + + MOVD ·x_cgo_thread_start_call(SB), R1 + MOVD (R1), R1 + BL R1 + + MOVD 152(R15), R14 + ADD $176, R15 + BR R14 + +TEXT x_cgo_setenv_trampoline(SB), NOSPLIT|NOFRAME, $0-0 + MOVD R15, R1 + SUB $176, R15 + MOVD R1, 0(R15) // backchain + MOVD R14, 152(R15) // save R14 + + MOVD ·x_cgo_setenv_call(SB), R1 + MOVD (R1), R1 + BL R1 + + MOVD 152(R15), R14 + ADD $176, R15 + BR R14 + +TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT|NOFRAME, $0-0 + MOVD R15, R1 + SUB $176, R15 + MOVD R1, 0(R15) // backchain + MOVD R14, 152(R15) // save R14 + + MOVD ·x_cgo_unsetenv_call(SB), R1 + MOVD (R1), R1 + BL R1 + + MOVD 152(R15), R14 + ADD $176, R15 + BR R14 + +// These just tail-call into Go functions +TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT|NOFRAME, $0-0 + BR ·x_cgo_notify_runtime_init_done(SB) + +TEXT x_cgo_bindm_trampoline(SB), NOSPLIT|NOFRAME, $0-0 + BR ·x_cgo_bindm(SB) + +// setg_trampoline(setg uintptr, g uintptr) - called from Go +TEXT ·setg_trampoline(SB), NOSPLIT|NOFRAME, $0-16 + MOVD 8(R15), R1 // setg function pointer + MOVD 16(R15), R2 // g pointer -> C arg + + MOVD R14, R0 + MOVD R15, R3 + SUB $160, R15 + MOVD R3, 0(R15) + MOVD R0, 112(R15) + MOVD R2, 120(R15) // save newg before call + + BL R1 // call setg_gcc + + // Assign g directly instead of calling runtime·load_g + // setg_gcc has already stored newg into TLS; put it in the g register too. + MOVD 120(R15), g + + MOVD 112(R15), R14 + ADD $160, R15 + BR R14 + +TEXT threadentry_trampoline(SB), NOSPLIT|NOFRAME, $0-0 + STMG R6, R15, 48(R15) // C save area + MOVD R15, R1 + SUB $176, R15 + MOVD R1, 0(R15) // backchain + + MOVD ·threadentry_call(SB), R1 + MOVD (R1), R1 + BL R1 + + ADD $176, R15 + LMG 48(R15), R6, R15 + RET + +TEXT ·call5(SB), NOSPLIT|NOFRAME, $0-56 + // Load Go args before modifying R15 + MOVD 8(R15), R1 // fn + MOVD 16(R15), R7 // a1 + MOVD 24(R15), R8 // a2 + MOVD 32(R15), R9 // a3 + MOVD 40(R15), R10 // a4 + MOVD 48(R15), R11 // a5 + + // Save state + MOVD R15, R0 // original R15 + MOVD R12, R6 // Go's R12 + ADD $-128, R15 + + // Set up C frame with backchain + MOVD R0, 0(R15) // backchain -> original R15 + MOVD R0, R3 // R3 = original R15 (can't use R0 as base!) + MOVD 0(R3), R7 // save 0(original R15) + MOVD $0, 0(R3) // terminate backchain + + // Save context + MOVD R14, 8(R15) + MOVD R6, 16(R15) // R12 + MOVD R0, 24(R15) // original R15 + MOVD R7, 32(R15) // saved backchain + + // Set up C args (reload a1 since R7 was clobbered) + MOVD 16(R3), R2 // a1 (use R3 as base, not R0!) + MOVD R8, R3 // a2 + MOVD R9, R4 // a3 + MOVD R10, R5 // a4 + MOVD R11, R6 // a5 + + BL R1 + + // Store result and restore + MOVD 24(R15), R3 // original R15 + MOVD R2, 56(R3) // return value + MOVD 32(R15), R7 + MOVD R7, 0(R3) // restore backchain + + MOVD 8(R15), R14 + MOVD 16(R15), R12 + MOVD 24(R15), R15 + BR R14 diff --git a/sys_unix_s390x.s b/sys_unix_s390x.s index 9eed6d29..d32221d6 100644 --- a/sys_unix_s390x.s +++ b/sys_unix_s390x.s @@ -18,22 +18,24 @@ // // S390X uses R2-R6 for integer arguments (5 registers) and F0,F2,F4,F6 for floats (4 registers). // -// Our frame layout (total 264 bytes, 8-byte aligned): +// Our frame layout (total 200 bytes, 8-byte aligned). To stay under the +// 800-byte nosplit limit on Go 1.27 the callbackArgs struct is stored in the +// caller's free linkage slot (old_R15+16..48) instead of our own frame: // 0(R15) - back chain // 48(R15) - saved R6-R15 (done by STMG) -// 160(R15) - callbackArgs struct (32 bytes: index, args, result, stackArgs) -// 192(R15) - args array start +// 128(R15) - args array start // // Args array layout: // - floats F0,F2,F4,F6 (32 bytes) // - ints R2-R6 (40 bytes) -// Total args array: 72 bytes, ends at 264 +// Total args array: 72 bytes, ends at 200 // +// callbackArgs struct lives at old_R15+16 (32 bytes: index, args, result, stackArgs) // Stack args in caller's frame start at old_R15+160 -#define FRAME_SIZE 264 -#define CB_ARGS 160 -#define ARGS_ARRAY 192 +#define FRAME_SIZE 200 +#define ARGS_ARRAY 128 +#define CB_ARGS (FRAME_SIZE+16) #define FLOAT_OFF 0 #define INT_OFF 32 @@ -71,7 +73,7 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 MOVD R5, (ARGS_ARRAY+INT_OFF+3*8)(R15) // R6 (5th int arg) was saved at 48(old_R15) by STMG - // old_R15 = current R15 + FRAME_SIZE, so R6 is at 48+FRAME_SIZE(R15) = 312(R15) + // old_R15 = current R15 + FRAME_SIZE MOVD (48+FRAME_SIZE)(R15), R1 MOVD R1, (ARGS_ARRAY+INT_OFF+4*8)(R15) diff --git a/syscall_unix.go b/syscall_unix.go index 2c904371..7a7f9e7e 100644 --- a/syscall_unix.go +++ b/syscall_unix.go @@ -1,8 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors -// TODO: remove s390x cgo dependency once golang/go#77449 is resolved -//go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (cgo && s390x))) || netbsd +//go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (s390x && (cgo || go1.27)))) || netbsd package purego