Skip to content

wasmi majit tier: integrate patched wasmi + TLS elimination (#352)#442

Merged
youknowone merged 6 commits into
mainfrom
wasmi
Jul 9, 2026
Merged

wasmi majit tier: integrate patched wasmi + TLS elimination (#352)#442
youknowone merged 6 commits into
mainfrom
wasmi

Conversation

@youknowone

@youknowone youknowone commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

Integrate our patched wasmi (in-tree wasmi/ workspace, branch try-majit) with the majit meta-tracing JIT tier into the pyre wasm runner, resolving the blockers identified in #352.

Changes

pyre-wasm-runner: wasmi integration

  • Switch wasmi dependency from crates.io 1.1 to in-tree patched wasmi with majit-jit feature
  • Adapt wasmi_host.rs to wasmi 2.x API (table-based trace dispatch, Nullable import)
  • Exclude wasmi/ nested workspace from the root Cargo.toml

check.py: benchmark tooling

  • Add --wasm-bench / --wasm-bench-reps flags for comparing wasm execution across engines (wasmtime, wasmi stock, wasmi+majit)

majit: has_expanded_tail false positive fix

  • Fix initialize_virtualizable in pyjitpl.rs: state-field JIT's scalar state fields were misidentified as expanded vable tail, preventing additional state field additions

wasmi tier status (try-majit branch)

Metric Before After
TLS in hot path 73% 0%
int_loop majit time 2.3s 1.75s
Tests 153/155 pass (1 pre-existing)

#352 blockers resolved

  1. ✅ Prepass op-coverage gap — all mentioned ops implemented
  2. ✅ Structured control flow — pyre traces use single back-edge (trace JIT linearizes)
  3. ✅ Trace-exit tail call — MINI_RETURN_BAIL
  4. ✅ Memory-bound optimization — mem_base/mem_len/mem_trap/mem_did_store as register-promoted state fields
  5. ✅ Measurement infrastructure — WASMI_MAJIT_STATS=1

Closes #352

commented by Claude

Summary by CodeRabbit

  • New Features

    • Added a command-line check that runs WebAssembly benchmarks across multiple engines, compares results, and reports speed and correctness.
  • Bug Fixes

    • Improved JIT heap-read handling to avoid an unsafe fast path in cases involving identity-related state, reducing the risk of incorrect behavior.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 766f7129-12ef-45d8-917c-645946eadfaf

📥 Commits

Reviewing files that changed from the base of the PR and between ae65875 and 1954b2e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • majit/majit-metainterp/src/pyjitpl.rs
  • pyre/wasm_check.py

Walkthrough

This PR makes two unrelated changes: it tightens the JIT's expanded-tail heap-read decision to avoid aliasing when identity-ref state fields are present, and adds a new pyre/wasm_check.py script that benchmarks pyre's wasm output across wasmtime and wasmi engines, checking correctness and reporting timing/speedup.

Changes

Expanded-tail aliasing guard

Layer / File(s) Summary
has_expanded_tail identity-ref check
majit/majit-metainterp/src/pyjitpl.rs
Adds info.identity_ref_bank_index.is_none() to the has_expanded_tail condition, forcing the heap-read path when identity-ref scalar state fields are present.

Cross-engine wasm benchmark/correctness script

Layer / File(s) Summary
Script setup and runner execution
pyre/wasm_check.py
Adds configuration, engine matrix, ANSI color helpers, and run_timed for subprocess execution with timeout and user-CPU tracking.
Runner discovery and benchmark collection
pyre/wasm_check.py
Adds find_runner to locate pyre-wasm-runner and collect_benches to glob/filter benchmark scripts.
Benchmark loop, correctness check, and result table
pyre/wasm_check.py
Implements main, running each benchmark across engines, comparing stdout correctness, printing a results table with timings/speedup, and setting the exit code.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant wasm_check
  participant pyre_wasm_runner
  participant Engines as wasmtime/wasmi engines

  User->>wasm_check: run wasm_check.py [filters] [--reps] [--timeout]
  wasm_check->>wasm_check: find_runner()
  wasm_check->>wasm_check: collect_benches(filters)
  loop for each benchmark
    loop for each engine
      wasm_check->>pyre_wasm_runner: run_timed(bench, engine env, timeout)
      pyre_wasm_runner->>Engines: execute benchmark
      Engines-->>pyre_wasm_runner: stdout, cpu_time, returncode
      pyre_wasm_runner-->>wasm_check: best result per engine
    end
    wasm_check->>wasm_check: compare stdout across engines, mark correctness
    wasm_check->>User: print result row (timings, speedup, status)
  end
  wasm_check->>User: exit(1 if any incorrect else 0)
Loading

Poem

A tiny guard for tags gone astray,
keeps identity refs out of the fray. 🐇
Then off to the wasm track I hop,
timing engines till the best ones top.
Green means go, red means CRASH —
this rabbit counts every cycle-cash! ⏱️🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The diff does not show the scoped wasmi prepass/kernel op-coverage changes required by #352; it mainly touches adjacent majit/pyre infrastructure. Add the missing nested wasmi/ crates/wasmi/src/engine/executor/handler/majit/{prepass,kernel}.rs work for op coverage, control flow, and stats.
Out of Scope Changes check ⚠️ Warning The PR includes broad majit/pyre interpreter, tracing, and benchmark changes beyond the focused wasmi tier coverage work in #352. Split unrelated runtime, trace, and benchmark edits into separate PRs or trim them so this change stays focused on the #352 wasmi tier work.
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: integrating the patched wasmi tier with majit and removing TLS hot-path usage.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wasmi

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 45fa6bc749

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pyre/pyre-wasm-runner/Cargo.toml Outdated
# in-tree patched wasmi (nested workspace) with the `majit-jit` meta-tracing
# tier so the runner can execute the pyre module under the accelerated
# interpreter; the tier is on by default and toggled off with `WASMI_NO_MAJIT=1`.
wasmi = { path = "../../wasmi/crates/wasmi", features = ["majit-jit"] }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the wasmi workspace required by this dependency

This manifest now path-depends on ../../wasmi/crates/wasmi, but the reviewed commit does not add a wasmi/ directory or a .gitmodules entry, so a normal checkout has no package at that path. Any workflow that builds the wasm runner, including pyre/check.py --backend wasm and cargo build -p pyre-wasm-runner, will fail during dependency resolution before compiling; please vendor/add the nested workspace or point at a dependency that exists in the repo.

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit a631c67).

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

None.

3. Pre-existing mismatches (already present before this patch)

  • majit/majit-metainterp/src/pyjitpl.rs:2916 ↔ rpython/jit/metainterp/pyjitpl.py:3330: Rust returns on if total_vable == 0 { return; }; PyPy still appends the virtualizable identity with self.virtualizable_boxes.append(virtualizable_box) even when vinfo.read_boxes(...) returns an empty list.

  • majit/majit-metainterp/src/pyjitpl.rs:2920 ↔ rpython/jit/metainterp/pyjitpl.py:3326: Rust can return when there is neither an expanded live tail nor vable_ptr; PyPy’s only gate is if vinfo is not None, then it calls vinfo.read_boxes(...).

  • majit/majit-metainterp/src/pyjitpl.rs:2969 ↔ rpython/jit/metainterp/pyjitpl.py:3322: Rust enters the virtualizable read path without an equivalent of PyPy’s prior vinfo.clear_vable_token(virtualizable). I found no actual clear_vable_token(...) call in this initialization path.

  • majit/majit-metainterp/src/pyjitpl.rs:2983 ↔ rpython/jit/metainterp/pyjitpl.py:3326: Rust may reuse live_values[num_reds..num_reds + total_vable] as the virtualizable fields; PyPy always materializes them through vinfo.read_boxes(self.cpu, virtualizable, startindex).

4. Structural adaptations

  • majit/majit-metainterp/src/pyjitpl.rs:2961 ↔ rpython/jit/metainterp/pyjitpl.py:3326: The patch adds info.identity_ref_bank_index.is_none() before using the expanded-tail path. PyPy has no ref-bank/state-field split and always uses vinfo.read_boxes(...); this Rust-only guard is a structural adaptation for Pyre’s state-field JIT and improves parity by avoiding scalar state slots being mistaken for virtualizable boxes.

  • majit/majit-metainterp/src/pyjitpl.rs:2937 ↔ rpython/jit/metainterp/pyjitpl.py:3319: Rust mints virtualizable_box from OpRef::input_arg_ref(box_ref_index); PyPy uses virtualizable_box = original_boxes[index]. This reflects Pyre’s separate register-bank representation.

  • majit/majit-metainterp/src/pyjitpl.rs:2947 ↔ rpython/jit/metainterp/pyjitpl.py:3320: Rust may take the concrete virtualizable value from self.vable_ptr; PyPy unwraps virtualizable_box. This is the same state-field/heap-pointer adaptation.

  • majit/majit-metainterp/src/pyjitpl.rs:2988 ↔ rpython/jit/metainterp/virtualizable.py:92: Rust records fresh input args and stores initial constants when reading from heap; PyPy’s wrap(cpu, x, startindex) creates boxes directly. This is an implementation-language/IR-recording adaptation, with the same intended box ordering.

  • majit/majit-metainterp/src/pyjitpl.rs:3018 ↔ rpython/jit/metainterp/pyjitpl.py:3331: Rust does not directly call check_synchronized_virtualizable() here and documents that callers perform the check elsewhere; PyPy calls it immediately. This is debug-check placement, not translated runtime semantics.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 655419cec4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pyre/check.py Outdated
WASM_BENCH_CONFIGS = [
("wasmtime", "wasmtime", None),
("wasmi-stock", "wasmi", "1"),
("wasmi+majit", "wasmi", None),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compare a real majit-enabled wasmi build

Fresh evidence in the current tree is that this wasmi+majit row does not enable a different engine: pyre/pyre-wasm-runner/Cargo.toml still depends on crates.io wasmi = "1.1", and a repo-wide search finds no majit-jit feature/path dependency or wasmi/ workspace. As a result, --wasm-bench reports a majit speedup by comparing two invocations of the same stock wasmi binary (WASMI_NO_MAJIT is ignored by that dependency), which can corrupt the performance data this flag was added to collect; wire the patched dependency/feature before advertising this row.

Useful? React with 👍 / 👎.

Comment thread pyre/check.py Outdated
else:
spd = "-"
correct = green("yes") if ok else red("NO")
any_wrong = any_wrong or not ok

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fail wasm-bench when an engine crashes

When any engine returns nonzero or times out but at least one other engine succeeds, the correctness loop skips the failed output and this line keeps any_wrong driven only by output mismatches, so --wasm-bench can print CRASH(...)/TIMEOUT yet still exit 0. That makes the benchmark pass in exactly the failure modes it should surface; include nonzero return codes in the failure condition.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe6ecd8e97

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pyre/wasm_check.py
ENGINES = [
("wasmtime", "wasmtime", None),
("wasmi-stock", "wasmi", "1"),
("wasmi+majit", "wasmi", None),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compare a real majit-enabled wasmi engine

Fresh evidence is that the new standalone script reintroduces the wasmi+majit row, but it still launches the same runner with PYRE_WASM_ENGINE=wasmi and only removes WASMI_NO_MAJIT; I checked pyre/pyre-wasm-runner/Cargo.toml:23 and a repo-wide search for majit-jit/WASMI_NO_MAJIT, and the runner depends on stock crates.io wasmi = "1.1", which has no majit tier to toggle. As a result, the reported majit spd compares two stock wasmi runs and can publish meaningless performance data until this row is wired to a patched dependency/feature.

Useful? React with 👍 / 👎.

Comment thread pyre/wasm_check.py
else:
spd = "-"
correct = green("yes") if ok else red("NO")
any_wrong = any_wrong or not ok

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fail when any compared engine crashes

Fresh evidence is that this failure mode now exists in pyre/wasm_check.py: the correctness loop skips engines with nonzero status, and this line only records stdout mismatches among successful engines. If wasmi+majit crashes or times out while wasmtime succeeds, the table prints CRASH(...)/TIMEOUT but any_wrong stays false and the script exits 0, so benchmark automation can pass exactly the engine failures this tool is meant to catch.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@majit/majit-macros/src/jit_interp/jitcode_lower/lower_value.rs`:
- Around line 1466-1509: The fallback in lower_native_int_binop_call is masking
an invariant violation: once the call is recognized as a native int binop alias
and both operands have been lowered, returning None causes lower_call_value to
lower lhs and rhs again, duplicating any side effects. Tighten this path by
treating the non-int operand case as unreachable for this recognized alias and
fail fast instead of falling back, keeping the behavior localized to
lower_native_int_binop_call and its interaction with lower_call_value.

In `@majit/majit-metainterp/src/lib.rs`:
- Around line 169-270: The boolean env-var helpers are duplicated across many
near-identical functions, so refactor them into a shared macro while preserving
the current caching behavior. Introduce a small `macro_rules!` near the existing
`closedbg_enabled`, `bh_debug_enabled`, `nbody_debug_enabled`,
`mptrace_enabled`, `pcseq_enabled`, `tldbg_enabled`, `heapdbg_enabled`,
`diag_enabled`, `log_jtet_enabled`, `smallir_enabled`, `log_opt_enabled`,
`bridge_debug_enabled`, and `no_unroll_enabled` symbols, and use it to generate
each `OnceLock<bool>` helper from just the function name and env-var string.
Keep `original_boxes_enabled`, `stall_window`, and `step_limit` as-is unless
they can also be cleanly expressed through the same pattern.

In `@majit/majit-translate/src/annotator/bookkeeper.rs`:
- Around line 2170-2188: Add unit tests for the new enum-base bare-leaf redirect
in bookkeeper’s enum handling: exercise reg.is_enum_base with strip_generic_args
and canonical_struct_name to confirm two generic instantiations of the same enum
converge on the shared bare leaf/base at a phi merge, and also verify a
qualified non-generic enum path is not incorrectly redirected. Use the relevant
bookkeeper logic around seen, strip_generic_args, and canonical_struct_name so
the tests lock in the intended base selection behavior.

In `@pyre/pyre-jit-trace/src/jitcode_dispatch.rs`:
- Around line 1284-1291: The new FRAME_SHAPE_DECLINE_SEEN BTreeSet and the
covered HashSet in get_list_of_active_boxes introduce Rust-native dedup
containers that don’t obviously match the RPython/PyPy shape. Review these
locations in jitcode_dispatch.rs and either replace them with the closest
original data-structure pattern from the ported code or add a brief comment
citing the RPython source and explaining why the container is the minimal
borrow-checker/dedup deviation. Keep the change localized to
FRAME_SHAPE_DECLINE_SEEN and get_list_of_active_boxes/covered.

In `@pyre/wasm_check.py`:
- Around line 87-98: `collect_benches` only scans the top-level benchmark
directory, so the new `synth` benchmarks are missed. Update `collect_benches` in
`wasm_check.py` to search recursively from `BENCH_DIR` (or explicitly include
the `synth` subdirectory) while preserving the existing tmp_ exclusion and
filter-by-stem behavior, so the new benchmark files are returned along with the
existing ones.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c97272da-6101-4c37-847f-9c2f00e874c6

📥 Commits

Reviewing files that changed from the base of the PR and between a82ba81 and ae65875.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (88)
  • .github/workflows/pyre-ci.yml
  • Cargo.toml
  • majit/examples/spcount/Cargo.toml
  • majit/examples/spcount/src/main.rs
  • majit/majit-backend-cranelift/src/compiler.rs
  • majit/majit-backend-dynasm/src/aarch64/assembler.rs
  • majit/majit-backend-dynasm/src/runner.rs
  • majit/majit-backend-dynasm/src/x86/assembler.rs
  • majit/majit-backend-wasm/src/codegen.rs
  • majit/majit-backend-wasm/src/lib.rs
  • majit/majit-backend/src/model.rs
  • majit/majit-gc/src/collector.rs
  • majit/majit-gc/src/lib.rs
  • majit/majit-ir/src/eval_breaker.rs
  • majit/majit-ir/src/lib.rs
  • majit/majit-ir/src/resoperation.rs
  • majit/majit-macros/src/jit_interp/codegen_state.rs
  • majit/majit-macros/src/jit_interp/codegen_trace.rs
  • majit/majit-macros/src/jit_interp/jitcode_lower/lower_stmt.rs
  • majit/majit-macros/src/jit_interp/jitcode_lower/lower_value.rs
  • majit/majit-macros/src/jit_interp/jitcode_lower/mod.rs
  • majit/majit-macros/src/jit_interp/mod.rs
  • majit/majit-metainterp/src/blackhole.rs
  • majit/majit-metainterp/src/jit_state.rs
  • majit/majit-metainterp/src/jitcode/assembler.rs
  • majit/majit-metainterp/src/jitdriver.rs
  • majit/majit-metainterp/src/lib.rs
  • majit/majit-metainterp/src/optimizeopt/info.rs
  • majit/majit-metainterp/src/optimizeopt/mod.rs
  • majit/majit-metainterp/src/optimizeopt/optimizer.rs
  • majit/majit-metainterp/src/optimizeopt/rewrite.rs
  • majit/majit-metainterp/src/optimizeopt/unroll.rs
  • majit/majit-metainterp/src/optimizeopt/virtualstate.rs
  • majit/majit-metainterp/src/pyjitpl.rs
  • majit/majit-metainterp/src/pyjitpl/dispatch.rs
  • majit/majit-metainterp/src/resume.rs
  • majit/majit-translate/src/annotator/bookkeeper.rs
  • majit/majit-translate/src/annotator/unaryop.rs
  • majit/majit-translate/src/front/mir.rs
  • majit/majit-translate/src/front/option_closure_select.rs
  • majit/majit-translate/src/front/option_map_or.rs
  • majit/majit-translate/src/lib.rs
  • majit/majit-translate/src/local_crates.rs
  • majit/majit-translate/src/translator/rtyper/cutover.rs
  • majit/majit-translate/src/translator/rtyper/flowspace_adapter.rs
  • majit/majit-translate/src/translator/rtyper/lltypesystem/rordereddict.rs
  • majit/majit-translate/src/translator/rtyper/pyre_call_registry.rs
  • majit/majit-translate/src/translator/rtyper/rclass.rs
  • majit/majit-translate/src/translator/rtyper/rlist.rs
  • majit/majit-translate/src/translator/rtyper/rmodel.rs
  • majit/majit-translate/src/translator/rtyper/rtyper.rs
  • majit/majit-translate/tests/test_aheui_census.rs
  • pyre/bench/synth/foriter_in_while.py
  • pyre/bench/synth/kept_stack_deep_var_shortcircuit_mutate.py
  • pyre/bench/synth/nested_foriter_call.py
  • pyre/bench/synth/nested_foriter_poly.py
  • pyre/bench/synth/nested_foriter_range.py
  • pyre/pyre-interpreter/src/_pypy_generic_alias.rs
  • pyre/pyre-interpreter/src/baseobjspace.rs
  • pyre/pyre-interpreter/src/call.rs
  • pyre/pyre-interpreter/src/jit_fnaddr.rs
  • pyre/pyre-interpreter/src/module/_csv/mod.rs
  • pyre/pyre-interpreter/src/module/_socket/interp_socket.rs
  • pyre/pyre-interpreter/src/module/faulthandler/handler.rs
  • pyre/pyre-interpreter/src/module/signal/interp_signal.rs
  • pyre/pyre-interpreter/src/module/syslog/syslog.rs
  • pyre/pyre-interpreter/src/objspace/descroperation.rs
  • pyre/pyre-interpreter/src/pycode.rs
  • pyre/pyre-interpreter/src/pyframe.rs
  • pyre/pyre-interpreter/src/typedef.rs
  • pyre/pyre-jit-trace/src/helpers.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch.rs
  • pyre/pyre-jit-trace/src/state.rs
  • pyre/pyre-jit-trace/src/trace.rs
  • pyre/pyre-jit-trace/src/trace_opcode.rs
  • pyre/pyre-jit-trace/src/walker_frame_ops.rs
  • pyre/pyre-jit/src/eval.rs
  • pyre/pyre-jit/src/jit/codewriter.rs
  • pyre/pyre-object/src/celldict.rs
  • pyre/pyre-object/src/dictmultiobject.rs
  • pyre/pyre-object/src/identitydict.rs
  • pyre/pyre-object/src/intobject.rs
  • pyre/pyre-object/src/iterobject.rs
  • pyre/pyre-object/src/listobject.rs
  • pyre/pyre-object/src/pyobject.rs
  • pyre/pyre-object/src/sliceobject.rs
  • pyre/pyre-object/src/typeobject.rs
  • pyre/wasm_check.py

Comment thread majit/majit-macros/src/jit_interp/jitcode_lower/lower_value.rs
Comment on lines +169 to +270
// ── Cached diagnostic env-var helpers ────────────────────────────────
//
// Each env var is read once and cached via OnceLock so hot paths
// (back-edge, guard-failure, optimizer) never re-acquire the global
// env lock.

pub fn closedbg_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_CLOSEDBG").is_some())
}

pub fn bh_debug_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_BH_DEBUG").is_some())
}

pub fn nbody_debug_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("PYRE_NBODY_DEBUG").is_some())
}

pub fn mptrace_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_MPTRACE").is_some())
}

pub fn pcseq_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_PCSEQ").is_some())
}

pub fn tldbg_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_TLDBG").is_some())
}

pub fn heapdbg_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_HEAPDBG").is_some())
}

pub fn diag_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_DIAG").is_some())
}

pub fn log_jtet_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_LOG_JTET").is_some())
}

pub fn smallir_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_SMALLIR").is_some())
}

pub fn log_opt_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_LOG_OPT").is_some())
}

pub fn bridge_debug_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("MAJIT_BRIDGE_DEBUG").is_some())
}

pub fn no_unroll_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| std::env::var_os("PYRE_NO_UNROLL").is_some())
}

/// `PYRE_ORIGINAL_BOXES`: default true, only disabled by `0` or `false`.
pub fn original_boxes_enabled() -> bool {
static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*FLAG.get_or_init(|| match std::env::var_os("PYRE_ORIGINAL_BOXES") {
Some(v) => {
let v = v.to_string_lossy();
v != "0" && !v.eq_ignore_ascii_case("false")
}
None => true,
})
}

pub fn stall_window() -> u64 {
static VAL: std::sync::LazyLock<u64> = std::sync::LazyLock::new(|| {
std::env::var("MAJIT_STALL_WINDOW")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1_000_000)
});
*VAL
}

pub fn step_limit() -> u64 {
static VAL: std::sync::LazyLock<u64> = std::sync::LazyLock::new(|| {
std::env::var("MAJIT_STEP_LIMIT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(8_000_000)
});
*VAL
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Thirteen near-identical OnceLock boolean helpers — consolidate with a macro.

closedbg_enabled, bh_debug_enabled, nbody_debug_enabled, mptrace_enabled, pcseq_enabled, tldbg_enabled, heapdbg_enabled, diag_enabled, log_jtet_enabled, smallir_enabled, log_opt_enabled, bridge_debug_enabled, and no_unroll_enabled all repeat the identical static FLAG: OnceLock<bool> = ...; *FLAG.get_or_init(|| std::env::var_os("...").is_some()) pattern, differing only in the function/env-var name. A small macro_rules! would remove the duplication and make adding future flags a one-liner.

♻️ Proposed macro-based consolidation
+macro_rules! cached_env_flag {
+    ($name:ident, $var:literal) => {
+        pub fn $name() -> bool {
+            static FLAG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
+            *FLAG.get_or_init(|| std::env::var_os($var).is_some())
+        }
+    };
+}
+
+cached_env_flag!(closedbg_enabled, "MAJIT_CLOSEDBG");
+cached_env_flag!(bh_debug_enabled, "MAJIT_BH_DEBUG");
+cached_env_flag!(nbody_debug_enabled, "PYRE_NBODY_DEBUG");
+cached_env_flag!(mptrace_enabled, "MAJIT_MPTRACE");
+cached_env_flag!(pcseq_enabled, "MAJIT_PCSEQ");
+cached_env_flag!(tldbg_enabled, "MAJIT_TLDBG");
+cached_env_flag!(heapdbg_enabled, "MAJIT_HEAPDBG");
+cached_env_flag!(diag_enabled, "MAJIT_DIAG");
+cached_env_flag!(log_jtet_enabled, "MAJIT_LOG_JTET");
+cached_env_flag!(smallir_enabled, "MAJIT_SMALLIR");
+cached_env_flag!(log_opt_enabled, "MAJIT_LOG_OPT");
+cached_env_flag!(bridge_debug_enabled, "MAJIT_BRIDGE_DEBUG");
+cached_env_flag!(no_unroll_enabled, "PYRE_NO_UNROLL");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@majit/majit-metainterp/src/lib.rs` around lines 169 - 270, The boolean
env-var helpers are duplicated across many near-identical functions, so refactor
them into a shared macro while preserving the current caching behavior.
Introduce a small `macro_rules!` near the existing `closedbg_enabled`,
`bh_debug_enabled`, `nbody_debug_enabled`, `mptrace_enabled`, `pcseq_enabled`,
`tldbg_enabled`, `heapdbg_enabled`, `diag_enabled`, `log_jtet_enabled`,
`smallir_enabled`, `log_opt_enabled`, `bridge_debug_enabled`, and
`no_unroll_enabled` symbols, and use it to generate each `OnceLock<bool>` helper
from just the function name and env-var string. Keep `original_boxes_enabled`,
`stall_window`, and `step_limit` as-is unless they can also be cleanly expressed
through the same pattern.

Comment thread majit/majit-translate/src/annotator/bookkeeper.rs
Comment on lines +1284 to +1291
thread_local! {
/// Code objects already counted by
/// [`census_record_frame_shape_decline`], so a `CurrentFrameOnly` code
/// entered many times (a hot helper) records exactly one census entry
/// instead of one per call. Keyed by the stable `CodeObject` pointer.
static FRAME_SHAPE_DECLINE_SEEN: std::cell::RefCell<std::collections::BTreeSet<usize>> =
const { std::cell::RefCell::new(std::collections::BTreeSet::new()) };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Verify the newly introduced Rust-native containers against the RPython shape.

This change introduces a BTreeSet<usize> (FRAME_SHAPE_DECLINE_SEEN, Line 1289) and a HashSet<usize> (covered, Line 10244, inside the get_list_of_active_boxes port). The repo convention is to avoid casually introducing HashMap/HashSet/BTreeMap-style containers when porting RPython/PyPy code, preferring the original structure shape, and to cite the RPython original when a container is used as a borrow-checker/dedup workaround.

Both here are pointer/index dedup helpers with no corresponding RPython container. Please confirm they are the minimal deviation (or cite the RPython original in a comment); the covered set in particular sits directly in ported get_list_of_active_boxes logic.

As per coding guidelines: "When porting RPython/PyPy code, do not casually introduce Rust-native containers like HashMap, HashSet, or BTreeMap; match the original RPython data structure shape instead."

Also applies to: 10244-10245

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-jit-trace/src/jitcode_dispatch.rs` around lines 1284 - 1291, The
new FRAME_SHAPE_DECLINE_SEEN BTreeSet and the covered HashSet in
get_list_of_active_boxes introduce Rust-native dedup containers that don’t
obviously match the RPython/PyPy shape. Review these locations in
jitcode_dispatch.rs and either replace them with the closest original
data-structure pattern from the ported code or add a brief comment citing the
RPython source and explaining why the container is the minimal
borrow-checker/dedup deviation. Keep the change localized to
FRAME_SHAPE_DECLINE_SEEN and get_list_of_active_boxes/covered.

Source: Coding guidelines

Comment thread pyre/wasm_check.py
Comment on lines +87 to +98
def collect_benches(filters: list[str]) -> list[Path]:
"""Glob pyre/bench/*.py, optionally filtering by substring."""
all_scripts = sorted(Path(BENCH_DIR).glob("*.py"))
# Exclude tmp_ files
all_scripts = [s for s in all_scripts if not s.name.startswith("tmp_")]
if not filters:
return all_scripts
matched = []
for s in all_scripts:
if any(f in s.stem for f in filters):
matched.append(s)
return matched

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

collect_benches won't discover the new pyre/bench/synth/ benchmarks.

Path(BENCH_DIR).glob("*.py") is non-recursive, so the synth benchmarks added in this same PR (foriter_in_while.py, nested_foriter_call.py, etc.) are invisible to wasm_check.py. If these benchmarks are meant to be compared across engines, use a recursive glob or add the synth/ subdirectory.

Proposed fix: recursive glob
-    all_scripts = sorted(Path(BENCH_DIR).glob("*.py"))
+    all_scripts = sorted(Path(BENCH_DIR).glob("**/*.py"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def collect_benches(filters: list[str]) -> list[Path]:
"""Glob pyre/bench/*.py, optionally filtering by substring."""
all_scripts = sorted(Path(BENCH_DIR).glob("*.py"))
# Exclude tmp_ files
all_scripts = [s for s in all_scripts if not s.name.startswith("tmp_")]
if not filters:
return all_scripts
matched = []
for s in all_scripts:
if any(f in s.stem for f in filters):
matched.append(s)
return matched
def collect_benches(filters: list[str]) -> list[Path]:
"""Glob pyre/bench/*.py, optionally filtering by substring."""
all_scripts = sorted(Path(BENCH_DIR).glob("**/*.py"))
# Exclude tmp_ files
all_scripts = [s for s in all_scripts if not s.name.startswith("tmp_")]
if not filters:
return all_scripts
matched = []
for s in all_scripts:
if any(f in s.stem for f in filters):
matched.append(s)
return matched
🧰 Tools
🪛 Ruff (0.15.20)

[warning] 97-97: Use a list comprehension to create a transformed list

(PERF401)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/wasm_check.py` around lines 87 - 98, `collect_benches` only scans the
top-level benchmark directory, so the new `synth` benchmarks are missed. Update
`collect_benches` in `wasm_check.py` to search recursively from `BENCH_DIR` (or
explicitly include the `synth` subdirectory) while preserving the existing tmp_
exclusion and filter-by-stem behavior, so the new benchmark files are returned
along with the existing ones.

When identity_ref_bank_index is set (state-field JIT), live_values
includes scalar state fields that are not counted in num_reds.  The
comparison live_values.len() >= num_reds + total_vable can produce a
false positive, causing initialize_virtualizable to reuse scalar
inputarg slots as vable elements instead of minting fresh ones.

Gate on identity_ref_bank_index.is_none() so that state-field JIT
always takes the heap-read path via vable_ptr.

Assisted-by: Claude
Switch wasmi dependency from crates.io 1.1 to in-tree patched wasmi
(wasmi/ nested workspace) with the majit-jit meta-tracing feature.

- Cargo.toml: exclude wasmi/ from workspace members
- pyre-wasm-runner/Cargo.toml: path dependency with majit-jit feature
- wasmi_host.rs: adapt to wasmi 2.x API (Nullable import, table-based
  trace dispatch replacing HashMap, trace_base tracking)

Assisted-by: Claude
Add --wasm-bench / --wasm-bench-reps for comparing wasm execution across
engines: wasmtime, wasmi stock, and wasmi+majit. Reports a speedup table
for loop-heavy benchmarks (int_loop, float_loop, fib_loop, nested_loop,
inline_helper, spectral_norm).

Assisted-by: Claude
The patched wasmi with majit-jit requires the in-tree wasmi/ nested
workspace which is not available in CI. Revert to the crates.io
wasmi 1.1 dependency so CI builds pass.

For local development with the majit JIT tier, use .cargo/config.toml:
  [patch.crates-io]
  wasmi = { path = "wasmi/crates/wasmi" }

Assisted-by: Claude
Move the --wasm-bench functionality from check.py to a standalone
pyre/wasm_check.py that auto-discovers pyre/bench/*.py scripts
and compares wasmtime vs wasmi-stock vs wasmi+majit.

Revert check.py to its upstream state (no --wasm-bench flag).

Usage: python pyre/wasm_check.py [--reps N] [BENCH_SUBSTRING ...]

Assisted-by: Claude
@youknowone youknowone merged commit 99060a7 into main Jul 9, 2026
9 of 10 checks passed
@youknowone youknowone deleted the wasmi branch July 9, 2026 06:34

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1954b2ee95

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pyre/wasm_check.py

best, out, code = None, None, 0
for _ in range(reps):
o, t, c, _e = run_timed([runner, str(script)], timeout_s=timeout, env=env)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the stable wasm-host module for benchmarks

When this script is run after the normal wasm build and a later web build, it benchmarks the runner's default target/.../pyre_wasm.wasm instead of the stable host snapshot. pyre/check.py:114-121 documents that pyre_wasm.wasm is shared by the web and wasm-host builds and can be clobbered, and pyre/check.py:286-289 avoids that by setting PYRE_WASM_MODULE to pyre_wasm.wasm-host.wasm; this standalone path never does so, so the comparison can fail or measure the wrong module depending on the last wasm build.

Useful? React with 👍 / 👎.

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.

wasmi majit tier: real pyre traces bail the prepass → ~1.0x end-to-end (op-coverage epic)

1 participant