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 Unverified
B5690EEEBB952194The 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: | |||
| 121 | 121 | sudo apt-get update | |
| 122 | 122 | sudo apt-get install gcc-multilib | |
| 123 | 123 | sudo apt-get install g++-multilib | |
| 124 | + | env CGO_ENABLED=0 GOARCH=386 go test -shuffle=on -v -count=10 ./... | |
| 124 | 125 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... | |
| 125 | 126 | ||
| 126 | 127 | - name: go test (Linux arm) | |
| @@ -121,6 +121,7 @@ jobs: | |||||
| 121 | sudo apt-get update | 121 | sudo apt-get update | ||
| 122 | sudo apt-get install gcc-multilib | 122 | sudo apt-get install gcc-multilib | ||
| 123 | sudo apt-get install g++-multilib | 123 | 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 | |||
| 42 | 42 | ||
| 43 | 43 | - **Android**: 386<sup>1</sup>, arm<sup>1</sup> | |
| 44 | 44 | - **FreeBSD**: amd64<sup>2</sup>, arm64<sup>2</sup> | |
| 45 | - | - **Linux**: 386<sup>1</sup>, arm, loong64, riscv64 | |
| 45 | + | - **Linux**: 386, arm, loong64, riscv64 | |
| 46 | 46 | - **Windows**: 386<sup>3</sup>, arm<sup>3,4</sup> | |
| 47 | 47 | ||
| 48 | 48 | #### 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, riscv64 | 45 | + | - **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 Notes | 48 | #### Support Notes | ||
M callback_test.go +1 -1
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build darwin || (linux && (amd64 || arm || arm64 || loong64 || riscv64)) | |
| 4 | + | //go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64)) | |
| 5 | 5 | ||
| 6 | 6 | package purego_test | |
| 7 | 7 | ||
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2023 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build darwin || (linux && (amd64 || arm || arm64 || loong64 || riscv64)) | 4 | + | //go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64)) |
| 5 | 5 | ||||
| 6 | package purego_test | 6 | package purego_test | ||
| 7 | 7 | ||||
M func.go +33 -9
| @@ -133,7 +133,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 133 | 133 | panic("purego: cfn is nil") | |
| 134 | 134 | } | |
| 135 | 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 | 137 | panic("purego: float returns are not supported") | |
| 138 | 138 | } | |
| 139 | 139 | { | |
| @@ -167,10 +167,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 167 | 167 | stack++ | |
| 168 | 168 | } | |
| 169 | 169 | 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() { | |
| 174 | 171 | floats++ | |
| 175 | 172 | } else { | |
| 176 | 173 | stack++ | |
| @@ -227,7 +224,10 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 227 | 224 | ||
| 228 | 225 | v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { | |
| 229 | 226 | 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 | |
| 231 | 231 | var numInts int | |
| 232 | 232 | var numFloats int | |
| 233 | 233 | var numStack int | |
| @@ -247,7 +247,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 247 | 247 | } | |
| 248 | 248 | } | |
| 249 | 249 | addFloat = func(x uintptr) { | |
| 250 | - | if numFloats < numOfFloatRegisters { | |
| 250 | + | if numFloats < numOfFloatRegisters() { | |
| 251 | 251 | floats[numFloats] = x | |
| 252 | 252 | numFloats++ | |
| 253 | 253 | } else { | |
| @@ -358,7 +358,12 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 358 | 358 | case reflect.Float32: | |
| 359 | 359 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |
| 360 | 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 | 367 | case reflect.Float64: | |
| 363 | 368 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |
| 364 | 369 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |
| @@ -479,6 +484,22 @@ func roundUpTo8(val uintptr) uintptr { | |||
| 479 | 484 | 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 | 503 | func numOfIntegerRegisters() int { | |
| 483 | 504 | switch runtime.GOARCH { | |
| 484 | 505 | case "arm64", "loong64", "riscv64": | |
| @@ -487,6 +508,9 @@ func numOfIntegerRegisters() int { | |||
| 487 | 508 | return 6 | |
| 488 | 509 | case "arm": | |
| 489 | 510 | return 4 | |
| 511 | + | case "386": | |
| 512 | + | // i386 SysV ABI passes all arguments on the stack | |
| 513 | + | return 0 | |
| 490 | 514 | default: | |
| 491 | 515 | // since this platform isn't supported and can therefore only access | |
| 492 | 516 | // integer registers it is fine to return the maxArgs | |
| @@ -508,7 +532,7 @@ func estimateStackBytes(ty reflect.Type) int { | |||
| 508 | 532 | usesInt := arg.Kind() != reflect.Float32 && arg.Kind() != reflect.Float64 | |
| 509 | 533 | if usesInt && numInts < numOfIntegerRegisters() { | |
| 510 | 534 | numInts++ | |
| 511 | - | } else if !usesInt && numFloats < numOfFloatRegisters { | |
| 535 | + | } else if !usesInt && numFloats < numOfFloatRegisters() { | |
| 512 | 536 | numFloats++ | |
| 513 | 537 | } else { | |
| 514 | 538 | // 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]uintptr | 226 | 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 | |||
| 231 | var numInts int | 231 | var numInts int | ||
| 232 | var numFloats int | 232 | var numFloats int | ||
| 233 | var numStack int | 233 | 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] = x | 251 | 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) &^ align8ByteMask | 484 | 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 6 | 508 | return 6 | ||
| 488 | case "arm": | 509 | case "arm": | ||
| 489 | return 4 | 510 | 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 access | 515 | // since this platform isn't supported and can therefore only access | ||
| 492 | // integer registers it is fine to return the maxArgs | 516 | // 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.Float64 | 532 | 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 bytes | 538 | // Goes to stack - accumulate total bytes | ||
M func_test.go +7 -4
| @@ -50,7 +50,7 @@ func TestRegisterFunc(t *testing.T) { | |||
| 50 | 50 | } | |
| 51 | 51 | ||
| 52 | 52 | func Test_qsort(t *testing.T) { | |
| 53 | - | if runtime.GOARCH != "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 | 54 | t.Skip("Platform doesn't support Floats") | |
| 55 | 55 | return | |
| 56 | 56 | } | |
| @@ -79,10 +79,13 @@ func Test_qsort(t *testing.T) { | |||
| 79 | 79 | } | |
| 80 | 80 | ||
| 81 | 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 | 83 | t.Skip("Platform doesn't support Floats") | |
| 84 | 84 | 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 | 89 | library, err := getSystemLibrary() | |
| 87 | 90 | if err != nil { | |
| 88 | 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 | 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 | 125 | t.Skip("Platform doesn't support callbacks") | |
| 123 | 126 | return | |
| 124 | 127 | } | |
| @@ -373,7 +376,7 @@ func TestABI_ArgumentPassing(t *testing.T) { | |||
| 373 | 376 | if tt.name == "20_int32" && (runtime.GOOS != "darwin" || runtime.GOARCH != "arm64") { | |
| 374 | 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 | 380 | t.Skip("float32 stack arguments not yet supported on this platform") | |
| 378 | 381 | } | |
| 379 | 382 | // 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 | return | 55 | 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 | return | 84 | 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 | return | 126 | 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 AMD64 | 382 | // Struct tests require Darwin ARM64 or AMD64 | ||
M internal/cgo/syscall_cgo_unix.go +1 -1
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build freebsd || (linux && !(amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | |
| 4 | + | //go:build freebsd || (linux && !(386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | |
| 5 | 5 | ||
| 6 | 6 | package cgo | |
| 7 | 7 | ||
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build freebsd || (linux && !(amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | 4 | + | //go:build freebsd || (linux && !(386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd |
| 5 | 5 | ||||
| 6 | package cgo | 6 | 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 | |||
| 78 | 78 | if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 { | |
| 79 | 79 | // if this doesn't fit entirely in registers then | |
| 80 | 80 | // 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() | |
| 83 | 83 | } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() { | |
| 84 | 84 | *numInts = numOfIntegerRegisters() | |
| 85 | 85 | } | |
| @@ -365,7 +365,7 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |||
| 365 | 365 | isInt := !isFloat && kind != reflect.Struct | |
| 366 | 366 | primitiveOnStack := | |
| 367 | 367 | (isInt && numInts >= numOfIntegerRegisters()) || | |
| 368 | - | (isFloat && numFloats >= numOfFloatRegisters) | |
| 368 | + | (isFloat && numFloats >= numOfFloatRegisters()) | |
| 369 | 369 | if primitiveOnStack { | |
| 370 | 370 | return true | |
| 371 | 371 | } | |
| @@ -382,7 +382,7 @@ func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |||
| 382 | 382 | ||
| 383 | 383 | if hfa { | |
| 384 | 384 | need := v.NumField() | |
| 385 | - | return numFloats+need > numOfFloatRegisters | |
| 385 | + | return numFloats+need > numOfFloatRegisters() | |
| 386 | 386 | } | |
| 387 | 387 | ||
| 388 | 388 | if hva { | |
| @@ -407,7 +407,7 @@ func structFitsInRegisters(val reflect.Value, tempNumInts, tempNumFloats int) (b | |||
| 407 | 407 | ||
| 408 | 408 | if hfa { | |
| 409 | 409 | // HFA: check if elements fit in float registers | |
| 410 | - | if tempNumFloats+val.NumField() <= numOfFloatRegisters { | |
| 410 | + | if tempNumFloats+val.NumField() <= numOfFloatRegisters() { | |
| 411 | 411 | return true, tempNumInts, tempNumFloats + val.NumField() | |
| 412 | 412 | } | |
| 413 | 413 | } else if hva { | |
| @@ -451,7 +451,7 @@ func collectStackArgs(args []reflect.Value, startIdx int, numInts, numFloats int | |||
| 451 | 451 | // Primitive argument | |
| 452 | 452 | isFloat := val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64 | |
| 453 | 453 | if isFloat { | |
| 454 | - | fitsInRegister = tempNumFloats < numOfFloatRegisters | |
| 454 | + | fitsInRegister = tempNumFloats < numOfFloatRegisters() | |
| 455 | 455 | newNumFloats = tempNumFloats + 1 | |
| 456 | 456 | newNumInts = tempNumInts | |
| 457 | 457 | } 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 then | 79 | // if this doesn't fit entirely in registers then | ||
| 80 | // each element goes onto the stack | 80 | // each element goes onto the stack | ||
| 81 | - | if hfa && *numFloats+v.NumField() > numOfFloatRegisters { | 81 | + | if hfa && *numFloats+v.NumField() > numOfFloatRegisters() { |
| 82 | - | *numFloats = numOfFloatRegisters | 82 | + | *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.Struct | 365 | 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 true | 370 | 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 > numOfFloatRegisters | 385 | + | 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 registers | 409 | // 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 argument | 451 | // Primitive argument | ||
| 452 | isFloat := val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64 | 452 | isFloat := val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64 | ||
| 453 | if isFloat { | 453 | if isFloat { | ||
| 454 | - | fitsInRegister = tempNumFloats < numOfFloatRegisters | 454 | + | fitsInRegister = tempNumFloats < numOfFloatRegisters() |
| 455 | newNumFloats = tempNumFloats + 1 | 455 | newNumFloats = tempNumFloats + 1 | ||
| 456 | newNumInts = tempNumInts | 456 | 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 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // 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 | 6 | package purego | |
| 7 | 7 | ||
| @@ -13,8 +13,7 @@ package purego | |||
| 13 | 13 | type CDecl struct{} | |
| 14 | 14 | ||
| 15 | 15 | const ( | |
| 16 | - | maxArgs = 15 | |
| 17 | - | numOfFloatRegisters = 8 // arm64 and amd64 both have 8 float registers | |
| 16 | + | maxArgs = 15 | |
| 18 | 17 | ) | |
| 19 | 18 | ||
| 20 | 19 | type syscall15Args struct { | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build !arm && (darwin || freebsd || linux || netbsd || windows) | 4 | + | //go:build !386 && !arm && (darwin || freebsd || linux || netbsd || windows) |
| 5 | 5 | ||||
| 6 | package purego | 6 | 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 = 15 | 16 | + | 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 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // 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 | 6 | package purego | |
| 7 | 7 | ||
| @@ -13,8 +13,7 @@ package purego | |||
| 13 | 13 | type CDecl struct{} | |
| 14 | 14 | ||
| 15 | 15 | const ( | |
| 16 | - | maxArgs = 32 | |
| 17 | - | numOfFloatRegisters = 16 | |
| 16 | + | maxArgs = 32 | |
| 18 | 17 | ) | |
| 19 | 18 | ||
| 20 | 19 | type syscall15Args struct { | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build arm && (freebsd || linux || netbsd || windows) | 4 | + | //go:build (386 || arm) && (freebsd || linux || netbsd || windows) |
| 5 | 5 | ||||
| 6 | package purego | 6 | 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 = 32 | 16 | + | 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 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // 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 | 6 | package purego | |
| 7 | 7 | ||
| @@ -17,5 +17,5 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a | |||
| 17 | 17 | } | |
| 18 | 18 | ||
| 19 | 19 | func NewCallback(_ any) uintptr { | |
| 20 | - | panic("purego: NewCallback on Linux is only supported on amd64/arm64/arm/loong64/riscv64") | |
| 20 | + | panic("purego: NewCallback on Linux is only supported on 386/amd64/arm64/arm/loong64/riscv64") | |
| 21 | 21 | } | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build cgo && !(amd64 || arm || arm64 || loong64 || riscv64) | 4 | + | //go:build cgo && !(386 || amd64 || arm || arm64 || loong64 || riscv64) |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| @@ -17,5 +17,5 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a | |||||
| 17 | } | 17 | } | ||
| 18 | 18 | ||||
| 19 | func NewCallback(_ any) uintptr { | 19 | func NewCallback(_ any) uintptr { | ||
| 20 | - | panic("purego: NewCallback on Linux is only supported on amd64/arm64/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 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build darwin || freebsd || (linux && (amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | |
| 4 | + | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | |
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| @@ -152,7 +152,7 @@ func callbackWrap(a *callbackArgs) { | |||
| 152 | 152 | var intsN int | |
| 153 | 153 | // stackSlot points to the index into frame of the current stack element. | |
| 154 | 154 | // The stack begins after the float and integer registers. | |
| 155 | - | stackSlot := numOfIntegerRegisters() + numOfFloatRegisters | |
| 155 | + | stackSlot := numOfIntegerRegisters() + numOfFloatRegisters() | |
| 156 | 156 | // stackByteOffset tracks the byte offset within the stack area for Darwin ARM64 | |
| 157 | 157 | // tight packing. On Darwin ARM64, C passes small types packed on the stack. | |
| 158 | 158 | stackByteOffset := uintptr(0) | |
| @@ -163,7 +163,7 @@ func callbackWrap(a *callbackArgs) { | |||
| 163 | 163 | switch inType.Kind() { | |
| 164 | 164 | case reflect.Float32, reflect.Float64: | |
| 165 | 165 | slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize) | |
| 166 | - | if floatsN+slots > numOfFloatRegisters { | |
| 166 | + | if floatsN+slots > numOfFloatRegisters() { | |
| 167 | 167 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | |
| 168 | 168 | // Darwin ARM64: read from packed stack with proper alignment | |
| 169 | 169 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | |
| @@ -190,7 +190,7 @@ func callbackWrap(a *callbackArgs) { | |||
| 190 | 190 | } | |
| 191 | 191 | } else { | |
| 192 | 192 | // the integers begin after the floats in frame | |
| 193 | - | pos := intsN + numOfFloatRegisters | |
| 193 | + | pos := intsN + numOfFloatRegisters() | |
| 194 | 194 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() | |
| 195 | 195 | } | |
| 196 | 196 | intsN += slots | |
| @@ -256,8 +256,12 @@ func callbackasmAddr(i int) uintptr { | |||
| 256 | 256 | switch runtime.GOARCH { | |
| 257 | 257 | default: | |
| 258 | 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 | 261 | entrySize = 5 | |
| 262 | + | case "386": | |
| 263 | + | // On 386, each callback entry is MOVL $imm, CX (5 bytes) + JMP (5 bytes) | |
| 264 | + | entrySize = 10 | |
| 261 | 265 | case "arm", "arm64", "loong64", "riscv64": | |
| 262 | 266 | // On ARM, ARM64, Loong64, and RISCV64, each entry is a MOV instruction | |
| 263 | 267 | // followed by a branch instruction | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build darwin || freebsd || (linux && (amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | 4 | + | //go:build darwin || freebsd || (linux && (386 || amd64 || arm || arm64 || loong64 || riscv64)) || netbsd |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| @@ -152,7 +152,7 @@ func callbackWrap(a *callbackArgs) { | |||||
| 152 | var intsN int | 152 | 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() + numOfFloatRegisters | 155 | + | stackSlot := numOfIntegerRegisters() + numOfFloatRegisters() |
| 156 | // stackByteOffset tracks the byte offset within the stack area for Darwin ARM64 | 156 | // 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 alignment | 168 | // 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 frame | 192 | // the integers begin after the floats in frame | ||
| 193 | - | pos := intsN + numOfFloatRegisters | 193 | + | 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 += slots | 196 | 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 = 5 | 261 | 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 instruction | 266 | // On ARM, ARM64, Loong64, and RISCV64, each entry is a MOV instruction | ||
| 263 | // followed by a branch instruction | 267 | // followed by a branch instruction | ||
M wincallback.go +29 -0
| @@ -17,6 +17,34 @@ import ( | |||
| 17 | 17 | ||
| 18 | 18 | 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 | 48 | func genasmAmd64() { | |
| 21 | 49 | var buf bytes.Buffer | |
| 22 | 50 | ||
| @@ -166,6 +194,7 @@ TEXT callbackasm(SB), NOSPLIT|NOFRAME, $0 | |||
| 166 | 194 | } | |
| 167 | 195 | ||
| 168 | 196 | func main() { | |
| 197 | + | genasm386() | |
| 169 | 198 | genasmAmd64() | |
| 170 | 199 | genasmArm() | |
| 171 | 200 | genasmArm64() | |
| @@ -17,6 +17,34 @@ import ( | |||||
| 17 | 17 | ||||
| 18 | const maxCallback = 2000 | 18 | 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.Buffer | 49 | 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() | ||