Skip to content

feat(t27b): structs, field access, pointers and struct results - #6044

Merged
gHashTag merged 1 commit into
masterfrom
claude/t27b-structs
Oct 4, 2026
Merged

gHashTag merged 1 commit into
masterfrom
claude/t27b-structs

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Stacked on #6007 (memory primitives). The base is claude/t27b-memory; retarget it to master once #6007 lands.

What

t27b now lowers structs onto the memory primitives from #6007:

  • Declarations and literals. Struct declarations, named and anonymous (result-typed) literals, field defaults, and nested structs.
  • Fields. Reads and writes, including nested and compound (b.a.y = 9, p.x += 1).
  • Pointers. &x, p.*, *T and *const T, plus field access through a pointer. Self-referential pointees work (next: *const Node).
  • Parameters and results.
    • A struct parameter is passed by address and never written.
    • A struct result goes through a hidden last pointer parameter.
    • Such functions form an internal ABI (Program.internal_abi). t27b build does not export them and prints a note saying so.
  • Module struct constants. They go to __const, and a scalar field folds to a constant.
  • Layout. C rules, computed on first use.
  • Address-taken scalars. A scalar local or parameter whose address is taken lives in a frame slot.

Every refusal is precise. The cases are a missing, unknown or duplicate field, a positional initializer, an anonymous .{} with no result type, a write through a constant, a struct or pointer operand, a non-value X.y, a struct that contains itself, and a generic struct.

IR. New ExprKind::Seq { stmts, value } builds a struct temporary at the exact point where its argument is evaluated. Both the interpreter (the oracle) and the code generator implement it, and the random differential generator now emits Seq with stores and copies, with temporaries live around it.

Corpus (t27b corpus specs --jobs 6, 1185 files)

before (#6007) after
supported, every test passes 47 70 (223 tests, 84 invariants)
supported, a test fails 0 1 (the reference fails too, see below)
JIT/interpreter mismatch 0 0
codegen limit / crash 0 / 0 0 / 0
timeout 0 2 (load, see below)
  • specs/fpga/verification/build_verify.t27, test_module_count: t27c gen + zig test aborts on the same assertion (TOTAL_FPGA_MODULES is 33, the test expects 31). The reference fails here too, so this does not count against t27b.
  • The two timeouts, igla/coder/_tmp_pipeline_import.t27 and igla/race/backend.t27, are rejections. At a load average near 780, the t27c front-end parse alone took 10 s and 2 s on them. Both were 0 in the feat(t27b): memory primitives -- frame slots, loads/stores, rodata, bounds (Refs #5977) #6007 run.
  • Top remaining rejections: str 389, EnumDecl 104, []const u8 71, f64 53, BenchBlock 48. EnumDecl and f64 belong to the scalar lane; arrays, slices and strings come next in this lane.

Checks

  • cargo test --release -p t27b: all pass, including the new structs_fields_pointers_and_results and struct_rejections_are_precise tests, plus the differential run with Seq.
  • cargo check --release --target x86_64-unknown-linux-gnu -p t27b --all-targets: ok. The only warnings are from the sealed bootstrap/src/compiler.rs.
  • NOW entry: docs/now/2026-10-04-t27b-structs.md, which check_now_entry_shape.py accepts.

Refs #5977

🤖 Generated with Claude Code

Lower struct declarations, struct literals, field reads and writes,
`&x` / `p.*` / `*T`, struct parameters and struct results to the memory
primitives of the previous change. Structs always live in memory:

- A struct local or temporary lives in a frame slot. Its literal is built
  in place when the destination is fresh and its address is pure;
  otherwise it is built in a temporary and copied.
- A struct parameter is the caller's memory, passed by address and
  never written.
- A struct result is built through a hidden last pointer parameter.
  Such functions are listed in `Program.internal_abi`, and `t27b build`
  does not export them (it prints a note).
- A module struct constant goes to read-only data. A scalar field of it
  folds to a constant (`const OX: i32 = ORIGIN.x`).
- Layout follows the C rules and is computed on first use. A pointee is
  only named, so `next: *const Node` works. A struct that contains
  itself by value is rejected, and so is a generic struct.
- An address-taken scalar local or parameter lives in a slot.
- Compound assignment through an impure address evaluates the address
  once.

Every rejection is precise: a missing, unknown or duplicate field, a
positional initializer, an anonymous `.{}` with no result type, a write
through a constant, a struct or pointer operand, a non-value `X.y`, and
a struct that contains itself.

IR: new `ExprKind::Seq { stmts, value }` (stores and copies, then a
value), so a struct temporary is built exactly where its argument is
evaluated. The interpreter and the code generator both implement it, and
the random differential generator now emits it.

Corpus (specs/, --jobs 6): 47 -> 70 files supported with every test
passing, 0 JIT/interpreter mismatches.
- One newly reached file fails one test (fpga/verification/build_verify,
  module count 33 vs 31). t27c gen + zig test fails the same assertion,
  so the reference fails here too.
- Two files time out in the t27c parse phase alone at a load average
  near 780.

Refs #5977

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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