purego: fix struct-return stack-argument undercount (#476)
RegisterFunc's preliminary argument count reserved a register for the hidden struct-return pointer with a bare ints++. That fails to model the spill: when the integer registers are already full, the pointer is prepended as the first integer argument and pushes a regular argument onto the stack, so a stack slot must be counted instead. Without it, the "too many stack arguments" guard can pass while the actual call writes one past the end of the fixed-size sysargs array. The count now spills the same way an ordinary integer argument does. The bug was pre-existing on amd64 and reachable on amd64 and loong64. Windows/amd64 was unaffected because its guard uses the ints+floats+stack sum, and arm64 is unaffected because it returns the pointer in the dedicated R8 register. This also consolidates how the struct-return convention is selected. Replace the amd64StructReturnInMemory helper in func.go, and the runtime.GOARCH branches that guarded it, with a per-architecture structReturnInMemory function defined once in each struct_<arch>.go file and selected by the filename build tag, matching how getStruct, addStruct, and setStruct are already dispatched. arm64 keeps a separate branch because it uses R8 rather than an ordinary argument. Add TestABI_StructReturnHiddenPointer, which registers maxArgs integer arguments plus a large struct return and asserts that the registration is rejected. Closes #477 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- commit
- ca3432a0c92683e33afa2d39bf783ffb6524bfa8
- author
- Hajime Hoshi · · 2026-07-13 01:53 UTC
- committer
- GitHub · · 2026-07-13 01:53 UTC
- parent
- 5f49e7c
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
11 files · +135 -30
A export_test.go +14 -0
| @@ -0,0 +1,14 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build darwin || freebsd || linux || netbsd || windows | |
| 5 | + | ||
| 6 | + | package purego | |
| 7 | + | ||
| 8 | + | // MaxArgs re-exports maxArgs for external tests. | |
| 9 | + | const MaxArgs = maxArgs | |
| 10 | + | ||
| 11 | + | // StructReturnInMemory re-exports structReturnInMemory for external tests. | |
| 12 | + | func StructReturnInMemory(size uintptr) bool { | |
| 13 | + | return structReturnInMemory(size) | |
| 14 | + | } | |
| @@ -0,0 +1,14 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2026 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build darwin || freebsd || linux || netbsd || windows | |||
| 5 | + | ||||
| 6 | + | package purego | |||
| 7 | + | ||||
| 8 | + | // MaxArgs re-exports maxArgs for external tests. | |||
| 9 | + | const MaxArgs = maxArgs | |||
| 10 | + | ||||
| 11 | + | // StructReturnInMemory re-exports structReturnInMemory for external tests. | |||
| 12 | + | func StructReturnInMemory(size uintptr) bool { | |||
| 13 | + | return structReturnInMemory(size) | |||
| 14 | + | } | |||
M func.go +13 -29
| @@ -199,10 +199,16 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 199 | 199 | ensureStructSupported() | |
| 200 | 200 | outType := ty.Out(0) | |
| 201 | 201 | checkStructFieldsSupported(outType) | |
| 202 | - | if runtime.GOARCH == "amd64" && amd64StructReturnInMemory(outType.Size()) { | |
| 203 | - | // on amd64 a struct returned in memory is allocated by the caller | |
| 204 | - | // and its pointer is passed as a hidden first argument. | |
| 205 | - | ints++ | |
| 202 | + | if structReturnInMemory(outType.Size()) { | |
| 203 | + | // A struct returned in memory is allocated by the caller and its | |
| 204 | + | // pointer is passed as a hidden first integer argument. When the | |
| 205 | + | // integer registers are already full, prepending it spills a | |
| 206 | + | // regular argument onto the stack. | |
| 207 | + | if ints < numOfIntegerRegisters() { | |
| 208 | + | ints++ | |
| 209 | + | } else { | |
| 210 | + | stack++ | |
| 211 | + | } | |
| 206 | 212 | } | |
| 207 | 213 | } | |
| 208 | 214 | ||
| @@ -286,9 +292,9 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 286 | 292 | var arm64_r8 uintptr | |
| 287 | 293 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | |
| 288 | 294 | outType := ty.Out(0) | |
| 289 | - | amd64InMemory := runtime.GOARCH == "amd64" && amd64StructReturnInMemory(outType.Size()) | |
| 290 | - | otherInMemory := (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize | |
| 291 | - | if amd64InMemory || otherInMemory { | |
| 295 | + | if structReturnInMemory(outType.Size()) { | |
| 296 | + | // The caller allocates the return value and passes its pointer | |
| 297 | + | // as a hidden first integer argument. | |
| 292 | 298 | val := reflect.New(outType) | |
| 293 | 299 | keepAlive = append(keepAlive, val) | |
| 294 | 300 | addInt(val.Pointer()) | |
| @@ -533,28 +539,6 @@ func ensureCallbackStructSupported() { | |||
| 533 | 539 | // iOS (GOOS=ios) shares it with macOS (GOOS=darwin). | |
| 534 | 540 | const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios" | |
| 535 | 541 | ||
| 536 | - | // amd64StructReturnInMemory reports whether a struct return value of the given | |
| 537 | - | // size is returned through a caller-allocated hidden pointer (true) rather than | |
| 538 | - | // in registers (false). It must only be consulted on amd64. | |
| 539 | - | func amd64StructReturnInMemory(size uintptr) bool { | |
| 540 | - | if size == 0 { | |
| 541 | - | return false | |
| 542 | - | } | |
| 543 | - | if runtime.GOOS == "windows" { | |
| 544 | - | // The Win64 ABI returns aggregates of exactly 1, 2, 4, or 8 bytes in | |
| 545 | - | // RAX. Every other size is returned through a caller-allocated hidden | |
| 546 | - | // pointer that the callee also returns in RAX. | |
| 547 | - | switch size { | |
| 548 | - | case 1, 2, 4, 8: | |
| 549 | - | return false | |
| 550 | - | default: | |
| 551 | - | return true | |
| 552 | - | } | |
| 553 | - | } | |
| 554 | - | // The System V ABI returns aggregates of up to two eightbytes in registers. | |
| 555 | - | return size > maxRegAllocStructSize | |
| 556 | - | } | |
| 557 | - | ||
| 558 | 542 | func roundUpTo8(val uintptr) uintptr { | |
| 559 | 543 | return (val + align8ByteMask) &^ align8ByteMask | |
| 560 | 544 | } | |
| @@ -199,10 +199,16 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 199 | ensureStructSupported() | 199 | ensureStructSupported() | ||
| 200 | outType := ty.Out(0) | 200 | outType := ty.Out(0) | ||
| 201 | checkStructFieldsSupported(outType) | 201 | checkStructFieldsSupported(outType) | ||
| 202 | - | if runtime.GOARCH == "amd64" && amd64StructReturnInMemory(outType.Size()) { | 202 | + | if structReturnInMemory(outType.Size()) { |
| 203 | - | // on amd64 a struct returned in memory is allocated by the caller | 203 | + | // A struct returned in memory is allocated by the caller and its |
| 204 | - | // and its pointer is passed as a hidden first argument. | 204 | + | // pointer is passed as a hidden first integer argument. When the |
| 205 | - | ints++ | 205 | + | // integer registers are already full, prepending it spills a |
| 206 | + | // regular argument onto the stack. | |||
| 207 | + | if ints < numOfIntegerRegisters() { | |||
| 208 | + | ints++ | |||
| 209 | + | } else { | |||
| 210 | + | stack++ | |||
| 211 | + | } | |||
| 206 | } | 212 | } | ||
| 207 | } | 213 | } | ||
| 208 | 214 | ||||
| @@ -286,9 +292,9 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 286 | var arm64_r8 uintptr | 292 | var arm64_r8 uintptr | ||
| 287 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | 293 | if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { | ||
| 288 | outType := ty.Out(0) | 294 | outType := ty.Out(0) | ||
| 289 | - | amd64InMemory := runtime.GOARCH == "amd64" && amd64StructReturnInMemory(outType.Size()) | 295 | + | if structReturnInMemory(outType.Size()) { |
| 290 | - | otherInMemory := (runtime.GOARCH == "loong64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x") && outType.Size() > maxRegAllocStructSize | 296 | + | // The caller allocates the return value and passes its pointer |
| 291 | - | if amd64InMemory || otherInMemory { | 297 | + | // as a hidden first integer argument. |
| 292 | val := reflect.New(outType) | 298 | val := reflect.New(outType) | ||
| 293 | keepAlive = append(keepAlive, val) | 299 | keepAlive = append(keepAlive, val) | ||
| 294 | addInt(val.Pointer()) | 300 | addInt(val.Pointer()) | ||
| @@ -533,28 +539,6 @@ func ensureCallbackStructSupported() { | |||||
| 533 | // iOS (GOOS=ios) shares it with macOS (GOOS=darwin). | 539 | // iOS (GOOS=ios) shares it with macOS (GOOS=darwin). | ||
| 534 | const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios" | 540 | const isDarwin = runtime.GOOS == "darwin" || runtime.GOOS == "ios" | ||
| 535 | 541 | ||||
| 536 | - | // amd64StructReturnInMemory reports whether a struct return value of the given | |||
| 537 | - | // size is returned through a caller-allocated hidden pointer (true) rather than | |||
| 538 | - | // in registers (false). It must only be consulted on amd64. | |||
| 539 | - | func amd64StructReturnInMemory(size uintptr) bool { | |||
| 540 | - | if size == 0 { | |||
| 541 | - | return false | |||
| 542 | - | } | |||
| 543 | - | if runtime.GOOS == "windows" { | |||
| 544 | - | // The Win64 ABI returns aggregates of exactly 1, 2, 4, or 8 bytes in | |||
| 545 | - | // RAX. Every other size is returned through a caller-allocated hidden | |||
| 546 | - | // pointer that the callee also returns in RAX. | |||
| 547 | - | switch size { | |||
| 548 | - | case 1, 2, 4, 8: | |||
| 549 | - | return false | |||
| 550 | - | default: | |||
| 551 | - | return true | |||
| 552 | - | } | |||
| 553 | - | } | |||
| 554 | - | // The System V ABI returns aggregates of up to two eightbytes in registers. | |||
| 555 | - | return size > maxRegAllocStructSize | |||
| 556 | - | } | |||
| 557 | - | ||||
| 558 | func roundUpTo8(val uintptr) uintptr { | 542 | func roundUpTo8(val uintptr) uintptr { | ||
| 559 | return (val + align8ByteMask) &^ align8ByteMask | 543 | return (val + align8ByteMask) &^ align8ByteMask | ||
| 560 | } | 544 | } | ||
M func_test.go +36 -0
| @@ -10,6 +10,7 @@ import ( | |||
| 10 | 10 | "os" | |
| 11 | 11 | "os/exec" | |
| 12 | 12 | "path/filepath" | |
| 13 | + | "reflect" | |
| 13 | 14 | "runtime" | |
| 14 | 15 | "strings" | |
| 15 | 16 | "sync" | |
| @@ -597,6 +598,41 @@ func TestABI_TooManyArguments(t *testing.T) { | |||
| 597 | 598 | }) | |
| 598 | 599 | } | |
| 599 | 600 | ||
| 601 | + | func TestABI_StructReturnHiddenPointer(t *testing.T) { | |
| 602 | + | // A struct returned in memory is passed a hidden pointer as the first | |
| 603 | + | // integer argument, so maxArgs integer arguments plus it need one more slot | |
| 604 | + | // than sysargs holds and RegisterFunc must reject the registration. | |
| 605 | + | type bigStruct struct{ A, B, C uint64 } // larger than two eightbytes | |
| 606 | + | ||
| 607 | + | if !purego.StructReturnInMemory(reflect.TypeFor[bigStruct]().Size()) { | |
| 608 | + | t.Skipf("GOARCH=%s does not return large structs via a hidden integer argument", runtime.GOARCH) | |
| 609 | + | } | |
| 610 | + | ||
| 611 | + | in := make([]reflect.Type, purego.MaxArgs) | |
| 612 | + | for i := range in { | |
| 613 | + | in[i] = reflect.TypeFor[uintptr]() | |
| 614 | + | } | |
| 615 | + | fnType := reflect.FuncOf(in, []reflect.Type{reflect.TypeFor[bigStruct]()}, false) | |
| 616 | + | fptr := reflect.New(fnType) | |
| 617 | + | ||
| 618 | + | defer func() { | |
| 619 | + | switch r := recover(); { | |
| 620 | + | case r == nil: | |
| 621 | + | t.Fatal("RegisterFunc accepted the call; the hidden struct-return pointer was not counted against the stack limit") | |
| 622 | + | case strings.Contains(fmt.Sprint(r), "too many stack arguments"): | |
| 623 | + | // Expected: the guard fired. | |
| 624 | + | case strings.Contains(fmt.Sprint(r), "only supported on"): | |
| 625 | + | t.Skipf("struct returns are unsupported on this platform: %v", r) | |
| 626 | + | default: | |
| 627 | + | t.Fatalf("unexpected panic: %v", r) | |
| 628 | + | } | |
| 629 | + | }() | |
| 630 | + | ||
| 631 | + | // A non-zero cfn passes the nil check; the panic fires during the preflight | |
| 632 | + | // argument count, before the function is ever called. | |
| 633 | + | purego.RegisterFunc(fptr.Interface(), uintptr(1)) | |
| 634 | + | } | |
| 635 | + | ||
| 600 | 636 | func buildSharedLib(tb testing.TB, compilerEnv, libFile string, sources ...string) error { | |
| 601 | 637 | tb.Helper() | |
| 602 | 638 | // When PUREGO_TEST_PREBUILT_LIBDIR is set, the shared library has been | |
| @@ -10,6 +10,7 @@ import ( | |||||
| 10 | "os" | 10 | "os" | ||
| 11 | "os/exec" | 11 | "os/exec" | ||
| 12 | "path/filepath" | 12 | "path/filepath" | ||
| 13 | + | "reflect" | |||
| 13 | "runtime" | 14 | "runtime" | ||
| 14 | "strings" | 15 | "strings" | ||
| 15 | "sync" | 16 | "sync" | ||
| @@ -597,6 +598,41 @@ func TestABI_TooManyArguments(t *testing.T) { | |||||
| 597 | }) | 598 | }) | ||
| 598 | } | 599 | } | ||
| 599 | 600 | ||||
| 601 | + | func TestABI_StructReturnHiddenPointer(t *testing.T) { | |||
| 602 | + | // A struct returned in memory is passed a hidden pointer as the first | |||
| 603 | + | // integer argument, so maxArgs integer arguments plus it need one more slot | |||
| 604 | + | // than sysargs holds and RegisterFunc must reject the registration. | |||
| 605 | + | type bigStruct struct{ A, B, C uint64 } // larger than two eightbytes | |||
| 606 | + | ||||
| 607 | + | if !purego.StructReturnInMemory(reflect.TypeFor[bigStruct]().Size()) { | |||
| 608 | + | t.Skipf("GOARCH=%s does not return large structs via a hidden integer argument", runtime.GOARCH) | |||
| 609 | + | } | |||
| 610 | + | ||||
| 611 | + | in := make([]reflect.Type, purego.MaxArgs) | |||
| 612 | + | for i := range in { | |||
| 613 | + | in[i] = reflect.TypeFor[uintptr]() | |||
| 614 | + | } | |||
| 615 | + | fnType := reflect.FuncOf(in, []reflect.Type{reflect.TypeFor[bigStruct]()}, false) | |||
| 616 | + | fptr := reflect.New(fnType) | |||
| 617 | + | ||||
| 618 | + | defer func() { | |||
| 619 | + | switch r := recover(); { | |||
| 620 | + | case r == nil: | |||
| 621 | + | t.Fatal("RegisterFunc accepted the call; the hidden struct-return pointer was not counted against the stack limit") | |||
| 622 | + | case strings.Contains(fmt.Sprint(r), "too many stack arguments"): | |||
| 623 | + | // Expected: the guard fired. | |||
| 624 | + | case strings.Contains(fmt.Sprint(r), "only supported on"): | |||
| 625 | + | t.Skipf("struct returns are unsupported on this platform: %v", r) | |||
| 626 | + | default: | |||
| 627 | + | t.Fatalf("unexpected panic: %v", r) | |||
| 628 | + | } | |||
| 629 | + | }() | |||
| 630 | + | ||||
| 631 | + | // A non-zero cfn passes the nil check; the panic fires during the preflight | |||
| 632 | + | // argument count, before the function is ever called. | |||
| 633 | + | purego.RegisterFunc(fptr.Interface(), uintptr(1)) | |||
| 634 | + | } | |||
| 635 | + | ||||
| 600 | func buildSharedLib(tb testing.TB, compilerEnv, libFile string, sources ...string) error { | 636 | func buildSharedLib(tb testing.TB, compilerEnv, libFile string, sources ...string) error { | ||
| 601 | tb.Helper() | 637 | tb.Helper() | ||
| 602 | // When PUREGO_TEST_PREBUILT_LIBDIR is set, the shared library has been | 638 | // When PUREGO_TEST_PREBUILT_LIBDIR is set, the shared library has been | ||
M struct_amd64.go +23 -1
| @@ -10,13 +10,35 @@ import ( | |||
| 10 | 10 | "unsafe" | |
| 11 | 11 | ) | |
| 12 | 12 | ||
| 13 | + | // structReturnInMemory reports whether a struct return value of the given size | |
| 14 | + | // is returned through a caller-allocated hidden pointer passed as the first | |
| 15 | + | // integer argument (true) rather than in registers (false). | |
| 16 | + | func structReturnInMemory(size uintptr) bool { | |
| 17 | + | if size == 0 { | |
| 18 | + | return false | |
| 19 | + | } | |
| 20 | + | if runtime.GOOS == "windows" { | |
| 21 | + | // The Win64 ABI returns aggregates of exactly 1, 2, 4, or 8 bytes in | |
| 22 | + | // RAX. Every other size is returned through a caller-allocated hidden | |
| 23 | + | // pointer that the callee also returns in RAX. | |
| 24 | + | switch size { | |
| 25 | + | case 1, 2, 4, 8: | |
| 26 | + | return false | |
| 27 | + | default: | |
| 28 | + | return true | |
| 29 | + | } | |
| 30 | + | } | |
| 31 | + | // The System V ABI returns aggregates of up to two eightbytes in registers. | |
| 32 | + | return size > maxRegAllocStructSize | |
| 33 | + | } | |
| 34 | + | ||
| 13 | 35 | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | |
| 14 | 36 | outSize := outType.Size() | |
| 15 | 37 | if runtime.GOOS == "windows" { | |
| 16 | 38 | switch { | |
| 17 | 39 | case outSize == 0: | |
| 18 | 40 | return reflect.New(outType).Elem() | |
| 19 | - | case amd64StructReturnInMemory(outSize): | |
| 41 | + | case structReturnInMemory(outSize): | |
| 20 | 42 | // Returned through the caller-allocated hidden pointer, which the | |
| 21 | 43 | // callee also returns in RAX. | |
| 22 | 44 | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | |
| @@ -10,13 +10,35 @@ import ( | |||||
| 10 | "unsafe" | 10 | "unsafe" | ||
| 11 | ) | 11 | ) | ||
| 12 | 12 | ||||
| 13 | + | // structReturnInMemory reports whether a struct return value of the given size | |||
| 14 | + | // is returned through a caller-allocated hidden pointer passed as the first | |||
| 15 | + | // integer argument (true) rather than in registers (false). | |||
| 16 | + | func structReturnInMemory(size uintptr) bool { | |||
| 17 | + | if size == 0 { | |||
| 18 | + | return false | |||
| 19 | + | } | |||
| 20 | + | if runtime.GOOS == "windows" { | |||
| 21 | + | // The Win64 ABI returns aggregates of exactly 1, 2, 4, or 8 bytes in | |||
| 22 | + | // RAX. Every other size is returned through a caller-allocated hidden | |||
| 23 | + | // pointer that the callee also returns in RAX. | |||
| 24 | + | switch size { | |||
| 25 | + | case 1, 2, 4, 8: | |||
| 26 | + | return false | |||
| 27 | + | default: | |||
| 28 | + | return true | |||
| 29 | + | } | |||
| 30 | + | } | |||
| 31 | + | // The System V ABI returns aggregates of up to two eightbytes in registers. | |||
| 32 | + | return size > maxRegAllocStructSize | |||
| 33 | + | } | |||
| 34 | + | ||||
| 13 | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | 35 | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | ||
| 14 | outSize := outType.Size() | 36 | outSize := outType.Size() | ||
| 15 | if runtime.GOOS == "windows" { | 37 | if runtime.GOOS == "windows" { | ||
| 16 | switch { | 38 | switch { | ||
| 17 | case outSize == 0: | 39 | case outSize == 0: | ||
| 18 | return reflect.New(outType).Elem() | 40 | return reflect.New(outType).Elem() | ||
| 19 | - | case amd64StructReturnInMemory(outSize): | 41 | + | case structReturnInMemory(outSize): |
| 20 | // Returned through the caller-allocated hidden pointer, which the | 42 | // Returned through the caller-allocated hidden pointer, which the | ||
| 21 | // callee also returns in RAX. | 43 | // callee also returns in RAX. | ||
| 22 | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | 44 | return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem() | ||
M struct_arm.go +7 -0
| @@ -28,6 +28,13 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||
| 28 | 28 | return keepAlive | |
| 29 | 29 | } | |
| 30 | 30 | ||
| 31 | + | // structReturnInMemory always reports false on arm: an indirect struct return | |
| 32 | + | // is recovered from the pointer the callee leaves in a1 (see getStruct) rather | |
| 33 | + | // than through a caller-allocated hidden first argument. | |
| 34 | + | func structReturnInMemory(size uintptr) bool { | |
| 35 | + | return false | |
| 36 | + | } | |
| 37 | + | ||
| 31 | 38 | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | |
| 32 | 39 | outSize := outType.Size() | |
| 33 | 40 | if outSize == 0 { | |
| @@ -28,6 +28,13 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl | |||||
| 28 | return keepAlive | 28 | return keepAlive | ||
| 29 | } | 29 | } | ||
| 30 | 30 | ||||
| 31 | + | // structReturnInMemory always reports false on arm: an indirect struct return | |||
| 32 | + | // is recovered from the pointer the callee leaves in a1 (see getStruct) rather | |||
| 33 | + | // than through a caller-allocated hidden first argument. | |||
| 34 | + | func structReturnInMemory(size uintptr) bool { | |||
| 35 | + | return false | |||
| 36 | + | } | |||
| 37 | + | ||||
| 31 | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | 38 | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | ||
| 32 | outSize := outType.Size() | 39 | outSize := outType.Size() | ||
| 33 | if outSize == 0 { | 40 | if outSize == 0 { | ||
M struct_arm64.go +7 -0
| @@ -14,6 +14,13 @@ import ( | |||
| 14 | 14 | "github.com/ebitengine/purego/internal/strings" | |
| 15 | 15 | ) | |
| 16 | 16 | ||
| 17 | + | // structReturnInMemory always reports false on arm64: a struct returned in | |
| 18 | + | // memory is passed through the dedicated indirect result register (R8), not as | |
| 19 | + | // an ordinary integer argument, so it is handled separately. | |
| 20 | + | func structReturnInMemory(size uintptr) bool { | |
| 21 | + | return false | |
| 22 | + | } | |
| 23 | + | ||
| 17 | 24 | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | |
| 18 | 25 | outSize := outType.Size() | |
| 19 | 26 | switch { | |
| @@ -14,6 +14,13 @@ import ( | |||||
| 14 | "github.com/ebitengine/purego/internal/strings" | 14 | "github.com/ebitengine/purego/internal/strings" | ||
| 15 | ) | 15 | ) | ||
| 16 | 16 | ||||
| 17 | + | // structReturnInMemory always reports false on arm64: a struct returned in | |||
| 18 | + | // memory is passed through the dedicated indirect result register (R8), not as | |||
| 19 | + | // an ordinary integer argument, so it is handled separately. | |||
| 20 | + | func structReturnInMemory(size uintptr) bool { | |||
| 21 | + | return false | |||
| 22 | + | } | |||
| 23 | + | ||||
| 17 | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | 24 | func getStruct(outType reflect.Type, syscall syscallArgs) (v reflect.Value) { | ||
| 18 | outSize := outType.Size() | 25 | outSize := outType.Size() | ||
| 19 | switch { | 26 | switch { | ||
M struct_loong64.go +7 -0
| @@ -72,6 +72,13 @@ func loong64Classify(t reflect.Type) (leaves []loong64Leaf, useFP bool) { | |||
| 72 | 72 | } | |
| 73 | 73 | } | |
| 74 | 74 | ||
| 75 | + | // structReturnInMemory reports whether a struct return value of the given size | |
| 76 | + | // is returned through a caller-allocated hidden pointer passed as the first | |
| 77 | + | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |
| 78 | + | func structReturnInMemory(size uintptr) bool { | |
| 79 | + | return size > maxRegAllocStructSize | |
| 80 | + | } | |
| 81 | + | ||
| 75 | 82 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| 76 | 83 | outSize := outType.Size() | |
| 77 | 84 | if outSize == 0 { | |
| @@ -72,6 +72,13 @@ func loong64Classify(t reflect.Type) (leaves []loong64Leaf, useFP bool) { | |||||
| 72 | } | 72 | } | ||
| 73 | } | 73 | } | ||
| 74 | 74 | ||||
| 75 | + | // structReturnInMemory reports whether a struct return value of the given size | |||
| 76 | + | // is returned through a caller-allocated hidden pointer passed as the first | |||
| 77 | + | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |||
| 78 | + | func structReturnInMemory(size uintptr) bool { | |||
| 79 | + | return size > maxRegAllocStructSize | |||
| 80 | + | } | |||
| 81 | + | ||||
| 75 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | 82 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | ||
| 76 | outSize := outType.Size() | 83 | outSize := outType.Size() | ||
| 77 | if outSize == 0 { | 84 | if outSize == 0 { | ||
M struct_other.go +7 -0
| @@ -22,6 +22,13 @@ func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |||
| 22 | 22 | panic("purego: struct returns are not supported on this architecture") | |
| 23 | 23 | } | |
| 24 | 24 | ||
| 25 | + | // structReturnInMemory reports whether a struct return value of the given size | |
| 26 | + | // is returned through a caller-allocated hidden pointer. Structs are unsupported | |
| 27 | + | // on this architecture, so it always reports false. | |
| 28 | + | func structReturnInMemory(size uintptr) bool { | |
| 29 | + | return false | |
| 30 | + | } | |
| 31 | + | ||
| 25 | 32 | // shouldBundleStackArgs always returns false because C-style stack argument | |
| 26 | 33 | // bundling is only needed on Darwin ARM64. | |
| 27 | 34 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | |
| @@ -22,6 +22,13 @@ func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |||||
| 22 | panic("purego: struct returns are not supported on this architecture") | 22 | panic("purego: struct returns are not supported on this architecture") | ||
| 23 | } | 23 | } | ||
| 24 | 24 | ||||
| 25 | + | // structReturnInMemory reports whether a struct return value of the given size | |||
| 26 | + | // is returned through a caller-allocated hidden pointer. Structs are unsupported | |||
| 27 | + | // on this architecture, so it always reports false. | |||
| 28 | + | func structReturnInMemory(size uintptr) bool { | |||
| 29 | + | return false | |||
| 30 | + | } | |||
| 31 | + | ||||
| 25 | // shouldBundleStackArgs always returns false because C-style stack argument | 32 | // shouldBundleStackArgs always returns false because C-style stack argument | ||
| 26 | // bundling is only needed on Darwin ARM64. | 33 | // bundling is only needed on Darwin ARM64. | ||
| 27 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | 34 | func shouldBundleStackArgs(v reflect.Value, numInts, numFloats int) bool { | ||
M struct_ppc64le.go +7 -0
| @@ -8,6 +8,13 @@ import ( | |||
| 8 | 8 | "unsafe" | |
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | + | // structReturnInMemory reports whether a struct return value of the given size | |
| 12 | + | // is returned through a caller-allocated hidden pointer passed as the first | |
| 13 | + | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |
| 14 | + | func structReturnInMemory(size uintptr) bool { | |
| 15 | + | return size > maxRegAllocStructSize | |
| 16 | + | } | |
| 17 | + | ||
| 11 | 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| 12 | 19 | outSize := outType.Size() | |
| 13 | 20 | ||
| @@ -8,6 +8,13 @@ import ( | |||||
| 8 | "unsafe" | 8 | "unsafe" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | + | // structReturnInMemory reports whether a struct return value of the given size | |||
| 12 | + | // is returned through a caller-allocated hidden pointer passed as the first | |||
| 13 | + | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |||
| 14 | + | func structReturnInMemory(size uintptr) bool { | |||
| 15 | + | return size > maxRegAllocStructSize | |||
| 16 | + | } | |||
| 17 | + | ||||
| 11 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | ||
| 12 | outSize := outType.Size() | 19 | outSize := outType.Size() | ||
| 13 | 20 | ||||
M struct_riscv64.go +7 -0
| @@ -8,6 +8,13 @@ import ( | |||
| 8 | 8 | "unsafe" | |
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | + | // structReturnInMemory reports whether a struct return value of the given size | |
| 12 | + | // is returned through a caller-allocated hidden pointer passed as the first | |
| 13 | + | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |
| 14 | + | func structReturnInMemory(size uintptr) bool { | |
| 15 | + | return size > maxRegAllocStructSize | |
| 16 | + | } | |
| 17 | + | ||
| 11 | 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| 12 | 19 | outSize := outType.Size() | |
| 13 | 20 | ||
| @@ -8,6 +8,13 @@ import ( | |||||
| 8 | "unsafe" | 8 | "unsafe" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | + | // structReturnInMemory reports whether a struct return value of the given size | |||
| 12 | + | // is returned through a caller-allocated hidden pointer passed as the first | |||
| 13 | + | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |||
| 14 | + | func structReturnInMemory(size uintptr) bool { | |||
| 15 | + | return size > maxRegAllocStructSize | |||
| 16 | + | } | |||
| 17 | + | ||||
| 11 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | ||
| 12 | outSize := outType.Size() | 19 | outSize := outType.Size() | ||
| 13 | 20 | ||||
M struct_s390x.go +7 -0
| @@ -8,6 +8,13 @@ import ( | |||
| 8 | 8 | "unsafe" | |
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | + | // structReturnInMemory reports whether a struct return value of the given size | |
| 12 | + | // is returned through a caller-allocated hidden pointer passed as the first | |
| 13 | + | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |
| 14 | + | func structReturnInMemory(size uintptr) bool { | |
| 15 | + | return size > maxRegAllocStructSize | |
| 16 | + | } | |
| 17 | + | ||
| 11 | 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | |
| 12 | 19 | outSize := outType.Size() | |
| 13 | 20 | ||
| @@ -8,6 +8,13 @@ import ( | |||||
| 8 | "unsafe" | 8 | "unsafe" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | + | // structReturnInMemory reports whether a struct return value of the given size | |||
| 12 | + | // is returned through a caller-allocated hidden pointer passed as the first | |||
| 13 | + | // integer argument. Aggregates larger than two eightbytes are returned in memory. | |||
| 14 | + | func structReturnInMemory(size uintptr) bool { | |||
| 15 | + | return size > maxRegAllocStructSize | |||
| 16 | + | } | |||
| 17 | + | ||||
| 11 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | 18 | func getStruct(outType reflect.Type, syscall syscallArgs) reflect.Value { | ||
| 12 | outSize := outType.Size() | 19 | outSize := outType.Size() | ||
| 13 | 20 | ||||