yagehu opened PR #14964 from yagehu:veri-rhs-type-soundness to bytecodealliance:main:
Bug
#14957 reported an unsound ISLE rule passing the VeriISLE verifier. This is my take on why it passes currently and my attempt to fix it.
In the given example,
ctyis the result type of bothsextends, but nothing boundsxandyto the same width. The verifier currently reports skipped type assignments (102 in the example) and verifies the 6 wherex,y, andtyhappen to have the same width. So a rule like(ireduce ty (iadd cty (sextend cty x) (sextend cty y))) => (iadd ty x y)was accepted even though
xandymay differ in width.This Fix
A rule fires whenever its left-hand side matches, so its right-hand side must be well-typed under every type assignment the left-hand side accepts. If it isn't, then the rule should be rejected.
This commit changes the verifier so that:
- We mark which parts of the rule come from LHS or RHS.
- Solve LHS types first, then check RHS under each satisfying assigment.
- Report the RHS conflict as error.
Caveat
This addition type check step needs the solver, so
--skip-solverwould still skip these conflicts.
yagehu requested fitzgen for a review on PR #14964.
yagehu requested wasmtime-compiler-reviewers for a review on PR #14964.
yagehu removed the review request for wasmtime-compiler-reviewers from PR #14964.
github-actions[bot] added the label cranelift on PR #14964.
github-actions[bot] added the label isle on PR #14964.
github-actions[bot] commented on PR #14964:
Subscribe to Label Action
cc @avanhatt, @cfallin, @fitzgen, @mmcloughlin
<details>
This issue or pull request has been labeled: "cranelift", "isle"Thus the following users have been cc'd because of the following labels:
- avanhatt: isle
- cfallin: isle
- fitzgen: isle
- mmcloughlin: isle
To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.
Learn more.
</details>
yagehu updated PR #14964.
yagehu edited PR #14964:
Bug
#14957 reported an unsound ISLE rule passing the VeriISLE verifier. This is my take on why it passes currently and my attempt to fix it.
In the given example,
ctyis the result type of bothsextends, but nothing boundsxandyto the same width. The verifier currently reports skipped type assignments (102 in the example) and verifies the 6 wherex,y, andtyhappen to have the same width. So a rule like(ireduce ty (iadd cty (sextend cty x) (sextend cty y))) => (iadd ty x y)was accepted even though
xandymay differ in width.This Fix
A rule fires whenever its left-hand side matches, so its right-hand side must be well-typed under every type assignment the left-hand side accepts. If it isn't, then the rule should be rejected.
This commit changes the verifier so that:
- We mark which parts of the rule come from LHS or RHS.
- Solve LHS types first, then check RHS under each satisfying assigment.
- Report the RHS conflict as error. But don't report assignments that can never occur due to a guard like
(value_type ty).Lowering rule fixes
With the help of an LLM I made some fixes to the new lowering verification errors on
aarch64.
writable_zero_regwas a fixed 32-bit zero but is used as a 64-bit destination. It is now width-polymorphic.writable_reg_to_regrequired equal widths, but scalar float helpers allocate 128-bit vector temporaries and return them as 64-bitRegs. It now keeps the low bits.FixedToFP!narrowed its operand to the destination float width before converting, sofcvt_from_{u,s}int.f32of ani64only converted the low 32 bits. It now converts at full width, as the CLIF spec already does.trap_if_div_overflowemits flag-setting and flag-consuming instructions without threading the flags, which the specs don't model.Caveat
This addition type check step needs the solver, so
--skip-solverwould still skip these conflicts.
cfallin commented on PR #14964:
cc @avanhatt @mmcloughlin
github-actions[bot] added the label cranelift:area:aarch64 on PR #14964.
fitzgen requested cfallin for a review on PR #14964.
fitzgen removed the review request for fitzgen from PR #14964.
avanhatt commented on PR #14964:
Thanks for the PR!
I'll take a deeper look over the next few days (probably Monday), but some initial thoughts:
- I think the issue uncovered here may also warrant a deeper fix where we rule out inapplicable chains at the
expansionlevel to avoid similar gaps from inapplicability in the future.- I'd feel better about resolving the
trap_if_div_overflowspec issues in the same PR rather than changing it asTODObased on it being core integer arithmetic we want covered.- The arithmetic-precise.clif/related rules change should possibly live in a separate PR, since it actually changes/improves the optimizer output.
yagehu commented on PR #14964:
Thanks for the PR!
I'll take a deeper look over the next few days (probably Monday), but some initial thoughts:
* I think the issue uncovered here may also warrant a deeper fix where we rule out inapplicable chains at the `expansion` level to avoid similar gaps from inapplicability in the future. * I'd feel better about resolving the `trap_if_div_overflow` spec issues in the same PR rather than changing it as `TODO` based on it being core integer arithmetic we want covered. * The arithmetic-precise.clif/related rules change should possibly live in a separate PR, since it actually changes/improves the optimizer output.Totally makes sense. I can address points 2 and 3. Let me know what's a better deeper fix after you've thought about it.
yagehu updated PR #14964.
yagehu updated PR #14964.
yagehu edited PR #14964:
Bug
#14957 reported an unsound ISLE rule passing the VeriISLE verifier. This is my take on why it passes currently and my attempt to fix it.
In the given example,
ctyis the result type of bothsextends, but nothing boundsxandyto the same width. The verifier currently reports skipped type assignments (102 in the example) and verifies the 6 wherex,y, andtyhappen to have the same width. So a rule like(ireduce ty (iadd cty (sextend cty x) (sextend cty y))) => (iadd ty x y)was accepted even though
xandymay differ in width.This Fix
A rule fires whenever its left-hand side matches, so its right-hand side must be well-typed under every type assignment the left-hand side accepts. If it isn't, then the rule should be rejected.
This PR changes the verifier so that:
- We mark which parts of the rule come from LHS or RHS.
- Solve LHS types first, then check RHS under each satisfying assigment.
- Report the RHS conflict as error. But don't report assignments that can never occur due to a guard like
(value_type ty).What the new check found
Making these conflicts visible exposed rules that previously verified vacuously, because every type assignment was silently dropped. For example, the
sdiv_base_base_64rule uses(writable_zero_reg)as a 64-bit destination register. But its spec was a fixed 32-bit value:(spec (writable_zero_reg) (provide (= result #x00000000)))All the type assignments conflicted and were dropped. In this case the problem was the spec, but treating "no type assignment works" as "this rule doesn't apply" also hid this problem.
Mid end (
opt)The
x > max(x, y) --> 0rules inarithmetic.islereuse onetyfor a comparison's result type and for its operands. When matched, the type of anicmpis its result type (i8), so these rules only fire fori8operands. The verifier also can't relate thattyto the comparison's width. I've tagged themTODOhere. Switching them to a separate comparison type changes optimizer output, so it will be a separate PR.aarch64 lowering
With the help of an LLM I made some fixes to the new lowering verification errors on
aarch64.
writable_zero_regwas a fixed 32-bit zero but is used as a 64-bit destination. It is now polymorphic.writable_reg_to_regrequired equal widths, but scalar float helpers allocate 128-bit vector temporaries and return them as 64-bitRegs. It now keeps the low bits.FixedToFP!narrowed its operand to the destination float width before converting, sofcvt_from_{u,s}int.f32of ani64only converted the low 32 bits. It now converts at full width, as the CLIF spec already does.- Load and store addresses are scalars, but their specs left the lane count open, so the verifier could pick a vector address. The specs now say it's a scalar.
trap_if_div_overflowemits flag-setting and flag-consuming instructions without threading the flags, which the specs don't model.Enabling
with_flagsVerifying
sdivoverflow check needswith_flag, which was taggedTODO. Removing it brings flag-using lowering rules into scope, and some of them fail or have no spec. I've excluded the specific cases asTODO. I think this is OK because they were all excluded before, just implicitly.Caveat
This addition type check step needs the solver, so
--skip-solverwould still skip these conflicts.
yagehu commented on PR #14964:
I've updated the PR to drop the
arithmetic-precise.clifrelated rules because they change optimizer behavior. I've also addressed thetrap_if_div_overflowspec issue. PR description updated to reflect the changes.
:repeat: avanhatt submitted PR review:
Thanks again for the PR! The general approach makes sense (while the
rhsboolean tracking logic as is seems a bit fragile, I don't see another obvious approach without a larger redesign of type inference).My thought about avoiding
Inapplicablealtogether didn't actually pan out, since some actual inapplicable rule chains require the solver to rule out rather than just information available atexpansiontime.More comments inline, including some requests for changes.
:speech_balloon: avanhatt created PR review comment:
The implicit
rhs = falselogic is a bit easy to miss, would prefer this to be a parameter or otherwise set more explicitly.
:speech_balloon: avanhatt created PR review comment:
For clarity/symmetry, would prefer this be
lhs_width_domainson theSystem.
:speech_balloon: avanhatt created PR review comment:
I would add here that we need the solver to determine the satisfiability of value constraints.
:speech_balloon: avanhatt created PR review comment:
I think we should actually revert this back to just be
(provide true)since it is just a write sink as it was in the paper version (this was a mistake I introduced when upstreaming).As is, it seems this verifies the relevant
sdivrule below without modeling all cases (e.g., it forcesy = -1): if you break the rule to trap on cases it should not trap on, it still passes.
:speech_balloon: avanhatt created PR review comment:
Ah, I was missing that these failed as written under the new design. If you would rather the previous change that allows them to pass in this PR, I now think it's related enough to make sense. Sorry for the churn!
:speech_balloon: avanhatt created PR review comment:
These below should be resolvable with spec fixes, but I can resolve that in a followup PR.
:speech_balloon: avanhatt created PR review comment:
Likewise for
lhs_constraintsthere
:speech_balloon: avanhatt created PR review comment:
This should also now call
resolve_single_lane_maps()(or maybe should have a shared prelude withSolverencode).
:speech_balloon: avanhatt created PR review comment:
It's not clear to me why this (or the
corresponding_scalarshelper) is necessary.signatures/specsare assumed to be correct, so I don't see the case whereself.signature_type(&call.ret, sig.ret.clone());below is insufficient. Could you explain what case you think this is necessary for?
yagehu updated PR #14964.
:memo: yagehu submitted PR review.
:speech_balloon: yagehu created PR review comment:
Good catch, it isn’t necessary. I’ve removed both the no-signature branch and the helper. I initially added them while debugging some underconstrained type inference errors.
:memo: yagehu submitted PR review.
:speech_balloon: yagehu created PR review comment:
Done.
:memo: yagehu submitted PR review.
:speech_balloon: yagehu created PR review comment:
I think a follow-up fix makes sense. I'm less confident with lowering specs.
:memo: yagehu submitted PR review.
:speech_balloon: yagehu created PR review comment:
Added.
:memo: yagehu submitted PR review.
:speech_balloon: yagehu created PR review comment:
Done.
check_lhs_feasibilitynow sharesencode's prelude viaencode_exprs.
:memo: yagehu submitted PR review.
:speech_balloon: yagehu created PR review comment:
I changed
rhs: boolto aSide { Lhs, Rhs }to be more explicit. It's now only set throughon_side(side, |b| ...)which sets the side for the closure and restores the previous one after. I think this is a less intrusive change than threadingSidethrough every constraint helper.
:memo: yagehu submitted PR review.
:speech_balloon: yagehu created PR review comment:
Done
yagehu requested avanhatt for a review on PR #14964.
:thumbs_up: avanhatt submitted PR review:
These changes look good to me, thanks again!
CC @cfallin for reviewing the couple of changes to ISLE rules themselves instead of specs/the verifier code. One change restores a version of an
sdivrule we verified in the paper that was lost in upstreaming, the other are improvements tooptrule coverage. I'm planning follow up PRs re:optcontrolling type and to restore some of the lowering rule coverage.
:thumbs_up: cfallin submitted PR review:
Changes to aarch64 backend LGTM -- thanks! Looks like there is a merge conflict now; happy to merge this once that is resolved.
yagehu updated PR #14964.
yagehu commented on PR #14964:
Changes to aarch64 backend LGTM -- thanks! Looks like there is a merge conflict now; happy to merge this once that is resolved.
@cfallin Thanks for the review. I rebased on main. Should merge cleanly now.
:thumbs_up: cfallin submitted PR review.
cfallin added PR #14964 ISLE verifier: reject rules whose RHS is ill-typed to the merge queue.
:check: cfallin merged PR #14964.
cfallin removed PR #14964 ISLE verifier: reject rules whose RHS is ill-typed from the merge queue.
Last updated: Oct 31 2026 at 23:03 UTC