Skip to content

Fix subtle ISLE extractor issue leading to incorrect matching in *mul_overflow lowering. - #14295

Open
cfallin wants to merge 1 commit into
bytecodealliance:mainfrom
cfallin:fix-second-result-etor
Open

Fix subtle ISLE extractor issue leading to incorrect matching in *mul_overflow lowering.#14295
cfallin wants to merge 1 commit into
bytecodealliance:mainfrom
cfallin:fix-second-result-etor

Conversation

@cfallin

@cfallin cfallin commented Sep 8, 2026

Copy link
Copy Markdown
Member

In #14293, a test case that uses the first result (i.e., the product) of an smul_overflow operator as a condition (e.g. as part of icmp eq comparing to zero) incorrectly triggers the lowering rule I added in #14254 which was meant to match only compare-to-zero on the second (overflow) result.

This was a result of a fairly subtle issue involving auto-conversions in ISLE. I had written

(rule (is_nonzero (second_result umul @ (smul_overflow ...)))
      ...)

where the intent was to match an is_nonzero (which is a helper term) lowering with the second result (overflow flag) of the smul_overflow.

second_result has a term signature (Value) Inst, in other words it takes an Inst and returns an Option<Value>. is_nonzero takes a Value. So we auto-convert the Value in the first arg position of is_nonzer to an Inst; that uses def_inst, which looks up the defining instruction of the given value. Then second_result takes that Inst and gives the second value. But then the next level, (smul_overflow ...), again uses def_inst and goes from the (second result) Value back to the inst and matches.

In other words, we're too permissive with the autoconversions on Value to Inst; all of this was designed at a time when we more or less only handled single-result instructions with any nontrivial lowering rule, so the two were mostly interchangeable. The handling for the overflow-flag ops changes that.

The specific step in that chain above that is unambiguously wrong wrt intent is (first result) Value -> Inst -> second_result matching. So this PR instead introduces is_second_result that is Value -> Option<Value> and matches only when the specific Value is the second result of an instruction.

This does have me thinking a bit more about the role that the Value -> Inst autoconvert matching plays. It is absolutely essential to the ergonomics of ISLE: without it, we couldn't write

(rule (lower (iadd (imul a b) c)) ...)

because iadd's args are Values and we need to match back to an Inst for imul. But we also have cases like the one in this PR where we really shouldn't be so permissive. Perhaps we want a kind of type modifier (=Value ?) that means "exactly this type, not autoconverted". I'll bring this up in the Cranelift meeting this week.

Fixes #14293.

…l_overflow` lowering.

In bytecodealliance#14293, a test case that uses the *first* result (i.e., the product)
of an `smul_overflow` operator as a condition (e.g. as part of `icmp eq`
comparing to zero) incorrectly triggers the lowering rule I added in bytecodealliance#14254
which was meant to match only compare-to-zero on the *second* (overflow)
result.

This was a result if a fairly subtle issue involving auto-conversions in
ISLE. I had written

```
(rule (is_nonzero (second_result umul @ (smul_overflow ...)))
      ...)
```

where the intent was to match an `is_nonzero` (which is a helper term)
lowering with the second result (overflow flag) of the `smul_overflow`.

`second_result` has a term signature `(Value) Inst`, in other words it
takes an `Inst` and returns an `Option<Value>`.` `is_nonzero` takes a
`Value`. So we auto-convert the `Value` in the first arg position of
`is_nonzer` to an `Inst`; that uses `def_inst`, which looks up the
defining instruction of the given value. Then `second_result` takes that
`Inst` and gives the second value. But then the next level,
`(smul_overflow ...)`, *again* uses `def_inst` and goes from the (second
result) `Value` back to the inst and matches.

In other words, we're too permissive with the autoconversions on `Value`
to `Inst`; all of this was designed at a time when we more or less only
handled single-result instructions with any nontrivial lowering rule, so
the two were mostly interchangeable. The handling for the overflow-flag
ops changes that.

The specific step in that chain above that is unambiguously wrong wrt
intent is (first result) `Value` -> `Inst` -> `second_result` matching.
So this PR instead introduces `is_second_result` that is `Value` ->
`Option<Value>` and matches only when the specific `Value` is the second
result of an instruction.

This does have me thinking a bit more about the role that the `Value` ->
`Inst` autoconvert matching plays. It is absolutely essential to the
ergonomics of ISLE: without it, we couldn't write

```
(rule (lower (iadd (imul a b) c)) ...)
```

because `iadd`'s args are `Value`s and we need to match back to an
`Inst` for `imul`. *But* we also have cases like the one in this PR
where we really shouldn't be so permissive. Perhaps we want a kind of
type modifier (`=Value` ?) that means "exactly this type, not
autoconverted". I'll bring this up in the Cranelift meeting this week.

Fixes bytecodealliance#14293.
@cfallin
cfallin requested a review from a team as a code owner September 8, 2026 00:18
@cfallin
cfallin requested review from alexcrichton and removed request for a team September 8, 2026 00:18
@github-actions github-actions Bot added cranelift Issues related to the Cranelift code generator cranelift:area:machinst Issues related to instruction selection and the new MachInst backend. cranelift:area:aarch64 Issues related to AArch64 backend. cranelift:area:x64 Issues related to x64 codegen labels Sep 8, 2026
@alexcrichton

Copy link
Copy Markdown
Member

I don't really understand what's going on here, and I also didn't catch this in prior review, so I'm going to swap in @fitzgen

@alexcrichton
alexcrichton requested review from fitzgen and removed request for alexcrichton September 8, 2026 14:30

@fitzgen fitzgen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM modulo comments below

Comment on lines +219 to +227
let is_match = self
.lower_ctx
.dfg()
.inst_results(inst)
.iter()
.skip(1)
.next()
.copied()
== Some(val);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

self.lower_ctx.dfg().inst_results(inst).get(1) == Some(&val) is a little shorter and more clear to me

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Seems like we could probably minimize this test case a bit

Comment on lines +305 to +310
;; Be careful using this: if used to match on a *`Value`*, the
;; auto-converter will take that `Value` to its defining `Inst`, then
;; the inner result of this extractor will be the second def of the
;; value; in other words, it will *fetch* the second result but will
;; not *assert/only match if* the initial `Value` is the second
;; result. Use `is_second_result` for that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we still even want to have second_result? Is anything still using it that couldn't move to is_second_result?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cranelift:area:aarch64 Issues related to AArch64 backend. cranelift:area:machinst Issues related to instruction selection and the new MachInst backend. cranelift:area:x64 Issues related to x64 codegen cranelift Issues related to the Cranelift code generator

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Floating point exception with {u,s}mul_overflow branch fusion

3 participants