Skip to content

Infer flat word structs in Plan 9 asm signatures - #43

Closed
cpunion wants to merge 3 commits into
xgo-dev:mainfrom
cpunion:codex/fix-struct-asm-issue2691-20260929
Closed

cpunion wants to merge 3 commits into
xgo-dev:mainfrom
cpunion:codex/fix-struct-asm-issue2691-20260929

Conversation

@cpunion

@cpunion cpunion commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Draft investigation for xgo-dev/llgo#2691; not yet a complete or mergeable ABI0 fix.

Changes

  • Infer literal LLVM structs and FP frame slots for flat, word-sized integer/pointer fields, and recognize GO_ARGS metadata.
  • Reject padded, sub-word, nested, floating-point, and mismatched value/frame word layouts, with regression tests and an explicit explanation of the nested-struct restriction.
  • Refresh the third-party CI corpus to klauspost/compress v1.20.1 and pierrec/lz4/v4 v4.1.31, including lz4's new amd64 internal/xxh32 assembly inventory. Keep latest-version and exact-inventory checks enabled.

Validation

  • go test ./... -coverprofile=... passes; goWordStructParts has 100% statement coverage locally.
  • Both updated third-party corpus suites pass translation and LLVM compilation across all 11 configured targets: 42 assembly translations for compress and 14 for lz4.
  • The focused Uint128 test verifies inferred argument/frame layouts and LLVM IR validity; it does not establish correct ABI0 execution.

Remaining blocker

Tracked in #44: the generated modernc.org/libc ABI0 wrappers pass outgoing arguments through SP slots, while current amd64 CALL lowering reads registers. Successful LLVM translation can therefore still produce incorrect calls. The same 1,875-function file also contains complex values, other structured arguments, and 37 callback-trampoline wrappers. Correct ABI0 stack-call lowering and end-to-end Linux/amd64 execution are required before merge.

@codecov

codecov Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review: word-sized struct ABI translation

Solid, well-scoped change. goWordStructParts is deliberately conservative — it only accepts flat structs whose fields are each exactly one integer/pointer word in both the Go value layout and the assembly FP frame, and requires the value/frame word sizes to match (rejecting the wasm32-on-64-bit-frame case). Anything else falls through to a hard unsupported struct ... error rather than a silent miscompile, which is the right fail-closed behavior at an ABI boundary. The GO_ARGS no-op is consistent with the other Go metadata directives already grouped in that case, and the accompanying comments accurately describe the code.

Reviewed for correctness, security, performance, and doc accuracy across all three changed files. No blocking issues. Two minor, non-blocking suggestions are inline.

Comment thread go_translate_deep_test.go
Comment thread go_translate.go
@cpunion
cpunion marked this pull request as draft September 29, 2026 01:45
@cpunion cpunion changed the title Translate word-sized struct arguments in Go ABI0 assembly WIP: Infer flat word structs in Plan 9 asm signatures Sep 29, 2026
@cpunion
cpunion marked this pull request as ready for review September 29, 2026 06:44
@cpunion cpunion changed the title WIP: Infer flat word structs in Plan 9 asm signatures Infer flat word structs in Plan 9 asm signatures Sep 29, 2026

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review: word-sized flat struct ABI translation

Solid, well-guarded change. goWordStructParts is intentionally conservative — it flattens only flat structs whose fields are each exactly one integer/pointer word in both the Go value layout and the assembly FP frame, and fails closed to an unsupported struct error rather than emitting incorrect IR. The guard-test matrix (TestGoTranslateWordStructGuards) and the end-to-end ABI test (TestGoTranslateWordStructABI, verifying arg types, frame slot offsets/indices, and actual extractvalue/call IR) give strong coverage.

Verified clean:

  • Security: No memory-safety or overflow concerns — offsets/sizes come from trusted go/types.Sizes, and the flatten path is defensively gated. Dependency bumps (klauspost/compress v1.20.1, pierrec/lz4/v4 v4.1.31) have go.sum hashes consistent with upstream.
  • Docs: doc/plan9asm-corpus.md, go.mod, and reported-libraries.json versions are consistent; the new goWordStructParts and GO_ARGS comments accurately describe behavior.

One finding (inline): the non-flatten struct argument path recomputes goWordStructParts and re-lowers each field type, when the parts (which already carry .Type) are available in hand.

Minor / optional (not inline):

  • In goWordStructParts, sz.Offsetsof and frameSz.Offsetsof are computed separately even when sz == frameSz (the common case, e.g. the value-type call at the struct case passes sz, sz). Reusing the value offsets when the two Sizes are identical avoids a redundant allocation.
  • The doc comment could note that word-sized floats are deliberately excluded (they need FP register classification) — currently the least obvious rejection.
  • The flatten branch sets Field: -1, dropping the field index the non-flatten branch preserves; a one-line note on why results don't need per-field mapping would prevent future confusion.

Comment thread go_translate.go
if tt.NumFields() == 0 {
return LLVMType("[0 x i8]"), nil
}
if parts, ok := goWordStructParts(tt, goarch, sz, sz); ok {

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 aggregate LLVM type here is derived from a second, value-only goWordStructParts call. But on the non-flatten argument path in goLLVMArgsAndFrameSlotsForTuple, goFramePartsForTypeWithSizes has already run goWordStructParts and produced parts whose .Type fields are exactly these element types — so the struct type is classified and each field re-lowered twice per struct argument.

Beyond the duplicate compile-time work, this creates an implicit coupling: the arg path stays correct only because goWordStructParts requires word(sz) == word(frameSz), so the value-only call agrees with the frame-aware one. If that guard is ever relaxed (e.g. to allow value-word != frame-word structs), the aggregate type and the frame layout could silently diverge. Consider building the LLVM aggregate string from the already-computed parts in the non-flatten branch instead of recomputing via a separate value-only call.

@cpunion cpunion closed this Sep 29, 2026
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