Skip to content

No longer call wasm-opt with --opt 1#2238

Draft
vouillon wants to merge 6 commits into
masterfrom
fast-o1
Draft

No longer call wasm-opt with --opt 1#2238
vouillon wants to merge 6 commits into
masterfrom
fast-o1

Conversation

@vouillon

@vouillon vouillon commented May 11, 2026

Copy link
Copy Markdown
Member

We skip calling wasm-opt with --opt 1. Expected benefits:

  • lower compilation time
  • improved debugging experience:
    • variable names are preserved
    • events are not reordered

Instead, we perform a few optimizations to reduce the code size and avoid having too many variables per function:

  • sink local.set
  • coalesce local variables with disjoint lifetime
  • reorder locals

The wasm_of_ocaml compilation time for the test suite is reduced by about 60%. On the CI, dune build @runtest-wasm from 2m 19s down to 1m 40s (-28%).

Speedup on ocaml and PRT:

binary wall-clock time user time
ocamlc -32% -70%
PRT -26% -53%

vouillon added 6 commits June 4, 2026 14:54
Restructure [link_and_optimize] as a [link] producer followed by a list
of transformer passes ([dce], [optimize]), threaded through an explicit
[step_io] (wasm file + optional sourcemap). A small runner sends every
pass but the last to a fresh temp file, and the last straight to
[output_file] / [opt_sourcemap_file].

Pure refactoring: the same passes run in the same order for every
profile, so the output is unchanged. The point is that whichever pass
ends the pipeline now writes the final file itself, so a later change
that drops [optimize] at --opt 1 needs no copy to land the link's or
DCE's output at the right path.
At --opt 1, drop the [optimize] (wasm-opt) pass from the pipeline, both
in whole-program linking (remove it from the transformer list) and in
separate compilation (write the unit's wasm directly instead of routing
it through [Binaryen.optimize]). Our own passes handle what wasm-opt
would have done; at --opt 2/3 wasm-opt still runs.
Add a [Var_coalescing] pass: a linear-scan variable coalescing pass for
the Wasm backend, mirroring [Js_variable_coalescing]. When --opt 1 skips
Binaryen we need our own pass to reuse locals; at --opt 2/3 it is off and
Binaryen still handles coalescing.

Runs in [post_process_function_body]; gated on [O1] and the new
[wasm-var-coalescing] flag.
Add a [Local_sink] pass that rewrites

    local.set x e
    ...
    local.get x

into

    ...
    local.tee x e

when the sink is sound: we must not cross another write to [x], and
moving [e] past intermediate code must not reorder observable effects.

Because [Local_sink] always introduces [local.tee] (never inlines [e]
itself), some of the tees it produces are immediately dead: the variable
is never read again. Extend [Var_coalescing] to detect such dead tees
using the liveness it already computes — a [LocalTee x _] Def node whose
successors do not list [x] in their [live_in] is a dead store. Replace it
with its inner expression, and drop the local from the function's
[locals] list when its last reference was the tee we just erased.

Runs first in [post_process_function_body], before [Var_coalescing];
gated on [O1] and the new [wasm-local-sink] flag.
Wasm encodes local indices as LEB128 u32 (1 byte for < 128, 2 above).
With [wasm-opt] skipped at --opt 1, hot locals routinely cross the 128
boundary and pay an extra byte per access.

Add a [Reorder_locals] pass that sorts non-parameter locals into a
numeric block followed by a reference block, with same-type runs ordered
by descending total use count and individual locals inside a run by
descending per-local count. Indices are derived from list position, so
reordering [locals] alone suffices.

Runs at the end of [post_process_function_body], after
[Initialize_locals.f]; gated on [O1] and the new [wasm-reorder-locals]
flag.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant