JIT: Extend cast removal pattern for bitwise ops: OP(CAST(x), CAST(y)) → CAST(OP(x, y))#126671
JIT: Extend cast removal pattern for bitwise ops: OP(CAST(x), CAST(y)) → CAST(OP(x, y))#126671Copilot wants to merge 11 commits into
Conversation
When range assertions remove an outer cast from CAST(OP(CAST(x), CAST(y))), we're left with OP(CAST(x), CAST(y)) with two redundant casts. For bitwise ops (AND, OR, XOR), we can transform this to CAST(OP(x, y)) since these operations are bit-independent: sign/zero extension commutes with bitwise ops. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/7a203210-db47-47e7-9074-92b298f13949 Co-authored-by: adamperlin <10533886+adamperlin@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/7a203210-db47-47e7-9074-92b298f13949 Co-authored-by: adamperlin <10533886+adamperlin@users.noreply.github.com>
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
🤖 Copilot Code Review — PR #126671Note This review was generated by GitHub Copilot using multi-model analysis (Claude Opus 4.6 primary, with GPT-5.3-Codex and Claude Haiku 4.5 sub-agents). Holistic AssessmentMotivation: The optimization is well-motivated. When range assertions remove the outer cast from Approach: The approach is sound. For bitwise operations (AND, OR, XOR), truncation/extension commutes with the operation — the lower bits of the result depend only on the lower bits of the inputs. The transformation Summary: Detailed Findings✅ Correctness — Bitwise commutativity with truncation is validThe core claim that
✅ Type safety — No type mismatch for consumersOne sub-agent flagged a type mismatch concern, but this is a false positive. ✅ GTF_UNSIGNED flag — Not meaningful for small type castsThe ✅ LIR ordering — Correct linear sequence maintainedAfter transformation, the LIR order is
|
There was a problem hiding this comment.
Pull request overview
This PR adds a new rationalization-time (LIR) cast-reduction transform for bitwise ops so that AND/OR/XOR(CAST_T(x), CAST_T(y)) can be rewritten as CAST_T(AND/OR/XOR(x, y)), reducing two inner casts down to one outer cast after certain earlier optimizations remove an outer cast.
Changes:
- Add
Compiler::fgSimpleLowerSmpOpCastsinflowgraph.cppto fold two operand casts into a single outer cast forGT_AND/GT_OR/GT_XOR. - Invoke this new helper during rationalization for
GT_AND/GT_OR/GT_XOR, replacing the parent use when a rewrite occurs. - Declare the new helper in
compiler.h.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/coreclr/jit/rationalize.cpp | Calls the new cast-folding helper for GT_AND/GT_OR/GT_XOR and replaces the use edge when it rewrites. |
| src/coreclr/jit/flowgraph.cpp | Implements fgSimpleLowerSmpOpCasts to transform OP(CAST, CAST) into CAST(OP) in LIR. |
| src/coreclr/jit/compiler.h | Adds the declaration for fgSimpleLowerSmpOpCasts. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| case GT_AND: | ||
| case GT_OR: | ||
| case GT_XOR: | ||
| { | ||
| GenTree* parent = parentStack.Height() >= 2 ? parentStack.Top(1) : nullptr; | ||
| if ((parent == nullptr) || !parent->OperIs(GT_CAST)) | ||
| { | ||
| GenTree* replacement = m_compiler->fgSimpleLowerSmpOpCasts(BlockRange(), node->AsOp()); |
|
I'm not totally sure if this opt is worth it. Scanning for this specific pattern while processing any |
Not sure either, seems like it mostly only improves win-x64. Any particular reason you've done it in Rationalizer? Normally, we either do such transforms in gtFoldExpr, morph or Lower |
There is a comment on |
Description
PR #120980 introduced range assertions that can eliminate outer casts on bitwise operations when the result is provably in range. This breaks the existing
fgSimpleLowerCastOfSmpOppattern which expectsCAST(OP(CAST(x), CAST(y)))→CAST(OP(x, y)). When the outer cast is removed first, we're left withOP(CAST(x), CAST(y))— two casts where at most one is needed.New function
fgSimpleLowerSmpOpCastshandles the complementary case for AND, OR, XOR:This is valid because bitwise ops are bit-independent:
ext_n(x) OP ext_n(y) = ext_n(x OP y)for sign/zero extension with AND, OR, XOR.Diffs.