detached 49bede1

purego: add support for linux/386 (#403)

Adds the required assembly for linux/386.

Closes #369
commit
49bede11a66085d4400e4a257e4c77c9604c791a
author
George Adams · · 2026-01-30 03:11 UTC
committer
GitHub · · 2026-01-30 03:11 UTC
parent
df7b1ee
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
refs
tag: v0.10.0-alpha.4
17 files · +4617 -35
M .github/workflows/test.yml +1 -0
@@ -121,6 +121,7 @@ jobs:
121121 sudo apt-get update
122122 sudo apt-get install gcc-multilib
123123 sudo apt-get install g++-multilib
124+ env CGO_ENABLED=0 GOARCH=386 go test -shuffle=on -v -count=10 ./...
124125 env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./...
125126
126127 - name: go test (Linux arm)
@@ -121,6 +121,7 @@ jobs:
121 sudo apt-get update121 sudo apt-get update
122 sudo apt-get install gcc-multilib122 sudo apt-get install gcc-multilib
123 sudo apt-get install g++-multilib123 sudo apt-get install g++-multilib
124+ env CGO_ENABLED=0 GOARCH=386 go test -shuffle=on -v -count=10 ./...
124 env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./...125 env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./...
125 126
126 - name: go test (Linux arm)127 - name: go test (Linux arm)
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<sup>1</sup>, arm, loong64, riscv64
45+- **Linux**: 386, arm, loong64, 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<sup>1</sup>, arm, loong64, riscv6445+- **Linux**: 386, arm, loong64, 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 && (amd64 || arm || arm64 || loong64 || riscv64))
4+//go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || 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 && (amd64 || arm || arm64 || loong64 || riscv64))4+//go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64))
5 5
6 package purego_test6 package purego_test
7 7
M func.go +33 -9
@@ -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 != "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 != "riscv64" {
137137 panic("purego: float returns are not supported")
138138 }
139139 {
@@ -167,10 +167,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
167167 stack++
168168 }
169169 case reflect.Float32, reflect.Float64:
170- if is32bit && runtime.GOARCH != "arm" {
171- panic("purego: floats only supported on 64bit platforms")
172- }
173- if floats < numOfFloatRegisters {
170+ if floats < numOfFloatRegisters() {
174171 floats++
175172 } else {
176173 stack++
@@ -227,7 +224,10 @@ func RegisterFunc(fptr any, cfn uintptr) {
227224
228225 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
229226 var sysargs [maxArgs]uintptr
230- var floats [numOfFloatRegisters]uintptr
227+ // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free,
228+ // since numOfFloatRegisters() is a function call, not a constant.
229+ // maxArgs is always greater than or equal to numOfFloatRegisters() so this is safe.
230+ var floats [maxArgs]uintptr
231231 var numInts int
232232 var numFloats int
233233 var numStack int
@@ -247,7 +247,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
247247 }
248248 }
249249 addFloat = func(x uintptr) {
250- if numFloats < numOfFloatRegisters {
250+ if numFloats < numOfFloatRegisters() {
251251 floats[numFloats] = x
252252 numFloats++
253253 } else {
@@ -358,7 +358,12 @@ func RegisterFunc(fptr any, cfn uintptr) {
358358 case reflect.Float32:
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.
361- v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
361+ // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
362+ if runtime.GOARCH == "386" {
363+ v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
364+ } else {
365+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
366+ }
362367 case reflect.Float64:
363368 // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
364369 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
@@ -479,6 +484,22 @@ func roundUpTo8(val uintptr) uintptr {
479484 return (val + align8ByteMask) &^ align8ByteMask
480485 }
481486
487+func numOfFloatRegisters() int {
488+ switch runtime.GOARCH {
489+ case "amd64", "arm64", "loong64", "riscv64":
490+ return 8
491+ case "arm":
492+ return 16
493+ case "386":
494+ // i386 SysV ABI passes all arguments on the stack, including floats
495+ return 0
496+ default:
497+ // since this platform isn't supported and can therefore only access
498+ // integer registers it is safest to return 8
499+ return 8
500+ }
501+}
502+
482503 func numOfIntegerRegisters() int {
483504 switch runtime.GOARCH {
484505 case "arm64", "loong64", "riscv64":
@@ -487,6 +508,9 @@ func numOfIntegerRegisters() int {
487508 return 6
488509 case "arm":
489510 return 4
511+ case "386":
512+ // i386 SysV ABI passes all arguments on the stack
513+ return 0
490514 default:
491515 // since this platform isn't supported and can therefore only access
492516 // integer registers it is fine to return the maxArgs
@@ -508,7 +532,7 @@ func estimateStackBytes(ty reflect.Type) int {
508532 usesInt := arg.Kind() != reflect.Float32 && arg.Kind() != reflect.Float64
509533 if usesInt && numInts < numOfIntegerRegisters() {
510534 numInts++
511- } else if !usesInt && numFloats < numOfFloatRegisters {
535+ } else if !usesInt && numFloats < numOfFloatRegisters() {
512536 numFloats++
513537 } else {
514538 // Goes to stack - accumulate total bytes
@@ -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 != "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 != "riscv64" {
137 panic("purego: float returns are not supported")137 panic("purego: float returns are not supported")
138 }138 }
139 {139 {
@@ -167,10 +167,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
167 stack++167 stack++
168 }168 }
169 case reflect.Float32, reflect.Float64:169 case reflect.Float32, reflect.Float64:
170- if is32bit && runtime.GOARCH != "arm" {170+ if floats < numOfFloatRegisters() {
171- panic("purego: floats only supported on 64bit platforms")
172- }
173- if floats < numOfFloatRegisters {
174 floats++171 floats++
175 } else {172 } else {
176 stack++173 stack++
@@ -227,7 +224,10 @@ func RegisterFunc(fptr any, cfn uintptr) {
227 224
228 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {225 v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
229 var sysargs [maxArgs]uintptr226 var sysargs [maxArgs]uintptr
230- var floats [numOfFloatRegisters]uintptr227+ // Use maxArgs instead of numOfFloatRegisters() to keep this code path allocation-free,
228+ // since numOfFloatRegisters() is a function call, not a constant.
229+ // maxArgs is always greater than or equal to numOfFloatRegisters() so this is safe.
230+ var floats [maxArgs]uintptr
231 var numInts int231 var numInts int
232 var numFloats int232 var numFloats int
233 var numStack int233 var numStack int
@@ -247,7 +247,7 @@ func RegisterFunc(fptr any, cfn uintptr) {
247 }247 }
248 }248 }
249 addFloat = func(x uintptr) {249 addFloat = func(x uintptr) {
250- if numFloats < numOfFloatRegisters {250+ if numFloats < numOfFloatRegisters() {
251 floats[numFloats] = x251 floats[numFloats] = x
252 numFloats++252 numFloats++
253 } else {253 } else {
@@ -358,7 +358,12 @@ func RegisterFunc(fptr any, cfn uintptr) {
358 case reflect.Float32:358 case reflect.Float32:
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- v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))361+ // On 386, x87 FPU returns floats as float64 in ST(0), so we read as float64 and convert.
362+ if runtime.GOARCH == "386" {
363+ v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32)))
364+ } else {
365+ v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
366+ }
362 case reflect.Float64:367 case reflect.Float64:
363 // NOTE: syscall.r2 is only the floating return value on 64bit platforms.368 // NOTE: syscall.r2 is only the floating return value on 64bit platforms.
364 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.369 // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
@@ -479,6 +484,22 @@ func roundUpTo8(val uintptr) uintptr {
479 return (val + align8ByteMask) &^ align8ByteMask484 return (val + align8ByteMask) &^ align8ByteMask
480 }485 }
481 486
487+func numOfFloatRegisters() int {
488+ switch runtime.GOARCH {
489+ case "amd64", "arm64", "loong64", "riscv64":
490+ return 8
491+ case "arm":
492+ return 16
493+ case "386":
494+ // i386 SysV ABI passes all arguments on the stack, including floats
495+ return 0
496+ default:
497+ // since this platform isn't supported and can therefore only access
498+ // integer registers it is safest to return 8
499+ return 8
500+ }
501+}
502+
482 func numOfIntegerRegisters() int {503 func numOfIntegerRegisters() int {
483 switch runtime.GOARCH {504 switch runtime.GOARCH {
484 case "arm64", "loong64", "riscv64":505 case "arm64", "loong64", "riscv64":
@@ -487,6 +508,9 @@ func numOfIntegerRegisters() int {
487 return 6508 return 6
488 case "arm":509 case "arm":
489 return 4510 return 4
511+ case "386":
512+ // i386 SysV ABI passes all arguments on the stack
513+ return 0
490 default:514 default:
491 // since this platform isn't supported and can therefore only access515 // since this platform isn't supported and can therefore only access
492 // integer registers it is fine to return the maxArgs516 // integer registers it is fine to return the maxArgs
@@ -508,7 +532,7 @@ func estimateStackBytes(ty reflect.Type) int {
508 usesInt := arg.Kind() != reflect.Float32 && arg.Kind() != reflect.Float64532 usesInt := arg.Kind() != reflect.Float32 && arg.Kind() != reflect.Float64
509 if usesInt && numInts < numOfIntegerRegisters() {533 if usesInt && numInts < numOfIntegerRegisters() {
510 numInts++534 numInts++
511- } else if !usesInt && numFloats < numOfFloatRegisters {535+ } else if !usesInt && numFloats < numOfFloatRegisters() {
512 numFloats++536 numFloats++
513 } else {537 } else {
514 // Goes to stack - accumulate total bytes538 // Goes to stack - accumulate total bytes
M func_test.go +7 -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 != "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 != "riscv64" {
5454 t.Skip("Platform doesn't support Floats")
5555 return
5656 }
@@ -79,10 +79,13 @@ 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 != "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 != "riscv64" {
8383 t.Skip("Platform doesn't support Floats")
8484 return
8585 }
86+ if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
87+ t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test
88+ }
8689 library, err := getSystemLibrary()
8790 if err != nil {
8891 t.Fatalf("couldn't get system library: %s", err)
@@ -118,7 +121,7 @@ func TestRegisterFunc_Floats(t *testing.T) {
118121 }
119122
120123 func TestRegisterLibFunc_Bool(t *testing.T) {
121- if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && 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 != "riscv64" {
122125 t.Skip("Platform doesn't support callbacks")
123126 return
124127 }
@@ -373,7 +376,7 @@ func TestABI_ArgumentPassing(t *testing.T) {
373376 if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") {
374377 t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking")
375378 }
376- if tt.name == "10_float32" && (runtime.GOARCH == "386" || runtime.GOARCH == "arm" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64") {
379+ if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64") {
377380 t.Skip("float32 stack arguments not yet supported on this platform")
378381 }
379382 // 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 != "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 != "riscv64" {
54 t.Skip("Platform doesn't support Floats")54 t.Skip("Platform doesn't support Floats")
55 return55 return
56 }56 }
@@ -79,10 +79,13 @@ 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 != "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 != "riscv64" {
83 t.Skip("Platform doesn't support Floats")83 t.Skip("Platform doesn't support Floats")
84 return84 return
85 }85 }
86+ if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
87+ t.Skip("need a 32bit gcc to run this test") // TODO: find 32bit gcc for test
88+ }
86 library, err := getSystemLibrary()89 library, err := getSystemLibrary()
87 if err != nil {90 if err != nil {
88 t.Fatalf("couldn't get system library: %s", err)91 t.Fatalf("couldn't get system library: %s", err)
@@ -118,7 +121,7 @@ func TestRegisterFunc_Floats(t *testing.T) {
118 }121 }
119 122
120 func TestRegisterLibFunc_Bool(t *testing.T) {123 func TestRegisterLibFunc_Bool(t *testing.T) {
121- if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && 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 != "riscv64" {
122 t.Skip("Platform doesn't support callbacks")125 t.Skip("Platform doesn't support callbacks")
123 return126 return
124 }127 }
@@ -373,7 +376,7 @@ func TestABI_ArgumentPassing(t *testing.T) {
373 if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") {376 if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") {
374 t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking")377 t.Skip("20 int32 arguments only supported on Darwin ARM64 with smart stack checking")
375 }378 }
376- if tt.name == "10_float32" && (runtime.GOARCH == "386" || runtime.GOARCH == "arm" || runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64") {379+ if tt.name == "10_float32" && (runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64") {
377 t.Skip("float32 stack arguments not yet supported on this platform")380 t.Skip("float32 stack arguments not yet supported on this platform")
378 }381 }
379 // 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 && !(amd64 || arm || arm64 || loong64 || riscv64)) || netbsd
4+//go:build freebsd || (linux && !(386 || amd64 || arm || arm64 || loong64 || 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 && !(amd64 || arm || arm64 || loong64 || riscv64)) || netbsd4+//go:build freebsd || (linux && !(386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd
5 5
6 package cgo6 package cgo
7 7
A internal/fakecgo/asm_386.s +29 -0
@@ -0,0 +1,29 @@
1+// Copyright 2009 The Go Authors. All rights reserved.
2+// Use of this source code is governed by a BSD-style
3+// license that can be found in the LICENSE file.
4+
5+#include "textflag.h"
6+
7+// Called by C code generated by cmd/cgo.
8+// func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr)
9+// Saves C callee-saved registers and calls cgocallback with three arguments.
10+// fn is the PC of a func(a unsafe.Pointer) function.
11+TEXT crosscall2(SB), NOSPLIT, $28-16
12+ MOVL BP, 24(SP)
13+ MOVL BX, 20(SP)
14+ MOVL SI, 16(SP)
15+ MOVL DI, 12(SP)
16+
17+ MOVL ctxt+12(FP), AX
18+ MOVL AX, 8(SP)
19+ MOVL a+4(FP), AX
20+ MOVL AX, 4(SP)
21+ MOVL fn+0(FP), AX
22+ MOVL AX, 0(SP)
23+ CALL runtime·cgocallback(SB)
24+
25+ MOVL 12(SP), DI
26+ MOVL 16(SP), SI
27+ MOVL 20(SP), BX
28+ MOVL 24(SP), BP
29+ RET
@@ -0,0 +1,29 @@
1+// Copyright 2009 The Go Authors. All rights reserved.
2+// Use of this source code is governed by a BSD-style
3+// license that can be found in the LICENSE file.
4+
5+#include "textflag.h"
6+
7+// Called by C code generated by cmd/cgo.
8+// func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr)
9+// Saves C callee-saved registers and calls cgocallback with three arguments.
10+// fn is the PC of a func(a unsafe.Pointer) function.
11+TEXT crosscall2(SB), NOSPLIT, $28-16
12+ MOVL BP, 24(SP)
13+ MOVL BX, 20(SP)
14+ MOVL SI, 16(SP)
15+ MOVL DI, 12(SP)
16+
17+ MOVL ctxt+12(FP), AX
18+ MOVL AX, 8(SP)
19+ MOVL a+4(FP), AX
20+ MOVL AX, 4(SP)
21+ MOVL fn+0(FP), AX
22+ MOVL AX, 0(SP)
23+ CALL runtime·cgocallback(SB)
24+
25+ MOVL 12(SP), DI
26+ MOVL 16(SP), SI
27+ MOVL 20(SP), BX
28+ MOVL 24(SP), BP
29+ RET
A internal/fakecgo/trampolines_386.s +107 -0
@@ -0,0 +1,107 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && (freebsd || linux)
5+
6+#include "textflag.h"
7+#include "go_asm.h"
8+
9+// These trampolines map the gcc ABI to Go ABI0 and then call into the Go equivalent functions.
10+// On i386, both GCC and Go use stack-based calling conventions.
11+//
12+// When C calls a function, the stack looks like:
13+// 0(SP) = return address
14+// 4(SP) = arg1
15+// 8(SP) = arg2
16+// ...
17+//
18+// When we declare a Go function with frame size $N-0, Go's prologue
19+// effectively does SUB $N, SP, so the C arguments shift up by N bytes:
20+// N+0(SP) = return address
21+// N+4(SP) = arg1
22+// N+8(SP) = arg2
23+//
24+// Go ABI0 on 386 expects arguments starting at 0(FP) which equals N+4(SP)
25+// after the prologue (where N is the local frame size).
26+
27+TEXT x_cgo_init_trampoline(SB), NOSPLIT, $8-0
28+ // C args at 12(SP) and 16(SP) after frame setup (8 bytes local + 4 bytes ret addr)
29+ // Go function expects args at 0(SP) and 4(SP) in local frame
30+ MOVL 12(SP), AX // first C arg
31+ MOVL 16(SP), BX // second C arg
32+ MOVL AX, 0(SP) // Go arg 1
33+ MOVL BX, 4(SP) // Go arg 2
34+ MOVL ·x_cgo_init_call(SB), CX
35+ MOVL (CX), CX
36+ CALL CX
37+ RET
38+
39+TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $4-0
40+ // C args at 8(SP) after frame setup (4 bytes local + 4 bytes ret addr)
41+ MOVL 8(SP), AX // first C arg
42+ MOVL AX, 0(SP) // Go arg 1
43+ MOVL ·x_cgo_thread_start_call(SB), CX
44+ MOVL (CX), CX
45+ CALL CX
46+ RET
47+
48+TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $4-0
49+ MOVL 8(SP), AX // first C arg
50+ MOVL AX, 0(SP) // Go arg 1
51+ MOVL ·x_cgo_setenv_call(SB), CX
52+ MOVL (CX), CX
53+ CALL CX
54+ RET
55+
56+TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $4-0
57+ MOVL 8(SP), AX // first C arg
58+ MOVL AX, 0(SP) // Go arg 1
59+ MOVL ·x_cgo_unsetenv_call(SB), CX
60+ MOVL (CX), CX
61+ CALL CX
62+ RET
63+
64+TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0-0
65+ CALL ·x_cgo_notify_runtime_init_done(SB)
66+ RET
67+
68+TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0
69+ CALL ·x_cgo_bindm(SB)
70+ RET
71+
72+// func setg_trampoline(setg uintptr, g uintptr)
73+// This is called from Go, so args are at normal FP positions
74+TEXT ·setg_trampoline(SB), NOSPLIT, $4-8
75+ MOVL g+4(FP), AX
76+ MOVL setg+0(FP), BX
77+
78+ // setg expects g in 0(SP)
79+ MOVL AX, 0(SP)
80+ CALL BX
81+ RET
82+
83+TEXT threadentry_trampoline(SB), NOSPLIT, $4-0
84+ MOVL 8(SP), AX // first C arg
85+ MOVL AX, 0(SP) // Go arg 1
86+ MOVL ·threadentry_call(SB), CX
87+ MOVL (CX), CX
88+ CALL CX
89+ RET
90+
91+TEXT ·call5(SB), NOSPLIT, $20-28
92+ MOVL fn+0(FP), AX
93+ MOVL a1+4(FP), BX
94+ MOVL a2+8(FP), CX
95+ MOVL a3+12(FP), DX
96+ MOVL a4+16(FP), SI
97+ MOVL a5+20(FP), DI
98+
99+ // Place arguments on local stack frame for C calling convention
100+ MOVL BX, 0(SP) // a1
101+ MOVL CX, 4(SP) // a2
102+ MOVL DX, 8(SP) // a3
103+ MOVL SI, 12(SP) // a4
104+ MOVL DI, 16(SP) // a5
105+ CALL AX
106+ MOVL AX, r1+24(FP)
107+ RET
@@ -0,0 +1,107 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && (freebsd || linux)
5+
6+#include "textflag.h"
7+#include "go_asm.h"
8+
9+// These trampolines map the gcc ABI to Go ABI0 and then call into the Go equivalent functions.
10+// On i386, both GCC and Go use stack-based calling conventions.
11+//
12+// When C calls a function, the stack looks like:
13+// 0(SP) = return address
14+// 4(SP) = arg1
15+// 8(SP) = arg2
16+// ...
17+//
18+// When we declare a Go function with frame size $N-0, Go's prologue
19+// effectively does SUB $N, SP, so the C arguments shift up by N bytes:
20+// N+0(SP) = return address
21+// N+4(SP) = arg1
22+// N+8(SP) = arg2
23+//
24+// Go ABI0 on 386 expects arguments starting at 0(FP) which equals N+4(SP)
25+// after the prologue (where N is the local frame size).
26+
27+TEXT x_cgo_init_trampoline(SB), NOSPLIT, $8-0
28+ // C args at 12(SP) and 16(SP) after frame setup (8 bytes local + 4 bytes ret addr)
29+ // Go function expects args at 0(SP) and 4(SP) in local frame
30+ MOVL 12(SP), AX // first C arg
31+ MOVL 16(SP), BX // second C arg
32+ MOVL AX, 0(SP) // Go arg 1
33+ MOVL BX, 4(SP) // Go arg 2
34+ MOVL ·x_cgo_init_call(SB), CX
35+ MOVL (CX), CX
36+ CALL CX
37+ RET
38+
39+TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $4-0
40+ // C args at 8(SP) after frame setup (4 bytes local + 4 bytes ret addr)
41+ MOVL 8(SP), AX // first C arg
42+ MOVL AX, 0(SP) // Go arg 1
43+ MOVL ·x_cgo_thread_start_call(SB), CX
44+ MOVL (CX), CX
45+ CALL CX
46+ RET
47+
48+TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $4-0
49+ MOVL 8(SP), AX // first C arg
50+ MOVL AX, 0(SP) // Go arg 1
51+ MOVL ·x_cgo_setenv_call(SB), CX
52+ MOVL (CX), CX
53+ CALL CX
54+ RET
55+
56+TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $4-0
57+ MOVL 8(SP), AX // first C arg
58+ MOVL AX, 0(SP) // Go arg 1
59+ MOVL ·x_cgo_unsetenv_call(SB), CX
60+ MOVL (CX), CX
61+ CALL CX
62+ RET
63+
64+TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0-0
65+ CALL ·x_cgo_notify_runtime_init_done(SB)
66+ RET
67+
68+TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0
69+ CALL ·x_cgo_bindm(SB)
70+ RET
71+
72+// func setg_trampoline(setg uintptr, g uintptr)
73+// This is called from Go, so args are at normal FP positions
74+TEXT ·setg_trampoline(SB), NOSPLIT, $4-8
75+ MOVL g+4(FP), AX
76+ MOVL setg+0(FP), BX
77+
78+ // setg expects g in 0(SP)
79+ MOVL AX, 0(SP)
80+ CALL BX
81+ RET
82+
83+TEXT threadentry_trampoline(SB), NOSPLIT, $4-0
84+ MOVL 8(SP), AX // first C arg
85+ MOVL AX, 0(SP) // Go arg 1
86+ MOVL ·threadentry_call(SB), CX
87+ MOVL (CX), CX
88+ CALL CX
89+ RET
90+
91+TEXT ·call5(SB), NOSPLIT, $20-28
92+ MOVL fn+0(FP), AX
93+ MOVL a1+4(FP), BX
94+ MOVL a2+8(FP), CX
95+ MOVL a3+12(FP), DX
96+ MOVL a4+16(FP), SI
97+ MOVL a5+20(FP), DI
98+
99+ // Place arguments on local stack frame for C calling convention
100+ MOVL BX, 0(SP) // a1
101+ MOVL CX, 4(SP) // a2
102+ MOVL DX, 8(SP) // a3
103+ MOVL SI, 12(SP) // a4
104+ MOVL DI, 16(SP) // a5
105+ CALL AX
106+ MOVL AX, r1+24(FP)
107+ RET
M struct_arm64.go +6 -6
@@ -78,8 +78,8 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
7878 if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
7979 // if this doesn't fit entirely in registers then
8080 // each element goes onto the stack
81- if hfa && *numFloats+v.NumField() > numOfFloatRegisters {
82- *numFloats = numOfFloatRegisters
81+ if hfa && *numFloats+v.NumField() > numOfFloatRegisters() {
82+ *numFloats = numOfFloatRegisters()
8383 } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
8484 *numInts = numOfIntegerRegisters()
8585 }
@@ -365,7 +365,7 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
365365 isInt := !isFloat && kind != reflect.Struct
366366 primitiveOnStack :=
367367 (isInt && numInts >= numOfIntegerRegisters()) ||
368- (isFloat && numFloats >= numOfFloatRegisters)
368+ (isFloat && numFloats >= numOfFloatRegisters())
369369 if primitiveOnStack {
370370 return true
371371 }
@@ -382,7 +382,7 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
382382
383383 if hfa {
384384 need := v.NumField()
385- return numFloats+need > numOfFloatRegisters
385+ return numFloats+need > numOfFloatRegisters()
386386 }
387387
388388 if hva {
@@ -407,7 +407,7 @@ func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (b
407407
408408 if hfa {
409409 // HFA: check if elements fit in float registers
410- if tempNumFloats+val.NumField() <= numOfFloatRegisters {
410+ if tempNumFloats+val.NumField() <= numOfFloatRegisters() {
411411 return true, tempNumInts, tempNumFloats + val.NumField()
412412 }
413413 } else if hva {
@@ -451,7 +451,7 @@ func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int
451451 // Primitive argument
452452 isFloat := val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64
453453 if isFloat {
454- fitsInRegister = tempNumFloats < numOfFloatRegisters
454+ fitsInRegister = tempNumFloats < numOfFloatRegisters()
455455 newNumFloats = tempNumFloats + 1
456456 newNumInts = tempNumInts
457457 } else {
@@ -78,8 +78,8 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
78 if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {78 if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
79 // if this doesn't fit entirely in registers then79 // if this doesn't fit entirely in registers then
80 // each element goes onto the stack80 // each element goes onto the stack
81- if hfa && *numFloats+v.NumField() > numOfFloatRegisters {81+ if hfa && *numFloats+v.NumField() > numOfFloatRegisters() {
82- *numFloats = numOfFloatRegisters82+ *numFloats = numOfFloatRegisters()
83 } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {83 } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
84 *numInts = numOfIntegerRegisters()84 *numInts = numOfIntegerRegisters()
85 }85 }
@@ -365,7 +365,7 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
365 isInt := !isFloat && kind != reflect.Struct365 isInt := !isFloat && kind != reflect.Struct
366 primitiveOnStack :=366 primitiveOnStack :=
367 (isInt && numInts >= numOfIntegerRegisters()) ||367 (isInt && numInts >= numOfIntegerRegisters()) ||
368- (isFloat && numFloats >= numOfFloatRegisters)368+ (isFloat && numFloats >= numOfFloatRegisters())
369 if primitiveOnStack {369 if primitiveOnStack {
370 return true370 return true
371 }371 }
@@ -382,7 +382,7 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool {
382 382
383 if hfa {383 if hfa {
384 need := v.NumField()384 need := v.NumField()
385- return numFloats+need > numOfFloatRegisters385+ return numFloats+need > numOfFloatRegisters()
386 }386 }
387 387
388 if hva {388 if hva {
@@ -407,7 +407,7 @@ func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (b
407 407
408 if hfa {408 if hfa {
409 // HFA: check if elements fit in float registers409 // HFA: check if elements fit in float registers
410- if tempNumFloats+val.NumField() <= numOfFloatRegisters {410+ if tempNumFloats+val.NumField() <= numOfFloatRegisters() {
411 return true, tempNumInts, tempNumFloats + val.NumField()411 return true, tempNumInts, tempNumFloats + val.NumField()
412 }412 }
413 } else if hva {413 } else if hva {
@@ -451,7 +451,7 @@ func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int
451 // Primitive argument451 // Primitive argument
452 isFloat := val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64452 isFloat := val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64
453 if isFloat {453 if isFloat {
454- fitsInRegister = tempNumFloats < numOfFloatRegisters454+ fitsInRegister = tempNumFloats < numOfFloatRegisters()
455 newNumFloats = tempNumFloats + 1455 newNumFloats = tempNumFloats + 1
456 newNumInts = tempNumInts456 newNumInts = tempNumInts
457 } else {457 } else {
A sys_386.s +147 -0
@@ -0,0 +1,147 @@
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+#define STACK_SIZE 160
11+#define PTR_ADDRESS (STACK_SIZE - 4)
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+// ...
19+// a32 uintptr
20+// f1 uintptr
21+// ...
22+// f16 uintptr
23+// arm64_r8 uintptr
24+// }
25+// syscall15X must be called on the g0 stack with the
26+// C calling convention (use libcCall).
27+//
28+// On i386 System V ABI, all arguments are passed on the stack.
29+// Return value is in EAX (and EDX for 64-bit values).
30+GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $4
31+DATA ·syscall15XABI0(SB)/4, $syscall15X(SB)
32+TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0
33+ // Called via C calling convention: argument pointer at 4(SP)
34+ // NOT via Go calling convention
35+ // On i386, the first argument is at 4(SP) after CALL pushes return address
36+ MOVL 4(SP), AX // get pointer to syscall15Args
37+
38+ // Save callee-saved registers
39+ PUSHL BP
40+ PUSHL BX
41+ PUSHL SI
42+ PUSHL DI
43+
44+ MOVL AX, BX // save args pointer in BX
45+
46+ // Allocate stack space for C function arguments
47+ // i386 SysV: all 32 args on stack = 32 * 4 = 128 bytes
48+ // Plus 16 bytes for alignment and local storage
49+ SUBL $STACK_SIZE, SP
50+ MOVL BX, PTR_ADDRESS(SP) // save args pointer
51+
52+ // Load function pointer
53+ MOVL syscall15Args_fn(BX), AX
54+ MOVL AX, (PTR_ADDRESS-4)(SP) // save fn pointer
55+
56+ // Push all integer arguments onto the stack (a1-a32)
57+ // i386 SysV ABI: arguments pushed right-to-left, but we're
58+ // setting up the stack from low to high addresses
59+ MOVL syscall15Args_a1(BX), AX
60+ MOVL AX, 0(SP)
61+ MOVL syscall15Args_a2(BX), AX
62+ MOVL AX, 4(SP)
63+ MOVL syscall15Args_a3(BX), AX
64+ MOVL AX, 8(SP)
65+ MOVL syscall15Args_a4(BX), AX
66+ MOVL AX, 12(SP)
67+ MOVL syscall15Args_a5(BX), AX
68+ MOVL AX, 16(SP)
69+ MOVL syscall15Args_a6(BX), AX
70+ MOVL AX, 20(SP)
71+ MOVL syscall15Args_a7(BX), AX
72+ MOVL AX, 24(SP)
73+ MOVL syscall15Args_a8(BX), AX
74+ MOVL AX, 28(SP)
75+ MOVL syscall15Args_a9(BX), AX
76+ MOVL AX, 32(SP)
77+ MOVL syscall15Args_a10(BX), AX
78+ MOVL AX, 36(SP)
79+ MOVL syscall15Args_a11(BX), AX
80+ MOVL AX, 40(SP)
81+ MOVL syscall15Args_a12(BX), AX
82+ MOVL AX, 44(SP)
83+ MOVL syscall15Args_a13(BX), AX
84+ MOVL AX, 48(SP)
85+ MOVL syscall15Args_a14(BX), AX
86+ MOVL AX, 52(SP)
87+ MOVL syscall15Args_a15(BX), AX
88+ MOVL AX, 56(SP)
89+ MOVL syscall15Args_a16(BX), AX
90+ MOVL AX, 60(SP)
91+ MOVL syscall15Args_a17(BX), AX
92+ MOVL AX, 64(SP)
93+ MOVL syscall15Args_a18(BX), AX
94+ MOVL AX, 68(SP)
95+ MOVL syscall15Args_a19(BX), AX
96+ MOVL AX, 72(SP)
97+ MOVL syscall15Args_a20(BX), AX
98+ MOVL AX, 76(SP)
99+ MOVL syscall15Args_a21(BX), AX
100+ MOVL AX, 80(SP)
101+ MOVL syscall15Args_a22(BX), AX
102+ MOVL AX, 84(SP)
103+ MOVL syscall15Args_a23(BX), AX
104+ MOVL AX, 88(SP)
105+ MOVL syscall15Args_a24(BX), AX
106+ MOVL AX, 92(SP)
107+ MOVL syscall15Args_a25(BX), AX
108+ MOVL AX, 96(SP)
109+ MOVL syscall15Args_a26(BX), AX
110+ MOVL AX, 100(SP)
111+ MOVL syscall15Args_a27(BX), AX
112+ MOVL AX, 104(SP)
113+ MOVL syscall15Args_a28(BX), AX
114+ MOVL AX, 108(SP)
115+ MOVL syscall15Args_a29(BX), AX
116+ MOVL AX, 112(SP)
117+ MOVL syscall15Args_a30(BX), AX
118+ MOVL AX, 116(SP)
119+ MOVL syscall15Args_a31(BX), AX
120+ MOVL AX, 120(SP)
121+ MOVL syscall15Args_a32(BX), AX
122+ MOVL AX, 124(SP)
123+
124+ // Call the C function
125+ MOVL (PTR_ADDRESS-4)(SP), AX
126+ CALL AX
127+
128+ // Get args pointer back and save results
129+ MOVL PTR_ADDRESS(SP), BX
130+ MOVL AX, syscall15Args_a1(BX) // return value r1
131+ MOVL DX, syscall15Args_a2(BX) // return value r2 (for 64-bit returns)
132+
133+ // Save x87 FPU return value (ST0) to f1 field
134+ // On i386 System V ABI, float/double returns are in ST(0)
135+ // We save as float64 (8 bytes) to preserve precision
136+ FMOVDP F0, syscall15Args_f1(BX)
137+
138+ // Clean up stack
139+ ADDL $STACK_SIZE, SP
140+
141+ // Restore callee-saved registers
142+ POPL DI
143+ POPL SI
144+ POPL BX
145+ POPL BP
146+
147+ RET
@@ -0,0 +1,147 @@
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+#define STACK_SIZE 160
11+#define PTR_ADDRESS (STACK_SIZE - 4)
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+// ...
19+// a32 uintptr
20+// f1 uintptr
21+// ...
22+// f16 uintptr
23+// arm64_r8 uintptr
24+// }
25+// syscall15X must be called on the g0 stack with the
26+// C calling convention (use libcCall).
27+//
28+// On i386 System V ABI, all arguments are passed on the stack.
29+// Return value is in EAX (and EDX for 64-bit values).
30+GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $4
31+DATA ·syscall15XABI0(SB)/4, $syscall15X(SB)
32+TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0
33+ // Called via C calling convention: argument pointer at 4(SP)
34+ // NOT via Go calling convention
35+ // On i386, the first argument is at 4(SP) after CALL pushes return address
36+ MOVL 4(SP), AX // get pointer to syscall15Args
37+
38+ // Save callee-saved registers
39+ PUSHL BP
40+ PUSHL BX
41+ PUSHL SI
42+ PUSHL DI
43+
44+ MOVL AX, BX // save args pointer in BX
45+
46+ // Allocate stack space for C function arguments
47+ // i386 SysV: all 32 args on stack = 32 * 4 = 128 bytes
48+ // Plus 16 bytes for alignment and local storage
49+ SUBL $STACK_SIZE, SP
50+ MOVL BX, PTR_ADDRESS(SP) // save args pointer
51+
52+ // Load function pointer
53+ MOVL syscall15Args_fn(BX), AX
54+ MOVL AX, (PTR_ADDRESS-4)(SP) // save fn pointer
55+
56+ // Push all integer arguments onto the stack (a1-a32)
57+ // i386 SysV ABI: arguments pushed right-to-left, but we're
58+ // setting up the stack from low to high addresses
59+ MOVL syscall15Args_a1(BX), AX
60+ MOVL AX, 0(SP)
61+ MOVL syscall15Args_a2(BX), AX
62+ MOVL AX, 4(SP)
63+ MOVL syscall15Args_a3(BX), AX
64+ MOVL AX, 8(SP)
65+ MOVL syscall15Args_a4(BX), AX
66+ MOVL AX, 12(SP)
67+ MOVL syscall15Args_a5(BX), AX
68+ MOVL AX, 16(SP)
69+ MOVL syscall15Args_a6(BX), AX
70+ MOVL AX, 20(SP)
71+ MOVL syscall15Args_a7(BX), AX
72+ MOVL AX, 24(SP)
73+ MOVL syscall15Args_a8(BX), AX
74+ MOVL AX, 28(SP)
75+ MOVL syscall15Args_a9(BX), AX
76+ MOVL AX, 32(SP)
77+ MOVL syscall15Args_a10(BX), AX
78+ MOVL AX, 36(SP)
79+ MOVL syscall15Args_a11(BX), AX
80+ MOVL AX, 40(SP)
81+ MOVL syscall15Args_a12(BX), AX
82+ MOVL AX, 44(SP)
83+ MOVL syscall15Args_a13(BX), AX
84+ MOVL AX, 48(SP)
85+ MOVL syscall15Args_a14(BX), AX
86+ MOVL AX, 52(SP)
87+ MOVL syscall15Args_a15(BX), AX
88+ MOVL AX, 56(SP)
89+ MOVL syscall15Args_a16(BX), AX
90+ MOVL AX, 60(SP)
91+ MOVL syscall15Args_a17(BX), AX
92+ MOVL AX, 64(SP)
93+ MOVL syscall15Args_a18(BX), AX
94+ MOVL AX, 68(SP)
95+ MOVL syscall15Args_a19(BX), AX
96+ MOVL AX, 72(SP)
97+ MOVL syscall15Args_a20(BX), AX
98+ MOVL AX, 76(SP)
99+ MOVL syscall15Args_a21(BX), AX
100+ MOVL AX, 80(SP)
101+ MOVL syscall15Args_a22(BX), AX
102+ MOVL AX, 84(SP)
103+ MOVL syscall15Args_a23(BX), AX
104+ MOVL AX, 88(SP)
105+ MOVL syscall15Args_a24(BX), AX
106+ MOVL AX, 92(SP)
107+ MOVL syscall15Args_a25(BX), AX
108+ MOVL AX, 96(SP)
109+ MOVL syscall15Args_a26(BX), AX
110+ MOVL AX, 100(SP)
111+ MOVL syscall15Args_a27(BX), AX
112+ MOVL AX, 104(SP)
113+ MOVL syscall15Args_a28(BX), AX
114+ MOVL AX, 108(SP)
115+ MOVL syscall15Args_a29(BX), AX
116+ MOVL AX, 112(SP)
117+ MOVL syscall15Args_a30(BX), AX
118+ MOVL AX, 116(SP)
119+ MOVL syscall15Args_a31(BX), AX
120+ MOVL AX, 120(SP)
121+ MOVL syscall15Args_a32(BX), AX
122+ MOVL AX, 124(SP)
123+
124+ // Call the C function
125+ MOVL (PTR_ADDRESS-4)(SP), AX
126+ CALL AX
127+
128+ // Get args pointer back and save results
129+ MOVL PTR_ADDRESS(SP), BX
130+ MOVL AX, syscall15Args_a1(BX) // return value r1
131+ MOVL DX, syscall15Args_a2(BX) // return value r2 (for 64-bit returns)
132+
133+ // Save x87 FPU return value (ST0) to f1 field
134+ // On i386 System V ABI, float/double returns are in ST(0)
135+ // We save as float64 (8 bytes) to preserve precision
136+ FMOVDP F0, syscall15Args_f1(BX)
137+
138+ // Clean up stack
139+ ADDL $STACK_SIZE, SP
140+
141+ // Restore callee-saved registers
142+ POPL DI
143+ POPL SI
144+ POPL BX
145+ POPL BP
146+
147+ RET
A sys_unix_386.s +226 -0
@@ -0,0 +1,226 @@
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+// callbackasm1 is the second part of the callback trampoline.
11+// On entry:
12+// - CX contains the callback index (set by callbackasm)
13+// - 0(SP) contains the return address to C caller
14+// - 4(SP), 8(SP), ... contain C arguments (cdecl convention)
15+//
16+// i386 cdecl calling convention:
17+// - All arguments passed on stack
18+// - Return value in EAX (and EDX for 64-bit)
19+// - Caller cleans the stack
20+// - Callee must preserve: EBX, ESI, EDI, EBP
21+TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
22+ NO_LOCAL_POINTERS
23+
24+ // Save the return address
25+ MOVL 0(SP), AX
26+
27+ // Allocate stack frame (must be done carefully to preserve args access)
28+ // Layout:
29+ // 0-15: saved callee-saved registers (BX, SI, DI, BP)
30+ // 16-19: saved callback index
31+ // 20-23: saved return address
32+ // 24-35: callbackArgs struct (12 bytes)
33+ // 36-291: copy of C arguments (256 bytes for 64 args, matching callbackMaxFrame)
34+ // Total: 292 bytes, round up to 304 for alignment
35+ SUBL $304, SP
36+
37+ // Save callee-saved registers
38+ MOVL BX, 0(SP)
39+ MOVL SI, 4(SP)
40+ MOVL DI, 8(SP)
41+ MOVL BP, 12(SP)
42+
43+ // Save callback index and return address
44+ MOVL CX, 16(SP)
45+ MOVL AX, 20(SP)
46+
47+ // Copy C arguments from original stack location to our frame
48+ // Original args start at 304+4(SP) = 308(SP) (past our frame + original return addr)
49+ // Copy to our frame at 36(SP)
50+ // Copy 64 arguments (256 bytes, matching callbackMaxFrame = 64 * ptrSize)
51+ MOVL 308(SP), AX
52+ MOVL AX, 36(SP)
53+ MOVL 312(SP), AX
54+ MOVL AX, 40(SP)
55+ MOVL 316(SP), AX
56+ MOVL AX, 44(SP)
57+ MOVL 320(SP), AX
58+ MOVL AX, 48(SP)
59+ MOVL 324(SP), AX
60+ MOVL AX, 52(SP)
61+ MOVL 328(SP), AX
62+ MOVL AX, 56(SP)
63+ MOVL 332(SP), AX
64+ MOVL AX, 60(SP)
65+ MOVL 336(SP), AX
66+ MOVL AX, 64(SP)
67+ MOVL 340(SP), AX
68+ MOVL AX, 68(SP)
69+ MOVL 344(SP), AX
70+ MOVL AX, 72(SP)
71+ MOVL 348(SP), AX
72+ MOVL AX, 76(SP)
73+ MOVL 352(SP), AX
74+ MOVL AX, 80(SP)
75+ MOVL 356(SP), AX
76+ MOVL AX, 84(SP)
77+ MOVL 360(SP), AX
78+ MOVL AX, 88(SP)
79+ MOVL 364(SP), AX
80+ MOVL AX, 92(SP)
81+ MOVL 368(SP), AX
82+ MOVL AX, 96(SP)
83+ MOVL 372(SP), AX
84+ MOVL AX, 100(SP)
85+ MOVL 376(SP), AX
86+ MOVL AX, 104(SP)
87+ MOVL 380(SP), AX
88+ MOVL AX, 108(SP)
89+ MOVL 384(SP), AX
90+ MOVL AX, 112(SP)
91+ MOVL 388(SP), AX
92+ MOVL AX, 116(SP)
93+ MOVL 392(SP), AX
94+ MOVL AX, 120(SP)
95+ MOVL 396(SP), AX
96+ MOVL AX, 124(SP)
97+ MOVL 400(SP), AX
98+ MOVL AX, 128(SP)
99+ MOVL 404(SP), AX
100+ MOVL AX, 132(SP)
101+ MOVL 408(SP), AX
102+ MOVL AX, 136(SP)
103+ MOVL 412(SP), AX
104+ MOVL AX, 140(SP)
105+ MOVL 416(SP), AX
106+ MOVL AX, 144(SP)
107+ MOVL 420(SP), AX
108+ MOVL AX, 148(SP)
109+ MOVL 424(SP), AX
110+ MOVL AX, 152(SP)
111+ MOVL 428(SP), AX
112+ MOVL AX, 156(SP)
113+ MOVL 432(SP), AX
114+ MOVL AX, 160(SP)
115+ MOVL 436(SP), AX
116+ MOVL AX, 164(SP)
117+ MOVL 440(SP), AX
118+ MOVL AX, 168(SP)
119+ MOVL 444(SP), AX
120+ MOVL AX, 172(SP)
121+ MOVL 448(SP), AX
122+ MOVL AX, 176(SP)
123+ MOVL 452(SP), AX
124+ MOVL AX, 180(SP)
125+ MOVL 456(SP), AX
126+ MOVL AX, 184(SP)
127+ MOVL 460(SP), AX
128+ MOVL AX, 188(SP)
129+ MOVL 464(SP), AX
130+ MOVL AX, 192(SP)
131+ MOVL 468(SP), AX
132+ MOVL AX, 196(SP)
133+ MOVL 472(SP), AX
134+ MOVL AX, 200(SP)
135+ MOVL 476(SP), AX
136+ MOVL AX, 204(SP)
137+ MOVL 480(SP), AX
138+ MOVL AX, 208(SP)
139+ MOVL 484(SP), AX
140+ MOVL AX, 212(SP)
141+ MOVL 488(SP), AX
142+ MOVL AX, 216(SP)
143+ MOVL 492(SP), AX
144+ MOVL AX, 220(SP)
145+ MOVL 496(SP), AX
146+ MOVL AX, 224(SP)
147+ MOVL 500(SP), AX
148+ MOVL AX, 228(SP)
149+ MOVL 504(SP), AX
150+ MOVL AX, 232(SP)
151+ MOVL 508(SP), AX
152+ MOVL AX, 236(SP)
153+ MOVL 512(SP), AX
154+ MOVL AX, 240(SP)
155+ MOVL 516(SP), AX
156+ MOVL AX, 244(SP)
157+ MOVL 520(SP), AX
158+ MOVL AX, 248(SP)
159+ MOVL 524(SP), AX
160+ MOVL AX, 252(SP)
161+ MOVL 528(SP), AX
162+ MOVL AX, 256(SP)
163+ MOVL 532(SP), AX
164+ MOVL AX, 260(SP)
165+ MOVL 536(SP), AX
166+ MOVL AX, 264(SP)
167+ MOVL 540(SP), AX
168+ MOVL AX, 268(SP)
169+ MOVL 544(SP), AX
170+ MOVL AX, 272(SP)
171+ MOVL 548(SP), AX
172+ MOVL AX, 276(SP)
173+ MOVL 552(SP), AX
174+ MOVL AX, 280(SP)
175+ MOVL 556(SP), AX
176+ MOVL AX, 284(SP)
177+ MOVL 560(SP), AX
178+ MOVL AX, 288(SP)
179+
180+ // Set up callbackArgs struct at 24(SP)
181+ // struct callbackArgs {
182+ // index uintptr // offset 0
183+ // args *byte // offset 4
184+ // result uintptr // offset 8
185+ // }
186+ MOVL 16(SP), AX // callback index
187+ MOVL AX, 24(SP) // callbackArgs.index
188+ LEAL 36(SP), AX // pointer to copied arguments
189+ MOVL AX, 28(SP) // callbackArgs.args
190+ MOVL $0, 32(SP) // callbackArgs.result = 0
191+
192+ // Call crosscall2(fn, frame, 0, ctxt)
193+ // crosscall2 expects arguments on stack:
194+ // 0(SP) = fn
195+ // 4(SP) = frame (pointer to callbackArgs)
196+ // 8(SP) = ignored (was n)
197+ // 12(SP) = ctxt
198+ SUBL $16, SP
199+
200+ MOVL ·callbackWrap_call(SB), AX
201+ MOVL (AX), AX // fn = *callbackWrap_call
202+ MOVL AX, 0(SP) // fn
203+ LEAL (24+16)(SP), AX // &callbackArgs (adjusted for SUB $16)
204+ MOVL AX, 4(SP) // frame
205+ MOVL $0, 8(SP) // 0
206+ MOVL $0, 12(SP) // ctxt
207+
208+ CALL crosscall2(SB)
209+
210+ ADDL $16, SP
211+
212+ // Get result from callbackArgs.result
213+ MOVL 32(SP), AX
214+
215+ // Restore callee-saved registers
216+ MOVL 0(SP), BX
217+ MOVL 4(SP), SI
218+ MOVL 8(SP), DI
219+ MOVL 12(SP), BP
220+
221+ // Restore return address and clean up
222+ MOVL 20(SP), CX // get return address
223+ ADDL $304, SP // remove our frame
224+ MOVL CX, 0(SP) // put return address back
225+
226+ RET
@@ -0,0 +1,226 @@
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+// callbackasm1 is the second part of the callback trampoline.
11+// On entry:
12+// - CX contains the callback index (set by callbackasm)
13+// - 0(SP) contains the return address to C caller
14+// - 4(SP), 8(SP), ... contain C arguments (cdecl convention)
15+//
16+// i386 cdecl calling convention:
17+// - All arguments passed on stack
18+// - Return value in EAX (and EDX for 64-bit)
19+// - Caller cleans the stack
20+// - Callee must preserve: EBX, ESI, EDI, EBP
21+TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
22+ NO_LOCAL_POINTERS
23+
24+ // Save the return address
25+ MOVL 0(SP), AX
26+
27+ // Allocate stack frame (must be done carefully to preserve args access)
28+ // Layout:
29+ // 0-15: saved callee-saved registers (BX, SI, DI, BP)
30+ // 16-19: saved callback index
31+ // 20-23: saved return address
32+ // 24-35: callbackArgs struct (12 bytes)
33+ // 36-291: copy of C arguments (256 bytes for 64 args, matching callbackMaxFrame)
34+ // Total: 292 bytes, round up to 304 for alignment
35+ SUBL $304, SP
36+
37+ // Save callee-saved registers
38+ MOVL BX, 0(SP)
39+ MOVL SI, 4(SP)
40+ MOVL DI, 8(SP)
41+ MOVL BP, 12(SP)
42+
43+ // Save callback index and return address
44+ MOVL CX, 16(SP)
45+ MOVL AX, 20(SP)
46+
47+ // Copy C arguments from original stack location to our frame
48+ // Original args start at 304+4(SP) = 308(SP) (past our frame + original return addr)
49+ // Copy to our frame at 36(SP)
50+ // Copy 64 arguments (256 bytes, matching callbackMaxFrame = 64 * ptrSize)
51+ MOVL 308(SP), AX
52+ MOVL AX, 36(SP)
53+ MOVL 312(SP), AX
54+ MOVL AX, 40(SP)
55+ MOVL 316(SP), AX
56+ MOVL AX, 44(SP)
57+ MOVL 320(SP), AX
58+ MOVL AX, 48(SP)
59+ MOVL 324(SP), AX
60+ MOVL AX, 52(SP)
61+ MOVL 328(SP), AX
62+ MOVL AX, 56(SP)
63+ MOVL 332(SP), AX
64+ MOVL AX, 60(SP)
65+ MOVL 336(SP), AX
66+ MOVL AX, 64(SP)
67+ MOVL 340(SP), AX
68+ MOVL AX, 68(SP)
69+ MOVL 344(SP), AX
70+ MOVL AX, 72(SP)
71+ MOVL 348(SP), AX
72+ MOVL AX, 76(SP)
73+ MOVL 352(SP), AX
74+ MOVL AX, 80(SP)
75+ MOVL 356(SP), AX
76+ MOVL AX, 84(SP)
77+ MOVL 360(SP), AX
78+ MOVL AX, 88(SP)
79+ MOVL 364(SP), AX
80+ MOVL AX, 92(SP)
81+ MOVL 368(SP), AX
82+ MOVL AX, 96(SP)
83+ MOVL 372(SP), AX
84+ MOVL AX, 100(SP)
85+ MOVL 376(SP), AX
86+ MOVL AX, 104(SP)
87+ MOVL 380(SP), AX
88+ MOVL AX, 108(SP)
89+ MOVL 384(SP), AX
90+ MOVL AX, 112(SP)
91+ MOVL 388(SP), AX
92+ MOVL AX, 116(SP)
93+ MOVL 392(SP), AX
94+ MOVL AX, 120(SP)
95+ MOVL 396(SP), AX
96+ MOVL AX, 124(SP)
97+ MOVL 400(SP), AX
98+ MOVL AX, 128(SP)
99+ MOVL 404(SP), AX
100+ MOVL AX, 132(SP)
101+ MOVL 408(SP), AX
102+ MOVL AX, 136(SP)
103+ MOVL 412(SP), AX
104+ MOVL AX, 140(SP)
105+ MOVL 416(SP), AX
106+ MOVL AX, 144(SP)
107+ MOVL 420(SP), AX
108+ MOVL AX, 148(SP)
109+ MOVL 424(SP), AX
110+ MOVL AX, 152(SP)
111+ MOVL 428(SP), AX
112+ MOVL AX, 156(SP)
113+ MOVL 432(SP), AX
114+ MOVL AX, 160(SP)
115+ MOVL 436(SP), AX
116+ MOVL AX, 164(SP)
117+ MOVL 440(SP), AX
118+ MOVL AX, 168(SP)
119+ MOVL 444(SP), AX
120+ MOVL AX, 172(SP)
121+ MOVL 448(SP), AX
122+ MOVL AX, 176(SP)
123+ MOVL 452(SP), AX
124+ MOVL AX, 180(SP)
125+ MOVL 456(SP), AX
126+ MOVL AX, 184(SP)
127+ MOVL 460(SP), AX
128+ MOVL AX, 188(SP)
129+ MOVL 464(SP), AX
130+ MOVL AX, 192(SP)
131+ MOVL 468(SP), AX
132+ MOVL AX, 196(SP)
133+ MOVL 472(SP), AX
134+ MOVL AX, 200(SP)
135+ MOVL 476(SP), AX
136+ MOVL AX, 204(SP)
137+ MOVL 480(SP), AX
138+ MOVL AX, 208(SP)
139+ MOVL 484(SP), AX
140+ MOVL AX, 212(SP)
141+ MOVL 488(SP), AX
142+ MOVL AX, 216(SP)
143+ MOVL 492(SP), AX
144+ MOVL AX, 220(SP)
145+ MOVL 496(SP), AX
146+ MOVL AX, 224(SP)
147+ MOVL 500(SP), AX
148+ MOVL AX, 228(SP)
149+ MOVL 504(SP), AX
150+ MOVL AX, 232(SP)
151+ MOVL 508(SP), AX
152+ MOVL AX, 236(SP)
153+ MOVL 512(SP), AX
154+ MOVL AX, 240(SP)
155+ MOVL 516(SP), AX
156+ MOVL AX, 244(SP)
157+ MOVL 520(SP), AX
158+ MOVL AX, 248(SP)
159+ MOVL 524(SP), AX
160+ MOVL AX, 252(SP)
161+ MOVL 528(SP), AX
162+ MOVL AX, 256(SP)
163+ MOVL 532(SP), AX
164+ MOVL AX, 260(SP)
165+ MOVL 536(SP), AX
166+ MOVL AX, 264(SP)
167+ MOVL 540(SP), AX
168+ MOVL AX, 268(SP)
169+ MOVL 544(SP), AX
170+ MOVL AX, 272(SP)
171+ MOVL 548(SP), AX
172+ MOVL AX, 276(SP)
173+ MOVL 552(SP), AX
174+ MOVL AX, 280(SP)
175+ MOVL 556(SP), AX
176+ MOVL AX, 284(SP)
177+ MOVL 560(SP), AX
178+ MOVL AX, 288(SP)
179+
180+ // Set up callbackArgs struct at 24(SP)
181+ // struct callbackArgs {
182+ // index uintptr // offset 0
183+ // args *byte // offset 4
184+ // result uintptr // offset 8
185+ // }
186+ MOVL 16(SP), AX // callback index
187+ MOVL AX, 24(SP) // callbackArgs.index
188+ LEAL 36(SP), AX // pointer to copied arguments
189+ MOVL AX, 28(SP) // callbackArgs.args
190+ MOVL $0, 32(SP) // callbackArgs.result = 0
191+
192+ // Call crosscall2(fn, frame, 0, ctxt)
193+ // crosscall2 expects arguments on stack:
194+ // 0(SP) = fn
195+ // 4(SP) = frame (pointer to callbackArgs)
196+ // 8(SP) = ignored (was n)
197+ // 12(SP) = ctxt
198+ SUBL $16, SP
199+
200+ MOVL ·callbackWrap_call(SB), AX
201+ MOVL (AX), AX // fn = *callbackWrap_call
202+ MOVL AX, 0(SP) // fn
203+ LEAL (24+16)(SP), AX // &callbackArgs (adjusted for SUB $16)
204+ MOVL AX, 4(SP) // frame
205+ MOVL $0, 8(SP) // 0
206+ MOVL $0, 12(SP) // ctxt
207+
208+ CALL crosscall2(SB)
209+
210+ ADDL $16, SP
211+
212+ // Get result from callbackArgs.result
213+ MOVL 32(SP), AX
214+
215+ // Restore callee-saved registers
216+ MOVL 0(SP), BX
217+ MOVL 4(SP), SI
218+ MOVL 8(SP), DI
219+ MOVL 12(SP), BP
220+
221+ // Restore return address and clean up
222+ MOVL 20(SP), CX // get return address
223+ ADDL $304, SP // remove our frame
224+ MOVL CX, 0(SP) // put return address back
225+
226+ RET
M syscall.go +2 -3
@@ -1,7 +1,7 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
4-//go:build !arm && (darwin || freebsd || linux || netbsd || windows)
4+//go:build !386 && !arm && (darwin || freebsd || linux || netbsd || windows)
55
66 package purego
77
@@ -13,8 +13,7 @@ package purego
1313 type CDecl struct{}
1414
1515 const (
16- maxArgs = 15
17- numOfFloatRegisters = 8 // arm64 and amd64 both have 8 float registers
16+ maxArgs = 15
1817 )
1918
2019 type syscall15Args struct {
@@ -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 !arm && (darwin || freebsd || linux || netbsd || windows)4+//go:build !386 && !arm && (darwin || freebsd || linux || netbsd || windows)
5 5
6 package purego6 package purego
7 7
@@ -13,8 +13,7 @@ package purego
13 type CDecl struct{}13 type CDecl struct{}
14 14
15 const (15 const (
16- maxArgs = 1516+ maxArgs = 15
17- numOfFloatRegisters = 8 // arm64 and amd64 both have 8 float registers
18 )17 )
19 18
20 type syscall15Args struct {19 type syscall15Args struct {
M syscall_32bit.go +2 -3
@@ -1,7 +1,7 @@
11 // SPDX-License-Identifier: Apache-2.0
22 // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
33
4-//go:build arm && (freebsd || linux || netbsd || windows)
4+//go:build (386 || arm) && (freebsd || linux || netbsd || windows)
55
66 package purego
77
@@ -13,8 +13,7 @@ package purego
1313 type CDecl struct{}
1414
1515 const (
16- maxArgs = 32
17- numOfFloatRegisters = 16
16+ maxArgs = 32
1817 )
1918
2019 type syscall15Args struct {
@@ -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 arm && (freebsd || linux || netbsd || windows)4+//go:build (386 || arm) && (freebsd || linux || netbsd || windows)
5 5
6 package purego6 package purego
7 7
@@ -13,8 +13,7 @@ package purego
13 type CDecl struct{}13 type CDecl struct{}
14 14
15 const (15 const (
16- maxArgs = 3216+ maxArgs = 32
17- numOfFloatRegisters = 16
18 )17 )
19 18
20 type syscall15Args struct {19 type syscall15Args struct {
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 || arm || arm64 || loong64 || riscv64)
4+//go:build cgo && !(386 || amd64 || arm || arm64 || loong64 || 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 amd64/arm64/arm/loong64/riscv64")
20+ panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/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 && !(amd64 || arm || arm64 || loong64 || riscv64)4+//go:build cgo && !(386 || amd64 || arm || arm64 || loong64 || 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 amd64/arm64/arm/loong64/riscv64")20+ panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/riscv64")
21 }21 }
M syscall_sysv.go +9 -5
@@ -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 || arm || arm64 || loong64 || riscv64)) || netbsd
4+//go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd
55
66 package purego
77
@@ -152,7 +152,7 @@ func callbackWrap(a *callbackArgs) {
152152 var intsN int
153153 // stackSlot points to the index into frame of the current stack element.
154154 // The stack begins after the float and integer registers.
155- stackSlot := numOfIntegerRegisters() + numOfFloatRegisters
155+ stackSlot := numOfIntegerRegisters() + numOfFloatRegisters()
156156 // stackByteOffset tracks the byte offset within the stack area for Darwin ARM64
157157 // tight packing. On Darwin ARM64, C passes small types packed on the stack.
158158 stackByteOffset := uintptr(0)
@@ -163,7 +163,7 @@ func callbackWrap(a *callbackArgs) {
163163 switch inType.Kind() {
164164 case reflect.Float32, reflect.Float64:
165165 slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize)
166- if floatsN+slots > numOfFloatRegisters {
166+ if floatsN+slots > numOfFloatRegisters() {
167167 if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
168168 // Darwin ARM64: read from packed stack with proper alignment
169169 args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType)
@@ -190,7 +190,7 @@ func callbackWrap(a *callbackArgs) {
190190 }
191191 } else {
192192 // the integers begin after the floats in frame
193- pos := intsN + numOfFloatRegisters
193+ pos := intsN + numOfFloatRegisters()
194194 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem()
195195 }
196196 intsN += slots
@@ -256,8 +256,12 @@ func callbackasmAddr(i int) uintptr {
256256 switch runtime.GOARCH {
257257 default:
258258 panic("purego: unsupported architecture")
259- case "386", "amd64":
259+ case "amd64":
260+ // On amd64, each callback entry is just a CALL instruction (5 bytes)
260261 entrySize = 5
262+ case "386":
263+ // On 386, each callback entry is MOVL $imm, CX (5 bytes) + JMP (5 bytes)
264+ entrySize = 10
261265 case "arm", "arm64", "loong64", "riscv64":
262266 // On ARM, ARM64, Loong64, and RISCV64, each entry is a MOV instruction
263267 // followed by a branch instruction
@@ -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 || arm || arm64 || loong64 || riscv64)) || netbsd4+//go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd
5 5
6 package purego6 package purego
7 7
@@ -152,7 +152,7 @@ func callbackWrap(a *callbackArgs) {
152 var intsN int152 var intsN int
153 // stackSlot points to the index into frame of the current stack element.153 // stackSlot points to the index into frame of the current stack element.
154 // The stack begins after the float and integer registers.154 // The stack begins after the float and integer registers.
155- stackSlot := numOfIntegerRegisters() + numOfFloatRegisters155+ stackSlot := numOfIntegerRegisters() + numOfFloatRegisters()
156 // stackByteOffset tracks the byte offset within the stack area for Darwin ARM64156 // 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.157 // tight packing. On Darwin ARM64, C passes small types packed on the stack.
158 stackByteOffset := uintptr(0)158 stackByteOffset := uintptr(0)
@@ -163,7 +163,7 @@ func callbackWrap(a *callbackArgs) {
163 switch inType.Kind() {163 switch inType.Kind() {
164 case reflect.Float32, reflect.Float64:164 case reflect.Float32, reflect.Float64:
165 slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize)165 slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize)
166- if floatsN+slots > numOfFloatRegisters {166+ if floatsN+slots > numOfFloatRegisters() {
167 if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {167 if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
168 // Darwin ARM64: read from packed stack with proper alignment168 // Darwin ARM64: read from packed stack with proper alignment
169 args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType)169 args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType)
@@ -190,7 +190,7 @@ func callbackWrap(a *callbackArgs) {
190 }190 }
191 } else {191 } else {
192 // the integers begin after the floats in frame192 // the integers begin after the floats in frame
193- pos := intsN + numOfFloatRegisters193+ pos := intsN + numOfFloatRegisters()
194 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem()194 args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem()
195 }195 }
196 intsN += slots196 intsN += slots
@@ -256,8 +256,12 @@ func callbackasmAddr(i int) uintptr {
256 switch runtime.GOARCH {256 switch runtime.GOARCH {
257 default:257 default:
258 panic("purego: unsupported architecture")258 panic("purego: unsupported architecture")
259- case "386", "amd64":259+ case "amd64":
260+ // On amd64, each callback entry is just a CALL instruction (5 bytes)
260 entrySize = 5261 entrySize = 5
262+ case "386":
263+ // On 386, each callback entry is MOVL $imm, CX (5 bytes) + JMP (5 bytes)
264+ entrySize = 10
261 case "arm", "arm64", "loong64", "riscv64":265 case "arm", "arm64", "loong64", "riscv64":
262 // On ARM, ARM64, Loong64, and RISCV64, each entry is a MOV instruction266 // On ARM, ARM64, Loong64, and RISCV64, each entry is a MOV instruction
263 // followed by a branch instruction267 // followed by a branch instruction
M wincallback.go +29 -0
@@ -17,6 +17,34 @@ import (
1717
1818 const maxCallback = 2000
1919
20+func genasm386() {
21+ var buf bytes.Buffer
22+
23+ buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT.
24+
25+//go:build linux
26+
27+// External code calls into callbackasm at an offset corresponding
28+// to the callback index. Callbackasm is a table of MOVL and JMP instructions.
29+// The MOVL instruction loads CX with the callback index, and the
30+// JMP instruction branches to callbackasm1.
31+// callbackasm1 takes the callback index from CX and
32+// indexes into an array that stores information about each callback.
33+// It then calls the Go implementation for that callback.
34+#include "textflag.h"
35+
36+TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
37+`)
38+ for i := 0; i < maxCallback; i++ {
39+ fmt.Fprintf(&buf, "\tMOVL $%d, CX\n", i)
40+ buf.WriteString("\tJMP callbackasm1(SB)\n")
41+ }
42+ if err := os.WriteFile("zcallback_386.s", buf.Bytes(), 0644); err != nil {
43+ fmt.Fprintf(os.Stderr, "wincallback: %s\n", err)
44+ os.Exit(2)
45+ }
46+}
47+
2048 func genasmAmd64() {
2149 var buf bytes.Buffer
2250
@@ -166,6 +194,7 @@ TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
166194 }
167195
168196 func main() {
197+ genasm386()
169198 genasmAmd64()
170199 genasmArm()
171200 genasmArm64()
@@ -17,6 +17,34 @@ import (
17 17
18 const maxCallback = 200018 const maxCallback = 2000
19 19
20+func genasm386() {
21+ var buf bytes.Buffer
22+
23+ buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT.
24+
25+//go:build linux
26+
27+// External code calls into callbackasm at an offset corresponding
28+// to the callback index. Callbackasm is a table of MOVL and JMP instructions.
29+// The MOVL instruction loads CX with the callback index, and the
30+// JMP instruction branches to callbackasm1.
31+// callbackasm1 takes the callback index from CX and
32+// indexes into an array that stores information about each callback.
33+// It then calls the Go implementation for that callback.
34+#include "textflag.h"
35+
36+TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
37+`)
38+ for i := 0; i < maxCallback; i++ {
39+ fmt.Fprintf(&buf, "\tMOVL $%d, CX\n", i)
40+ buf.WriteString("\tJMP callbackasm1(SB)\n")
41+ }
42+ if err := os.WriteFile("zcallback_386.s", buf.Bytes(), 0644); err != nil {
43+ fmt.Fprintf(os.Stderr, "wincallback: %s\n", err)
44+ os.Exit(2)
45+ }
46+}
47+
20 func genasmAmd64() {48 func genasmAmd64() {
21 var buf bytes.Buffer49 var buf bytes.Buffer
22 50
@@ -166,6 +194,7 @@ TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0
166 }194 }
167 195
168 func main() {196 func main() {
197+ genasm386()
169 genasmAmd64()198 genasmAmd64()
170 genasmArm()199 genasmArm()
171 genasmArm64()200 genasmArm64()
A zcallback_386.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