Skip to content
cccskills
FreePublished on GitHub

optimizer-debug

Debug the Wado optimizer with the WADO_TRACE, WADO_DUMP_PASS_BEFORE/AFTER, WADO_LIST_PASSES, and WADO_SKIP_PASS env vars. Use whenever a NIR/WIR pass is in question — wrong code, a WIR pipeline ICE, or just to see what a pass did to the IR. For guest-side slowness see wado-performance.

The English summary is being prepared. Showing the original description.

wado-lang/wado119 stars(GitHub 119 + cccskills 0)

Run score Not tested

See how to install

Files included (1)

  • SKILL.md10.0 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Optimizer pass debugging

The optimize.rs and wir_optimize.rs pipelines are big — many passes, each rewriting the IR in place. When a pass is wrong, the symptom usually shows up two passes later as "the WIR validates but produces the wrong behaviour" or as "the codegen finds an invalid Wasm module" deep in the final emitter. The debug hooks below let you diff the IR around any single pass without sprinkling eprintln! through pass internals.

All three are env-var-driven so they work uniformly across wado compile, wado test, wado run, and Kiln invocations from package-gale.

Quick recipes

Which pass changed the IR?

WADO_LIST_PASSES=1 cargo run --bin wado --quiet -- compile -O1 file.wado -o scratchpad/out.wasm 2>&1 | grep '\[pass\]'

Prints every pass name in execution order. Lets you correlate the order in source (optimize.rs and wir_optimize.rs) with what actually fires under your -Ox choice.

What does pass X produce?

WADO_DUMP_PASS_AFTER=wir/sroa_multi_value_returns \
  cargo run --bin wado --quiet -- compile -O1 file.wado -o scratchpad/out.wasm 2>scratchpad/after.log

scratchpad/after.log holds the full WIR (or NIR, depending on the pass) right after the named pass, framed by === WIR after <name> === / === end WIR after <name> === (NIR passes use === NIR after <name> ===).

What does pass X consume?

WADO_DUMP_PASS_BEFORE=wir/sroa_multi_value_returns \
  cargo run --bin wado --quiet -- compile -O1 file.wado -o scratchpad/out.wasm 2>scratchpad/before.log

Same framing, before the pass runs. diff the two logs to see exactly what the pass rewrote.

Multiple passes at once

The variables accept a comma-separated list:

WADO_DUMP_PASS_AFTER=nir/inline,wir/sroa_multi_value_returns \
  cargo run --bin wado --quiet -- compile -O1 file.wado -o scratchpad/out.wasm 2>scratchpad/dump.log

Bisect by skipping a pass

WADO_SKIP_PASS=nir/cse cargo run --bin wado --quiet -- compile -O3 file.wado -o scratchpad/out.wasm

Same comma-separated list as WADO_DUMP_PASS_*, with one extra convenience: a @N suffix targets the Nth invocation of a pass within the fixed-point loop (1-based). WADO_SKIP_PASS=nir/cse@2 skips cse only on the second iteration — invaluable for iteration-dependent bugs whose failing test passes on the first iteration and only diverges once the inliner expands an additional function on a later one.

Only standalone run_pass spans are skippable; confirm a name with WADO_LIST_PASSES=1 first. Local rules folded into the peephole session (ref_elim, elide_box_local, match_to_switch, value_copy_elide, array_literal, …) are not individually addressable — target nir/peephole to skip the whole session.

When a pass is the only one whose skipping makes the bug go away that just narrows the participants in the buggy interaction — it does not prove the pass is itself buggy. Pair the skip-bisection result with WADO_DUMP_PASS_AFTER on the same pass to compare its output across the working vs. broken configuration before concluding.

Trace pass-internal decisions

For developer-only messages from inside a pass. The crate denies eprintln!, so use compiler_trace!, which writes to the sink the host installed:

WADO_TRACE=sroa_return cargo run --bin wado --quiet -- compile -O1 file.wado -o scratchpad/out.wasm 2>&1 | grep '\[sroa_return\]'

Output is framed [target] message. Targets are passed verbatim to compiler_trace!(target, ...) calls inside the compiler. Use WADO_TRACE='*' to enable every target at once.

To add a new tracing call inside a pass:

use crate::compiler_trace;
// ...
compiler_trace!("sroa_return", "candidates = {}", candidates.len());
compiler_trace!("sroa_return", "rewriting return at {span:?}");

The cost when the target is disabled is one OnceLock get + a linear scan of the configured target list — fine for any rate that makes sense in a compiler pass.

Workflow for a fixed-point loop that never converges

--log-level debug ends the NIR loop with either "converged after N iteration(s)" or "hit the N-iteration cap without converging", the latter naming the passes still reporting changes. From there:

  1. WADO_TRACE=opt_loop lists, per iteration, every pass that reported a change. The tail of that list is the culprit set.
  2. WADO_TRACE=const_fold names each function const_fold changed, so a pass that keeps reporting a change points at the body it keeps rewriting. WADO_TRACE=inline_sites names, per round, the callees spliced into each caller: a late round splicing small helpers is a callee that only shrank under the threshold once its own callees were spliced.
  3. WADO_DUMP_PASS_BEFORE/_AFTER=<pass> around a late round, diffed, says which of three it is: nothing rewritten at all (the pass reports a change it did not make), a rewrite a later pass deletes (two passes fighting), or real work that tapers (the pass takes one step per round where its own fixed point is one sweep away).

Workflow for output that is far larger than the level below it

-O3 emitting several times -O2's wasm is the inliner, not the loop — check the iteration count first (WADO_TRACE=opt_loop) and stop suspecting convergence once it is small.

  1. Sweep --optimize-inline-threshold. A cliff rather than a curve means one callee crossed the budget and is now copied at every call site.
  2. WADO_TRACE=inline reports, per round, the unit size and how many candidates the threshold admitted only because the cold discount put them under it — with what those are worth in growth. A handful of callees accounting for most of a round's growth is that pattern. WADO_TRACE=inline_verdict gives each callee's verdict: its hot cost, its call sites, and which of them it reaches.
  3. WADO_TRACE=cold_outline says why such a callee was not split: control leaving the region, or a local the call cannot hand over.
  4. --optimize-inline-growth <pct> caps unit growth, and --log-level debug then names what the cap turned down, with both of the callee's prices.

Workflow for an optimization that stopped firing

A wir_expect that disappears when an unrelated knob moves is a precision hole somewhere else: one pass reshaped the IR into a form the second pass does not recognise, and the second pass is the one to fix.

  1. Bisect on the knob, not the source — --optimize-inline-threshold one step at a time until the expectation flips. One step is one callee, so the before/after dump --nir diff is small enough to read.
  2. WADO_TRACE=<pass> for the pass that stopped firing. const_object_globalization names each function it walks and, per let, either the hoist or the check that declined it.
  3. Read the declined shape in the NIR. A shape that is accepted in one syntactic position and rejected in another — &x as a call argument versus &x bound by a let — is the hole; the inliner just moved it from the first to the second.

Workflow for a "WIR pipeline generated invalid core Wasm module" ICE

The codegen-time validator catches type mismatches the optimizer introduced. The error always points at codegen, but the bug is upstream. Walk the pass pipeline like this:

  1. Get the failing fixture compiling at -O0 first to confirm it is an optimization-introduced bug (not a lower/codegen bug).

  2. List the passes that run at the failing -Ox level:

    WADO_LIST_PASSES=1 cargo run --bin wado --quiet -- compile -O1 fixture.wado -o scratchpad/out.wasm 2>&1 | grep '\[pass\]'
    
  3. Bisect: pick a pass roughly mid-pipeline, dump after it, and check whether the IR is already broken. If it is, the bug is at or before that pass; otherwise it is later.

  4. Once you have the suspect pass, dump before AND after it and read the diff. The mismatch will be visible — usually a function whose signature was rewritten but whose return sites weren't (the canonical shape of the SROA / signature-rewrite class of bugs), or a struct layout that changed in one place but not at consumers.

  5. Add compiler_trace!("<pass_name>", ...) calls at the suspect rewrite site to confirm which subtrees the pass visits. The *_mut walkers in wir_visitor.rs and WirInstr::for_each_boxed_child_mut cover most rewrite needs.

Pass-name conventions

PrefixPhase
nir/<name>NIR-level pass (optimize.rs)
wir/<name>WIR-level pass (wir_optimize.rs)

A #![wasm_module] core module (the allocator, mem) runs the WIR list as a package of its own, under wir/<module>:<name> — so wir/run_peephole stays the main module's and wir/mem:run_peephole targets the allocator.

WADO_LIST_PASSES=1 is the source of truth — names there match exactly the strings the env vars want.

When to NOT reach for these

  • For runtime bugs (program compiles cleanly but produces wrong output), use the debugger skill (rust-gdb) or read the WIR/Wasm directly.
  • For LSP / annotate-time issues, these hooks fire only during the optimization phase. Add tracing calls in annotate.rs directly.
  • For monomorphization or lowering issues, dump the pre-optimize IR with wado dump --tir-resolved / --tir-monomorphized (TIR, before lowering) or --nir-lowered (NIR, right after lowering) instead; those are exposed as proper CLI flags.

See also

  • wado-compiler/src/trace.rs — compiler_trace! macro and filter parsing (with unit tests).
  • wado-compiler/src/optimize.rs — run_pass for NIR passes; defines the env-var hook implementation in mod pass_dump.
  • wado-compiler/src/wir_optimize.rs — wir_pass for WIR passes.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Measure Wado's performance or the size of the Wasm it generates, and refresh the benchmark/ and wasm-size/ README files. Use for any benchmarking or size-measurement request, whichever benchmark it names.

The English summary is being prepared. Showing the original description.

wado-lang/wado119Updated Oct 26, 2026

Measure how long GitHub Actions jobs and steps took across past runs, and find the pull request that made CI slower. Use for any question about CI duration, a slow job, or when CI got slow.

The English summary is being prepared. Showing the original description.

wado-lang/wado119Updated Oct 26, 2026

How to answer code review feedback — a human reviewer, CodeRabbit, or any review bot. Verify the findings, report the classes they are instances of to the user before fixing, then fix the class rather than the sites named. Put design decisions to the user, then run /distill. Invoke when responding to review comments on a pull request, or to the findings of a /code-review run.

The English summary is being prepared. Showing the original description.

wado-lang/wado119Updated Oct 26, 2026

Investigate and improve code coverage for the wado-compiler crate. Use for any question about what the tests do and do not reach, dead code included.

The English summary is being prepared. Showing the original description.

wado-lang/wado119Updated Oct 26, 2026

cr

Free

Review the branch with /code-review at high, then answer the findings with /code-review-response.

The English summary is being prepared. Showing the original description.

wado-lang/wado119Updated Oct 26, 2026

Use rust-gdb to inspect variables and step through code without modifying it. Prefer it over print debugging for any investigation into what the compiler is actually doing (lldb is unavailable on Claude Code Web).

The English summary is being prepared. Showing the original description.

wado-lang/wado119Updated Oct 26, 2026

See all skills by wado-lang

Report a problem with this skill