cfallin opened PR #14228 from cfallin:opportunistic-value-defs to bytecodealliance:main:
Add a new lowering primitive, an "opportunistic def", which allows a lowering that incidentally computes another value to register that value for possible later use. When the backward scan reaches the value's actual definition, if the use-count has not grown (no further uses appeared while scanning up), the definition can be skipped entirely and the value aliased to the opportunistically-computed regs.
Also add a
value_usedhelper that reports whether a value still has uses, allowing skipping of some part of a lowering when not needed.With these two features available in the lowering environment, this PR then adds new lowerings for brif-of-
uadd_overflow, and bareuadd_overflow, on x86-64 and aarch64:
When
uadd_overflow's overflow flag is used as a branch condition, the branch directly uses flags produced by theaddinstruction, skipping (slow and verbose) materialization of the bool overflow flag into a GPR.If the sum is only used past the branch, then this
addalso produces that value; so only oneaddis ever emitted. Thus aadd; SETcc; test; jnzsequence turns into (x64)add; jb/ (aarch64)adds; b.hs.If the sum is used below the
uadd_overflowbut above the branch, anotheraddis also emitted. That's fine: adds are cheap; cheaper certainly than materializing flags withSETccorCSET.But to optimize that case further...
...when
uadd_overflowis only used for its sum, and not its overflow flag, we now emit anaddwithout aSETcc.As a result of these lowerings, the case
v1, v2 = uadd_overflow ...; brif v2, ...lowers toadd; jb, and the casev1, v2 = uadd_overflow ...; (use v1); brif v2, ...lowers toadd; (use sum); add; jb. The only remaining case where we use a slowSETccis when the bool result is actually materialized and used as an integer value; or if the add and branch are pushed into separate blocks.This resolves the same issue as #13919, following discussion in that PR and the Cranelift weekly meeting. In particular, (i) we do not build an ad-hoc separate scan (this mechanism works as part of the main lowering/instruction-selection scan); and (ii) we are resilient to instructions placed between the
uadd_overflowand branch, which is likely to happen due to egraph demand-based elaboration, and which foils a simpler peephole-based approach.<!--
Please make sure you include the following information:
If this work has been discussed elsewhere, please include a link to that
conversation. If it was discussed in an issue, just mention "issue #...".Explain why this change is needed. If the details are in an issue already,
this can be brief.Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.htmlPlease review the Bytecode Alliance's AI tool usage policy at
https://github.com/bytecodealliance/governance/blob/main/AI_TOOL_POLICY.mdPlease ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->
cfallin requested alexcrichton for a review on PR #14228.
cfallin requested wasmtime-compiler-reviewers for a review on PR #14228.
cfallin commented on PR #14228:
(This has been on my TODO list for a long time; sorry about the delay, @playX18, but hopefully this addresses your use-case!)
:memo: alexcrichton submitted PR review:
Seems reasonable to me! Question on this: would it be possible to somehow determine, before fusing this into a branch, if the opportunistic def is going to be thrown away and re-calculated? For something like an addition it seems probably fine to always duplicate that, but for something like a multiplication it might be better to never duplicate that given its latency. (maybe? unsure). Basically it seems to me like a reasonable heuristic here would be to only fuse into branches where the opportunistic def actually works as the def, and in all other cases fall back to materializing the flag and then testing it later.
Although now that I actually write this down what I'm going for is to put the onus on CLIF producers to make sure the flag-and-branch are close together such that the opportunistic def always matches. In some sense that's no different from this PR as-is where it's still on them to do that to avoid the double-translate if it matters... Anyway, still curious on the question at least as a data point
:speech_balloon: alexcrichton created PR review comment:
Would it be possible to fold this into
is_nonzero_cmpinstead of having a rule specifically forbrif? That'd then additionally handle the condition flowing intp atrapz,trapnz, andselectall at the same time. If this does work, could this be done for x64 too?
:speech_balloon: alexcrichton created PR review comment:
Is this rule needed for the codegen tests that were added, or was this opportunistically added mostly to prove this out?
Additionally, could this skip using
addsand just useaddinstead? If so I think theConsumesFlagsNopinfrastructure can be removed.
:speech_balloon: alexcrichton created PR review comment:
Additionally, since we might want to use this for a number of other things, could a new helper like
produces_flags_with_opportunistic_defbe added? That'd internally then useproduces_flags_get_reg, callopportunistic_def, and then return aProducesFlagscreated withproduces_flags_ignore. Basically factoring out the boilerplate-y parts of this if possible
cfallin requested pchickey for a review on PR #14228.
cfallin requested wasmtime-core-reviewers for a review on PR #14228.
cfallin updated PR #14228.
:memo: cfallin submitted PR review.
:speech_balloon: cfallin created PR review comment:
Yes, this is what handles the "intermediate user" case (and removes the unused
SETcc). But yeah great point, I dunno why I kept that bit (copy+paste I guess); this is really just a plain old add. Updated!
cfallin commented on PR #14228:
Seems reasonable to me! Question on this: would it be possible to somehow determine, before fusing this into a branch, if the opportunistic def is going to be thrown away and re-calculated? For something like an addition it seems probably fine to always duplicate that, but for something like a multiplication it might be better to never duplicate that given its latency. (maybe? unsure). Basically it seems to me like a reasonable heuristic here would be to only fuse into branches where the opportunistic def actually works as the def, and in all other cases fall back to materializing the flag and then testing it later.
Although now that I actually write this down what I'm going for is to put the onus on CLIF producers to make sure the flag-and-branch are close together such that the opportunistic def always matches. In some sense that's no different from this PR as-is where it's still on them to do that to avoid the double-translate if it matters... Anyway, still curious on the question at least as a data point
Unfortunately we can't really know "the future" during the backward lowering scan (or, well, we could, but that would be a separate scan). The conclusion I came to is that it's way simpler to build this opportunistic mechanism and just let the lowering fire twice if there is actually a user in the block in between the add and branch.
I also address this with "If the sum is used below the uadd_overflow but above the branch, another add is also emitted. That's fine: adds are cheap; cheaper certainly than materializing flags with SETcc or CSET.", I think: what we get here is actually still cheaper than what you're asking for (strictly one fewer instruction --
add; add; branchrather thanadd; setcc; test; branch; and thesetccis slow).
:memo: cfallin submitted PR review.
:speech_balloon: cfallin created PR review comment:
I played with this a bit, but IMHO it's not really that much simpler to fold this into
ProducesFlags. It requires pulling apart all the pieces from the original inst anyway and stuffing them into theProducesFlags(the other result value, the emitted result from theadds) and requires thinking about the emission combinations with variousConsumesFlags. Arguably more brittle, and distributes the logic over a larger area. In other words I don't think it actually avoids any boilerplate, it just explodes it into shards of boiler iron embedded in the walls...
:memo: cfallin submitted PR review.
:speech_balloon: cfallin created PR review comment:
(Happy to tackle this or see someone else tackle this in a followup of course if there are more ideas here -- but maybe good to get the building blocks in first regardless?)
github-actions[bot] added the label cranelift on PR #14228.
github-actions[bot] added the label cranelift:area:machinst on PR #14228.
github-actions[bot] added the label cranelift:area:aarch64 on PR #14228.
github-actions[bot] added the label cranelift:area:x64 on PR #14228.
Last updated: Aug 30 2026 at 09:07 UTC