test(gc): assert the poll guard's CFG shape, not its text order - #8126
Conversation
`a_surviving_poll_is_guarded_by_the_arming_word` fails on main. The lowering is correct: every `load volatile @PERRY_GC_POLL_ARMED` is followed by `icmp` and `br i1` into a `gcpoll.N` block that holds the call, which then branches to `gcpoll.done.N`. The safety property the test names — the call sits behind the branch, not in the same straight line as the load — holds. What broke is the test's proxy for it. It searched the whole remainder of the IR after each load and required the first `br i1` to precede the first poll call, which silently assumed each poll block is printed next to its guard. It is not: the guards sit inline in `for.cond` / `for.body` / `for.update`, while every `gcpoll.N` block is emitted together after the loop. Under that layout the first two segments contain no call at all, so `after.find(POLL)` returned `None` and the `matches!` arm — which requires `Some(c)` — rejected a correctly-guarded poll. Bound the search at the load's own basic block instead: that block must end in a conditional branch and must not contain the call. This asserts the actual CFG property and does not care where the successor block is printed. The negative controls still pass, so the test keeps its teeth: the loops that must KEEP a poll (coercible bound, coercible accumulator, call in the body, object literal in the body, module-global accumulator, string concat) all still emit one and are still checked through this same code.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe loop safepoint purity assertion now validates each poll load within its basic block. It requires a conditional branch and excludes the poll call from that block. Negative controls remain unchanged. ChangesLoop poll CFG assertion
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
✨ 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 |
|
Verified on a landing-equivalent tree (merged current So this fixes exactly one of the eleven and leaves the other alone, which is the right blast radius. The important consequence: one of the eleven was never a real failure. I have been using "27 suites / 1434 passed / 11 failed" as the codegen baseline all session, and treating those eleven as genuine pre-existing breakage. At least one was a broken test proxy — the lowering was correct the whole time. Worth re-examining the other ten in that light before anyone bisects them. The diagnosis is right and worth restating: the old check searched the whole remainder of the IR after each Bounding the search at the load's own basic block asserts the actual CFG property — this block ends in a conditional branch and does not contain the call — with no dependence on print order. That is a strictly better assertion, not a weakened one. |
a_surviving_poll_is_guarded_by_the_arming_wordfails on main, but the lowering is correct — everyload volatile @PERRY_GC_POLL_ARMEDis followed byicmp+br i1into agcpoll.Nblock holding the call.The test's proxy for that property broke. It searched the whole remainder of the IR after each load and required the first
br i1to precede the first poll call — silently assuming each poll block is printed next to its guard. It isn't: guards sit inline infor.cond/for.body/for.update, while thegcpoll.Nblocks are emitted together after the loop. So the first two segments contain no call,find(POLL)returnedNone, and thematches!arm rejected a correctly-guarded poll.Now the search is bounded at the load's own basic block: that block must end in a conditional branch and must not contain the call. Same property, no dependence on block print order.
Teeth preserved: all six negative controls (coercible bound, coercible accumulator, call in body, object literal in body, module-global accumulator, string concat) still emit a poll and still run through this check — they pass.
Note this does not make
e2e-scopedgreen on its own:loop_safepoint_purityhas one other failure (proven_numeric_counted_loop_emits_no_back_edge_poll) which is a genuine regression — the loop lowers tojs_dynamic_string_or_number_add/js_rel_lt, so the numeric proof is not applying — and that is tracked separately from this test-shape fix.Summary by CodeRabbit
Bug Fixes
Tests