Skip to content

feat: Renode example tests + v0.1.0 release prep#63

Merged
avrabe merged 1 commit into
mainfrom
feat/renode-tests-release-prep
Mar 21, 2026
Merged

feat: Renode example tests + v0.1.0 release prep#63
avrabe merged 1 commit into
mainfrom
feat/renode-tests-release-prep

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented Mar 21, 2026

Summary

Renode Execution Tests (85 assertions)

  • calculator.wast (62 tests): add/sub/mul/div_safe, accumulator, bitwise, min/max/abs
  • anti_pinch.wast (23 tests): jam detection, PWM ramp, position/direction getters
  • Both wired into Bazel wast_multi_func_test pipeline

Release v0.1.0 Prep

  • CHANGELOG.md: comprehensive release notes for all features
  • 16 crate Cargo.toml: descriptions, homepage, keywords, categories
  • README.md: updated feature matrix, quick start guide, current stats
  • CLI help: usage examples, accurate about text

Meld/Kiln Integration Instructions

  • docs/design/meld-kiln-integration-instructions.md
  • Defines the contract between synth, meld, and kiln-builtins
  • OSxCAR end-to-end test plan (fetch → fuse → compile → link → run)

Test plan

  • cargo test --workspace — 851 tests, 0 failures
  • cargo clippy — clean
  • cargo fmt --check — clean

🤖 Generated with Claude Code

Renode execution tests:
- tests/wast/calculator.wast — 62 assert_return (arithmetic, bitwise, min/max/abs, accumulator)
- tests/wast/anti_pinch.wast — 23 assert_return (jam detection, PWM ramp, getters, tick)
- Wired into Bazel wast_multi_func_test pipeline

Release v0.1.0 preparation:
- CHANGELOG.md — comprehensive release notes
- All 16 crate Cargo.toml — descriptions, metadata, keywords
- README.md — updated features, quick start, 851 tests, 197+ opcodes
- CLI help — usage examples, accurate descriptions

Meld/kiln integration instructions:
- docs/design/meld-kiln-integration-instructions.md
- Contract: what synth expects from meld (single-memory core module)
- Contract: what synth expects from kiln-builtins (3 C ABI functions)
- OSxCAR end-to-end test plan

Trace: skip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@avrabe avrabe merged commit 956b45a into main Mar 21, 2026
6 checks passed
@avrabe avrabe deleted the feat/renode-tests-release-prep branch March 21, 2026 19:57
avrabe added a commit that referenced this pull request May 10, 2026
…x-M (#93)

Pre-fix, `optimizer_bridge::wasm_to_ir` had no handler for
`WasmOp::I64ExtendI32U` / `I64ExtendI32S` / `I32WrapI64`. They fell through
the catch-all `_ => Opcode::Nop`, advancing `inst_id` by 1 but never
registering the produced i64-pair vregs in `vreg_to_arm`. When a downstream
`Opcode::I64ShrU` / `I64Shl` looked up its shift-count vreg via
`get_arm_reg`, the lookup missed the map and silently fell back to
`Reg::R0` (`optimizer_bridge.rs:1333`). The ARM emitter for the variable
i64 shift writes to both `rm_lo` and `rm_hi` as scratch (`AND.W rm_lo,
rm_lo, #63; SUBS.W rm_hi, rm_lo, #32; ...`) — so any function that did
`i64.shr_u` of an `i64.extend_i32_u`-ed shift count clobbered the AAPCS
first-param register R0 inside the shift expansion. For
`compiler_builtins::memset`, R0 is the destination pointer, so the byte
loop's pointer was destroyed every iteration → non-terminating loop on
silicon at `memset+0x4c`.

This patch:

- Adds `Opcode::I64ExtendI32U`, `Opcode::I64ExtendI32S`, and
  `Opcode::I32WrapI64` to `synth-opt`.
- Handles those WasmOps in `wasm_to_ir`, with slot accounting that keeps
  `inst_id.saturating_sub(K)` arithmetic correct for downstream i64 ops
  (extend reuses the consumed i32 slot for `dest_lo`; wrap reuses the
  i64-lo slot and decrements `inst_id` so the natural +1 cancels with the
  -1 net slot delta).
- Lowers them in `ir_to_arm`. Critically, the extend lowerings allocate a
  callee-saved consecutive pair via `alloc_i64_pair` and Mov the i32 source
  into `dest_lo` even when the source already lives in a non-param
  register. This ensures the downstream i64-shift's `rm_lo`/`rm_hi` (which
  the emitter treats as clobbered scratch) are never AAPCS param
  registers.
- Updates `analyze_i64_local_gets` to skip the i32 LocalGet that feeds an
  `I64ExtendI32U`/`I64ExtendI32S` so the analyzer doesn't mistakenly mark
  it as i64.

Regression test: `crates/synth-synthesis/tests/issue_93_memset_i64_codegen.rs`
exercises the bug pattern in the optimized path (4-param function shifting
an `i64.const` by `i64.extend_i32_u(local.get $n)`) and asserts the
emitted i64-shift's `rm_lo`/`rm_hi` are not in R0..R3. Pre-fix: 3 of 5
tests fail (one per shift variant: shr_u, shl, shr_s). Post-fix: 5/5
pass. The no-optimize path was always correct and is exercised by a
sanity test in the same file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
avrabe added a commit that referenced this pull request May 11, 2026
…x-M (#93)

Pre-fix, `optimizer_bridge::wasm_to_ir` had no handler for
`WasmOp::I64ExtendI32U` / `I64ExtendI32S` / `I32WrapI64`. They fell through
the catch-all `_ => Opcode::Nop`, advancing `inst_id` by 1 but never
registering the produced i64-pair vregs in `vreg_to_arm`. When a downstream
`Opcode::I64ShrU` / `I64Shl` looked up its shift-count vreg via
`get_arm_reg`, the lookup missed the map and silently fell back to
`Reg::R0` (`optimizer_bridge.rs:1333`). The ARM emitter for the variable
i64 shift writes to both `rm_lo` and `rm_hi` as scratch (`AND.W rm_lo,
rm_lo, #63; SUBS.W rm_hi, rm_lo, #32; ...`) — so any function that did
`i64.shr_u` of an `i64.extend_i32_u`-ed shift count clobbered the AAPCS
first-param register R0 inside the shift expansion. For
`compiler_builtins::memset`, R0 is the destination pointer, so the byte
loop's pointer was destroyed every iteration → non-terminating loop on
silicon at `memset+0x4c`.

This patch:

- Adds `Opcode::I64ExtendI32U`, `Opcode::I64ExtendI32S`, and
  `Opcode::I32WrapI64` to `synth-opt`.
- Handles those WasmOps in `wasm_to_ir`, with slot accounting that keeps
  `inst_id.saturating_sub(K)` arithmetic correct for downstream i64 ops
  (extend reuses the consumed i32 slot for `dest_lo`; wrap reuses the
  i64-lo slot and decrements `inst_id` so the natural +1 cancels with the
  -1 net slot delta).
- Lowers them in `ir_to_arm`. Critically, the extend lowerings allocate a
  callee-saved consecutive pair via `alloc_i64_pair` and Mov the i32 source
  into `dest_lo` even when the source already lives in a non-param
  register. This ensures the downstream i64-shift's `rm_lo`/`rm_hi` (which
  the emitter treats as clobbered scratch) are never AAPCS param
  registers.
- Updates `analyze_i64_local_gets` to skip the i32 LocalGet that feeds an
  `I64ExtendI32U`/`I64ExtendI32S` so the analyzer doesn't mistakenly mark
  it as i64.

Regression test: `crates/synth-synthesis/tests/issue_93_memset_i64_codegen.rs`
exercises the bug pattern in the optimized path (4-param function shifting
an `i64.const` by `i64.extend_i32_u(local.get $n)`) and asserts the
emitted i64-shift's `rm_lo`/`rm_hi` are not in R0..R3. Pre-fix: 3 of 5
tests fail (one per shift variant: shr_u, shl, shr_s). Post-fix: 5/5
pass. The no-optimize path was always correct and is exercised by a
sanity test in the same file.

Co-Authored-By: Claude Opus 4.7 (1M context) <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