purego: fix struct-return stack-argument undercount#476
Merged
hajimehoshi merged 1 commit intoJul 13, 2026
Merged
Conversation
Member
Author
|
This is a pure refactoring, and this should be a good change itself Please take a look @TotallyGamerJet |
Contributor
There was a problem hiding this comment.
Pull request overview
Refactors struct-return ABI handling by moving the “struct returned via hidden first integer argument” predicate into per-architecture files selected by filename/build constraints, removing GOARCH branching from RegisterFunc.
Changes:
- Introduces
structReturnInMemory(size uintptr) boolin eachstruct_<arch>.goimplementation and removesamd64StructReturnInMemoryfromfunc.go. - Updates
RegisterFuncto usestructReturnInMemory(...)for both preflight argument counting and runtime call setup. - Keeps arm64’s indirect-result (R8) handling as a separate path by making
structReturnInMemoryalwaysfalseon arm64.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| func.go | Replaces per-GOARCH struct-return handling with structReturnInMemory(...) and removes the old amd64-only helper. |
| struct_amd64.go | Adds amd64-specific structReturnInMemory (Win64 + SysV logic) and switches getStruct to use it. |
| struct_arm64.go | Adds arm64 structReturnInMemory stub returning false to keep R8 indirect-result handling separate. |
| struct_arm.go | Adds arm structReturnInMemory stub returning false. |
| struct_loong64.go | Adds loong64 structReturnInMemory predicate based on maxRegAllocStructSize. |
| struct_ppc64le.go | Adds ppc64le structReturnInMemory predicate based on maxRegAllocStructSize. |
| struct_riscv64.go | Adds riscv64 structReturnInMemory predicate based on maxRegAllocStructSize. |
| struct_s390x.go | Adds s390x structReturnInMemory predicate based on maxRegAllocStructSize. |
| struct_other.go | Adds fallback structReturnInMemory stub returning false for unsupported architectures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
7 tasks
hajimehoshi
force-pushed
the
claude/consolidate-struct-return-7c9bb6
branch
from
July 12, 2026 14:25
287b272 to
edb6ac5
Compare
Member
Author
|
This is no longer a pure refactoring but a bug fix for #477 |
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 ebitengine#477 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
hajimehoshi
force-pushed
the
claude/consolidate-struct-return-7c9bb6
branch
from
July 12, 2026 14:30
edb6ac5 to
c279735
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What issue is this addressing?
Closes #477
What type of issue is this addressing?
bug + refactor
What this PR does | solves
Bug fix (#477)
The preliminary argument count in
RegisterFuncreserved a register for thehidden struct-return pointer with a bare
ints++. That fails to model thespill: when the integer registers are already full, the hidden 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
stack > sizeOfStackguard can pass while the real call writes one past theend of the fixed-size
sysargsarray. The count now spills the same way anordinary integer argument does.
Scope: pre-existing on amd64 and reachable on amd64 and loong64. Windows/amd64
was unaffected (its guard uses the
ints+floats+stacksum) and arm64 isunaffected (it returns the pointer in the dedicated R8 register).
Refactor
func.godecided how a struct return value is passed by inspectingruntime.GOARCHat two call sites inRegisterFunc, via theamd64StructReturnInMemoryhelper plus explicitamd64/loong64/ppc64le/riscv64/s390xbranches.This replaces that with a per-architecture
structReturnInMemory(size uintptr) boolfunction, defined once in each
struct_<arch>.gofile and selected by thefilename build tag — the same dispatch mechanism already used for
getStruct,addStruct, andsetStruct. The two struct-return call sites inRegisterFuncnow just callstructReturnInMemory(...)with noGOARCHchecks.
Per-arch behavior of the new predicate ("is the struct returned via a
caller-allocated hidden pointer passed as the first integer argument?"):
size > maxRegAllocStructSize.false— arm64 returns the pointer through the dedicatedR8 indirect-result register, not an ordinary argument, so that case remains a
separate branch.
false.Test
Adds
TestABI_StructReturnHiddenPointer, which registersmaxArgsintegerarguments plus a large struct return and asserts the registration is rejected.
It fails on the pre-fix counting and passes after the fix; it self-gates so it
skips on architectures that do not route the return through a hidden integer
argument.
Testing
gofmt -sclean;go vetclean.amd64/arm64, darwin amd64/arm64. (linux/s390x's pre-existing cgo/assembly
build requirement is unaffected by this change.)
Authored by Claude (Claude Code), on behalf of @hajimehoshi.