purego: add support for linux/arm32 (#391)
This PR adds the necessary code logic to support linux/arm32 with both CGO enabled/disabled. Closes #336 Updats #369
- commit
- 93970fe7f85ee188499e64091de23e8929094062
- author
- George Adams · · 2026-01-29 14:02 UTC
- committer
- GitHub · · 2026-01-29 14:02 UTC
- parent
- 1d6e403
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
18 files · +4665 -53
M .github/workflows/test.yml +16 -0
| @@ -119,6 +119,22 @@ jobs: | |||
| 119 | 119 | sudo apt-get install gcc-multilib | |
| 120 | 120 | sudo apt-get install g++-multilib | |
| 121 | 121 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... | |
| 122 | + | ||
| 123 | + | - name: go test (Linux arm) | |
| 124 | + | if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} | |
| 125 | + | run: | | |
| 126 | + | sudo apt-get update | |
| 127 | + | sudo apt-get install -y qemu-user gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross | |
| 128 | + | # Create symlink for the dynamic linker that QEMU expects | |
| 129 | + | sudo ln -sf /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3 | |
| 130 | + | go env -w CC=arm-linux-gnueabihf-gcc | |
| 131 | + | go env -w CXX=arm-linux-gnueabihf-g++ | |
| 132 | + | env GOOS=linux GOARCH=arm CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |
| 133 | + | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |
| 134 | + | env GOOS=linux GOARCH=arm CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |
| 135 | + | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |
| 136 | + | go env -u CC | |
| 137 | + | go env -u CXX | |
| 122 | 138 | ||
| 123 | 139 | - name: go test (Linux riscv64) | |
| 124 | 140 | if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') }} | |
| @@ -119,6 +119,22 @@ jobs: | |||||
| 119 | sudo apt-get install gcc-multilib | 119 | sudo apt-get install gcc-multilib | ||
| 120 | sudo apt-get install g++-multilib | 120 | sudo apt-get install g++-multilib | ||
| 121 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... | 121 | env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -count=10 ./... | ||
| 122 | + | ||||
| 123 | + | - name: go test (Linux arm) | |||
| 124 | + | if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} | |||
| 125 | + | run: | | |||
| 126 | + | sudo apt-get update | |||
| 127 | + | sudo apt-get install -y qemu-user gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross | |||
| 128 | + | # Create symlink for the dynamic linker that QEMU expects | |||
| 129 | + | sudo ln -sf /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3 | |||
| 130 | + | go env -w CC=arm-linux-gnueabihf-gcc | |||
| 131 | + | go env -w CXX=arm-linux-gnueabihf-g++ | |||
| 132 | + | env GOOS=linux GOARCH=arm CGO_ENABLED=0 go test -c -o=purego-test-nocgo . | |||
| 133 | + | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-nocgo -test.shuffle=on -test.v -test.count=10 | |||
| 134 | + | env GOOS=linux GOARCH=arm CGO_ENABLED=1 go test -c -o=purego-test-cgo . | |||
| 135 | + | env QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf qemu-arm ./purego-test-cgo -test.shuffle=on -test.v -test.count=10 | |||
| 136 | + | go env -u CC | |||
| 137 | + | go env -u CXX | |||
| 122 | 138 | ||||
| 123 | - name: go test (Linux riscv64) | 139 | - name: go test (Linux riscv64) | ||
| 124 | if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') }} | 140 | if: ${{ runner.os == 'Linux' && runner.arch != 'ARM64' && !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') && !startsWith(matrix.go, '1.20.') && !startsWith(matrix.go, '1.21.') }} | ||
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<sup>1</sup>, loong64, riscv64 | |
| 45 | + | - **Linux**: 386<sup>1</sup>, 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<sup>1</sup>, loong64, riscv64 | 45 | + | - **Linux**: 386<sup>1</sup>, 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 || arm64 || loong64 || riscv64)) | |
| 4 | + | //go:build darwin || (linux && (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 || arm64 || loong64 || riscv64)) | 4 | + | //go:build darwin || (linux && (amd64 || arm || arm64 || loong64 || riscv64)) |
| 5 | 5 | ||||
| 6 | package purego_test | 6 | package purego_test | ||
| 7 | 7 | ||||
M func.go +21 -22
| @@ -120,6 +120,7 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) { | |||
| 120 | 120 | // | |
| 121 | 121 | // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C | |
| 122 | 122 | func RegisterFunc(fptr any, cfn uintptr) { | |
| 123 | + | const is32bit = unsafe.Sizeof(uintptr(0)) == 4 | |
| 123 | 124 | fn := reflect.ValueOf(fptr).Elem() | |
| 124 | 125 | ty := fn.Type() | |
| 125 | 126 | if ty.Kind() != reflect.Func { | |
| @@ -132,7 +133,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 132 | 133 | panic("purego: cfn is nil") | |
| 133 | 134 | } | |
| 134 | 135 | if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) && | |
| 135 | - | runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 136 | + | runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 136 | 137 | panic("purego: float returns are not supported") | |
| 137 | 138 | } | |
| 138 | 139 | { | |
| @@ -166,8 +167,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 166 | 167 | stack++ | |
| 167 | 168 | } | |
| 168 | 169 | case reflect.Float32, reflect.Float64: | |
| 169 | - | const is32bit = unsafe.Sizeof(uintptr(0)) == 4 | |
| 170 | - | if is32bit { | |
| 170 | + | if is32bit && runtime.GOARCH != "arm" { | |
| 171 | 171 | panic("purego: floats only supported on 64bit platforms") | |
| 172 | 172 | } | |
| 173 | 173 | if floats < numOfFloatRegisters { | |
| @@ -247,7 +247,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 247 | 247 | } | |
| 248 | 248 | } | |
| 249 | 249 | addFloat = func(x uintptr) { | |
| 250 | - | if numFloats < len(floats) { | |
| 250 | + | if numFloats < numOfFloatRegisters { | |
| 251 | 251 | floats[numFloats] = x | |
| 252 | 252 | numFloats++ | |
| 253 | 253 | } else { | |
| @@ -318,25 +318,11 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 318 | 318 | defer thePool.Put(syscall) | |
| 319 | 319 | ||
| 320 | 320 | if runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64" { | |
| 321 | - | *syscall = syscall15Args{ | |
| 322 | - | cfn, | |
| 323 | - | sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], | |
| 324 | - | sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | |
| 325 | - | sysargs[12], sysargs[13], sysargs[14], | |
| 326 | - | floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], | |
| 327 | - | 0, | |
| 328 | - | } | |
| 321 | + | syscall.Set(cfn, sysargs[:], floats[:], 0) | |
| 329 | 322 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |
| 330 | 323 | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | |
| 331 | 324 | // Use the normal arm64 calling convention even on Windows | |
| 332 | - | *syscall = syscall15Args{ | |
| 333 | - | cfn, | |
| 334 | - | sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], | |
| 335 | - | sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | |
| 336 | - | sysargs[12], sysargs[13], sysargs[14], | |
| 337 | - | floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], | |
| 338 | - | arm64_r8, | |
| 339 | - | } | |
| 325 | + | syscall.Set(cfn, sysargs[:], floats[:], arm64_r8) | |
| 340 | 326 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | |
| 341 | 327 | } else { | |
| 342 | 328 | *syscall = syscall15Args{} | |
| @@ -376,7 +362,11 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 376 | 362 | case reflect.Float64: | |
| 377 | 363 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | |
| 378 | 364 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | |
| 379 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |
| 365 | + | if is32bit { | |
| 366 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |
| 367 | + | } else { | |
| 368 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |
| 369 | + | } | |
| 380 | 370 | case reflect.Struct: | |
| 381 | 371 | v = getStruct(outType, *syscall) | |
| 382 | 372 | default: | |
| @@ -394,6 +384,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 394 | 384 | } | |
| 395 | 385 | ||
| 396 | 386 | func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any { | |
| 387 | + | const is32bit = unsafe.Sizeof(uintptr(0)) == 4 | |
| 397 | 388 | switch v.Kind() { | |
| 398 | 389 | case reflect.String: | |
| 399 | 390 | ptr := strings.CString(v.String()) | |
| @@ -417,7 +408,13 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||
| 417 | 408 | case reflect.Float32: | |
| 418 | 409 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | |
| 419 | 410 | case reflect.Float64: | |
| 420 | - | addFloat(uintptr(math.Float64bits(v.Float()))) | |
| 411 | + | if is32bit { | |
| 412 | + | bits := math.Float64bits(v.Float()) | |
| 413 | + | addFloat(uintptr(bits)) | |
| 414 | + | addFloat(uintptr(bits >> 32)) | |
| 415 | + | } else { | |
| 416 | + | addFloat(uintptr(math.Float64bits(v.Float()))) | |
| 417 | + | } | |
| 421 | 418 | case reflect.Struct: | |
| 422 | 419 | keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) | |
| 423 | 420 | default: | |
| @@ -488,6 +485,8 @@ func numOfIntegerRegisters() int { | |||
| 488 | 485 | return 8 | |
| 489 | 486 | case "amd64": | |
| 490 | 487 | return 6 | |
| 488 | + | case "arm": | |
| 489 | + | return 4 | |
| 491 | 490 | default: | |
| 492 | 491 | // since this platform isn't supported and can therefore only access | |
| 493 | 492 | // integer registers it is fine to return the maxArgs | |
| @@ -120,6 +120,7 @@ func RegisterLibFunc(fptr any, handle uintptr, name string) { | |||||
| 120 | // | 120 | // | ||
| 121 | // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C | 121 | // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C | ||
| 122 | func RegisterFunc(fptr any, cfn uintptr) { | 122 | func RegisterFunc(fptr any, cfn uintptr) { | ||
| 123 | + | const is32bit = unsafe.Sizeof(uintptr(0)) == 4 | |||
| 123 | fn := reflect.ValueOf(fptr).Elem() | 124 | fn := reflect.ValueOf(fptr).Elem() | ||
| 124 | ty := fn.Type() | 125 | ty := fn.Type() | ||
| 125 | if ty.Kind() != reflect.Func { | 126 | if ty.Kind() != reflect.Func { | ||
| @@ -132,7 +133,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 132 | panic("purego: cfn is nil") | 133 | panic("purego: cfn is nil") | ||
| 133 | } | 134 | } | ||
| 134 | 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) && | ||
| 135 | - | runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | 136 | + | runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { |
| 136 | panic("purego: float returns are not supported") | 137 | panic("purego: float returns are not supported") | ||
| 137 | } | 138 | } | ||
| 138 | { | 139 | { | ||
| @@ -166,8 +167,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 166 | stack++ | 167 | stack++ | ||
| 167 | } | 168 | } | ||
| 168 | case reflect.Float32, reflect.Float64: | 169 | case reflect.Float32, reflect.Float64: | ||
| 169 | - | const is32bit = unsafe.Sizeof(uintptr(0)) == 4 | 170 | + | if is32bit && runtime.GOARCH != "arm" { |
| 170 | - | if is32bit { | |||
| 171 | panic("purego: floats only supported on 64bit platforms") | 171 | panic("purego: floats only supported on 64bit platforms") | ||
| 172 | } | 172 | } | ||
| 173 | if floats < numOfFloatRegisters { | 173 | if floats < numOfFloatRegisters { | ||
| @@ -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 < len(floats) { | 250 | + | if numFloats < numOfFloatRegisters { |
| 251 | floats[numFloats] = x | 251 | floats[numFloats] = x | ||
| 252 | numFloats++ | 252 | numFloats++ | ||
| 253 | } else { | 253 | } else { | ||
| @@ -318,25 +318,11 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 318 | defer thePool.Put(syscall) | 318 | defer thePool.Put(syscall) | ||
| 319 | 319 | ||||
| 320 | if runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64" { | 320 | if runtime.GOARCH == "loong64" || runtime.GOARCH == "riscv64" { | ||
| 321 | - | *syscall = syscall15Args{ | 321 | + | syscall.Set(cfn, sysargs[:], floats[:], 0) |
| 322 | - | cfn, | |||
| 323 | - | sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], | |||
| 324 | - | sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | |||
| 325 | - | sysargs[12], sysargs[13], sysargs[14], | |||
| 326 | - | floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], | |||
| 327 | - | 0, | |||
| 328 | - | } | |||
| 329 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | 322 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | ||
| 330 | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | 323 | } else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { | ||
| 331 | // Use the normal arm64 calling convention even on Windows | 324 | // Use the normal arm64 calling convention even on Windows | ||
| 332 | - | *syscall = syscall15Args{ | 325 | + | syscall.Set(cfn, sysargs[:], floats[:], arm64_r8) |
| 333 | - | cfn, | |||
| 334 | - | sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], | |||
| 335 | - | sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], | |||
| 336 | - | sysargs[12], sysargs[13], sysargs[14], | |||
| 337 | - | floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], | |||
| 338 | - | arm64_r8, | |||
| 339 | - | } | |||
| 340 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | 326 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall)) | ||
| 341 | } else { | 327 | } else { | ||
| 342 | *syscall = syscall15Args{} | 328 | *syscall = syscall15Args{} | ||
| @@ -376,7 +362,11 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 376 | case reflect.Float64: | 362 | case reflect.Float64: | ||
| 377 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | 363 | // NOTE: syscall.r2 is only the floating return value on 64bit platforms. | ||
| 378 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | 364 | // On 32bit platforms syscall.r2 is the upper part of a 64bit return. | ||
| 379 | - | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | 365 | + | if is32bit { |
| 366 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1) | (uint64(syscall.f2) << 32))) | |||
| 367 | + | } else { | |||
| 368 | + | v.SetFloat(math.Float64frombits(uint64(syscall.f1))) | |||
| 369 | + | } | |||
| 380 | case reflect.Struct: | 370 | case reflect.Struct: | ||
| 381 | v = getStruct(outType, *syscall) | 371 | v = getStruct(outType, *syscall) | ||
| 382 | default: | 372 | default: | ||
| @@ -394,6 +384,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 394 | } | 384 | } | ||
| 395 | 385 | ||||
| 396 | func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any { | 386 | func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any { | ||
| 387 | + | const is32bit = unsafe.Sizeof(uintptr(0)) == 4 | |||
| 397 | switch v.Kind() { | 388 | switch v.Kind() { | ||
| 398 | case reflect.String: | 389 | case reflect.String: | ||
| 399 | ptr := strings.CString(v.String()) | 390 | ptr := strings.CString(v.String()) | ||
| @@ -417,7 +408,13 @@ func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat | |||||
| 417 | case reflect.Float32: | 408 | case reflect.Float32: | ||
| 418 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | 409 | addFloat(uintptr(math.Float32bits(float32(v.Float())))) | ||
| 419 | case reflect.Float64: | 410 | case reflect.Float64: | ||
| 420 | - | addFloat(uintptr(math.Float64bits(v.Float()))) | 411 | + | if is32bit { |
| 412 | + | bits := math.Float64bits(v.Float()) | |||
| 413 | + | addFloat(uintptr(bits)) | |||
| 414 | + | addFloat(uintptr(bits >> 32)) | |||
| 415 | + | } else { | |||
| 416 | + | addFloat(uintptr(math.Float64bits(v.Float()))) | |||
| 417 | + | } | |||
| 421 | case reflect.Struct: | 418 | case reflect.Struct: | ||
| 422 | keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) | 419 | keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive) | ||
| 423 | default: | 420 | default: | ||
| @@ -488,6 +485,8 @@ func numOfIntegerRegisters() int { | |||||
| 488 | return 8 | 485 | return 8 | ||
| 489 | case "amd64": | 486 | case "amd64": | ||
| 490 | return 6 | 487 | return 6 | ||
| 488 | + | case "arm": | |||
| 489 | + | return 4 | |||
| 491 | default: | 490 | default: | ||
| 492 | // since this platform isn't supported and can therefore only access | 491 | // since this platform isn't supported and can therefore only access | ||
| 493 | // integer registers it is fine to return the maxArgs | 492 | // integer registers it is fine to return the maxArgs | ||
M func_test.go +7 -7
| @@ -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 != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 53 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 54 | 54 | t.Skip("Platform doesn't support Floats") | |
| 55 | 55 | return | |
| 56 | 56 | } | |
| @@ -79,7 +79,7 @@ func Test_qsort(t *testing.T) { | |||
| 79 | 79 | } | |
| 80 | 80 | ||
| 81 | 81 | func TestRegisterFunc_Floats(t *testing.T) { | |
| 82 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 82 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 83 | 83 | t.Skip("Platform doesn't support Floats") | |
| 84 | 84 | return | |
| 85 | 85 | } | |
| @@ -118,7 +118,7 @@ func TestRegisterFunc_Floats(t *testing.T) { | |||
| 118 | 118 | } | |
| 119 | 119 | ||
| 120 | 120 | func TestRegisterLibFunc_Bool(t *testing.T) { | |
| 121 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 121 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | |
| 122 | 122 | t.Skip("Platform doesn't support callbacks") | |
| 123 | 123 | return | |
| 124 | 124 | } | |
| @@ -412,8 +412,8 @@ func TestABI_TooManyArguments(t *testing.T) { | |||
| 412 | 412 | } | |
| 413 | 413 | }) | |
| 414 | 414 | ||
| 415 | - | // Test that 25 int64 arguments (17 slots needed) exceeds the limit | |
| 416 | - | t.Run("25_int64_exceeds_limit", func(t *testing.T) { | |
| 415 | + | // Test that 35 int64 arguments (27 slots needed) exceeds the limit | |
| 416 | + | t.Run("35_int64_exceeds_limit", func(t *testing.T) { | |
| 417 | 417 | defer func() { | |
| 418 | 418 | if r := recover(); r != nil { | |
| 419 | 419 | t.Logf("Got expected panic: %v", r) | |
| @@ -422,8 +422,8 @@ func TestABI_TooManyArguments(t *testing.T) { | |||
| 422 | 422 | } | |
| 423 | 423 | }() | |
| 424 | 424 | ||
| 425 | - | var fn func(*byte, uintptr, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) | |
| 426 | - | purego.RegisterLibFunc(&fn, lib, "stack_25_int64_exceeds") | |
| 425 | + | var fn func(*byte, uintptr, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) | |
| 426 | + | purego.RegisterLibFunc(&fn, lib, "stack_35_int64_exceeds") | |
| 427 | 427 | }) | |
| 428 | 428 | } | |
| 429 | 429 | ||
| @@ -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 != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | 53 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && 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,7 +79,7 @@ func Test_qsort(t *testing.T) { | |||||
| 79 | } | 79 | } | ||
| 80 | 80 | ||||
| 81 | func TestRegisterFunc_Floats(t *testing.T) { | 81 | func TestRegisterFunc_Floats(t *testing.T) { | ||
| 82 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | 82 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && 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 | } | ||
| @@ -118,7 +118,7 @@ func TestRegisterFunc_Floats(t *testing.T) { | |||||
| 118 | } | 118 | } | ||
| 119 | 119 | ||||
| 120 | func TestRegisterLibFunc_Bool(t *testing.T) { | 120 | func TestRegisterLibFunc_Bool(t *testing.T) { | ||
| 121 | - | if runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { | 121 | + | if runtime.GOARCH != "arm" && runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" && runtime.GOARCH != "riscv64" { |
| 122 | t.Skip("Platform doesn't support callbacks") | 122 | t.Skip("Platform doesn't support callbacks") | ||
| 123 | return | 123 | return | ||
| 124 | } | 124 | } | ||
| @@ -412,8 +412,8 @@ func TestABI_TooManyArguments(t *testing.T) { | |||||
| 412 | } | 412 | } | ||
| 413 | }) | 413 | }) | ||
| 414 | 414 | ||||
| 415 | - | // Test that 25 int64 arguments (17 slots needed) exceeds the limit | 415 | + | // Test that 35 int64 arguments (27 slots needed) exceeds the limit |
| 416 | - | t.Run("25_int64_exceeds_limit", func(t *testing.T) { | 416 | + | t.Run("35_int64_exceeds_limit", func(t *testing.T) { |
| 417 | defer func() { | 417 | defer func() { | ||
| 418 | if r := recover(); r != nil { | 418 | if r := recover(); r != nil { | ||
| 419 | t.Logf("Got expected panic: %v", r) | 419 | t.Logf("Got expected panic: %v", r) | ||
| @@ -422,8 +422,8 @@ func TestABI_TooManyArguments(t *testing.T) { | |||||
| 422 | } | 422 | } | ||
| 423 | }() | 423 | }() | ||
| 424 | 424 | ||||
| 425 | - | var fn func(*byte, uintptr, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) | 425 | + | var fn func(*byte, uintptr, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64, int64) |
| 426 | - | purego.RegisterLibFunc(&fn, lib, "stack_25_int64_exceeds") | 426 | + | purego.RegisterLibFunc(&fn, lib, "stack_35_int64_exceeds") |
| 427 | }) | 427 | }) | ||
| 428 | } | 428 | } | ||
| 429 | 429 | ||||
M internal/cgo/syscall_cgo_unix.go +1 -1
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build freebsd || (linux && !(arm64 || amd64 || loong64 || riscv64)) || netbsd | |
| 4 | + | //go:build freebsd || (linux && !(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 && !(arm64 || amd64 || loong64 || riscv64)) || netbsd | 4 | + | //go:build freebsd || (linux && !(amd64 || arm || arm64 || loong64 || riscv64)) || netbsd |
| 5 | 5 | ||||
| 6 | package cgo | 6 | package cgo | ||
| 7 | 7 | ||||
A internal/fakecgo/asm_arm.s +52 -0
| @@ -0,0 +1,52 @@ | |||
| 1 | + | // Copyright 2012 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|NOFRAME, $0 | |
| 12 | + | SUB $(8*9), R13 // Reserve space for the floating point registers. | |
| 13 | + | ||
| 14 | + | // The C arguments arrive in R0, R1, R2, and R3. We want to | |
| 15 | + | // pass R0, R1, and R3 to Go, so we push those on the stack. | |
| 16 | + | // Also, save C callee-save registers R4-R12. | |
| 17 | + | MOVM.WP [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12], (R13) | |
| 18 | + | ||
| 19 | + | // Finally, save the link register R14. This also puts the | |
| 20 | + | // arguments we pushed for cgocallback where they need to be, | |
| 21 | + | // starting at 4(R13). | |
| 22 | + | MOVW.W R14, -4(R13) | |
| 23 | + | ||
| 24 | + | // Save VFP callee-saved registers D8-D15 (same as S16-S31). | |
| 25 | + | // Note: We always save these since we target hard-float ABI. | |
| 26 | + | MOVD F8, (13*4+8*1)(R13) | |
| 27 | + | MOVD F9, (13*4+8*2)(R13) | |
| 28 | + | MOVD F10, (13*4+8*3)(R13) | |
| 29 | + | MOVD F11, (13*4+8*4)(R13) | |
| 30 | + | MOVD F12, (13*4+8*5)(R13) | |
| 31 | + | MOVD F13, (13*4+8*6)(R13) | |
| 32 | + | MOVD F14, (13*4+8*7)(R13) | |
| 33 | + | MOVD F15, (13*4+8*8)(R13) | |
| 34 | + | ||
| 35 | + | BL runtime·load_g(SB) | |
| 36 | + | ||
| 37 | + | // We set up the arguments to cgocallback when saving registers above. | |
| 38 | + | BL runtime·cgocallback(SB) | |
| 39 | + | ||
| 40 | + | MOVD (13*4+8*1)(R13), F8 | |
| 41 | + | MOVD (13*4+8*2)(R13), F9 | |
| 42 | + | MOVD (13*4+8*3)(R13), F10 | |
| 43 | + | MOVD (13*4+8*4)(R13), F11 | |
| 44 | + | MOVD (13*4+8*5)(R13), F12 | |
| 45 | + | MOVD (13*4+8*6)(R13), F13 | |
| 46 | + | MOVD (13*4+8*7)(R13), F14 | |
| 47 | + | MOVD (13*4+8*8)(R13), F15 | |
| 48 | + | ||
| 49 | + | MOVW.P 4(R13), R14 | |
| 50 | + | MOVM.IAW (R13), [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12] | |
| 51 | + | ADD $(8*9), R13 | |
| 52 | + | MOVW R14, R15 | |
| @@ -0,0 +1,52 @@ | |||||
| 1 | + | // Copyright 2012 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|NOFRAME, $0 | |||
| 12 | + | SUB $(8*9), R13 // Reserve space for the floating point registers. | |||
| 13 | + | ||||
| 14 | + | // The C arguments arrive in R0, R1, R2, and R3. We want to | |||
| 15 | + | // pass R0, R1, and R3 to Go, so we push those on the stack. | |||
| 16 | + | // Also, save C callee-save registers R4-R12. | |||
| 17 | + | MOVM.WP [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12], (R13) | |||
| 18 | + | ||||
| 19 | + | // Finally, save the link register R14. This also puts the | |||
| 20 | + | // arguments we pushed for cgocallback where they need to be, | |||
| 21 | + | // starting at 4(R13). | |||
| 22 | + | MOVW.W R14, -4(R13) | |||
| 23 | + | ||||
| 24 | + | // Save VFP callee-saved registers D8-D15 (same as S16-S31). | |||
| 25 | + | // Note: We always save these since we target hard-float ABI. | |||
| 26 | + | MOVD F8, (13*4+8*1)(R13) | |||
| 27 | + | MOVD F9, (13*4+8*2)(R13) | |||
| 28 | + | MOVD F10, (13*4+8*3)(R13) | |||
| 29 | + | MOVD F11, (13*4+8*4)(R13) | |||
| 30 | + | MOVD F12, (13*4+8*5)(R13) | |||
| 31 | + | MOVD F13, (13*4+8*6)(R13) | |||
| 32 | + | MOVD F14, (13*4+8*7)(R13) | |||
| 33 | + | MOVD F15, (13*4+8*8)(R13) | |||
| 34 | + | ||||
| 35 | + | BL runtime·load_g(SB) | |||
| 36 | + | ||||
| 37 | + | // We set up the arguments to cgocallback when saving registers above. | |||
| 38 | + | BL runtime·cgocallback(SB) | |||
| 39 | + | ||||
| 40 | + | MOVD (13*4+8*1)(R13), F8 | |||
| 41 | + | MOVD (13*4+8*2)(R13), F9 | |||
| 42 | + | MOVD (13*4+8*3)(R13), F10 | |||
| 43 | + | MOVD (13*4+8*4)(R13), F11 | |||
| 44 | + | MOVD (13*4+8*5)(R13), F12 | |||
| 45 | + | MOVD (13*4+8*6)(R13), F13 | |||
| 46 | + | MOVD (13*4+8*7)(R13), F14 | |||
| 47 | + | MOVD (13*4+8*8)(R13), F15 | |||
| 48 | + | ||||
| 49 | + | MOVW.P 4(R13), R14 | |||
| 50 | + | MOVM.IAW (R13), [R0, R1, R3, R4, R5, R6, R7, R8, R9, g, R11, R12] | |||
| 51 | + | ADD $(8*9), R13 | |||
| 52 | + | MOVW R14, R15 | |||
M internal/fakecgo/go_util.go +3 -2
| @@ -28,8 +28,9 @@ func x_cgo_thread_start(arg *ThreadStart) { | |||
| 28 | 28 | abort() | |
| 29 | 29 | } | |
| 30 | 30 | // *ts = *arg would cause a writebarrier so copy using slices | |
| 31 | - | s1 := unsafe.Slice((*uintptr)(unsafe.Pointer(ts)), unsafe.Sizeof(*ts)/8) | |
| 32 | - | s2 := unsafe.Slice((*uintptr)(unsafe.Pointer(arg)), unsafe.Sizeof(*arg)/8) | |
| 31 | + | const ptrSize = unsafe.Sizeof(uintptr(0)) | |
| 32 | + | s1 := unsafe.Slice((*uintptr)(unsafe.Pointer(ts)), unsafe.Sizeof(*ts)/ptrSize) | |
| 33 | + | s2 := unsafe.Slice((*uintptr)(unsafe.Pointer(arg)), unsafe.Sizeof(*arg)/ptrSize) | |
| 33 | 34 | for i := range s2 { | |
| 34 | 35 | s1[i] = s2[i] | |
| 35 | 36 | } | |
| @@ -28,8 +28,9 @@ func x_cgo_thread_start(arg *ThreadStart) { | |||||
| 28 | abort() | 28 | abort() | ||
| 29 | } | 29 | } | ||
| 30 | // *ts = *arg would cause a writebarrier so copy using slices | 30 | // *ts = *arg would cause a writebarrier so copy using slices | ||
| 31 | - | s1 := unsafe.Slice((*uintptr)(unsafe.Pointer(ts)), unsafe.Sizeof(*ts)/8) | 31 | + | const ptrSize = unsafe.Sizeof(uintptr(0)) |
| 32 | - | s2 := unsafe.Slice((*uintptr)(unsafe.Pointer(arg)), unsafe.Sizeof(*arg)/8) | 32 | + | s1 := unsafe.Slice((*uintptr)(unsafe.Pointer(ts)), unsafe.Sizeof(*ts)/ptrSize) |
| 33 | + | s2 := unsafe.Slice((*uintptr)(unsafe.Pointer(arg)), unsafe.Sizeof(*arg)/ptrSize) | |||
| 33 | for i := range s2 { | 34 | for i := range s2 { | ||
| 34 | s1[i] = s2[i] | 35 | s1[i] = s2[i] | ||
| 35 | } | 36 | } | ||
A internal/fakecgo/trampolines_arm.s +81 -0
| @@ -0,0 +1,81 @@ | |||
| 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 ARM32, Go ABI0 uses stack-based calling convention. | |
| 11 | + | // Arguments are placed on the stack starting at 4(SP) after the prologue. | |
| 12 | + | ||
| 13 | + | TEXT x_cgo_init_trampoline(SB), NOSPLIT, $8-0 | |
| 14 | + | MOVW R0, 4(R13) | |
| 15 | + | MOVW R1, 8(R13) | |
| 16 | + | MOVW ·x_cgo_init_call(SB), R12 | |
| 17 | + | MOVW (R12), R12 | |
| 18 | + | CALL (R12) | |
| 19 | + | RET | |
| 20 | + | ||
| 21 | + | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8-0 | |
| 22 | + | MOVW R0, 4(R13) | |
| 23 | + | MOVW ·x_cgo_thread_start_call(SB), R12 | |
| 24 | + | MOVW (R12), R12 | |
| 25 | + | CALL (R12) | |
| 26 | + | RET | |
| 27 | + | ||
| 28 | + | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8-0 | |
| 29 | + | MOVW R0, 4(R13) | |
| 30 | + | MOVW ·x_cgo_setenv_call(SB), R12 | |
| 31 | + | MOVW (R12), R12 | |
| 32 | + | CALL (R12) | |
| 33 | + | RET | |
| 34 | + | ||
| 35 | + | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8-0 | |
| 36 | + | MOVW R0, 4(R13) | |
| 37 | + | MOVW ·x_cgo_unsetenv_call(SB), R12 | |
| 38 | + | MOVW (R12), R12 | |
| 39 | + | CALL (R12) | |
| 40 | + | RET | |
| 41 | + | ||
| 42 | + | TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0-0 | |
| 43 | + | CALL ·x_cgo_notify_runtime_init_done(SB) | |
| 44 | + | RET | |
| 45 | + | ||
| 46 | + | TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 | |
| 47 | + | CALL ·x_cgo_bindm(SB) | |
| 48 | + | RET | |
| 49 | + | ||
| 50 | + | // func setg_trampoline(setg uintptr, g uintptr) | |
| 51 | + | TEXT ·setg_trampoline(SB), NOSPLIT, $0-8 | |
| 52 | + | MOVW G+4(FP), R0 | |
| 53 | + | MOVW setg+0(FP), R12 | |
| 54 | + | BL (R12) | |
| 55 | + | RET | |
| 56 | + | ||
| 57 | + | TEXT threadentry_trampoline(SB), NOSPLIT, $8-0 | |
| 58 | + | // See crosscall2. | |
| 59 | + | MOVW R0, 4(R13) | |
| 60 | + | MOVW ·threadentry_call(SB), R12 | |
| 61 | + | MOVW (R12), R12 | |
| 62 | + | CALL (R12) | |
| 63 | + | RET | |
| 64 | + | ||
| 65 | + | TEXT ·call5(SB), NOSPLIT, $8-28 | |
| 66 | + | MOVW fn+0(FP), R12 | |
| 67 | + | MOVW a1+4(FP), R0 | |
| 68 | + | MOVW a2+8(FP), R1 | |
| 69 | + | MOVW a3+12(FP), R2 | |
| 70 | + | MOVW a4+16(FP), R3 | |
| 71 | + | MOVW a5+20(FP), R4 | |
| 72 | + | ||
| 73 | + | // Store 5th arg below SP (in local frame area) | |
| 74 | + | MOVW R4, arg5-8(SP) | |
| 75 | + | ||
| 76 | + | // Align SP to 8 bytes for call (required by ARM AAPCS) | |
| 77 | + | SUB $8, R13 | |
| 78 | + | CALL (R12) | |
| 79 | + | ADD $8, R13 | |
| 80 | + | MOVW R0, r1+24(FP) | |
| 81 | + | RET | |
| @@ -0,0 +1,81 @@ | |||||
| 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 ARM32, Go ABI0 uses stack-based calling convention. | |||
| 11 | + | // Arguments are placed on the stack starting at 4(SP) after the prologue. | |||
| 12 | + | ||||
| 13 | + | TEXT x_cgo_init_trampoline(SB), NOSPLIT, $8-0 | |||
| 14 | + | MOVW R0, 4(R13) | |||
| 15 | + | MOVW R1, 8(R13) | |||
| 16 | + | MOVW ·x_cgo_init_call(SB), R12 | |||
| 17 | + | MOVW (R12), R12 | |||
| 18 | + | CALL (R12) | |||
| 19 | + | RET | |||
| 20 | + | ||||
| 21 | + | TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8-0 | |||
| 22 | + | MOVW R0, 4(R13) | |||
| 23 | + | MOVW ·x_cgo_thread_start_call(SB), R12 | |||
| 24 | + | MOVW (R12), R12 | |||
| 25 | + | CALL (R12) | |||
| 26 | + | RET | |||
| 27 | + | ||||
| 28 | + | TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8-0 | |||
| 29 | + | MOVW R0, 4(R13) | |||
| 30 | + | MOVW ·x_cgo_setenv_call(SB), R12 | |||
| 31 | + | MOVW (R12), R12 | |||
| 32 | + | CALL (R12) | |||
| 33 | + | RET | |||
| 34 | + | ||||
| 35 | + | TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8-0 | |||
| 36 | + | MOVW R0, 4(R13) | |||
| 37 | + | MOVW ·x_cgo_unsetenv_call(SB), R12 | |||
| 38 | + | MOVW (R12), R12 | |||
| 39 | + | CALL (R12) | |||
| 40 | + | RET | |||
| 41 | + | ||||
| 42 | + | TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0-0 | |||
| 43 | + | CALL ·x_cgo_notify_runtime_init_done(SB) | |||
| 44 | + | RET | |||
| 45 | + | ||||
| 46 | + | TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0 | |||
| 47 | + | CALL ·x_cgo_bindm(SB) | |||
| 48 | + | RET | |||
| 49 | + | ||||
| 50 | + | // func setg_trampoline(setg uintptr, g uintptr) | |||
| 51 | + | TEXT ·setg_trampoline(SB), NOSPLIT, $0-8 | |||
| 52 | + | MOVW G+4(FP), R0 | |||
| 53 | + | MOVW setg+0(FP), R12 | |||
| 54 | + | BL (R12) | |||
| 55 | + | RET | |||
| 56 | + | ||||
| 57 | + | TEXT threadentry_trampoline(SB), NOSPLIT, $8-0 | |||
| 58 | + | // See crosscall2. | |||
| 59 | + | MOVW R0, 4(R13) | |||
| 60 | + | MOVW ·threadentry_call(SB), R12 | |||
| 61 | + | MOVW (R12), R12 | |||
| 62 | + | CALL (R12) | |||
| 63 | + | RET | |||
| 64 | + | ||||
| 65 | + | TEXT ·call5(SB), NOSPLIT, $8-28 | |||
| 66 | + | MOVW fn+0(FP), R12 | |||
| 67 | + | MOVW a1+4(FP), R0 | |||
| 68 | + | MOVW a2+8(FP), R1 | |||
| 69 | + | MOVW a3+12(FP), R2 | |||
| 70 | + | MOVW a4+16(FP), R3 | |||
| 71 | + | MOVW a5+20(FP), R4 | |||
| 72 | + | ||||
| 73 | + | // Store 5th arg below SP (in local frame area) | |||
| 74 | + | MOVW R4, arg5-8(SP) | |||
| 75 | + | ||||
| 76 | + | // Align SP to 8 bytes for call (required by ARM AAPCS) | |||
| 77 | + | SUB $8, R13 | |||
| 78 | + | CALL (R12) | |||
| 79 | + | ADD $8, R13 | |||
| 80 | + | MOVW R0, r1+24(FP) | |||
| 81 | + | RET | |||
M struct_arm.go +48 -4
| @@ -3,18 +3,62 @@ | |||
| 3 | 3 | ||
| 4 | 4 | package purego | |
| 5 | 5 | ||
| 6 | - | import "reflect" | |
| 6 | + | import ( | |
| 7 | + | "reflect" | |
| 8 | + | "unsafe" | |
| 9 | + | ) | |
| 7 | 10 | ||
| 8 | 11 | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | |
| 9 | - | panic("purego: struct arguments are not supported") | |
| 12 | + | size := v.Type().Size() | |
| 13 | + | if size == 0 { | |
| 14 | + | return keepAlive | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | // TODO: ARM EABI: small structs are passed in registers or on stack | |
| 18 | + | // For simplicity, pass by pointer for now | |
| 19 | + | ptr := v.Addr().UnsafePointer() | |
| 20 | + | keepAlive = append(keepAlive, ptr) | |
| 21 | + | if *numInts < 4 { | |
| 22 | + | addInt(uintptr(ptr)) | |
| 23 | + | *numInts++ | |
| 24 | + | } else { | |
| 25 | + | addStack(uintptr(ptr)) | |
| 26 | + | *numStack++ | |
| 27 | + | } | |
| 28 | + | return keepAlive | |
| 10 | 29 | } | |
| 11 | 30 | ||
| 12 | 31 | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | |
| 13 | - | panic("purego: struct returns are not supported") | |
| 32 | + | outSize := outType.Size() | |
| 33 | + | if outSize == 0 { | |
| 34 | + | return reflect.New(outType).Elem() | |
| 35 | + | } | |
| 36 | + | if outSize <= 4 { | |
| 37 | + | // Fits in one register | |
| 38 | + | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() | |
| 39 | + | } | |
| 40 | + | if outSize <= 8 { | |
| 41 | + | // Fits in two registers | |
| 42 | + | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{syscall.a1, syscall.a2})).Elem() | |
| 43 | + | } | |
| 44 | + | // Larger structs returned via pointer in a1 | |
| 45 | + | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | |
| 14 | 46 | } | |
| 15 | 47 | ||
| 16 | 48 | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | |
| 17 | - | panic("purego: placeRegisters not implemented on arm") | |
| 49 | + | // TODO: For ARM32, just pass the struct data directly | |
| 50 | + | // This is a simplified implementation | |
| 51 | + | size := v.Type().Size() | |
| 52 | + | if size == 0 { | |
| 53 | + | return | |
| 54 | + | } | |
| 55 | + | ptr := unsafe.Pointer(v.UnsafeAddr()) | |
| 56 | + | if size <= 4 { | |
| 57 | + | addInt(*(*uintptr)(ptr)) | |
| 58 | + | } else if size <= 8 { | |
| 59 | + | addInt(*(*uintptr)(ptr)) | |
| 60 | + | addInt(*(*uintptr)(unsafe.Add(ptr, 4))) | |
| 61 | + | } | |
| 18 | 62 | } | |
| 19 | 63 | ||
| 20 | 64 | // shouldBundleStackArgs always returns false on arm | |
| @@ -3,18 +3,62 @@ | |||||
| 3 | 3 | ||||
| 4 | package purego | 4 | package purego | ||
| 5 | 5 | ||||
| 6 | - | import "reflect" | 6 | + | import ( |
| 7 | + | "reflect" | |||
| 8 | + | "unsafe" | |||
| 9 | + | ) | |||
| 7 | 10 | ||||
| 8 | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | 11 | func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any { | ||
| 9 | - | panic("purego: struct arguments are not supported") | 12 | + | size := v.Type().Size() |
| 13 | + | if size == 0 { | |||
| 14 | + | return keepAlive | |||
| 15 | + | } | |||
| 16 | + | ||||
| 17 | + | // TODO: ARM EABI: small structs are passed in registers or on stack | |||
| 18 | + | // For simplicity, pass by pointer for now | |||
| 19 | + | ptr := v.Addr().UnsafePointer() | |||
| 20 | + | keepAlive = append(keepAlive, ptr) | |||
| 21 | + | if *numInts < 4 { | |||
| 22 | + | addInt(uintptr(ptr)) | |||
| 23 | + | *numInts++ | |||
| 24 | + | } else { | |||
| 25 | + | addStack(uintptr(ptr)) | |||
| 26 | + | *numStack++ | |||
| 27 | + | } | |||
| 28 | + | return keepAlive | |||
| 10 | } | 29 | } | ||
| 11 | 30 | ||||
| 12 | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | 31 | func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { | ||
| 13 | - | panic("purego: struct returns are not supported") | 32 | + | outSize := outType.Size() |
| 33 | + | if outSize == 0 { | |||
| 34 | + | return reflect.New(outType).Elem() | |||
| 35 | + | } | |||
| 36 | + | if outSize <= 4 { | |||
| 37 | + | // Fits in one register | |||
| 38 | + | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{syscall.a1})).Elem() | |||
| 39 | + | } | |||
| 40 | + | if outSize <= 8 { | |||
| 41 | + | // Fits in two registers | |||
| 42 | + | return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{syscall.a1, syscall.a2})).Elem() | |||
| 43 | + | } | |||
| 44 | + | // Larger structs returned via pointer in a1 | |||
| 45 | + | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | |||
| 14 | } | 46 | } | ||
| 15 | 47 | ||||
| 16 | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | 48 | func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) { | ||
| 17 | - | panic("purego: placeRegisters not implemented on arm") | 49 | + | // TODO: For ARM32, just pass the struct data directly |
| 50 | + | // This is a simplified implementation | |||
| 51 | + | size := v.Type().Size() | |||
| 52 | + | if size == 0 { | |||
| 53 | + | return | |||
| 54 | + | } | |||
| 55 | + | ptr := unsafe.Pointer(v.UnsafeAddr()) | |||
| 56 | + | if size <= 4 { | |||
| 57 | + | addInt(*(*uintptr)(ptr)) | |||
| 58 | + | } else if size <= 8 { | |||
| 59 | + | addInt(*(*uintptr)(ptr)) | |||
| 60 | + | addInt(*(*uintptr)(unsafe.Add(ptr, 4))) | |||
| 61 | + | } | |||
| 18 | } | 62 | } | ||
| 19 | 63 | ||||
| 20 | // shouldBundleStackArgs always returns false on arm | 64 | // shouldBundleStackArgs always returns false on arm | ||
A sys_arm.s +142 -0
| @@ -0,0 +1,142 @@ | |||
| 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 128 | |
| 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 | + | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $4 | |
| 28 | + | DATA ·syscall15XABI0(SB)/4, $syscall15X(SB) | |
| 29 | + | TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |
| 30 | + | // Called via C calling convention: R0 = pointer to syscall15Args | |
| 31 | + | // NOT via Go calling convention | |
| 32 | + | // Save link register and callee-saved registers first | |
| 33 | + | MOVW.W R14, -4(R13) // save LR (decrement and store) | |
| 34 | + | MOVM.DB.W [R4, R5, R6, R7, R8, R9, R11], (R13) // save callee-saved regs | |
| 35 | + | ||
| 36 | + | MOVW R0, R8 | |
| 37 | + | SUB $STACK_SIZE, R13 | |
| 38 | + | MOVW R8, PTR_ADDRESS(R13) | |
| 39 | + | ||
| 40 | + | // Load function pointer first (before anything can corrupt R8) | |
| 41 | + | MOVW syscall15Args_fn(R8), R5 | |
| 42 | + | MOVW R5, (PTR_ADDRESS-4)(R13) // save fn at offset 56 | |
| 43 | + | ||
| 44 | + | // Load floating point arguments | |
| 45 | + | // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 2 | |
| 46 | + | MOVD syscall15Args_f1(R8), F0 // f1+f2 -> D0 | |
| 47 | + | MOVD syscall15Args_f3(R8), F1 // f3+f4 -> D1 | |
| 48 | + | MOVD syscall15Args_f5(R8), F2 // f5+f6 -> D2 | |
| 49 | + | MOVD syscall15Args_f7(R8), F3 // f7+f8 -> D3 | |
| 50 | + | MOVD syscall15Args_f9(R8), F4 // f9+f10 -> D4 | |
| 51 | + | MOVD syscall15Args_f11(R8), F5 // f11+f12 -> D5 | |
| 52 | + | MOVD syscall15Args_f13(R8), F6 // f13+f14 -> D6 | |
| 53 | + | MOVD syscall15Args_f15(R8), F7 // f15+f16 -> D7 | |
| 54 | + | ||
| 55 | + | // Load integer arguments into registers (R0-R3 for ARM EABI) | |
| 56 | + | MOVW syscall15Args_a1(R8), R0 // a1 | |
| 57 | + | MOVW syscall15Args_a2(R8), R1 // a2 | |
| 58 | + | MOVW syscall15Args_a3(R8), R2 // a3 | |
| 59 | + | MOVW syscall15Args_a4(R8), R3 // a4 | |
| 60 | + | ||
| 61 | + | // push a5-a32 onto stack | |
| 62 | + | MOVW syscall15Args_a5(R8), R4 | |
| 63 | + | MOVW R4, 0(R13) | |
| 64 | + | MOVW syscall15Args_a6(R8), R4 | |
| 65 | + | MOVW R4, 4(R13) | |
| 66 | + | MOVW syscall15Args_a7(R8), R4 | |
| 67 | + | MOVW R4, 8(R13) | |
| 68 | + | MOVW syscall15Args_a8(R8), R4 | |
| 69 | + | MOVW R4, 12(R13) | |
| 70 | + | MOVW syscall15Args_a9(R8), R4 | |
| 71 | + | MOVW R4, 16(R13) | |
| 72 | + | MOVW syscall15Args_a10(R8), R4 | |
| 73 | + | MOVW R4, 20(R13) | |
| 74 | + | MOVW syscall15Args_a11(R8), R4 | |
| 75 | + | MOVW R4, 24(R13) | |
| 76 | + | MOVW syscall15Args_a12(R8), R4 | |
| 77 | + | MOVW R4, 28(R13) | |
| 78 | + | MOVW syscall15Args_a13(R8), R4 | |
| 79 | + | MOVW R4, 32(R13) | |
| 80 | + | MOVW syscall15Args_a14(R8), R4 | |
| 81 | + | MOVW R4, 36(R13) | |
| 82 | + | MOVW syscall15Args_a15(R8), R4 | |
| 83 | + | MOVW R4, 40(R13) | |
| 84 | + | MOVW syscall15Args_a16(R8), R4 | |
| 85 | + | MOVW R4, 44(R13) | |
| 86 | + | MOVW syscall15Args_a17(R8), R4 | |
| 87 | + | MOVW R4, 48(R13) | |
| 88 | + | MOVW syscall15Args_a18(R8), R4 | |
| 89 | + | MOVW R4, 52(R13) | |
| 90 | + | MOVW syscall15Args_a19(R8), R4 | |
| 91 | + | MOVW R4, 56(R13) | |
| 92 | + | MOVW syscall15Args_a20(R8), R4 | |
| 93 | + | MOVW R4, 60(R13) | |
| 94 | + | MOVW syscall15Args_a21(R8), R4 | |
| 95 | + | MOVW R4, 64(R13) | |
| 96 | + | MOVW syscall15Args_a22(R8), R4 | |
| 97 | + | MOVW R4, 68(R13) | |
| 98 | + | MOVW syscall15Args_a23(R8), R4 | |
| 99 | + | MOVW R4, 72(R13) | |
| 100 | + | MOVW syscall15Args_a24(R8), R4 | |
| 101 | + | MOVW R4, 76(R13) | |
| 102 | + | MOVW syscall15Args_a25(R8), R4 | |
| 103 | + | MOVW R4, 80(R13) | |
| 104 | + | MOVW syscall15Args_a26(R8), R4 | |
| 105 | + | MOVW R4, 84(R13) | |
| 106 | + | MOVW syscall15Args_a27(R8), R4 | |
| 107 | + | MOVW R4, 88(R13) | |
| 108 | + | MOVW syscall15Args_a28(R8), R4 | |
| 109 | + | MOVW R4, 92(R13) | |
| 110 | + | MOVW syscall15Args_a29(R8), R4 | |
| 111 | + | MOVW R4, 96(R13) | |
| 112 | + | MOVW syscall15Args_a30(R8), R4 | |
| 113 | + | MOVW R4, 100(R13) | |
| 114 | + | MOVW syscall15Args_a31(R8), R4 | |
| 115 | + | MOVW R4, 104(R13) | |
| 116 | + | MOVW syscall15Args_a32(R8), R4 | |
| 117 | + | MOVW R4, 108(R13) | |
| 118 | + | ||
| 119 | + | // Load saved function pointer and call | |
| 120 | + | MOVW (PTR_ADDRESS-4)(R13), R4 | |
| 121 | + | ||
| 122 | + | // Use BLX for Thumb interworking - Go assembler doesn't support BLX Rn | |
| 123 | + | // BLX R4 = 0xE12FFF34 (ARM encoding, always condition) | |
| 124 | + | WORD $0xE12FFF34 // blx r4 | |
| 125 | + | ||
| 126 | + | // pop structure pointer | |
| 127 | + | MOVW PTR_ADDRESS(R13), R8 | |
| 128 | + | ADD $STACK_SIZE, R13 | |
| 129 | + | ||
| 130 | + | // save R0, R1 | |
| 131 | + | MOVW R0, syscall15Args_a1(R8) | |
| 132 | + | MOVW R1, syscall15Args_a2(R8) | |
| 133 | + | ||
| 134 | + | // save f0-f3 (each float64 spans 2 uintptr slots on ARM32) | |
| 135 | + | MOVD F0, syscall15Args_f1(R8) | |
| 136 | + | MOVD F1, syscall15Args_f3(R8) | |
| 137 | + | MOVD F2, syscall15Args_f5(R8) | |
| 138 | + | MOVD F3, syscall15Args_f7(R8) | |
| 139 | + | ||
| 140 | + | // Restore callee-saved registers and return | |
| 141 | + | MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11] | |
| 142 | + | MOVW.P 4(R13), R15 // pop LR into PC (return) | |
| @@ -0,0 +1,142 @@ | |||||
| 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 128 | |||
| 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 | + | GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $4 | |||
| 28 | + | DATA ·syscall15XABI0(SB)/4, $syscall15X(SB) | |||
| 29 | + | TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0-0 | |||
| 30 | + | // Called via C calling convention: R0 = pointer to syscall15Args | |||
| 31 | + | // NOT via Go calling convention | |||
| 32 | + | // Save link register and callee-saved registers first | |||
| 33 | + | MOVW.W R14, -4(R13) // save LR (decrement and store) | |||
| 34 | + | MOVM.DB.W [R4, R5, R6, R7, R8, R9, R11], (R13) // save callee-saved regs | |||
| 35 | + | ||||
| 36 | + | MOVW R0, R8 | |||
| 37 | + | SUB $STACK_SIZE, R13 | |||
| 38 | + | MOVW R8, PTR_ADDRESS(R13) | |||
| 39 | + | ||||
| 40 | + | // Load function pointer first (before anything can corrupt R8) | |||
| 41 | + | MOVW syscall15Args_fn(R8), R5 | |||
| 42 | + | MOVW R5, (PTR_ADDRESS-4)(R13) // save fn at offset 56 | |||
| 43 | + | ||||
| 44 | + | // Load floating point arguments | |||
| 45 | + | // Each float64 spans 2 uintptr slots (8 bytes) on ARM32, so we skip by 2 | |||
| 46 | + | MOVD syscall15Args_f1(R8), F0 // f1+f2 -> D0 | |||
| 47 | + | MOVD syscall15Args_f3(R8), F1 // f3+f4 -> D1 | |||
| 48 | + | MOVD syscall15Args_f5(R8), F2 // f5+f6 -> D2 | |||
| 49 | + | MOVD syscall15Args_f7(R8), F3 // f7+f8 -> D3 | |||
| 50 | + | MOVD syscall15Args_f9(R8), F4 // f9+f10 -> D4 | |||
| 51 | + | MOVD syscall15Args_f11(R8), F5 // f11+f12 -> D5 | |||
| 52 | + | MOVD syscall15Args_f13(R8), F6 // f13+f14 -> D6 | |||
| 53 | + | MOVD syscall15Args_f15(R8), F7 // f15+f16 -> D7 | |||
| 54 | + | ||||
| 55 | + | // Load integer arguments into registers (R0-R3 for ARM EABI) | |||
| 56 | + | MOVW syscall15Args_a1(R8), R0 // a1 | |||
| 57 | + | MOVW syscall15Args_a2(R8), R1 // a2 | |||
| 58 | + | MOVW syscall15Args_a3(R8), R2 // a3 | |||
| 59 | + | MOVW syscall15Args_a4(R8), R3 // a4 | |||
| 60 | + | ||||
| 61 | + | // push a5-a32 onto stack | |||
| 62 | + | MOVW syscall15Args_a5(R8), R4 | |||
| 63 | + | MOVW R4, 0(R13) | |||
| 64 | + | MOVW syscall15Args_a6(R8), R4 | |||
| 65 | + | MOVW R4, 4(R13) | |||
| 66 | + | MOVW syscall15Args_a7(R8), R4 | |||
| 67 | + | MOVW R4, 8(R13) | |||
| 68 | + | MOVW syscall15Args_a8(R8), R4 | |||
| 69 | + | MOVW R4, 12(R13) | |||
| 70 | + | MOVW syscall15Args_a9(R8), R4 | |||
| 71 | + | MOVW R4, 16(R13) | |||
| 72 | + | MOVW syscall15Args_a10(R8), R4 | |||
| 73 | + | MOVW R4, 20(R13) | |||
| 74 | + | MOVW syscall15Args_a11(R8), R4 | |||
| 75 | + | MOVW R4, 24(R13) | |||
| 76 | + | MOVW syscall15Args_a12(R8), R4 | |||
| 77 | + | MOVW R4, 28(R13) | |||
| 78 | + | MOVW syscall15Args_a13(R8), R4 | |||
| 79 | + | MOVW R4, 32(R13) | |||
| 80 | + | MOVW syscall15Args_a14(R8), R4 | |||
| 81 | + | MOVW R4, 36(R13) | |||
| 82 | + | MOVW syscall15Args_a15(R8), R4 | |||
| 83 | + | MOVW R4, 40(R13) | |||
| 84 | + | MOVW syscall15Args_a16(R8), R4 | |||
| 85 | + | MOVW R4, 44(R13) | |||
| 86 | + | MOVW syscall15Args_a17(R8), R4 | |||
| 87 | + | MOVW R4, 48(R13) | |||
| 88 | + | MOVW syscall15Args_a18(R8), R4 | |||
| 89 | + | MOVW R4, 52(R13) | |||
| 90 | + | MOVW syscall15Args_a19(R8), R4 | |||
| 91 | + | MOVW R4, 56(R13) | |||
| 92 | + | MOVW syscall15Args_a20(R8), R4 | |||
| 93 | + | MOVW R4, 60(R13) | |||
| 94 | + | MOVW syscall15Args_a21(R8), R4 | |||
| 95 | + | MOVW R4, 64(R13) | |||
| 96 | + | MOVW syscall15Args_a22(R8), R4 | |||
| 97 | + | MOVW R4, 68(R13) | |||
| 98 | + | MOVW syscall15Args_a23(R8), R4 | |||
| 99 | + | MOVW R4, 72(R13) | |||
| 100 | + | MOVW syscall15Args_a24(R8), R4 | |||
| 101 | + | MOVW R4, 76(R13) | |||
| 102 | + | MOVW syscall15Args_a25(R8), R4 | |||
| 103 | + | MOVW R4, 80(R13) | |||
| 104 | + | MOVW syscall15Args_a26(R8), R4 | |||
| 105 | + | MOVW R4, 84(R13) | |||
| 106 | + | MOVW syscall15Args_a27(R8), R4 | |||
| 107 | + | MOVW R4, 88(R13) | |||
| 108 | + | MOVW syscall15Args_a28(R8), R4 | |||
| 109 | + | MOVW R4, 92(R13) | |||
| 110 | + | MOVW syscall15Args_a29(R8), R4 | |||
| 111 | + | MOVW R4, 96(R13) | |||
| 112 | + | MOVW syscall15Args_a30(R8), R4 | |||
| 113 | + | MOVW R4, 100(R13) | |||
| 114 | + | MOVW syscall15Args_a31(R8), R4 | |||
| 115 | + | MOVW R4, 104(R13) | |||
| 116 | + | MOVW syscall15Args_a32(R8), R4 | |||
| 117 | + | MOVW R4, 108(R13) | |||
| 118 | + | ||||
| 119 | + | // Load saved function pointer and call | |||
| 120 | + | MOVW (PTR_ADDRESS-4)(R13), R4 | |||
| 121 | + | ||||
| 122 | + | // Use BLX for Thumb interworking - Go assembler doesn't support BLX Rn | |||
| 123 | + | // BLX R4 = 0xE12FFF34 (ARM encoding, always condition) | |||
| 124 | + | WORD $0xE12FFF34 // blx r4 | |||
| 125 | + | ||||
| 126 | + | // pop structure pointer | |||
| 127 | + | MOVW PTR_ADDRESS(R13), R8 | |||
| 128 | + | ADD $STACK_SIZE, R13 | |||
| 129 | + | ||||
| 130 | + | // save R0, R1 | |||
| 131 | + | MOVW R0, syscall15Args_a1(R8) | |||
| 132 | + | MOVW R1, syscall15Args_a2(R8) | |||
| 133 | + | ||||
| 134 | + | // save f0-f3 (each float64 spans 2 uintptr slots on ARM32) | |||
| 135 | + | MOVD F0, syscall15Args_f1(R8) | |||
| 136 | + | MOVD F1, syscall15Args_f3(R8) | |||
| 137 | + | MOVD F2, syscall15Args_f5(R8) | |||
| 138 | + | MOVD F3, syscall15Args_f7(R8) | |||
| 139 | + | ||||
| 140 | + | // Restore callee-saved registers and return | |||
| 141 | + | MOVM.IA.W (R13), [R4, R5, R6, R7, R8, R9, R11] | |||
| 142 | + | MOVW.P 4(R13), R15 // pop LR into PC (return) | |||
A sys_unix_arm.s +89 -0
| @@ -0,0 +1,89 @@ | |||
| 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 | + | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |
| 11 | + | NO_LOCAL_POINTERS | |
| 12 | + | ||
| 13 | + | // Allocate stack frame: 48 + 16 + 64 + 16 = 144 bytes | |
| 14 | + | SUB $144, R13 | |
| 15 | + | ||
| 16 | + | // Save callee-saved registers at SP+0 | |
| 17 | + | MOVW R4, 0(R13) | |
| 18 | + | MOVW R5, 4(R13) | |
| 19 | + | MOVW R6, 8(R13) | |
| 20 | + | MOVW R7, 12(R13) | |
| 21 | + | MOVW R8, 16(R13) | |
| 22 | + | MOVW R9, 20(R13) | |
| 23 | + | MOVW g, 24(R13) | |
| 24 | + | MOVW R11, 28(R13) | |
| 25 | + | MOVW R14, 32(R13) | |
| 26 | + | ||
| 27 | + | // Save callback index (passed in R12) at SP+36 | |
| 28 | + | MOVW R12, 36(R13) | |
| 29 | + | ||
| 30 | + | // Save integer arguments R0-R3 at SP+128 (frame[16..19]) | |
| 31 | + | MOVW R0, 128(R13) | |
| 32 | + | MOVW R1, 132(R13) | |
| 33 | + | MOVW R2, 136(R13) | |
| 34 | + | MOVW R3, 140(R13) | |
| 35 | + | ||
| 36 | + | // Save floating point registers F0-F7 at SP+64 (frame[0..15]) | |
| 37 | + | // Note: We always save these since we target hard-float ABI. | |
| 38 | + | MOVD F0, 64(R13) | |
| 39 | + | MOVD F1, 72(R13) | |
| 40 | + | MOVD F2, 80(R13) | |
| 41 | + | MOVD F3, 88(R13) | |
| 42 | + | MOVD F4, 96(R13) | |
| 43 | + | MOVD F5, 104(R13) | |
| 44 | + | MOVD F6, 112(R13) | |
| 45 | + | MOVD F7, 120(R13) | |
| 46 | + | ||
| 47 | + | // Set up callbackArgs at SP+48 | |
| 48 | + | MOVW 36(R13), R4 | |
| 49 | + | MOVW R4, 48(R13) | |
| 50 | + | ADD $64, R13, R4 | |
| 51 | + | MOVW R4, 52(R13) | |
| 52 | + | MOVW $0, R4 | |
| 53 | + | MOVW R4, 56(R13) | |
| 54 | + | ||
| 55 | + | // Call crosscall2(fn, frame, 0, ctxt) | |
| 56 | + | MOVW ·callbackWrap_call(SB), R0 | |
| 57 | + | MOVW (R0), R0 | |
| 58 | + | ADD $48, R13, R1 | |
| 59 | + | MOVW $0, R2 | |
| 60 | + | MOVW $0, R3 | |
| 61 | + | ||
| 62 | + | BL crosscall2(SB) | |
| 63 | + | ||
| 64 | + | // Get result | |
| 65 | + | MOVW 56(R13), R0 | |
| 66 | + | ||
| 67 | + | // Restore float registers | |
| 68 | + | MOVD 64(R13), F0 | |
| 69 | + | MOVD 72(R13), F1 | |
| 70 | + | MOVD 80(R13), F2 | |
| 71 | + | MOVD 88(R13), F3 | |
| 72 | + | MOVD 96(R13), F4 | |
| 73 | + | MOVD 104(R13), F5 | |
| 74 | + | MOVD 112(R13), F6 | |
| 75 | + | MOVD 120(R13), F7 | |
| 76 | + | ||
| 77 | + | // Restore callee-saved registers | |
| 78 | + | MOVW 0(R13), R4 | |
| 79 | + | MOVW 4(R13), R5 | |
| 80 | + | MOVW 8(R13), R6 | |
| 81 | + | MOVW 12(R13), R7 | |
| 82 | + | MOVW 16(R13), R8 | |
| 83 | + | MOVW 20(R13), R9 | |
| 84 | + | MOVW 24(R13), g | |
| 85 | + | MOVW 28(R13), R11 | |
| 86 | + | MOVW 32(R13), R14 | |
| 87 | + | ||
| 88 | + | ADD $144, R13 | |
| 89 | + | RET | |
| @@ -0,0 +1,89 @@ | |||||
| 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 | + | TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0 | |||
| 11 | + | NO_LOCAL_POINTERS | |||
| 12 | + | ||||
| 13 | + | // Allocate stack frame: 48 + 16 + 64 + 16 = 144 bytes | |||
| 14 | + | SUB $144, R13 | |||
| 15 | + | ||||
| 16 | + | // Save callee-saved registers at SP+0 | |||
| 17 | + | MOVW R4, 0(R13) | |||
| 18 | + | MOVW R5, 4(R13) | |||
| 19 | + | MOVW R6, 8(R13) | |||
| 20 | + | MOVW R7, 12(R13) | |||
| 21 | + | MOVW R8, 16(R13) | |||
| 22 | + | MOVW R9, 20(R13) | |||
| 23 | + | MOVW g, 24(R13) | |||
| 24 | + | MOVW R11, 28(R13) | |||
| 25 | + | MOVW R14, 32(R13) | |||
| 26 | + | ||||
| 27 | + | // Save callback index (passed in R12) at SP+36 | |||
| 28 | + | MOVW R12, 36(R13) | |||
| 29 | + | ||||
| 30 | + | // Save integer arguments R0-R3 at SP+128 (frame[16..19]) | |||
| 31 | + | MOVW R0, 128(R13) | |||
| 32 | + | MOVW R1, 132(R13) | |||
| 33 | + | MOVW R2, 136(R13) | |||
| 34 | + | MOVW R3, 140(R13) | |||
| 35 | + | ||||
| 36 | + | // Save floating point registers F0-F7 at SP+64 (frame[0..15]) | |||
| 37 | + | // Note: We always save these since we target hard-float ABI. | |||
| 38 | + | MOVD F0, 64(R13) | |||
| 39 | + | MOVD F1, 72(R13) | |||
| 40 | + | MOVD F2, 80(R13) | |||
| 41 | + | MOVD F3, 88(R13) | |||
| 42 | + | MOVD F4, 96(R13) | |||
| 43 | + | MOVD F5, 104(R13) | |||
| 44 | + | MOVD F6, 112(R13) | |||
| 45 | + | MOVD F7, 120(R13) | |||
| 46 | + | ||||
| 47 | + | // Set up callbackArgs at SP+48 | |||
| 48 | + | MOVW 36(R13), R4 | |||
| 49 | + | MOVW R4, 48(R13) | |||
| 50 | + | ADD $64, R13, R4 | |||
| 51 | + | MOVW R4, 52(R13) | |||
| 52 | + | MOVW $0, R4 | |||
| 53 | + | MOVW R4, 56(R13) | |||
| 54 | + | ||||
| 55 | + | // Call crosscall2(fn, frame, 0, ctxt) | |||
| 56 | + | MOVW ·callbackWrap_call(SB), R0 | |||
| 57 | + | MOVW (R0), R0 | |||
| 58 | + | ADD $48, R13, R1 | |||
| 59 | + | MOVW $0, R2 | |||
| 60 | + | MOVW $0, R3 | |||
| 61 | + | ||||
| 62 | + | BL crosscall2(SB) | |||
| 63 | + | ||||
| 64 | + | // Get result | |||
| 65 | + | MOVW 56(R13), R0 | |||
| 66 | + | ||||
| 67 | + | // Restore float registers | |||
| 68 | + | MOVD 64(R13), F0 | |||
| 69 | + | MOVD 72(R13), F1 | |||
| 70 | + | MOVD 80(R13), F2 | |||
| 71 | + | MOVD 88(R13), F3 | |||
| 72 | + | MOVD 96(R13), F4 | |||
| 73 | + | MOVD 104(R13), F5 | |||
| 74 | + | MOVD 112(R13), F6 | |||
| 75 | + | MOVD 120(R13), F7 | |||
| 76 | + | ||||
| 77 | + | // Restore callee-saved registers | |||
| 78 | + | MOVW 0(R13), R4 | |||
| 79 | + | MOVW 4(R13), R5 | |||
| 80 | + | MOVW 8(R13), R6 | |||
| 81 | + | MOVW 12(R13), R7 | |||
| 82 | + | MOVW 16(R13), R8 | |||
| 83 | + | MOVW 20(R13), R9 | |||
| 84 | + | MOVW 24(R13), g | |||
| 85 | + | MOVW 28(R13), R11 | |||
| 86 | + | MOVW 32(R13), R14 | |||
| 87 | + | ||||
| 88 | + | ADD $144, R13 | |||
| 89 | + | RET | |||
M syscall.go +29 -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 darwin || freebsd || linux || netbsd || windows | |
| 4 | + | //go:build !arm && (darwin || freebsd || linux || netbsd || windows) | |
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| @@ -23,6 +23,34 @@ type syscall15Args struct { | |||
| 23 | 23 | arm64_r8 uintptr | |
| 24 | 24 | } | |
| 25 | 25 | ||
| 26 | + | func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uintptr) { | |
| 27 | + | s.fn = fn | |
| 28 | + | s.a1 = ints[0] | |
| 29 | + | s.a2 = ints[1] | |
| 30 | + | s.a3 = ints[2] | |
| 31 | + | s.a4 = ints[3] | |
| 32 | + | s.a5 = ints[4] | |
| 33 | + | s.a6 = ints[5] | |
| 34 | + | s.a7 = ints[6] | |
| 35 | + | s.a8 = ints[7] | |
| 36 | + | s.a9 = ints[8] | |
| 37 | + | s.a10 = ints[9] | |
| 38 | + | s.a11 = ints[10] | |
| 39 | + | s.a12 = ints[11] | |
| 40 | + | s.a13 = ints[12] | |
| 41 | + | s.a14 = ints[13] | |
| 42 | + | s.a15 = ints[14] | |
| 43 | + | s.f1 = floats[0] | |
| 44 | + | s.f2 = floats[1] | |
| 45 | + | s.f3 = floats[2] | |
| 46 | + | s.f4 = floats[3] | |
| 47 | + | s.f5 = floats[4] | |
| 48 | + | s.f6 = floats[5] | |
| 49 | + | s.f7 = floats[6] | |
| 50 | + | s.f8 = floats[7] | |
| 51 | + | s.arm64_r8 = r8 | |
| 52 | + | } | |
| 53 | + | ||
| 26 | 54 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | |
| 27 | 55 | // There is an internal maximum number of arguments that SyscallN can take. It panics | |
| 28 | 56 | // when the maximum is exceeded. It returns the result and the libc error code if there is one. | |
| @@ -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 || netbsd || windows | 4 | + | //go:build !arm && (darwin || freebsd || linux || netbsd || windows) |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| @@ -23,6 +23,34 @@ type syscall15Args struct { | |||||
| 23 | arm64_r8 uintptr | 23 | arm64_r8 uintptr | ||
| 24 | } | 24 | } | ||
| 25 | 25 | ||||
| 26 | + | func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uintptr) { | |||
| 27 | + | s.fn = fn | |||
| 28 | + | s.a1 = ints[0] | |||
| 29 | + | s.a2 = ints[1] | |||
| 30 | + | s.a3 = ints[2] | |||
| 31 | + | s.a4 = ints[3] | |||
| 32 | + | s.a5 = ints[4] | |||
| 33 | + | s.a6 = ints[5] | |||
| 34 | + | s.a7 = ints[6] | |||
| 35 | + | s.a8 = ints[7] | |||
| 36 | + | s.a9 = ints[8] | |||
| 37 | + | s.a10 = ints[9] | |||
| 38 | + | s.a11 = ints[10] | |||
| 39 | + | s.a12 = ints[11] | |||
| 40 | + | s.a13 = ints[12] | |||
| 41 | + | s.a14 = ints[13] | |||
| 42 | + | s.a15 = ints[14] | |||
| 43 | + | s.f1 = floats[0] | |||
| 44 | + | s.f2 = floats[1] | |||
| 45 | + | s.f3 = floats[2] | |||
| 46 | + | s.f4 = floats[3] | |||
| 47 | + | s.f5 = floats[4] | |||
| 48 | + | s.f6 = floats[5] | |||
| 49 | + | s.f7 = floats[6] | |||
| 50 | + | s.f8 = floats[7] | |||
| 51 | + | s.arm64_r8 = r8 | |||
| 52 | + | } | |||
| 53 | + | ||||
| 26 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | 54 | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | ||
| 27 | // There is an internal maximum number of arguments that SyscallN can take. It panics | 55 | // There is an internal maximum number of arguments that SyscallN can take. It panics | ||
| 28 | // when the maximum is exceeded. It returns the result and the libc error code if there is one. | 56 | // when the maximum is exceeded. It returns the result and the libc error code if there is one. | ||
A syscall_32bit.go +110 -0
| @@ -0,0 +1,110 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build arm && (freebsd || linux || netbsd || windows) | |
| 5 | + | ||
| 6 | + | package purego | |
| 7 | + | ||
| 8 | + | // CDecl marks a function as being called using the __cdecl calling convention as defined in | |
| 9 | + | // the [MSDocs] when passed to NewCallback. It must be the first argument to the function. | |
| 10 | + | // This is only useful on 386 Windows, but it is safe to use on other platforms. | |
| 11 | + | // | |
| 12 | + | // [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170 | |
| 13 | + | type CDecl struct{} | |
| 14 | + | ||
| 15 | + | const ( | |
| 16 | + | maxArgs = 32 | |
| 17 | + | numOfFloatRegisters = 16 | |
| 18 | + | ) | |
| 19 | + | ||
| 20 | + | type syscall15Args struct { | |
| 21 | + | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr | |
| 22 | + | a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32 uintptr | |
| 23 | + | f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16 uintptr | |
| 24 | + | arm64_r8 uintptr | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uintptr) { | |
| 28 | + | s.fn = fn | |
| 29 | + | s.a1 = ints[0] | |
| 30 | + | s.a2 = ints[1] | |
| 31 | + | s.a3 = ints[2] | |
| 32 | + | s.a4 = ints[3] | |
| 33 | + | s.a5 = ints[4] | |
| 34 | + | s.a6 = ints[5] | |
| 35 | + | s.a7 = ints[6] | |
| 36 | + | s.a8 = ints[7] | |
| 37 | + | s.a9 = ints[8] | |
| 38 | + | s.a10 = ints[9] | |
| 39 | + | s.a11 = ints[10] | |
| 40 | + | s.a12 = ints[11] | |
| 41 | + | s.a13 = ints[12] | |
| 42 | + | s.a14 = ints[13] | |
| 43 | + | s.a15 = ints[14] | |
| 44 | + | s.a16 = ints[15] | |
| 45 | + | s.a17 = ints[16] | |
| 46 | + | s.a18 = ints[17] | |
| 47 | + | s.a19 = ints[18] | |
| 48 | + | s.a20 = ints[19] | |
| 49 | + | s.a21 = ints[20] | |
| 50 | + | s.a22 = ints[21] | |
| 51 | + | s.a23 = ints[22] | |
| 52 | + | s.a24 = ints[23] | |
| 53 | + | s.a25 = ints[24] | |
| 54 | + | s.a26 = ints[25] | |
| 55 | + | s.a27 = ints[26] | |
| 56 | + | s.a28 = ints[27] | |
| 57 | + | s.a29 = ints[28] | |
| 58 | + | s.a30 = ints[29] | |
| 59 | + | s.a31 = ints[30] | |
| 60 | + | s.a32 = ints[31] | |
| 61 | + | s.f1 = floats[0] | |
| 62 | + | s.f2 = floats[1] | |
| 63 | + | s.f3 = floats[2] | |
| 64 | + | s.f4 = floats[3] | |
| 65 | + | s.f5 = floats[4] | |
| 66 | + | s.f6 = floats[5] | |
| 67 | + | s.f7 = floats[6] | |
| 68 | + | s.f8 = floats[7] | |
| 69 | + | s.f9 = floats[8] | |
| 70 | + | s.f10 = floats[9] | |
| 71 | + | s.f11 = floats[10] | |
| 72 | + | s.f12 = floats[11] | |
| 73 | + | s.f13 = floats[12] | |
| 74 | + | s.f14 = floats[13] | |
| 75 | + | s.f15 = floats[14] | |
| 76 | + | s.f16 = floats[15] | |
| 77 | + | s.arm64_r8 = r8 | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | |
| 81 | + | // There is an internal maximum number of arguments that SyscallN can take. It panics | |
| 82 | + | // when the maximum is exceeded. It returns the result and the libc error code if there is one. | |
| 83 | + | // | |
| 84 | + | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | |
| 85 | + | // especially point 4. | |
| 86 | + | // | |
| 87 | + | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | |
| 88 | + | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | |
| 89 | + | // for an explanation of why that is. | |
| 90 | + | // | |
| 91 | + | // On amd64, if there are more than 8 floats the 9th and so on will be placed incorrectly on the | |
| 92 | + | // stack. | |
| 93 | + | // | |
| 94 | + | // The pragma go:nosplit is not needed at this function declaration because it uses go:uintptrescapes | |
| 95 | + | // which forces all the objects that the uintptrs point to onto the heap where a stack split won't affect | |
| 96 | + | // their memory location. | |
| 97 | + | // | |
| 98 | + | //go:uintptrescapes | |
| 99 | + | func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |
| 100 | + | if fn == 0 { | |
| 101 | + | panic("purego: fn is nil") | |
| 102 | + | } | |
| 103 | + | if len(args) > maxArgs { | |
| 104 | + | panic("purego: too many arguments to SyscallN") | |
| 105 | + | } | |
| 106 | + | // add padding so there is no out-of-bounds slicing | |
| 107 | + | var tmp [maxArgs]uintptr | |
| 108 | + | copy(tmp[:], args) | |
| 109 | + | return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14]) | |
| 110 | + | } | |
| @@ -0,0 +1,110 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build arm && (freebsd || linux || netbsd || windows) | |||
| 5 | + | ||||
| 6 | + | package purego | |||
| 7 | + | ||||
| 8 | + | // CDecl marks a function as being called using the __cdecl calling convention as defined in | |||
| 9 | + | // the [MSDocs] when passed to NewCallback. It must be the first argument to the function. | |||
| 10 | + | // This is only useful on 386 Windows, but it is safe to use on other platforms. | |||
| 11 | + | // | |||
| 12 | + | // [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170 | |||
| 13 | + | type CDecl struct{} | |||
| 14 | + | ||||
| 15 | + | const ( | |||
| 16 | + | maxArgs = 32 | |||
| 17 | + | numOfFloatRegisters = 16 | |||
| 18 | + | ) | |||
| 19 | + | ||||
| 20 | + | type syscall15Args struct { | |||
| 21 | + | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr | |||
| 22 | + | a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32 uintptr | |||
| 23 | + | f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16 uintptr | |||
| 24 | + | arm64_r8 uintptr | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | func (s *syscall15Args) Set(fn uintptr, ints []uintptr, floats []uintptr, r8 uintptr) { | |||
| 28 | + | s.fn = fn | |||
| 29 | + | s.a1 = ints[0] | |||
| 30 | + | s.a2 = ints[1] | |||
| 31 | + | s.a3 = ints[2] | |||
| 32 | + | s.a4 = ints[3] | |||
| 33 | + | s.a5 = ints[4] | |||
| 34 | + | s.a6 = ints[5] | |||
| 35 | + | s.a7 = ints[6] | |||
| 36 | + | s.a8 = ints[7] | |||
| 37 | + | s.a9 = ints[8] | |||
| 38 | + | s.a10 = ints[9] | |||
| 39 | + | s.a11 = ints[10] | |||
| 40 | + | s.a12 = ints[11] | |||
| 41 | + | s.a13 = ints[12] | |||
| 42 | + | s.a14 = ints[13] | |||
| 43 | + | s.a15 = ints[14] | |||
| 44 | + | s.a16 = ints[15] | |||
| 45 | + | s.a17 = ints[16] | |||
| 46 | + | s.a18 = ints[17] | |||
| 47 | + | s.a19 = ints[18] | |||
| 48 | + | s.a20 = ints[19] | |||
| 49 | + | s.a21 = ints[20] | |||
| 50 | + | s.a22 = ints[21] | |||
| 51 | + | s.a23 = ints[22] | |||
| 52 | + | s.a24 = ints[23] | |||
| 53 | + | s.a25 = ints[24] | |||
| 54 | + | s.a26 = ints[25] | |||
| 55 | + | s.a27 = ints[26] | |||
| 56 | + | s.a28 = ints[27] | |||
| 57 | + | s.a29 = ints[28] | |||
| 58 | + | s.a30 = ints[29] | |||
| 59 | + | s.a31 = ints[30] | |||
| 60 | + | s.a32 = ints[31] | |||
| 61 | + | s.f1 = floats[0] | |||
| 62 | + | s.f2 = floats[1] | |||
| 63 | + | s.f3 = floats[2] | |||
| 64 | + | s.f4 = floats[3] | |||
| 65 | + | s.f5 = floats[4] | |||
| 66 | + | s.f6 = floats[5] | |||
| 67 | + | s.f7 = floats[6] | |||
| 68 | + | s.f8 = floats[7] | |||
| 69 | + | s.f9 = floats[8] | |||
| 70 | + | s.f10 = floats[9] | |||
| 71 | + | s.f11 = floats[10] | |||
| 72 | + | s.f12 = floats[11] | |||
| 73 | + | s.f13 = floats[12] | |||
| 74 | + | s.f14 = floats[13] | |||
| 75 | + | s.f15 = floats[14] | |||
| 76 | + | s.f16 = floats[15] | |||
| 77 | + | s.arm64_r8 = r8 | |||
| 78 | + | } | |||
| 79 | + | ||||
| 80 | + | // SyscallN takes fn, a C function pointer and a list of arguments as uintptr. | |||
| 81 | + | // There is an internal maximum number of arguments that SyscallN can take. It panics | |||
| 82 | + | // when the maximum is exceeded. It returns the result and the libc error code if there is one. | |||
| 83 | + | // | |||
| 84 | + | // In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer] | |||
| 85 | + | // especially point 4. | |||
| 86 | + | // | |||
| 87 | + | // NOTE: SyscallN does not properly call functions that have both integer and float parameters. | |||
| 88 | + | // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 | |||
| 89 | + | // for an explanation of why that is. | |||
| 90 | + | // | |||
| 91 | + | // On amd64, if there are more than 8 floats the 9th and so on will be placed incorrectly on the | |||
| 92 | + | // stack. | |||
| 93 | + | // | |||
| 94 | + | // The pragma go:nosplit is not needed at this function declaration because it uses go:uintptrescapes | |||
| 95 | + | // which forces all the objects that the uintptrs point to onto the heap where a stack split won't affect | |||
| 96 | + | // their memory location. | |||
| 97 | + | // | |||
| 98 | + | //go:uintptrescapes | |||
| 99 | + | func SyscallN(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { | |||
| 100 | + | if fn == 0 { | |||
| 101 | + | panic("purego: fn is nil") | |||
| 102 | + | } | |||
| 103 | + | if len(args) > maxArgs { | |||
| 104 | + | panic("purego: too many arguments to SyscallN") | |||
| 105 | + | } | |||
| 106 | + | // add padding so there is no out-of-bounds slicing | |||
| 107 | + | var tmp [maxArgs]uintptr | |||
| 108 | + | copy(tmp[:], args) | |||
| 109 | + | return syscall_syscall15X(fn, tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14]) | |||
| 110 | + | } | |||
M syscall_cgo_linux.go +2 -2
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build cgo && !(amd64 || arm64 || loong64 || riscv64) | |
| 4 | + | //go:build cgo && !(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/loong64/riscv64") | |
| 20 | + | panic("purego: NewCallback on Linux is only supported on 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 || arm64 || loong64 || riscv64) | 4 | + | //go:build cgo && !(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/loong64/riscv64") | 20 | + | panic("purego: NewCallback on Linux is only supported on amd64/arm64/arm/loong64/riscv64") |
| 21 | } | 21 | } | ||
M syscall_sysv.go +19 -12
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | |
| 3 | 3 | ||
| 4 | - | //go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64 || riscv64)) || netbsd | |
| 4 | + | //go:build darwin || freebsd || (linux && (amd64 || arm || arm64 || loong64 || riscv64)) || netbsd | |
| 5 | 5 | ||
| 6 | 6 | package purego | |
| 7 | 7 | ||
| @@ -19,9 +19,10 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a | |||
| 19 | 19 | defer thePool.Put(args) | |
| 20 | 20 | ||
| 21 | 21 | *args = syscall15Args{ | |
| 22 | - | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, | |
| 23 | - | a1, a2, a3, a4, a5, a6, a7, a8, | |
| 24 | - | 0, | |
| 22 | + | fn: fn, | |
| 23 | + | a1: a1, a2: a2, a3: a3, a4: a4, a5: a5, a6: a6, a7: a7, a8: a8, | |
| 24 | + | a9: a9, a10: a10, a11: a11, a12: a12, a13: a13, a14: a14, a15: a15, | |
| 25 | + | f1: a1, f2: a2, f3: a3, f4: a4, f5: a5, f6: a6, f7: a7, f8: a8, | |
| 25 | 26 | } | |
| 26 | 27 | ||
| 27 | 28 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(args)) | |
| @@ -145,8 +146,10 @@ func callbackWrap(a *callbackArgs) { | |||
| 145 | 146 | fnType := fn.Type() | |
| 146 | 147 | args := make([]reflect.Value, fnType.NumIn()) | |
| 147 | 148 | frame := (*[callbackMaxFrame]uintptr)(a.args) | |
| 148 | - | var floatsN int // floatsN represents the number of float arguments processed | |
| 149 | - | var intsN int // intsN represents the number of integer arguments processed | |
| 149 | + | // floatsN and intsN track the number of register slots used, not argument count. | |
| 150 | + | // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers). | |
| 151 | + | var floatsN int | |
| 152 | + | var intsN int | |
| 150 | 153 | // stackSlot points to the index into frame of the current stack element. | |
| 151 | 154 | // The stack begins after the float and integer registers. | |
| 152 | 155 | stackSlot := numOfIntegerRegisters() + numOfFloatRegisters | |
| @@ -154,39 +157,43 @@ func callbackWrap(a *callbackArgs) { | |||
| 154 | 157 | // tight packing. On Darwin ARM64, C passes small types packed on the stack. | |
| 155 | 158 | stackByteOffset := uintptr(0) | |
| 156 | 159 | for i := range args { | |
| 160 | + | // slots is the number of pointer-sized slots the argument takes | |
| 161 | + | var slots int | |
| 157 | 162 | inType := fnType.In(i) | |
| 158 | 163 | switch inType.Kind() { | |
| 159 | 164 | case reflect.Float32, reflect.Float64: | |
| 160 | - | if floatsN >= numOfFloatRegisters { | |
| 165 | + | slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize) | |
| 166 | + | if floatsN+slots > numOfFloatRegisters { | |
| 161 | 167 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | |
| 162 | 168 | // Darwin ARM64: read from packed stack with proper alignment | |
| 163 | 169 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | |
| 164 | 170 | } else { | |
| 165 | 171 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |
| 166 | - | stackSlot++ | |
| 172 | + | stackSlot += slots | |
| 167 | 173 | } | |
| 168 | 174 | } else { | |
| 169 | 175 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | |
| 170 | 176 | } | |
| 171 | - | floatsN++ | |
| 177 | + | floatsN += slots | |
| 172 | 178 | case reflect.Struct: | |
| 173 | 179 | // This is the CDecl field | |
| 174 | 180 | args[i] = reflect.Zero(inType) | |
| 175 | 181 | default: | |
| 176 | - | if intsN >= numOfIntegerRegisters() { | |
| 182 | + | slots = int((inType.Size() + ptrSize - 1) / ptrSize) | |
| 183 | + | if intsN+slots > numOfIntegerRegisters() { | |
| 177 | 184 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | |
| 178 | 185 | // Darwin ARM64: read from packed stack with proper alignment | |
| 179 | 186 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | |
| 180 | 187 | } else { | |
| 181 | 188 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | |
| 182 | - | stackSlot++ | |
| 189 | + | stackSlot += slots | |
| 183 | 190 | } | |
| 184 | 191 | } else { | |
| 185 | 192 | // the integers begin after the floats in frame | |
| 186 | 193 | pos := intsN + numOfFloatRegisters | |
| 187 | 194 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() | |
| 188 | 195 | } | |
| 189 | - | intsN++ | |
| 196 | + | intsN += slots | |
| 190 | 197 | } | |
| 191 | 198 | } | |
| 192 | 199 | ret := fn.Call(args) | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | // SPDX-License-Identifier: Apache-2.0 | 1 | // SPDX-License-Identifier: Apache-2.0 | ||
| 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | 2 | // SPDX-FileCopyrightText: 2022 The Ebitengine Authors | ||
| 3 | 3 | ||||
| 4 | - | //go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64 || riscv64)) || netbsd | 4 | + | //go:build darwin || freebsd || (linux && (amd64 || arm || arm64 || loong64 || riscv64)) || netbsd |
| 5 | 5 | ||||
| 6 | package purego | 6 | package purego | ||
| 7 | 7 | ||||
| @@ -19,9 +19,10 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a | |||||
| 19 | defer thePool.Put(args) | 19 | defer thePool.Put(args) | ||
| 20 | 20 | ||||
| 21 | *args = syscall15Args{ | 21 | *args = syscall15Args{ | ||
| 22 | - | fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, | 22 | + | fn: fn, |
| 23 | - | a1, a2, a3, a4, a5, a6, a7, a8, | 23 | + | a1: a1, a2: a2, a3: a3, a4: a4, a5: a5, a6: a6, a7: a7, a8: a8, |
| 24 | - | 0, | 24 | + | a9: a9, a10: a10, a11: a11, a12: a12, a13: a13, a14: a14, a15: a15, |
| 25 | + | f1: a1, f2: a2, f3: a3, f4: a4, f5: a5, f6: a6, f7: a7, f8: a8, | |||
| 25 | } | 26 | } | ||
| 26 | 27 | ||||
| 27 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(args)) | 28 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(args)) | ||
| @@ -145,8 +146,10 @@ func callbackWrap(a *callbackArgs) { | |||||
| 145 | fnType := fn.Type() | 146 | fnType := fn.Type() | ||
| 146 | args := make([]reflect.Value, fnType.NumIn()) | 147 | args := make([]reflect.Value, fnType.NumIn()) | ||
| 147 | frame := (*[callbackMaxFrame]uintptr)(a.args) | 148 | frame := (*[callbackMaxFrame]uintptr)(a.args) | ||
| 148 | - | var floatsN int // floatsN represents the number of float arguments processed | 149 | + | // floatsN and intsN track the number of register slots used, not argument count. |
| 149 | - | var intsN int // intsN represents the number of integer arguments processed | 150 | + | // This distinction matters on ARM32 where float64 uses 2 slots (32-bit registers). |
| 151 | + | var floatsN int | |||
| 152 | + | var intsN int | |||
| 150 | // 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. | ||
| 151 | // The stack begins after the float and integer registers. | 154 | // The stack begins after the float and integer registers. | ||
| 152 | stackSlot := numOfIntegerRegisters() + numOfFloatRegisters | 155 | stackSlot := numOfIntegerRegisters() + numOfFloatRegisters | ||
| @@ -154,39 +157,43 @@ func callbackWrap(a *callbackArgs) { | |||||
| 154 | // 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. | ||
| 155 | stackByteOffset := uintptr(0) | 158 | stackByteOffset := uintptr(0) | ||
| 156 | for i := range args { | 159 | for i := range args { | ||
| 160 | + | // slots is the number of pointer-sized slots the argument takes | |||
| 161 | + | var slots int | |||
| 157 | inType := fnType.In(i) | 162 | inType := fnType.In(i) | ||
| 158 | switch inType.Kind() { | 163 | switch inType.Kind() { | ||
| 159 | case reflect.Float32, reflect.Float64: | 164 | case reflect.Float32, reflect.Float64: | ||
| 160 | - | if floatsN >= numOfFloatRegisters { | 165 | + | slots = int((fnType.In(i).Size() + ptrSize - 1) / ptrSize) |
| 166 | + | if floatsN+slots > numOfFloatRegisters { | |||
| 161 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | 167 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | ||
| 162 | // Darwin ARM64: read from packed stack with proper alignment | 168 | // Darwin ARM64: read from packed stack with proper alignment | ||
| 163 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | 169 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | ||
| 164 | } else { | 170 | } else { | ||
| 165 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | 171 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | ||
| 166 | - | stackSlot++ | 172 | + | stackSlot += slots |
| 167 | } | 173 | } | ||
| 168 | } else { | 174 | } else { | ||
| 169 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | 175 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[floatsN])).Elem() | ||
| 170 | } | 176 | } | ||
| 171 | - | floatsN++ | 177 | + | floatsN += slots |
| 172 | case reflect.Struct: | 178 | case reflect.Struct: | ||
| 173 | // This is the CDecl field | 179 | // This is the CDecl field | ||
| 174 | args[i] = reflect.Zero(inType) | 180 | args[i] = reflect.Zero(inType) | ||
| 175 | default: | 181 | default: | ||
| 176 | - | if intsN >= numOfIntegerRegisters() { | 182 | + | slots = int((inType.Size() + ptrSize - 1) / ptrSize) |
| 183 | + | if intsN+slots > numOfIntegerRegisters() { | |||
| 177 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | 184 | if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { | ||
| 178 | // Darwin ARM64: read from packed stack with proper alignment | 185 | // Darwin ARM64: read from packed stack with proper alignment | ||
| 179 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | 186 | args[i] = callbackArgFromStack(a.args, stackSlot, &stackByteOffset, inType) | ||
| 180 | } else { | 187 | } else { | ||
| 181 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | 188 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[stackSlot])).Elem() | ||
| 182 | - | stackSlot++ | 189 | + | stackSlot += slots |
| 183 | } | 190 | } | ||
| 184 | } else { | 191 | } else { | ||
| 185 | // the integers begin after the floats in frame | 192 | // the integers begin after the floats in frame | ||
| 186 | pos := intsN + numOfFloatRegisters | 193 | pos := intsN + numOfFloatRegisters | ||
| 187 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() | 194 | args[i] = reflect.NewAt(inType, unsafe.Pointer(&frame[pos])).Elem() | ||
| 188 | } | 195 | } | ||
| 189 | - | intsN++ | 196 | + | intsN += slots |
| 190 | } | 197 | } | ||
| 191 | } | 198 | } | ||
| 192 | ret := fn.Call(args) | 199 | ret := fn.Call(args) | ||
M wincallback.go +29 -0
| @@ -44,6 +44,34 @@ TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||
| 44 | 44 | } | |
| 45 | 45 | } | |
| 46 | 46 | ||
| 47 | + | func genasmArm() { | |
| 48 | + | var buf bytes.Buffer | |
| 49 | + | ||
| 50 | + | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. | |
| 51 | + | ||
| 52 | + | //go:build linux | |
| 53 | + | ||
| 54 | + | // External code calls into callbackasm at an offset corresponding | |
| 55 | + | // to the callback index. Callbackasm is a table of MOVW and B instructions. | |
| 56 | + | // The MOVW instruction loads R12 with the callback index, and the | |
| 57 | + | // B instruction branches to callbackasm1. | |
| 58 | + | // callbackasm1 takes the callback index from R12 and | |
| 59 | + | // indexes into an array that stores information about each callback. | |
| 60 | + | // It then calls the Go implementation for that callback. | |
| 61 | + | #include "textflag.h" | |
| 62 | + | ||
| 63 | + | TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |
| 64 | + | `) | |
| 65 | + | for i := 0; i < maxCallback; i++ { | |
| 66 | + | fmt.Fprintf(&buf, "\tMOVW\t$%d, R12\n", i) | |
| 67 | + | buf.WriteString("\tB\tcallbackasm1(SB)\n") | |
| 68 | + | } | |
| 69 | + | if err := os.WriteFile("zcallback_arm.s", buf.Bytes(), 0644); err != nil { | |
| 70 | + | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) | |
| 71 | + | os.Exit(2) | |
| 72 | + | } | |
| 73 | + | } | |
| 74 | + | ||
| 47 | 75 | func genasmArm64() { | |
| 48 | 76 | var buf bytes.Buffer | |
| 49 | 77 | ||
| @@ -137,6 +165,7 @@ TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||
| 137 | 165 | ||
| 138 | 166 | func main() { | |
| 139 | 167 | genasmAmd64() | |
| 168 | + | genasmArm() | |
| 140 | 169 | genasmArm64() | |
| 141 | 170 | genasmLoong64() | |
| 142 | 171 | genasmRiscv64() | |
| @@ -44,6 +44,34 @@ TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||||
| 44 | } | 44 | } | ||
| 45 | } | 45 | } | ||
| 46 | 46 | ||||
| 47 | + | func genasmArm() { | |||
| 48 | + | var buf bytes.Buffer | |||
| 49 | + | ||||
| 50 | + | buf.WriteString(`// Code generated by wincallback.go using 'go generate'. DO NOT EDIT. | |||
| 51 | + | ||||
| 52 | + | //go:build linux | |||
| 53 | + | ||||
| 54 | + | // External code calls into callbackasm at an offset corresponding | |||
| 55 | + | // to the callback index. Callbackasm is a table of MOVW and B instructions. | |||
| 56 | + | // The MOVW instruction loads R12 with the callback index, and the | |||
| 57 | + | // B instruction branches to callbackasm1. | |||
| 58 | + | // callbackasm1 takes the callback index from R12 and | |||
| 59 | + | // indexes into an array that stores information about each callback. | |||
| 60 | + | // It then calls the Go implementation for that callback. | |||
| 61 | + | #include "textflag.h" | |||
| 62 | + | ||||
| 63 | + | TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||
| 64 | + | `) | |||
| 65 | + | for i := 0; i < maxCallback; i++ { | |||
| 66 | + | fmt.Fprintf(&buf, "\tMOVW\t$%d, R12\n", i) | |||
| 67 | + | buf.WriteString("\tB\tcallbackasm1(SB)\n") | |||
| 68 | + | } | |||
| 69 | + | if err := os.WriteFile("zcallback_arm.s", buf.Bytes(), 0644); err != nil { | |||
| 70 | + | fmt.Fprintf(os.Stderr, "wincallback: %s\n", err) | |||
| 71 | + | os.Exit(2) | |||
| 72 | + | } | |||
| 73 | + | } | |||
| 74 | + | ||||
| 47 | func genasmArm64() { | 75 | func genasmArm64() { | ||
| 48 | var buf bytes.Buffer | 76 | var buf bytes.Buffer | ||
| 49 | 77 | ||||
| @@ -137,6 +165,7 @@ TEXT callbackasm(SB),NOSPLIT|NOFRAME,$0 | |||||
| 137 | 165 | ||||
| 138 | func main() { | 166 | func main() { | ||
| 139 | genasmAmd64() | 167 | genasmAmd64() | ||
| 168 | + | genasmArm() | |||
| 140 | genasmArm64() | 169 | genasmArm64() | ||
| 141 | genasmLoong64() | 170 | genasmLoong64() | ||
| 142 | genasmRiscv64() | 171 | genasmRiscv64() | ||