purego: add support for riscv64 (#385)
This change adds the neccessary code logic to support RISCV64 with both CGO enabled/disabled. Closes #382
- commit
- 6f34d839a741ceeab45dd7fb86b9f8fc17eb318d
- author
- George Adams · · 2026-01-21 18:42 UTC
- committer
- GitHub · · 2026-01-21 18:42 UTC
- parent
- ef2baba
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
16 files · +6552 -20
M .github/workflows/test.yml +16 -0
| @@ -120,6 +120,22 @@ jobs: | |||
| 120 | 120 | sudo apt-get install g++-multilib | |
| 121 | 121 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... | |
| 122 | 122 | ||
| 123 | + | - name: go test (Linux riscv64) | |
| 124 | + | 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.') }} | |
| 125 | + | run: | | |
| 126 | + | sudo apt-get update | |
| 127 | + | sudo apt-get install -y gcc-riscv64-linux-gnu g++-riscv64-linux-gnu qemu-user | |
| 128 | + | go env -w CC=riscv64-linux-gnu-gcc | |
| 129 | + | go env -w CXX=riscv64-linux-gnu-g++ | |
| 130 | + | # Create symlink for the dynamic linker that QEMU expects | |
| 131 | + | sudo ln -sf /usr/riscv64-linux-gnu/lib/ld-linux-riscv64-lp64d.so.1 /lib/ld.so.1 | |
| 132 | + | env GOOS=linux GOARCH=riscv64 CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |
| 133 | + | env QEMU_LD_PREFIX=/usr/riscv64-linux-gnu qemu-riscv64 ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |
| 134 | + | env GOOS=linux GOARCH=riscv64 CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |
| 135 | + | env QEMU_LD_PREFIX=/usr/riscv64-linux-gnu qemu-riscv64 ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |
| 136 | + | go env -u CC | |
| 137 | + | go env -u CXX | |
| 138 | + | ||
| 123 | 139 | - name: go test race (no Cgo) | |
| 124 | 140 | if: ${{ runner.os == 'macOS' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }} | |
| 125 | 141 | run: | | |
| @@ -120,6 +120,22 @@ jobs: | |||||
| 120 | sudo apt-get install g++-multilib | 120 | sudo apt-get install g++-multilib | ||
| 121 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... | 121 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... | ||
| 122 | 122 | ||||
| 123 | + | - name: go test (Linux riscv64) | |||
| 124 | + | 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.') }} | |||
| 125 | + | run: | | |||
| 126 | + | sudo apt-get update | |||
| 127 | + | sudo apt-get install -y gcc-riscv64-linux-gnu g++-riscv64-linux-gnu qemu-user | |||
| 128 | + | go env -w CC=riscv64-linux-gnu-gcc | |||
| 129 | + | go env -w CXX=riscv64-linux-gnu-g++ | |||
| 130 | + | # Create symlink for the dynamic linker that QEMU expects | |||
| 131 | + | sudo ln -sf /usr/riscv64-linux-gnu/lib/ld-linux-riscv64-lp64d.so.1 /lib/ld.so.1 | |||
| 132 | + | env GOOS=linux GOARCH=riscv64 CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |||
| 133 | + | env QEMU_LD_PREFIX=/usr/riscv64-linux-gnu qemu-riscv64 ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |||
| 134 | + | env GOOS=linux GOARCH=riscv64 CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |||
| 135 | + | env QEMU_LD_PREFIX=/usr/riscv64-linux-gnu qemu-riscv64 ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |||
| 136 | + | go env -u CC | |||
| 137 | + | go env -u CXX | |||
| 138 | + | ||||
| 123 | - name: go test race (no Cgo) | 139 | - name: go test race (no Cgo) | ||
| 124 | if: ${{ runner.os == 'macOS' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }} | 140 | if: ${{ runner.os == 'macOS' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }} | ||
| 125 | run: | | 141 | 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, arm | |
| 44 | 44 | - **FreeBSD**: amd64, arm64 | |
| 45 | - | - **Linux**: 386, arm, loong64 | |
| 45 | + | - **Linux**: 386, arm, loong64, riscv64 | |
| 46 | 46 | - **Windows**: 386*, arm* | |
| 47 | 47 | ||
| 48 | 48 | `*` These architectures only support `SyscallN` and `NewCallback` | |
| @@ -42,7 +42,7 @@ Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs o | |||||
| 42 | 42 | ||||
| 43 | - **Android**: 386, arm | 43 | - **Android**: 386, arm | ||
| 44 | - **FreeBSD**: amd64, arm64 | 44 | - **FreeBSD**: amd64, arm64 | ||
| 45 | - | - **Linux**: 386, arm, loong64 | 45 | + | - **Linux**: 386, arm, loong64, riscv64 |
| 46 | - **Windows**: 386*, arm* | 46 | - **Windows**: 386*, arm* | ||
| 47 | 47 | ||||
| 48 | `*` These architectures only support `SyscallN` and `NewCallback` | 48 | `*` These architectures only support `SyscallN` and `NewCallback` | ||
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 && (amd64 || arm64 || loong64)) | |
| 4 | + | //go:build darwin || (linux && (amd64 || arm64 || loong64 || 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 && (amd64 || arm64 || loong64)) | 4 | + | //go:build darwin || (linux && (amd64 || arm64 || loong64 || riscv64)) |
| 5 | 5 | ||||
| 6 | package purego_test | 6 | package purego_test | ||
| 7 | 7 | ||||
M func.go +7 -7
| @@ -132,7 +132,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 132 | 132 | panic("purego: cfn is nil") | |
| 133 | 133 | } | |
| 134 | 134 | if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && | |
| 135 | - | runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | |
| 135 | + | runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 136 | 136 | panic("purego: float returns are not supported") | |
| 137 | 137 | } | |
| 138 | 138 | { | |
| @@ -281,7 +281,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 281 | 281 | var arm64_r8 uintptr | |
| 282 | 282 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 283 | 283 | outType := ty.Out(0) | |
| 284 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64") && outType.Size() > maxRegAllocStructSize { | |
| 284 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64") && outType.Size() > maxRegAllocStructSize { | |
| 285 | 285 | val := reflect.New(outType) | |
| 286 | 286 | keepAlive = append(keepAlive, val) | |
| 287 | 287 | addInt(val.Pointer()) | |
| @@ -323,7 +323,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 323 | 323 | syscall := thePool.Get().(*syscall15Args) | |
| 324 | 324 | defer thePool.Put(syscall) | |
| 325 | 325 | ||
| 326 | - | if runtime.GOARCH == "loong64" { | |
| 326 | + | if runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64" { | |
| 327 | 327 | *syscall = syscall15Args{ | |
| 328 | 328 | cfn, | |
| 329 | 329 | sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], | |
| @@ -490,7 +490,7 @@ func roundUpTo8(val uintptr) uintptr { | |||
| 490 | 490 | ||
| 491 | 491 | func numOfIntegerRegisters() int { | |
| 492 | 492 | switch runtime.GOARCH { | |
| 493 | - | case "arm64", "loong64": | |
| 493 | + | case "arm64", "loong64", "riscv64": | |
| 494 | 494 | return 8 | |
| 495 | 495 | case "amd64": | |
| 496 | 496 | return 6 | |
| @@ -537,9 +537,9 @@ func estimateStackBytes(ty reflect.Type) int { | |||
| 537 | 537 | // tight packing as normal C function calls. | |
| 538 | 538 | func isCallbackFunction(cfn uintptr) bool { | |
| 539 | 539 | // Only platforms with syscall_sysv.go have callback detection. | |
| 540 | - | // Match the build constraint: darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd | |
| 540 | + | // Match the build constraint: darwin || freebsd || (linux && (amd64 || arm64 || loong64 || riscv64)) || netbsd | |
| 541 | 541 | hasSyscallSysv := runtime.GOOS == "darwin" || runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" || | |
| 542 | - | (runtime.GOOS == "linux" && (runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" || runtime.GOARCH == "loong64")) | |
| 542 | + | (runtime.GOOS == "linux" && (runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64")) | |
| 543 | 543 | if !hasSyscallSysv { | |
| 544 | 544 | return false | |
| 545 | 545 | } | |
| @@ -549,7 +549,7 @@ func isCallbackFunction(cfn uintptr) bool { | |||
| 549 | 549 | switch runtime.GOARCH { | |
| 550 | 550 | case "386", "amd64": | |
| 551 | 551 | entrySize = 5 | |
| 552 | - | case "arm", "arm64", "loong64": | |
| 552 | + | case "arm", "arm64", "loong64", "riscv64": | |
| 553 | 553 | entrySize = 8 | |
| 554 | 554 | default: | |
| 555 | 555 | return false | |
| @@ -132,7 +132,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 132 | panic("purego: cfn is nil") | 132 | panic("purego: cfn is nil") | ||
| 133 | } | 133 | } | ||
| 134 | if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && | 134 | if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && | ||
| 135 | - | runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | 135 | + | runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { |
| 136 | panic("purego: float returns are not supported") | 136 | panic("purego: float returns are not supported") | ||
| 137 | } | 137 | } | ||
| 138 | { | 138 | { | ||
| @@ -281,7 +281,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 281 | var arm64_r8 uintptr | 281 | var arm64_r8 uintptr | ||
| 282 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | 282 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | ||
| 283 | outType := ty.Out(0) | 283 | outType := ty.Out(0) | ||
| 284 | - | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64") && outType.Size() > maxRegAllocStructSize { | 284 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64") && outType.Size() > maxRegAllocStructSize { |
| 285 | val := reflect.New(outType) | 285 | val := reflect.New(outType) | ||
| 286 | keepAlive = append(keepAlive, val) | 286 | keepAlive = append(keepAlive, val) | ||
| 287 | addInt(val.Pointer()) | 287 | addInt(val.Pointer()) | ||
| @@ -323,7 +323,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 323 | syscall := thePool.Get().(*syscall15Args) | 323 | syscall := thePool.Get().(*syscall15Args) | ||
| 324 | defer thePool.Put(syscall) | 324 | defer thePool.Put(syscall) | ||
| 325 | 325 | ||||
| 326 | - | if runtime.GOARCH == "loong64" { | 326 | + | if runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64" { |
| 327 | *syscall = syscall15Args{ | 327 | *syscall = syscall15Args{ | ||
| 328 | cfn, | 328 | cfn, | ||
| 329 | sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], | 329 | sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], | ||
| @@ -490,7 +490,7 @@ func roundUpTo8(val uintptr) uintptr { | |||||
| 490 | 490 | ||||
| 491 | func numOfIntegerRegisters() int { | 491 | func numOfIntegerRegisters() int { | ||
| 492 | switch runtime.GOARCH { | 492 | switch runtime.GOARCH { | ||
| 493 | - | case "arm64", "loong64": | 493 | + | case "arm64", "loong64", "riscv64": |
| 494 | return 8 | 494 | return 8 | ||
| 495 | case "amd64": | 495 | case "amd64": | ||
| 496 | return 6 | 496 | return 6 | ||
| @@ -537,9 +537,9 @@ func estimateStackBytes(ty reflect.Type) int { | |||||
| 537 | // tight packing as normal C function calls. | 537 | // tight packing as normal C function calls. | ||
| 538 | func isCallbackFunction(cfn uintptr) bool { | 538 | func isCallbackFunction(cfn uintptr) bool { | ||
| 539 | // Only platforms with syscall_sysv.go have callback detection. | 539 | // Only platforms with syscall_sysv.go have callback detection. | ||
| 540 | - | // Match the build constraint: darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd | 540 | + | // Match the build constraint: darwin || freebsd || (linux && (amd64 || arm64 || loong64 || riscv64)) || netbsd |
| 541 | hasSyscallSysv := runtime.GOOS == "darwin" || runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" || | 541 | hasSyscallSysv := runtime.GOOS == "darwin" || runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" || | ||
| 542 | - | (runtime.GOOS == "linux" && (runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" || runtime.GOARCH == "loong64")) | 542 | + | (runtime.GOOS == "linux" && (runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64")) |
| 543 | if !hasSyscallSysv { | 543 | if !hasSyscallSysv { | ||
| 544 | return false | 544 | return false | ||
| 545 | } | 545 | } | ||
| @@ -549,7 +549,7 @@ func isCallbackFunction(cfn uintptr) bool { | |||||
| 549 | switch runtime.GOARCH { | 549 | switch runtime.GOARCH { | ||
| 550 | case "386", "amd64": | 550 | case "386", "amd64": | ||
| 551 | entrySize = 5 | 551 | entrySize = 5 | ||
| 552 | - | case "arm", "arm64", "loong64": | 552 | + | case "arm", "arm64", "loong64", "riscv64": |
| 553 | entrySize = 8 | 553 | entrySize = 8 | ||
| 554 | default: | 554 | default: | ||
| 555 | return false | 555 | return false | ||
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 != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | |
| 53 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && 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 != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | |
| 82 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 83 | 83 | t.Skip("Platform doesn't support Floats") | |
| 84 | 84 | return | |
| 85 | 85 | } | |
| @@ -118,7 +118,7 @@ func TestRegisterFunc_Floats(t *testing.T) { | |||
| 118 | 118 | } | |
| 119 | 119 | ||
| 120 | 120 | func TestRegisterLibFunc_Bool(t *testing.T) { | |
| 121 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | |
| 121 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 122 | 122 | t.Skip("Platform doesn't support callbacks") | |
| 123 | 123 | return | |
| 124 | 124 | } | |
| @@ -373,7 +373,7 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||
| 373 | 373 | if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") { | |
| 374 | 374 | t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking") | |
| 375 | 375 | } | |
| 376 | - | if tt.name == "10_float32" && (runtime.GOARCH == "386" || runtime.GOARCH == "arm" || runtime.GOARCH == "loong64") { | |
| 376 | + | if tt.name == "10_float32" && (runtime.GOARCH == "386" || runtime.GOARCH == "arm" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64") { | |
| 377 | 377 | t.Skip("float32 stack arguments not yet supported on this platform") | |
| 378 | 378 | } | |
| 379 | 379 | // 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 != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | 53 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && 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 != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | 82 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && 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 | } | ||
| @@ -118,7 +118,7 @@ func TestRegisterFunc_Floats(t *testing.T) { | |||||
| 118 | } | 118 | } | ||
| 119 | 119 | ||||
| 120 | func TestRegisterLibFunc_Bool(t *testing.T) { | 120 | func TestRegisterLibFunc_Bool(t *testing.T) { | ||
| 121 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | 121 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { |
| 122 | t.Skip("Platform doesn't support callbacks") | 122 | t.Skip("Platform doesn't support callbacks") | ||
| 123 | return | 123 | return | ||
| 124 | } | 124 | } | ||
| @@ -373,7 +373,7 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||||
| 373 | if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") { | 373 | if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") { | ||
| 374 | t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking") | 374 | t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking") | ||
| 375 | } | 375 | } | ||
| 376 | - | if tt.name == "10_float32" && (runtime.GOARCH == "386" || runtime.GOARCH == "arm" || runtime.GOARCH == "loong64") { | 376 | + | if tt.name == "10_float32" && (runtime.GOARCH == "386" || runtime.GOARCH == "arm" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64") { |
| 377 | t.Skip("float32 stack arguments not yet supported on this platform") | 377 | t.Skip("float32 stack arguments not yet supported on this platform") | ||
| 378 | } | 378 | } | ||
| 379 | // Struct tests require Darwin ARM64 or AMD64 | 379 | // 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 && !(arm64 || amd64 || loong64)) || netbsd | |
| 4 | + | //go:build freebsd || (linux && !(arm64 || amd64 || loong64 || 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 && !(arm64 || amd64 || loong64)) || netbsd | 4 | + | //go:build freebsd || (linux && !(arm64 || amd64 || loong64 || riscv64)) || netbsd |
| 5 | 5 | ||||
| 6 | package cgo | 6 | package cgo | ||
| 7 | 7 | ||||
A internal/fakecgo/asm_riscv64.s +78 -0
| @@ -0,0 +1,78 @@ | |||
| 1 | + | // Copyright 2020 The Go Authors. All rights reserved. | |
| 2 | + | // Use of this source code is governed by a BSD-style | |
| 3 | + | // license that can be found in the LICENSE file. | |
| 4 | + | ||
| 5 | + | #include "textflag.h" | |
| 6 | + | ||
| 7 | + | // Called by C code generated by cmd/cgo. | |
| 8 | + | // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) | |
| 9 | + | // Saves C callee-saved registers and calls cgocallback with three arguments. | |
| 10 | + | // fn is the PC of a func(a unsafe.Pointer) function. | |
| 11 | + | TEXT crosscall2(SB),NOSPLIT|NOFRAME,$0 | |
| 12 | + | /* | |
| 13 | + | * Push arguments for fn (X10, X11, X13), along with all callee-save | |
| 14 | + | * registers. Note that at procedure entry the first argument is at | |
| 15 | + | * 8(X2). | |
| 16 | + | */ | |
| 17 | + | ADD $(-8*29), X2 | |
| 18 | + | MOV X10, (8*1)(X2) // fn unsafe.Pointer | |
| 19 | + | MOV X11, (8*2)(X2) // a unsafe.Pointer | |
| 20 | + | MOV X13, (8*3)(X2) // ctxt uintptr | |
| 21 | + | MOV X8, (8*4)(X2) | |
| 22 | + | MOV X9, (8*5)(X2) | |
| 23 | + | MOV X18, (8*6)(X2) | |
| 24 | + | MOV X19, (8*7)(X2) | |
| 25 | + | MOV X20, (8*8)(X2) | |
| 26 | + | MOV X21, (8*9)(X2) | |
| 27 | + | MOV X22, (8*10)(X2) | |
| 28 | + | MOV X23, (8*11)(X2) | |
| 29 | + | MOV X24, (8*12)(X2) | |
| 30 | + | MOV X25, (8*13)(X2) | |
| 31 | + | MOV X26, (8*14)(X2) | |
| 32 | + | MOV g, (8*15)(X2) | |
| 33 | + | MOV X1, (8*16)(X2) | |
| 34 | + | MOVD F8, (8*17)(X2) | |
| 35 | + | MOVD F9, (8*18)(X2) | |
| 36 | + | MOVD F18, (8*19)(X2) | |
| 37 | + | MOVD F19, (8*20)(X2) | |
| 38 | + | MOVD F20, (8*21)(X2) | |
| 39 | + | MOVD F21, (8*22)(X2) | |
| 40 | + | MOVD F22, (8*23)(X2) | |
| 41 | + | MOVD F23, (8*24)(X2) | |
| 42 | + | MOVD F24, (8*25)(X2) | |
| 43 | + | MOVD F25, (8*26)(X2) | |
| 44 | + | MOVD F26, (8*27)(X2) | |
| 45 | + | MOVD F27, (8*28)(X2) | |
| 46 | + | ||
| 47 | + | // Initialize Go ABI environment | |
| 48 | + | CALL runtime·load_g(SB) | |
| 49 | + | CALL runtime·cgocallback(SB) | |
| 50 | + | ||
| 51 | + | MOV (8*4)(X2), X8 | |
| 52 | + | MOV (8*5)(X2), X9 | |
| 53 | + | MOV (8*6)(X2), X18 | |
| 54 | + | MOV (8*7)(X2), X19 | |
| 55 | + | MOV (8*8)(X2), X20 | |
| 56 | + | MOV (8*9)(X2), X21 | |
| 57 | + | MOV (8*10)(X2), X22 | |
| 58 | + | MOV (8*11)(X2), X23 | |
| 59 | + | MOV (8*12)(X2), X24 | |
| 60 | + | MOV (8*13)(X2), X25 | |
| 61 | + | MOV (8*14)(X2), X26 | |
| 62 | + | MOV (8*15)(X2), g | |
| 63 | + | MOV (8*16)(X2), X1 | |
| 64 | + | MOVD (8*17)(X2), F8 | |
| 65 | + | MOVD (8*18)(X2), F9 | |
| 66 | + | MOVD (8*19)(X2), F18 | |
| 67 | + | MOVD (8*20)(X2), F19 | |
| 68 | + | MOVD (8*21)(X2), F20 | |
| 69 | + | MOVD (8*22)(X2), F21 | |
| 70 | + | MOVD (8*23)(X2), F22 | |
| 71 | + | MOVD (8*24)(X2), F23 | |
| 72 | + | MOVD (8*25)(X2), F24 | |
| 73 | + | MOVD (8*26)(X2), F25 | |
| 74 | + | MOVD (8*27)(X2), F26 | |
| 75 | + | MOVD (8*28)(X2), F27 | |
| 76 | + | ADD $(8*29), X2 | |
| 77 | + | ||
| 78 | + | RET | |
| @@ -0,0 +1,78 @@ | |||||
| 1 | + | // Copyright 2020 The Go Authors. All rights reserved. | |||
| 2 | + | // Use of this source code is governed by a BSD-style | |||
| 3 | + | // license that can be found in the LICENSE file. | |||
| 4 | + | ||||
| 5 | + | #include "textflag.h" | |||
| 6 | + | ||||
| 7 | + | // Called by C code generated by cmd/cgo. | |||
| 8 | + | // func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr) | |||
| 9 | + | // Saves C callee-saved registers and calls cgocallback with three arguments. | |||
| 10 | + | // fn is the PC of a func(a unsafe.Pointer) function. | |||
| 11 | + | TEXT crosscall2(SB),NOSPLIT|NOFRAME,$0 | |||
| 12 | + | /* | |||
| 13 | + | * Push arguments for fn (X10, X11, X13), along with all callee-save | |||
| 14 | + | * registers. Note that at procedure entry the first argument is at | |||
| 15 | + | * 8(X2). | |||
| 16 | + | */ | |||
| 17 | + | ADD $(-8*29), X2 | |||
| 18 | + | MOV X10, (8*1)(X2) // fn unsafe.Pointer | |||
| 19 | + | MOV X11, (8*2)(X2) // a unsafe.Pointer | |||
| 20 | + | MOV X13, (8*3)(X2) // ctxt uintptr | |||
| 21 | + | MOV X8, (8*4)(X2) | |||
| 22 | + | MOV X9, (8*5)(X2) | |||
| 23 | + | MOV X18, (8*6)(X2) | |||
| 24 | + | MOV X19, (8*7)(X2) | |||
| 25 | + | MOV X20, (8*8)(X2) | |||
| 26 | + | MOV X21, (8*9)(X2) | |||
| 27 | + | MOV X22, (8*10)(X2) | |||
| 28 | + | MOV X23, (8*11)(X2) | |||
| 29 | + | MOV X24, (8*12)(X2) | |||
| 30 | + | MOV X25, (8*13)(X2) | |||
| 31 | + | MOV X26, (8*14)(X2) | |||
| 32 | + | MOV g, (8*15)(X2) | |||
| 33 | + | MOV X1, (8*16)(X2) | |||
| 34 | + | MOVD F8, (8*17)(X2) | |||
| 35 | + | MOVD F9, (8*18)(X2) | |||
| 36 | + | MOVD F18, (8*19)(X2) | |||
| 37 | + | MOVD F19, (8*20)(X2) | |||
| 38 | + | MOVD F20, (8*21)(X2) | |||
| 39 | + | MOVD F21, (8*22)(X2) | |||
| 40 | + | MOVD F22, (8*23)(X2) | |||
| 41 | + | MOVD F23, (8*24)(X2) | |||
| 42 | + | MOVD F24, (8*25)(X2) | |||
| 43 | + | MOVD F25, (8*26)(X2) | |||
| 44 | + | MOVD F26, (8*27)(X2) | |||
| 45 | + | MOVD F27, (8*28)(X2) | |||
| 46 | + | ||||
| 47 | + | // Initialize Go ABI environment | |||
| 48 | + | CALL runtime·load_g(SB) | |||
| 49 | + | CALL runtime·cgocallback(SB) | |||
| 50 | + | ||||
| 51 | + | MOV (8*4)(X2), X8 | |||
| 52 | + | MOV (8*5)(X2), X9 | |||
| 53 | + | MOV (8*6)(X2), X18 | |||
| 54 | + | MOV (8*7)(X2), X19 | |||
| 55 | + | MOV (8*8)(X2), X20 | |||
| 56 | + | MOV (8*9)(X2), X21 | |||
| 57 | + | MOV (8*10)(X2), X22 | |||
| 58 | + | MOV (8*11)(X2), X23 | |||
| 59 | + | MOV (8*12)(X2), X24 | |||
| 60 | + | MOV (8*13)(X2), X25 | |||
| 61 | + | MOV (8*14)(X2), X26 | |||
| 62 | + | MOV (8*15)(X2), g | |||
| 63 | + | MOV (8*16)(X2), X1 | |||
| 64 | + | MOVD (8*17)(X2), F8 | |||
| 65 | + | MOVD (8*18)(X2), F9 | |||
| 66 | + | MOVD (8*19)(X2), F18 | |||
| 67 | + | MOVD (8*20)(X2), F19 | |||
| 68 | + | MOVD (8*21)(X2), F20 | |||
| 69 | + | MOVD (8*22)(X2), F21 | |||
| 70 | + | MOVD (8*23)(X2), F22 | |||
| 71 | + | MOVD (8*24)(X2), F23 | |||
| 72 | + | MOVD (8*25)(X2), F24 | |||
| 73 | + | MOVD (8*26)(X2), F25 | |||
| 74 | + | MOVD (8*27)(X2), F26 | |||
| 75 | + | MOVD (8*28)(X2), F27 | |||
| 76 | + | ADD $(8*29), X2 | |||
| 77 | + | ||||
| 78 | + | RET | |||
A internal/fakecgo/trampolines_riscv64.s +71 -0
| @@ -0,0 +1,71 @@ | |||
| 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 gcc ABI to Go ABI and then calls into the Go equivalent functions. | |
| 10 | + | ||
| 11 | + | TEXT x_cgo_init_trampoline(SB), NOSPLIT, $16 | |
| 12 | + | MOV X10, 8(SP) | |
| 13 | + | MOV X11, 16(SP) | |
| 14 | + | MOV ·x_cgo_init_call(SB), X5 | |
| 15 | + | MOV (X5), X6 | |
| 16 | + | JALR RA, 0(X6) | |
| 17 | + | RET | |
| 18 | + | ||
| 19 | + | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8 | |
| 20 | + | MOV X10, 8(SP) | |
| 21 | + | MOV ·x_cgo_thread_start_call(SB), X5 | |
| 22 | + | MOV (X5), X6 | |
| 23 | + | JALR RA, 0(X6) | |
| 24 | + | RET | |
| 25 | + | ||
| 26 | + | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8 | |
| 27 | + | MOV X10, 8(SP) | |
| 28 | + | MOV ·x_cgo_setenv_call(SB), X5 | |
| 29 | + | MOV (X5), X6 | |
| 30 | + | JALR RA, 0(X6) | |
| 31 | + | RET | |
| 32 | + | ||
| 33 | + | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8 | |
| 34 | + | MOV X10, 8(SP) | |
| 35 | + | MOV ·x_cgo_unsetenv_call(SB), X5 | |
| 36 | + | MOV (X5), X6 | |
| 37 | + | JALR RA, 0(X6) | |
| 38 | + | RET | |
| 39 | + | ||
| 40 | + | TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0 | |
| 41 | + | CALL ·x_cgo_notify_runtime_init_done(SB) | |
| 42 | + | RET | |
| 43 | + | ||
| 44 | + | TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 | |
| 45 | + | CALL ·x_cgo_bindm(SB) | |
| 46 | + | RET | |
| 47 | + | ||
| 48 | + | // func setg_trampoline(setg uintptr, g uintptr) | |
| 49 | + | TEXT ·setg_trampoline(SB), NOSPLIT, $0 | |
| 50 | + | MOV gp+8(FP), X10 | |
| 51 | + | MOV setg+0(FP), X5 | |
| 52 | + | JALR RA, 0(X5) | |
| 53 | + | RET | |
| 54 | + | ||
| 55 | + | TEXT threadentry_trampoline(SB), NOSPLIT, $16 | |
| 56 | + | MOV X10, 8(SP) | |
| 57 | + | MOV ·threadentry_call(SB), X5 | |
| 58 | + | MOV (X5), X6 | |
| 59 | + | JALR RA, 0(X6) | |
| 60 | + | RET | |
| 61 | + | ||
| 62 | + | TEXT ·call5(SB), NOSPLIT, $0-48 | |
| 63 | + | MOV fn+0(FP), X9 | |
| 64 | + | MOV a1+8(FP), X10 | |
| 65 | + | MOV a2+16(FP), X11 | |
| 66 | + | MOV a3+24(FP), X12 | |
| 67 | + | MOV a4+32(FP), X13 | |
| 68 | + | MOV a5+40(FP), X14 | |
| 69 | + | JALR RA, 0(X9) | |
| 70 | + | MOV X10, ret+48(FP) | |
| 71 | + | RET | |
| @@ -0,0 +1,71 @@ | |||||
| 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 gcc ABI to Go ABI and then calls into the Go equivalent functions. | |||
| 10 | + | ||||
| 11 | + | TEXT x_cgo_init_trampoline(SB), NOSPLIT, $16 | |||
| 12 | + | MOV X10, 8(SP) | |||
| 13 | + | MOV X11, 16(SP) | |||
| 14 | + | MOV ·x_cgo_init_call(SB), X5 | |||
| 15 | + | MOV (X5), X6 | |||
| 16 | + | JALR RA, 0(X6) | |||
| 17 | + | RET | |||
| 18 | + | ||||
| 19 | + | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8 | |||
| 20 | + | MOV X10, 8(SP) | |||
| 21 | + | MOV ·x_cgo_thread_start_call(SB), X5 | |||
| 22 | + | MOV (X5), X6 | |||
| 23 | + | JALR RA, 0(X6) | |||
| 24 | + | RET | |||
| 25 | + | ||||
| 26 | + | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8 | |||
| 27 | + | MOV X10, 8(SP) | |||
| 28 | + | MOV ·x_cgo_setenv_call(SB), X5 | |||
| 29 | + | MOV (X5), X6 | |||
| 30 | + | JALR RA, 0(X6) | |||
| 31 | + | RET | |||
| 32 | + | ||||
| 33 | + | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8 | |||
| 34 | + | MOV X10, 8(SP) | |||
| 35 | + | MOV ·x_cgo_unsetenv_call(SB), X5 | |||
| 36 | + | MOV (X5), X6 | |||
| 37 | + | JALR RA, 0(X6) | |||
| 38 | + | RET | |||
| 39 | + | ||||
| 40 | + | TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0 | |||
| 41 | + | CALL ·x_cgo_notify_runtime_init_done(SB) | |||
| 42 | + | RET | |||
| 43 | + | ||||
| 44 | + | TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 | |||
| 45 | + | CALL ·x_cgo_bindm(SB) | |||
| 46 | + | RET | |||
| 47 | + | ||||
| 48 | + | // func setg_trampoline(setg uintptr, g uintptr) | |||
| 49 | + | TEXT ·setg_trampoline(SB), NOSPLIT, $0 | |||
| 50 | + | MOV gp+8(FP), X10 | |||
| 51 | + | MOV setg+0(FP), X5 | |||
| 52 | + | JALR RA, 0(X5) | |||
| 53 | + | RET | |||
| 54 | + | ||||
| 55 | + | TEXT threadentry_trampoline(SB), NOSPLIT, $16 | |||
| 56 | + | MOV X10, 8(SP) | |||
| 57 | + | MOV ·threadentry_call(SB), X5 | |||
| 58 | + | MOV (X5), X6 | |||
| 59 | + | JALR RA, 0(X6) | |||
| 60 | + | RET | |||
| 61 | + | ||||
| 62 | + | TEXT ·call5(SB), NOSPLIT, $0-48 | |||
| 63 | + | MOV fn+0(FP), X9 | |||
| 64 | + | MOV a1+8(FP), X10 | |||
| 65 | + | MOV a2+16(FP), X11 | |||
| 66 | + | MOV a3+24(FP), X12 | |||
| 67 | + | MOV a4+32(FP), X13 | |||
| 68 | + | MOV a5+40(FP), X14 | |||
| 69 | + | JALR RA, 0(X9) | |||
| 70 | + | MOV X10, ret+48(FP) | |||
| 71 | + | RET | |||
A struct_riscv64.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 are NaN-boxed in FP regs; use low 32 bits only | |
| 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 placeSmallAggregateRISCV64(v, addFloat, addInt, keepAlive) | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | return placeStack(v, keepAlive, addInt) | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | func placeSmallAggregateRISCV64( | |
| 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 | + | // RISCV64 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 RISCV64") | |
| 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 are NaN-boxed in FP regs; use low 32 bits only | |||
| 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 placeSmallAggregateRISCV64(v, addFloat, addInt, keepAlive) | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | return placeStack(v, keepAlive, addInt) | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | func placeSmallAggregateRISCV64( | |||
| 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 | + | // RISCV64 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 RISCV64") | |||
| 143 | + | } | |||
A sys_riscv64.s +101 -0
| @@ -0,0 +1,101 @@ | |||
| 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 | + | // Stack usage: | |
| 11 | + | // 0(SP) - 56(SP): stack args a9-a15 (7 * 8 bytes = 56) | |
| 12 | + | // 56(SP) - 64(SP): saved RA (x1) | |
| 13 | + | // 64(SP) - 72(SP): saved X9 (s1) | |
| 14 | + | // 72(SP) - 80(SP): saved X18 (s2) | |
| 15 | + | // 80(SP) - 88(SP): saved args pointer (original X10) | |
| 16 | + | // 88(SP) - 96(SP): padding | |
| 17 | + | #define STACK_SIZE 96 | |
| 18 | + | #define SAVE_RA 56 | |
| 19 | + | #define SAVE_X9 64 | |
| 20 | + | #define SAVE_X18 72 | |
| 21 | + | #define SAVE_ARGP 80 | |
| 22 | + | ||
| 23 | + | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |
| 24 | + | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |
| 25 | + | ||
| 26 | + | TEXT syscall15X(SB), NOSPLIT, $0 | |
| 27 | + | // Allocate stack frame (keeps 16-byte alignment) | |
| 28 | + | SUB $STACK_SIZE, SP | |
| 29 | + | ||
| 30 | + | // Save callee-saved regs we clobber + return address | |
| 31 | + | MOV X1, SAVE_RA(SP) | |
| 32 | + | MOV X9, SAVE_X9(SP) | |
| 33 | + | MOV X18, SAVE_X18(SP) | |
| 34 | + | ||
| 35 | + | // Save original args pointer (in a0/X10) | |
| 36 | + | MOV X10, SAVE_ARGP(SP) | |
| 37 | + | ||
| 38 | + | // X9 := args pointer (syscall15Args*) | |
| 39 | + | MOV X10, X9 | |
| 40 | + | ||
| 41 | + | // Load float args into fa0-fa7 (F10-F17) | |
| 42 | + | MOVD syscall15Args_f1(X9), F10 | |
| 43 | + | MOVD syscall15Args_f2(X9), F11 | |
| 44 | + | MOVD syscall15Args_f3(X9), F12 | |
| 45 | + | MOVD syscall15Args_f4(X9), F13 | |
| 46 | + | MOVD syscall15Args_f5(X9), F14 | |
| 47 | + | MOVD syscall15Args_f6(X9), F15 | |
| 48 | + | MOVD syscall15Args_f7(X9), F16 | |
| 49 | + | MOVD syscall15Args_f8(X9), F17 | |
| 50 | + | ||
| 51 | + | // Load integer args into a0-a7 (X10-X17) | |
| 52 | + | MOV syscall15Args_a1(X9), X10 | |
| 53 | + | MOV syscall15Args_a2(X9), X11 | |
| 54 | + | MOV syscall15Args_a3(X9), X12 | |
| 55 | + | MOV syscall15Args_a4(X9), X13 | |
| 56 | + | MOV syscall15Args_a5(X9), X14 | |
| 57 | + | MOV syscall15Args_a6(X9), X15 | |
| 58 | + | MOV syscall15Args_a7(X9), X16 | |
| 59 | + | MOV syscall15Args_a8(X9), X17 | |
| 60 | + | ||
| 61 | + | // Spill a9-a15 onto the stack (C ABI) | |
| 62 | + | MOV syscall15Args_a9(X9), X18 | |
| 63 | + | MOV X18, 0(SP) | |
| 64 | + | MOV syscall15Args_a10(X9), X18 | |
| 65 | + | MOV X18, 8(SP) | |
| 66 | + | MOV syscall15Args_a11(X9), X18 | |
| 67 | + | MOV X18, 16(SP) | |
| 68 | + | MOV syscall15Args_a12(X9), X18 | |
| 69 | + | MOV X18, 24(SP) | |
| 70 | + | MOV syscall15Args_a13(X9), X18 | |
| 71 | + | MOV X18, 32(SP) | |
| 72 | + | MOV syscall15Args_a14(X9), X18 | |
| 73 | + | MOV X18, 40(SP) | |
| 74 | + | MOV syscall15Args_a15(X9), X18 | |
| 75 | + | MOV X18, 48(SP) | |
| 76 | + | ||
| 77 | + | // Call fn | |
| 78 | + | // IMPORTANT: preserve RA across this call (we saved it above) | |
| 79 | + | MOV syscall15Args_fn(X9), X18 | |
| 80 | + | JALR RA, X18 | |
| 81 | + | ||
| 82 | + | // Restore args pointer (syscall15Args*) for storing results | |
| 83 | + | MOV SAVE_ARGP(SP), X9 | |
| 84 | + | ||
| 85 | + | // Store results back | |
| 86 | + | MOV X10, syscall15Args_a1(X9) | |
| 87 | + | MOV X11, syscall15Args_a2(X9) | |
| 88 | + | ||
| 89 | + | // Store back float return regs if used by your ABI contract | |
| 90 | + | MOVD F10, syscall15Args_f1(X9) | |
| 91 | + | MOVD F11, syscall15Args_f2(X9) | |
| 92 | + | MOVD F12, syscall15Args_f3(X9) | |
| 93 | + | MOVD F13, syscall15Args_f4(X9) | |
| 94 | + | ||
| 95 | + | // Restore callee-saved regs and return address | |
| 96 | + | MOV SAVE_X18(SP), X18 | |
| 97 | + | MOV SAVE_X9(SP), X9 | |
| 98 | + | MOV SAVE_RA(SP), X1 | |
| 99 | + | ||
| 100 | + | ADD $STACK_SIZE, SP | |
| 101 | + | RET | |
| @@ -0,0 +1,101 @@ | |||||
| 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 | + | // Stack usage: | |||
| 11 | + | // 0(SP) - 56(SP): stack args a9-a15 (7 * 8 bytes = 56) | |||
| 12 | + | // 56(SP) - 64(SP): saved RA (x1) | |||
| 13 | + | // 64(SP) - 72(SP): saved X9 (s1) | |||
| 14 | + | // 72(SP) - 80(SP): saved X18 (s2) | |||
| 15 | + | // 80(SP) - 88(SP): saved args pointer (original X10) | |||
| 16 | + | // 88(SP) - 96(SP): padding | |||
| 17 | + | #define STACK_SIZE 96 | |||
| 18 | + | #define SAVE_RA 56 | |||
| 19 | + | #define SAVE_X9 64 | |||
| 20 | + | #define SAVE_X18 72 | |||
| 21 | + | #define SAVE_ARGP 80 | |||
| 22 | + | ||||
| 23 | + | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |||
| 24 | + | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |||
| 25 | + | ||||
| 26 | + | TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 27 | + | // Allocate stack frame (keeps 16-byte alignment) | |||
| 28 | + | SUB $STACK_SIZE, SP | |||
| 29 | + | ||||
| 30 | + | // Save callee-saved regs we clobber + return address | |||
| 31 | + | MOV X1, SAVE_RA(SP) | |||
| 32 | + | MOV X9, SAVE_X9(SP) | |||
| 33 | + | MOV X18, SAVE_X18(SP) | |||
| 34 | + | ||||
| 35 | + | // Save original args pointer (in a0/X10) | |||
| 36 | + | MOV X10, SAVE_ARGP(SP) | |||
| 37 | + | ||||
| 38 | + | // X9 := args pointer (syscall15Args*) | |||
| 39 | + | MOV X10, X9 | |||
| 40 | + | ||||
| 41 | + | // Load float args into fa0-fa7 (F10-F17) | |||
| 42 | + | MOVD syscall15Args_f1(X9), F10 | |||
| 43 | + | MOVD syscall15Args_f2(X9), F11 | |||
| 44 | + | MOVD syscall15Args_f3(X9), F12 | |||
| 45 | + | MOVD syscall15Args_f4(X9), F13 | |||
| 46 | + | MOVD syscall15Args_f5(X9), F14 | |||
| 47 | + | MOVD syscall15Args_f6(X9), F15 | |||
| 48 | + | MOVD syscall15Args_f7(X9), F16 | |||
| 49 | + | MOVD syscall15Args_f8(X9), F17 | |||
| 50 | + | ||||
| 51 | + | // Load integer args into a0-a7 (X10-X17) | |||
| 52 | + | MOV syscall15Args_a1(X9), X10 | |||
| 53 | + | MOV syscall15Args_a2(X9), X11 | |||
| 54 | + | MOV syscall15Args_a3(X9), X12 | |||
| 55 | + | MOV syscall15Args_a4(X9), X13 | |||
| 56 | + | MOV syscall15Args_a5(X9), X14 | |||
| 57 | + | MOV syscall15Args_a6(X9), X15 | |||
| 58 | + | MOV syscall15Args_a7(X9), X16 | |||
| 59 | + | MOV syscall15Args_a8(X9), X17 | |||
| 60 | + | ||||
| 61 | + | // Spill a9-a15 onto the stack (C ABI) | |||
| 62 | + | MOV syscall15Args_a9(X9), X18 | |||
| 63 | + | MOV X18, 0(SP) | |||
| 64 | + | MOV syscall15Args_a10(X9), X18 | |||
| 65 | + | MOV X18, 8(SP) | |||
| 66 | + | MOV syscall15Args_a11(X9), X18 | |||
| 67 | + | MOV X18, 16(SP) | |||
| 68 | + | MOV syscall15Args_a12(X9), X18 | |||
| 69 | + | MOV X18, 24(SP) | |||
| 70 | + | MOV syscall15Args_a13(X9), X18 | |||
| 71 | + | MOV X18, 32(SP) | |||
| 72 | + | MOV syscall15Args_a14(X9), X18 | |||
| 73 | + | MOV X18, 40(SP) | |||
| 74 | + | MOV syscall15Args_a15(X9), X18 | |||
| 75 | + | MOV X18, 48(SP) | |||
| 76 | + | ||||
| 77 | + | // Call fn | |||
| 78 | + | // IMPORTANT: preserve RA across this call (we saved it above) | |||
| 79 | + | MOV syscall15Args_fn(X9), X18 | |||
| 80 | + | JALR RA, X18 | |||
| 81 | + | ||||
| 82 | + | // Restore args pointer (syscall15Args*) for storing results | |||
| 83 | + | MOV SAVE_ARGP(SP), X9 | |||
| 84 | + | ||||
| 85 | + | // Store results back | |||
| 86 | + | MOV X10, syscall15Args_a1(X9) | |||
| 87 | + | MOV X11, syscall15Args_a2(X9) | |||
| 88 | + | ||||
| 89 | + | // Store back float return regs if used by your ABI contract | |||
| 90 | + | MOVD F10, syscall15Args_f1(X9) | |||
| 91 | + | MOVD F11, syscall15Args_f2(X9) | |||
| 92 | + | MOVD F12, syscall15Args_f3(X9) | |||
| 93 | + | MOVD F13, syscall15Args_f4(X9) | |||
| 94 | + | ||||
| 95 | + | // Restore callee-saved regs and return address | |||
| 96 | + | MOV SAVE_X18(SP), X18 | |||
| 97 | + | MOV SAVE_X9(SP), X9 | |||
| 98 | + | MOV SAVE_RA(SP), X1 | |||
| 99 | + | ||||
| 100 | + | ADD $STACK_SIZE, SP | |||
| 101 | + | RET | |||
A sys_unix_riscv64.s +79 -0
| @@ -0,0 +1,79 @@ | |||
| 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 | + | TEXT callbackasm1(SB), NOFRAME, $0 | |
| 11 | + | NO_LOCAL_POINTERS | |
| 12 | + | ||
| 13 | + | // On entry, the trampoline in zcallback_riscv64.s left | |
| 14 | + | // the callback index in X7. | |
| 15 | + | ||
| 16 | + | // Save callback register arguments X10-X17 and F10-F17. | |
| 17 | + | // Stack args (if any) are at 0(SP), 8(SP), etc. | |
| 18 | + | // We save register args at SP-128, making them contiguous with stack args. | |
| 19 | + | ADD $-(16*8), SP, X6 | |
| 20 | + | ||
| 21 | + | // Save float arg regs fa0-fa7 (F10-F17) | |
| 22 | + | MOVD F10, 0(X6) | |
| 23 | + | MOVD F11, 8(X6) | |
| 24 | + | MOVD F12, 16(X6) | |
| 25 | + | MOVD F13, 24(X6) | |
| 26 | + | MOVD F14, 32(X6) | |
| 27 | + | MOVD F15, 40(X6) | |
| 28 | + | MOVD F16, 48(X6) | |
| 29 | + | MOVD F17, 56(X6) | |
| 30 | + | ||
| 31 | + | // Save integer arg regs a0-a7 (X10-X17) | |
| 32 | + | MOV X10, 64(X6) | |
| 33 | + | MOV X11, 72(X6) | |
| 34 | + | MOV X12, 80(X6) | |
| 35 | + | MOV X13, 88(X6) | |
| 36 | + | MOV X14, 96(X6) | |
| 37 | + | MOV X15, 104(X6) | |
| 38 | + | MOV X16, 112(X6) | |
| 39 | + | MOV X17, 120(X6) | |
| 40 | + | ||
| 41 | + | // Allocate space on stack for RA, saved regs, and callbackArgs. | |
| 42 | + | // We need: 8 (RA) + 8 (X9 callee-saved) + 24 (callbackArgs) = 40, round to 176 (22*8) | |
| 43 | + | // to match loong64 and ensure we don't overlap with saved register args. | |
| 44 | + | // The saved regs end at SP-8 (original), so we need new SP below SP-128. | |
| 45 | + | ADD $-(22*8), SP | |
| 46 | + | ||
| 47 | + | // Save link register (RA/X1) and callee-saved register X9 | |
| 48 | + | // (X9 is used by the assembler for some instructions) | |
| 49 | + | MOV X1, 0(SP) | |
| 50 | + | MOV X9, 8(SP) | |
| 51 | + | ||
| 52 | + | // Create a struct callbackArgs on our stack. | |
| 53 | + | // callbackArgs struct: index(0), args(8), result(16) | |
| 54 | + | // Place it at 16(SP) to avoid overlap | |
| 55 | + | ADD $16, SP, X9 | |
| 56 | + | MOV X7, 0(X9) // callback index | |
| 57 | + | MOV X6, 8(X9) // address of args vector | |
| 58 | + | MOV X0, 16(X9) // result = 0 | |
| 59 | + | ||
| 60 | + | // Call crosscall2 with arguments in registers | |
| 61 | + | MOV ·callbackWrap_call(SB), X10 // Get the ABIInternal function pointer | |
| 62 | + | MOV (X10), X10 // without <ABIInternal> by using a closure. X10 = fn | |
| 63 | + | MOV X9, X11 // X11 = frame (address of callbackArgs) | |
| 64 | + | MOV X0, X13 // X13 = ctxt = 0 | |
| 65 | + | ||
| 66 | + | CALL crosscall2(SB) | |
| 67 | + | ||
| 68 | + | // Get callback result. | |
| 69 | + | ADD $16, SP, X9 | |
| 70 | + | MOV 16(X9), X10 | |
| 71 | + | ||
| 72 | + | // Restore link register and callee-saved X9 | |
| 73 | + | MOV 8(SP), X9 | |
| 74 | + | MOV 0(SP), X1 | |
| 75 | + | ||
| 76 | + | // Restore stack pointer | |
| 77 | + | ADD $(22*8), SP | |
| 78 | + | ||
| 79 | + | RET | |
| @@ -0,0 +1,79 @@ | |||||
| 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 | + | TEXT callbackasm1(SB), NOFRAME, $0 | |||
| 11 | + | NO_LOCAL_POINTERS | |||
| 12 | + | ||||
| 13 | + | // On entry, the trampoline in zcallback_riscv64.s left | |||
| 14 | + | // the callback index in X7. | |||
| 15 | + | ||||
| 16 | + | // Save callback register arguments X10-X17 and F10-F17. | |||
| 17 | + | // Stack args (if any) are at 0(SP), 8(SP), etc. | |||
| 18 | + | // We save register args at SP-128, making them contiguous with stack args. | |||
| 19 | + | ADD $-(16*8), SP, X6 | |||
| 20 | + | ||||
| 21 | + | // Save float arg regs fa0-fa7 (F10-F17) | |||
| 22 | + | MOVD F10, 0(X6) | |||
| 23 | + | MOVD F11, 8(X6) | |||
| 24 | + | MOVD F12, 16(X6) | |||
| 25 | + | MOVD F13, 24(X6) | |||
| 26 | + | MOVD F14, 32(X6) | |||
| 27 | + | MOVD F15, 40(X6) | |||
| 28 | + | MOVD F16, 48(X6) | |||
| 29 | + | MOVD F17, 56(X6) | |||
| 30 | + | ||||
| 31 | + | // Save integer arg regs a0-a7 (X10-X17) | |||
| 32 | + | MOV X10, 64(X6) | |||
| 33 | + | MOV X11, 72(X6) | |||
| 34 | + | MOV X12, 80(X6) | |||
| 35 | + | MOV X13, 88(X6) | |||
| 36 | + | MOV X14, 96(X6) | |||
| 37 | + | MOV X15, 104(X6) | |||
| 38 | + | MOV X16, 112(X6) | |||
| 39 | + | MOV X17, 120(X6) | |||
| 40 | + | ||||
| 41 | + | // Allocate space on stack for RA, saved regs, and callbackArgs. | |||
| 42 | + | // We need: 8 (RA) + 8 (X9 callee-saved) + 24 (callbackArgs) = 40, round to 176 (22*8) | |||
| 43 | + | // to match loong64 and ensure we don't overlap with saved register args. | |||
| 44 | + | // The saved regs end at SP-8 (original), so we need new SP below SP-128. | |||
| 45 | + | ADD $-(22*8), SP | |||
| 46 | + | ||||
| 47 | + | // Save link register (RA/X1) and callee-saved register X9 | |||
| 48 | + | // (X9 is used by the assembler for some instructions) | |||
| 49 | + | MOV X1, 0(SP) | |||
| 50 | + | MOV X9, 8(SP) | |||
| 51 | + | ||||
| 52 | + | // Create a struct callbackArgs on our stack. | |||
| 53 | + | // callbackArgs struct: index(0), args(8), result(16) | |||
| 54 | + | // Place it at 16(SP) to avoid overlap | |||
| 55 | + | ADD $16, SP, X9 | |||
| 56 | + | MOV X7, 0(X9) // callback index | |||
| 57 | + | MOV X6, 8(X9) // address of args vector | |||
| 58 | + | MOV X0, 16(X9) // result = 0 | |||
| 59 | + | ||||
| 60 | + | // Call crosscall2 with arguments in registers | |||
| 61 | + | MOV ·callbackWrap_call(SB), X10 // Get the ABIInternal function pointer | |||
| 62 | + | MOV (X10), X10 // without <ABIInternal> by using a closure. X10 = fn | |||
| 63 | + | MOV X9, X11 // X11 = frame (address of callbackArgs) | |||
| 64 | + | MOV X0, X13 // X13 = ctxt = 0 | |||
| 65 | + | ||||
| 66 | + | CALL crosscall2(SB) | |||
| 67 | + | ||||
| 68 | + | // Get callback result. | |||
| 69 | + | ADD $16, SP, X9 | |||
| 70 | + | MOV 16(X9), X10 | |||
| 71 | + | ||||
| 72 | + | // Restore link register and callee-saved X9 | |||
| 73 | + | MOV 8(SP), X9 | |||
| 74 | + | MOV 0(SP), X1 | |||
| 75 | + | ||||
| 76 | + | // Restore stack pointer | |||
| 77 | + | ADD $(22*8), SP | |||
| 78 | + | ||||
| 79 | + | 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 && !(amd64 || arm64 || loong64) | |
| 4 | + | //go:build cgo && !(amd64 || arm64 || loong64 || 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 amd64/arm64/loong64") | |
| 20 | + | panic("purego: NewCallback on Linux is only supported on amd64/arm64/loong64/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 && !(amd64 || arm64 || loong64) | 4 | + | //go:build cgo && !(amd64 || arm64 || loong64 || 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 amd64/arm64/loong64") | 20 | + | panic("purego: NewCallback on Linux is only supported on amd64/arm64/loong64/riscv64") |
| 21 | } | 21 | } | ||
M syscall_other.go +1 -1
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build linux && !amd64 && !arm64 && !loong64 | |
| 4 | + | //go:build linux && !amd64 && !arm64 && !loong64 && !riscv64 | |
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build linux && !amd64 && !arm64 && !loong64 | 4 | + | //go:build linux && !amd64 && !arm64 && !loong64 && !riscv64 |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
M syscall_sysv.go +3 -3
| @@ -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 && (amd64 || arm64 || loong64)) || netbsd | |
| 4 | + | //go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64 || riscv64)) || netbsd | |
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| @@ -217,8 +217,8 @@ func callbackasmAddr(i int) uintptr { | |||
| 217 | 217 | panic("purego: unsupported architecture") | |
| 218 | 218 | case "386", "amd64": | |
| 219 | 219 | entrySize = 5 | |
| 220 | - | case "arm", "arm64", "loong64": | |
| 221 | - | // On ARM and ARM64, each entry is a MOV instruction | |
| 220 | + | case "arm", "arm64", "loong64", "riscv64": | |
| 221 | + | // On ARM, ARM64, Loong64, and RISCV64, each entry is a MOV instruction | |
| 222 | 222 | // followed by a branch instruction | |
| 223 | 223 | entrySize = 8 | |
| 224 | 224 | } | |
| @@ -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 && (amd64 || arm64 || loong64)) || netbsd | 4 | + | //go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64 || riscv64)) || netbsd |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| @@ -217,8 +217,8 @@ func callbackasmAddr(i int) uintptr { | |||||
| 217 | panic("purego: unsupported architecture") | 217 | panic("purego: unsupported architecture") | ||
| 218 | case "386", "amd64": | 218 | case "386", "amd64": | ||
| 219 | entrySize = 5 | 219 | entrySize = 5 | ||
| 220 | - | case "arm", "arm64", "loong64": | 220 | + | case "arm", "arm64", "loong64", "riscv64": |
| 221 | - | // On ARM and ARM64, each entry is a MOV instruction | 221 | + | // On ARM, ARM64, Loong64, and RISCV64, each entry is a MOV instruction |
| 222 | // followed by a branch instruction | 222 | // followed by a branch instruction | ||
| 223 | entrySize = 8 | 223 | entrySize = 8 | ||
| 224 | } | 224 | } | ||
M wincallback.go +30 -0
| @@ -100,8 +100,38 @@ TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||
| 100 | 100 | } | |
| 101 | 101 | } | |
| 102 | 102 | ||
| 103 | + | func genasmRiscv64() { | |
| 104 | + | var buf bytes.Buffer | |
| 105 | + | ||
| 106 | + | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. | |
| 107 | + | ||
| 108 | + | //go:build darwin || freebsd || linux || netbsd | |
| 109 | + | ||
| 110 | + | // External code calls into callbackasm at an offset corresponding | |
| 111 | + | // to the callback index. Callbackasm is a table of MOV and JMP instructions. | |
| 112 | + | // The MOV instruction loads X7 with the callback index, and the | |
| 113 | + | // JMP instruction branches to callbackasm1. | |
| 114 | + | // callbackasm1 takes the callback index from X7 and | |
| 115 | + | // indexes into an array that stores information about each callback. | |
| 116 | + | // It then calls the Go implementation for that callback. | |
| 117 | + | #include "textflag.h" | |
| 118 | + | ||
| 119 | + | TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |
| 120 | + | `) | |
| 121 | + | for i := 0; i < maxCallback; i++ { | |
| 122 | + | fmt.Fprintf(&buf, "\tPCALIGN\t$8\n") | |
| 123 | + | fmt.Fprintf(&buf, "\tMOV\t$%d, X7\n", i) | |
| 124 | + | buf.WriteString("\tJMP\tcallbackasm1(SB)\n") | |
| 125 | + | } | |
| 126 | + | if err := os.WriteFile("zcallback_riscv64.s", buf.Bytes(), 0644); err != nil { | |
| 127 | + | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) | |
| 128 | + | os.Exit(2) | |
| 129 | + | } | |
| 130 | + | } | |
| 131 | + | ||
| 103 | 132 | func main() { | |
| 104 | 133 | genasmAmd64() | |
| 105 | 134 | genasmArm64() | |
| 106 | 135 | genasmLoong64() | |
| 136 | + | genasmRiscv64() | |
| 107 | 137 | } | |
| @@ -100,8 +100,38 @@ TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||||
| 100 | } | 100 | } | ||
| 101 | } | 101 | } | ||
| 102 | 102 | ||||
| 103 | + | func genasmRiscv64() { | |||
| 104 | + | var buf bytes.Buffer | |||
| 105 | + | ||||
| 106 | + | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. | |||
| 107 | + | ||||
| 108 | + | //go:build darwin || freebsd || linux || netbsd | |||
| 109 | + | ||||
| 110 | + | // External code calls into callbackasm at an offset corresponding | |||
| 111 | + | // to the callback index. Callbackasm is a table of MOV and JMP instructions. | |||
| 112 | + | // The MOV instruction loads X7 with the callback index, and the | |||
| 113 | + | // JMP instruction branches to callbackasm1. | |||
| 114 | + | // callbackasm1 takes the callback index from X7 and | |||
| 115 | + | // indexes into an array that stores information about each callback. | |||
| 116 | + | // It then calls the Go implementation for that callback. | |||
| 117 | + | #include "textflag.h" | |||
| 118 | + | ||||
| 119 | + | TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||
| 120 | + | `) | |||
| 121 | + | for i := 0; i < maxCallback; i++ { | |||
| 122 | + | fmt.Fprintf(&buf, "\tPCALIGN\t$8\n") | |||
| 123 | + | fmt.Fprintf(&buf, "\tMOV\t$%d, X7\n", i) | |||
| 124 | + | buf.WriteString("\tJMP\tcallbackasm1(SB)\n") | |||
| 125 | + | } | |||
| 126 | + | if err := os.WriteFile("zcallback_riscv64.s", buf.Bytes(), 0644); err != nil { | |||
| 127 | + | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) | |||
| 128 | + | os.Exit(2) | |||
| 129 | + | } | |||
| 130 | + | } | |||
| 131 | + | ||||
| 103 | func main() { | 132 | func main() { | ||
| 104 | genasmAmd64() | 133 | genasmAmd64() | ||
| 105 | genasmArm64() | 134 | genasmArm64() | ||
| 106 | genasmLoong64() | 135 | genasmLoong64() | ||
| 136 | + | genasmRiscv64() | |||
| 107 | } | 137 | } | ||