Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,19 @@ Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs o

- **Android**: 386<sup>1,3</sup>, arm<sup>1,3</sup>
- **FreeBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>
- **Linux**: 386<sup>3</sup>, arm<sup>3</sup>, loong64<sup>3</sup>, ppc64le<sup>3</sup>, riscv64<sup>3</sup>, s390x<sup>1,3</sup>
- **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>
- **NetBSD**: amd64<sup>3,4</sup>, arm64<sup>3,4</sup>
- **Windows**: 386<sup>3,5</sup>, arm<sup>3,5,6</sup>
- **Windows**: 386<sup>3,6</sup>, arm<sup>3,6,7</sup>

#### Support Notes

1. These architectures require CGO_ENABLED=1 to compile
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

Expand Down
51 changes: 51 additions & 0 deletions internal/fakecgo/trampolines_linux_s390x.s
Original file line number Diff line number Diff line change
@@ -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
158 changes: 158 additions & 0 deletions internal/fakecgo/trampolines_s390x.s
Original file line number Diff line number Diff line change
@@ -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
18 changes: 10 additions & 8 deletions sys_unix_s390x.s
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down
3 changes: 1 addition & 2 deletions syscall_unix.go
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Loading