majit: reject ambiguous boxing stores across phi roots - #1191
Conversation
`fuse_boxing_alloc` resolved a payload store and a header store by taking the first `FieldWrite` matching `(base, field)` anywhere in the graph. The scan is graph-wide because the stores feeding an aggregate sit in whichever block built it, and the pass has no reaching-definition analysis, so with two stores on one field it cannot tell which the malloc sees. Collect every matching store instead and return one only when they carry equal values; disagreeing stores leave `malloc_typed` residual for the fail-closed reject. `rpython/translator/backendopt/malloc.py:176-186` applies the same rule, downgrading a variable with more than one creation point to a plain use so the malloc optimisation is disabled for it. The reject census over the build-time lowering is unchanged at 9, all `w_long_from_raw` directly and through `unary_invert_value` / `unary_negative_value`, so no cluster that fused before declines now. Assisted-by: Claude
`store_roots` walks `Block.inputargs` / `Link.args` backwards to recover the op result a phi stands for. `rpython/translator/backendopt/malloc.py:169` reaches the same relation forwards, unioning each link arg with the inputarg it feeds into one lifetime; it can, because it owns a persistent whole-graph lifetime table this local rewrite has none of. `resolve_vtable_addr` derives the type pointer from the constructed value's `ob_header.ob_type`. `jtransform.py:1023 rewrite_op_malloc` reads it off the operation instead, because `malloc` carries the `STRUCT` and `heaptracker.get_vtable_for_gcstruct` answers from the type alone. `malloc_typed(value)` carries a value, which is also why the `w_class` check exists: a value can pair a base `ob_type` with a subclass `w_class` where a `STRUCT` cannot. Comments only. Assisted-by: Claude
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Walkthrough
ChangesBoxing fusion validation
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Poem
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 138cec8235
ℹ️ 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".
| if b != base || field.name.as_str() != field_name { | ||
| continue; |
There was a problem hiding this comment.
Track stores through block-input aliases
When an aggregate or header enters a branch as a fresh Block.inputargs variable, a FieldWrite in that block names the inputarg rather than the producer variable. The later store_roots call resolves the malloc argument back to the producer, but this exact-base comparison then ignores the aliased write and can fuse using the stale payload or vtable, changing the boxed value or type. The new test hides this case by writing through agg instead of arm_args[0]; the store census needs the same link-arg/inputarg equivalence that upstream establishes in rpython/translator/backendopt/malloc.py:169-179.
AGENTS.md reference: AGENTS.md:L231-L233
Useful? React with 👍 / 👎.
🤖 Codex parity reviewStatic analysis of this diff vs the local RPython/PyPy sources (commit 138cec8). Files in the reviewed diff1. Regressions to PyPy parity introduced by this patchNone. 2. Other mismatches introduced by this patch
3. Pre-existing mismatches (already present before this patch)None. 4. Structural adaptations
|
What changed
Why
fuse_boxing_allocsearched the whole graph for a matching(base, field)store but selected the first result. When a field was written differently on one branch, the chosen value depended on block order because this local pass has no reaching-definition analysis. Conversely, a header crossing a block boundary arrived as a phi while its stores remained attached to the producer variable, so a valid cluster could be declined.The pass now fails closed on disagreeing stores and resolves phi roots before reading their stores. This matches RPython's rule that multiple creation points disable malloc optimization when aliasing cannot be proven safe.
Validation
cargo fmt --all -- --checkcargo check --features dynasmcargo test --features dynasmcargo test -p majit-translate fuse_boxing_alloc(9 passed)python3 pyre/check.py --backend dynasm --no-synthetic --no-cpython-suite(17/17 passed)Summary by CodeRabbit