purego: add support for s390x (#413)
Implements the necessary changes to support linux/s390x (with CGO_ENABLED=1). For now we can't support without cgo until golang/go#77449 has been addressed although I have a CL to implement the required changes.
- commit
- 585a4c527464bb6ef91b22aef08d55219c8fab09
- author
- George Adams · · 2026-02-16 09:23 UTC
- committer
- GitHub · · 2026-02-16 09:23 UTC
- parent
- d685103
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown - refs
- tag: v0.10.0-alpha.5
14 files · +4561 -20
M .github/workflows/test.yml +11 -0
| @@ -72,6 +72,7 @@ jobs: | |||
| 72 | 72 | ||
| 73 | 73 | - name: go build (Linux minor architectures) | |
| 74 | 74 | # Test them only on the latest Go to reduce CI time. | |
| 75 | + | # s390x cannot be tested here as it requires Cgo. | |
| 75 | 76 | if: startsWith(matrix.go, '1.26.') | |
| 76 | 77 | run: | | |
| 77 | 78 | # Check cross-compiling Linux binaries for minor architectures. | |
| @@ -179,6 +180,7 @@ jobs: | |||
| 179 | 180 | gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu \ | |
| 180 | 181 | gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu \ | |
| 181 | 182 | gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \ | |
| 183 | + | gcc-s390x-linux-gnu g++-s390x-linux-gnu \ | |
| 182 | 184 | qemu-user | |
| 183 | 185 | ||
| 184 | 186 | - name: go test (Linux loong64) | |
| @@ -216,6 +218,15 @@ jobs: | |||
| 216 | 218 | go env -u CC | |
| 217 | 219 | go env -u CXX | |
| 218 | 220 | ||
| 221 | + | - name: go test (Linux s390x) | |
| 222 | + | run: | | |
| 223 | + | go env -w CC=s390x-linux-gnu-gcc | |
| 224 | + | go env -w CXX=s390x-linux-gnu-g++ | |
| 225 | + | env GOOS=linux GOARCH=s390x CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |
| 226 | + | env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |
| 227 | + | go env -u CC | |
| 228 | + | go env -u CXX | |
| 229 | + | ||
| 219 | 230 | bsd: | |
| 220 | 231 | strategy: | |
| 221 | 232 | matrix: | |
| @@ -72,6 +72,7 @@ jobs: | |||||
| 72 | 72 | ||||
| 73 | - name: go build (Linux minor architectures) | 73 | - name: go build (Linux minor architectures) | ||
| 74 | # Test them only on the latest Go to reduce CI time. | 74 | # Test them only on the latest Go to reduce CI time. | ||
| 75 | + | # s390x cannot be tested here as it requires Cgo. | |||
| 75 | if: startsWith(matrix.go, '1.26.') | 76 | if: startsWith(matrix.go, '1.26.') | ||
| 76 | run: | | 77 | run: | | ||
| 77 | # Check cross-compiling Linux binaries for minor architectures. | 78 | # Check cross-compiling Linux binaries for minor architectures. | ||
| @@ -179,6 +180,7 @@ jobs: | |||||
| 179 | gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu \ | 180 | gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu \ | ||
| 180 | gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu \ | 181 | gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu \ | ||
| 181 | gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \ | 182 | gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \ | ||
| 183 | + | gcc-s390x-linux-gnu g++-s390x-linux-gnu \ | |||
| 182 | qemu-user | 184 | qemu-user | ||
| 183 | 185 | ||||
| 184 | - name: go test (Linux loong64) | 186 | - name: go test (Linux loong64) | ||
| @@ -216,6 +218,15 @@ jobs: | |||||
| 216 | go env -u CC | 218 | go env -u CC | ||
| 217 | go env -u CXX | 219 | go env -u CXX | ||
| 218 | 220 | ||||
| 221 | + | - name: go test (Linux s390x) | |||
| 222 | + | run: | | |||
| 223 | + | go env -w CC=s390x-linux-gnu-gcc | |||
| 224 | + | go env -w CXX=s390x-linux-gnu-g++ | |||
| 225 | + | env GOOS=linux GOARCH=s390x CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |||
| 226 | + | env QEMU_LD_PREFIX=/usr/s390x-linux-gnu qemu-s390x ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |||
| 227 | + | go env -u CC | |||
| 228 | + | go env -u CXX | |||
| 229 | + | ||||
| 219 | bsd: | 230 | bsd: | ||
| 220 | strategy: | 231 | strategy: | ||
| 221 | matrix: | 232 | matrix: | ||
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</sup>, arm<sup>1</sup> | |
| 44 | 44 | - **FreeBSD**: amd64<sup>2</sup>, arm64<sup>2</sup> | |
| 45 | - | - **Linux**: 386, arm, loong64, ppc64le, riscv64 | |
| 45 | + | - **Linux**: 386, arm, loong64, ppc64le, riscv64, s390x<sup>1</sup> | |
| 46 | 46 | - **Windows**: 386<sup>3</sup>, arm<sup>3,4</sup> | |
| 47 | 47 | ||
| 48 | 48 | #### Support Notes | |
| @@ -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</sup>, arm<sup>1</sup> | 43 | - **Android**: 386<sup>1</sup>, arm<sup>1</sup> | ||
| 44 | - **FreeBSD**: amd64<sup>2</sup>, arm64<sup>2</sup> | 44 | - **FreeBSD**: amd64<sup>2</sup>, arm64<sup>2</sup> | ||
| 45 | - | - **Linux**: 386, arm, loong64, ppc64le, riscv64 | 45 | + | - **Linux**: 386, arm, loong64, ppc64le, riscv64, s390x<sup>1</sup> |
| 46 | - **Windows**: 386<sup>3</sup>, arm<sup>3,4</sup> | 46 | - **Windows**: 386<sup>3</sup>, arm<sup>3,4</sup> | ||
| 47 | 47 | ||||
| 48 | #### Support Notes | 48 | #### Support Notes | ||
M callback_test.go +6 -1
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64)) | |
| 4 | + | //go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) | |
| 5 | 5 | ||
| 6 | 6 | package purego_test | |
| 7 | 7 | ||
| @@ -126,6 +126,11 @@ func TestNewCallbackFloat32(t *testing.T) { | |||
| 126 | 126 | } | |
| 127 | 127 | ||
| 128 | 128 | func TestNewCallbackFloat32AndFloat64(t *testing.T) { | |
| 129 | + | if runtime.GOARCH == "s390x" { | |
| 130 | + | // S390X has only 4 float registers (F0,F2,F4,F6), so 15 floats requires | |
| 131 | + | // 11 stack slots, but only 10 are available (maxArgs - numIntRegs = 15 - 5) | |
| 132 | + | t.Skip("Test requires too many stack arguments for s390x") | |
| 133 | + | } | |
| 129 | 134 | // This tests that calling a function with a mix of float32 and float64 arguments works | |
| 130 | 135 | const ( | |
| 131 | 136 | expectedCbTotalF32 = float32(72) | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64)) | 4 | + | //go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x)) |
| 5 | 5 | ||||
| 6 | package purego_test | 6 | package purego_test | ||
| 7 | 7 | ||||
| @@ -126,6 +126,11 @@ func TestNewCallbackFloat32(t *testing.T) { | |||||
| 126 | } | 126 | } | ||
| 127 | 127 | ||||
| 128 | func TestNewCallbackFloat32AndFloat64(t *testing.T) { | 128 | func TestNewCallbackFloat32AndFloat64(t *testing.T) { | ||
| 129 | + | if runtime.GOARCH == "s390x" { | |||
| 130 | + | // S390X has only 4 float registers (F0,F2,F4,F6), so 15 floats requires | |||
| 131 | + | // 11 stack slots, but only 10 are available (maxArgs - numIntRegs = 15 - 5) | |||
| 132 | + | t.Skip("Test requires too many stack arguments for s390x") | |||
| 133 | + | } | |||
| 129 | // This tests that calling a function with a mix of float32 and float64 arguments works | 134 | // This tests that calling a function with a mix of float32 and float64 arguments works | ||
| 130 | const ( | 135 | const ( | ||
| 131 | expectedCbTotalF32 = float32(72) | 136 | expectedCbTotalF32 = float32(72) | ||
M func.go +18 -4
| @@ -133,7 +133,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 133 | 133 | panic("purego: cfn is nil") | |
| 134 | 134 | } | |
| 135 | 135 | if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && | |
| 136 | - | runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | |
| 136 | + | runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" { | |
| 137 | 137 | panic("purego: float returns are not supported") | |
| 138 | 138 | } | |
| 139 | 139 | { | |
| @@ -273,7 +273,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 273 | 273 | var arm64_r8 uintptr | |
| 274 | 274 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 275 | 275 | outType := ty.Out(0) | |
| 276 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64") && outType.Size() > maxRegAllocStructSize { | |
| 276 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize { | |
| 277 | 277 | val := reflect.New(outType) | |
| 278 | 278 | keepAlive = append(keepAlive, val) | |
| 279 | 279 | addInt(val.Pointer()) | |
| @@ -313,7 +313,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 313 | 313 | syscall := thePool.Get().(*syscall15Args) | |
| 314 | 314 | defer thePool.Put(syscall) | |
| 315 | 315 | ||
| 316 | - | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" { | |
| 316 | + | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" { | |
| 317 | 317 | syscall.Set(cfn, sysargs[:], floats[:], 0) | |
| 318 | 318 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |
| 319 | 319 | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | |
| @@ -356,11 +356,15 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 356 | 356 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |
| 357 | 357 | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | |
| 358 | 358 | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | |
| 359 | + | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | |
| 359 | 360 | switch runtime.GOARCH { | |
| 360 | 361 | case "386": | |
| 361 | 362 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |
| 362 | 363 | case "ppc64le": | |
| 363 | 364 | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |
| 365 | + | case "s390x": | |
| 366 | + | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | |
| 367 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | |
| 364 | 368 | default: | |
| 365 | 369 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |
| 366 | 370 | } | |
| @@ -411,7 +415,12 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||
| 411 | 415 | addInt(0) | |
| 412 | 416 | } | |
| 413 | 417 | case reflect.Float32: | |
| 414 | - | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | |
| 418 | + | // On S390X big-endian, float32 goes in upper 32 bits of 64-bit FP register | |
| 419 | + | if runtime.GOARCH == "s390x" { | |
| 420 | + | addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) | |
| 421 | + | } else { | |
| 422 | + | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | |
| 423 | + | } | |
| 415 | 424 | case reflect.Float64: | |
| 416 | 425 | if is32bit { | |
| 417 | 426 | bits := math.Float64bits(v.Float()) | |
| @@ -498,6 +507,8 @@ func numOfFloatRegisters() int { | |||
| 498 | 507 | switch runtime.GOARCH { | |
| 499 | 508 | case "amd64", "arm64", "loong64", "ppc64le", "riscv64": | |
| 500 | 509 | return 8 | |
| 510 | + | case "s390x": | |
| 511 | + | return 4 | |
| 501 | 512 | case "arm": | |
| 502 | 513 | return 16 | |
| 503 | 514 | case "386": | |
| @@ -516,6 +527,9 @@ func numOfIntegerRegisters() int { | |||
| 516 | 527 | return 8 | |
| 517 | 528 | case "amd64": | |
| 518 | 529 | return 6 | |
| 530 | + | case "s390x": | |
| 531 | + | // S390X uses R2-R6 for integer arguments | |
| 532 | + | return 5 | |
| 519 | 533 | case "arm": | |
| 520 | 534 | return 4 | |
| 521 | 535 | case "386": | |
| @@ -133,7 +133,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 133 | panic("purego: cfn is nil") | 133 | panic("purego: cfn is nil") | ||
| 134 | } | 134 | } | ||
| 135 | if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && | 135 | if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && | ||
| 136 | - | runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | 136 | + | runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" { |
| 137 | panic("purego: float returns are not supported") | 137 | panic("purego: float returns are not supported") | ||
| 138 | } | 138 | } | ||
| 139 | { | 139 | { | ||
| @@ -273,7 +273,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 273 | var arm64_r8 uintptr | 273 | var arm64_r8 uintptr | ||
| 274 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | 274 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | ||
| 275 | outType := ty.Out(0) | 275 | outType := ty.Out(0) | ||
| 276 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64") && outType.Size() > maxRegAllocStructSize { | 276 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize { |
| 277 | val := reflect.New(outType) | 277 | val := reflect.New(outType) | ||
| 278 | keepAlive = append(keepAlive, val) | 278 | keepAlive = append(keepAlive, val) | ||
| 279 | addInt(val.Pointer()) | 279 | addInt(val.Pointer()) | ||
| @@ -313,7 +313,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 313 | syscall := thePool.Get().(*syscall15Args) | 313 | syscall := thePool.Get().(*syscall15Args) | ||
| 314 | defer thePool.Put(syscall) | 314 | defer thePool.Put(syscall) | ||
| 315 | 315 | ||||
| 316 | - | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" { | 316 | + | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" { |
| 317 | syscall.Set(cfn, sysargs[:], floats[:], 0) | 317 | syscall.Set(cfn, sysargs[:], floats[:], 0) | ||
| 318 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | 318 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | ||
| 319 | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | 319 | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | ||
| @@ -356,11 +356,15 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 356 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | 356 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | ||
| 357 | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | 357 | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | ||
| 358 | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | 358 | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | ||
| 359 | + | // On S390X (big-endian), float32 is in upper 32 bits of the 64-bit FP register. | |||
| 359 | switch runtime.GOARCH { | 360 | switch runtime.GOARCH { | ||
| 360 | case "386": | 361 | case "386": | ||
| 361 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | 362 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | ||
| 362 | case "ppc64le": | 363 | case "ppc64le": | ||
| 363 | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | 364 | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | ||
| 365 | + | case "s390x": | |||
| 366 | + | // S390X is big-endian: float32 in upper 32 bits of 64-bit register | |||
| 367 | + | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1 >> 32)))) | |||
| 364 | default: | 368 | default: | ||
| 365 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | 369 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | ||
| 366 | } | 370 | } | ||
| @@ -411,7 +415,12 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||||
| 411 | addInt(0) | 415 | addInt(0) | ||
| 412 | } | 416 | } | ||
| 413 | case reflect.Float32: | 417 | case reflect.Float32: | ||
| 414 | - | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | 418 | + | // On S390X big-endian, float32 goes in upper 32 bits of 64-bit FP register |
| 419 | + | if runtime.GOARCH == "s390x" { | |||
| 420 | + | addFloat(uintptr(math.Float32bits(float32(v.Float()))) << 32) | |||
| 421 | + | } else { | |||
| 422 | + | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | |||
| 423 | + | } | |||
| 415 | case reflect.Float64: | 424 | case reflect.Float64: | ||
| 416 | if is32bit { | 425 | if is32bit { | ||
| 417 | bits := math.Float64bits(v.Float()) | 426 | bits := math.Float64bits(v.Float()) | ||
| @@ -498,6 +507,8 @@ func numOfFloatRegisters() int { | |||||
| 498 | switch runtime.GOARCH { | 507 | switch runtime.GOARCH { | ||
| 499 | case "amd64", "arm64", "loong64", "ppc64le", "riscv64": | 508 | case "amd64", "arm64", "loong64", "ppc64le", "riscv64": | ||
| 500 | return 8 | 509 | return 8 | ||
| 510 | + | case "s390x": | |||
| 511 | + | return 4 | |||
| 501 | case "arm": | 512 | case "arm": | ||
| 502 | return 16 | 513 | return 16 | ||
| 503 | case "386": | 514 | case "386": | ||
| @@ -516,6 +527,9 @@ func numOfIntegerRegisters() int { | |||||
| 516 | return 8 | 527 | return 8 | ||
| 517 | case "amd64": | 528 | case "amd64": | ||
| 518 | return 6 | 529 | return 6 | ||
| 530 | + | case "s390x": | |||
| 531 | + | // S390X uses R2-R6 for integer arguments | |||
| 532 | + | return 5 | |||
| 519 | case "arm": | 533 | case "arm": | ||
| 520 | return 4 | 534 | return 4 | ||
| 521 | case "386": | 535 | case "386": | ||
M func_test.go +4 -4
| @@ -50,7 +50,7 @@ func TestRegisterFunc(t *testing.T) { | |||
| 50 | 50 | } | |
| 51 | 51 | ||
| 52 | 52 | func Test_qsort(t *testing.T) { | |
| 53 | - | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | |
| 53 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" { | |
| 54 | 54 | t.Skip("Platform doesn't support Floats") | |
| 55 | 55 | return | |
| 56 | 56 | } | |
| @@ -79,7 +79,7 @@ func Test_qsort(t *testing.T) { | |||
| 79 | 79 | } | |
| 80 | 80 | ||
| 81 | 81 | func TestRegisterFunc_Floats(t *testing.T) { | |
| 82 | - | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | |
| 82 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" { | |
| 83 | 83 | t.Skip("Platform doesn't support Floats") | |
| 84 | 84 | return | |
| 85 | 85 | } | |
| @@ -121,7 +121,7 @@ func TestRegisterFunc_Floats(t *testing.T) { | |||
| 121 | 121 | } | |
| 122 | 122 | ||
| 123 | 123 | func TestRegisterLibFunc_Bool(t *testing.T) { | |
| 124 | - | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | |
| 124 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" { | |
| 125 | 125 | t.Skip("Platform doesn't support callbacks") | |
| 126 | 126 | return | |
| 127 | 127 | } | |
| @@ -376,7 +376,7 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||
| 376 | 376 | if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") { | |
| 377 | 377 | t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking") | |
| 378 | 378 | } | |
| 379 | - | if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64") { | |
| 379 | + | if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") { | |
| 380 | 380 | t.Skip("float32 stack arguments not yet supported on this platform") | |
| 381 | 381 | } | |
| 382 | 382 | // Struct tests require Darwin ARM64 or AMD64 | |
| @@ -50,7 +50,7 @@ func TestRegisterFunc(t *testing.T) { | |||||
| 50 | } | 50 | } | ||
| 51 | 51 | ||||
| 52 | func Test_qsort(t *testing.T) { | 52 | func Test_qsort(t *testing.T) { | ||
| 53 | - | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | 53 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" { |
| 54 | t.Skip("Platform doesn't support Floats") | 54 | t.Skip("Platform doesn't support Floats") | ||
| 55 | return | 55 | return | ||
| 56 | } | 56 | } | ||
| @@ -79,7 +79,7 @@ func Test_qsort(t *testing.T) { | |||||
| 79 | } | 79 | } | ||
| 80 | 80 | ||||
| 81 | func TestRegisterFunc_Floats(t *testing.T) { | 81 | func TestRegisterFunc_Floats(t *testing.T) { | ||
| 82 | - | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | 82 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" { |
| 83 | t.Skip("Platform doesn't support Floats") | 83 | t.Skip("Platform doesn't support Floats") | ||
| 84 | return | 84 | return | ||
| 85 | } | 85 | } | ||
| @@ -121,7 +121,7 @@ func TestRegisterFunc_Floats(t *testing.T) { | |||||
| 121 | } | 121 | } | ||
| 122 | 122 | ||||
| 123 | func TestRegisterLibFunc_Bool(t *testing.T) { | 123 | func TestRegisterLibFunc_Bool(t *testing.T) { | ||
| 124 | - | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | 124 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" && runtime.GOARCH != "s390x" { |
| 125 | t.Skip("Platform doesn't support callbacks") | 125 | t.Skip("Platform doesn't support callbacks") | ||
| 126 | return | 126 | return | ||
| 127 | } | 127 | } | ||
| @@ -376,7 +376,7 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||||
| 376 | if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") { | 376 | if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") { | ||
| 377 | t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking") | 377 | t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking") | ||
| 378 | } | 378 | } | ||
| 379 | - | if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64") { | 379 | + | if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") { |
| 380 | t.Skip("float32 stack arguments not yet supported on this platform") | 380 | t.Skip("float32 stack arguments not yet supported on this platform") | ||
| 381 | } | 381 | } | ||
| 382 | // Struct tests require Darwin ARM64 or AMD64 | 382 | // Struct tests require Darwin ARM64 or AMD64 | ||
A internal/fakecgo/asm_s390x.s +55 -0
| @@ -0,0 +1,55 @@ | |||
| 1 | + | // Copyright 2016 The Go Authors. All rights reserved. | |
| 2 | + | // Use of this source code is governed by a BSD-style | |
| 3 | + | // license that can be found in the LICENSE file. | |
| 4 | + | ||
| 5 | + | #include "textflag.h" | |
| 6 | + | ||
| 7 | + | // Called by C code generated by cmd/cgo. | |
| 8 | + | // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) | |
| 9 | + | // Saves C callee-saved registers and calls cgocallback with three arguments. | |
| 10 | + | // fn is the PC of a func(a unsafe.Pointer) function. | |
| 11 | + | TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |
| 12 | + | // Start with standard C stack frame layout and linkage. | |
| 13 | + | ||
| 14 | + | // Save R6-R15 in the register save area of the calling function. | |
| 15 | + | STMG R6, R15, 48(R15) | |
| 16 | + | ||
| 17 | + | // Allocate 96 bytes on the stack. | |
| 18 | + | MOVD $-96(R15), R15 | |
| 19 | + | ||
| 20 | + | // Save F8-F15 in our stack frame. | |
| 21 | + | FMOVD F8, 32(R15) | |
| 22 | + | FMOVD F9, 40(R15) | |
| 23 | + | FMOVD F10, 48(R15) | |
| 24 | + | FMOVD F11, 56(R15) | |
| 25 | + | FMOVD F12, 64(R15) | |
| 26 | + | FMOVD F13, 72(R15) | |
| 27 | + | FMOVD F14, 80(R15) | |
| 28 | + | FMOVD F15, 88(R15) | |
| 29 | + | ||
| 30 | + | // Initialize Go ABI environment. | |
| 31 | + | BL runtime·load_g(SB) | |
| 32 | + | ||
| 33 | + | MOVD R2, 8(R15) // fn unsafe.Pointer | |
| 34 | + | MOVD R3, 16(R15) // a unsafe.Pointer | |
| 35 | + | ||
| 36 | + | // Skip R4 = n uint32 | |
| 37 | + | MOVD R5, 24(R15) // ctxt uintptr | |
| 38 | + | BL runtime·cgocallback(SB) | |
| 39 | + | ||
| 40 | + | FMOVD 32(R15), F8 | |
| 41 | + | FMOVD 40(R15), F9 | |
| 42 | + | FMOVD 48(R15), F10 | |
| 43 | + | FMOVD 56(R15), F11 | |
| 44 | + | FMOVD 64(R15), F12 | |
| 45 | + | FMOVD 72(R15), F13 | |
| 46 | + | FMOVD 80(R15), F14 | |
| 47 | + | FMOVD 88(R15), F15 | |
| 48 | + | ||
| 49 | + | // De-allocate stack frame. | |
| 50 | + | MOVD $96(R15), R15 | |
| 51 | + | ||
| 52 | + | // Restore R6-R15. | |
| 53 | + | LMG 48(R15), R6, R15 | |
| 54 | + | ||
| 55 | + | RET | |
| @@ -0,0 +1,55 @@ | |||||
| 1 | + | // Copyright 2016 The Go Authors. All rights reserved. | |||
| 2 | + | // Use of this source code is governed by a BSD-style | |||
| 3 | + | // license that can be found in the LICENSE file. | |||
| 4 | + | ||||
| 5 | + | #include "textflag.h" | |||
| 6 | + | ||||
| 7 | + | // Called by C code generated by cmd/cgo. | |||
| 8 | + | // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) | |||
| 9 | + | // Saves C callee-saved registers and calls cgocallback with three arguments. | |||
| 10 | + | // fn is the PC of a func(a unsafe.Pointer) function. | |||
| 11 | + | TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||
| 12 | + | // Start with standard C stack frame layout and linkage. | |||
| 13 | + | ||||
| 14 | + | // Save R6-R15 in the register save area of the calling function. | |||
| 15 | + | STMG R6, R15, 48(R15) | |||
| 16 | + | ||||
| 17 | + | // Allocate 96 bytes on the stack. | |||
| 18 | + | MOVD $-96(R15), R15 | |||
| 19 | + | ||||
| 20 | + | // Save F8-F15 in our stack frame. | |||
| 21 | + | FMOVD F8, 32(R15) | |||
| 22 | + | FMOVD F9, 40(R15) | |||
| 23 | + | FMOVD F10, 48(R15) | |||
| 24 | + | FMOVD F11, 56(R15) | |||
| 25 | + | FMOVD F12, 64(R15) | |||
| 26 | + | FMOVD F13, 72(R15) | |||
| 27 | + | FMOVD F14, 80(R15) | |||
| 28 | + | FMOVD F15, 88(R15) | |||
| 29 | + | ||||
| 30 | + | // Initialize Go ABI environment. | |||
| 31 | + | BL runtime·load_g(SB) | |||
| 32 | + | ||||
| 33 | + | MOVD R2, 8(R15) // fn unsafe.Pointer | |||
| 34 | + | MOVD R3, 16(R15) // a unsafe.Pointer | |||
| 35 | + | ||||
| 36 | + | // Skip R4 = n uint32 | |||
| 37 | + | MOVD R5, 24(R15) // ctxt uintptr | |||
| 38 | + | BL runtime·cgocallback(SB) | |||
| 39 | + | ||||
| 40 | + | FMOVD 32(R15), F8 | |||
| 41 | + | FMOVD 40(R15), F9 | |||
| 42 | + | FMOVD 48(R15), F10 | |||
| 43 | + | FMOVD 56(R15), F11 | |||
| 44 | + | FMOVD 64(R15), F12 | |||
| 45 | + | FMOVD 72(R15), F13 | |||
| 46 | + | FMOVD 80(R15), F14 | |||
| 47 | + | FMOVD 88(R15), F15 | |||
| 48 | + | ||||
| 49 | + | // De-allocate stack frame. | |||
| 50 | + | MOVD $96(R15), R15 | |||
| 51 | + | ||||
| 52 | + | // Restore R6-R15. | |||
| 53 | + | LMG 48(R15), R6, R15 | |||
| 54 | + | ||||
| 55 | + | RET | |||
A struct_s390x.go +143 -0
| @@ -0,0 +1,143 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | package purego | |
| 5 | + | ||
| 6 | + | import ( | |
| 7 | + | "reflect" | |
| 8 | + | "unsafe" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | |
| 12 | + | outSize := outType.Size() | |
| 13 | + | ||
| 14 | + | switch { | |
| 15 | + | case outSize == 0: | |
| 16 | + | return reflect.New(outType).Elem() | |
| 17 | + | ||
| 18 | + | case outSize <= 16: | |
| 19 | + | // Reconstruct from registers by copying raw bytes | |
| 20 | + | var buf [16]byte | |
| 21 | + | ||
| 22 | + | // Integer registers | |
| 23 | + | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.a1 | |
| 24 | + | if outSize > 8 { | |
| 25 | + | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.a2 | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | // Homogeneous float aggregates override integer regs | |
| 29 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 30 | + | if outType.Field(0).Type.Kind() == reflect.Float32 { | |
| 31 | + | // float32 values in FP regs | |
| 32 | + | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | |
| 33 | + | for i := 0; i < numFields; i++ { | |
| 34 | + | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) | |
| 35 | + | } | |
| 36 | + | } else { | |
| 37 | + | // float64: whole register value is valid | |
| 38 | + | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.f1 | |
| 39 | + | if outSize > 8 { | |
| 40 | + | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.f2 | |
| 41 | + | } | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem() | |
| 46 | + | ||
| 47 | + | default: | |
| 48 | + | // Returned indirectly via pointer in a1 | |
| 49 | + | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)) | |
| 50 | + | return reflect.NewAt(outType, ptr).Elem() | |
| 51 | + | } | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | func addStruct( | |
| 55 | + | v reflect.Value, | |
| 56 | + | numInts, numFloats, numStack *int, | |
| 57 | + | addInt, addFloat, addStack func(uintptr), | |
| 58 | + | keepAlive []any, | |
| 59 | + | ) []any { | |
| 60 | + | size := v.Type().Size() | |
| 61 | + | if size == 0 { | |
| 62 | + | return keepAlive | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | if size <= 16 { | |
| 66 | + | return placeSmallAggregateS390X(v, addFloat, addInt, keepAlive) | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | return placeStack(v, keepAlive, addInt) | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | func placeSmallAggregateS390X( | |
| 73 | + | v reflect.Value, | |
| 74 | + | addFloat, addInt func(uintptr), | |
| 75 | + | keepAlive []any, | |
| 76 | + | ) []any { | |
| 77 | + | size := v.Type().Size() | |
| 78 | + | ||
| 79 | + | var ptr unsafe.Pointer | |
| 80 | + | if v.CanAddr() { | |
| 81 | + | ptr = v.Addr().UnsafePointer() | |
| 82 | + | } else { | |
| 83 | + | tmp := reflect.New(v.Type()) | |
| 84 | + | tmp.Elem().Set(v) | |
| 85 | + | ptr = tmp.UnsafePointer() | |
| 86 | + | keepAlive = append(keepAlive, tmp.Interface()) | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | var buf [16]byte | |
| 90 | + | src := unsafe.Slice((*byte)(ptr), size) | |
| 91 | + | copy(buf[:], src) | |
| 92 | + | ||
| 93 | + | w0 := *(*uintptr)(unsafe.Pointer(&buf[0])) | |
| 94 | + | w1 := uintptr(0) | |
| 95 | + | if size > 8 { | |
| 96 | + | w1 = *(*uintptr)(unsafe.Pointer(&buf[8])) | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | if isFloats, _ := isAllSameFloat(v.Type()); isFloats { | |
| 100 | + | addFloat(w0) | |
| 101 | + | if size > 8 { | |
| 102 | + | addFloat(w1) | |
| 103 | + | } | |
| 104 | + | } else { | |
| 105 | + | addInt(w0) | |
| 106 | + | if size > 8 { | |
| 107 | + | addInt(w1) | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | return keepAlive | |
| 112 | + | } | |
| 113 | + | ||
| 114 | + | // placeStack is a fallback for structs that are too large to fit in registers | |
| 115 | + | func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | |
| 116 | + | if v.CanAddr() { | |
| 117 | + | addInt(v.Addr().Pointer()) | |
| 118 | + | return keepAlive | |
| 119 | + | } | |
| 120 | + | ptr := reflect.New(v.Type()) | |
| 121 | + | ptr.Elem().Set(v) | |
| 122 | + | addInt(ptr.Pointer()) | |
| 123 | + | return append(keepAlive, ptr.Interface()) | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |
| 127 | + | // S390X does not bundle stack args | |
| 128 | + | return false | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | func collectStackArgs( | |
| 132 | + | args []reflect.Value, | |
| 133 | + | i, numInts, numFloats int, | |
| 134 | + | keepAlive []any, | |
| 135 | + | addInt, addFloat, addStack func(uintptr), | |
| 136 | + | numIntsPtr, numFloatsPtr, numStackPtr *int, | |
| 137 | + | ) ([]reflect.Value, []any) { | |
| 138 | + | return nil, keepAlive | |
| 139 | + | } | |
| 140 | + | ||
| 141 | + | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |
| 142 | + | panic("bundleStackArgs not supported on S390X") | |
| 143 | + | } | |
| @@ -0,0 +1,143 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | package purego | |||
| 5 | + | ||||
| 6 | + | import ( | |||
| 7 | + | "reflect" | |||
| 8 | + | "unsafe" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | func getStruct(outType reflect.Type, syscall syscall15Args) reflect.Value { | |||
| 12 | + | outSize := outType.Size() | |||
| 13 | + | ||||
| 14 | + | switch { | |||
| 15 | + | case outSize == 0: | |||
| 16 | + | return reflect.New(outType).Elem() | |||
| 17 | + | ||||
| 18 | + | case outSize <= 16: | |||
| 19 | + | // Reconstruct from registers by copying raw bytes | |||
| 20 | + | var buf [16]byte | |||
| 21 | + | ||||
| 22 | + | // Integer registers | |||
| 23 | + | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.a1 | |||
| 24 | + | if outSize > 8 { | |||
| 25 | + | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.a2 | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | // Homogeneous float aggregates override integer regs | |||
| 29 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |||
| 30 | + | if outType.Field(0).Type.Kind() == reflect.Float32 { | |||
| 31 | + | // float32 values in FP regs | |||
| 32 | + | f := []uintptr{syscall.f1, syscall.f2, syscall.f3, syscall.f4} | |||
| 33 | + | for i := 0; i < numFields; i++ { | |||
| 34 | + | *(*uint32)(unsafe.Pointer(&buf[i*4])) = uint32(f[i]) | |||
| 35 | + | } | |||
| 36 | + | } else { | |||
| 37 | + | // float64: whole register value is valid | |||
| 38 | + | *(*uintptr)(unsafe.Pointer(&buf[0])) = syscall.f1 | |||
| 39 | + | if outSize > 8 { | |||
| 40 | + | *(*uintptr)(unsafe.Pointer(&buf[8])) = syscall.f2 | |||
| 41 | + | } | |||
| 42 | + | } | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | return reflect.NewAt(outType, unsafe.Pointer(&buf[0])).Elem() | |||
| 46 | + | ||||
| 47 | + | default: | |||
| 48 | + | // Returned indirectly via pointer in a1 | |||
| 49 | + | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)) | |||
| 50 | + | return reflect.NewAt(outType, ptr).Elem() | |||
| 51 | + | } | |||
| 52 | + | } | |||
| 53 | + | ||||
| 54 | + | func addStruct( | |||
| 55 | + | v reflect.Value, | |||
| 56 | + | numInts, numFloats, numStack *int, | |||
| 57 | + | addInt, addFloat, addStack func(uintptr), | |||
| 58 | + | keepAlive []any, | |||
| 59 | + | ) []any { | |||
| 60 | + | size := v.Type().Size() | |||
| 61 | + | if size == 0 { | |||
| 62 | + | return keepAlive | |||
| 63 | + | } | |||
| 64 | + | ||||
| 65 | + | if size <= 16 { | |||
| 66 | + | return placeSmallAggregateS390X(v, addFloat, addInt, keepAlive) | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | return placeStack(v, keepAlive, addInt) | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | func placeSmallAggregateS390X( | |||
| 73 | + | v reflect.Value, | |||
| 74 | + | addFloat, addInt func(uintptr), | |||
| 75 | + | keepAlive []any, | |||
| 76 | + | ) []any { | |||
| 77 | + | size := v.Type().Size() | |||
| 78 | + | ||||
| 79 | + | var ptr unsafe.Pointer | |||
| 80 | + | if v.CanAddr() { | |||
| 81 | + | ptr = v.Addr().UnsafePointer() | |||
| 82 | + | } else { | |||
| 83 | + | tmp := reflect.New(v.Type()) | |||
| 84 | + | tmp.Elem().Set(v) | |||
| 85 | + | ptr = tmp.UnsafePointer() | |||
| 86 | + | keepAlive = append(keepAlive, tmp.Interface()) | |||
| 87 | + | } | |||
| 88 | + | ||||
| 89 | + | var buf [16]byte | |||
| 90 | + | src := unsafe.Slice((*byte)(ptr), size) | |||
| 91 | + | copy(buf[:], src) | |||
| 92 | + | ||||
| 93 | + | w0 := *(*uintptr)(unsafe.Pointer(&buf[0])) | |||
| 94 | + | w1 := uintptr(0) | |||
| 95 | + | if size > 8 { | |||
| 96 | + | w1 = *(*uintptr)(unsafe.Pointer(&buf[8])) | |||
| 97 | + | } | |||
| 98 | + | ||||
| 99 | + | if isFloats, _ := isAllSameFloat(v.Type()); isFloats { | |||
| 100 | + | addFloat(w0) | |||
| 101 | + | if size > 8 { | |||
| 102 | + | addFloat(w1) | |||
| 103 | + | } | |||
| 104 | + | } else { | |||
| 105 | + | addInt(w0) | |||
| 106 | + | if size > 8 { | |||
| 107 | + | addInt(w1) | |||
| 108 | + | } | |||
| 109 | + | } | |||
| 110 | + | ||||
| 111 | + | return keepAlive | |||
| 112 | + | } | |||
| 113 | + | ||||
| 114 | + | // placeStack is a fallback for structs that are too large to fit in registers | |||
| 115 | + | func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | |||
| 116 | + | if v.CanAddr() { | |||
| 117 | + | addInt(v.Addr().Pointer()) | |||
| 118 | + | return keepAlive | |||
| 119 | + | } | |||
| 120 | + | ptr := reflect.New(v.Type()) | |||
| 121 | + | ptr.Elem().Set(v) | |||
| 122 | + | addInt(ptr.Pointer()) | |||
| 123 | + | return append(keepAlive, ptr.Interface()) | |||
| 124 | + | } | |||
| 125 | + | ||||
| 126 | + | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |||
| 127 | + | // S390X does not bundle stack args | |||
| 128 | + | return false | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | func collectStackArgs( | |||
| 132 | + | args []reflect.Value, | |||
| 133 | + | i, numInts, numFloats int, | |||
| 134 | + | keepAlive []any, | |||
| 135 | + | addInt, addFloat, addStack func(uintptr), | |||
| 136 | + | numIntsPtr, numFloatsPtr, numStackPtr *int, | |||
| 137 | + | ) ([]reflect.Value, []any) { | |||
| 138 | + | return nil, keepAlive | |||
| 139 | + | } | |||
| 140 | + | ||||
| 141 | + | func bundleStackArgs(stackArgs []reflect.Value, addStack func(uintptr)) { | |||
| 142 | + | panic("bundleStackArgs not supported on S390X") | |||
| 143 | + | } | |||
A sys_s390x.s +114 -0
| @@ -0,0 +1,114 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build linux | |
| 5 | + | ||
| 6 | + | #include "textflag.h" | |
| 7 | + | #include "go_asm.h" | |
| 8 | + | #include "funcdata.h" | |
| 9 | + | ||
| 10 | + | // S390X ELF ABI: | |
| 11 | + | // - Integer args: R2-R6 (5 registers) | |
| 12 | + | // - Float args: F0, F2, F4, F6 (4 registers, even-numbered) | |
| 13 | + | // - Return: R2 (integer), F0 (float) | |
| 14 | + | // - Stack pointer: R15 | |
| 15 | + | // - Link register: R14 | |
| 16 | + | // - Callee-saved: R6-R13, F8-F15 (but R6 is also used for 5th param) | |
| 17 | + | // | |
| 18 | + | // Stack frame layout (aligned to 8 bytes): | |
| 19 | + | // 0(R15) - back chain | |
| 20 | + | // 8(R15) - reserved | |
| 21 | + | // 16(R15) - reserved | |
| 22 | + | // ... - register save area (R6-R15 at 48(R15)) | |
| 23 | + | // 160(R15) - parameter area start (args beyond registers) | |
| 24 | + | // | |
| 25 | + | // We need space for: | |
| 26 | + | // - 160 bytes standard frame (with register save area) | |
| 27 | + | // - Stack args a6-a15 (10 * 8 = 80 bytes) | |
| 28 | + | // - Saved args pointer (8 bytes) | |
| 29 | + | // - Padding for alignment | |
| 30 | + | // Total: 264 bytes (rounded to 8-byte alignment) | |
| 31 | + | ||
| 32 | + | #define STACK_SIZE 264 | |
| 33 | + | #define STACK_ARGS 160 | |
| 34 | + | #define ARGP_SAVE 248 | |
| 35 | + | ||
| 36 | + | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |
| 37 | + | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |
| 38 | + | ||
| 39 | + | TEXT syscall15X(SB), NOSPLIT, $0 | |
| 40 | + | // On entry, R2 contains the args pointer | |
| 41 | + | // Save callee-saved registers in caller's frame (per ABI) | |
| 42 | + | STMG R6, R15, 48(R15) | |
| 43 | + | ||
| 44 | + | // Allocate our stack frame | |
| 45 | + | MOVD R15, R1 | |
| 46 | + | SUB $STACK_SIZE, R15 | |
| 47 | + | MOVD R1, 0(R15) // back chain | |
| 48 | + | ||
| 49 | + | // Save args pointer | |
| 50 | + | MOVD R2, ARGP_SAVE(R15) | |
| 51 | + | ||
| 52 | + | // R9 := args pointer (syscall15Args*) | |
| 53 | + | MOVD R2, R9 | |
| 54 | + | ||
| 55 | + | // Load float args into F0, F2, F4, F6 (s390x uses even-numbered FPRs) | |
| 56 | + | FMOVD syscall15Args_f1(R9), F0 | |
| 57 | + | FMOVD syscall15Args_f2(R9), F2 | |
| 58 | + | FMOVD syscall15Args_f3(R9), F4 | |
| 59 | + | FMOVD syscall15Args_f4(R9), F6 | |
| 60 | + | ||
| 61 | + | // Load integer args into R2-R6 (5 registers) | |
| 62 | + | MOVD syscall15Args_a1(R9), R2 | |
| 63 | + | MOVD syscall15Args_a2(R9), R3 | |
| 64 | + | MOVD syscall15Args_a3(R9), R4 | |
| 65 | + | MOVD syscall15Args_a4(R9), R5 | |
| 66 | + | MOVD syscall15Args_a5(R9), R6 | |
| 67 | + | ||
| 68 | + | // Spill remaining args (a6-a15) onto the stack at 160(R15) | |
| 69 | + | MOVD ARGP_SAVE(R15), R9 // reload args pointer | |
| 70 | + | MOVD syscall15Args_a6(R9), R1 | |
| 71 | + | MOVD R1, (STACK_ARGS+0*8)(R15) | |
| 72 | + | MOVD syscall15Args_a7(R9), R1 | |
| 73 | + | MOVD R1, (STACK_ARGS+1*8)(R15) | |
| 74 | + | MOVD syscall15Args_a8(R9), R1 | |
| 75 | + | MOVD R1, (STACK_ARGS+2*8)(R15) | |
| 76 | + | MOVD syscall15Args_a9(R9), R1 | |
| 77 | + | MOVD R1, (STACK_ARGS+3*8)(R15) | |
| 78 | + | MOVD syscall15Args_a10(R9), R1 | |
| 79 | + | MOVD R1, (STACK_ARGS+4*8)(R15) | |
| 80 | + | MOVD syscall15Args_a11(R9), R1 | |
| 81 | + | MOVD R1, (STACK_ARGS+5*8)(R15) | |
| 82 | + | MOVD syscall15Args_a12(R9), R1 | |
| 83 | + | MOVD R1, (STACK_ARGS+6*8)(R15) | |
| 84 | + | MOVD syscall15Args_a13(R9), R1 | |
| 85 | + | MOVD R1, (STACK_ARGS+7*8)(R15) | |
| 86 | + | MOVD syscall15Args_a14(R9), R1 | |
| 87 | + | MOVD R1, (STACK_ARGS+8*8)(R15) | |
| 88 | + | MOVD syscall15Args_a15(R9), R1 | |
| 89 | + | MOVD R1, (STACK_ARGS+9*8)(R15) | |
| 90 | + | ||
| 91 | + | // Call function | |
| 92 | + | MOVD syscall15Args_fn(R9), R1 | |
| 93 | + | BL (R1) | |
| 94 | + | ||
| 95 | + | // Restore args pointer for storing results | |
| 96 | + | MOVD ARGP_SAVE(R15), R9 | |
| 97 | + | ||
| 98 | + | // Store integer results back (R2, R3) | |
| 99 | + | MOVD R2, syscall15Args_a1(R9) | |
| 100 | + | MOVD R3, syscall15Args_a2(R9) | |
| 101 | + | ||
| 102 | + | // Store float return values (F0, F2, F4, F6) | |
| 103 | + | FMOVD F0, syscall15Args_f1(R9) | |
| 104 | + | FMOVD F2, syscall15Args_f2(R9) | |
| 105 | + | FMOVD F4, syscall15Args_f3(R9) | |
| 106 | + | FMOVD F6, syscall15Args_f4(R9) | |
| 107 | + | ||
| 108 | + | // Deallocate stack frame | |
| 109 | + | ADD $STACK_SIZE, R15 | |
| 110 | + | ||
| 111 | + | // Restore callee-saved registers from caller's save area | |
| 112 | + | LMG 48(R15), R6, R15 | |
| 113 | + | ||
| 114 | + | RET | |
| @@ -0,0 +1,114 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build linux | |||
| 5 | + | ||||
| 6 | + | #include "textflag.h" | |||
| 7 | + | #include "go_asm.h" | |||
| 8 | + | #include "funcdata.h" | |||
| 9 | + | ||||
| 10 | + | // S390X ELF ABI: | |||
| 11 | + | // - Integer args: R2-R6 (5 registers) | |||
| 12 | + | // - Float args: F0, F2, F4, F6 (4 registers, even-numbered) | |||
| 13 | + | // - Return: R2 (integer), F0 (float) | |||
| 14 | + | // - Stack pointer: R15 | |||
| 15 | + | // - Link register: R14 | |||
| 16 | + | // - Callee-saved: R6-R13, F8-F15 (but R6 is also used for 5th param) | |||
| 17 | + | // | |||
| 18 | + | // Stack frame layout (aligned to 8 bytes): | |||
| 19 | + | // 0(R15) - back chain | |||
| 20 | + | // 8(R15) - reserved | |||
| 21 | + | // 16(R15) - reserved | |||
| 22 | + | // ... - register save area (R6-R15 at 48(R15)) | |||
| 23 | + | // 160(R15) - parameter area start (args beyond registers) | |||
| 24 | + | // | |||
| 25 | + | // We need space for: | |||
| 26 | + | // - 160 bytes standard frame (with register save area) | |||
| 27 | + | // - Stack args a6-a15 (10 * 8 = 80 bytes) | |||
| 28 | + | // - Saved args pointer (8 bytes) | |||
| 29 | + | // - Padding for alignment | |||
| 30 | + | // Total: 264 bytes (rounded to 8-byte alignment) | |||
| 31 | + | ||||
| 32 | + | #define STACK_SIZE 264 | |||
| 33 | + | #define STACK_ARGS 160 | |||
| 34 | + | #define ARGP_SAVE 248 | |||
| 35 | + | ||||
| 36 | + | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |||
| 37 | + | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |||
| 38 | + | ||||
| 39 | + | TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 40 | + | // On entry, R2 contains the args pointer | |||
| 41 | + | // Save callee-saved registers in caller's frame (per ABI) | |||
| 42 | + | STMG R6, R15, 48(R15) | |||
| 43 | + | ||||
| 44 | + | // Allocate our stack frame | |||
| 45 | + | MOVD R15, R1 | |||
| 46 | + | SUB $STACK_SIZE, R15 | |||
| 47 | + | MOVD R1, 0(R15) // back chain | |||
| 48 | + | ||||
| 49 | + | // Save args pointer | |||
| 50 | + | MOVD R2, ARGP_SAVE(R15) | |||
| 51 | + | ||||
| 52 | + | // R9 := args pointer (syscall15Args*) | |||
| 53 | + | MOVD R2, R9 | |||
| 54 | + | ||||
| 55 | + | // Load float args into F0, F2, F4, F6 (s390x uses even-numbered FPRs) | |||
| 56 | + | FMOVD syscall15Args_f1(R9), F0 | |||
| 57 | + | FMOVD syscall15Args_f2(R9), F2 | |||
| 58 | + | FMOVD syscall15Args_f3(R9), F4 | |||
| 59 | + | FMOVD syscall15Args_f4(R9), F6 | |||
| 60 | + | ||||
| 61 | + | // Load integer args into R2-R6 (5 registers) | |||
| 62 | + | MOVD syscall15Args_a1(R9), R2 | |||
| 63 | + | MOVD syscall15Args_a2(R9), R3 | |||
| 64 | + | MOVD syscall15Args_a3(R9), R4 | |||
| 65 | + | MOVD syscall15Args_a4(R9), R5 | |||
| 66 | + | MOVD syscall15Args_a5(R9), R6 | |||
| 67 | + | ||||
| 68 | + | // Spill remaining args (a6-a15) onto the stack at 160(R15) | |||
| 69 | + | MOVD ARGP_SAVE(R15), R9 // reload args pointer | |||
| 70 | + | MOVD syscall15Args_a6(R9), R1 | |||
| 71 | + | MOVD R1, (STACK_ARGS+0*8)(R15) | |||
| 72 | + | MOVD syscall15Args_a7(R9), R1 | |||
| 73 | + | MOVD R1, (STACK_ARGS+1*8)(R15) | |||
| 74 | + | MOVD syscall15Args_a8(R9), R1 | |||
| 75 | + | MOVD R1, (STACK_ARGS+2*8)(R15) | |||
| 76 | + | MOVD syscall15Args_a9(R9), R1 | |||
| 77 | + | MOVD R1, (STACK_ARGS+3*8)(R15) | |||
| 78 | + | MOVD syscall15Args_a10(R9), R1 | |||
| 79 | + | MOVD R1, (STACK_ARGS+4*8)(R15) | |||
| 80 | + | MOVD syscall15Args_a11(R9), R1 | |||
| 81 | + | MOVD R1, (STACK_ARGS+5*8)(R15) | |||
| 82 | + | MOVD syscall15Args_a12(R9), R1 | |||
| 83 | + | MOVD R1, (STACK_ARGS+6*8)(R15) | |||
| 84 | + | MOVD syscall15Args_a13(R9), R1 | |||
| 85 | + | MOVD R1, (STACK_ARGS+7*8)(R15) | |||
| 86 | + | MOVD syscall15Args_a14(R9), R1 | |||
| 87 | + | MOVD R1, (STACK_ARGS+8*8)(R15) | |||
| 88 | + | MOVD syscall15Args_a15(R9), R1 | |||
| 89 | + | MOVD R1, (STACK_ARGS+9*8)(R15) | |||
| 90 | + | ||||
| 91 | + | // Call function | |||
| 92 | + | MOVD syscall15Args_fn(R9), R1 | |||
| 93 | + | BL (R1) | |||
| 94 | + | ||||
| 95 | + | // Restore args pointer for storing results | |||
| 96 | + | MOVD ARGP_SAVE(R15), R9 | |||
| 97 | + | ||||
| 98 | + | // Store integer results back (R2, R3) | |||
| 99 | + | MOVD R2, syscall15Args_a1(R9) | |||
| 100 | + | MOVD R3, syscall15Args_a2(R9) | |||
| 101 | + | ||||
| 102 | + | // Store float return values (F0, F2, F4, F6) | |||
| 103 | + | FMOVD F0, syscall15Args_f1(R9) | |||
| 104 | + | FMOVD F2, syscall15Args_f2(R9) | |||
| 105 | + | FMOVD F4, syscall15Args_f3(R9) | |||
| 106 | + | FMOVD F6, syscall15Args_f4(R9) | |||
| 107 | + | ||||
| 108 | + | // Deallocate stack frame | |||
| 109 | + | ADD $STACK_SIZE, R15 | |||
| 110 | + | ||||
| 111 | + | // Restore callee-saved registers from caller's save area | |||
| 112 | + | LMG 48(R15), R6, R15 | |||
| 113 | + | ||||
| 114 | + | RET | |||
A sys_unix_s390x.s +109 -0
| @@ -0,0 +1,109 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build linux | |
| 5 | + | ||
| 6 | + | #include "textflag.h" | |
| 7 | + | #include "go_asm.h" | |
| 8 | + | #include "funcdata.h" | |
| 9 | + | ||
| 10 | + | // S390X ELF ABI callbackasm1 implementation | |
| 11 | + | // On entry, R0 contains the callback index (set by callbackasm) | |
| 12 | + | // NOTE: We use R0 instead of R11 because R11 is callee-saved on S390X. | |
| 13 | + | // | |
| 14 | + | // S390X stack frame layout: | |
| 15 | + | // 0(R15) - back chain | |
| 16 | + | // 48(R15) - register save area (R6-R15) | |
| 17 | + | // 160(R15) - parameter area | |
| 18 | + | // | |
| 19 | + | // S390X uses R2-R6 for integer arguments (5 registers) and F0,F2,F4,F6 for floats (4 registers). | |
| 20 | + | // | |
| 21 | + | // Our frame layout (total 264 bytes, 8-byte aligned): | |
| 22 | + | // 0(R15) - back chain | |
| 23 | + | // 48(R15) - saved R6-R15 (done by STMG) | |
| 24 | + | // 160(R15) - callbackArgs struct (32 bytes: index, args, result, stackArgs) | |
| 25 | + | // 192(R15) - args array start | |
| 26 | + | // | |
| 27 | + | // Args array layout: | |
| 28 | + | // - floats F0,F2,F4,F6 (32 bytes) | |
| 29 | + | // - ints R2-R6 (40 bytes) | |
| 30 | + | // Total args array: 72 bytes, ends at 264 | |
| 31 | + | // | |
| 32 | + | // Stack args in caller's frame start at old_R15+160 | |
| 33 | + | ||
| 34 | + | #define FRAME_SIZE 264 | |
| 35 | + | #define CB_ARGS 160 | |
| 36 | + | #define ARGS_ARRAY 192 | |
| 37 | + | #define FLOAT_OFF 0 | |
| 38 | + | #define INT_OFF 32 | |
| 39 | + | ||
| 40 | + | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |
| 41 | + | NO_LOCAL_POINTERS | |
| 42 | + | ||
| 43 | + | // On entry, the trampoline in zcallback_s390x.s left | |
| 44 | + | // the callback index in R0 (NOT R11, since R11 is callee-saved). | |
| 45 | + | // R6 contains the 5th integer argument. | |
| 46 | + | ||
| 47 | + | // Save R6-R15 in caller's frame (per S390X ABI) BEFORE allocating our frame | |
| 48 | + | // STMG stores R6's current value (the 5th arg) at 48(R15) | |
| 49 | + | STMG R6, R15, 48(R15) | |
| 50 | + | ||
| 51 | + | // Save current stack pointer (will be back chain) | |
| 52 | + | MOVD R15, R1 | |
| 53 | + | ||
| 54 | + | // Allocate our stack frame | |
| 55 | + | SUB $FRAME_SIZE, R15 | |
| 56 | + | MOVD R1, 0(R15) // back chain | |
| 57 | + | ||
| 58 | + | // Save R0 (callback index) immediately - it's volatile | |
| 59 | + | MOVD R0, (CB_ARGS+0)(R15) | |
| 60 | + | ||
| 61 | + | // Save callback arguments to args array. | |
| 62 | + | // Layout: floats first (F0,F2,F4,F6), then ints (R2-R6) | |
| 63 | + | FMOVD F0, (ARGS_ARRAY+FLOAT_OFF+0*8)(R15) | |
| 64 | + | FMOVD F2, (ARGS_ARRAY+FLOAT_OFF+1*8)(R15) | |
| 65 | + | FMOVD F4, (ARGS_ARRAY+FLOAT_OFF+2*8)(R15) | |
| 66 | + | FMOVD F6, (ARGS_ARRAY+FLOAT_OFF+3*8)(R15) | |
| 67 | + | ||
| 68 | + | MOVD R2, (ARGS_ARRAY+INT_OFF+0*8)(R15) | |
| 69 | + | MOVD R3, (ARGS_ARRAY+INT_OFF+1*8)(R15) | |
| 70 | + | MOVD R4, (ARGS_ARRAY+INT_OFF+2*8)(R15) | |
| 71 | + | MOVD R5, (ARGS_ARRAY+INT_OFF+3*8)(R15) | |
| 72 | + | ||
| 73 | + | // R6 (5th int arg) was saved at 48(old_R15) by STMG | |
| 74 | + | // old_R15 = current R15 + FRAME_SIZE, so R6 is at 48+FRAME_SIZE(R15) = 312(R15) | |
| 75 | + | MOVD (48+FRAME_SIZE)(R15), R1 | |
| 76 | + | MOVD R1, (ARGS_ARRAY+INT_OFF+4*8)(R15) | |
| 77 | + | ||
| 78 | + | // Finish setting up callbackArgs struct at CB_ARGS(R15) | |
| 79 | + | // struct { index uintptr; args unsafe.Pointer; result uintptr; stackArgs unsafe.Pointer } | |
| 80 | + | // Note: index was already saved earlier | |
| 81 | + | ADD $ARGS_ARRAY, R15, R1 | |
| 82 | + | MOVD R1, (CB_ARGS+8)(R15) // args = address of register args | |
| 83 | + | MOVD $0, (CB_ARGS+16)(R15) // result = 0 | |
| 84 | + | ||
| 85 | + | // stackArgs points to caller's stack arguments at old_R15+160 = R15+FRAME_SIZE+160 | |
| 86 | + | ADD $(FRAME_SIZE+160), R15, R1 | |
| 87 | + | MOVD R1, (CB_ARGS+24)(R15) // stackArgs = &caller_stack_args | |
| 88 | + | ||
| 89 | + | // Call crosscall2 with arguments in registers: | |
| 90 | + | // R2 = fn (from callbackWrap_call closure) | |
| 91 | + | // R3 = frame (address of callbackArgs) | |
| 92 | + | // R5 = ctxt (0) | |
| 93 | + | MOVD ·callbackWrap_call(SB), R2 | |
| 94 | + | MOVD (R2), R2 // dereference closure to get fn | |
| 95 | + | ADD $CB_ARGS, R15, R3 // frame = &callbackArgs | |
| 96 | + | MOVD $0, R5 // ctxt = 0 | |
| 97 | + | ||
| 98 | + | BL crosscall2(SB) | |
| 99 | + | ||
| 100 | + | // Get callback result into R2 | |
| 101 | + | MOVD (CB_ARGS+16)(R15), R2 | |
| 102 | + | ||
| 103 | + | // Deallocate frame | |
| 104 | + | ADD $FRAME_SIZE, R15 | |
| 105 | + | ||
| 106 | + | // Restore R6-R15 from caller's frame | |
| 107 | + | LMG 48(R15), R6, R15 | |
| 108 | + | ||
| 109 | + | RET | |
| @@ -0,0 +1,109 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build linux | |||
| 5 | + | ||||
| 6 | + | #include "textflag.h" | |||
| 7 | + | #include "go_asm.h" | |||
| 8 | + | #include "funcdata.h" | |||
| 9 | + | ||||
| 10 | + | // S390X ELF ABI callbackasm1 implementation | |||
| 11 | + | // On entry, R0 contains the callback index (set by callbackasm) | |||
| 12 | + | // NOTE: We use R0 instead of R11 because R11 is callee-saved on S390X. | |||
| 13 | + | // | |||
| 14 | + | // S390X stack frame layout: | |||
| 15 | + | // 0(R15) - back chain | |||
| 16 | + | // 48(R15) - register save area (R6-R15) | |||
| 17 | + | // 160(R15) - parameter area | |||
| 18 | + | // | |||
| 19 | + | // S390X uses R2-R6 for integer arguments (5 registers) and F0,F2,F4,F6 for floats (4 registers). | |||
| 20 | + | // | |||
| 21 | + | // Our frame layout (total 264 bytes, 8-byte aligned): | |||
| 22 | + | // 0(R15) - back chain | |||
| 23 | + | // 48(R15) - saved R6-R15 (done by STMG) | |||
| 24 | + | // 160(R15) - callbackArgs struct (32 bytes: index, args, result, stackArgs) | |||
| 25 | + | // 192(R15) - args array start | |||
| 26 | + | // | |||
| 27 | + | // Args array layout: | |||
| 28 | + | // - floats F0,F2,F4,F6 (32 bytes) | |||
| 29 | + | // - ints R2-R6 (40 bytes) | |||
| 30 | + | // Total args array: 72 bytes, ends at 264 | |||
| 31 | + | // | |||
| 32 | + | // Stack args in caller's frame start at old_R15+160 | |||
| 33 | + | ||||
| 34 | + | #define FRAME_SIZE 264 | |||
| 35 | + | #define CB_ARGS 160 | |||
| 36 | + | #define ARGS_ARRAY 192 | |||
| 37 | + | #define FLOAT_OFF 0 | |||
| 38 | + | #define INT_OFF 32 | |||
| 39 | + | ||||
| 40 | + | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 41 | + | NO_LOCAL_POINTERS | |||
| 42 | + | ||||
| 43 | + | // On entry, the trampoline in zcallback_s390x.s left | |||
| 44 | + | // the callback index in R0 (NOT R11, since R11 is callee-saved). | |||
| 45 | + | // R6 contains the 5th integer argument. | |||
| 46 | + | ||||
| 47 | + | // Save R6-R15 in caller's frame (per S390X ABI) BEFORE allocating our frame | |||
| 48 | + | // STMG stores R6's current value (the 5th arg) at 48(R15) | |||
| 49 | + | STMG R6, R15, 48(R15) | |||
| 50 | + | ||||
| 51 | + | // Save current stack pointer (will be back chain) | |||
| 52 | + | MOVD R15, R1 | |||
| 53 | + | ||||
| 54 | + | // Allocate our stack frame | |||
| 55 | + | SUB $FRAME_SIZE, R15 | |||
| 56 | + | MOVD R1, 0(R15) // back chain | |||
| 57 | + | ||||
| 58 | + | // Save R0 (callback index) immediately - it's volatile | |||
| 59 | + | MOVD R0, (CB_ARGS+0)(R15) | |||
| 60 | + | ||||
| 61 | + | // Save callback arguments to args array. | |||
| 62 | + | // Layout: floats first (F0,F2,F4,F6), then ints (R2-R6) | |||
| 63 | + | FMOVD F0, (ARGS_ARRAY+FLOAT_OFF+0*8)(R15) | |||
| 64 | + | FMOVD F2, (ARGS_ARRAY+FLOAT_OFF+1*8)(R15) | |||
| 65 | + | FMOVD F4, (ARGS_ARRAY+FLOAT_OFF+2*8)(R15) | |||
| 66 | + | FMOVD F6, (ARGS_ARRAY+FLOAT_OFF+3*8)(R15) | |||
| 67 | + | ||||
| 68 | + | MOVD R2, (ARGS_ARRAY+INT_OFF+0*8)(R15) | |||
| 69 | + | MOVD R3, (ARGS_ARRAY+INT_OFF+1*8)(R15) | |||
| 70 | + | MOVD R4, (ARGS_ARRAY+INT_OFF+2*8)(R15) | |||
| 71 | + | MOVD R5, (ARGS_ARRAY+INT_OFF+3*8)(R15) | |||
| 72 | + | ||||
| 73 | + | // R6 (5th int arg) was saved at 48(old_R15) by STMG | |||
| 74 | + | // old_R15 = current R15 + FRAME_SIZE, so R6 is at 48+FRAME_SIZE(R15) = 312(R15) | |||
| 75 | + | MOVD (48+FRAME_SIZE)(R15), R1 | |||
| 76 | + | MOVD R1, (ARGS_ARRAY+INT_OFF+4*8)(R15) | |||
| 77 | + | ||||
| 78 | + | // Finish setting up callbackArgs struct at CB_ARGS(R15) | |||
| 79 | + | // struct { index uintptr; args unsafe.Pointer; result uintptr; stackArgs unsafe.Pointer } | |||
| 80 | + | // Note: index was already saved earlier | |||
| 81 | + | ADD $ARGS_ARRAY, R15, R1 | |||
| 82 | + | MOVD R1, (CB_ARGS+8)(R15) // args = address of register args | |||
| 83 | + | MOVD $0, (CB_ARGS+16)(R15) // result = 0 | |||
| 84 | + | ||||
| 85 | + | // stackArgs points to caller's stack arguments at old_R15+160 = R15+FRAME_SIZE+160 | |||
| 86 | + | ADD $(FRAME_SIZE+160), R15, R1 | |||
| 87 | + | MOVD R1, (CB_ARGS+24)(R15) // stackArgs = &caller_stack_args | |||
| 88 | + | ||||
| 89 | + | // Call crosscall2 with arguments in registers: | |||
| 90 | + | // R2 = fn (from callbackWrap_call closure) | |||
| 91 | + | // R3 = frame (address of callbackArgs) | |||
| 92 | + | // R5 = ctxt (0) | |||
| 93 | + | MOVD ·callbackWrap_call(SB), R2 | |||
| 94 | + | MOVD (R2), R2 // dereference closure to get fn | |||
| 95 | + | ADD $CB_ARGS, R15, R3 // frame = &callbackArgs | |||
| 96 | + | MOVD $0, R5 // ctxt = 0 | |||
| 97 | + | ||||
| 98 | + | BL crosscall2(SB) | |||
| 99 | + | ||||
| 100 | + | // Get callback result into R2 | |||
| 101 | + | MOVD (CB_ARGS+16)(R15), R2 | |||
| 102 | + | ||||
| 103 | + | // Deallocate frame | |||
| 104 | + | ADD $FRAME_SIZE, R15 | |||
| 105 | + | ||||
| 106 | + | // Restore R6-R15 from caller's frame | |||
| 107 | + | LMG 48(R15), R6, R15 | |||
| 108 | + | ||||
| 109 | + | RET | |||
M syscall_cgo_linux.go +2 -2
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build cgo && !(386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64) | |
| 4 | + | //go:build cgo && !(386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x) | |
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| @@ -17,5 +17,5 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a | |||
| 17 | 17 | } | |
| 18 | 18 | ||
| 19 | 19 | func NewCallback(_ any) uintptr { | |
| 20 | - | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64") | |
| 20 | + | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64/s390x") | |
| 21 | 21 | } | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build cgo && !(386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64) | 4 | + | //go:build cgo && !(386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x) |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| @@ -17,5 +17,5 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a | |||||
| 17 | } | 17 | } | ||
| 18 | 18 | ||||
| 19 | func NewCallback(_ any) uintptr { | 19 | func NewCallback(_ any) uintptr { | ||
| 20 | - | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64") | 20 | + | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64/s390x") |
| 21 | } | 21 | } | ||
M syscall_sysv.go +52 -7
| @@ -1,7 +1,8 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64)) || netbsd | |
| 4 | + | // TODO: remove s390x cgo dependency once golang/go#77449 is resolved | |
| 5 | + | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (cgo && s390x))) || netbsd | |
| 5 | 6 | ||
| 6 | 7 | package purego | |
| 7 | 8 | ||
| @@ -164,15 +165,25 @@ func callbackWrap(a *callbackArgs) { | |||
| 164 | 165 | // Darwin ARM64: read from packed stack with proper alignment | |
| 165 | 166 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | |
| 166 | 167 | } else if stackFrame != nil { | |
| 167 | - | // ppc64le: stack args are in separate stackFrame | |
| 168 | - | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |
| 168 | + | // ppc64le/s390x: stack args are in separate stackFrame | |
| 169 | + | if runtime.GOARCH == "s390x" { | |
| 170 | + | // s390x big-endian: sub-8-byte values are right-justified | |
| 171 | + | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType) | |
| 172 | + | } else { | |
| 173 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |
| 174 | + | } | |
| 169 | 175 | stackSlot += slots | |
| 170 | 176 | } else { | |
| 171 | 177 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |
| 172 | 178 | stackSlot += slots | |
| 173 | 179 | } | |
| 174 | 180 | } else { | |
| 175 | - | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | |
| 181 | + | if runtime.GOARCH == "s390x" { | |
| 182 | + | // s390x big-endian: float32 is right-justified in 8-byte FPR slot | |
| 183 | + | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType) | |
| 184 | + | } else { | |
| 185 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | |
| 186 | + | } | |
| 176 | 187 | } | |
| 177 | 188 | floatsN += slots | |
| 178 | 189 | case reflect.Struct: | |
| @@ -185,8 +196,13 @@ func callbackWrap(a *callbackArgs) { | |||
| 185 | 196 | // Darwin ARM64: read from packed stack with proper alignment | |
| 186 | 197 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | |
| 187 | 198 | } else if stackFrame != nil { | |
| 188 | - | // ppc64le: stack args are in separate stackFrame | |
| 189 | - | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |
| 199 | + | // ppc64le/s390x: stack args are in separate stackFrame | |
| 200 | + | if runtime.GOARCH == "s390x" { | |
| 201 | + | // s390x big-endian: sub-8-byte values are right-justified | |
| 202 | + | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType) | |
| 203 | + | } else { | |
| 204 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |
| 205 | + | } | |
| 190 | 206 | stackSlot += slots | |
| 191 | 207 | } else { | |
| 192 | 208 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |
| @@ -195,7 +211,12 @@ func callbackWrap(a *callbackArgs) { | |||
| 195 | 211 | } else { | |
| 196 | 212 | // the integers begin after the floats in frame | |
| 197 | 213 | pos := intsN + numOfFloatRegisters() | |
| 198 | - | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() | |
| 214 | + | if runtime.GOARCH == "s390x" { | |
| 215 | + | // s390x big-endian: sub-8-byte values are right-justified in GPR slot | |
| 216 | + | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[pos]), inType) | |
| 217 | + | } else { | |
| 218 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() | |
| 219 | + | } | |
| 199 | 220 | } | |
| 200 | 221 | intsN += slots | |
| 201 | 222 | } | |
| @@ -246,6 +267,27 @@ func callbackArgFromStack(argsBase unsafe.Pointer, stackSlot int, stackByteOffse | |||
| 246 | 267 | return reflect.NewAt(inType, ptr).Elem() | |
| 247 | 268 | } | |
| 248 | 269 | ||
| 270 | + | // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures. | |
| 271 | + | // On s390x: | |
| 272 | + | // - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size) | |
| 273 | + | // - Float32 in FPRs is left-justified: stored in upper 32 bits, so at offset 0 | |
| 274 | + | // - Float64 occupies the full 8-byte slot | |
| 275 | + | func callbackArgFromSlotBigEndian(slotPtr unsafe.Pointer, inType reflect.Type) reflect.Value { | |
| 276 | + | size := inType.Size() | |
| 277 | + | if size >= 8 { | |
| 278 | + | // 8-byte values occupy the entire slot | |
| 279 | + | return reflect.NewAt(inType, slotPtr).Elem() | |
| 280 | + | } | |
| 281 | + | // Float32 is left-justified in FPRs (upper 32 bits), so offset is 0 | |
| 282 | + | if inType.Kind() == reflect.Float32 { | |
| 283 | + | return reflect.NewAt(inType, slotPtr).Elem() | |
| 284 | + | } | |
| 285 | + | // Integer types are right-justified: offset = 8 - size | |
| 286 | + | offset := 8 - size | |
| 287 | + | ptr := unsafe.Add(slotPtr, offset) | |
| 288 | + | return reflect.NewAt(inType, ptr).Elem() | |
| 289 | + | } | |
| 290 | + | ||
| 249 | 291 | // callbackasmAddr returns address of runtime.callbackasm | |
| 250 | 292 | // function adjusted by i. | |
| 251 | 293 | // On x86 and amd64, runtime.callbackasm is a series of CALL instructions, | |
| @@ -270,6 +312,9 @@ func callbackasmAddr(i int) uintptr { | |||
| 270 | 312 | // On ARM, ARM64, Loong64, PPC64LE and RISCV64, each entry is a MOV instruction | |
| 271 | 313 | // followed by a branch instruction | |
| 272 | 314 | entrySize = 8 | |
| 315 | + | case "s390x": | |
| 316 | + | // On S390X, each entry is LGHI (4 bytes) + JG (6 bytes) | |
| 317 | + | entrySize = 10 | |
| 273 | 318 | } | |
| 274 | 319 | return callbackasmABI0 + uintptr(i*entrySize) | |
| 275 | 320 | } | |
| @@ -1,7 +1,8 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64)) || netbsd | 4 | + | // TODO: remove s390x cgo dependency once golang/go#77449 is resolved |
| 5 | + | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || (cgo && s390x))) || netbsd | |||
| 5 | 6 | ||||
| 6 | package purego | 7 | package purego | ||
| 7 | 8 | ||||
| @@ -164,15 +165,25 @@ func callbackWrap(a *callbackArgs) { | |||||
| 164 | // Darwin ARM64: read from packed stack with proper alignment | 165 | // Darwin ARM64: read from packed stack with proper alignment | ||
| 165 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | 166 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | ||
| 166 | } else if stackFrame != nil { | 167 | } else if stackFrame != nil { | ||
| 167 | - | // ppc64le: stack args are in separate stackFrame | 168 | + | // ppc64le/s390x: stack args are in separate stackFrame |
| 168 | - | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | 169 | + | if runtime.GOARCH == "s390x" { |
| 170 | + | // s390x big-endian: sub-8-byte values are right-justified | |||
| 171 | + | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType) | |||
| 172 | + | } else { | |||
| 173 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |||
| 174 | + | } | |||
| 169 | stackSlot += slots | 175 | stackSlot += slots | ||
| 170 | } else { | 176 | } else { | ||
| 171 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | 177 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | ||
| 172 | stackSlot += slots | 178 | stackSlot += slots | ||
| 173 | } | 179 | } | ||
| 174 | } else { | 180 | } else { | ||
| 175 | - | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | 181 | + | if runtime.GOARCH == "s390x" { |
| 182 | + | // s390x big-endian: float32 is right-justified in 8-byte FPR slot | |||
| 183 | + | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[floatsN]), inType) | |||
| 184 | + | } else { | |||
| 185 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | |||
| 186 | + | } | |||
| 176 | } | 187 | } | ||
| 177 | floatsN += slots | 188 | floatsN += slots | ||
| 178 | case reflect.Struct: | 189 | case reflect.Struct: | ||
| @@ -185,8 +196,13 @@ func callbackWrap(a *callbackArgs) { | |||||
| 185 | // Darwin ARM64: read from packed stack with proper alignment | 196 | // Darwin ARM64: read from packed stack with proper alignment | ||
| 186 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | 197 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | ||
| 187 | } else if stackFrame != nil { | 198 | } else if stackFrame != nil { | ||
| 188 | - | // ppc64le: stack args are in separate stackFrame | 199 | + | // ppc64le/s390x: stack args are in separate stackFrame |
| 189 | - | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | 200 | + | if runtime.GOARCH == "s390x" { |
| 201 | + | // s390x big-endian: sub-8-byte values are right-justified | |||
| 202 | + | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&stackFrame[stackSlot]), inType) | |||
| 203 | + | } else { | |||
| 204 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |||
| 205 | + | } | |||
| 190 | stackSlot += slots | 206 | stackSlot += slots | ||
| 191 | } else { | 207 | } else { | ||
| 192 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | 208 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | ||
| @@ -195,7 +211,12 @@ func callbackWrap(a *callbackArgs) { | |||||
| 195 | } else { | 211 | } else { | ||
| 196 | // the integers begin after the floats in frame | 212 | // the integers begin after the floats in frame | ||
| 197 | pos := intsN + numOfFloatRegisters() | 213 | pos := intsN + numOfFloatRegisters() | ||
| 198 | - | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() | 214 | + | if runtime.GOARCH == "s390x" { |
| 215 | + | // s390x big-endian: sub-8-byte values are right-justified in GPR slot | |||
| 216 | + | args[i] = callbackArgFromSlotBigEndian(unsafe.Pointer(&frame[pos]), inType) | |||
| 217 | + | } else { | |||
| 218 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() | |||
| 219 | + | } | |||
| 199 | } | 220 | } | ||
| 200 | intsN += slots | 221 | intsN += slots | ||
| 201 | } | 222 | } | ||
| @@ -246,6 +267,27 @@ func callbackArgFromStack(argsBase unsafe.Pointer, stackSlot int, stackByteOffse | |||||
| 246 | return reflect.NewAt(inType, ptr).Elem() | 267 | return reflect.NewAt(inType, ptr).Elem() | ||
| 247 | } | 268 | } | ||
| 248 | 269 | ||||
| 270 | + | // callbackArgFromSlotBigEndian reads an argument from an 8-byte slot on big-endian architectures. | |||
| 271 | + | // On s390x: | |||
| 272 | + | // - Integer types are right-justified in GPRs: sub-8-byte values are at offset (8 - size) | |||
| 273 | + | // - Float32 in FPRs is left-justified: stored in upper 32 bits, so at offset 0 | |||
| 274 | + | // - Float64 occupies the full 8-byte slot | |||
| 275 | + | func callbackArgFromSlotBigEndian(slotPtr unsafe.Pointer, inType reflect.Type) reflect.Value { | |||
| 276 | + | size := inType.Size() | |||
| 277 | + | if size >= 8 { | |||
| 278 | + | // 8-byte values occupy the entire slot | |||
| 279 | + | return reflect.NewAt(inType, slotPtr).Elem() | |||
| 280 | + | } | |||
| 281 | + | // Float32 is left-justified in FPRs (upper 32 bits), so offset is 0 | |||
| 282 | + | if inType.Kind() == reflect.Float32 { | |||
| 283 | + | return reflect.NewAt(inType, slotPtr).Elem() | |||
| 284 | + | } | |||
| 285 | + | // Integer types are right-justified: offset = 8 - size | |||
| 286 | + | offset := 8 - size | |||
| 287 | + | ptr := unsafe.Add(slotPtr, offset) | |||
| 288 | + | return reflect.NewAt(inType, ptr).Elem() | |||
| 289 | + | } | |||
| 290 | + | ||||
| 249 | // callbackasmAddr returns address of runtime.callbackasm | 291 | // callbackasmAddr returns address of runtime.callbackasm | ||
| 250 | // function adjusted by i. | 292 | // function adjusted by i. | ||
| 251 | // On x86 and amd64, runtime.callbackasm is a series of CALL instructions, | 293 | // On x86 and amd64, runtime.callbackasm is a series of CALL instructions, | ||
| @@ -270,6 +312,9 @@ func callbackasmAddr(i int) uintptr { | |||||
| 270 | // On ARM, ARM64, Loong64, PPC64LE and RISCV64, each entry is a MOV instruction | 312 | // On ARM, ARM64, Loong64, PPC64LE and RISCV64, each entry is a MOV instruction | ||
| 271 | // followed by a branch instruction | 313 | // followed by a branch instruction | ||
| 272 | entrySize = 8 | 314 | entrySize = 8 | ||
| 315 | + | case "s390x": | |||
| 316 | + | // On S390X, each entry is LGHI (4 bytes) + JG (6 bytes) | |||
| 317 | + | entrySize = 10 | |||
| 273 | } | 318 | } | ||
| 274 | return callbackasmABI0 + uintptr(i*entrySize) | 319 | return callbackasmABI0 + uintptr(i*entrySize) | ||
| 275 | } | 320 | } | ||
R syscall_sysv_ppc64le.go → syscall_sysv_stackargs.go +1 -1 renamed, 95% similar
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build linux | |
| 4 | + | //go:build linux && (ppc64le || s390x) | |
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build linux | 4 | + | //go:build linux && (ppc64le || s390x) |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
M wincallback.go +30 -0
| @@ -221,6 +221,35 @@ TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |||
| 221 | 221 | } | |
| 222 | 222 | } | |
| 223 | 223 | ||
| 224 | + | func genasmS390x() { | |
| 225 | + | var buf bytes.Buffer | |
| 226 | + | ||
| 227 | + | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. | |
| 228 | + | ||
| 229 | + | //go:build linux | |
| 230 | + | ||
| 231 | + | // External code calls into callbackasm at an offset corresponding | |
| 232 | + | // to the callback index. Callbackasm is a table of MOVD and BR instructions. | |
| 233 | + | // The MOVD instruction loads R0 with the callback index, and the | |
| 234 | + | // BR instruction branches to callbackasm1. | |
| 235 | + | // callbackasm1 takes the callback index from R0 and | |
| 236 | + | // indexes into an array that stores information about each callback. | |
| 237 | + | // It then calls the Go implementation for that callback. | |
| 238 | + | // NOTE: We use R0 instead of R11 because R11 is callee-saved on S390X. | |
| 239 | + | #include "textflag.h" | |
| 240 | + | ||
| 241 | + | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |
| 242 | + | `) | |
| 243 | + | for i := 0; i < maxCallback; i++ { | |
| 244 | + | fmt.Fprintf(&buf, "\tMOVD $%d, R0\n", i) | |
| 245 | + | buf.WriteString("\tBR callbackasm1(SB)\n") | |
| 246 | + | } | |
| 247 | + | if err := os.WriteFile("zcallback_s390x.s", buf.Bytes(), 0644); err != nil { | |
| 248 | + | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) | |
| 249 | + | os.Exit(2) | |
| 250 | + | } | |
| 251 | + | } | |
| 252 | + | ||
| 224 | 253 | func main() { | |
| 225 | 254 | genasm386() | |
| 226 | 255 | genasmAmd64() | |
| @@ -229,4 +258,5 @@ func main() { | |||
| 229 | 258 | genasmLoong64() | |
| 230 | 259 | genasmPpc64le() | |
| 231 | 260 | genasmRiscv64() | |
| 261 | + | genasmS390x() | |
| 232 | 262 | } | |
| @@ -221,6 +221,35 @@ TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |||||
| 221 | } | 221 | } | ||
| 222 | } | 222 | } | ||
| 223 | 223 | ||||
| 224 | + | func genasmS390x() { | |||
| 225 | + | var buf bytes.Buffer | |||
| 226 | + | ||||
| 227 | + | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. | |||
| 228 | + | ||||
| 229 | + | //go:build linux | |||
| 230 | + | ||||
| 231 | + | // External code calls into callbackasm at an offset corresponding | |||
| 232 | + | // to the callback index. Callbackasm is a table of MOVD and BR instructions. | |||
| 233 | + | // The MOVD instruction loads R0 with the callback index, and the | |||
| 234 | + | // BR instruction branches to callbackasm1. | |||
| 235 | + | // callbackasm1 takes the callback index from R0 and | |||
| 236 | + | // indexes into an array that stores information about each callback. | |||
| 237 | + | // It then calls the Go implementation for that callback. | |||
| 238 | + | // NOTE: We use R0 instead of R11 because R11 is callee-saved on S390X. | |||
| 239 | + | #include "textflag.h" | |||
| 240 | + | ||||
| 241 | + | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |||
| 242 | + | `) | |||
| 243 | + | for i := 0; i < maxCallback; i++ { | |||
| 244 | + | fmt.Fprintf(&buf, "\tMOVD $%d, R0\n", i) | |||
| 245 | + | buf.WriteString("\tBR callbackasm1(SB)\n") | |||
| 246 | + | } | |||
| 247 | + | if err := os.WriteFile("zcallback_s390x.s", buf.Bytes(), 0644); err != nil { | |||
| 248 | + | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) | |||
| 249 | + | os.Exit(2) | |||
| 250 | + | } | |||
| 251 | + | } | |||
| 252 | + | ||||
| 224 | func main() { | 253 | func main() { | ||
| 225 | genasm386() | 254 | genasm386() | ||
| 226 | genasmAmd64() | 255 | genasmAmd64() | ||
| @@ -229,4 +258,5 @@ func main() { | |||||
| 229 | genasmLoong64() | 258 | genasmLoong64() | ||
| 230 | genasmPpc64le() | 259 | genasmPpc64le() | ||
| 231 | genasmRiscv64() | 260 | genasmRiscv64() | ||
| 261 | + | genasmS390x() | |||
| 232 | } | 262 | } | ||