detached 0457326

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 UnverifiedB5690EEEBB952194 The key is unknown
19 files · +4772 -16
M .github/workflows/test.yml +20 -1
@@ -32,7 +32,7 @@ jobs:
3232 if: runner.os == 'Linux'
3333 run: |
3434 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
3636
3737 - name: Set up the prerequisites
3838 if: runner.os == 'Windows'
@@ -74,6 +74,13 @@ jobs:
7474 env GOOS=netbsd GOARCH=amd64 go build -v ./...
7575 env GOOS=netbsd GOARCH=arm64 go build -v ./...
7676
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+
7784 - name: go build (plugin)
7885 if: runner.os == 'Linux' || runner.os == 'macOS'
7986 run:
@@ -123,6 +130,18 @@ jobs:
123130 go env -u CC
124131 go env -u CXX
125132
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+
126145 - name: go test (Windows 386)
127146 if: runner.os == 'Windows'
128147 run: |
@@ -32,7 +32,7 @@ jobs:
32 if: runner.os == 'Linux'32 if: runner.os == 'Linux'
33 run: |33 run: |
34 sudo apt-get update34 sudo apt-get update
35- sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qemu-user35+ 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 prerequisites37 - 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 CC130 go env -u CC
124 go env -u CXX131 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.
2727 ## Supported Platforms
2828
2929 - **FreeBSD**: amd64, arm64
30-- **Linux**: amd64, arm64
30+- **Linux**: amd64, arm64, loong64
3131 - **macOS / iOS**: amd64, arm64
3232 - **Windows**: 386*, amd64, arm*, arm64
3333
@@ -27,7 +27,7 @@ except for float arguments and return values.
27 ## Supported Platforms27 ## Supported Platforms
28 28
29 - **FreeBSD**: amd64, arm6429 - **FreeBSD**: amd64, arm64
30-- **Linux**: amd64, arm6430+- **Linux**: amd64, arm64, loong64
31 - **macOS / iOS**: amd64, arm6431 - **macOS / iOS**: amd64, arm64
32 - **Windows**: 386*, amd64, arm*, arm6432 - **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 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2023 The Ebitengine Authors
33
4-//go:build darwin || (linux && (amd64 || arm64))
4+//go:build darwin || (linux && (amd64 || arm64 || loong64))
55
66 package purego_test
77
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2023 The Ebitengine Authors2 // 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_test6 package purego_test
7 7
M func.go +14 -4
@@ -124,7 +124,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
124124 panic("purego: cfn is nil")
125125 }
126126 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" {
128128 panic("purego: float returns are not supported")
129129 }
130130 {
@@ -257,7 +257,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
257257 var arm64_r8 uintptr
258258 if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
259259 outType := ty.Out(0)
260- if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize {
260+ if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64") && outType.Size() > maxRegAllocStructSize {
261261 val := reflect.New(outType)
262262 keepAlive = append(keepAlive, val)
263263 addInt(val.Pointer())
@@ -310,7 +310,17 @@ func RegisterFunc(fptr any, cfn uintptr) {
310310 syscall := thePool.Get().(*syscall15Args)
311311 defer thePool.Put(syscall)
312312
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" {
314324 // Use the normal arm64 calling convention even on Windows
315325 *syscall = syscall15Args{
316326 cfn,
@@ -467,7 +477,7 @@ func roundUpTo8(val uintptr) uintptr {
467477
468478 func numOfIntegerRegisters() int {
469479 switch runtime.GOARCH {
470- case "arm64":
480+ case "arm64", "loong64":
471481 return 8
472482 case "amd64":
473483 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 uintptr257 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 Windows324 // 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 8481 return 8
472 case "amd64":482 case "amd64":
473 return 6483 return 6
M func_test.go +3 -3
@@ -49,7 +49,7 @@ func TestRegisterFunc(t *testing.T) {
4949 }
5050
5151 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" {
5353 t.Skip("Platform doesn't support Floats")
5454 return
5555 }
@@ -78,7 +78,7 @@ func Test_qsort(t *testing.T) {
7878 }
7979
8080 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" {
8282 t.Skip("Platform doesn't support Floats")
8383 return
8484 }
@@ -117,7 +117,7 @@ func TestRegisterFunc_Floats(t *testing.T) {
117117 }
118118
119119 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" {
121121 t.Skip("Platform doesn't support callbacks")
122122 return
123123 }
@@ -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 return54 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 return83 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 return122 return
123 }123 }
M internal/cgo/syscall_cgo_unix.go +1 -1
@@ -1,7 +1,7 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
4-//go:build freebsd || (linux && !(arm64 || amd64)) || netbsd
4+//go:build freebsd || (linux && !(arm64 || amd64 || loong64)) || netbsd
55
66 package cgo
77
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4-//go:build freebsd || (linux && !(arm64 || amd64)) || netbsd4+//go:build freebsd || (linux && !(arm64 || amd64 || loong64)) || netbsd
5 5
6 package cgo6 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 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
33
4-//go:build !amd64 && !arm64
4+//go:build !amd64 && !arm64 && !loong64
55
66 package purego
77
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
3 3
4-//go:build !amd64 && !arm644+//go:build !amd64 && !arm64 && !loong64
5 5
6 package purego6 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 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
4-//go:build cgo && !(amd64 || arm64)
4+//go:build cgo && !(amd64 || arm64 || loong64)
55
66 package purego
77
@@ -17,5 +17,5 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a
1717 }
1818
1919 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")
2121 }
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors2 // 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 purego6 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 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
4-//go:build darwin || freebsd || (linux && (amd64 || arm64)) || netbsd
4+//go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd
55
66 package purego
77
@@ -217,7 +217,7 @@ func callbackasmAddr(i int) uintptr {
217217 panic("purego: unsupported architecture")
218218 case "386", "amd64":
219219 entrySize = 5
220- case "arm", "arm64":
220+ case "arm", "arm64", "loong64":
221221 // On ARM and ARM64, each entry is a MOV instruction
222222 // followed by a branch instruction
223223 entrySize = 8
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: Apache-2.01 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors2 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
3 3
4-//go:build darwin || freebsd || (linux && (amd64 || arm64)) || netbsd4+//go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd
5 5
6 package purego6 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 = 5219 entrySize = 5
220- case "arm", "arm64":220+ case "arm", "arm64", "loong64":
221 // On ARM and ARM64, each entry is a MOV instruction221 // On ARM and ARM64, each entry is a MOV instruction
222 // followed by a branch instruction222 // followed by a branch instruction
223 entrySize = 8223 entrySize = 8
M wincallback.go +29 -0
@@ -72,7 +72,36 @@ TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0
7272 }
7373 }
7474
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+
75103 func main() {
76104 genasmAmd64()
77105 genasmArm64()
106+ genasmLoong64()
78107 }
@@ -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 }
A zcallback_loong64.s +4014 -0 large diff (4014 lines changed): collapsed

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close