detached 071d22a

purego: add support for ppc64le (#408)

This change adds the neccessary code logic to support PPC64LE with both CGO
enabled/disabled.

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