Skip to content

wasm-opt: add crate with a local cleanup pass - #284

Merged
rvcas merged 1 commit into
mainfrom
rvcas/wasm-opt-local-cleanup
Oct 28, 2026
Merged

rvcas merged 1 commit into
mainfrom
rvcas/wasm-opt-local-cleanup

Conversation

@rvcas

@rvcas rvcas commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds starstream-wasm-opt, a Wasm-to-Wasm pass over the core module starstream-to-wasm emits. Neo-Wasm proves an execution by single-stepping it, so every executed operator is one trace row, and every guest-to-guest call adds a row per declared local of the callee to zero it. The passes here aim to execute fewer operators and declare fewer locals.

It runs at the end of Compiler::finish, before componentization, so the published bytes (and the contract id hashed from them) are the optimized ones. CompileOptions::optimize turns it off; it defaults to on, and the CLI turns it on.

The first pass cleans up how values move through locals. Codegen often stores a value in a local only to read it right back. The rules, applied until none applies:

  1. local.set N; local.get N → local.tee N
  2. local.tee N; drop → local.set N
  3. local.tee N, where N is never read → nothing
  4. local.set N, where N is never read → drop
  5. local.get, global.get or a constant, then drop → nothing

Declared locals no instruction refers to anymore are then removed and the rest renumbered. Parameters are never removed. Every other section, and every body the pass leaves alone, is copied byte for byte. Neo-Wasm already supports local.tee.

Breaking: CompileOptions gains a public optimize field, so struct literals need it.

Results

From the trace-row census (starstream-proving-runtime/tests/trace_rows.rs), main vs this branch:

main this PR
total rows 5,890 5,854
padded rows 6,000 5,952
Wasm operator rows 708 674
call-frame rows 83 81

Across the 17 changed codegen snapshots, the net static change is local.get −56, local.set −69, local.tee +13, drop +2, global.get −9, i32.const −2. I checked with a script that nothing else changed in those diffs.

Testing

  • New unit tests in starstream-wasm-opt for each rule, renumbering after parameters, and byte-for-byte copying of the other sections (memory, globals, exports, data, the component-type custom section). Each test also validates the output and checks the pass is idempotent.
  • The starstream-to-wasm tests pass, including the overflow and division tests, which run the optimized modules.
  • The starstream-proving-runtime, starstream-runtime, starstream-ledger and starstream-cli tests pass.

Add `starstream-wasm-opt`, a Wasm-to-Wasm pass over the core module
`starstream-to-wasm` emits, run at the end of `Compiler::finish` unless
`CompileOptions::optimize` is off.

Its first pass removes redundant local traffic: set-then-get becomes a
tee, stores to never-read locals become drops, pushes that are
immediately dropped go away, and declared locals nothing refers to are
removed and the rest renumbered. Every other section, and every body
the pass leaves alone, is copied byte for byte.

Signed-off-by: rvcas <x@rvcas.dev>
@rvcas
rvcas merged commit 88f2a2c into main Oct 28, 2026
5 checks passed
@rvcas
rvcas deleted the rvcas/wasm-opt-local-cleanup branch October 28, 2026 15:58

@SpaceManiac SpaceManiac left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The location it's inserted into starstream-to-wasm looks good. I didn't spot any mistakes in the changed snapshots.

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.

2 participants