purego: add support for loong64 (#335)
- commit
- 045732660bc4b298c428c8d98716c8eec4266289
- author
- Meidan Li · · 2025-09-16 13:43 UTC
- committer
- GitHub · · 2025-09-16 13:43 UTC
- parent
- 0788778
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
19 files · +4772 -16
M .github/workflows/test.yml +20 -1
| @@ -32,7 +32,7 @@ jobs: | |||
| 32 | 32 | if: runner.os == 'Linux' | |
| 33 | 33 | run: | | |
| 34 | 34 | sudo apt-get update | |
| 35 | - | sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qemu-user | |
| 35 | + | sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu qemu-user | |
| 36 | 36 | ||
| 37 | 37 | - name: Set up the prerequisites | |
| 38 | 38 | if: runner.os == 'Windows' | |
| @@ -74,6 +74,13 @@ jobs: | |||
| 74 | 74 | env GOOS=netbsd GOARCH=amd64 go build -v ./... | |
| 75 | 75 | env GOOS=netbsd GOARCH=arm64 go build -v ./... | |
| 76 | 76 | ||
| 77 | + | - name: go build (Linux loong64) | |
| 78 | + | if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') && !startsWith(matrix.go, '1.22.') && !startsWith(matrix.go, '1.23.') && !startsWith(matrix.go, '1.24.') }} | |
| 79 | + | run: | | |
| 80 | + | # Check cross-compiling Linux binaries for loong64. | |
| 81 | + | # Loong64 support internal linking from go1.25. | |
| 82 | + | env GOOS=linux GOARCH=loong64 go build -v ./... | |
| 83 | + | ||
| 77 | 84 | - name: go build (plugin) | |
| 78 | 85 | if: runner.os == 'Linux' || runner.os == 'macOS' | |
| 79 | 86 | run: | |
| @@ -123,6 +130,18 @@ jobs: | |||
| 123 | 130 | go env -u CC | |
| 124 | 131 | go env -u CXX | |
| 125 | 132 | ||
| 133 | + | - name: go test (Linux loong64) | |
| 134 | + | if: ${{ runner.os == 'Linux' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') && !startsWith(matrix.go, '1.22.') && !startsWith(matrix.go, '1.23.') && !startsWith(matrix.go, '1.24.') }} | |
| 135 | + | run: | | |
| 136 | + | go env -w CC=loongarch64-linux-gnu-gcc-14 | |
| 137 | + | go env -w CXX=loongarch64-linux-gnu-g++-14 | |
| 138 | + | env GOOS=linux GOARCH=loong64 CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |
| 139 | + | env QEMU_LD_PREFIX=/usr/loongarch64-linux-gnu qemu-loongarch64 ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |
| 140 | + | env GOOS=linux GOARCH=loong64 CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |
| 141 | + | env QEMU_LD_PREFIX=/usr/loongarch64-linux-gnu qemu-loongarch64 ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |
| 142 | + | go env -u CC | |
| 143 | + | go env -u CXX | |
| 144 | + | ||
| 126 | 145 | - name: go test (Windows 386) | |
| 127 | 146 | if: runner.os == 'Windows' | |
| 128 | 147 | run: | | |
| @@ -32,7 +32,7 @@ jobs: | |||||
| 32 | if: runner.os == 'Linux' | 32 | if: runner.os == 'Linux' | ||
| 33 | run: | | 33 | run: | | ||
| 34 | sudo apt-get update | 34 | sudo apt-get update | ||
| 35 | - | sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qemu-user | 35 | + | sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu qemu-user |
| 36 | 36 | ||||
| 37 | - name: Set up the prerequisites | 37 | - name: Set up the prerequisites | ||
| 38 | if: runner.os == 'Windows' | 38 | if: runner.os == 'Windows' | ||
| @@ -74,6 +74,13 @@ jobs: | |||||
| 74 | env GOOS=netbsd GOARCH=amd64 go build -v ./... | 74 | env GOOS=netbsd GOARCH=amd64 go build -v ./... | ||
| 75 | env GOOS=netbsd GOARCH=arm64 go build -v ./... | 75 | env GOOS=netbsd GOARCH=arm64 go build -v ./... | ||
| 76 | 76 | ||||
| 77 | + | - name: go build (Linux loong64) | |||
| 78 | + | if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') && !startsWith(matrix.go, '1.22.') && !startsWith(matrix.go, '1.23.') && !startsWith(matrix.go, '1.24.') }} | |||
| 79 | + | run: | | |||
| 80 | + | # Check cross-compiling Linux binaries for loong64. | |||
| 81 | + | # Loong64 support internal linking from go1.25. | |||
| 82 | + | env GOOS=linux GOARCH=loong64 go build -v ./... | |||
| 83 | + | ||||
| 77 | - name: go build (plugin) | 84 | - name: go build (plugin) | ||
| 78 | if: runner.os == 'Linux' || runner.os == 'macOS' | 85 | if: runner.os == 'Linux' || runner.os == 'macOS' | ||
| 79 | run: | 86 | run: | ||
| @@ -123,6 +130,18 @@ jobs: | |||||
| 123 | go env -u CC | 130 | go env -u CC | ||
| 124 | go env -u CXX | 131 | go env -u CXX | ||
| 125 | 132 | ||||
| 133 | + | - name: go test (Linux loong64) | |||
| 134 | + | if: ${{ runner.os == 'Linux' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') && !startsWith(matrix.go, '1.22.') && !startsWith(matrix.go, '1.23.') && !startsWith(matrix.go, '1.24.') }} | |||
| 135 | + | run: | | |||
| 136 | + | go env -w CC=loongarch64-linux-gnu-gcc-14 | |||
| 137 | + | go env -w CXX=loongarch64-linux-gnu-g++-14 | |||
| 138 | + | env GOOS=linux GOARCH=loong64 CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |||
| 139 | + | env QEMU_LD_PREFIX=/usr/loongarch64-linux-gnu qemu-loongarch64 ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |||
| 140 | + | env GOOS=linux GOARCH=loong64 CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |||
| 141 | + | env QEMU_LD_PREFIX=/usr/loongarch64-linux-gnu qemu-loongarch64 ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |||
| 142 | + | go env -u CC | |||
| 143 | + | go env -u CXX | |||
| 144 | + | ||||
| 126 | - name: go test (Windows 386) | 145 | - name: go test (Windows 386) | ||
| 127 | if: runner.os == 'Windows' | 146 | if: runner.os == 'Windows' | ||
| 128 | run: | | 147 | run: | | ||
M README.md +1 -1
| @@ -27,7 +27,7 @@ except for float arguments and return values. | |||
| 27 | 27 | ## Supported Platforms | |
| 28 | 28 | ||
| 29 | 29 | - **FreeBSD**: amd64, arm64 | |
| 30 | - | - **Linux**: amd64, arm64 | |
| 30 | + | - **Linux**: amd64, arm64, loong64 | |
| 31 | 31 | - **macOS / iOS**: amd64, arm64 | |
| 32 | 32 | - **Windows**: 386*, amd64, arm*, arm64 | |
| 33 | 33 | ||
| @@ -27,7 +27,7 @@ except for float arguments and return values. | |||||
| 27 | ## Supported Platforms | 27 | ## Supported Platforms | ||
| 28 | 28 | ||||
| 29 | - **FreeBSD**: amd64, arm64 | 29 | - **FreeBSD**: amd64, arm64 | ||
| 30 | - | - **Linux**: amd64, arm64 | 30 | + | - **Linux**: amd64, arm64, loong64 |
| 31 | - **macOS / iOS**: amd64, arm64 | 31 | - **macOS / iOS**: amd64, arm64 | ||
| 32 | - **Windows**: 386*, amd64, arm*, arm64 | 32 | - **Windows**: 386*, amd64, arm*, arm64 | ||
| 33 | 33 | ||||
A abi_loong64.h +60 -0
| @@ -0,0 +1,60 @@ | |||
| 1 | + | // Copyright 2022 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 ABI0. | |
| 6 | + | // | |
| 7 | + | // These macros save and restore the callee-saved registers | |
| 8 | + | // from the stack, but they don't adjust stack pointer, so | |
| 9 | + | // the user should prepare stack space in advance. | |
| 10 | + | // SAVE_R22_TO_R31(offset) saves R22 ~ R31 to the stack space | |
| 11 | + | // of ((offset)+0*8)(R3) ~ ((offset)+9*8)(R3). | |
| 12 | + | // | |
| 13 | + | // SAVE_F24_TO_F31(offset) saves F24 ~ F31 to the stack space | |
| 14 | + | // of ((offset)+0*8)(R3) ~ ((offset)+7*8)(R3). | |
| 15 | + | // | |
| 16 | + | // Note: g is R22 | |
| 17 | + | ||
| 18 | + | #define SAVE_R22_TO_R31(offset) \ | |
| 19 | + | MOVV g, ((offset)+(0*8))(R3) \ | |
| 20 | + | MOVV R23, ((offset)+(1*8))(R3) \ | |
| 21 | + | MOVV R24, ((offset)+(2*8))(R3) \ | |
| 22 | + | MOVV R25, ((offset)+(3*8))(R3) \ | |
| 23 | + | MOVV R26, ((offset)+(4*8))(R3) \ | |
| 24 | + | MOVV R27, ((offset)+(5*8))(R3) \ | |
| 25 | + | MOVV R28, ((offset)+(6*8))(R3) \ | |
| 26 | + | MOVV R29, ((offset)+(7*8))(R3) \ | |
| 27 | + | MOVV R30, ((offset)+(8*8))(R3) \ | |
| 28 | + | MOVV R31, ((offset)+(9*8))(R3) | |
| 29 | + | ||
| 30 | + | #define SAVE_F24_TO_F31(offset) \ | |
| 31 | + | MOVD F24, ((offset)+(0*8))(R3) \ | |
| 32 | + | MOVD F25, ((offset)+(1*8))(R3) \ | |
| 33 | + | MOVD F26, ((offset)+(2*8))(R3) \ | |
| 34 | + | MOVD F27, ((offset)+(3*8))(R3) \ | |
| 35 | + | MOVD F28, ((offset)+(4*8))(R3) \ | |
| 36 | + | MOVD F29, ((offset)+(5*8))(R3) \ | |
| 37 | + | MOVD F30, ((offset)+(6*8))(R3) \ | |
| 38 | + | MOVD F31, ((offset)+(7*8))(R3) | |
| 39 | + | ||
| 40 | + | #define RESTORE_R22_TO_R31(offset) \ | |
| 41 | + | MOVV ((offset)+(0*8))(R3), g \ | |
| 42 | + | MOVV ((offset)+(1*8))(R3), R23 \ | |
| 43 | + | MOVV ((offset)+(2*8))(R3), R24 \ | |
| 44 | + | MOVV ((offset)+(3*8))(R3), R25 \ | |
| 45 | + | MOVV ((offset)+(4*8))(R3), R26 \ | |
| 46 | + | MOVV ((offset)+(5*8))(R3), R27 \ | |
| 47 | + | MOVV ((offset)+(6*8))(R3), R28 \ | |
| 48 | + | MOVV ((offset)+(7*8))(R3), R29 \ | |
| 49 | + | MOVV ((offset)+(8*8))(R3), R30 \ | |
| 50 | + | MOVV ((offset)+(9*8))(R3), R31 | |
| 51 | + | ||
| 52 | + | #define RESTORE_F24_TO_F31(offset) \ | |
| 53 | + | MOVD ((offset)+(0*8))(R3), F24 \ | |
| 54 | + | MOVD ((offset)+(1*8))(R3), F25 \ | |
| 55 | + | MOVD ((offset)+(2*8))(R3), F26 \ | |
| 56 | + | MOVD ((offset)+(3*8))(R3), F27 \ | |
| 57 | + | MOVD ((offset)+(4*8))(R3), F28 \ | |
| 58 | + | MOVD ((offset)+(5*8))(R3), F29 \ | |
| 59 | + | MOVD ((offset)+(6*8))(R3), F30 \ | |
| 60 | + | MOVD ((offset)+(7*8))(R3), F31 | |
| @@ -0,0 +1,60 @@ | |||||
| 1 | + | // Copyright 2022 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 ABI0. | |||
| 6 | + | // | |||
| 7 | + | // These macros save and restore the callee-saved registers | |||
| 8 | + | // from the stack, but they don't adjust stack pointer, so | |||
| 9 | + | // the user should prepare stack space in advance. | |||
| 10 | + | // SAVE_R22_TO_R31(offset) saves R22 ~ R31 to the stack space | |||
| 11 | + | // of ((offset)+0*8)(R3) ~ ((offset)+9*8)(R3). | |||
| 12 | + | // | |||
| 13 | + | // SAVE_F24_TO_F31(offset) saves F24 ~ F31 to the stack space | |||
| 14 | + | // of ((offset)+0*8)(R3) ~ ((offset)+7*8)(R3). | |||
| 15 | + | // | |||
| 16 | + | // Note: g is R22 | |||
| 17 | + | ||||
| 18 | + | #define SAVE_R22_TO_R31(offset) \ | |||
| 19 | + | MOVV g, ((offset)+(0*8))(R3) \ | |||
| 20 | + | MOVV R23, ((offset)+(1*8))(R3) \ | |||
| 21 | + | MOVV R24, ((offset)+(2*8))(R3) \ | |||
| 22 | + | MOVV R25, ((offset)+(3*8))(R3) \ | |||
| 23 | + | MOVV R26, ((offset)+(4*8))(R3) \ | |||
| 24 | + | MOVV R27, ((offset)+(5*8))(R3) \ | |||
| 25 | + | MOVV R28, ((offset)+(6*8))(R3) \ | |||
| 26 | + | MOVV R29, ((offset)+(7*8))(R3) \ | |||
| 27 | + | MOVV R30, ((offset)+(8*8))(R3) \ | |||
| 28 | + | MOVV R31, ((offset)+(9*8))(R3) | |||
| 29 | + | ||||
| 30 | + | #define SAVE_F24_TO_F31(offset) \ | |||
| 31 | + | MOVD F24, ((offset)+(0*8))(R3) \ | |||
| 32 | + | MOVD F25, ((offset)+(1*8))(R3) \ | |||
| 33 | + | MOVD F26, ((offset)+(2*8))(R3) \ | |||
| 34 | + | MOVD F27, ((offset)+(3*8))(R3) \ | |||
| 35 | + | MOVD F28, ((offset)+(4*8))(R3) \ | |||
| 36 | + | MOVD F29, ((offset)+(5*8))(R3) \ | |||
| 37 | + | MOVD F30, ((offset)+(6*8))(R3) \ | |||
| 38 | + | MOVD F31, ((offset)+(7*8))(R3) | |||
| 39 | + | ||||
| 40 | + | #define RESTORE_R22_TO_R31(offset) \ | |||
| 41 | + | MOVV ((offset)+(0*8))(R3), g \ | |||
| 42 | + | MOVV ((offset)+(1*8))(R3), R23 \ | |||
| 43 | + | MOVV ((offset)+(2*8))(R3), R24 \ | |||
| 44 | + | MOVV ((offset)+(3*8))(R3), R25 \ | |||
| 45 | + | MOVV ((offset)+(4*8))(R3), R26 \ | |||
| 46 | + | MOVV ((offset)+(5*8))(R3), R27 \ | |||
| 47 | + | MOVV ((offset)+(6*8))(R3), R28 \ | |||
| 48 | + | MOVV ((offset)+(7*8))(R3), R29 \ | |||
| 49 | + | MOVV ((offset)+(8*8))(R3), R30 \ | |||
| 50 | + | MOVV ((offset)+(9*8))(R3), R31 | |||
| 51 | + | ||||
| 52 | + | #define RESTORE_F24_TO_F31(offset) \ | |||
| 53 | + | MOVD ((offset)+(0*8))(R3), F24 \ | |||
| 54 | + | MOVD ((offset)+(1*8))(R3), F25 \ | |||
| 55 | + | MOVD ((offset)+(2*8))(R3), F26 \ | |||
| 56 | + | MOVD ((offset)+(3*8))(R3), F27 \ | |||
| 57 | + | MOVD ((offset)+(4*8))(R3), F28 \ | |||
| 58 | + | MOVD ((offset)+(5*8))(R3), F29 \ | |||
| 59 | + | MOVD ((offset)+(6*8))(R3), F30 \ | |||
| 60 | + | MOVD ((offset)+(7*8))(R3), F31 | |||
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)) | |
| 4 | + | //go:build darwin || (linux && (amd64 || arm64 || loong64)) | |
| 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)) | 4 | + | //go:build darwin || (linux && (amd64 || arm64 || loong64)) |
| 5 | 5 | ||||
| 6 | package purego_test | 6 | package purego_test | ||
| 7 | 7 | ||||
M func.go +14 -4
| @@ -124,7 +124,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 124 | 124 | panic("purego: cfn is nil") | |
| 125 | 125 | } | |
| 126 | 126 | if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && | |
| 127 | - | runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" { | |
| 127 | + | runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | |
| 128 | 128 | panic("purego: float returns are not supported") | |
| 129 | 129 | } | |
| 130 | 130 | { | |
| @@ -257,7 +257,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 257 | 257 | var arm64_r8 uintptr | |
| 258 | 258 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 259 | 259 | outType := ty.Out(0) | |
| 260 | - | if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { | |
| 260 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64") && outType.Size() > maxRegAllocStructSize { | |
| 261 | 261 | val := reflect.New(outType) | |
| 262 | 262 | keepAlive = append(keepAlive, val) | |
| 263 | 263 | addInt(val.Pointer()) | |
| @@ -310,7 +310,17 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 310 | 310 | syscall := thePool.Get().(*syscall15Args) | |
| 311 | 311 | defer thePool.Put(syscall) | |
| 312 | 312 | ||
| 313 | - | if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | |
| 313 | + | if runtime.GOARCH == "loong64" { | |
| 314 | + | *syscall = syscall15Args{ | |
| 315 | + | cfn, | |
| 316 | + | sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], | |
| 317 | + | sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | |
| 318 | + | sysargs[12], sysargs[13], sysargs[14], | |
| 319 | + | floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], | |
| 320 | + | 0, | |
| 321 | + | } | |
| 322 | + | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |
| 323 | + | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | |
| 314 | 324 | // Use the normal arm64 calling convention even on Windows | |
| 315 | 325 | *syscall = syscall15Args{ | |
| 316 | 326 | cfn, | |
| @@ -467,7 +477,7 @@ func roundUpTo8(val uintptr) uintptr { | |||
| 467 | 477 | ||
| 468 | 478 | func numOfIntegerRegisters() int { | |
| 469 | 479 | switch runtime.GOARCH { | |
| 470 | - | case "arm64": | |
| 480 | + | case "arm64", "loong64": | |
| 471 | 481 | return 8 | |
| 472 | 482 | case "amd64": | |
| 473 | 483 | return 6 | |
| @@ -124,7 +124,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 124 | panic("purego: cfn is nil") | 124 | panic("purego: cfn is nil") | ||
| 125 | } | 125 | } | ||
| 126 | if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && | 126 | if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && | ||
| 127 | - | runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" { | 127 | + | runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { |
| 128 | panic("purego: float returns are not supported") | 128 | panic("purego: float returns are not supported") | ||
| 129 | } | 129 | } | ||
| 130 | { | 130 | { | ||
| @@ -257,7 +257,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 257 | var arm64_r8 uintptr | 257 | var arm64_r8 uintptr | ||
| 258 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | 258 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | ||
| 259 | outType := ty.Out(0) | 259 | outType := ty.Out(0) | ||
| 260 | - | if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { | 260 | + | if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64") && outType.Size() > maxRegAllocStructSize { |
| 261 | val := reflect.New(outType) | 261 | val := reflect.New(outType) | ||
| 262 | keepAlive = append(keepAlive, val) | 262 | keepAlive = append(keepAlive, val) | ||
| 263 | addInt(val.Pointer()) | 263 | addInt(val.Pointer()) | ||
| @@ -310,7 +310,17 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 310 | syscall := thePool.Get().(*syscall15Args) | 310 | syscall := thePool.Get().(*syscall15Args) | ||
| 311 | defer thePool.Put(syscall) | 311 | defer thePool.Put(syscall) | ||
| 312 | 312 | ||||
| 313 | - | if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | 313 | + | if runtime.GOARCH == "loong64" { |
| 314 | + | *syscall = syscall15Args{ | |||
| 315 | + | cfn, | |||
| 316 | + | sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], | |||
| 317 | + | sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | |||
| 318 | + | sysargs[12], sysargs[13], sysargs[14], | |||
| 319 | + | floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], | |||
| 320 | + | 0, | |||
| 321 | + | } | |||
| 322 | + | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |||
| 323 | + | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | |||
| 314 | // Use the normal arm64 calling convention even on Windows | 324 | // Use the normal arm64 calling convention even on Windows | ||
| 315 | *syscall = syscall15Args{ | 325 | *syscall = syscall15Args{ | ||
| 316 | cfn, | 326 | cfn, | ||
| @@ -467,7 +477,7 @@ func roundUpTo8(val uintptr) uintptr { | |||||
| 467 | 477 | ||||
| 468 | func numOfIntegerRegisters() int { | 478 | func numOfIntegerRegisters() int { | ||
| 469 | switch runtime.GOARCH { | 479 | switch runtime.GOARCH { | ||
| 470 | - | case "arm64": | 480 | + | case "arm64", "loong64": |
| 471 | return 8 | 481 | return 8 | ||
| 472 | case "amd64": | 482 | case "amd64": | ||
| 473 | return 6 | 483 | return 6 | ||
M func_test.go +3 -3
| @@ -49,7 +49,7 @@ func TestRegisterFunc(t *testing.T) { | |||
| 49 | 49 | } | |
| 50 | 50 | ||
| 51 | 51 | func Test_qsort(t *testing.T) { | |
| 52 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" { | |
| 52 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | |
| 53 | 53 | t.Skip("Platform doesn't support Floats") | |
| 54 | 54 | return | |
| 55 | 55 | } | |
| @@ -78,7 +78,7 @@ func Test_qsort(t *testing.T) { | |||
| 78 | 78 | } | |
| 79 | 79 | ||
| 80 | 80 | func TestRegisterFunc_Floats(t *testing.T) { | |
| 81 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" { | |
| 81 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | |
| 82 | 82 | t.Skip("Platform doesn't support Floats") | |
| 83 | 83 | return | |
| 84 | 84 | } | |
| @@ -117,7 +117,7 @@ func TestRegisterFunc_Floats(t *testing.T) { | |||
| 117 | 117 | } | |
| 118 | 118 | ||
| 119 | 119 | func TestRegisterLibFunc_Bool(t *testing.T) { | |
| 120 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" { | |
| 120 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { | |
| 121 | 121 | t.Skip("Platform doesn't support callbacks") | |
| 122 | 122 | return | |
| 123 | 123 | } | |
| @@ -49,7 +49,7 @@ func TestRegisterFunc(t *testing.T) { | |||||
| 49 | } | 49 | } | ||
| 50 | 50 | ||||
| 51 | func Test_qsort(t *testing.T) { | 51 | func Test_qsort(t *testing.T) { | ||
| 52 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" { | 52 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { |
| 53 | t.Skip("Platform doesn't support Floats") | 53 | t.Skip("Platform doesn't support Floats") | ||
| 54 | return | 54 | return | ||
| 55 | } | 55 | } | ||
| @@ -78,7 +78,7 @@ func Test_qsort(t *testing.T) { | |||||
| 78 | } | 78 | } | ||
| 79 | 79 | ||||
| 80 | func TestRegisterFunc_Floats(t *testing.T) { | 80 | func TestRegisterFunc_Floats(t *testing.T) { | ||
| 81 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" { | 81 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { |
| 82 | t.Skip("Platform doesn't support Floats") | 82 | t.Skip("Platform doesn't support Floats") | ||
| 83 | return | 83 | return | ||
| 84 | } | 84 | } | ||
| @@ -117,7 +117,7 @@ func TestRegisterFunc_Floats(t *testing.T) { | |||||
| 117 | } | 117 | } | ||
| 118 | 118 | ||||
| 119 | func TestRegisterLibFunc_Bool(t *testing.T) { | 119 | func TestRegisterLibFunc_Bool(t *testing.T) { | ||
| 120 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" { | 120 | + | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" { |
| 121 | t.Skip("Platform doesn't support callbacks") | 121 | t.Skip("Platform doesn't support callbacks") | ||
| 122 | return | 122 | return | ||
| 123 | } | 123 | } | ||
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)) || netbsd | |
| 4 | + | //go:build freebsd || (linux && !(arm64 || amd64 || loong64)) || 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)) || netbsd | 4 | + | //go:build freebsd || (linux && !(arm64 || amd64 || loong64)) || netbsd |
| 5 | 5 | ||||
| 6 | package cgo | 6 | package cgo | ||
| 7 | 7 | ||||
A internal/fakecgo/abi_loong64.h +60 -0
| @@ -0,0 +1,60 @@ | |||
| 1 | + | // Copyright 2022 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 ABI0. | |
| 6 | + | // | |
| 7 | + | // These macros save and restore the callee-saved registers | |
| 8 | + | // from the stack, but they don't adjust stack pointer, so | |
| 9 | + | // the user should prepare stack space in advance. | |
| 10 | + | // SAVE_R22_TO_R31(offset) saves R22 ~ R31 to the stack space | |
| 11 | + | // of ((offset)+0*8)(R3) ~ ((offset)+9*8)(R3). | |
| 12 | + | // | |
| 13 | + | // SAVE_F24_TO_F31(offset) saves F24 ~ F31 to the stack space | |
| 14 | + | // of ((offset)+0*8)(R3) ~ ((offset)+7*8)(R3). | |
| 15 | + | // | |
| 16 | + | // Note: g is R22 | |
| 17 | + | ||
| 18 | + | #define SAVE_R22_TO_R31(offset) \ | |
| 19 | + | MOVV g, ((offset)+(0*8))(R3) \ | |
| 20 | + | MOVV R23, ((offset)+(1*8))(R3) \ | |
| 21 | + | MOVV R24, ((offset)+(2*8))(R3) \ | |
| 22 | + | MOVV R25, ((offset)+(3*8))(R3) \ | |
| 23 | + | MOVV R26, ((offset)+(4*8))(R3) \ | |
| 24 | + | MOVV R27, ((offset)+(5*8))(R3) \ | |
| 25 | + | MOVV R28, ((offset)+(6*8))(R3) \ | |
| 26 | + | MOVV R29, ((offset)+(7*8))(R3) \ | |
| 27 | + | MOVV R30, ((offset)+(8*8))(R3) \ | |
| 28 | + | MOVV R31, ((offset)+(9*8))(R3) | |
| 29 | + | ||
| 30 | + | #define SAVE_F24_TO_F31(offset) \ | |
| 31 | + | MOVD F24, ((offset)+(0*8))(R3) \ | |
| 32 | + | MOVD F25, ((offset)+(1*8))(R3) \ | |
| 33 | + | MOVD F26, ((offset)+(2*8))(R3) \ | |
| 34 | + | MOVD F27, ((offset)+(3*8))(R3) \ | |
| 35 | + | MOVD F28, ((offset)+(4*8))(R3) \ | |
| 36 | + | MOVD F29, ((offset)+(5*8))(R3) \ | |
| 37 | + | MOVD F30, ((offset)+(6*8))(R3) \ | |
| 38 | + | MOVD F31, ((offset)+(7*8))(R3) | |
| 39 | + | ||
| 40 | + | #define RESTORE_R22_TO_R31(offset) \ | |
| 41 | + | MOVV ((offset)+(0*8))(R3), g \ | |
| 42 | + | MOVV ((offset)+(1*8))(R3), R23 \ | |
| 43 | + | MOVV ((offset)+(2*8))(R3), R24 \ | |
| 44 | + | MOVV ((offset)+(3*8))(R3), R25 \ | |
| 45 | + | MOVV ((offset)+(4*8))(R3), R26 \ | |
| 46 | + | MOVV ((offset)+(5*8))(R3), R27 \ | |
| 47 | + | MOVV ((offset)+(6*8))(R3), R28 \ | |
| 48 | + | MOVV ((offset)+(7*8))(R3), R29 \ | |
| 49 | + | MOVV ((offset)+(8*8))(R3), R30 \ | |
| 50 | + | MOVV ((offset)+(9*8))(R3), R31 | |
| 51 | + | ||
| 52 | + | #define RESTORE_F24_TO_F31(offset) \ | |
| 53 | + | MOVD ((offset)+(0*8))(R3), F24 \ | |
| 54 | + | MOVD ((offset)+(1*8))(R3), F25 \ | |
| 55 | + | MOVD ((offset)+(2*8))(R3), F26 \ | |
| 56 | + | MOVD ((offset)+(3*8))(R3), F27 \ | |
| 57 | + | MOVD ((offset)+(4*8))(R3), F28 \ | |
| 58 | + | MOVD ((offset)+(5*8))(R3), F29 \ | |
| 59 | + | MOVD ((offset)+(6*8))(R3), F30 \ | |
| 60 | + | MOVD ((offset)+(7*8))(R3), F31 | |
| @@ -0,0 +1,60 @@ | |||||
| 1 | + | // Copyright 2022 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 ABI0. | |||
| 6 | + | // | |||
| 7 | + | // These macros save and restore the callee-saved registers | |||
| 8 | + | // from the stack, but they don't adjust stack pointer, so | |||
| 9 | + | // the user should prepare stack space in advance. | |||
| 10 | + | // SAVE_R22_TO_R31(offset) saves R22 ~ R31 to the stack space | |||
| 11 | + | // of ((offset)+0*8)(R3) ~ ((offset)+9*8)(R3). | |||
| 12 | + | // | |||
| 13 | + | // SAVE_F24_TO_F31(offset) saves F24 ~ F31 to the stack space | |||
| 14 | + | // of ((offset)+0*8)(R3) ~ ((offset)+7*8)(R3). | |||
| 15 | + | // | |||
| 16 | + | // Note: g is R22 | |||
| 17 | + | ||||
| 18 | + | #define SAVE_R22_TO_R31(offset) \ | |||
| 19 | + | MOVV g, ((offset)+(0*8))(R3) \ | |||
| 20 | + | MOVV R23, ((offset)+(1*8))(R3) \ | |||
| 21 | + | MOVV R24, ((offset)+(2*8))(R3) \ | |||
| 22 | + | MOVV R25, ((offset)+(3*8))(R3) \ | |||
| 23 | + | MOVV R26, ((offset)+(4*8))(R3) \ | |||
| 24 | + | MOVV R27, ((offset)+(5*8))(R3) \ | |||
| 25 | + | MOVV R28, ((offset)+(6*8))(R3) \ | |||
| 26 | + | MOVV R29, ((offset)+(7*8))(R3) \ | |||
| 27 | + | MOVV R30, ((offset)+(8*8))(R3) \ | |||
| 28 | + | MOVV R31, ((offset)+(9*8))(R3) | |||
| 29 | + | ||||
| 30 | + | #define SAVE_F24_TO_F31(offset) \ | |||
| 31 | + | MOVD F24, ((offset)+(0*8))(R3) \ | |||
| 32 | + | MOVD F25, ((offset)+(1*8))(R3) \ | |||
| 33 | + | MOVD F26, ((offset)+(2*8))(R3) \ | |||
| 34 | + | MOVD F27, ((offset)+(3*8))(R3) \ | |||
| 35 | + | MOVD F28, ((offset)+(4*8))(R3) \ | |||
| 36 | + | MOVD F29, ((offset)+(5*8))(R3) \ | |||
| 37 | + | MOVD F30, ((offset)+(6*8))(R3) \ | |||
| 38 | + | MOVD F31, ((offset)+(7*8))(R3) | |||
| 39 | + | ||||
| 40 | + | #define RESTORE_R22_TO_R31(offset) \ | |||
| 41 | + | MOVV ((offset)+(0*8))(R3), g \ | |||
| 42 | + | MOVV ((offset)+(1*8))(R3), R23 \ | |||
| 43 | + | MOVV ((offset)+(2*8))(R3), R24 \ | |||
| 44 | + | MOVV ((offset)+(3*8))(R3), R25 \ | |||
| 45 | + | MOVV ((offset)+(4*8))(R3), R26 \ | |||
| 46 | + | MOVV ((offset)+(5*8))(R3), R27 \ | |||
| 47 | + | MOVV ((offset)+(6*8))(R3), R28 \ | |||
| 48 | + | MOVV ((offset)+(7*8))(R3), R29 \ | |||
| 49 | + | MOVV ((offset)+(8*8))(R3), R30 \ | |||
| 50 | + | MOVV ((offset)+(9*8))(R3), R31 | |||
| 51 | + | ||||
| 52 | + | #define RESTORE_F24_TO_F31(offset) \ | |||
| 53 | + | MOVD ((offset)+(0*8))(R3), F24 \ | |||
| 54 | + | MOVD ((offset)+(1*8))(R3), F25 \ | |||
| 55 | + | MOVD ((offset)+(2*8))(R3), F26 \ | |||
| 56 | + | MOVD ((offset)+(3*8))(R3), F27 \ | |||
| 57 | + | MOVD ((offset)+(4*8))(R3), F28 \ | |||
| 58 | + | MOVD ((offset)+(5*8))(R3), F29 \ | |||
| 59 | + | MOVD ((offset)+(6*8))(R3), F30 \ | |||
| 60 | + | MOVD ((offset)+(7*8))(R3), F31 | |||
A internal/fakecgo/asm_loong64.s +40 -0
| @@ -0,0 +1,40 @@ | |||
| 1 | + | // Copyright 2022 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_loong64.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 | + | TEXT crosscall2(SB),NOSPLIT|NOFRAME,$0 | |
| 13 | + | /* | |
| 14 | + | * We still need to save all callee save register as before, and then | |
| 15 | + | * push 3 args for fn (R4, R5, R7), skipping R6. | |
| 16 | + | * Also note that at procedure entry in gc world, 8(R29) will be the | |
| 17 | + | * first arg. | |
| 18 | + | */ | |
| 19 | + | ||
| 20 | + | ADDV $(-23*8), R3 | |
| 21 | + | MOVV R4, (1*8)(R3) // fn unsafe.Pointer | |
| 22 | + | MOVV R5, (2*8)(R3) // a unsafe.Pointer | |
| 23 | + | MOVV R7, (3*8)(R3) // ctxt uintptr | |
| 24 | + | ||
| 25 | + | SAVE_R22_TO_R31((4*8)) | |
| 26 | + | SAVE_F24_TO_F31((14*8)) | |
| 27 | + | MOVV R1, (22*8)(R3) | |
| 28 | + | ||
| 29 | + | // Initialize Go ABI environment | |
| 30 | + | JAL runtime·load_g(SB) | |
| 31 | + | ||
| 32 | + | JAL runtime·cgocallback(SB) | |
| 33 | + | ||
| 34 | + | RESTORE_R22_TO_R31((4*8)) | |
| 35 | + | RESTORE_F24_TO_F31((14*8)) | |
| 36 | + | MOVV (22*8)(R3), R1 | |
| 37 | + | ||
| 38 | + | ADDV $(23*8), R3 | |
| 39 | + | ||
| 40 | + | RET | |
| @@ -0,0 +1,40 @@ | |||||
| 1 | + | // Copyright 2022 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_loong64.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 | + | TEXT crosscall2(SB),NOSPLIT|NOFRAME,$0 | |||
| 13 | + | /* | |||
| 14 | + | * We still need to save all callee save register as before, and then | |||
| 15 | + | * push 3 args for fn (R4, R5, R7), skipping R6. | |||
| 16 | + | * Also note that at procedure entry in gc world, 8(R29) will be the | |||
| 17 | + | * first arg. | |||
| 18 | + | */ | |||
| 19 | + | ||||
| 20 | + | ADDV $(-23*8), R3 | |||
| 21 | + | MOVV R4, (1*8)(R3) // fn unsafe.Pointer | |||
| 22 | + | MOVV R5, (2*8)(R3) // a unsafe.Pointer | |||
| 23 | + | MOVV R7, (3*8)(R3) // ctxt uintptr | |||
| 24 | + | ||||
| 25 | + | SAVE_R22_TO_R31((4*8)) | |||
| 26 | + | SAVE_F24_TO_F31((14*8)) | |||
| 27 | + | MOVV R1, (22*8)(R3) | |||
| 28 | + | ||||
| 29 | + | // Initialize Go ABI environment | |||
| 30 | + | JAL runtime·load_g(SB) | |||
| 31 | + | ||||
| 32 | + | JAL runtime·cgocallback(SB) | |||
| 33 | + | ||||
| 34 | + | RESTORE_R22_TO_R31((4*8)) | |||
| 35 | + | RESTORE_F24_TO_F31((14*8)) | |||
| 36 | + | MOVV (22*8)(R3), R1 | |||
| 37 | + | ||||
| 38 | + | ADDV $(23*8), R3 | |||
| 39 | + | ||||
| 40 | + | RET | |||
A internal/fakecgo/go_linux_loong64.go +92 -0
| @@ -0,0 +1,92 @@ | |||
| 1 | + | // Copyright 2011 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 | + | //go:build !cgo | |
| 6 | + | ||
| 7 | + | package fakecgo | |
| 8 | + | ||
| 9 | + | import "unsafe" | |
| 10 | + | ||
| 11 | + | //go:nosplit | |
| 12 | + | func _cgo_sys_thread_start(ts *ThreadStart) { | |
| 13 | + | var attr pthread_attr_t | |
| 14 | + | var ign, oset sigset_t | |
| 15 | + | var p pthread_t | |
| 16 | + | var size size_t | |
| 17 | + | var err int | |
| 18 | + | ||
| 19 | + | sigfillset(&ign) | |
| 20 | + | pthread_sigmask(SIG_SETMASK, &ign, &oset) | |
| 21 | + | ||
| 22 | + | pthread_attr_init(&attr) | |
| 23 | + | pthread_attr_getstacksize(&attr, &size) | |
| 24 | + | // Leave stacklo=0 and set stackhi=size; mstart will do the rest. | |
| 25 | + | ts.g.stackhi = uintptr(size) | |
| 26 | + | ||
| 27 | + | err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) | |
| 28 | + | ||
| 29 | + | pthread_sigmask(SIG_SETMASK, &oset, nil) | |
| 30 | + | ||
| 31 | + | if err != 0 { | |
| 32 | + | print("fakecgo: pthread_create failed: ") | |
| 33 | + | println(err) | |
| 34 | + | abort() | |
| 35 | + | } | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function | |
| 39 | + | // | |
| 40 | + | //go:linkname x_threadentry_trampoline threadentry_trampoline | |
| 41 | + | var x_threadentry_trampoline byte | |
| 42 | + | var threadentry_trampolineABI0 = &x_threadentry_trampoline | |
| 43 | + | ||
| 44 | + | //go:nosplit | |
| 45 | + | func threadentry(v unsafe.Pointer) unsafe.Pointer { | |
| 46 | + | ts := *(*ThreadStart)(v) | |
| 47 | + | free(v) | |
| 48 | + | ||
| 49 | + | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) | |
| 50 | + | ||
| 51 | + | // faking funcs in go is a bit a... involved - but the following works :) | |
| 52 | + | fn := uintptr(unsafe.Pointer(&ts.fn)) | |
| 53 | + | (*(*func())(unsafe.Pointer(&fn)))() | |
| 54 | + | ||
| 55 | + | return nil | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | // here we will store a pointer to the provided setg func | |
| 59 | + | var setg_func uintptr | |
| 60 | + | ||
| 61 | + | //go:nosplit | |
| 62 | + | func x_cgo_init(g *G, setg uintptr) { | |
| 63 | + | var size size_t | |
| 64 | + | var attr *pthread_attr_t | |
| 65 | + | ||
| 66 | + | /* The memory sanitizer distributed with versions of clang | |
| 67 | + | before 3.8 has a bug: if you call mmap before malloc, mmap | |
| 68 | + | may return an address that is later overwritten by the msan | |
| 69 | + | library. Avoid this problem by forcing a call to malloc | |
| 70 | + | here, before we ever call malloc. | |
| 71 | + | ||
| 72 | + | This is only required for the memory sanitizer, so it's | |
| 73 | + | unfortunate that we always run it. It should be possible | |
| 74 | + | to remove this when we no longer care about versions of | |
| 75 | + | clang before 3.8. The test for this is | |
| 76 | + | misc/cgo/testsanitizers. | |
| 77 | + | ||
| 78 | + | GCC works hard to eliminate a seemingly unnecessary call to | |
| 79 | + | malloc, so we actually use the memory we allocate. */ | |
| 80 | + | ||
| 81 | + | setg_func = setg | |
| 82 | + | attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) | |
| 83 | + | if attr == nil { | |
| 84 | + | println("fakecgo: malloc failed") | |
| 85 | + | abort() | |
| 86 | + | } | |
| 87 | + | pthread_attr_init(attr) | |
| 88 | + | pthread_attr_getstacksize(attr, &size) | |
| 89 | + | g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 | |
| 90 | + | pthread_attr_destroy(attr) | |
| 91 | + | free(unsafe.Pointer(attr)) | |
| 92 | + | } | |
| @@ -0,0 +1,92 @@ | |||||
| 1 | + | // Copyright 2011 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 | + | //go:build !cgo | |||
| 6 | + | ||||
| 7 | + | package fakecgo | |||
| 8 | + | ||||
| 9 | + | import "unsafe" | |||
| 10 | + | ||||
| 11 | + | //go:nosplit | |||
| 12 | + | func _cgo_sys_thread_start(ts *ThreadStart) { | |||
| 13 | + | var attr pthread_attr_t | |||
| 14 | + | var ign, oset sigset_t | |||
| 15 | + | var p pthread_t | |||
| 16 | + | var size size_t | |||
| 17 | + | var err int | |||
| 18 | + | ||||
| 19 | + | sigfillset(&ign) | |||
| 20 | + | pthread_sigmask(SIG_SETMASK, &ign, &oset) | |||
| 21 | + | ||||
| 22 | + | pthread_attr_init(&attr) | |||
| 23 | + | pthread_attr_getstacksize(&attr, &size) | |||
| 24 | + | // Leave stacklo=0 and set stackhi=size; mstart will do the rest. | |||
| 25 | + | ts.g.stackhi = uintptr(size) | |||
| 26 | + | ||||
| 27 | + | err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts) | |||
| 28 | + | ||||
| 29 | + | pthread_sigmask(SIG_SETMASK, &oset, nil) | |||
| 30 | + | ||||
| 31 | + | if err != 0 { | |||
| 32 | + | print("fakecgo: pthread_create failed: ") | |||
| 33 | + | println(err) | |||
| 34 | + | abort() | |||
| 35 | + | } | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | // threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function | |||
| 39 | + | // | |||
| 40 | + | //go:linkname x_threadentry_trampoline threadentry_trampoline | |||
| 41 | + | var x_threadentry_trampoline byte | |||
| 42 | + | var threadentry_trampolineABI0 = &x_threadentry_trampoline | |||
| 43 | + | ||||
| 44 | + | //go:nosplit | |||
| 45 | + | func threadentry(v unsafe.Pointer) unsafe.Pointer { | |||
| 46 | + | ts := *(*ThreadStart)(v) | |||
| 47 | + | free(v) | |||
| 48 | + | ||||
| 49 | + | setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g))) | |||
| 50 | + | ||||
| 51 | + | // faking funcs in go is a bit a... involved - but the following works :) | |||
| 52 | + | fn := uintptr(unsafe.Pointer(&ts.fn)) | |||
| 53 | + | (*(*func())(unsafe.Pointer(&fn)))() | |||
| 54 | + | ||||
| 55 | + | return nil | |||
| 56 | + | } | |||
| 57 | + | ||||
| 58 | + | // here we will store a pointer to the provided setg func | |||
| 59 | + | var setg_func uintptr | |||
| 60 | + | ||||
| 61 | + | //go:nosplit | |||
| 62 | + | func x_cgo_init(g *G, setg uintptr) { | |||
| 63 | + | var size size_t | |||
| 64 | + | var attr *pthread_attr_t | |||
| 65 | + | ||||
| 66 | + | /* The memory sanitizer distributed with versions of clang | |||
| 67 | + | before 3.8 has a bug: if you call mmap before malloc, mmap | |||
| 68 | + | may return an address that is later overwritten by the msan | |||
| 69 | + | library. Avoid this problem by forcing a call to malloc | |||
| 70 | + | here, before we ever call malloc. | |||
| 71 | + | ||||
| 72 | + | This is only required for the memory sanitizer, so it's | |||
| 73 | + | unfortunate that we always run it. It should be possible | |||
| 74 | + | to remove this when we no longer care about versions of | |||
| 75 | + | clang before 3.8. The test for this is | |||
| 76 | + | misc/cgo/testsanitizers. | |||
| 77 | + | ||||
| 78 | + | GCC works hard to eliminate a seemingly unnecessary call to | |||
| 79 | + | malloc, so we actually use the memory we allocate. */ | |||
| 80 | + | ||||
| 81 | + | setg_func = setg | |||
| 82 | + | attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr))) | |||
| 83 | + | if attr == nil { | |||
| 84 | + | println("fakecgo: malloc failed") | |||
| 85 | + | abort() | |||
| 86 | + | } | |||
| 87 | + | pthread_attr_init(attr) | |||
| 88 | + | pthread_attr_getstacksize(attr, &size) | |||
| 89 | + | g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096 | |||
| 90 | + | pthread_attr_destroy(attr) | |||
| 91 | + | free(unsafe.Pointer(attr)) | |||
| 92 | + | } | |||
A internal/fakecgo/trampolines_loong64.s +71 -0
| @@ -0,0 +1,71 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2025 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 | + | MOVV R4, 8(R3) | |
| 13 | + | MOVV R5, 16(R3) | |
| 14 | + | MOVV ·x_cgo_init_call(SB), R6 | |
| 15 | + | MOVV (R6), R7 | |
| 16 | + | CALL (R7) | |
| 17 | + | RET | |
| 18 | + | ||
| 19 | + | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8 | |
| 20 | + | MOVV R4, 8(R3) | |
| 21 | + | MOVV ·x_cgo_thread_start_call(SB), R5 | |
| 22 | + | MOVV (R5), R6 | |
| 23 | + | CALL (R6) | |
| 24 | + | RET | |
| 25 | + | ||
| 26 | + | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8 | |
| 27 | + | MOVV R4, 8(R3) | |
| 28 | + | MOVV ·x_cgo_setenv_call(SB), R5 | |
| 29 | + | MOVV (R5), R6 | |
| 30 | + | CALL (R6) | |
| 31 | + | RET | |
| 32 | + | ||
| 33 | + | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8 | |
| 34 | + | MOVV R4, 8(R3) | |
| 35 | + | MOVV ·x_cgo_unsetenv_call(SB), R5 | |
| 36 | + | MOVV (R5), R6 | |
| 37 | + | CALL (R6) | |
| 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 | + | MOVV G+8(FP), R4 | |
| 51 | + | MOVV setg+0(FP), R5 | |
| 52 | + | CALL (R5) | |
| 53 | + | RET | |
| 54 | + | ||
| 55 | + | TEXT threadentry_trampoline(SB), NOSPLIT, $16 | |
| 56 | + | MOVV R4, 8(R3) | |
| 57 | + | MOVV ·threadentry_call(SB), R5 | |
| 58 | + | MOVV (R5), R6 | |
| 59 | + | CALL (R6) | |
| 60 | + | RET | |
| 61 | + | ||
| 62 | + | TEXT ·call5(SB), NOSPLIT, $0-0 | |
| 63 | + | MOVV fn+0(FP), R9 | |
| 64 | + | MOVV a1+8(FP), R4 | |
| 65 | + | MOVV a2+16(FP), R5 | |
| 66 | + | MOVV a3+24(FP), R6 | |
| 67 | + | MOVV a4+32(FP), R7 | |
| 68 | + | MOVV a5+40(FP), R8 | |
| 69 | + | CALL (R9) | |
| 70 | + | MOVV R4, ret+48(FP) | |
| 71 | + | RET | |
| @@ -0,0 +1,71 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2025 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 | + | MOVV R4, 8(R3) | |||
| 13 | + | MOVV R5, 16(R3) | |||
| 14 | + | MOVV ·x_cgo_init_call(SB), R6 | |||
| 15 | + | MOVV (R6), R7 | |||
| 16 | + | CALL (R7) | |||
| 17 | + | RET | |||
| 18 | + | ||||
| 19 | + | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8 | |||
| 20 | + | MOVV R4, 8(R3) | |||
| 21 | + | MOVV ·x_cgo_thread_start_call(SB), R5 | |||
| 22 | + | MOVV (R5), R6 | |||
| 23 | + | CALL (R6) | |||
| 24 | + | RET | |||
| 25 | + | ||||
| 26 | + | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8 | |||
| 27 | + | MOVV R4, 8(R3) | |||
| 28 | + | MOVV ·x_cgo_setenv_call(SB), R5 | |||
| 29 | + | MOVV (R5), R6 | |||
| 30 | + | CALL (R6) | |||
| 31 | + | RET | |||
| 32 | + | ||||
| 33 | + | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8 | |||
| 34 | + | MOVV R4, 8(R3) | |||
| 35 | + | MOVV ·x_cgo_unsetenv_call(SB), R5 | |||
| 36 | + | MOVV (R5), R6 | |||
| 37 | + | CALL (R6) | |||
| 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 | + | MOVV G+8(FP), R4 | |||
| 51 | + | MOVV setg+0(FP), R5 | |||
| 52 | + | CALL (R5) | |||
| 53 | + | RET | |||
| 54 | + | ||||
| 55 | + | TEXT threadentry_trampoline(SB), NOSPLIT, $16 | |||
| 56 | + | MOVV R4, 8(R3) | |||
| 57 | + | MOVV ·threadentry_call(SB), R5 | |||
| 58 | + | MOVV (R5), R6 | |||
| 59 | + | CALL (R6) | |||
| 60 | + | RET | |||
| 61 | + | ||||
| 62 | + | TEXT ·call5(SB), NOSPLIT, $0-0 | |||
| 63 | + | MOVV fn+0(FP), R9 | |||
| 64 | + | MOVV a1+8(FP), R4 | |||
| 65 | + | MOVV a2+16(FP), R5 | |||
| 66 | + | MOVV a3+24(FP), R6 | |||
| 67 | + | MOVV a4+32(FP), R7 | |||
| 68 | + | MOVV a5+40(FP), R8 | |||
| 69 | + | CALL (R9) | |||
| 70 | + | MOVV R4, ret+48(FP) | |||
| 71 | + | RET | |||
A struct_loong64.go +190 -0
| @@ -0,0 +1,190 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | package purego | |
| 5 | + | ||
| 6 | + | import ( | |
| 7 | + | "math" | |
| 8 | + | "reflect" | |
| 9 | + | "unsafe" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |
| 13 | + | outSize := outType.Size() | |
| 14 | + | switch { | |
| 15 | + | case outSize == 0: | |
| 16 | + | return reflect.New(outType).Elem() | |
| 17 | + | case outSize <= 8: | |
| 18 | + | r1 := syscall.a1 | |
| 19 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 20 | + | r1 = syscall.f1 | |
| 21 | + | if numFields == 2 { | |
| 22 | + | r1 = syscall.f2<<32 | syscall.f1 | |
| 23 | + | } | |
| 24 | + | } | |
| 25 | + | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | |
| 26 | + | case outSize <= 16: | |
| 27 | + | r1, r2 := syscall.a1, syscall.a2 | |
| 28 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |
| 29 | + | switch numFields { | |
| 30 | + | case 4: | |
| 31 | + | r1 = syscall.f2<<32 | syscall.f1 | |
| 32 | + | r2 = syscall.f4<<32 | syscall.f3 | |
| 33 | + | case 3: | |
| 34 | + | r1 = syscall.f2<<32 | syscall.f1 | |
| 35 | + | r2 = syscall.f3 | |
| 36 | + | case 2: | |
| 37 | + | r1 = syscall.f1 | |
| 38 | + | r2 = syscall.f2 | |
| 39 | + | default: | |
| 40 | + | panic("unreachable") | |
| 41 | + | } | |
| 42 | + | } | |
| 43 | + | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |
| 44 | + | default: | |
| 45 | + | // create struct from the Go pointer created above | |
| 46 | + | // weird pointer dereference to circumvent go vet | |
| 47 | + | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | |
| 48 | + | } | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | const ( | |
| 52 | + | _NO_CLASS = 0b00 | |
| 53 | + | _FLOAT = 0b01 | |
| 54 | + | _INT = 0b11 | |
| 55 | + | ) | |
| 56 | + | ||
| 57 | + | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | |
| 58 | + | if v.Type().Size() == 0 { | |
| 59 | + | return keepAlive | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | if size := v.Type().Size(); size <= 16 { | |
| 63 | + | placeRegisters(v, addFloat, addInt) | |
| 64 | + | } else { | |
| 65 | + | keepAlive = placeStack(v, keepAlive, addInt) | |
| 66 | + | } | |
| 67 | + | return keepAlive // the struct was allocated so don't panic | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | |
| 71 | + | var val uint64 | |
| 72 | + | var shift byte | |
| 73 | + | var flushed bool | |
| 74 | + | class := _NO_CLASS | |
| 75 | + | var place func(v reflect.Value) | |
| 76 | + | place = func(v reflect.Value) { | |
| 77 | + | var numFields int | |
| 78 | + | if v.Kind() == reflect.Struct { | |
| 79 | + | numFields = v.Type().NumField() | |
| 80 | + | } else { | |
| 81 | + | numFields = v.Type().Len() | |
| 82 | + | } | |
| 83 | + | for k := 0; k < numFields; k++ { | |
| 84 | + | flushed = false | |
| 85 | + | var f reflect.Value | |
| 86 | + | if v.Kind() == reflect.Struct { | |
| 87 | + | f = v.Field(k) | |
| 88 | + | } else { | |
| 89 | + | f = v.Index(k) | |
| 90 | + | } | |
| 91 | + | align := byte(f.Type().Align()*8 - 1) | |
| 92 | + | shift = (shift + align) &^ align | |
| 93 | + | if shift >= 64 { | |
| 94 | + | shift = 0 | |
| 95 | + | flushed = true | |
| 96 | + | if class == _FLOAT { | |
| 97 | + | addFloat(uintptr(val)) | |
| 98 | + | } else { | |
| 99 | + | addInt(uintptr(val)) | |
| 100 | + | } | |
| 101 | + | } | |
| 102 | + | switch f.Type().Kind() { | |
| 103 | + | case reflect.Struct: | |
| 104 | + | place(f) | |
| 105 | + | case reflect.Bool: | |
| 106 | + | if f.Bool() { | |
| 107 | + | val |= 1 | |
| 108 | + | } | |
| 109 | + | shift += 8 | |
| 110 | + | class |= _INT | |
| 111 | + | case reflect.Uint8: | |
| 112 | + | val |= f.Uint() << shift | |
| 113 | + | shift += 8 | |
| 114 | + | class |= _INT | |
| 115 | + | case reflect.Uint16: | |
| 116 | + | val |= f.Uint() << shift | |
| 117 | + | shift += 16 | |
| 118 | + | class |= _INT | |
| 119 | + | case reflect.Uint32: | |
| 120 | + | val |= f.Uint() << shift | |
| 121 | + | shift += 32 | |
| 122 | + | class |= _INT | |
| 123 | + | case reflect.Uint64, reflect.Uint, reflect.Uintptr: | |
| 124 | + | addInt(uintptr(f.Uint())) | |
| 125 | + | shift = 0 | |
| 126 | + | flushed = true | |
| 127 | + | class = _NO_CLASS | |
| 128 | + | case reflect.Int8: | |
| 129 | + | val |= uint64(f.Int()&0xFF) << shift | |
| 130 | + | shift += 8 | |
| 131 | + | class |= _INT | |
| 132 | + | case reflect.Int16: | |
| 133 | + | val |= uint64(f.Int()&0xFFFF) << shift | |
| 134 | + | shift += 16 | |
| 135 | + | class |= _INT | |
| 136 | + | case reflect.Int32: | |
| 137 | + | val |= uint64(f.Int()&0xFFFF_FFFF) << shift | |
| 138 | + | shift += 32 | |
| 139 | + | class |= _INT | |
| 140 | + | case reflect.Int64, reflect.Int: | |
| 141 | + | addInt(uintptr(f.Int())) | |
| 142 | + | shift = 0 | |
| 143 | + | flushed = true | |
| 144 | + | class = _NO_CLASS | |
| 145 | + | case reflect.Float32: | |
| 146 | + | if class == _FLOAT { | |
| 147 | + | addFloat(uintptr(val)) | |
| 148 | + | val = 0 | |
| 149 | + | shift = 0 | |
| 150 | + | } | |
| 151 | + | val |= uint64(math.Float32bits(float32(f.Float()))) << shift | |
| 152 | + | shift += 32 | |
| 153 | + | class |= _FLOAT | |
| 154 | + | case reflect.Float64: | |
| 155 | + | addFloat(uintptr(math.Float64bits(float64(f.Float())))) | |
| 156 | + | shift = 0 | |
| 157 | + | flushed = true | |
| 158 | + | class = _NO_CLASS | |
| 159 | + | case reflect.Ptr: | |
| 160 | + | addInt(f.Pointer()) | |
| 161 | + | shift = 0 | |
| 162 | + | flushed = true | |
| 163 | + | class = _NO_CLASS | |
| 164 | + | case reflect.Array: | |
| 165 | + | place(f) | |
| 166 | + | default: | |
| 167 | + | panic("purego: unsupported kind " + f.Kind().String()) | |
| 168 | + | } | |
| 169 | + | } | |
| 170 | + | } | |
| 171 | + | place(v) | |
| 172 | + | if !flushed { | |
| 173 | + | if class == _FLOAT { | |
| 174 | + | addFloat(uintptr(val)) | |
| 175 | + | } else { | |
| 176 | + | addInt(uintptr(val)) | |
| 177 | + | } | |
| 178 | + | } | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | |
| 182 | + | // Struct is too big to be placed in registers. | |
| 183 | + | // Copy to heap and place the pointer in register | |
| 184 | + | ptrStruct := reflect.New(v.Type()) | |
| 185 | + | ptrStruct.Elem().Set(v) | |
| 186 | + | ptr := ptrStruct.Elem().Addr().UnsafePointer() | |
| 187 | + | keepAlive = append(keepAlive, ptr) | |
| 188 | + | addInt(uintptr(ptr)) | |
| 189 | + | return keepAlive | |
| 190 | + | } | |
| @@ -0,0 +1,190 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | package purego | |||
| 5 | + | ||||
| 6 | + | import ( | |||
| 7 | + | "math" | |||
| 8 | + | "reflect" | |||
| 9 | + | "unsafe" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |||
| 13 | + | outSize := outType.Size() | |||
| 14 | + | switch { | |||
| 15 | + | case outSize == 0: | |||
| 16 | + | return reflect.New(outType).Elem() | |||
| 17 | + | case outSize <= 8: | |||
| 18 | + | r1 := syscall.a1 | |||
| 19 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |||
| 20 | + | r1 = syscall.f1 | |||
| 21 | + | if numFields == 2 { | |||
| 22 | + | r1 = syscall.f2<<32 | syscall.f1 | |||
| 23 | + | } | |||
| 24 | + | } | |||
| 25 | + | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() | |||
| 26 | + | case outSize <= 16: | |||
| 27 | + | r1, r2 := syscall.a1, syscall.a2 | |||
| 28 | + | if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats { | |||
| 29 | + | switch numFields { | |||
| 30 | + | case 4: | |||
| 31 | + | r1 = syscall.f2<<32 | syscall.f1 | |||
| 32 | + | r2 = syscall.f4<<32 | syscall.f3 | |||
| 33 | + | case 3: | |||
| 34 | + | r1 = syscall.f2<<32 | syscall.f1 | |||
| 35 | + | r2 = syscall.f3 | |||
| 36 | + | case 2: | |||
| 37 | + | r1 = syscall.f1 | |||
| 38 | + | r2 = syscall.f2 | |||
| 39 | + | default: | |||
| 40 | + | panic("unreachable") | |||
| 41 | + | } | |||
| 42 | + | } | |||
| 43 | + | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() | |||
| 44 | + | default: | |||
| 45 | + | // create struct from the Go pointer created above | |||
| 46 | + | // weird pointer dereference to circumvent go vet | |||
| 47 | + | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | |||
| 48 | + | } | |||
| 49 | + | } | |||
| 50 | + | ||||
| 51 | + | const ( | |||
| 52 | + | _NO_CLASS = 0b00 | |||
| 53 | + | _FLOAT = 0b01 | |||
| 54 | + | _INT = 0b11 | |||
| 55 | + | ) | |||
| 56 | + | ||||
| 57 | + | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | |||
| 58 | + | if v.Type().Size() == 0 { | |||
| 59 | + | return keepAlive | |||
| 60 | + | } | |||
| 61 | + | ||||
| 62 | + | if size := v.Type().Size(); size <= 16 { | |||
| 63 | + | placeRegisters(v, addFloat, addInt) | |||
| 64 | + | } else { | |||
| 65 | + | keepAlive = placeStack(v, keepAlive, addInt) | |||
| 66 | + | } | |||
| 67 | + | return keepAlive // the struct was allocated so don't panic | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | |||
| 71 | + | var val uint64 | |||
| 72 | + | var shift byte | |||
| 73 | + | var flushed bool | |||
| 74 | + | class := _NO_CLASS | |||
| 75 | + | var place func(v reflect.Value) | |||
| 76 | + | place = func(v reflect.Value) { | |||
| 77 | + | var numFields int | |||
| 78 | + | if v.Kind() == reflect.Struct { | |||
| 79 | + | numFields = v.Type().NumField() | |||
| 80 | + | } else { | |||
| 81 | + | numFields = v.Type().Len() | |||
| 82 | + | } | |||
| 83 | + | for k := 0; k < numFields; k++ { | |||
| 84 | + | flushed = false | |||
| 85 | + | var f reflect.Value | |||
| 86 | + | if v.Kind() == reflect.Struct { | |||
| 87 | + | f = v.Field(k) | |||
| 88 | + | } else { | |||
| 89 | + | f = v.Index(k) | |||
| 90 | + | } | |||
| 91 | + | align := byte(f.Type().Align()*8 - 1) | |||
| 92 | + | shift = (shift + align) &^ align | |||
| 93 | + | if shift >= 64 { | |||
| 94 | + | shift = 0 | |||
| 95 | + | flushed = true | |||
| 96 | + | if class == _FLOAT { | |||
| 97 | + | addFloat(uintptr(val)) | |||
| 98 | + | } else { | |||
| 99 | + | addInt(uintptr(val)) | |||
| 100 | + | } | |||
| 101 | + | } | |||
| 102 | + | switch f.Type().Kind() { | |||
| 103 | + | case reflect.Struct: | |||
| 104 | + | place(f) | |||
| 105 | + | case reflect.Bool: | |||
| 106 | + | if f.Bool() { | |||
| 107 | + | val |= 1 | |||
| 108 | + | } | |||
| 109 | + | shift += 8 | |||
| 110 | + | class |= _INT | |||
| 111 | + | case reflect.Uint8: | |||
| 112 | + | val |= f.Uint() << shift | |||
| 113 | + | shift += 8 | |||
| 114 | + | class |= _INT | |||
| 115 | + | case reflect.Uint16: | |||
| 116 | + | val |= f.Uint() << shift | |||
| 117 | + | shift += 16 | |||
| 118 | + | class |= _INT | |||
| 119 | + | case reflect.Uint32: | |||
| 120 | + | val |= f.Uint() << shift | |||
| 121 | + | shift += 32 | |||
| 122 | + | class |= _INT | |||
| 123 | + | case reflect.Uint64, reflect.Uint, reflect.Uintptr: | |||
| 124 | + | addInt(uintptr(f.Uint())) | |||
| 125 | + | shift = 0 | |||
| 126 | + | flushed = true | |||
| 127 | + | class = _NO_CLASS | |||
| 128 | + | case reflect.Int8: | |||
| 129 | + | val |= uint64(f.Int()&0xFF) << shift | |||
| 130 | + | shift += 8 | |||
| 131 | + | class |= _INT | |||
| 132 | + | case reflect.Int16: | |||
| 133 | + | val |= uint64(f.Int()&0xFFFF) << shift | |||
| 134 | + | shift += 16 | |||
| 135 | + | class |= _INT | |||
| 136 | + | case reflect.Int32: | |||
| 137 | + | val |= uint64(f.Int()&0xFFFF_FFFF) << shift | |||
| 138 | + | shift += 32 | |||
| 139 | + | class |= _INT | |||
| 140 | + | case reflect.Int64, reflect.Int: | |||
| 141 | + | addInt(uintptr(f.Int())) | |||
| 142 | + | shift = 0 | |||
| 143 | + | flushed = true | |||
| 144 | + | class = _NO_CLASS | |||
| 145 | + | case reflect.Float32: | |||
| 146 | + | if class == _FLOAT { | |||
| 147 | + | addFloat(uintptr(val)) | |||
| 148 | + | val = 0 | |||
| 149 | + | shift = 0 | |||
| 150 | + | } | |||
| 151 | + | val |= uint64(math.Float32bits(float32(f.Float()))) << shift | |||
| 152 | + | shift += 32 | |||
| 153 | + | class |= _FLOAT | |||
| 154 | + | case reflect.Float64: | |||
| 155 | + | addFloat(uintptr(math.Float64bits(float64(f.Float())))) | |||
| 156 | + | shift = 0 | |||
| 157 | + | flushed = true | |||
| 158 | + | class = _NO_CLASS | |||
| 159 | + | case reflect.Ptr: | |||
| 160 | + | addInt(f.Pointer()) | |||
| 161 | + | shift = 0 | |||
| 162 | + | flushed = true | |||
| 163 | + | class = _NO_CLASS | |||
| 164 | + | case reflect.Array: | |||
| 165 | + | place(f) | |||
| 166 | + | default: | |||
| 167 | + | panic("purego: unsupported kind " + f.Kind().String()) | |||
| 168 | + | } | |||
| 169 | + | } | |||
| 170 | + | } | |||
| 171 | + | place(v) | |||
| 172 | + | if !flushed { | |||
| 173 | + | if class == _FLOAT { | |||
| 174 | + | addFloat(uintptr(val)) | |||
| 175 | + | } else { | |||
| 176 | + | addInt(uintptr(val)) | |||
| 177 | + | } | |||
| 178 | + | } | |||
| 179 | + | } | |||
| 180 | + | ||||
| 181 | + | func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any { | |||
| 182 | + | // Struct is too big to be placed in registers. | |||
| 183 | + | // Copy to heap and place the pointer in register | |||
| 184 | + | ptrStruct := reflect.New(v.Type()) | |||
| 185 | + | ptrStruct.Elem().Set(v) | |||
| 186 | + | ptr := ptrStruct.Elem().Addr().UnsafePointer() | |||
| 187 | + | keepAlive = append(keepAlive, ptr) | |||
| 188 | + | addInt(uintptr(ptr)) | |||
| 189 | + | return keepAlive | |||
| 190 | + | } | |||
M struct_other.go +1 -1
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build !amd64 && !arm64 | |
| 4 | + | //go:build !amd64 && !arm64 && !loong64 | |
| 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: 2024 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2024 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build !amd64 && !arm64 | 4 | + | //go:build !amd64 && !arm64 && !loong64 |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
A sys_loong64.s +96 -0
| @@ -0,0 +1,96 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build linux | |
| 5 | + | ||
| 6 | + | #include "textflag.h" | |
| 7 | + | #include "go_asm.h" | |
| 8 | + | #include "funcdata.h" | |
| 9 | + | ||
| 10 | + | #define STACK_SIZE 64 | |
| 11 | + | #define PTR_ADDRESS (STACK_SIZE - 8) | |
| 12 | + | ||
| 13 | + | // syscall15X calls a function in libc on behalf of the syscall package. | |
| 14 | + | // syscall15X takes a pointer to a struct like: | |
| 15 | + | // struct { | |
| 16 | + | // fn uintptr | |
| 17 | + | // a1 uintptr | |
| 18 | + | // a2 uintptr | |
| 19 | + | // a3 uintptr | |
| 20 | + | // a4 uintptr | |
| 21 | + | // a5 uintptr | |
| 22 | + | // a6 uintptr | |
| 23 | + | // a7 uintptr | |
| 24 | + | // a8 uintptr | |
| 25 | + | // a9 uintptr | |
| 26 | + | // a10 uintptr | |
| 27 | + | // a11 uintptr | |
| 28 | + | // a12 uintptr | |
| 29 | + | // a13 uintptr | |
| 30 | + | // a14 uintptr | |
| 31 | + | // a15 uintptr | |
| 32 | + | // r1 uintptr | |
| 33 | + | // r2 uintptr | |
| 34 | + | // err uintptr | |
| 35 | + | // } | |
| 36 | + | // syscall15X must be called on the g0 stack with the | |
| 37 | + | // C calling convention (use libcCall). | |
| 38 | + | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |
| 39 | + | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |
| 40 | + | TEXT syscall15X(SB), NOSPLIT, $0 | |
| 41 | + | // push structure pointer | |
| 42 | + | SUBV $STACK_SIZE, R3 | |
| 43 | + | MOVV R4, PTR_ADDRESS(R3) | |
| 44 | + | MOVV R4, R13 | |
| 45 | + | ||
| 46 | + | MOVD syscall15Args_f1(R13), F0 // f1 | |
| 47 | + | MOVD syscall15Args_f2(R13), F1 // f2 | |
| 48 | + | MOVD syscall15Args_f3(R13), F2 // f3 | |
| 49 | + | MOVD syscall15Args_f4(R13), F3 // f4 | |
| 50 | + | MOVD syscall15Args_f5(R13), F4 // f5 | |
| 51 | + | MOVD syscall15Args_f6(R13), F5 // f6 | |
| 52 | + | MOVD syscall15Args_f7(R13), F6 // f7 | |
| 53 | + | MOVD syscall15Args_f8(R13), F7 // f8 | |
| 54 | + | ||
| 55 | + | MOVV syscall15Args_a1(R13), R4 // a1 | |
| 56 | + | MOVV syscall15Args_a2(R13), R5 // a2 | |
| 57 | + | MOVV syscall15Args_a3(R13), R6 // a3 | |
| 58 | + | MOVV syscall15Args_a4(R13), R7 // a4 | |
| 59 | + | MOVV syscall15Args_a5(R13), R8 // a5 | |
| 60 | + | MOVV syscall15Args_a6(R13), R9 // a6 | |
| 61 | + | MOVV syscall15Args_a7(R13), R10 // a7 | |
| 62 | + | MOVV syscall15Args_a8(R13), R11 // a8 | |
| 63 | + | ||
| 64 | + | // push a9-a15 onto stack | |
| 65 | + | MOVV syscall15Args_a9(R13), R12 | |
| 66 | + | MOVV R12, 0(R3) | |
| 67 | + | MOVV syscall15Args_a10(R13), R12 | |
| 68 | + | MOVV R12, 8(R3) | |
| 69 | + | MOVV syscall15Args_a11(R13), R12 | |
| 70 | + | MOVV R12, 16(R3) | |
| 71 | + | MOVV syscall15Args_a12(R13), R12 | |
| 72 | + | MOVV R12, 24(R3) | |
| 73 | + | MOVV syscall15Args_a13(R13), R12 | |
| 74 | + | MOVV R12, 32(R3) | |
| 75 | + | MOVV syscall15Args_a14(R13), R12 | |
| 76 | + | MOVV R12, 40(R3) | |
| 77 | + | MOVV syscall15Args_a15(R13), R12 | |
| 78 | + | MOVV R12, 48(R3) | |
| 79 | + | ||
| 80 | + | MOVV syscall15Args_fn(R13), R12 | |
| 81 | + | JAL (R12) | |
| 82 | + | ||
| 83 | + | // pop structure pointer | |
| 84 | + | MOVV PTR_ADDRESS(R3), R13 | |
| 85 | + | ADDV $STACK_SIZE, R3 | |
| 86 | + | ||
| 87 | + | // save R4, R5 | |
| 88 | + | MOVV R4, syscall15Args_a1(R13) | |
| 89 | + | MOVV R5, syscall15Args_a2(R13) | |
| 90 | + | ||
| 91 | + | // save f0-f3 | |
| 92 | + | MOVD F0, syscall15Args_f1(R13) | |
| 93 | + | MOVD F1, syscall15Args_f2(R13) | |
| 94 | + | MOVD F2, syscall15Args_f3(R13) | |
| 95 | + | MOVD F3, syscall15Args_f4(R13) | |
| 96 | + | RET | |
| @@ -0,0 +1,96 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build linux | |||
| 5 | + | ||||
| 6 | + | #include "textflag.h" | |||
| 7 | + | #include "go_asm.h" | |||
| 8 | + | #include "funcdata.h" | |||
| 9 | + | ||||
| 10 | + | #define STACK_SIZE 64 | |||
| 11 | + | #define PTR_ADDRESS (STACK_SIZE - 8) | |||
| 12 | + | ||||
| 13 | + | // syscall15X calls a function in libc on behalf of the syscall package. | |||
| 14 | + | // syscall15X takes a pointer to a struct like: | |||
| 15 | + | // struct { | |||
| 16 | + | // fn uintptr | |||
| 17 | + | // a1 uintptr | |||
| 18 | + | // a2 uintptr | |||
| 19 | + | // a3 uintptr | |||
| 20 | + | // a4 uintptr | |||
| 21 | + | // a5 uintptr | |||
| 22 | + | // a6 uintptr | |||
| 23 | + | // a7 uintptr | |||
| 24 | + | // a8 uintptr | |||
| 25 | + | // a9 uintptr | |||
| 26 | + | // a10 uintptr | |||
| 27 | + | // a11 uintptr | |||
| 28 | + | // a12 uintptr | |||
| 29 | + | // a13 uintptr | |||
| 30 | + | // a14 uintptr | |||
| 31 | + | // a15 uintptr | |||
| 32 | + | // r1 uintptr | |||
| 33 | + | // r2 uintptr | |||
| 34 | + | // err uintptr | |||
| 35 | + | // } | |||
| 36 | + | // syscall15X must be called on the g0 stack with the | |||
| 37 | + | // C calling convention (use libcCall). | |||
| 38 | + | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8 | |||
| 39 | + | DATA ·syscall15XABI0(SB)/8, $syscall15X(SB) | |||
| 40 | + | TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 41 | + | // push structure pointer | |||
| 42 | + | SUBV $STACK_SIZE, R3 | |||
| 43 | + | MOVV R4, PTR_ADDRESS(R3) | |||
| 44 | + | MOVV R4, R13 | |||
| 45 | + | ||||
| 46 | + | MOVD syscall15Args_f1(R13), F0 // f1 | |||
| 47 | + | MOVD syscall15Args_f2(R13), F1 // f2 | |||
| 48 | + | MOVD syscall15Args_f3(R13), F2 // f3 | |||
| 49 | + | MOVD syscall15Args_f4(R13), F3 // f4 | |||
| 50 | + | MOVD syscall15Args_f5(R13), F4 // f5 | |||
| 51 | + | MOVD syscall15Args_f6(R13), F5 // f6 | |||
| 52 | + | MOVD syscall15Args_f7(R13), F6 // f7 | |||
| 53 | + | MOVD syscall15Args_f8(R13), F7 // f8 | |||
| 54 | + | ||||
| 55 | + | MOVV syscall15Args_a1(R13), R4 // a1 | |||
| 56 | + | MOVV syscall15Args_a2(R13), R5 // a2 | |||
| 57 | + | MOVV syscall15Args_a3(R13), R6 // a3 | |||
| 58 | + | MOVV syscall15Args_a4(R13), R7 // a4 | |||
| 59 | + | MOVV syscall15Args_a5(R13), R8 // a5 | |||
| 60 | + | MOVV syscall15Args_a6(R13), R9 // a6 | |||
| 61 | + | MOVV syscall15Args_a7(R13), R10 // a7 | |||
| 62 | + | MOVV syscall15Args_a8(R13), R11 // a8 | |||
| 63 | + | ||||
| 64 | + | // push a9-a15 onto stack | |||
| 65 | + | MOVV syscall15Args_a9(R13), R12 | |||
| 66 | + | MOVV R12, 0(R3) | |||
| 67 | + | MOVV syscall15Args_a10(R13), R12 | |||
| 68 | + | MOVV R12, 8(R3) | |||
| 69 | + | MOVV syscall15Args_a11(R13), R12 | |||
| 70 | + | MOVV R12, 16(R3) | |||
| 71 | + | MOVV syscall15Args_a12(R13), R12 | |||
| 72 | + | MOVV R12, 24(R3) | |||
| 73 | + | MOVV syscall15Args_a13(R13), R12 | |||
| 74 | + | MOVV R12, 32(R3) | |||
| 75 | + | MOVV syscall15Args_a14(R13), R12 | |||
| 76 | + | MOVV R12, 40(R3) | |||
| 77 | + | MOVV syscall15Args_a15(R13), R12 | |||
| 78 | + | MOVV R12, 48(R3) | |||
| 79 | + | ||||
| 80 | + | MOVV syscall15Args_fn(R13), R12 | |||
| 81 | + | JAL (R12) | |||
| 82 | + | ||||
| 83 | + | // pop structure pointer | |||
| 84 | + | MOVV PTR_ADDRESS(R3), R13 | |||
| 85 | + | ADDV $STACK_SIZE, R3 | |||
| 86 | + | ||||
| 87 | + | // save R4, R5 | |||
| 88 | + | MOVV R4, syscall15Args_a1(R13) | |||
| 89 | + | MOVV R5, syscall15Args_a2(R13) | |||
| 90 | + | ||||
| 91 | + | // save f0-f3 | |||
| 92 | + | MOVD F0, syscall15Args_f1(R13) | |||
| 93 | + | MOVD F1, syscall15Args_f2(R13) | |||
| 94 | + | MOVD F2, syscall15Args_f3(R13) | |||
| 95 | + | MOVD F3, syscall15Args_f4(R13) | |||
| 96 | + | RET | |||
A sys_unix_loong64.s +75 -0
| @@ -0,0 +1,75 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build linux | |
| 5 | + | ||
| 6 | + | #include "textflag.h" | |
| 7 | + | #include "go_asm.h" | |
| 8 | + | #include "funcdata.h" | |
| 9 | + | #include "abi_loong64.h" | |
| 10 | + | ||
| 11 | + | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |
| 12 | + | NO_LOCAL_POINTERS | |
| 13 | + | ||
| 14 | + | SUBV $(16*8), R3, R14 | |
| 15 | + | MOVD F0, 0(R14) | |
| 16 | + | MOVD F1, 8(R14) | |
| 17 | + | MOVD F2, 16(R14) | |
| 18 | + | MOVD F3, 24(R14) | |
| 19 | + | MOVD F4, 32(R14) | |
| 20 | + | MOVD F5, 40(R14) | |
| 21 | + | MOVD F6, 48(R14) | |
| 22 | + | MOVD F7, 56(R14) | |
| 23 | + | MOVV R4, 64(R14) | |
| 24 | + | MOVV R5, 72(R14) | |
| 25 | + | MOVV R6, 80(R14) | |
| 26 | + | MOVV R7, 88(R14) | |
| 27 | + | MOVV R8, 96(R14) | |
| 28 | + | MOVV R9, 104(R14) | |
| 29 | + | MOVV R10, 112(R14) | |
| 30 | + | MOVV R11, 120(R14) | |
| 31 | + | ||
| 32 | + | // Adjust SP by frame size. | |
| 33 | + | SUBV $(22*8), R3 | |
| 34 | + | ||
| 35 | + | // It is important to save R30 because the go assembler | |
| 36 | + | // uses it for move instructions for a variable. | |
| 37 | + | // This line: | |
| 38 | + | // MOVV ·callbackWrap_call(SB), R4 | |
| 39 | + | // Creates the instructions: | |
| 40 | + | // PCALAU12I off1(PC), R30 | |
| 41 | + | // MOVV off2(R30), R4 | |
| 42 | + | // R30 is a callee saved register so we are responsible | |
| 43 | + | // for ensuring its value doesn't change. So save it and | |
| 44 | + | // restore it at the end of this function. | |
| 45 | + | // R1 is the link register. crosscall2 doesn't save it | |
| 46 | + | // so it's saved here. | |
| 47 | + | MOVV R1, 0(R3) | |
| 48 | + | MOVV R30, 8(R3) | |
| 49 | + | ||
| 50 | + | // Create a struct callbackArgs on our stack. | |
| 51 | + | MOVV $(callbackArgs__size)(R3), R13 | |
| 52 | + | MOVV R12, callbackArgs_index(R13) // callback index | |
| 53 | + | MOVV R14, callbackArgs_args(R13) // address of args vector | |
| 54 | + | MOVV $0, callbackArgs_result(R13) // result | |
| 55 | + | ||
| 56 | + | // Move parameters into registers | |
| 57 | + | // Get the ABIInternal function pointer | |
| 58 | + | // without <ABIInternal> by using a closure. | |
| 59 | + | MOVV ·callbackWrap_call(SB), R4 | |
| 60 | + | MOVV (R4), R4 // fn unsafe.Pointer | |
| 61 | + | MOVV R13, R5 // frame (&callbackArgs{...}) | |
| 62 | + | MOVV $0, R7 // ctxt uintptr | |
| 63 | + | ||
| 64 | + | JAL crosscall2(SB) | |
| 65 | + | ||
| 66 | + | // Get callback result. | |
| 67 | + | MOVV $(callbackArgs__size)(R3), R13 | |
| 68 | + | MOVV callbackArgs_result(R13), R4 | |
| 69 | + | ||
| 70 | + | // Restore LR and R30 | |
| 71 | + | MOVV 0(R3), R1 | |
| 72 | + | MOVV 8(R3), R30 | |
| 73 | + | ADDV $(22*8), R3 | |
| 74 | + | ||
| 75 | + | RET | |
| @@ -0,0 +1,75 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build linux | |||
| 5 | + | ||||
| 6 | + | #include "textflag.h" | |||
| 7 | + | #include "go_asm.h" | |||
| 8 | + | #include "funcdata.h" | |||
| 9 | + | #include "abi_loong64.h" | |||
| 10 | + | ||||
| 11 | + | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 12 | + | NO_LOCAL_POINTERS | |||
| 13 | + | ||||
| 14 | + | SUBV $(16*8), R3, R14 | |||
| 15 | + | MOVD F0, 0(R14) | |||
| 16 | + | MOVD F1, 8(R14) | |||
| 17 | + | MOVD F2, 16(R14) | |||
| 18 | + | MOVD F3, 24(R14) | |||
| 19 | + | MOVD F4, 32(R14) | |||
| 20 | + | MOVD F5, 40(R14) | |||
| 21 | + | MOVD F6, 48(R14) | |||
| 22 | + | MOVD F7, 56(R14) | |||
| 23 | + | MOVV R4, 64(R14) | |||
| 24 | + | MOVV R5, 72(R14) | |||
| 25 | + | MOVV R6, 80(R14) | |||
| 26 | + | MOVV R7, 88(R14) | |||
| 27 | + | MOVV R8, 96(R14) | |||
| 28 | + | MOVV R9, 104(R14) | |||
| 29 | + | MOVV R10, 112(R14) | |||
| 30 | + | MOVV R11, 120(R14) | |||
| 31 | + | ||||
| 32 | + | // Adjust SP by frame size. | |||
| 33 | + | SUBV $(22*8), R3 | |||
| 34 | + | ||||
| 35 | + | // It is important to save R30 because the go assembler | |||
| 36 | + | // uses it for move instructions for a variable. | |||
| 37 | + | // This line: | |||
| 38 | + | // MOVV ·callbackWrap_call(SB), R4 | |||
| 39 | + | // Creates the instructions: | |||
| 40 | + | // PCALAU12I off1(PC), R30 | |||
| 41 | + | // MOVV off2(R30), R4 | |||
| 42 | + | // R30 is a callee saved register so we are responsible | |||
| 43 | + | // for ensuring its value doesn't change. So save it and | |||
| 44 | + | // restore it at the end of this function. | |||
| 45 | + | // R1 is the link register. crosscall2 doesn't save it | |||
| 46 | + | // so it's saved here. | |||
| 47 | + | MOVV R1, 0(R3) | |||
| 48 | + | MOVV R30, 8(R3) | |||
| 49 | + | ||||
| 50 | + | // Create a struct callbackArgs on our stack. | |||
| 51 | + | MOVV $(callbackArgs__size)(R3), R13 | |||
| 52 | + | MOVV R12, callbackArgs_index(R13) // callback index | |||
| 53 | + | MOVV R14, callbackArgs_args(R13) // address of args vector | |||
| 54 | + | MOVV $0, callbackArgs_result(R13) // result | |||
| 55 | + | ||||
| 56 | + | // Move parameters into registers | |||
| 57 | + | // Get the ABIInternal function pointer | |||
| 58 | + | // without <ABIInternal> by using a closure. | |||
| 59 | + | MOVV ·callbackWrap_call(SB), R4 | |||
| 60 | + | MOVV (R4), R4 // fn unsafe.Pointer | |||
| 61 | + | MOVV R13, R5 // frame (&callbackArgs{...}) | |||
| 62 | + | MOVV $0, R7 // ctxt uintptr | |||
| 63 | + | ||||
| 64 | + | JAL crosscall2(SB) | |||
| 65 | + | ||||
| 66 | + | // Get callback result. | |||
| 67 | + | MOVV $(callbackArgs__size)(R3), R13 | |||
| 68 | + | MOVV callbackArgs_result(R13), R4 | |||
| 69 | + | ||||
| 70 | + | // Restore LR and R30 | |||
| 71 | + | MOVV 0(R3), R1 | |||
| 72 | + | MOVV 8(R3), R30 | |||
| 73 | + | ADDV $(22*8), R3 | |||
| 74 | + | ||||
| 75 | + | 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) | |
| 4 | + | //go:build cgo && !(amd64 || arm64 || loong64) | |
| 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") | |
| 20 | + | panic("purego: NewCallback on Linux is only supported on amd64/arm64/loong64") | |
| 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) | 4 | + | //go:build cgo && !(amd64 || arm64 || loong64) |
| 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") | 20 | + | panic("purego: NewCallback on Linux is only supported on amd64/arm64/loong64") |
| 21 | } | 21 | } | ||
M syscall_sysv.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 darwin || freebsd || (linux && (amd64 || arm64)) || netbsd | |
| 4 | + | //go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd | |
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| @@ -217,7 +217,7 @@ func callbackasmAddr(i int) uintptr { | |||
| 217 | 217 | panic("purego: unsupported architecture") | |
| 218 | 218 | case "386", "amd64": | |
| 219 | 219 | entrySize = 5 | |
| 220 | - | case "arm", "arm64": | |
| 220 | + | case "arm", "arm64", "loong64": | |
| 221 | 221 | // On ARM and ARM64, each entry is a MOV instruction | |
| 222 | 222 | // followed by a branch instruction | |
| 223 | 223 | entrySize = 8 | |
| @@ -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)) || netbsd | 4 | + | //go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| @@ -217,7 +217,7 @@ 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": | 220 | + | case "arm", "arm64", "loong64": |
| 221 | // On ARM and ARM64, each entry is a MOV instruction | 221 | // On ARM and ARM64, each entry is a MOV instruction | ||
| 222 | // followed by a branch instruction | 222 | // followed by a branch instruction | ||
| 223 | entrySize = 8 | 223 | entrySize = 8 | ||
M wincallback.go +29 -0
| @@ -72,7 +72,36 @@ TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||
| 72 | 72 | } | |
| 73 | 73 | } | |
| 74 | 74 | ||
| 75 | + | func genasmLoong64() { | |
| 76 | + | var buf bytes.Buffer | |
| 77 | + | ||
| 78 | + | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. | |
| 79 | + | ||
| 80 | + | //go:build darwin || freebsd || linux || netbsd | |
| 81 | + | ||
| 82 | + | // External code calls into callbackasm at an offset corresponding | |
| 83 | + | // to the callback index. Callbackasm is a table of MOVV and JMP instructions. | |
| 84 | + | // The MOVV instruction loads R12 with the callback index, and the | |
| 85 | + | // JMP instruction branches to callbackasm1. | |
| 86 | + | // callbackasm1 takes the callback index from R12 and | |
| 87 | + | // indexes into an array that stores information about each callback. | |
| 88 | + | // It then calls the Go implementation for that callback. | |
| 89 | + | #include "textflag.h" | |
| 90 | + | ||
| 91 | + | TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |
| 92 | + | `) | |
| 93 | + | for i := 0; i < maxCallback; i++ { | |
| 94 | + | fmt.Fprintf(&buf, "\tMOVV\t$%d, R13\n", i) | |
| 95 | + | buf.WriteString("\tJMP\tcallbackasm1(SB)\n") | |
| 96 | + | } | |
| 97 | + | if err := os.WriteFile("zcallback_loong64.s", buf.Bytes(), 0644); err != nil { | |
| 98 | + | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) | |
| 99 | + | os.Exit(2) | |
| 100 | + | } | |
| 101 | + | } | |
| 102 | + | ||
| 75 | 103 | func main() { | |
| 76 | 104 | genasmAmd64() | |
| 77 | 105 | genasmArm64() | |
| 106 | + | genasmLoong64() | |
| 78 | 107 | } | |
| @@ -72,7 +72,36 @@ TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||||
| 72 | } | 72 | } | ||
| 73 | } | 73 | } | ||
| 74 | 74 | ||||
| 75 | + | func genasmLoong64() { | |||
| 76 | + | var buf bytes.Buffer | |||
| 77 | + | ||||
| 78 | + | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. | |||
| 79 | + | ||||
| 80 | + | //go:build darwin || freebsd || linux || netbsd | |||
| 81 | + | ||||
| 82 | + | // External code calls into callbackasm at an offset corresponding | |||
| 83 | + | // to the callback index. Callbackasm is a table of MOVV and JMP instructions. | |||
| 84 | + | // The MOVV instruction loads R12 with the callback index, and the | |||
| 85 | + | // JMP instruction branches to callbackasm1. | |||
| 86 | + | // callbackasm1 takes the callback index from R12 and | |||
| 87 | + | // indexes into an array that stores information about each callback. | |||
| 88 | + | // It then calls the Go implementation for that callback. | |||
| 89 | + | #include "textflag.h" | |||
| 90 | + | ||||
| 91 | + | TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||
| 92 | + | `) | |||
| 93 | + | for i := 0; i < maxCallback; i++ { | |||
| 94 | + | fmt.Fprintf(&buf, "\tMOVV\t$%d, R13\n", i) | |||
| 95 | + | buf.WriteString("\tJMP\tcallbackasm1(SB)\n") | |||
| 96 | + | } | |||
| 97 | + | if err := os.WriteFile("zcallback_loong64.s", buf.Bytes(), 0644); err != nil { | |||
| 98 | + | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) | |||
| 99 | + | os.Exit(2) | |||
| 100 | + | } | |||
| 101 | + | } | |||
| 102 | + | ||||
| 75 | func main() { | 103 | func main() { | ||
| 76 | genasmAmd64() | 104 | genasmAmd64() | ||
| 77 | genasmArm64() | 105 | genasmArm64() | ||
| 106 | + | genasmLoong64() | |||
| 78 | } | 107 | } | ||