Skip to content

feat(codegen): multi-table call_indirect — contiguous R11 table region (#650)#653

Merged
avrabe merged 1 commit into
mainfrom
feat/650-multi-table
Jul 8, 2026
Merged

feat(codegen): multi-table call_indirect — contiguous R11 table region (#650)#653
avrabe merged 1 commit into
mainfrom
feat/650-multi-table

Conversation

@avrabe

@avrabe avrabe commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #650. Builds on #646 (the #642 guards) and the #275 R11 arc.

The gap

The ARM backend linked only table 0 at R11: any call_indirect through table index ≥ 1 loud-declined, and #646's closed-world verifier additionally rejected element segments targeting a non-zero table. falcon's fused v1.112 component carries two funcref tables — (table 0 7 7) + (table 1 41 41) — with 20 of 146 functions dispatching through table 1, making this the second-largest skip class after #369.

Layout contract (#650)

Tables become one contiguous region of raw 4-byte code pointers based at R11, in declaration order (imported tables first):

table 0 at R11 + 0                      (unchanged)
table N at R11 + sum(size(0..N)) * 4    (compile-time constant)

The offsets are constants because tables are provably fixed-size (#642: table.grow/table.set are unsupported ops whose functions loud-skip at decode). The contract is documented on CallIndirectGuards and in the CI oracle job.

What changed

Single-table byte-identity — BY CONSTRUCTION, then verified

Offset 0 emits the exact pre-#650 byte sequence (a literal branch in the encoder, not a re-derivation). Verified against an origin/main-built binary: whole-ELF cmp identity on the #642, #594, and #597 fixtures across cortex-m3 / cortex-r5 / cortex-m7dp, both --relocatable and self-contained. Frozen anchors 10/10.

Oracle (CI-gated: call-indirect-650-multitable-oracle)

scripts/repro/call_indirect_650_differential.py + call_indirect_650_multitable.wat: two 3-entry tables, overlapping indices, distinct functions — the aliasing canary (table0[1] → x+200, table1[1] → 1000−x) catches a backend that drops the table index. Per case, unicorn-executed synth code (region linked at R11 per the contract) vs wasmtime:

The issue's exact via_t1 repro now compiles on cortex-m7dp (3/3 functions) — including with table 0 left uninitialized, since poison is per-table.

Honest residuals (named declines, unchanged behavior class)

Workspace tests green (re-run after rebasing onto #648, which also touched the decoder), fmt clean, clippy -D warnings clean workspace-wide.

🤖 Generated with Claude Code

#650)

Tables become ONE contiguous region of raw 4-byte code pointers at R11,
in declaration order: table 0 at R11+0 (unchanged), table N at
R11 + sum(size(0..N))*4 — a compile-time constant, since tables are
provably fixed-size (#642: table.grow/table.set loud-skip at decode).

- decoder: per-table sizes (table_sizes), per-segment table attribution
  (ElemSegmentInfo.table_index), CallIndirectGuards restructured to
  per-table TableGuards { table_size, base_byte_offset, type_reject } —
  the #646 closed-world type verification now runs per (table, type),
  and an unverifiable segment poisons only the table it targets
  (passive/declared/non-const-offset segments still poison all).
- selector: shared resolve_call_indirect_guards() feeds both arms; the
  bounds guard compares against the DISPATCHED table's own size; a
  table past the linked region, an unknown size, an unknown base
  (preceding growable import), or a base past LDR imm12 (4095) each
  loud-decline with a named reason.
- encoder (Thumb-2 + A32): non-zero base folds into the pointer load
  (add ip, r11, ip; ldr ip, [ip, #off]); offset 0 emits the exact
  pre-#650 bytes, so single-table modules are byte-identical BY
  CONSTRUCTION (verified: whole-ELF identity vs origin/main on the
  #642/#594/#597 fixtures, cortex-m3/-r5/-m7dp, ±relocatable; frozen
  anchors 10/10).
- oracle: call_indirect_650_differential.py (CI-gated) — two tables,
  overlapping indices, aliasing canary (table0[1] != table1[1]), OOB
  traps per-table, both ISAs; 26/26 green here, red on <= v0.33.1
  (compile-time decline — this is a capability upgrade).

Unblocks falcon's multi-table fused components (20 of 146 functions
dispatched through table 1).

Fixes #650. Builds on #646 (#642 guards) and the #275 R11 arc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@temper-pulseengine

Copy link
Copy Markdown
Contributor

Automated review for PR #653

pulseengine/synth:feat/650-multi-table → pulseengine/synth:main

Verdict: 💬 Comment

Summary: The changes proposed in this pull request are well-thought-out and align with the project's goals. They address a critical issue related to the layout contract for multi-table call_indirect operations, ensuring that tables are linked back-to-back in declaration order and providing compile-time size information for bounds checking. The addition of a frozen-fixture job and oracles helps maintain the

Findings: 0 mechanical (rivet) · 1 from local AI model.

Findings (1):

  1. .github/workflows/ci.yml:489
    call-indirect-650-multitable-oracle:
    
    The changes proposed in this pull request are well-thought-out and align with the project's goals. They address a critical issue related to the layout contract for multi-table call_indirect operations, ensuring that tables are linked back-to-back in declaration order and providing compile-time size information for bounds checking.

Generated by a local AI model and post-validated against a strict JSON contract. Each finding includes the verbatim line being criticised — verify by reading the file at the cited location.

Reviewed at 2fc963f

@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.26214% with 36 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/synth-core/src/wasm_decoder.rs 89.53% 18 Missing ⚠️
crates/synth-synthesis/src/instruction_selector.rs 88.97% 15 Missing ⚠️
crates/synth-backend/src/arm_encoder.rs 97.11% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@avrabe avrabe merged commit 9789651 into main Jul 8, 2026
32 checks passed
@avrabe avrabe deleted the feat/650-multi-table branch July 8, 2026 16:19
avrabe added a commit that referenced this pull request Jul 8, 2026
…sound mask bounds (#648/#652/#653/#654) (#657)

Pin sweep 0.33.1 -> 0.34.0 + CHANGELOG.

Co-authored-by: Claude Fable 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.

arm backend links only table 0 (R11); call_indirect through table index ≥1 loud-declines — blocks multi-table fused components (falcon: 20 funcs)

1 participant