Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.15.0] - 2026-06-24

**Immediate-shift folding is now DEFAULT-ON (VCR-RA, #390, epic #242).** The stack
selector lowers `i32.shl/shr_s/shr_u` to register-shift forms, materializing a
constant shift amount into a scratch register (`movw rM,#C; lsl rD,rN,rM`); the
peephole now folds that to the immediate form (`lsl rD,rN,#C`) and removes the dead
`movw` — −1 instruction and −1 live register per folded shift. This is a
**byte-changing** release on top of v0.14.0: `.text` shrinks on shift-heavy
functions — control_step 316→304 B, flight_seam 866→774, flight_seam_flat 1006→910
(−200 B across the frozen fixtures); signed_div_const (no register-shift folds)
unchanged.

Execution results are unchanged: control_step `0x00210A55` (differential 13/13) and
flat+inlined flight_algo `0x07FDF307` are preserved. Validated bit-identical and a
net cycle win on the dissolved hot path (−2 cyc/call, `.text` 100→90 B on gust_mix).
Cumulative dissolved hot-path improvement across the codegen levers:
64.0 → 58.0 (cmp→select) → 50.0 (local promotion) → 48.0 cyc/call.

Soundness: only folds shift amounts in [1,31] (where register- and immediate-shift
forms provably agree), the shift-amount register must be dead after the shift, and
the rewrite is removal-only (offset-neutral before branch resolution). Escape hatch:
`SYNTH_NO_IMM_SHIFT_FOLD=1` restores the register-shift form.

## [0.14.0] - 2026-06-24

**i32 local promotion is now DEFAULT-ON (VCR-RA-001, #390, epic #242).** The ARM
Expand Down
34 changes: 17 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ resolver = "2"
# semver to publish, so the convention now catches up: workspace
# version follows the release tag, bumped pre-tag in the release
# checklist. See docs/release-process.md.
version = "0.14.0"
version = "0.15.0"
edition = "2024"
rust-version = "1.88"
authors = ["PulseEngine Team"]
Expand Down
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module(
name = "synth",
# Kept in lockstep with [workspace.package] version in Cargo.toml.
# Both are bumped pre-tag — see docs/release-process.md.
version = "0.14.0",
version = "0.15.0",
)

# Bazel dependencies
Expand Down
2 changes: 1 addition & 1 deletion crates/synth-backend-awsm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ categories.workspace = true
description = "aWsm backend integration for the Synth compiler"

[dependencies]
synth-core = { path = "../synth-core", version = "0.14.0" }
synth-core = { path = "../synth-core", version = "0.15.0" }
anyhow.workspace = true
thiserror.workspace = true
4 changes: 2 additions & 2 deletions crates/synth-backend-riscv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ categories.workspace = true
description = "RISC-V encoder, ELF builder, PMP allocator, and bare-metal startup for synth"

[dependencies]
synth-core = { path = "../synth-core", version = "0.14.0" }
synth-synthesis = { path = "../synth-synthesis", version = "0.14.0" }
synth-core = { path = "../synth-core", version = "0.15.0" }
synth-synthesis = { path = "../synth-synthesis", version = "0.15.0" }
anyhow.workspace = true
thiserror.workspace = true
tracing.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/synth-backend-wasker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ categories.workspace = true
description = "Wasker backend integration for the Synth compiler"

[dependencies]
synth-core = { path = "../synth-core", version = "0.14.0" }
synth-core = { path = "../synth-core", version = "0.15.0" }
anyhow.workspace = true
thiserror.workspace = true
4 changes: 2 additions & 2 deletions crates/synth-backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ default = ["arm-cortex-m"]
arm-cortex-m = ["synth-synthesis"]

[dependencies]
synth-core = { path = "../synth-core", version = "0.14.0" }
synth-synthesis = { path = "../synth-synthesis", version = "0.14.0", optional = true }
synth-core = { path = "../synth-core", version = "0.15.0" }
synth-synthesis = { path = "../synth-synthesis", version = "0.15.0", optional = true }
anyhow.workspace = true
thiserror.workspace = true
9 changes: 4 additions & 5 deletions crates/synth-backend/src/arm_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,11 +492,10 @@ fn compile_wasm_to_arm(
// stack selector materialized into a scratch register (`movw rM,#C; lsl rD,rN,rM`)
// folds to the immediate form (`lsl rD,rN,#C`), removing the dead `movw` — −1
// instruction, −1 live register. Removal-only (offset-neutral before branch
// resolution, like the dead-store pass). BEHIND `SYNTH_IMM_SHIFT_FOLD=1`
// (opt-in, off by default ⇒ bit-identical) while it earns the execution
// differential + gale's G474RE DWT gate — the same gated path local promotion
// and cmp→select took before shipping default-on. gale-named lever toward ≤1.3×.
let arm_instrs = if std::env::var("SYNTH_IMM_SHIFT_FOLD").is_ok() {
// resolution, like the dead-store pass). DEFAULT-ON as of v0.15.0: validated
// bit-identical results + a net cycle win on the dissolved hot path (−2
// cyc/call, .text 100→90 B on gust_mix). Escape hatch: `SYNTH_NO_IMM_SHIFT_FOLD=1`.
let arm_instrs = if std::env::var("SYNTH_NO_IMM_SHIFT_FOLD").is_err() {
let (out, folds) = synth_synthesis::liveness::fold_immediate_shifts(&arm_instrs);
if std::env::var("SYNTH_FUSE_STATS").is_ok() {
eprintln!(
Expand Down
16 changes: 8 additions & 8 deletions crates/synth-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,18 @@ verify = ["synth-verify"]
# Path deps carry `version` so `cargo publish` rewrites them to the
# crates.io coordinate. Bumping the workspace version requires
# updating these in lockstep — see docs/release-process.md.
synth-core = { path = "../synth-core", version = "0.14.0" }
synth-frontend = { path = "../synth-frontend", version = "0.14.0" }
synth-synthesis = { path = "../synth-synthesis", version = "0.14.0" }
synth-backend = { path = "../synth-backend", version = "0.14.0" }
synth-core = { path = "../synth-core", version = "0.15.0" }
synth-frontend = { path = "../synth-frontend", version = "0.15.0" }
synth-synthesis = { path = "../synth-synthesis", version = "0.15.0" }
synth-backend = { path = "../synth-backend", version = "0.15.0" }

# Optional external backends
synth-backend-awsm = { path = "../synth-backend-awsm", version = "0.14.0", optional = true }
synth-backend-wasker = { path = "../synth-backend-wasker", version = "0.14.0", optional = true }
synth-backend-riscv = { path = "../synth-backend-riscv", version = "0.14.0", optional = true }
synth-backend-awsm = { path = "../synth-backend-awsm", version = "0.15.0", optional = true }
synth-backend-wasker = { path = "../synth-backend-wasker", version = "0.15.0", optional = true }
synth-backend-riscv = { path = "../synth-backend-riscv", version = "0.15.0", optional = true }

# Optional verification (requires z3)
synth-verify = { path = "../synth-verify", version = "0.14.0", optional = true, features = ["z3-solver", "arm"] }
synth-verify = { path = "../synth-verify", version = "0.15.0", optional = true, features = ["z3-solver", "arm"] }

# Optional PulseEngine WASM optimizer
# Uncomment when loom crate is available:
Expand Down
33 changes: 17 additions & 16 deletions crates/synth-cli/tests/frozen_codegen_bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ fn text_sha256(wasm: &str, backend: &str, target: &str) -> (String, usize) {
let out = Command::new(synth())
.env_remove("SYNTH_NO_CMP_SELECT_FUSE")
.env_remove("SYNTH_NO_LOCAL_PROMOTE")
.env_remove("SYNTH_NO_IMM_SHIFT_FOLD")
.env_remove("SYNTH_CONST_CSE")
.args([
"compile",
Expand Down Expand Up @@ -129,33 +130,33 @@ fn assert_frozen(cases: &[(&str, &str, usize)], backend: &str, target: &str) {
/// the `.py` differentials cover): control_step ↔ 0x00210A55, flight_seam_flat ↔
/// flat+inlined flight_algo 0x07FDF307, plus flight_seam and the div seam.
///
/// Goldens RE-FROZEN for v0.14.0 (#390): local promotion is now default-on (on top
/// of v0.13.0 cmp→select), so these lock the PROMOTED+FUSED .text. The execution
/// RESULTS are preserved — re-verified on this commit with both default-on:
/// control_step still 0x00210A55 (control_step_differential.py 13/13), flat+inlined
/// flight_algo still 0x07FDF307 (flight_seam_differential.py MATCH). .text shrank
/// again (stack spill/reloads eliminated): control_step 324→316, flight_seam
/// 902→866, flight_seam_flat 1122→1006 (−154 B total); signed_div_const (no
/// promotable i32 locals) unchanged. gale G474RE DWT: gust_mix 58→50 cyc/call
/// (−14%), 5→0 [sp] traffic. Prior cmp→select-only goldens were on main @ 377b93e
/// (v0.13.0), 2026-06-24.
/// Goldens RE-FROZEN for v0.15.0 (#390): immediate-shift folding is now default-on
/// (on top of v0.13.0 cmp→select + v0.14.0 local promotion), so these lock the
/// folded+promoted+fused .text. The execution RESULTS are preserved — re-verified
/// on this commit: control_step still 0x00210A55 (control_step_differential.py
/// 13/13), flat+inlined flight_algo still 0x07FDF307 (flight_seam_differential.py
/// MATCH). .text shrank again (constant shift-amount `movw`s removed): control_step
/// 316→304, flight_seam 866→774, flight_seam_flat 1006→910 (−200 B total);
/// signed_div_const (no register-shift folds) unchanged. Measured −2 cyc/call on the
/// dissolved hot path (.text 100→90 B on gust_mix). Prior promotion goldens were on
/// main @ 6b46f09 (v0.14.0), 2026-06-24.
#[test]
fn frozen_fixtures_text_is_bit_identical_oracle_001() {
let cases = [
(
"control_step.wasm",
"cd929e7d91a8f7aad93f0e1cf0c93ecf3ccc6584ee94fb32e68d591134ed1410",
316usize,
"1a97711cfb4754794a8577814388f08b81eff444edcba3de7d3e3d18ff435183",
304usize,
),
(
"flight_seam.wasm",
"52b19365e32bcd9d5a4be74565d0fa467517eb3a07648a3e1ccd0a67556c1948",
866,
"9e73eea3867ba085820329951e84a7d650c38a7fc78d9d03a6a83d02963f9670",
774,
),
(
"flight_seam_flat.wasm",
"fa019f18cbc93869fff51630c6ab9cff6c4e052e22d783fe741b663ece49fa1e",
1006,
"887ea546429a4569112147fdc94b0ba90f02a6ccd2b511aa2ca48dab017dbc2c",
910,
),
(
"signed_div_const.wasm",
Expand Down
2 changes: 1 addition & 1 deletion crates/synth-frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ description = "WASM/WAT parser and module decoder frontend for the Synth compile
# Internal path deps carry an explicit version so `cargo publish`
# can rewrite to the crates.io coordinate. `path` is used for
# in-workspace builds; `version` is what crates.io sees.
synth-core = { path = "../synth-core", version = "0.14.0" }
synth-core = { path = "../synth-core", version = "0.15.0" }

wasmparser.workspace = true
wasm-encoder.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/synth-opt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ categories.workspace = true
description = "Peephole optimization passes for the Synth compiler"

[dependencies]
synth-cfg = { path = "../synth-cfg", version = "0.14.0" }
synth-cfg = { path = "../synth-cfg", version = "0.15.0" }

[dev-dependencies]
criterion = { version = "0.8", features = ["html_reports"] }
Expand Down
6 changes: 3 additions & 3 deletions crates/synth-synthesis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ categories.workspace = true
description = "WASM-to-ARM instruction selection and peephole optimizer"

[dependencies]
synth-core = { path = "../synth-core", version = "0.14.0" }
synth-cfg = { path = "../synth-cfg", version = "0.14.0" }
synth-opt = { path = "../synth-opt", version = "0.14.0" }
synth-core = { path = "../synth-core", version = "0.15.0" }
synth-cfg = { path = "../synth-cfg", version = "0.15.0" }
synth-opt = { path = "../synth-opt", version = "0.15.0" }
serde.workspace = true
anyhow.workspace = true
thiserror.workspace = true
Expand Down
8 changes: 4 additions & 4 deletions crates/synth-verify/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ arm = ["synth-synthesis"]

[dependencies]
# Core dependencies (always required)
synth-core = { path = "../synth-core", version = "0.14.0" }
synth-cfg = { path = "../synth-cfg", version = "0.14.0" }
synth-opt = { path = "../synth-opt", version = "0.14.0" }
synth-core = { path = "../synth-core", version = "0.15.0" }
synth-cfg = { path = "../synth-cfg", version = "0.15.0" }
synth-opt = { path = "../synth-opt", version = "0.15.0" }

# ARM synthesis (optional, behind 'arm' feature)
synth-synthesis = { path = "../synth-synthesis", version = "0.14.0", optional = true }
synth-synthesis = { path = "../synth-synthesis", version = "0.15.0", optional = true }

# SMT solver for formal verification
z3 = { version = "0.19", features = ["static-link-z3"], optional = true }
Expand Down
Loading