Skip to content

purego: support structs on loong64#473

Open
hajimehoshi wants to merge 7 commits into
ebitengine:mainfrom
hajimehoshi:claude/affectionate-sammet-5282e3
Open

purego: support structs on loong64#473
hajimehoshi wants to merge 7 commits into
ebitengine:mainfrom
hajimehoshi:claude/affectionate-sammet-5282e3

Conversation

@hajimehoshi

@hajimehoshi hajimehoshi commented Jul 5, 2026

Copy link
Copy Markdown
Member

What issue is this addressing?

Updates #474

What type of issue is this addressing?

feature

What this PR does | solves

Enables passing and returning C structs by value through RegisterFunc /
RegisterLibFunc on linux/loong64.

The per-architecture code added in #431 was unreachable behind a guard that
only permitted amd64 and arm64. This PR makes that guard direction-aware and
adds loong64:

  • ensureStructSupported gates the forward call path (RegisterFunc) and now
    allows loong64 in addition to amd64/arm64.
  • A new ensureCallbackStructSupported keeps the callback path (NewCallback)
    restricted to amd64/arm64, where getCallbackStruct / setStruct are
    implemented. Struct callbacks remain unsupported on loong64.
  • Struct return types are now validated when creating a callback, so an
    unsupported signature fails at NewCallback time instead of panicking later.

The loong64 struct_loong64.go implementation was rewritten to follow the
LoongArch hard-float calling convention: an aggregate uses FP registers only
when, after flattening nested structs/arrays and ignoring blank padding fields,
it has one or two floating-point members and nothing else, or exactly one
floating-point member with one integer member; everything else uses the integer
convention. float32 values are NaN-boxed in FP registers. This was validated
against the real compiler with zig cc -target loongarch64-linux-gnu -S and by
the QEMU test job.

Scope: only loong64 is enabled here. ppc64le, riscv64, and s390x still use
the old isAllSameFloat heuristic, which mishandles floating-point aggregates
(ppc64le currently crashes under test). Each will be enabled in its own PR once
its calling convention is implemented correctly. README.md reflects this:
loong64 moves to "structs when calling C functions, but not in callbacks", while
the other three stay unsupported.


Authored by Claude (Claude Code) on behalf of @hajimehoshi.

🤖 Generated with Claude Code

Struct arguments and return values can now be passed to and received
from C functions via RegisterFunc and RegisterLibFunc on linux/loong64,
linux/ppc64le, linux/riscv64, and linux/s390x. The per-architecture
addStruct and getStruct implementations already existed but were
unreachable behind a guard that only permitted amd64 and arm64.

The guard is now direction-aware. ensureStructSupported gates the
forward call path (RegisterFunc), while the new
ensureCallbackStructSupported keeps the callback path (NewCallback)
restricted to amd64 and arm64, where getCallbackStruct and setStruct
are implemented. Struct callbacks therefore remain unsupported on the
newly enabled architectures.

The struct tests are un-gated for these architectures, with the
callback-based variants skipped where callbacks are unsupported.

32-bit arm and 386 stay unsupported: arm's EABI struct handling is
still a placeholder and the tests assume a 64-bit int.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 5, 2026 15:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands PureGo’s ability to pass and return C structs by value via RegisterFunc/RegisterLibFunc to additional 64-bit Linux architectures (loong64, ppc64le, riscv64, s390x), while keeping struct support in NewCallback restricted to amd64/arm64.

Changes:

  • Broadened ensureStructSupported to allow struct args/returns on loong64, ppc64le, riscv64, and s390x for direct C calls.
  • Introduced ensureCallbackStructSupported and applied it to the callback compilation path.
  • Un-gated struct tests for the newly supported arches, skipping callback-based variants where callbacks don’t support structs; updated README support notes accordingly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
syscall_unix.go Switches struct gating in callback signature validation to callback-specific support checks.
func.go Expands direct-call struct support to additional 64-bit Linux architectures; adds callback-specific support guard.
struct_test.go Enables struct tests on newly supported architectures; skips callback variants where unsupported.
README.md Updates platform support notes to reflect direct-call-only struct support on the new architectures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread syscall_unix.go
hajimehoshi and others added 2 commits July 6, 2026 00:32
Passing and returning structs on loong64 mishandled floating-point
aggregates. getStruct did not mask the NaN-boxed upper bits of a
single-precision value read back from an FP register, so a struct of two
float32 came back with a corrupted second field. addStruct placed every
floating-point field in its own FP register, so a struct with three or
more float members was passed in FP registers instead of the integer
registers the LoongArch hard-float ABI requires.

Replace the isAllSameFloat heuristic with a classifier that flattens
nested structs and arrays and applies the LoongArch rule: an aggregate
uses FP registers only when it has one or two floating-point members and
nothing else, or exactly one floating-point member with one integer
member. Every other aggregate uses the integer calling convention. This
also fixes aggregates of two differently sized floating-point members
such as a float32 and a float64, which the old heuristic misclassified.

Also validate struct return types when creating a callback so an
unsupported signature fails at NewCallback time instead of panicking
later when the callback is invoked.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The LoongArch struct classifier flattened blank (_) padding fields into
members, so a struct padded to match the C layout was misclassified. A
bool and a float32 separated by byte padding looked like one float and
several integers, and a float32 and float64 separated by a blank float32
looked like three floats. Both fell back to the integer calling
convention instead of the floating-point registers the ABI requires.

Skip blank fields when flattening: they are explicit padding, not
members that the C calling convention counts.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@hajimehoshi hajimehoshi changed the title purego: support structs on loong64, ppc64le, riscv64, s390x purego: support structs on loong64 Jul 5, 2026
Only loong64 is enabled and tested here. ppc64le, riscv64, and s390x
still use the isAllSameFloat heuristic that mishandles floating-point
aggregates (ppc64le currently crashes under test), so each will be
enabled separately once its calling convention is implemented correctly.

Drop them from the struct-support guard, the test build tag, and the
README so the guard once again permits only amd64, arm64, and loong64.

Updates ebitengine#474

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@hajimehoshi hajimehoshi force-pushed the claude/affectionate-sammet-5282e3 branch from d29ea72 to 79850cf Compare July 5, 2026 16:08
@hajimehoshi

Copy link
Copy Markdown
Member Author

@TotallyGamerJet Please take a look

Comment thread struct_loong64.go Outdated
The loong-prefixed identifiers in struct_loong64.go read like a
misspelling of "long". Prefix them with the full GOARCH name: loongLeaf,
loongFlatten, loongClassify, loongStoreInt, and loongLoadInt become
loong64Leaf, loong64Flatten, loong64Classify, loong64StoreInt, and
loong64LoadInt.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread struct_loong64.go Outdated
Comment thread struct_loong64.go
Comment thread struct_loong64.go Outdated
Comment thread struct_test.go Outdated
Comment thread struct_test.go Outdated
hajimehoshi and others added 2 commits July 6, 2026 12:07
- Use `for range` instead of a C-style loop in loong64Flatten.
- Copy the return-pointer register into a local before taking its
  address so the reflect.Value cannot alias the pooled syscallArgs.
- Invert the float check in getStruct to flatten the integer path.
- Drop the redundant amd64/arm64 comments in the callback test skip.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
getStruct receives syscallArgs by value, so taking the address of
syscall.a1 does not alias the pooled syscallArgs and the local copy
added earlier is unnecessary. Read the return pointer directly.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

@TotallyGamerJet TotallyGamerJet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants