purego: add support for ppc64le (#408)
This change adds the neccessary code logic to support PPC64LE with both CGO enabled/disabled. Closes #407
- commit
- 071d22a94b4bc442118b3b0927274dd5ae4d7551
- author
- George Adams · · 2026-02-08 17:39 UTC
- committer
- GitHub · · 2026-02-08 17:39 UTC
- parent
- e8ef616
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
18 files · +5044 -37
M .github/workflows/test.yml +14 -0
| @@ -140,6 +140,20 @@ jobs: | |||
| 140 | 140 | go env -u CC | |
| 141 | 141 | go env -u CXX | |
| 142 | 142 | ||
| 143 | + | - name: go test (Linux ppc64le) | |
| 144 | + | if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') }} | |
| 145 | + | run: | | |
| 146 | + | sudo apt-get update | |
| 147 | + | sudo apt-get install -y gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu qemu-user | |
| 148 | + | go env -w CC=powerpc64le-linux-gnu-gcc | |
| 149 | + | go env -w CXX=powerpc64le-linux-gnu-g++ | |
| 150 | + | env GOOS=linux GOARCH=ppc64le CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |
| 151 | + | env QEMU_LD_PREFIX=/usr/powerpc64le-linux-gnu qemu-ppc64le ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |
| 152 | + | env GOOS=linux GOARCH=ppc64le CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |
| 153 | + | env QEMU_LD_PREFIX=/usr/powerpc64le-linux-gnu qemu-ppc64le ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |
| 154 | + | go env -u CC | |
| 155 | + | go env -u CXX | |
| 156 | + | ||
| 143 | 157 | - name: go test (Linux riscv64) | |
| 144 | 158 | if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') }} | |
| 145 | 159 | run: | | |
| @@ -140,6 +140,20 @@ jobs: | |||||
| 140 | go env -u CC | 140 | go env -u CC | ||
| 141 | go env -u CXX | 141 | go env -u CXX | ||
| 142 | 142 | ||||
| 143 | + | - name: go test (Linux ppc64le) | |||
| 144 | + | if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') }} | |||
| 145 | + | run: | | |||
| 146 | + | sudo apt-get update | |||
| 147 | + | sudo apt-get install -y gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu qemu-user | |||
| 148 | + | go env -w CC=powerpc64le-linux-gnu-gcc | |||
| 149 | + | go env -w CXX=powerpc64le-linux-gnu-g++ | |||
| 150 | + | env GOOS=linux GOARCH=ppc64le CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |||
| 151 | + | env QEMU_LD_PREFIX=/usr/powerpc64le-linux-gnu qemu-ppc64le ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |||
| 152 | + | env GOOS=linux GOARCH=ppc64le CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |||
| 153 | + | env QEMU_LD_PREFIX=/usr/powerpc64le-linux-gnu qemu-ppc64le ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |||
| 154 | + | go env -u CC | |||
| 155 | + | go env -u CXX | |||
| 156 | + | ||||
| 143 | - name: go test (Linux riscv64) | 157 | - name: go test (Linux riscv64) | ||
| 144 | if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') }} | 158 | if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') }} | ||
| 145 | run: | | 159 | run: | | ||
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, riscv64 | |
| 45 | + | - **Linux**: 386, arm, loong64, ppc64le, riscv64 | |
| 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, riscv64 | 45 | + | - **Linux**: 386, arm, loong64, ppc64le, riscv64 |
| 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 +1 -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 || riscv64)) | |
| 4 | + | //go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64)) | |
| 5 | 5 | ||
| 6 | 6 | package purego_test | |
| 7 | 7 | ||
| @@ -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 || riscv64)) | 4 | + | //go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64)) |
| 5 | 5 | ||||
| 6 | package purego_test | 6 | package purego_test | ||
| 7 | 7 | ||||
M func.go +11 -7
| @@ -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 != "riscv64" { | |
| 136 | + | runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | |
| 137 | 137 | panic("purego: float returns are not supported") | |
| 138 | 138 | } | |
| 139 | 139 | { | |
| @@ -277,7 +277,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 277 | 277 | var arm64_r8 uintptr | |
| 278 | 278 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 279 | 279 | outType := ty.Out(0) | |
| 280 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64") && outType.Size() > maxRegAllocStructSize { | |
| 280 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64") && outType.Size() > maxRegAllocStructSize { | |
| 281 | 281 | val := reflect.New(outType) | |
| 282 | 282 | keepAlive = append(keepAlive, val) | |
| 283 | 283 | addInt(val.Pointer()) | |
| @@ -317,7 +317,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 317 | 317 | syscall := thePool.Get().(*syscall15Args) | |
| 318 | 318 | defer thePool.Put(syscall) | |
| 319 | 319 | ||
| 320 | - | if runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64" { | |
| 320 | + | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" { | |
| 321 | 321 | syscall.Set(cfn, sysargs[:], floats[:], 0) | |
| 322 | 322 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |
| 323 | 323 | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | |
| @@ -359,9 +359,13 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 359 | 359 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |
| 360 | 360 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |
| 361 | 361 | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | |
| 362 | - | if runtime.GOARCH == "386" { | |
| 362 | + | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. | |
| 363 | + | switch runtime.GOARCH { | |
| 364 | + | case "386": | |
| 363 | 365 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |
| 364 | - | } else { | |
| 366 | + | case "ppc64le": | |
| 367 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |
| 368 | + | default: | |
| 365 | 369 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | |
| 366 | 370 | } | |
| 367 | 371 | case reflect.Float64: | |
| @@ -486,7 +490,7 @@ func roundUpTo8(val uintptr) uintptr { | |||
| 486 | 490 | ||
| 487 | 491 | func numOfFloatRegisters() int { | |
| 488 | 492 | switch runtime.GOARCH { | |
| 489 | - | case "amd64", "arm64", "loong64", "riscv64": | |
| 493 | + | case "amd64", "arm64", "loong64", "ppc64le", "riscv64": | |
| 490 | 494 | return 8 | |
| 491 | 495 | case "arm": | |
| 492 | 496 | return 16 | |
| @@ -502,7 +506,7 @@ func numOfFloatRegisters() int { | |||
| 502 | 506 | ||
| 503 | 507 | func numOfIntegerRegisters() int { | |
| 504 | 508 | switch runtime.GOARCH { | |
| 505 | - | case "arm64", "loong64", "riscv64": | |
| 509 | + | case "arm64", "loong64", "ppc64le", "riscv64": | |
| 506 | 510 | return 8 | |
| 507 | 511 | case "amd64": | |
| 508 | 512 | return 6 | |
| @@ -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 != "riscv64" { | 136 | + | runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { |
| 137 | panic("purego: float returns are not supported") | 137 | panic("purego: float returns are not supported") | ||
| 138 | } | 138 | } | ||
| 139 | { | 139 | { | ||
| @@ -277,7 +277,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 277 | var arm64_r8 uintptr | 277 | var arm64_r8 uintptr | ||
| 278 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | 278 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | ||
| 279 | outType := ty.Out(0) | 279 | outType := ty.Out(0) | ||
| 280 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64") && outType.Size() > maxRegAllocStructSize { | 280 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64") && outType.Size() > maxRegAllocStructSize { |
| 281 | val := reflect.New(outType) | 281 | val := reflect.New(outType) | ||
| 282 | keepAlive = append(keepAlive, val) | 282 | keepAlive = append(keepAlive, val) | ||
| 283 | addInt(val.Pointer()) | 283 | addInt(val.Pointer()) | ||
| @@ -317,7 +317,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 317 | syscall := thePool.Get().(*syscall15Args) | 317 | syscall := thePool.Get().(*syscall15Args) | ||
| 318 | defer thePool.Put(syscall) | 318 | defer thePool.Put(syscall) | ||
| 319 | 319 | ||||
| 320 | - | if runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64" { | 320 | + | if runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" { |
| 321 | syscall.Set(cfn, sysargs[:], floats[:], 0) | 321 | syscall.Set(cfn, sysargs[:], floats[:], 0) | ||
| 322 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | 322 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | ||
| 323 | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | 323 | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | ||
| @@ -359,9 +359,13 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 359 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | 359 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | ||
| 360 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | 360 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | ||
| 361 | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | 361 | // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert. | ||
| 362 | - | if runtime.GOARCH == "386" { | 362 | + | // On PPC64LE, C ABI converts float32 to double in FPR, so we read as float64. |
| 363 | + | switch runtime.GOARCH { | |||
| 364 | + | case "386": | |||
| 363 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | 365 | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | ||
| 364 | - | } else { | 366 | + | case "ppc64le": |
| 367 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |||
| 368 | + | default: | |||
| 365 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | 369 | v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1)))) | ||
| 366 | } | 370 | } | ||
| 367 | case reflect.Float64: | 371 | case reflect.Float64: | ||
| @@ -486,7 +490,7 @@ func roundUpTo8(val uintptr) uintptr { | |||||
| 486 | 490 | ||||
| 487 | func numOfFloatRegisters() int { | 491 | func numOfFloatRegisters() int { | ||
| 488 | switch runtime.GOARCH { | 492 | switch runtime.GOARCH { | ||
| 489 | - | case "amd64", "arm64", "loong64", "riscv64": | 493 | + | case "amd64", "arm64", "loong64", "ppc64le", "riscv64": |
| 490 | return 8 | 494 | return 8 | ||
| 491 | case "arm": | 495 | case "arm": | ||
| 492 | return 16 | 496 | return 16 | ||
| @@ -502,7 +506,7 @@ func numOfFloatRegisters() int { | |||||
| 502 | 506 | ||||
| 503 | func numOfIntegerRegisters() int { | 507 | func numOfIntegerRegisters() int { | ||
| 504 | switch runtime.GOARCH { | 508 | switch runtime.GOARCH { | ||
| 505 | - | case "arm64", "loong64", "riscv64": | 509 | + | case "arm64", "loong64", "ppc64le", "riscv64": |
| 506 | return 8 | 510 | return 8 | ||
| 507 | case "amd64": | 511 | case "amd64": | ||
| 508 | return 6 | 512 | return 6 | ||
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 != "riscv64" { | |
| 53 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | |
| 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 != "riscv64" { | |
| 82 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | |
| 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 != "riscv64" { | |
| 124 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { | |
| 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 == "riscv64") { | |
| 379 | + | if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64") { | |
| 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 != "riscv64" { | 53 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { |
| 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 != "riscv64" { | 82 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { |
| 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 != "riscv64" { | 124 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "386" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "riscv64" { |
| 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 == "riscv64") { | 379 | + | if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64") { |
| 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 | ||
M internal/cgo/syscall_cgo_unix.go +1 -1
| @@ -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 freebsd || (linux && !(386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | |
| 4 | + | //go:build freebsd || (linux && !(386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64)) || netbsd | |
| 5 | 5 | ||
| 6 | 6 | package cgo | |
| 7 | 7 | ||
| @@ -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 freebsd || (linux && !(386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | 4 | + | //go:build freebsd || (linux && !(386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64)) || netbsd |
| 5 | 5 | ||||
| 6 | package cgo | 6 | package cgo | ||
| 7 | 7 | ||||
A internal/fakecgo/abi_ppc64x.h +195 -0
| @@ -0,0 +1,195 @@ | |||
| 1 | + | // Copyright 2023 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 | + | // Macros for transitioning from the host ABI to Go ABI | |
| 6 | + | // | |
| 7 | + | // On PPC64/ELFv2 targets, the following registers are callee | |
| 8 | + | // saved when called from C. They must be preserved before | |
| 9 | + | // calling into Go which does not preserve any of them. | |
| 10 | + | // | |
| 11 | + | // R14-R31 | |
| 12 | + | // CR2-4 | |
| 13 | + | // VR20-31 | |
| 14 | + | // F14-F31 | |
| 15 | + | // | |
| 16 | + | // xcoff(aix) and ELFv1 are similar, but may only require a | |
| 17 | + | // subset of these. | |
| 18 | + | // | |
| 19 | + | // These macros assume a 16 byte aligned stack pointer. This | |
| 20 | + | // is required by ELFv1, ELFv2, and AIX PPC64. | |
| 21 | + | ||
| 22 | + | #define SAVE_GPR_SIZE (18*8) | |
| 23 | + | #define SAVE_GPR(offset) \ | |
| 24 | + | MOVD R14, (offset+8*0)(R1) \ | |
| 25 | + | MOVD R15, (offset+8*1)(R1) \ | |
| 26 | + | MOVD R16, (offset+8*2)(R1) \ | |
| 27 | + | MOVD R17, (offset+8*3)(R1) \ | |
| 28 | + | MOVD R18, (offset+8*4)(R1) \ | |
| 29 | + | MOVD R19, (offset+8*5)(R1) \ | |
| 30 | + | MOVD R20, (offset+8*6)(R1) \ | |
| 31 | + | MOVD R21, (offset+8*7)(R1) \ | |
| 32 | + | MOVD R22, (offset+8*8)(R1) \ | |
| 33 | + | MOVD R23, (offset+8*9)(R1) \ | |
| 34 | + | MOVD R24, (offset+8*10)(R1) \ | |
| 35 | + | MOVD R25, (offset+8*11)(R1) \ | |
| 36 | + | MOVD R26, (offset+8*12)(R1) \ | |
| 37 | + | MOVD R27, (offset+8*13)(R1) \ | |
| 38 | + | MOVD R28, (offset+8*14)(R1) \ | |
| 39 | + | MOVD R29, (offset+8*15)(R1) \ | |
| 40 | + | MOVD g, (offset+8*16)(R1) \ | |
| 41 | + | MOVD R31, (offset+8*17)(R1) | |
| 42 | + | ||
| 43 | + | #define RESTORE_GPR(offset) \ | |
| 44 | + | MOVD (offset+8*0)(R1), R14 \ | |
| 45 | + | MOVD (offset+8*1)(R1), R15 \ | |
| 46 | + | MOVD (offset+8*2)(R1), R16 \ | |
| 47 | + | MOVD (offset+8*3)(R1), R17 \ | |
| 48 | + | MOVD (offset+8*4)(R1), R18 \ | |
| 49 | + | MOVD (offset+8*5)(R1), R19 \ | |
| 50 | + | MOVD (offset+8*6)(R1), R20 \ | |
| 51 | + | MOVD (offset+8*7)(R1), R21 \ | |
| 52 | + | MOVD (offset+8*8)(R1), R22 \ | |
| 53 | + | MOVD (offset+8*9)(R1), R23 \ | |
| 54 | + | MOVD (offset+8*10)(R1), R24 \ | |
| 55 | + | MOVD (offset+8*11)(R1), R25 \ | |
| 56 | + | MOVD (offset+8*12)(R1), R26 \ | |
| 57 | + | MOVD (offset+8*13)(R1), R27 \ | |
| 58 | + | MOVD (offset+8*14)(R1), R28 \ | |
| 59 | + | MOVD (offset+8*15)(R1), R29 \ | |
| 60 | + | MOVD (offset+8*16)(R1), g \ | |
| 61 | + | MOVD (offset+8*17)(R1), R31 | |
| 62 | + | ||
| 63 | + | #define SAVE_FPR_SIZE (18*8) | |
| 64 | + | #define SAVE_FPR(offset) \ | |
| 65 | + | FMOVD F14, (offset+8*0)(R1) \ | |
| 66 | + | FMOVD F15, (offset+8*1)(R1) \ | |
| 67 | + | FMOVD F16, (offset+8*2)(R1) \ | |
| 68 | + | FMOVD F17, (offset+8*3)(R1) \ | |
| 69 | + | FMOVD F18, (offset+8*4)(R1) \ | |
| 70 | + | FMOVD F19, (offset+8*5)(R1) \ | |
| 71 | + | FMOVD F20, (offset+8*6)(R1) \ | |
| 72 | + | FMOVD F21, (offset+8*7)(R1) \ | |
| 73 | + | FMOVD F22, (offset+8*8)(R1) \ | |
| 74 | + | FMOVD F23, (offset+8*9)(R1) \ | |
| 75 | + | FMOVD F24, (offset+8*10)(R1) \ | |
| 76 | + | FMOVD F25, (offset+8*11)(R1) \ | |
| 77 | + | FMOVD F26, (offset+8*12)(R1) \ | |
| 78 | + | FMOVD F27, (offset+8*13)(R1) \ | |
| 79 | + | FMOVD F28, (offset+8*14)(R1) \ | |
| 80 | + | FMOVD F29, (offset+8*15)(R1) \ | |
| 81 | + | FMOVD F30, (offset+8*16)(R1) \ | |
| 82 | + | FMOVD F31, (offset+8*17)(R1) | |
| 83 | + | ||
| 84 | + | #define RESTORE_FPR(offset) \ | |
| 85 | + | FMOVD (offset+8*0)(R1), F14 \ | |
| 86 | + | FMOVD (offset+8*1)(R1), F15 \ | |
| 87 | + | FMOVD (offset+8*2)(R1), F16 \ | |
| 88 | + | FMOVD (offset+8*3)(R1), F17 \ | |
| 89 | + | FMOVD (offset+8*4)(R1), F18 \ | |
| 90 | + | FMOVD (offset+8*5)(R1), F19 \ | |
| 91 | + | FMOVD (offset+8*6)(R1), F20 \ | |
| 92 | + | FMOVD (offset+8*7)(R1), F21 \ | |
| 93 | + | FMOVD (offset+8*8)(R1), F22 \ | |
| 94 | + | FMOVD (offset+8*9)(R1), F23 \ | |
| 95 | + | FMOVD (offset+8*10)(R1), F24 \ | |
| 96 | + | FMOVD (offset+8*11)(R1), F25 \ | |
| 97 | + | FMOVD (offset+8*12)(R1), F26 \ | |
| 98 | + | FMOVD (offset+8*13)(R1), F27 \ | |
| 99 | + | FMOVD (offset+8*14)(R1), F28 \ | |
| 100 | + | FMOVD (offset+8*15)(R1), F29 \ | |
| 101 | + | FMOVD (offset+8*16)(R1), F30 \ | |
| 102 | + | FMOVD (offset+8*17)(R1), F31 | |
| 103 | + | ||
| 104 | + | // Save and restore VR20-31 (aka VSR56-63). These | |
| 105 | + | // macros must point to a 16B aligned offset. | |
| 106 | + | #define SAVE_VR_SIZE (12*16) | |
| 107 | + | #define SAVE_VR(offset, rtmp) \ | |
| 108 | + | MOVD $(offset+16*0), rtmp \ | |
| 109 | + | STVX V20, (rtmp)(R1) \ | |
| 110 | + | MOVD $(offset+16*1), rtmp \ | |
| 111 | + | STVX V21, (rtmp)(R1) \ | |
| 112 | + | MOVD $(offset+16*2), rtmp \ | |
| 113 | + | STVX V22, (rtmp)(R1) \ | |
| 114 | + | MOVD $(offset+16*3), rtmp \ | |
| 115 | + | STVX V23, (rtmp)(R1) \ | |
| 116 | + | MOVD $(offset+16*4), rtmp \ | |
| 117 | + | STVX V24, (rtmp)(R1) \ | |
| 118 | + | MOVD $(offset+16*5), rtmp \ | |
| 119 | + | STVX V25, (rtmp)(R1) \ | |
| 120 | + | MOVD $(offset+16*6), rtmp \ | |
| 121 | + | STVX V26, (rtmp)(R1) \ | |
| 122 | + | MOVD $(offset+16*7), rtmp \ | |
| 123 | + | STVX V27, (rtmp)(R1) \ | |
| 124 | + | MOVD $(offset+16*8), rtmp \ | |
| 125 | + | STVX V28, (rtmp)(R1) \ | |
| 126 | + | MOVD $(offset+16*9), rtmp \ | |
| 127 | + | STVX V29, (rtmp)(R1) \ | |
| 128 | + | MOVD $(offset+16*10), rtmp \ | |
| 129 | + | STVX V30, (rtmp)(R1) \ | |
| 130 | + | MOVD $(offset+16*11), rtmp \ | |
| 131 | + | STVX V31, (rtmp)(R1) | |
| 132 | + | ||
| 133 | + | #define RESTORE_VR(offset, rtmp) \ | |
| 134 | + | MOVD $(offset+16*0), rtmp \ | |
| 135 | + | LVX (rtmp)(R1), V20 \ | |
| 136 | + | MOVD $(offset+16*1), rtmp \ | |
| 137 | + | LVX (rtmp)(R1), V21 \ | |
| 138 | + | MOVD $(offset+16*2), rtmp \ | |
| 139 | + | LVX (rtmp)(R1), V22 \ | |
| 140 | + | MOVD $(offset+16*3), rtmp \ | |
| 141 | + | LVX (rtmp)(R1), V23 \ | |
| 142 | + | MOVD $(offset+16*4), rtmp \ | |
| 143 | + | LVX (rtmp)(R1), V24 \ | |
| 144 | + | MOVD $(offset+16*5), rtmp \ | |
| 145 | + | LVX (rtmp)(R1), V25 \ | |
| 146 | + | MOVD $(offset+16*6), rtmp \ | |
| 147 | + | LVX (rtmp)(R1), V26 \ | |
| 148 | + | MOVD $(offset+16*7), rtmp \ | |
| 149 | + | LVX (rtmp)(R1), V27 \ | |
| 150 | + | MOVD $(offset+16*8), rtmp \ | |
| 151 | + | LVX (rtmp)(R1), V28 \ | |
| 152 | + | MOVD $(offset+16*9), rtmp \ | |
| 153 | + | LVX (rtmp)(R1), V29 \ | |
| 154 | + | MOVD $(offset+16*10), rtmp \ | |
| 155 | + | LVX (rtmp)(R1), V30 \ | |
| 156 | + | MOVD $(offset+16*11), rtmp \ | |
| 157 | + | LVX (rtmp)(R1), V31 | |
| 158 | + | ||
| 159 | + | // LR and CR are saved in the caller's frame. The callee must | |
| 160 | + | // make space for all other callee-save registers. | |
| 161 | + | #define SAVE_ALL_REG_SIZE (SAVE_GPR_SIZE+SAVE_FPR_SIZE+SAVE_VR_SIZE) | |
| 162 | + | ||
| 163 | + | // Stack a frame and save all callee-save registers following the | |
| 164 | + | // host OS's ABI. Fortunately, this is identical for AIX, ELFv1, and | |
| 165 | + | // ELFv2. All host ABIs require the stack pointer to maintain 16 byte | |
| 166 | + | // alignment, and save the callee-save registers in the same places. | |
| 167 | + | // | |
| 168 | + | // To restate, R1 is assumed to be aligned when this macro is used. | |
| 169 | + | // This assumes the caller's frame is compliant with the host ABI. | |
| 170 | + | // CR and LR are saved into the caller's frame per the host ABI. | |
| 171 | + | // R0 is initialized to $0 as expected by Go. | |
| 172 | + | #define STACK_AND_SAVE_HOST_TO_GO_ABI(extra) \ | |
| 173 | + | MOVD LR, R0 \ | |
| 174 | + | MOVD R0, 16(R1) \ | |
| 175 | + | MOVW CR, R0 \ | |
| 176 | + | MOVD R0, 8(R1) \ | |
| 177 | + | MOVDU R1, -(extra)-FIXED_FRAME-SAVE_ALL_REG_SIZE(R1) \ | |
| 178 | + | SAVE_GPR(extra+FIXED_FRAME) \ | |
| 179 | + | SAVE_FPR(extra+FIXED_FRAME+SAVE_GPR_SIZE) \ | |
| 180 | + | SAVE_VR(extra+FIXED_FRAME+SAVE_GPR_SIZE+SAVE_FPR_SIZE, R0) \ | |
| 181 | + | MOVD $0, R0 | |
| 182 | + | ||
| 183 | + | // This unstacks the frame, restoring all callee-save registers | |
| 184 | + | // as saved by STACK_AND_SAVE_HOST_TO_GO_ABI. | |
| 185 | + | // | |
| 186 | + | // R0 is not guaranteed to contain $0 after this macro. | |
| 187 | + | #define UNSTACK_AND_RESTORE_GO_TO_HOST_ABI(extra) \ | |
| 188 | + | RESTORE_GPR(extra+FIXED_FRAME) \ | |
| 189 | + | RESTORE_FPR(extra+FIXED_FRAME+SAVE_GPR_SIZE) \ | |
| 190 | + | RESTORE_VR(extra+FIXED_FRAME+SAVE_GPR_SIZE+SAVE_FPR_SIZE, R0) \ | |
| 191 | + | ADD $(extra+FIXED_FRAME+SAVE_ALL_REG_SIZE), R1 \ | |
| 192 | + | MOVD 16(R1), R0 \ | |
| 193 | + | MOVD R0, LR \ | |
| 194 | + | MOVD 8(R1), R0 \ | |
| 195 | + | MOVW R0, CR | |
| @@ -0,0 +1,195 @@ | |||||
| 1 | + | // Copyright 2023 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 | + | // Macros for transitioning from the host ABI to Go ABI | |||
| 6 | + | // | |||
| 7 | + | // On PPC64/ELFv2 targets, the following registers are callee | |||
| 8 | + | // saved when called from C. They must be preserved before | |||
| 9 | + | // calling into Go which does not preserve any of them. | |||
| 10 | + | // | |||
| 11 | + | // R14-R31 | |||
| 12 | + | // CR2-4 | |||
| 13 | + | // VR20-31 | |||
| 14 | + | // F14-F31 | |||
| 15 | + | // | |||
| 16 | + | // xcoff(aix) and ELFv1 are similar, but may only require a | |||
| 17 | + | // subset of these. | |||
| 18 | + | // | |||
| 19 | + | // These macros assume a 16 byte aligned stack pointer. This | |||
| 20 | + | // is required by ELFv1, ELFv2, and AIX PPC64. | |||
| 21 | + | ||||
| 22 | + | #define SAVE_GPR_SIZE (18*8) | |||
| 23 | + | #define SAVE_GPR(offset) \ | |||
| 24 | + | MOVD R14, (offset+8*0)(R1) \ | |||
| 25 | + | MOVD R15, (offset+8*1)(R1) \ | |||
| 26 | + | MOVD R16, (offset+8*2)(R1) \ | |||
| 27 | + | MOVD R17, (offset+8*3)(R1) \ | |||
| 28 | + | MOVD R18, (offset+8*4)(R1) \ | |||
| 29 | + | MOVD R19, (offset+8*5)(R1) \ | |||
| 30 | + | MOVD R20, (offset+8*6)(R1) \ | |||
| 31 | + | MOVD R21, (offset+8*7)(R1) \ | |||
| 32 | + | MOVD R22, (offset+8*8)(R1) \ | |||
| 33 | + | MOVD R23, (offset+8*9)(R1) \ | |||
| 34 | + | MOVD R24, (offset+8*10)(R1) \ | |||
| 35 | + | MOVD R25, (offset+8*11)(R1) \ | |||
| 36 | + | MOVD R26, (offset+8*12)(R1) \ | |||
| 37 | + | MOVD R27, (offset+8*13)(R1) \ | |||
| 38 | + | MOVD R28, (offset+8*14)(R1) \ | |||
| 39 | + | MOVD R29, (offset+8*15)(R1) \ | |||
| 40 | + | MOVD g, (offset+8*16)(R1) \ | |||
| 41 | + | MOVD R31, (offset+8*17)(R1) | |||
| 42 | + | ||||
| 43 | + | #define RESTORE_GPR(offset) \ | |||
| 44 | + | MOVD (offset+8*0)(R1), R14 \ | |||
| 45 | + | MOVD (offset+8*1)(R1), R15 \ | |||
| 46 | + | MOVD (offset+8*2)(R1), R16 \ | |||
| 47 | + | MOVD (offset+8*3)(R1), R17 \ | |||
| 48 | + | MOVD (offset+8*4)(R1), R18 \ | |||
| 49 | + | MOVD (offset+8*5)(R1), R19 \ | |||
| 50 | + | MOVD (offset+8*6)(R1), R20 \ | |||
| 51 | + | MOVD (offset+8*7)(R1), R21 \ | |||
| 52 | + | MOVD (offset+8*8)(R1), R22 \ | |||
| 53 | + | MOVD (offset+8*9)(R1), R23 \ | |||
| 54 | + | MOVD (offset+8*10)(R1), R24 \ | |||
| 55 | + | MOVD (offset+8*11)(R1), R25 \ | |||
| 56 | + | MOVD (offset+8*12)(R1), R26 \ | |||
| 57 | + | MOVD (offset+8*13)(R1), R27 \ | |||
| 58 | + | MOVD (offset+8*14)(R1), R28 \ | |||
| 59 | + | MOVD (offset+8*15)(R1), R29 \ | |||
| 60 | + | MOVD (offset+8*16)(R1), g \ | |||
| 61 | + | MOVD (offset+8*17)(R1), R31 | |||
| 62 | + | ||||
| 63 | + | #define SAVE_FPR_SIZE (18*8) | |||
| 64 | + | #define SAVE_FPR(offset) \ | |||
| 65 | + | FMOVD F14, (offset+8*0)(R1) \ | |||
| 66 | + | FMOVD F15, (offset+8*1)(R1) \ | |||
| 67 | + | FMOVD F16, (offset+8*2)(R1) \ | |||
| 68 | + | FMOVD F17, (offset+8*3)(R1) \ | |||
| 69 | + | FMOVD F18, (offset+8*4)(R1) \ | |||
| 70 | + | FMOVD F19, (offset+8*5)(R1) \ | |||
| 71 | + | FMOVD F20, (offset+8*6)(R1) \ | |||
| 72 | + | FMOVD F21, (offset+8*7)(R1) \ | |||
| 73 | + | FMOVD F22, (offset+8*8)(R1) \ | |||
| 74 | + | FMOVD F23, (offset+8*9)(R1) \ | |||
| 75 | + | FMOVD F24, (offset+8*10)(R1) \ | |||
| 76 | + | FMOVD F25, (offset+8*11)(R1) \ | |||
| 77 | + | FMOVD F26, (offset+8*12)(R1) \ | |||
| 78 | + | FMOVD F27, (offset+8*13)(R1) \ | |||
| 79 | + | FMOVD F28, (offset+8*14)(R1) \ | |||
| 80 | + | FMOVD F29, (offset+8*15)(R1) \ | |||
| 81 | + | FMOVD F30, (offset+8*16)(R1) \ | |||
| 82 | + | FMOVD F31, (offset+8*17)(R1) | |||
| 83 | + | ||||
| 84 | + | #define RESTORE_FPR(offset) \ | |||
| 85 | + | FMOVD (offset+8*0)(R1), F14 \ | |||
| 86 | + | FMOVD (offset+8*1)(R1), F15 \ | |||
| 87 | + | FMOVD (offset+8*2)(R1), F16 \ | |||
| 88 | + | FMOVD (offset+8*3)(R1), F17 \ | |||
| 89 | + | FMOVD (offset+8*4)(R1), F18 \ | |||
| 90 | + | FMOVD (offset+8*5)(R1), F19 \ | |||
| 91 | + | FMOVD (offset+8*6)(R1), F20 \ | |||
| 92 | + | FMOVD (offset+8*7)(R1), F21 \ | |||
| 93 | + | FMOVD (offset+8*8)(R1), F22 \ | |||
| 94 | + | FMOVD (offset+8*9)(R1), F23 \ | |||
| 95 | + | FMOVD (offset+8*10)(R1), F24 \ | |||
| 96 | + | FMOVD (offset+8*11)(R1), F25 \ | |||
| 97 | + | FMOVD (offset+8*12)(R1), F26 \ | |||
| 98 | + | FMOVD (offset+8*13)(R1), F27 \ | |||
| 99 | + | FMOVD (offset+8*14)(R1), F28 \ | |||
| 100 | + | FMOVD (offset+8*15)(R1), F29 \ | |||
| 101 | + | FMOVD (offset+8*16)(R1), F30 \ | |||
| 102 | + | FMOVD (offset+8*17)(R1), F31 | |||
| 103 | + | ||||
| 104 | + | // Save and restore VR20-31 (aka VSR56-63). These | |||
| 105 | + | // macros must point to a 16B aligned offset. | |||
| 106 | + | #define SAVE_VR_SIZE (12*16) | |||
| 107 | + | #define SAVE_VR(offset, rtmp) \ | |||
| 108 | + | MOVD $(offset+16*0), rtmp \ | |||
| 109 | + | STVX V20, (rtmp)(R1) \ | |||
| 110 | + | MOVD $(offset+16*1), rtmp \ | |||
| 111 | + | STVX V21, (rtmp)(R1) \ | |||
| 112 | + | MOVD $(offset+16*2), rtmp \ | |||
| 113 | + | STVX V22, (rtmp)(R1) \ | |||
| 114 | + | MOVD $(offset+16*3), rtmp \ | |||
| 115 | + | STVX V23, (rtmp)(R1) \ | |||
| 116 | + | MOVD $(offset+16*4), rtmp \ | |||
| 117 | + | STVX V24, (rtmp)(R1) \ | |||
| 118 | + | MOVD $(offset+16*5), rtmp \ | |||
| 119 | + | STVX V25, (rtmp)(R1) \ | |||
| 120 | + | MOVD $(offset+16*6), rtmp \ | |||
| 121 | + | STVX V26, (rtmp)(R1) \ | |||
| 122 | + | MOVD $(offset+16*7), rtmp \ | |||
| 123 | + | STVX V27, (rtmp)(R1) \ | |||
| 124 | + | MOVD $(offset+16*8), rtmp \ | |||
| 125 | + | STVX V28, (rtmp)(R1) \ | |||
| 126 | + | MOVD $(offset+16*9), rtmp \ | |||
| 127 | + | STVX V29, (rtmp)(R1) \ | |||
| 128 | + | MOVD $(offset+16*10), rtmp \ | |||
| 129 | + | STVX V30, (rtmp)(R1) \ | |||
| 130 | + | MOVD $(offset+16*11), rtmp \ | |||
| 131 | + | STVX V31, (rtmp)(R1) | |||
| 132 | + | ||||
| 133 | + | #define RESTORE_VR(offset, rtmp) \ | |||
| 134 | + | MOVD $(offset+16*0), rtmp \ | |||
| 135 | + | LVX (rtmp)(R1), V20 \ | |||
| 136 | + | MOVD $(offset+16*1), rtmp \ | |||
| 137 | + | LVX (rtmp)(R1), V21 \ | |||
| 138 | + | MOVD $(offset+16*2), rtmp \ | |||
| 139 | + | LVX (rtmp)(R1), V22 \ | |||
| 140 | + | MOVD $(offset+16*3), rtmp \ | |||
| 141 | + | LVX (rtmp)(R1), V23 \ | |||
| 142 | + | MOVD $(offset+16*4), rtmp \ | |||
| 143 | + | LVX (rtmp)(R1), V24 \ | |||
| 144 | + | MOVD $(offset+16*5), rtmp \ | |||
| 145 | + | LVX (rtmp)(R1), V25 \ | |||
| 146 | + | MOVD $(offset+16*6), rtmp \ | |||
| 147 | + | LVX (rtmp)(R1), V26 \ | |||
| 148 | + | MOVD $(offset+16*7), rtmp \ | |||
| 149 | + | LVX (rtmp)(R1), V27 \ | |||
| 150 | + | MOVD $(offset+16*8), rtmp \ | |||
| 151 | + | LVX (rtmp)(R1), V28 \ | |||
| 152 | + | MOVD $(offset+16*9), rtmp \ | |||
| 153 | + | LVX (rtmp)(R1), V29 \ | |||
| 154 | + | MOVD $(offset+16*10), rtmp \ | |||
| 155 | + | LVX (rtmp)(R1), V30 \ | |||
| 156 | + | MOVD $(offset+16*11), rtmp \ | |||
| 157 | + | LVX (rtmp)(R1), V31 | |||
| 158 | + | ||||
| 159 | + | // LR and CR are saved in the caller's frame. The callee must | |||
| 160 | + | // make space for all other callee-save registers. | |||
| 161 | + | #define SAVE_ALL_REG_SIZE (SAVE_GPR_SIZE+SAVE_FPR_SIZE+SAVE_VR_SIZE) | |||
| 162 | + | ||||
| 163 | + | // Stack a frame and save all callee-save registers following the | |||
| 164 | + | // host OS's ABI. Fortunately, this is identical for AIX, ELFv1, and | |||
| 165 | + | // ELFv2. All host ABIs require the stack pointer to maintain 16 byte | |||
| 166 | + | // alignment, and save the callee-save registers in the same places. | |||
| 167 | + | // | |||
| 168 | + | // To restate, R1 is assumed to be aligned when this macro is used. | |||
| 169 | + | // This assumes the caller's frame is compliant with the host ABI. | |||
| 170 | + | // CR and LR are saved into the caller's frame per the host ABI. | |||
| 171 | + | // R0 is initialized to $0 as expected by Go. | |||
| 172 | + | #define STACK_AND_SAVE_HOST_TO_GO_ABI(extra) \ | |||
| 173 | + | MOVD LR, R0 \ | |||
| 174 | + | MOVD R0, 16(R1) \ | |||
| 175 | + | MOVW CR, R0 \ | |||
| 176 | + | MOVD R0, 8(R1) \ | |||
| 177 | + | MOVDU R1, -(extra)-FIXED_FRAME-SAVE_ALL_REG_SIZE(R1) \ | |||
| 178 | + | SAVE_GPR(extra+FIXED_FRAME) \ | |||
| 179 | + | SAVE_FPR(extra+FIXED_FRAME+SAVE_GPR_SIZE) \ | |||
| 180 | + | SAVE_VR(extra+FIXED_FRAME+SAVE_GPR_SIZE+SAVE_FPR_SIZE, R0) \ | |||
| 181 | + | MOVD $0, R0 | |||
| 182 | + | ||||
| 183 | + | // This unstacks the frame, restoring all callee-save registers | |||
| 184 | + | // as saved by STACK_AND_SAVE_HOST_TO_GO_ABI. | |||
| 185 | + | // | |||
| 186 | + | // R0 is not guaranteed to contain $0 after this macro. | |||
| 187 | + | #define UNSTACK_AND_RESTORE_GO_TO_HOST_ABI(extra) \ | |||
| 188 | + | RESTORE_GPR(extra+FIXED_FRAME) \ | |||
| 189 | + | RESTORE_FPR(extra+FIXED_FRAME+SAVE_GPR_SIZE) \ | |||
| 190 | + | RESTORE_VR(extra+FIXED_FRAME+SAVE_GPR_SIZE+SAVE_FPR_SIZE, R0) \ | |||
| 191 | + | ADD $(extra+FIXED_FRAME+SAVE_ALL_REG_SIZE), R1 \ | |||
| 192 | + | MOVD 16(R1), R0 \ | |||
| 193 | + | MOVD R0, LR \ | |||
| 194 | + | MOVD 8(R1), R0 \ | |||
| 195 | + | MOVW R0, CR | |||
A internal/fakecgo/asm_ppc64le.s +82 -0
| @@ -0,0 +1,82 @@ | |||
| 1 | + | // Copyright 2014 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 | + | #include "abi_ppc64x.h" | |
| 7 | + | ||
| 8 | + | // Called by C code generated by cmd/cgo. | |
| 9 | + | // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) | |
| 10 | + | // Saves C callee-saved registers and calls cgocallback with three arguments. | |
| 11 | + | // fn is the PC of a func(a unsafe.Pointer) function. | |
| 12 | + | // | |
| 13 | + | // This is a simplified version that only saves GPR and FPR registers, | |
| 14 | + | // not vector registers. This keeps the stack frame smaller to avoid | |
| 15 | + | // exceeding the nosplit stack limit. | |
| 16 | + | // | |
| 17 | + | // On PPC64LE ELFv2, callee-save registers are: | |
| 18 | + | // R14-R31 (18 GPRs = 144 bytes) | |
| 19 | + | // F14-F31 (18 FPRs = 144 bytes) | |
| 20 | + | // CR2-CR4 (saved in CR field) | |
| 21 | + | // | |
| 22 | + | // Stack layout (must be 16-byte aligned): | |
| 23 | + | // 32 (FIXED_FRAME) + 24 (args) + 144 (GPR) + 144 (FPR) = 344 | |
| 24 | + | // Rounded to 352 for 16-byte alignment. | |
| 25 | + | ||
| 26 | + | #define FIXED_FRAME 32 | |
| 27 | + | #define SAVE_SIZE 352 | |
| 28 | + | #define GPR_OFFSET (FIXED_FRAME+24) | |
| 29 | + | #define FPR_OFFSET (GPR_OFFSET+SAVE_GPR_SIZE) | |
| 30 | + | ||
| 31 | + | TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |
| 32 | + | // Save LR and CR in caller's frame per ELFv2 ABI | |
| 33 | + | MOVD LR, R0 | |
| 34 | + | MOVD R0, 16(R1) | |
| 35 | + | MOVW CR, R0 | |
| 36 | + | MOVD R0, 8(R1) | |
| 37 | + | ||
| 38 | + | // Allocate our stack frame | |
| 39 | + | MOVDU R1, -SAVE_SIZE(R1) | |
| 40 | + | ||
| 41 | + | // Save TOC (R2) in case needed | |
| 42 | + | MOVD R2, 24(R1) | |
| 43 | + | ||
| 44 | + | // Save callee-save GPRs | |
| 45 | + | SAVE_GPR(GPR_OFFSET) | |
| 46 | + | ||
| 47 | + | // Save callee-save FPRs | |
| 48 | + | SAVE_FPR(FPR_OFFSET) | |
| 49 | + | ||
| 50 | + | // Initialize R0 to 0 as expected by Go | |
| 51 | + | MOVD $0, R0 | |
| 52 | + | ||
| 53 | + | // Load the current g. | |
| 54 | + | BL runtime·load_g(SB) | |
| 55 | + | ||
| 56 | + | // Set up arguments for cgocallback | |
| 57 | + | MOVD R3, FIXED_FRAME+0(R1) // fn unsafe.Pointer | |
| 58 | + | MOVD R4, FIXED_FRAME+8(R1) // a unsafe.Pointer | |
| 59 | + | ||
| 60 | + | // Skip R5 = n uint32 | |
| 61 | + | MOVD R6, FIXED_FRAME+16(R1) // ctxt uintptr | |
| 62 | + | BL runtime·cgocallback(SB) | |
| 63 | + | ||
| 64 | + | // Restore callee-save FPRs | |
| 65 | + | RESTORE_FPR(FPR_OFFSET) | |
| 66 | + | ||
| 67 | + | // Restore callee-save GPRs | |
| 68 | + | RESTORE_GPR(GPR_OFFSET) | |
| 69 | + | ||
| 70 | + | // Restore TOC | |
| 71 | + | MOVD 24(R1), R2 | |
| 72 | + | ||
| 73 | + | // Deallocate stack frame | |
| 74 | + | ADD $SAVE_SIZE, R1 | |
| 75 | + | ||
| 76 | + | // Restore LR and CR from caller's frame | |
| 77 | + | MOVD 16(R1), R0 | |
| 78 | + | MOVD R0, LR | |
| 79 | + | MOVD 8(R1), R0 | |
| 80 | + | MOVW R0, CR | |
| 81 | + | ||
| 82 | + | RET | |
| @@ -0,0 +1,82 @@ | |||||
| 1 | + | // Copyright 2014 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 | + | #include "abi_ppc64x.h" | |||
| 7 | + | ||||
| 8 | + | // Called by C code generated by cmd/cgo. | |||
| 9 | + | // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) | |||
| 10 | + | // Saves C callee-saved registers and calls cgocallback with three arguments. | |||
| 11 | + | // fn is the PC of a func(a unsafe.Pointer) function. | |||
| 12 | + | // | |||
| 13 | + | // This is a simplified version that only saves GPR and FPR registers, | |||
| 14 | + | // not vector registers. This keeps the stack frame smaller to avoid | |||
| 15 | + | // exceeding the nosplit stack limit. | |||
| 16 | + | // | |||
| 17 | + | // On PPC64LE ELFv2, callee-save registers are: | |||
| 18 | + | // R14-R31 (18 GPRs = 144 bytes) | |||
| 19 | + | // F14-F31 (18 FPRs = 144 bytes) | |||
| 20 | + | // CR2-CR4 (saved in CR field) | |||
| 21 | + | // | |||
| 22 | + | // Stack layout (must be 16-byte aligned): | |||
| 23 | + | // 32 (FIXED_FRAME) + 24 (args) + 144 (GPR) + 144 (FPR) = 344 | |||
| 24 | + | // Rounded to 352 for 16-byte alignment. | |||
| 25 | + | ||||
| 26 | + | #define FIXED_FRAME 32 | |||
| 27 | + | #define SAVE_SIZE 352 | |||
| 28 | + | #define GPR_OFFSET (FIXED_FRAME+24) | |||
| 29 | + | #define FPR_OFFSET (GPR_OFFSET+SAVE_GPR_SIZE) | |||
| 30 | + | ||||
| 31 | + | TEXT crosscall2(SB), NOSPLIT|NOFRAME, $0 | |||
| 32 | + | // Save LR and CR in caller's frame per ELFv2 ABI | |||
| 33 | + | MOVD LR, R0 | |||
| 34 | + | MOVD R0, 16(R1) | |||
| 35 | + | MOVW CR, R0 | |||
| 36 | + | MOVD R0, 8(R1) | |||
| 37 | + | ||||
| 38 | + | // Allocate our stack frame | |||
| 39 | + | MOVDU R1, -SAVE_SIZE(R1) | |||
| 40 | + | ||||
| 41 | + | // Save TOC (R2) in case needed | |||
| 42 | + | MOVD R2, 24(R1) | |||
| 43 | + | ||||
| 44 | + | // Save callee-save GPRs | |||
| 45 | + | SAVE_GPR(GPR_OFFSET) | |||
| 46 | + | ||||
| 47 | + | // Save callee-save FPRs | |||
| 48 | + | SAVE_FPR(FPR_OFFSET) | |||
| 49 | + | ||||
| 50 | + | // Initialize R0 to 0 as expected by Go | |||
| 51 | + | MOVD $0, R0 | |||
| 52 | + | ||||
| 53 | + | // Load the current g. | |||
| 54 | + | BL runtime·load_g(SB) | |||
| 55 | + | ||||
| 56 | + | // Set up arguments for cgocallback | |||
| 57 | + | MOVD R3, FIXED_FRAME+0(R1) // fn unsafe.Pointer | |||
| 58 | + | MOVD R4, FIXED_FRAME+8(R1) // a unsafe.Pointer | |||
| 59 | + | ||||
| 60 | + | // Skip R5 = n uint32 | |||
| 61 | + | MOVD R6, FIXED_FRAME+16(R1) // ctxt uintptr | |||
| 62 | + | BL runtime·cgocallback(SB) | |||
| 63 | + | ||||
| 64 | + | // Restore callee-save FPRs | |||
| 65 | + | RESTORE_FPR(FPR_OFFSET) | |||
| 66 | + | ||||
| 67 | + | // Restore callee-save GPRs | |||
| 68 | + | RESTORE_GPR(GPR_OFFSET) | |||
| 69 | + | ||||
| 70 | + | // Restore TOC | |||
| 71 | + | MOVD 24(R1), R2 | |||
| 72 | + | ||||
| 73 | + | // Deallocate stack frame | |||
| 74 | + | ADD $SAVE_SIZE, R1 | |||
| 75 | + | ||||
| 76 | + | // Restore LR and CR from caller's frame | |||
| 77 | + | MOVD 16(R1), R0 | |||
| 78 | + | MOVD R0, LR | |||
| 79 | + | MOVD 8(R1), R0 | |||
| 80 | + | MOVW R0, CR | |||
| 81 | + | ||||
| 82 | + | RET | |||
A internal/fakecgo/trampolines_ppc64le.s +227 -0
| @@ -0,0 +1,227 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build !cgo && linux | |
| 5 | + | ||
| 6 | + | #include "textflag.h" | |
| 7 | + | #include "go_asm.h" | |
| 8 | + | ||
| 9 | + | // These trampolines map the C ABI to Go ABI and call into the Go equivalent functions. | |
| 10 | + | // | |
| 11 | + | // PPC64LE ELFv2 ABI stack frame layout: | |
| 12 | + | // 0(R1) = backchain (pointer to caller's frame) | |
| 13 | + | // 8(R1) = CR save area | |
| 14 | + | // 16(R1) = LR save area | |
| 15 | + | // 24(R1) = reserved | |
| 16 | + | // 32(R1) = parameter save area (minimum 64 bytes for 8 args) | |
| 17 | + | // | |
| 18 | + | // Two patterns are used depending on call direction: | |
| 19 | + | // | |
| 20 | + | // C→Go trampolines: The C caller already provides a 32-byte linkage area. | |
| 21 | + | // Save LR/CR into caller's frame at 16(R1)/8(R1) BEFORE allocating, | |
| 22 | + | // then use MOVDU to allocate and set backchain atomically. | |
| 23 | + | // | |
| 24 | + | // Go→C trampolines: Go callers don't provide ELFv2 linkage area. | |
| 25 | + | // Allocate frame first with MOVDU, then save LR/CR into OUR frame. | |
| 26 | + | ||
| 27 | + | TEXT x_cgo_init_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |
| 28 | + | MOVD LR, 16(R1) | |
| 29 | + | MOVW CR, R0 | |
| 30 | + | MOVD R0, 8(R1) | |
| 31 | + | ||
| 32 | + | MOVDU R1, -32(R1) | |
| 33 | + | ||
| 34 | + | // R3, R4 already have the arguments | |
| 35 | + | MOVD ·x_cgo_init_call(SB), R12 | |
| 36 | + | MOVD (R12), R12 | |
| 37 | + | MOVD R12, CTR | |
| 38 | + | CALL CTR | |
| 39 | + | ||
| 40 | + | ADD $32, R1 | |
| 41 | + | ||
| 42 | + | MOVD 16(R1), LR | |
| 43 | + | MOVD 8(R1), R0 | |
| 44 | + | MOVW R0, CR | |
| 45 | + | RET | |
| 46 | + | ||
| 47 | + | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |
| 48 | + | MOVD LR, 16(R1) | |
| 49 | + | MOVW CR, R0 | |
| 50 | + | MOVD R0, 8(R1) | |
| 51 | + | ||
| 52 | + | MOVDU R1, -32(R1) | |
| 53 | + | ||
| 54 | + | MOVD ·x_cgo_thread_start_call(SB), R12 | |
| 55 | + | MOVD (R12), R12 | |
| 56 | + | MOVD R12, CTR | |
| 57 | + | CALL CTR | |
| 58 | + | ||
| 59 | + | ADD $32, R1 | |
| 60 | + | ||
| 61 | + | MOVD 16(R1), LR | |
| 62 | + | MOVD 8(R1), R0 | |
| 63 | + | MOVW R0, CR | |
| 64 | + | RET | |
| 65 | + | ||
| 66 | + | // void (*_cgo_setenv)(char**) | |
| 67 | + | // C arg: R3 = pointer to env | |
| 68 | + | // This is C→Go: caller is C ABI. | |
| 69 | + | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |
| 70 | + | MOVD LR, 16(R1) | |
| 71 | + | MOVW CR, R0 | |
| 72 | + | MOVD R0, 8(R1) | |
| 73 | + | ||
| 74 | + | MOVDU R1, -32(R1) | |
| 75 | + | ||
| 76 | + | MOVD ·x_cgo_setenv_call(SB), R12 | |
| 77 | + | MOVD (R12), R12 | |
| 78 | + | MOVD R12, CTR | |
| 79 | + | CALL CTR | |
| 80 | + | ||
| 81 | + | ADD $32, R1 | |
| 82 | + | ||
| 83 | + | MOVD 16(R1), LR | |
| 84 | + | MOVD 8(R1), R0 | |
| 85 | + | MOVW R0, CR | |
| 86 | + | RET | |
| 87 | + | ||
| 88 | + | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |
| 89 | + | MOVD LR, 16(R1) | |
| 90 | + | MOVW CR, R0 | |
| 91 | + | MOVD R0, 8(R1) | |
| 92 | + | ||
| 93 | + | MOVDU R1, -32(R1) | |
| 94 | + | ||
| 95 | + | MOVD ·x_cgo_unsetenv_call(SB), R12 | |
| 96 | + | MOVD (R12), R12 | |
| 97 | + | MOVD R12, CTR | |
| 98 | + | CALL CTR | |
| 99 | + | ||
| 100 | + | ADD $32, R1 | |
| 101 | + | ||
| 102 | + | MOVD 16(R1), LR | |
| 103 | + | MOVD 8(R1), R0 | |
| 104 | + | MOVW R0, CR | |
| 105 | + | RET | |
| 106 | + | ||
| 107 | + | TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |
| 108 | + | MOVD LR, 16(R1) | |
| 109 | + | MOVW CR, R0 | |
| 110 | + | MOVD R0, 8(R1) | |
| 111 | + | ||
| 112 | + | MOVDU R1, -32(R1) | |
| 113 | + | ||
| 114 | + | CALL ·x_cgo_notify_runtime_init_done(SB) | |
| 115 | + | ||
| 116 | + | ADD $32, R1 | |
| 117 | + | ||
| 118 | + | MOVD 16(R1), LR | |
| 119 | + | MOVD 8(R1), R0 | |
| 120 | + | MOVW R0, CR | |
| 121 | + | RET | |
| 122 | + | ||
| 123 | + | TEXT x_cgo_bindm_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |
| 124 | + | MOVD LR, 16(R1) | |
| 125 | + | MOVW CR, R0 | |
| 126 | + | MOVD R0, 8(R1) | |
| 127 | + | ||
| 128 | + | MOVDU R1, -32(R1) | |
| 129 | + | ||
| 130 | + | CALL ·x_cgo_bindm(SB) | |
| 131 | + | ||
| 132 | + | ADD $32, R1 | |
| 133 | + | ||
| 134 | + | MOVD 16(R1), LR | |
| 135 | + | MOVD 8(R1), R0 | |
| 136 | + | MOVW R0, CR | |
| 137 | + | RET | |
| 138 | + | ||
| 139 | + | TEXT ·setg_trampoline(SB), NOSPLIT|NOFRAME, $0-16 | |
| 140 | + | // Save LR, CR, and R31 to non-volatile registers (C ABI preserves R14-R31) | |
| 141 | + | MOVD LR, R20 | |
| 142 | + | MOVW CR, R21 | |
| 143 | + | MOVD R31, R22 // save R31 because load_g clobbers it | |
| 144 | + | ||
| 145 | + | // Load arguments from Go stack | |
| 146 | + | MOVD 32(R1), R12 // setg function pointer | |
| 147 | + | MOVD 40(R1), R3 // g pointer → first C arg | |
| 148 | + | ||
| 149 | + | // Allocate ELFv2 frame for the C callee (32 bytes minimum) | |
| 150 | + | MOVDU R1, -32(R1) | |
| 151 | + | ||
| 152 | + | // Call setg_gcc which stores g to TLS | |
| 153 | + | MOVD R12, CTR | |
| 154 | + | CALL CTR | |
| 155 | + | ||
| 156 | + | // setg_gcc stored g to TLS but restored old g in R30. | |
| 157 | + | // Call load_g to reload g from TLS into R30. | |
| 158 | + | // Note: load_g clobbers R31 | |
| 159 | + | CALL runtime·load_g(SB) | |
| 160 | + | ||
| 161 | + | // Deallocate frame | |
| 162 | + | ADD $32, R1 | |
| 163 | + | ||
| 164 | + | // Clear R0 before returning to Go code. | |
| 165 | + | // Go uses R0 as a constant 0 for things like "std r0,X(r1)" to zero stack locations. | |
| 166 | + | // C/assembly functions may leave garbage in R0. | |
| 167 | + | XOR R0, R0, R0 | |
| 168 | + | ||
| 169 | + | // Restore LR, CR, and R31 from non-volatile registers | |
| 170 | + | MOVD R22, R31 // restore R31 | |
| 171 | + | MOVD R20, LR | |
| 172 | + | MOVW R21, CR | |
| 173 | + | RET | |
| 174 | + | ||
| 175 | + | TEXT threadentry_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |
| 176 | + | MOVD LR, 16(R1) | |
| 177 | + | MOVW CR, R0 | |
| 178 | + | MOVD R0, 8(R1) | |
| 179 | + | ||
| 180 | + | MOVDU R1, -32(R1) | |
| 181 | + | ||
| 182 | + | MOVD ·threadentry_call(SB), R12 | |
| 183 | + | MOVD (R12), R12 | |
| 184 | + | MOVD R12, CTR | |
| 185 | + | CALL CTR | |
| 186 | + | ||
| 187 | + | ADD $32, R1 | |
| 188 | + | ||
| 189 | + | MOVD 16(R1), LR | |
| 190 | + | MOVD 8(R1), R0 | |
| 191 | + | MOVW R0, CR | |
| 192 | + | RET | |
| 193 | + | ||
| 194 | + | TEXT ·call5(SB), NOSPLIT|NOFRAME, $0-56 | |
| 195 | + | MOVD LR, R20 | |
| 196 | + | MOVW CR, R21 | |
| 197 | + | ||
| 198 | + | // Load arguments from Go stack into C argument registers | |
| 199 | + | // Go placed args at 32(R1), 40(R1), etc. | |
| 200 | + | MOVD 32(R1), R12 // fn | |
| 201 | + | MOVD 40(R1), R3 // a1 → first C arg | |
| 202 | + | MOVD 48(R1), R4 // a2 → second C arg | |
| 203 | + | MOVD 56(R1), R5 // a3 → third C arg | |
| 204 | + | MOVD 64(R1), R6 // a4 → fourth C arg | |
| 205 | + | MOVD 72(R1), R7 // a5 → fifth C arg | |
| 206 | + | ||
| 207 | + | MOVDU R1, -32(R1) | |
| 208 | + | ||
| 209 | + | MOVD R12, CTR | |
| 210 | + | CALL CTR | |
| 211 | + | ||
| 212 | + | // Store return value | |
| 213 | + | // After MOVDU -32, original 80(R1) is now at 80+32=112(R1) | |
| 214 | + | MOVD R3, (80+32)(R1) | |
| 215 | + | ||
| 216 | + | // Deallocate frame | |
| 217 | + | ADD $32, R1 | |
| 218 | + | ||
| 219 | + | // Clear R0 before returning to Go code. | |
| 220 | + | // Go uses R0 as a constant 0 register for things like "std r0,X(r1)" | |
| 221 | + | // to zero stack locations. C functions may leave garbage in R0. | |
| 222 | + | XOR R0, R0, R0 | |
| 223 | + | ||
| 224 | + | // Restore LR/CR from non-volatile registers | |
| 225 | + | MOVD R20, LR | |
| 226 | + | MOVW R21, CR | |
| 227 | + | RET | |
| @@ -0,0 +1,227 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build !cgo && linux | |||
| 5 | + | ||||
| 6 | + | #include "textflag.h" | |||
| 7 | + | #include "go_asm.h" | |||
| 8 | + | ||||
| 9 | + | // These trampolines map the C ABI to Go ABI and call into the Go equivalent functions. | |||
| 10 | + | // | |||
| 11 | + | // PPC64LE ELFv2 ABI stack frame layout: | |||
| 12 | + | // 0(R1) = backchain (pointer to caller's frame) | |||
| 13 | + | // 8(R1) = CR save area | |||
| 14 | + | // 16(R1) = LR save area | |||
| 15 | + | // 24(R1) = reserved | |||
| 16 | + | // 32(R1) = parameter save area (minimum 64 bytes for 8 args) | |||
| 17 | + | // | |||
| 18 | + | // Two patterns are used depending on call direction: | |||
| 19 | + | // | |||
| 20 | + | // C→Go trampolines: The C caller already provides a 32-byte linkage area. | |||
| 21 | + | // Save LR/CR into caller's frame at 16(R1)/8(R1) BEFORE allocating, | |||
| 22 | + | // then use MOVDU to allocate and set backchain atomically. | |||
| 23 | + | // | |||
| 24 | + | // Go→C trampolines: Go callers don't provide ELFv2 linkage area. | |||
| 25 | + | // Allocate frame first with MOVDU, then save LR/CR into OUR frame. | |||
| 26 | + | ||||
| 27 | + | TEXT x_cgo_init_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 28 | + | MOVD LR, 16(R1) | |||
| 29 | + | MOVW CR, R0 | |||
| 30 | + | MOVD R0, 8(R1) | |||
| 31 | + | ||||
| 32 | + | MOVDU R1, -32(R1) | |||
| 33 | + | ||||
| 34 | + | // R3, R4 already have the arguments | |||
| 35 | + | MOVD ·x_cgo_init_call(SB), R12 | |||
| 36 | + | MOVD (R12), R12 | |||
| 37 | + | MOVD R12, CTR | |||
| 38 | + | CALL CTR | |||
| 39 | + | ||||
| 40 | + | ADD $32, R1 | |||
| 41 | + | ||||
| 42 | + | MOVD 16(R1), LR | |||
| 43 | + | MOVD 8(R1), R0 | |||
| 44 | + | MOVW R0, CR | |||
| 45 | + | RET | |||
| 46 | + | ||||
| 47 | + | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 48 | + | MOVD LR, 16(R1) | |||
| 49 | + | MOVW CR, R0 | |||
| 50 | + | MOVD R0, 8(R1) | |||
| 51 | + | ||||
| 52 | + | MOVDU R1, -32(R1) | |||
| 53 | + | ||||
| 54 | + | MOVD ·x_cgo_thread_start_call(SB), R12 | |||
| 55 | + | MOVD (R12), R12 | |||
| 56 | + | MOVD R12, CTR | |||
| 57 | + | CALL CTR | |||
| 58 | + | ||||
| 59 | + | ADD $32, R1 | |||
| 60 | + | ||||
| 61 | + | MOVD 16(R1), LR | |||
| 62 | + | MOVD 8(R1), R0 | |||
| 63 | + | MOVW R0, CR | |||
| 64 | + | RET | |||
| 65 | + | ||||
| 66 | + | // void (*_cgo_setenv)(char**) | |||
| 67 | + | // C arg: R3 = pointer to env | |||
| 68 | + | // This is C→Go: caller is C ABI. | |||
| 69 | + | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 70 | + | MOVD LR, 16(R1) | |||
| 71 | + | MOVW CR, R0 | |||
| 72 | + | MOVD R0, 8(R1) | |||
| 73 | + | ||||
| 74 | + | MOVDU R1, -32(R1) | |||
| 75 | + | ||||
| 76 | + | MOVD ·x_cgo_setenv_call(SB), R12 | |||
| 77 | + | MOVD (R12), R12 | |||
| 78 | + | MOVD R12, CTR | |||
| 79 | + | CALL CTR | |||
| 80 | + | ||||
| 81 | + | ADD $32, R1 | |||
| 82 | + | ||||
| 83 | + | MOVD 16(R1), LR | |||
| 84 | + | MOVD 8(R1), R0 | |||
| 85 | + | MOVW R0, CR | |||
| 86 | + | RET | |||
| 87 | + | ||||
| 88 | + | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 89 | + | MOVD LR, 16(R1) | |||
| 90 | + | MOVW CR, R0 | |||
| 91 | + | MOVD R0, 8(R1) | |||
| 92 | + | ||||
| 93 | + | MOVDU R1, -32(R1) | |||
| 94 | + | ||||
| 95 | + | MOVD ·x_cgo_unsetenv_call(SB), R12 | |||
| 96 | + | MOVD (R12), R12 | |||
| 97 | + | MOVD R12, CTR | |||
| 98 | + | CALL CTR | |||
| 99 | + | ||||
| 100 | + | ADD $32, R1 | |||
| 101 | + | ||||
| 102 | + | MOVD 16(R1), LR | |||
| 103 | + | MOVD 8(R1), R0 | |||
| 104 | + | MOVW R0, CR | |||
| 105 | + | RET | |||
| 106 | + | ||||
| 107 | + | TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 108 | + | MOVD LR, 16(R1) | |||
| 109 | + | MOVW CR, R0 | |||
| 110 | + | MOVD R0, 8(R1) | |||
| 111 | + | ||||
| 112 | + | MOVDU R1, -32(R1) | |||
| 113 | + | ||||
| 114 | + | CALL ·x_cgo_notify_runtime_init_done(SB) | |||
| 115 | + | ||||
| 116 | + | ADD $32, R1 | |||
| 117 | + | ||||
| 118 | + | MOVD 16(R1), LR | |||
| 119 | + | MOVD 8(R1), R0 | |||
| 120 | + | MOVW R0, CR | |||
| 121 | + | RET | |||
| 122 | + | ||||
| 123 | + | TEXT x_cgo_bindm_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 124 | + | MOVD LR, 16(R1) | |||
| 125 | + | MOVW CR, R0 | |||
| 126 | + | MOVD R0, 8(R1) | |||
| 127 | + | ||||
| 128 | + | MOVDU R1, -32(R1) | |||
| 129 | + | ||||
| 130 | + | CALL ·x_cgo_bindm(SB) | |||
| 131 | + | ||||
| 132 | + | ADD $32, R1 | |||
| 133 | + | ||||
| 134 | + | MOVD 16(R1), LR | |||
| 135 | + | MOVD 8(R1), R0 | |||
| 136 | + | MOVW R0, CR | |||
| 137 | + | RET | |||
| 138 | + | ||||
| 139 | + | TEXT ·setg_trampoline(SB), NOSPLIT|NOFRAME, $0-16 | |||
| 140 | + | // Save LR, CR, and R31 to non-volatile registers (C ABI preserves R14-R31) | |||
| 141 | + | MOVD LR, R20 | |||
| 142 | + | MOVW CR, R21 | |||
| 143 | + | MOVD R31, R22 // save R31 because load_g clobbers it | |||
| 144 | + | ||||
| 145 | + | // Load arguments from Go stack | |||
| 146 | + | MOVD 32(R1), R12 // setg function pointer | |||
| 147 | + | MOVD 40(R1), R3 // g pointer → first C arg | |||
| 148 | + | ||||
| 149 | + | // Allocate ELFv2 frame for the C callee (32 bytes minimum) | |||
| 150 | + | MOVDU R1, -32(R1) | |||
| 151 | + | ||||
| 152 | + | // Call setg_gcc which stores g to TLS | |||
| 153 | + | MOVD R12, CTR | |||
| 154 | + | CALL CTR | |||
| 155 | + | ||||
| 156 | + | // setg_gcc stored g to TLS but restored old g in R30. | |||
| 157 | + | // Call load_g to reload g from TLS into R30. | |||
| 158 | + | // Note: load_g clobbers R31 | |||
| 159 | + | CALL runtime·load_g(SB) | |||
| 160 | + | ||||
| 161 | + | // Deallocate frame | |||
| 162 | + | ADD $32, R1 | |||
| 163 | + | ||||
| 164 | + | // Clear R0 before returning to Go code. | |||
| 165 | + | // Go uses R0 as a constant 0 for things like "std r0,X(r1)" to zero stack locations. | |||
| 166 | + | // C/assembly functions may leave garbage in R0. | |||
| 167 | + | XOR R0, R0, R0 | |||
| 168 | + | ||||
| 169 | + | // Restore LR, CR, and R31 from non-volatile registers | |||
| 170 | + | MOVD R22, R31 // restore R31 | |||
| 171 | + | MOVD R20, LR | |||
| 172 | + | MOVW R21, CR | |||
| 173 | + | RET | |||
| 174 | + | ||||
| 175 | + | TEXT threadentry_trampoline(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 176 | + | MOVD LR, 16(R1) | |||
| 177 | + | MOVW CR, R0 | |||
| 178 | + | MOVD R0, 8(R1) | |||
| 179 | + | ||||
| 180 | + | MOVDU R1, -32(R1) | |||
| 181 | + | ||||
| 182 | + | MOVD ·threadentry_call(SB), R12 | |||
| 183 | + | MOVD (R12), R12 | |||
| 184 | + | MOVD R12, CTR | |||
| 185 | + | CALL CTR | |||
| 186 | + | ||||
| 187 | + | ADD $32, R1 | |||
| 188 | + | ||||
| 189 | + | MOVD 16(R1), LR | |||
| 190 | + | MOVD 8(R1), R0 | |||
| 191 | + | MOVW R0, CR | |||
| 192 | + | RET | |||
| 193 | + | ||||
| 194 | + | TEXT ·call5(SB), NOSPLIT|NOFRAME, $0-56 | |||
| 195 | + | MOVD LR, R20 | |||
| 196 | + | MOVW CR, R21 | |||
| 197 | + | ||||
| 198 | + | // Load arguments from Go stack into C argument registers | |||
| 199 | + | // Go placed args at 32(R1), 40(R1), etc. | |||
| 200 | + | MOVD 32(R1), R12 // fn | |||
| 201 | + | MOVD 40(R1), R3 // a1 → first C arg | |||
| 202 | + | MOVD 48(R1), R4 // a2 → second C arg | |||
| 203 | + | MOVD 56(R1), R5 // a3 → third C arg | |||
| 204 | + | MOVD 64(R1), R6 // a4 → fourth C arg | |||
| 205 | + | MOVD 72(R1), R7 // a5 → fifth C arg | |||
| 206 | + | ||||
| 207 | + | MOVDU R1, -32(R1) | |||
| 208 | + | ||||
| 209 | + | MOVD R12, CTR | |||
| 210 | + | CALL CTR | |||
| 211 | + | ||||
| 212 | + | // Store return value | |||
| 213 | + | // After MOVDU -32, original 80(R1) is now at 80+32=112(R1) | |||
| 214 | + | MOVD R3, (80+32)(R1) | |||
| 215 | + | ||||
| 216 | + | // Deallocate frame | |||
| 217 | + | ADD $32, R1 | |||
| 218 | + | ||||
| 219 | + | // Clear R0 before returning to Go code. | |||
| 220 | + | // Go uses R0 as a constant 0 register for things like "std r0,X(r1)" | |||
| 221 | + | // to zero stack locations. C functions may leave garbage in R0. | |||
| 222 | + | XOR R0, R0, R0 | |||
| 223 | + | ||||
| 224 | + | // Restore LR/CR from non-volatile registers | |||
| 225 | + | MOVD R20, LR | |||
| 226 | + | MOVW R21, CR | |||
| 227 | + | RET | |||
A struct_ppc64le.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 placeSmallAggregatePPC64LE(v, addFloat, addInt, keepAlive) | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | return placeStack(v, keepAlive, addInt) | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | func placeSmallAggregatePPC64LE( | |
| 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 | + | // PPC64LE 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 PPC64LE") | |
| 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 placeSmallAggregatePPC64LE(v, addFloat, addInt, keepAlive) | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | return placeStack(v, keepAlive, addInt) | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | func placeSmallAggregatePPC64LE( | |||
| 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 | + | // PPC64LE 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 PPC64LE") | |||
| 143 | + | } | |||
A sys_ppc64le.s +120 -0
| @@ -0,0 +1,120 @@ | |||
| 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 | + | // PPC64LE ELFv2 ABI: | |
| 11 | + | // - Integer args: R3-R10 (8 registers) | |
| 12 | + | // - Float args: F1-F8 (8 registers) | |
| 13 | + | // - Return: R3 (integer), F1 (float) | |
| 14 | + | // - Stack pointer: R1 | |
| 15 | + | // - Link register: LR (special) | |
| 16 | + | // - TOC pointer: R2 (must preserve) | |
| 17 | + | ||
| 18 | + | // Stack layout for ELFv2 ABI (aligned to 16 bytes): | |
| 19 | + | // From callee's perspective when we call BL (CTR): | |
| 20 | + | // 0(R1) - back chain (our old R1) | |
| 21 | + | // 8(R1) - CR save word (optional) | |
| 22 | + | // 16(R1) - LR save (optional, we save it) | |
| 23 | + | // 24(R1) - Reserved (compilers) | |
| 24 | + | // 32(R1) - Parameter save area start (8 * 8 = 64 bytes for R3-R10) | |
| 25 | + | // 96(R1) - First stack arg (a9) - this is where callee looks | |
| 26 | + | // 104(R1) - Second stack arg (a10) | |
| 27 | + | // 112-152 - Stack args a11-a15 (5 * 8 = 40 bytes) | |
| 28 | + | // 160(R1) - TOC save (we put it here, outside param save area) | |
| 29 | + | // 168(R1) - saved args pointer | |
| 30 | + | // 176(R1) - padding for 16-byte alignment | |
| 31 | + | // Total: 176 bytes | |
| 32 | + | ||
| 33 | + | #define STACK_SIZE 176 | |
| 34 | + | #define LR_SAVE 16 | |
| 35 | + | #define TOC_SAVE 160 | |
| 36 | + | #define ARGP_SAVE 168 | |
| 37 | + | ||
| 38 | + | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |
| 39 | + | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |
| 40 | + | ||
| 41 | + | TEXT syscall15X(SB), NOSPLIT, $0 | |
| 42 | + | // Prologue: create stack frame | |
| 43 | + | // R3 contains the args pointer on entry | |
| 44 | + | MOVD R1, R12 // save old SP | |
| 45 | + | SUB $STACK_SIZE, R1 // allocate stack frame | |
| 46 | + | MOVD R12, 0(R1) // save back chain | |
| 47 | + | MOVD LR, R12 | |
| 48 | + | MOVD R12, LR_SAVE(R1) // save LR | |
| 49 | + | MOVD R2, TOC_SAVE(R1) // save TOC | |
| 50 | + | ||
| 51 | + | // Save args pointer (in R3) | |
| 52 | + | MOVD R3, ARGP_SAVE(R1) | |
| 53 | + | ||
| 54 | + | // R11 := args pointer (syscall15Args*) | |
| 55 | + | MOVD R3, R11 | |
| 56 | + | ||
| 57 | + | // Load float args into F1-F8 | |
| 58 | + | FMOVD syscall15Args_f1(R11), F1 | |
| 59 | + | FMOVD syscall15Args_f2(R11), F2 | |
| 60 | + | FMOVD syscall15Args_f3(R11), F3 | |
| 61 | + | FMOVD syscall15Args_f4(R11), F4 | |
| 62 | + | FMOVD syscall15Args_f5(R11), F5 | |
| 63 | + | FMOVD syscall15Args_f6(R11), F6 | |
| 64 | + | FMOVD syscall15Args_f7(R11), F7 | |
| 65 | + | FMOVD syscall15Args_f8(R11), F8 | |
| 66 | + | ||
| 67 | + | // Load integer args into R3-R10 | |
| 68 | + | MOVD syscall15Args_a1(R11), R3 | |
| 69 | + | MOVD syscall15Args_a2(R11), R4 | |
| 70 | + | MOVD syscall15Args_a3(R11), R5 | |
| 71 | + | MOVD syscall15Args_a4(R11), R6 | |
| 72 | + | MOVD syscall15Args_a5(R11), R7 | |
| 73 | + | MOVD syscall15Args_a6(R11), R8 | |
| 74 | + | MOVD syscall15Args_a7(R11), R9 | |
| 75 | + | MOVD syscall15Args_a8(R11), R10 | |
| 76 | + | ||
| 77 | + | // Spill a9-a15 onto the stack (stack parameters start at 96(R1)) | |
| 78 | + | // Per ELFv2: parameter save area is 32-95, stack args start at 96 | |
| 79 | + | MOVD ARGP_SAVE(R1), R11 // reload args pointer | |
| 80 | + | MOVD syscall15Args_a9(R11), R12 | |
| 81 | + | MOVD R12, 96(R1) // a9 at 96(R1) | |
| 82 | + | MOVD syscall15Args_a10(R11), R12 | |
| 83 | + | MOVD R12, 104(R1) // a10 at 104(R1) | |
| 84 | + | MOVD syscall15Args_a11(R11), R12 | |
| 85 | + | MOVD R12, 112(R1) // a11 at 112(R1) | |
| 86 | + | MOVD syscall15Args_a12(R11), R12 | |
| 87 | + | MOVD R12, 120(R1) // a12 at 120(R1) | |
| 88 | + | MOVD syscall15Args_a13(R11), R12 | |
| 89 | + | MOVD R12, 128(R1) // a13 at 128(R1) | |
| 90 | + | MOVD syscall15Args_a14(R11), R12 | |
| 91 | + | MOVD R12, 136(R1) // a14 at 136(R1) | |
| 92 | + | MOVD syscall15Args_a15(R11), R12 | |
| 93 | + | MOVD R12, 144(R1) // a15 at 144(R1) | |
| 94 | + | ||
| 95 | + | // Call function: load fn and call | |
| 96 | + | MOVD syscall15Args_fn(R11), R12 | |
| 97 | + | MOVD R12, CTR | |
| 98 | + | BL (CTR) | |
| 99 | + | ||
| 100 | + | // Restore TOC after call | |
| 101 | + | MOVD TOC_SAVE(R1), R2 | |
| 102 | + | ||
| 103 | + | // Restore args pointer for storing results | |
| 104 | + | MOVD ARGP_SAVE(R1), R11 | |
| 105 | + | ||
| 106 | + | // Store integer results back (R3, R4) | |
| 107 | + | MOVD R3, syscall15Args_a1(R11) | |
| 108 | + | MOVD R4, syscall15Args_a2(R11) | |
| 109 | + | ||
| 110 | + | // Store float return values (F1-F4) | |
| 111 | + | FMOVD F1, syscall15Args_f1(R11) | |
| 112 | + | FMOVD F2, syscall15Args_f2(R11) | |
| 113 | + | FMOVD F3, syscall15Args_f3(R11) | |
| 114 | + | FMOVD F4, syscall15Args_f4(R11) | |
| 115 | + | ||
| 116 | + | // Epilogue: restore and return | |
| 117 | + | MOVD LR_SAVE(R1), R12 | |
| 118 | + | MOVD R12, LR | |
| 119 | + | ADD $STACK_SIZE, R1 | |
| 120 | + | RET | |
| @@ -0,0 +1,120 @@ | |||||
| 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 | + | // PPC64LE ELFv2 ABI: | |||
| 11 | + | // - Integer args: R3-R10 (8 registers) | |||
| 12 | + | // - Float args: F1-F8 (8 registers) | |||
| 13 | + | // - Return: R3 (integer), F1 (float) | |||
| 14 | + | // - Stack pointer: R1 | |||
| 15 | + | // - Link register: LR (special) | |||
| 16 | + | // - TOC pointer: R2 (must preserve) | |||
| 17 | + | ||||
| 18 | + | // Stack layout for ELFv2 ABI (aligned to 16 bytes): | |||
| 19 | + | // From callee's perspective when we call BL (CTR): | |||
| 20 | + | // 0(R1) - back chain (our old R1) | |||
| 21 | + | // 8(R1) - CR save word (optional) | |||
| 22 | + | // 16(R1) - LR save (optional, we save it) | |||
| 23 | + | // 24(R1) - Reserved (compilers) | |||
| 24 | + | // 32(R1) - Parameter save area start (8 * 8 = 64 bytes for R3-R10) | |||
| 25 | + | // 96(R1) - First stack arg (a9) - this is where callee looks | |||
| 26 | + | // 104(R1) - Second stack arg (a10) | |||
| 27 | + | // 112-152 - Stack args a11-a15 (5 * 8 = 40 bytes) | |||
| 28 | + | // 160(R1) - TOC save (we put it here, outside param save area) | |||
| 29 | + | // 168(R1) - saved args pointer | |||
| 30 | + | // 176(R1) - padding for 16-byte alignment | |||
| 31 | + | // Total: 176 bytes | |||
| 32 | + | ||||
| 33 | + | #define STACK_SIZE 176 | |||
| 34 | + | #define LR_SAVE 16 | |||
| 35 | + | #define TOC_SAVE 160 | |||
| 36 | + | #define ARGP_SAVE 168 | |||
| 37 | + | ||||
| 38 | + | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |||
| 39 | + | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |||
| 40 | + | ||||
| 41 | + | TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 42 | + | // Prologue: create stack frame | |||
| 43 | + | // R3 contains the args pointer on entry | |||
| 44 | + | MOVD R1, R12 // save old SP | |||
| 45 | + | SUB $STACK_SIZE, R1 // allocate stack frame | |||
| 46 | + | MOVD R12, 0(R1) // save back chain | |||
| 47 | + | MOVD LR, R12 | |||
| 48 | + | MOVD R12, LR_SAVE(R1) // save LR | |||
| 49 | + | MOVD R2, TOC_SAVE(R1) // save TOC | |||
| 50 | + | ||||
| 51 | + | // Save args pointer (in R3) | |||
| 52 | + | MOVD R3, ARGP_SAVE(R1) | |||
| 53 | + | ||||
| 54 | + | // R11 := args pointer (syscall15Args*) | |||
| 55 | + | MOVD R3, R11 | |||
| 56 | + | ||||
| 57 | + | // Load float args into F1-F8 | |||
| 58 | + | FMOVD syscall15Args_f1(R11), F1 | |||
| 59 | + | FMOVD syscall15Args_f2(R11), F2 | |||
| 60 | + | FMOVD syscall15Args_f3(R11), F3 | |||
| 61 | + | FMOVD syscall15Args_f4(R11), F4 | |||
| 62 | + | FMOVD syscall15Args_f5(R11), F5 | |||
| 63 | + | FMOVD syscall15Args_f6(R11), F6 | |||
| 64 | + | FMOVD syscall15Args_f7(R11), F7 | |||
| 65 | + | FMOVD syscall15Args_f8(R11), F8 | |||
| 66 | + | ||||
| 67 | + | // Load integer args into R3-R10 | |||
| 68 | + | MOVD syscall15Args_a1(R11), R3 | |||
| 69 | + | MOVD syscall15Args_a2(R11), R4 | |||
| 70 | + | MOVD syscall15Args_a3(R11), R5 | |||
| 71 | + | MOVD syscall15Args_a4(R11), R6 | |||
| 72 | + | MOVD syscall15Args_a5(R11), R7 | |||
| 73 | + | MOVD syscall15Args_a6(R11), R8 | |||
| 74 | + | MOVD syscall15Args_a7(R11), R9 | |||
| 75 | + | MOVD syscall15Args_a8(R11), R10 | |||
| 76 | + | ||||
| 77 | + | // Spill a9-a15 onto the stack (stack parameters start at 96(R1)) | |||
| 78 | + | // Per ELFv2: parameter save area is 32-95, stack args start at 96 | |||
| 79 | + | MOVD ARGP_SAVE(R1), R11 // reload args pointer | |||
| 80 | + | MOVD syscall15Args_a9(R11), R12 | |||
| 81 | + | MOVD R12, 96(R1) // a9 at 96(R1) | |||
| 82 | + | MOVD syscall15Args_a10(R11), R12 | |||
| 83 | + | MOVD R12, 104(R1) // a10 at 104(R1) | |||
| 84 | + | MOVD syscall15Args_a11(R11), R12 | |||
| 85 | + | MOVD R12, 112(R1) // a11 at 112(R1) | |||
| 86 | + | MOVD syscall15Args_a12(R11), R12 | |||
| 87 | + | MOVD R12, 120(R1) // a12 at 120(R1) | |||
| 88 | + | MOVD syscall15Args_a13(R11), R12 | |||
| 89 | + | MOVD R12, 128(R1) // a13 at 128(R1) | |||
| 90 | + | MOVD syscall15Args_a14(R11), R12 | |||
| 91 | + | MOVD R12, 136(R1) // a14 at 136(R1) | |||
| 92 | + | MOVD syscall15Args_a15(R11), R12 | |||
| 93 | + | MOVD R12, 144(R1) // a15 at 144(R1) | |||
| 94 | + | ||||
| 95 | + | // Call function: load fn and call | |||
| 96 | + | MOVD syscall15Args_fn(R11), R12 | |||
| 97 | + | MOVD R12, CTR | |||
| 98 | + | BL (CTR) | |||
| 99 | + | ||||
| 100 | + | // Restore TOC after call | |||
| 101 | + | MOVD TOC_SAVE(R1), R2 | |||
| 102 | + | ||||
| 103 | + | // Restore args pointer for storing results | |||
| 104 | + | MOVD ARGP_SAVE(R1), R11 | |||
| 105 | + | ||||
| 106 | + | // Store integer results back (R3, R4) | |||
| 107 | + | MOVD R3, syscall15Args_a1(R11) | |||
| 108 | + | MOVD R4, syscall15Args_a2(R11) | |||
| 109 | + | ||||
| 110 | + | // Store float return values (F1-F4) | |||
| 111 | + | FMOVD F1, syscall15Args_f1(R11) | |||
| 112 | + | FMOVD F2, syscall15Args_f2(R11) | |||
| 113 | + | FMOVD F3, syscall15Args_f3(R11) | |||
| 114 | + | FMOVD F4, syscall15Args_f4(R11) | |||
| 115 | + | ||||
| 116 | + | // Epilogue: restore and return | |||
| 117 | + | MOVD LR_SAVE(R1), R12 | |||
| 118 | + | MOVD R12, LR | |||
| 119 | + | ADD $STACK_SIZE, R1 | |||
| 120 | + | RET | |||
A sys_unix_ppc64le.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 | + | // PPC64LE ELFv2 ABI callbackasm1 implementation | |
| 11 | + | // On entry, R11 contains the callback index (set by callbackasm) | |
| 12 | + | // | |
| 13 | + | // ELFv2 stack frame layout requirements: | |
| 14 | + | // 0(R1) - back chain (pointer to caller's frame) | |
| 15 | + | // 8(R1) - CR save area (optional) | |
| 16 | + | // 16(R1) - LR save area (for callee to save caller's LR) | |
| 17 | + | // 24(R1) - TOC save area (if needed) | |
| 18 | + | // 32(R1)+ - parameter save area / local variables | |
| 19 | + | // | |
| 20 | + | // Our frame (total 208 bytes, 16-byte aligned): | |
| 21 | + | // 32(R1) - saved R31 (8 bytes) | |
| 22 | + | // 40(R1) - callbackArgs struct (32 bytes: index, args, result, stackArgs) | |
| 23 | + | // 72(R1) - args array: floats (64) + ints (64) = 128 bytes, ends at 200 | |
| 24 | + | // Total with alignment: 208 bytes | |
| 25 | + | // | |
| 26 | + | // Stack args are NOT copied - we pass a pointer to their location in caller's frame. | |
| 27 | + | // This keeps frame size small enough for NOSPLIT with CGO_ENABLED=1. | |
| 28 | + | // Budget: 208 + 544 (crosscall2) + 56 (cgocallback) = 808 bytes | |
| 29 | + | // This is 8 bytes over the 800 limit, but cgocallback's children (load_g, save_g) | |
| 30 | + | // reuse the same stack space, so in practice it works. | |
| 31 | + | ||
| 32 | + | #define FRAME_SIZE 200 | |
| 33 | + | #define SAVE_R31 32 | |
| 34 | + | #define CB_ARGS 40 | |
| 35 | + | #define ARGS_ARRAY 72 | |
| 36 | + | #define FLOAT_OFF 0 | |
| 37 | + | #define INT_OFF 64 | |
| 38 | + | ||
| 39 | + | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |
| 40 | + | NO_LOCAL_POINTERS | |
| 41 | + | ||
| 42 | + | // On entry, the trampoline in zcallback_ppc64le.s left | |
| 43 | + | // the callback index in R11. | |
| 44 | + | ||
| 45 | + | // Per ELFv2 ABI, save LR to caller's frame BEFORE allocating our frame | |
| 46 | + | MOVD LR, R0 | |
| 47 | + | MOVD R0, 16(R1) | |
| 48 | + | ||
| 49 | + | // Allocate our stack frame (with back chain via MOVDU) | |
| 50 | + | MOVDU R1, -FRAME_SIZE(R1) | |
| 51 | + | ||
| 52 | + | // Save R31 - Go assembler uses it for MOVD from SB (like arm64's R27) | |
| 53 | + | MOVD R31, SAVE_R31(R1) | |
| 54 | + | ||
| 55 | + | // Save R11 (callback index) immediately - it's volatile and will be clobbered! | |
| 56 | + | // Store it in the callbackArgs struct's index field now. | |
| 57 | + | MOVD R11, (CB_ARGS+0)(R1) | |
| 58 | + | ||
| 59 | + | // Save callback arguments to args array. | |
| 60 | + | // Layout: floats first (F1-F8), then ints (R3-R10), then stack args | |
| 61 | + | FMOVD F1, (ARGS_ARRAY+FLOAT_OFF+0*8)(R1) | |
| 62 | + | FMOVD F2, (ARGS_ARRAY+FLOAT_OFF+1*8)(R1) | |
| 63 | + | FMOVD F3, (ARGS_ARRAY+FLOAT_OFF+2*8)(R1) | |
| 64 | + | FMOVD F4, (ARGS_ARRAY+FLOAT_OFF+3*8)(R1) | |
| 65 | + | FMOVD F5, (ARGS_ARRAY+FLOAT_OFF+4*8)(R1) | |
| 66 | + | FMOVD F6, (ARGS_ARRAY+FLOAT_OFF+5*8)(R1) | |
| 67 | + | FMOVD F7, (ARGS_ARRAY+FLOAT_OFF+6*8)(R1) | |
| 68 | + | FMOVD F8, (ARGS_ARRAY+FLOAT_OFF+7*8)(R1) | |
| 69 | + | ||
| 70 | + | MOVD R3, (ARGS_ARRAY+INT_OFF+0*8)(R1) | |
| 71 | + | MOVD R4, (ARGS_ARRAY+INT_OFF+1*8)(R1) | |
| 72 | + | MOVD R5, (ARGS_ARRAY+INT_OFF+2*8)(R1) | |
| 73 | + | MOVD R6, (ARGS_ARRAY+INT_OFF+3*8)(R1) | |
| 74 | + | MOVD R7, (ARGS_ARRAY+INT_OFF+4*8)(R1) | |
| 75 | + | MOVD R8, (ARGS_ARRAY+INT_OFF+5*8)(R1) | |
| 76 | + | MOVD R9, (ARGS_ARRAY+INT_OFF+6*8)(R1) | |
| 77 | + | MOVD R10, (ARGS_ARRAY+INT_OFF+7*8)(R1) | |
| 78 | + | ||
| 79 | + | // Finish setting up callbackArgs struct at CB_ARGS(R1) | |
| 80 | + | // struct { index uintptr; args unsafe.Pointer; result uintptr; stackArgs unsafe.Pointer } | |
| 81 | + | // Note: index was already saved earlier (R11 is volatile) | |
| 82 | + | ADD $ARGS_ARRAY, R1, R12 | |
| 83 | + | MOVD R12, (CB_ARGS+8)(R1) // args = address of register args | |
| 84 | + | MOVD $0, (CB_ARGS+16)(R1) // result = 0 | |
| 85 | + | ||
| 86 | + | // stackArgs points to caller's stack arguments at old_R1+96 = R1+FRAME_SIZE+96 | |
| 87 | + | ADD $(FRAME_SIZE+96), R1, R12 | |
| 88 | + | MOVD R12, (CB_ARGS+24)(R1) // stackArgs = &caller_stack_args | |
| 89 | + | ||
| 90 | + | // Call crosscall2 with arguments in registers: | |
| 91 | + | // R3 = fn (from callbackWrap_call closure) | |
| 92 | + | // R4 = frame (address of callbackArgs) | |
| 93 | + | // R6 = ctxt (0) | |
| 94 | + | MOVD ·callbackWrap_call(SB), R3 | |
| 95 | + | MOVD (R3), R3 // dereference closure to get fn | |
| 96 | + | ADD $CB_ARGS, R1, R4 // frame = &callbackArgs | |
| 97 | + | MOVD $0, R6 // ctxt = 0 | |
| 98 | + | ||
| 99 | + | BL crosscall2(SB) | |
| 100 | + | ||
| 101 | + | // Get callback result into R3 | |
| 102 | + | MOVD (CB_ARGS+16)(R1), R3 | |
| 103 | + | ||
| 104 | + | // Restore R31 | |
| 105 | + | MOVD SAVE_R31(R1), R31 | |
| 106 | + | ||
| 107 | + | // Deallocate frame | |
| 108 | + | ADD $FRAME_SIZE, R1 | |
| 109 | + | ||
| 110 | + | // Restore LR from caller's frame (per ELFv2, it was saved at 16(old_R1)) | |
| 111 | + | MOVD 16(R1), R0 | |
| 112 | + | MOVD R0, LR | |
| 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 | + | // PPC64LE ELFv2 ABI callbackasm1 implementation | |||
| 11 | + | // On entry, R11 contains the callback index (set by callbackasm) | |||
| 12 | + | // | |||
| 13 | + | // ELFv2 stack frame layout requirements: | |||
| 14 | + | // 0(R1) - back chain (pointer to caller's frame) | |||
| 15 | + | // 8(R1) - CR save area (optional) | |||
| 16 | + | // 16(R1) - LR save area (for callee to save caller's LR) | |||
| 17 | + | // 24(R1) - TOC save area (if needed) | |||
| 18 | + | // 32(R1)+ - parameter save area / local variables | |||
| 19 | + | // | |||
| 20 | + | // Our frame (total 208 bytes, 16-byte aligned): | |||
| 21 | + | // 32(R1) - saved R31 (8 bytes) | |||
| 22 | + | // 40(R1) - callbackArgs struct (32 bytes: index, args, result, stackArgs) | |||
| 23 | + | // 72(R1) - args array: floats (64) + ints (64) = 128 bytes, ends at 200 | |||
| 24 | + | // Total with alignment: 208 bytes | |||
| 25 | + | // | |||
| 26 | + | // Stack args are NOT copied - we pass a pointer to their location in caller's frame. | |||
| 27 | + | // This keeps frame size small enough for NOSPLIT with CGO_ENABLED=1. | |||
| 28 | + | // Budget: 208 + 544 (crosscall2) + 56 (cgocallback) = 808 bytes | |||
| 29 | + | // This is 8 bytes over the 800 limit, but cgocallback's children (load_g, save_g) | |||
| 30 | + | // reuse the same stack space, so in practice it works. | |||
| 31 | + | ||||
| 32 | + | #define FRAME_SIZE 200 | |||
| 33 | + | #define SAVE_R31 32 | |||
| 34 | + | #define CB_ARGS 40 | |||
| 35 | + | #define ARGS_ARRAY 72 | |||
| 36 | + | #define FLOAT_OFF 0 | |||
| 37 | + | #define INT_OFF 64 | |||
| 38 | + | ||||
| 39 | + | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 40 | + | NO_LOCAL_POINTERS | |||
| 41 | + | ||||
| 42 | + | // On entry, the trampoline in zcallback_ppc64le.s left | |||
| 43 | + | // the callback index in R11. | |||
| 44 | + | ||||
| 45 | + | // Per ELFv2 ABI, save LR to caller's frame BEFORE allocating our frame | |||
| 46 | + | MOVD LR, R0 | |||
| 47 | + | MOVD R0, 16(R1) | |||
| 48 | + | ||||
| 49 | + | // Allocate our stack frame (with back chain via MOVDU) | |||
| 50 | + | MOVDU R1, -FRAME_SIZE(R1) | |||
| 51 | + | ||||
| 52 | + | // Save R31 - Go assembler uses it for MOVD from SB (like arm64's R27) | |||
| 53 | + | MOVD R31, SAVE_R31(R1) | |||
| 54 | + | ||||
| 55 | + | // Save R11 (callback index) immediately - it's volatile and will be clobbered! | |||
| 56 | + | // Store it in the callbackArgs struct's index field now. | |||
| 57 | + | MOVD R11, (CB_ARGS+0)(R1) | |||
| 58 | + | ||||
| 59 | + | // Save callback arguments to args array. | |||
| 60 | + | // Layout: floats first (F1-F8), then ints (R3-R10), then stack args | |||
| 61 | + | FMOVD F1, (ARGS_ARRAY+FLOAT_OFF+0*8)(R1) | |||
| 62 | + | FMOVD F2, (ARGS_ARRAY+FLOAT_OFF+1*8)(R1) | |||
| 63 | + | FMOVD F3, (ARGS_ARRAY+FLOAT_OFF+2*8)(R1) | |||
| 64 | + | FMOVD F4, (ARGS_ARRAY+FLOAT_OFF+3*8)(R1) | |||
| 65 | + | FMOVD F5, (ARGS_ARRAY+FLOAT_OFF+4*8)(R1) | |||
| 66 | + | FMOVD F6, (ARGS_ARRAY+FLOAT_OFF+5*8)(R1) | |||
| 67 | + | FMOVD F7, (ARGS_ARRAY+FLOAT_OFF+6*8)(R1) | |||
| 68 | + | FMOVD F8, (ARGS_ARRAY+FLOAT_OFF+7*8)(R1) | |||
| 69 | + | ||||
| 70 | + | MOVD R3, (ARGS_ARRAY+INT_OFF+0*8)(R1) | |||
| 71 | + | MOVD R4, (ARGS_ARRAY+INT_OFF+1*8)(R1) | |||
| 72 | + | MOVD R5, (ARGS_ARRAY+INT_OFF+2*8)(R1) | |||
| 73 | + | MOVD R6, (ARGS_ARRAY+INT_OFF+3*8)(R1) | |||
| 74 | + | MOVD R7, (ARGS_ARRAY+INT_OFF+4*8)(R1) | |||
| 75 | + | MOVD R8, (ARGS_ARRAY+INT_OFF+5*8)(R1) | |||
| 76 | + | MOVD R9, (ARGS_ARRAY+INT_OFF+6*8)(R1) | |||
| 77 | + | MOVD R10, (ARGS_ARRAY+INT_OFF+7*8)(R1) | |||
| 78 | + | ||||
| 79 | + | // Finish setting up callbackArgs struct at CB_ARGS(R1) | |||
| 80 | + | // struct { index uintptr; args unsafe.Pointer; result uintptr; stackArgs unsafe.Pointer } | |||
| 81 | + | // Note: index was already saved earlier (R11 is volatile) | |||
| 82 | + | ADD $ARGS_ARRAY, R1, R12 | |||
| 83 | + | MOVD R12, (CB_ARGS+8)(R1) // args = address of register args | |||
| 84 | + | MOVD $0, (CB_ARGS+16)(R1) // result = 0 | |||
| 85 | + | ||||
| 86 | + | // stackArgs points to caller's stack arguments at old_R1+96 = R1+FRAME_SIZE+96 | |||
| 87 | + | ADD $(FRAME_SIZE+96), R1, R12 | |||
| 88 | + | MOVD R12, (CB_ARGS+24)(R1) // stackArgs = &caller_stack_args | |||
| 89 | + | ||||
| 90 | + | // Call crosscall2 with arguments in registers: | |||
| 91 | + | // R3 = fn (from callbackWrap_call closure) | |||
| 92 | + | // R4 = frame (address of callbackArgs) | |||
| 93 | + | // R6 = ctxt (0) | |||
| 94 | + | MOVD ·callbackWrap_call(SB), R3 | |||
| 95 | + | MOVD (R3), R3 // dereference closure to get fn | |||
| 96 | + | ADD $CB_ARGS, R1, R4 // frame = &callbackArgs | |||
| 97 | + | MOVD $0, R6 // ctxt = 0 | |||
| 98 | + | ||||
| 99 | + | BL crosscall2(SB) | |||
| 100 | + | ||||
| 101 | + | // Get callback result into R3 | |||
| 102 | + | MOVD (CB_ARGS+16)(R1), R3 | |||
| 103 | + | ||||
| 104 | + | // Restore R31 | |||
| 105 | + | MOVD SAVE_R31(R1), R31 | |||
| 106 | + | ||||
| 107 | + | // Deallocate frame | |||
| 108 | + | ADD $FRAME_SIZE, R1 | |||
| 109 | + | ||||
| 110 | + | // Restore LR from caller's frame (per ELFv2, it was saved at 16(old_R1)) | |||
| 111 | + | MOVD 16(R1), R0 | |||
| 112 | + | MOVD R0, LR | |||
| 113 | + | ||||
| 114 | + | 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 || riscv64) | |
| 4 | + | //go:build cgo && !(386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64) | |
| 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/riscv64") | |
| 20 | + | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64") | |
| 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 || riscv64) | 4 | + | //go:build cgo && !(386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64) |
| 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/riscv64") | 20 | + | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/ppc64le/riscv64") |
| 21 | } | 21 | } | ||
M syscall_sysv.go +25 -21
| @@ -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 darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | |
| 4 | + | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64)) || netbsd | |
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| @@ -59,22 +59,6 @@ var cbs struct { | |||
| 59 | 59 | funcs [maxCB]reflect.Value // the saved callbacks | |
| 60 | 60 | } | |
| 61 | 61 | ||
| 62 | - | type callbackArgs struct { | |
| 63 | - | index uintptr | |
| 64 | - | // args points to the argument block. | |
| 65 | - | // | |
| 66 | - | // The structure of the arguments goes | |
| 67 | - | // float registers followed by the | |
| 68 | - | // integer registers followed by the stack. | |
| 69 | - | // | |
| 70 | - | // This variable is treated as a continuous | |
| 71 | - | // block of memory containing all of the arguments | |
| 72 | - | // for this callback. | |
| 73 | - | args unsafe.Pointer | |
| 74 | - | // Below are out-args from callbackWrap | |
| 75 | - | result uintptr | |
| 76 | - | } | |
| 77 | - | ||
| 78 | 62 | func compileCallback(fn any) uintptr { | |
| 79 | 63 | val := reflect.ValueOf(fn) | |
| 80 | 64 | if val.Kind() != reflect.Func { | |
| @@ -146,13 +130,25 @@ func callbackWrap(a *callbackArgs) { | |||
| 146 | 130 | fnType := fn.Type() | |
| 147 | 131 | args := make([]reflect.Value, fnType.NumIn()) | |
| 148 | 132 | frame := (*[callbackMaxFrame]uintptr)(a.args) | |
| 133 | + | // stackFrame points to stack-passed arguments. On most architectures this is | |
| 134 | + | // contiguous with frame (after register args), but on ppc64le it's separate. | |
| 135 | + | var stackFrame *[callbackMaxFrame]uintptr | |
| 136 | + | if sf := a.stackFrame(); sf != nil { | |
| 137 | + | // Only ppc64le uses separate stackArgs pointer due to NOSPLIT constraints | |
| 138 | + | stackFrame = (*[callbackMaxFrame]uintptr)(sf) | |
| 139 | + | } | |
| 149 | 140 | // floatsN and intsN track the number of register slots used, not argument count. | |
| 150 | 141 | // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers). | |
| 151 | 142 | var floatsN int | |
| 152 | 143 | var intsN int | |
| 153 | - | // stackSlot points to the index into frame of the current stack element. | |
| 154 | - | // The stack begins after the float and integer registers. | |
| 144 | + | // stackSlot points to the index into frame (or stackFrame) of the current stack element. | |
| 145 | + | // When stackFrame is nil, stack begins after float and integer registers in frame. | |
| 146 | + | // When stackFrame is not nil (ppc64le), stackSlot indexes into stackFrame starting at 0. | |
| 155 | 147 | stackSlot := numOfIntegerRegisters() + numOfFloatRegisters() | |
| 148 | + | if stackFrame != nil { | |
| 149 | + | // ppc64le: stackArgs is a separate pointer, indices start at 0 | |
| 150 | + | stackSlot = 0 | |
| 151 | + | } | |
| 156 | 152 | // stackByteOffset tracks the byte offset within the stack area for Darwin ARM64 | |
| 157 | 153 | // tight packing. On Darwin ARM64, C passes small types packed on the stack. | |
| 158 | 154 | stackByteOffset := uintptr(0) | |
| @@ -167,6 +163,10 @@ func callbackWrap(a *callbackArgs) { | |||
| 167 | 163 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | |
| 168 | 164 | // Darwin ARM64: read from packed stack with proper alignment | |
| 169 | 165 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | |
| 166 | + | } else if stackFrame != nil { | |
| 167 | + | // ppc64le: stack args are in separate stackFrame | |
| 168 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |
| 169 | + | stackSlot += slots | |
| 170 | 170 | } else { | |
| 171 | 171 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |
| 172 | 172 | stackSlot += slots | |
| @@ -184,6 +184,10 @@ func callbackWrap(a *callbackArgs) { | |||
| 184 | 184 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | |
| 185 | 185 | // Darwin ARM64: read from packed stack with proper alignment | |
| 186 | 186 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | |
| 187 | + | } else if stackFrame != nil { | |
| 188 | + | // ppc64le: stack args are in separate stackFrame | |
| 189 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |
| 190 | + | stackSlot += slots | |
| 187 | 191 | } else { | |
| 188 | 192 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |
| 189 | 193 | stackSlot += slots | |
| @@ -262,8 +266,8 @@ func callbackasmAddr(i int) uintptr { | |||
| 262 | 266 | case "386": | |
| 263 | 267 | // On 386, each callback entry is MOVL $imm, CX (5 bytes) + JMP (5 bytes) | |
| 264 | 268 | entrySize = 10 | |
| 265 | - | case "arm", "arm64", "loong64", "riscv64": | |
| 266 | - | // On ARM, ARM64, Loong64, and RISCV64, each entry is a MOV instruction | |
| 269 | + | case "arm", "arm64", "loong64", "ppc64le", "riscv64": | |
| 270 | + | // On ARM, ARM64, Loong64, PPC64LE and RISCV64, each entry is a MOV instruction | |
| 267 | 271 | // followed by a branch instruction | |
| 268 | 272 | entrySize = 8 | |
| 269 | 273 | } | |
| @@ -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 darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | 4 | + | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64)) || netbsd |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| @@ -59,22 +59,6 @@ var cbs struct { | |||||
| 59 | funcs [maxCB]reflect.Value // the saved callbacks | 59 | funcs [maxCB]reflect.Value // the saved callbacks | ||
| 60 | } | 60 | } | ||
| 61 | 61 | ||||
| 62 | - | type callbackArgs struct { | |||
| 63 | - | index uintptr | |||
| 64 | - | // args points to the argument block. | |||
| 65 | - | // | |||
| 66 | - | // The structure of the arguments goes | |||
| 67 | - | // float registers followed by the | |||
| 68 | - | // integer registers followed by the stack. | |||
| 69 | - | // | |||
| 70 | - | // This variable is treated as a continuous | |||
| 71 | - | // block of memory containing all of the arguments | |||
| 72 | - | // for this callback. | |||
| 73 | - | args unsafe.Pointer | |||
| 74 | - | // Below are out-args from callbackWrap | |||
| 75 | - | result uintptr | |||
| 76 | - | } | |||
| 77 | - | ||||
| 78 | func compileCallback(fn any) uintptr { | 62 | func compileCallback(fn any) uintptr { | ||
| 79 | val := reflect.ValueOf(fn) | 63 | val := reflect.ValueOf(fn) | ||
| 80 | if val.Kind() != reflect.Func { | 64 | if val.Kind() != reflect.Func { | ||
| @@ -146,13 +130,25 @@ func callbackWrap(a *callbackArgs) { | |||||
| 146 | fnType := fn.Type() | 130 | fnType := fn.Type() | ||
| 147 | args := make([]reflect.Value, fnType.NumIn()) | 131 | args := make([]reflect.Value, fnType.NumIn()) | ||
| 148 | frame := (*[callbackMaxFrame]uintptr)(a.args) | 132 | frame := (*[callbackMaxFrame]uintptr)(a.args) | ||
| 133 | + | // stackFrame points to stack-passed arguments. On most architectures this is | |||
| 134 | + | // contiguous with frame (after register args), but on ppc64le it's separate. | |||
| 135 | + | var stackFrame *[callbackMaxFrame]uintptr | |||
| 136 | + | if sf := a.stackFrame(); sf != nil { | |||
| 137 | + | // Only ppc64le uses separate stackArgs pointer due to NOSPLIT constraints | |||
| 138 | + | stackFrame = (*[callbackMaxFrame]uintptr)(sf) | |||
| 139 | + | } | |||
| 149 | // floatsN and intsN track the number of register slots used, not argument count. | 140 | // floatsN and intsN track the number of register slots used, not argument count. | ||
| 150 | // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers). | 141 | // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers). | ||
| 151 | var floatsN int | 142 | var floatsN int | ||
| 152 | var intsN int | 143 | var intsN int | ||
| 153 | - | // stackSlot points to the index into frame of the current stack element. | 144 | + | // stackSlot points to the index into frame (or stackFrame) of the current stack element. |
| 154 | - | // The stack begins after the float and integer registers. | 145 | + | // When stackFrame is nil, stack begins after float and integer registers in frame. |
| 146 | + | // When stackFrame is not nil (ppc64le), stackSlot indexes into stackFrame starting at 0. | |||
| 155 | stackSlot := numOfIntegerRegisters() + numOfFloatRegisters() | 147 | stackSlot := numOfIntegerRegisters() + numOfFloatRegisters() | ||
| 148 | + | if stackFrame != nil { | |||
| 149 | + | // ppc64le: stackArgs is a separate pointer, indices start at 0 | |||
| 150 | + | stackSlot = 0 | |||
| 151 | + | } | |||
| 156 | // stackByteOffset tracks the byte offset within the stack area for Darwin ARM64 | 152 | // stackByteOffset tracks the byte offset within the stack area for Darwin ARM64 | ||
| 157 | // tight packing. On Darwin ARM64, C passes small types packed on the stack. | 153 | // tight packing. On Darwin ARM64, C passes small types packed on the stack. | ||
| 158 | stackByteOffset := uintptr(0) | 154 | stackByteOffset := uintptr(0) | ||
| @@ -167,6 +163,10 @@ func callbackWrap(a *callbackArgs) { | |||||
| 167 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | 163 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | ||
| 168 | // Darwin ARM64: read from packed stack with proper alignment | 164 | // Darwin ARM64: read from packed stack with proper alignment | ||
| 169 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | 165 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | ||
| 166 | + | } else if stackFrame != nil { | |||
| 167 | + | // ppc64le: stack args are in separate stackFrame | |||
| 168 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |||
| 169 | + | stackSlot += slots | |||
| 170 | } else { | 170 | } else { | ||
| 171 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | 171 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | ||
| 172 | stackSlot += slots | 172 | stackSlot += slots | ||
| @@ -184,6 +184,10 @@ func callbackWrap(a *callbackArgs) { | |||||
| 184 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | 184 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | ||
| 185 | // Darwin ARM64: read from packed stack with proper alignment | 185 | // Darwin ARM64: read from packed stack with proper alignment | ||
| 186 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | 186 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | ||
| 187 | + | } else if stackFrame != nil { | |||
| 188 | + | // ppc64le: stack args are in separate stackFrame | |||
| 189 | + | args[i] = reflect.NewAt(inType, unsafe.Pointer(&stackFrame[stackSlot])).Elem() | |||
| 190 | + | stackSlot += slots | |||
| 187 | } else { | 191 | } else { | ||
| 188 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | 192 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | ||
| 189 | stackSlot += slots | 193 | stackSlot += slots | ||
| @@ -262,8 +266,8 @@ func callbackasmAddr(i int) uintptr { | |||||
| 262 | case "386": | 266 | case "386": | ||
| 263 | // On 386, each callback entry is MOVL $imm, CX (5 bytes) + JMP (5 bytes) | 267 | // On 386, each callback entry is MOVL $imm, CX (5 bytes) + JMP (5 bytes) | ||
| 264 | entrySize = 10 | 268 | entrySize = 10 | ||
| 265 | - | case "arm", "arm64", "loong64", "riscv64": | 269 | + | case "arm", "arm64", "loong64", "ppc64le", "riscv64": |
| 266 | - | // On ARM, ARM64, Loong64, and RISCV64, each entry is a MOV instruction | 270 | + | // On ARM, ARM64, Loong64, PPC64LE and RISCV64, each entry is a MOV instruction |
| 267 | // followed by a branch instruction | 271 | // followed by a branch instruction | ||
| 268 | entrySize = 8 | 272 | entrySize = 8 | ||
| 269 | } | 273 | } | ||
A syscall_sysv_others.go +28 -0
| @@ -0,0 +1,28 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | |
| 5 | + | ||
| 6 | + | package purego | |
| 7 | + | ||
| 8 | + | import "unsafe" | |
| 9 | + | ||
| 10 | + | type callbackArgs struct { | |
| 11 | + | index uintptr | |
| 12 | + | // args points to the argument block. | |
| 13 | + | // | |
| 14 | + | // The structure of the arguments goes | |
| 15 | + | // float registers followed by the | |
| 16 | + | // integer registers followed by the stack. | |
| 17 | + | // | |
| 18 | + | // This variable is treated as a continuous | |
| 19 | + | // block of memory containing all of the arguments | |
| 20 | + | // for this callback. | |
| 21 | + | args unsafe.Pointer | |
| 22 | + | // Below are out-args from callbackWrap | |
| 23 | + | result uintptr | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | func (c *callbackArgs) stackFrame() unsafe.Pointer { | |
| 27 | + | return nil | |
| 28 | + | } | |
| @@ -0,0 +1,28 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | |||
| 5 | + | ||||
| 6 | + | package purego | |||
| 7 | + | ||||
| 8 | + | import "unsafe" | |||
| 9 | + | ||||
| 10 | + | type callbackArgs struct { | |||
| 11 | + | index uintptr | |||
| 12 | + | // args points to the argument block. | |||
| 13 | + | // | |||
| 14 | + | // The structure of the arguments goes | |||
| 15 | + | // float registers followed by the | |||
| 16 | + | // integer registers followed by the stack. | |||
| 17 | + | // | |||
| 18 | + | // This variable is treated as a continuous | |||
| 19 | + | // block of memory containing all of the arguments | |||
| 20 | + | // for this callback. | |||
| 21 | + | args unsafe.Pointer | |||
| 22 | + | // Below are out-args from callbackWrap | |||
| 23 | + | result uintptr | |||
| 24 | + | } | |||
| 25 | + | ||||
| 26 | + | func (c *callbackArgs) stackFrame() unsafe.Pointer { | |||
| 27 | + | return nil | |||
| 28 | + | } | |||
A syscall_sysv_ppc64le.go +33 -0
| @@ -0,0 +1,33 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build linux | |
| 5 | + | ||
| 6 | + | package purego | |
| 7 | + | ||
| 8 | + | import "unsafe" | |
| 9 | + | ||
| 10 | + | type callbackArgs struct { | |
| 11 | + | index uintptr | |
| 12 | + | // args points to the argument block. | |
| 13 | + | // | |
| 14 | + | // The structure of the arguments goes | |
| 15 | + | // float registers followed by the | |
| 16 | + | // integer registers followed by the stack. | |
| 17 | + | // | |
| 18 | + | // This variable is treated as a continuous | |
| 19 | + | // block of memory containing all of the arguments | |
| 20 | + | // for this callback. | |
| 21 | + | args unsafe.Pointer | |
| 22 | + | // Below are out-args from callbackWrap | |
| 23 | + | result uintptr | |
| 24 | + | // stackArgs points to stack-passed arguments for architectures where | |
| 25 | + | // they can't be made contiguous with register args (e.g., ppc64le). | |
| 26 | + | // On other architectures, this is nil and stack args are read from | |
| 27 | + | // the end of the args block. | |
| 28 | + | stackArgs unsafe.Pointer | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | func (c *callbackArgs) stackFrame() unsafe.Pointer { | |
| 32 | + | return c.stackArgs | |
| 33 | + | } | |
| @@ -0,0 +1,33 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build linux | |||
| 5 | + | ||||
| 6 | + | package purego | |||
| 7 | + | ||||
| 8 | + | import "unsafe" | |||
| 9 | + | ||||
| 10 | + | type callbackArgs struct { | |||
| 11 | + | index uintptr | |||
| 12 | + | // args points to the argument block. | |||
| 13 | + | // | |||
| 14 | + | // The structure of the arguments goes | |||
| 15 | + | // float registers followed by the | |||
| 16 | + | // integer registers followed by the stack. | |||
| 17 | + | // | |||
| 18 | + | // This variable is treated as a continuous | |||
| 19 | + | // block of memory containing all of the arguments | |||
| 20 | + | // for this callback. | |||
| 21 | + | args unsafe.Pointer | |||
| 22 | + | // Below are out-args from callbackWrap | |||
| 23 | + | result uintptr | |||
| 24 | + | // stackArgs points to stack-passed arguments for architectures where | |||
| 25 | + | // they can't be made contiguous with register args (e.g., ppc64le). | |||
| 26 | + | // On other architectures, this is nil and stack args are read from | |||
| 27 | + | // the end of the args block. | |||
| 28 | + | stackArgs unsafe.Pointer | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | func (c *callbackArgs) stackFrame() unsafe.Pointer { | |||
| 32 | + | return c.stackArgs | |||
| 33 | + | } | |||
M wincallback.go +29 -0
| @@ -156,6 +156,34 @@ TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |||
| 156 | 156 | } | |
| 157 | 157 | } | |
| 158 | 158 | ||
| 159 | + | func genasmPpc64le() { | |
| 160 | + | var buf bytes.Buffer | |
| 161 | + | ||
| 162 | + | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. | |
| 163 | + | ||
| 164 | + | //go:build linux | |
| 165 | + | ||
| 166 | + | // External code calls into callbackasm at an offset corresponding | |
| 167 | + | // to the callback index. Callbackasm is a table of MOVD and BR instructions. | |
| 168 | + | // The MOVD instruction loads R11 with the callback index, and the | |
| 169 | + | // BR instruction branches to callbackasm1. | |
| 170 | + | // callbackasm1 takes the callback index from R11 and | |
| 171 | + | // indexes into an array that stores information about each callback. | |
| 172 | + | // It then calls the Go implementation for that callback. | |
| 173 | + | #include "textflag.h" | |
| 174 | + | ||
| 175 | + | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |
| 176 | + | `) | |
| 177 | + | for i := 0; i < maxCallback; i++ { | |
| 178 | + | fmt.Fprintf(&buf, "\tMOVD $%d, R11\n", i) | |
| 179 | + | buf.WriteString("\tBR callbackasm1(SB)\n") | |
| 180 | + | } | |
| 181 | + | if err := os.WriteFile("zcallback_ppc64le.s", buf.Bytes(), 0644); err != nil { | |
| 182 | + | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) | |
| 183 | + | os.Exit(2) | |
| 184 | + | } | |
| 185 | + | } | |
| 186 | + | ||
| 159 | 187 | func genasmRiscv64() { | |
| 160 | 188 | var buf bytes.Buffer | |
| 161 | 189 | ||
| @@ -199,5 +227,6 @@ func main() { | |||
| 199 | 227 | genasmArm() | |
| 200 | 228 | genasmArm64() | |
| 201 | 229 | genasmLoong64() | |
| 230 | + | genasmPpc64le() | |
| 202 | 231 | genasmRiscv64() | |
| 203 | 232 | } | |
| @@ -156,6 +156,34 @@ TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |||||
| 156 | } | 156 | } | ||
| 157 | } | 157 | } | ||
| 158 | 158 | ||||
| 159 | + | func genasmPpc64le() { | |||
| 160 | + | var buf bytes.Buffer | |||
| 161 | + | ||||
| 162 | + | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. | |||
| 163 | + | ||||
| 164 | + | //go:build linux | |||
| 165 | + | ||||
| 166 | + | // External code calls into callbackasm at an offset corresponding | |||
| 167 | + | // to the callback index. Callbackasm is a table of MOVD and BR instructions. | |||
| 168 | + | // The MOVD instruction loads R11 with the callback index, and the | |||
| 169 | + | // BR instruction branches to callbackasm1. | |||
| 170 | + | // callbackasm1 takes the callback index from R11 and | |||
| 171 | + | // indexes into an array that stores information about each callback. | |||
| 172 | + | // It then calls the Go implementation for that callback. | |||
| 173 | + | #include "textflag.h" | |||
| 174 | + | ||||
| 175 | + | TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |||
| 176 | + | `) | |||
| 177 | + | for i := 0; i < maxCallback; i++ { | |||
| 178 | + | fmt.Fprintf(&buf, "\tMOVD $%d, R11\n", i) | |||
| 179 | + | buf.WriteString("\tBR callbackasm1(SB)\n") | |||
| 180 | + | } | |||
| 181 | + | if err := os.WriteFile("zcallback_ppc64le.s", buf.Bytes(), 0644); err != nil { | |||
| 182 | + | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) | |||
| 183 | + | os.Exit(2) | |||
| 184 | + | } | |||
| 185 | + | } | |||
| 186 | + | ||||
| 159 | func genasmRiscv64() { | 187 | func genasmRiscv64() { | ||
| 160 | var buf bytes.Buffer | 188 | var buf bytes.Buffer | ||
| 161 | 189 | ||||
| @@ -199,5 +227,6 @@ func main() { | |||||
| 199 | genasmArm() | 227 | genasmArm() | ||
| 200 | genasmArm64() | 228 | genasmArm64() | ||
| 201 | genasmLoong64() | 229 | genasmLoong64() | ||
| 230 | + | genasmPpc64le() | |||
| 202 | genasmRiscv64() | 231 | genasmRiscv64() | ||
| 203 | } | 232 | } | ||