Skip to content

plan9asmll: lower aggregate signatures for llgo cabi#6

Merged
cpunion merged 3 commits into
xgo-dev:mainfrom
cpunion:design/llgo-cabi-compatible-ir
Mar 6, 2026
Merged

plan9asmll: lower aggregate signatures for llgo cabi#6
cpunion merged 3 commits into
xgo-dev:mainfrom
cpunion:design/llgo-cabi-compatible-ir

Conversation

@cpunion
Copy link
Copy Markdown
Collaborator

@cpunion cpunion commented Mar 6, 2026

Summary

  • lower string/slice/interface signatures from Go declarations instead of hard-coded slice-only handling
  • reconstruct sequential-register aggregate arguments in the ARM64 translator and fall back to register returns when FP result slots were never written
  • add focused tests plus a short design note for llgo/cabi-compatible signature lowering

Testing

  • go test .
  • (cd cmd/plan9asmll && go test .)

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refines how Go aggregate types are translated into LLVM signatures for llgo's cabi compatibility, particularly for the ARM64 architecture. The changes introduce a more robust, declaration-driven system for handling types like strings, slices, and interfaces, ensuring accurate representation in LLVM. It also improves ARM64's handling of aggregate arguments and provides a crucial fallback mechanism for return values, enhancing the correctness and extensibility of the plan9asmll toolchain.

Highlights

  • Generalized Signature Lowering: Implemented a more flexible and declaration-driven approach for lowering Go string, slice, and interface types into LLVM signatures, moving away from hard-coded, slice-specific handling.
  • ARM64 Aggregate Argument Reconstruction: Enhanced the ARM64 translator to correctly reconstruct aggregate arguments passed in sequential registers.
  • ARM64 Return Value Fallback: Introduced a mechanism for ARM64 return values to fall back to register-based returns when Floating Point (FP) result slots were not explicitly written to or their addresses were not taken.
  • Design Documentation and Tests: Added a detailed design note explaining the new signature lowering approach and included focused unit tests to validate the changes.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • arm64_ctx.go
    • Added fpResWritten and fpResAddrTaken fields to arm64Ctx to track the status of FP result slots.
    • Initialized the new fpResWritten and fpResAddrTaken maps in newARM64Ctx.
  • arm64_eval.go
    • Called c.markFPResultAddrTaken when evaluating the address of an FP result slot.
  • arm64_fp.go
    • Added fpResultSlotByOffset helper to find a frame slot by its offset.
    • Added markFPResultWritten to mark an FP result slot as having been written to.
    • Added markFPResultAddrTaken to mark an FP result slot whose address has been taken.
    • Modified storeFPResult64 to use fpResultSlotByOffset and to call markFPResultWritten.
    • Implemented loadRetSlotFallback to load return values from registers when FP result slots are not used.
  • arm64_lower_branch.go
    • Added castI64RegToArg function to convert an i64 register value to a specified LLVM type.
    • Added structArgFromSequentialRegs function to reconstruct aggregate arguments from sequential registers.
    • Updated callSym to use structArgFromSequentialRegs for aggregate arguments and castI64RegToArg for scalar arguments.
    • Updated tailCallAndRet to use structArgFromSequentialRegs for aggregate arguments and castI64RegToArg for scalar arguments.
  • arm64_ret_fallback_test.go
    • Added a new test file with TestARM64ReturnFallbackToRegisterWhenResultSlotNotWritten to verify the ARM64 return fallback logic.
  • arm64_translate.go
    • Modified lowerRET to conditionally load FP results from either the stored slot or by falling back to register values based on fpResWritten and fpResAddrTaken flags.
  • cmd/plan9asmll/main.go
    • Removed hardcoded word and wordTy variables from llvmArgsAndFrameSlotsForTuple.
    • Refactored llvmArgsAndFrameSlotsForTuple to use the new framePartsForType function for handling aggregate types like strings, slices, and interfaces.
    • Introduced framePart struct and framePartsForType function to provide a generic way to determine the LLVM frame parts for Go types.
    • Updated llvmTypeForGo to return { ptr, ptr } for interface types.
  • cmd/plan9asmll/main_test.go
    • Added TestLLVMArgsAndFrameSlotsForTupleSliceParam to test slice parameter lowering.
    • Added TestLLVMArgsAndFrameSlotsForTupleSliceResultFlatten to test flattened slice result lowering.
    • Added TestLLVMArgsAndFrameSlotsForTupleInterfaceParam to test interface parameter lowering.
  • doc/llgo-cabi-signature-lowering.md
    • Added a new design document detailing the background, goals, problems, and solutions for plan9asm -> llgo/cabi signature lowering, specifically for slice and aggregate parameters.
Activity
  • The pull request was created by cpunion.
  • No specific review comments or other activities are provided in the context.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant and well-executed refactoring of the plan9asm to LLVM IR translation for ARM64. The changes to lower aggregate type signatures (string, slice, interface) based on their Go declarations make the translator more robust and extensible. The addition of a fallback to register-based returns when result slots are not explicitly written is a key improvement for correctness. The new tests and design document are valuable additions. I have a couple of minor suggestions to reduce code duplication.

Comment thread arm64_fp.go Outdated
Comment thread arm64_lower_branch.go Outdated
Comment thread arm64_fp.go
func (c *arm64Ctx) loadRetSlotFallback(slot FrameSlot) (string, error) {
if slot.Index < 0 || slot.Index > 31 {
return llvmZeroValue(slot.Type), nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

slot.Index is a result index, not a physical register number. Mapping it directly to R{Index} works only if the calling convention happens to place the Nth result in R{N} — which holds today for single-result functions, but is an undocumented invariant. For multi-result functions this becomes fragile. Consider either documenting the invariant explicitly or deriving the fallback register through the same mechanism used for parameters.

Comment thread arm64_fp.go
}

func (c *arm64Ctx) loadRetSlotFallback(slot FrameSlot) (string, error) {
if slot.Index < 0 || slot.Index > 31 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Returning a zero value silently when slot.Index > 31 masks misconfigured frame layouts. A zero return is a valid result, making this indistinguishable from correct behavior. Returning an error would make bugs in signature inference much easier to diagnose.

Comment thread arm64_lower_branch.go
for fi, fty := range fields {
r := Reg(fmt.Sprintf("R%d", *regCursor))
*regCursor++
v, err := c.loadReg(r)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

castI64RegToArg handles integer types and ptr, but not double or float. Functions with floating-point parameters will hit the default error branch and fail to translate. By contrast, loadRetSlotFallback (and the old inline switch) both handle double/float. This omission will break any callee with FP parameters declared in sigs.

Comment thread arm64_lower_branch.go
v, err := c.loadReg(r)
if err != nil {
return err
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When len(csig.ArgRegs) > 0 but i >= len(csig.ArgRegs), the fallback uses the loop index i as the register number rather than regCursor. This is inconsistent with tailCallAndRet (which uses regCursor for overflow args) and won't account for registers already consumed by aggregate arguments earlier in the argument list.

Comment thread arm64_lower_branch.go
}

args := make([]string, 0, len(csig.Args))
regCursor := 0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The useLLVMArgs signature-equality check (which compares callee and caller Args slices) is loop-invariant — its result is the same for all iterations of i. Moving it outside the loop avoids an O(N²) comparison for N-argument callees.

Comment thread doc/llgo-cabi-signature-lowering.md Outdated
Comment thread doc/llgo-cabi-signature-lowering.md Outdated
Comment thread doc/llgo-cabi-signature-lowering.md Outdated
@fennoai
Copy link
Copy Markdown

fennoai Bot commented Mar 6, 2026

Good refactor — replacing the ad-hoc string/slice switch blocks with framePartsForType and unifying the arg-cast path with castI64RegToArg is a clear improvement. A few noteworthy issues to address:

  • castI64RegToArg missing double/float — will break any callee with FP parameters (see inline)
  • loadRetSlotFallback conflates result index with register number — undocumented invariant, fragile for multi-result cases (see inline)
  • Inconsistent regCursor vs i in callSym overflow path — asymmetry with tailCallAndRet (see inline)
  • Design doc is Chinese-only — inconsistent with the rest of the repo (see inline)

@cpunion cpunion merged commit 9203c54 into xgo-dev:main Mar 6, 2026
5 checks passed
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.

1 participant