Uh oh!
There was an error while loading. Please reload this page.
[experiment] JIT: Introduce KnownBits - #129082
Conversation
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Pull request overview
This PR adds a new JIT-internal KnownBits analysis (LLVM-style “known zero/known one” bit lattice) and wires it into assertion propagation so the JIT can fold more comparisons (including TYP_LONG) and prove more casts redundant/overflow-safe using bit-level facts.
Changes:
- Introduces
KnownBits/KnownBitsOps(bit lattice + transfer functions) and aKnownBits::ComputeVN/assertion-driven analysis. - Uses KnownBits in global assertion propagation to fold relops and to remove/relax casts when provably safe.
- Updates JIT build wiring to compile the new implementation.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/coreclr/jit/knownbits.h | New KnownBits lattice + transfer helpers (And/Or/UDiv/Cast/EvalRelop) and analysis entrypoint. |
| src/coreclr/jit/knownbits.cpp | Implements VN/assertion-based KnownBits computation, including PHI merging and assertion refinement. |
| src/coreclr/jit/CMakeLists.txt | Adds KnownBits sources/headers to the JIT build. |
| src/coreclr/jit/assertionprop.cpp | Hooks KnownBits into global relop folding and cast simplification paths; widens some assertion creation gates to include TYP_LONG. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Adds a 32/64-bit fixed-width KnownBits lattice for integral VNs, plus an
assertion-aware analysis (KnownBits::Compute) that derives the known bits of
a value number from its VN structure and the incoming assertions. The struct
and transfer functions are ports of llvm::KnownBits from
llvm/Support/KnownBits.{h,cpp}; only the subset of operations needed by the
current consumers (And/Or/UDiv/Cast/EvalRelop) is included.
Three consumers wired in assertionprop.cpp:
* optAssertionProp_RangeProperties - sign-bit/non-zero from KnownBits
* optAssertionPropGlobal_RelOp - relop folding
* optAssertionProp_BndsChk - (uint)index < (uint)length
Plus a one-shot KnownBits refinement at the end of
RangeCheck::GetRangeFromAssertionsWorker that tightens the signed [lo, hi]
when an interval side is at the type extreme.
libraries.pmi: -14,515 / +6 bytes (489 contexts, 409 size improvements,
2 regressions).
libraries_tests.run: -246k / +0.4k bytes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>The bounds-check consumer hardcoded width=32 in EvalRelop, which is unsound on 64-bit targets where fgMorphIndexAddr (morph.cpp:2995-3015) widens the bounds check to TYP_I_IMPL when the index is native int. With a TYP_LONG index whose low 32 bits are provably small but whose high 32 bits are unknown, the fold could prove (uint)idx < (uint)len using only the low half and incorrectly drop a required bounds check (memory corruption). Derive the width from the operand type, matching the other two consumers. Caught by Opus 4.7 (xhigh), Opus 4.8, and GPT-5.5 in parallel review. Also drop the now-dead #include "knownbits.h" from rangecheck.cpp left over from the previous cleanup commit. libraries.pmi: -13,920 bytes (was -14,515; ~600 byte loss is the correctness recovery -- those wins were unsound TYP_LONG bounds-check drops). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| // See if we can fold the relop based on known bits. This complements the range-based folding | ||
| // above (which is limited to TYP_INT) by reasoning about individual bits and TYP_LONG values. | ||
| if (varTypeIsIntegral(op1) && (op1VN != ValueNumStore::NoVN) && (op2VN != ValueNumStore::NoVN)) | ||
| { | ||
| const unsigned width = genTypeSize(genActualType(op1)) * BITS_PER_BYTE; | ||
| const KnownBits kb1 = KnownBits::Compute(this, op1VN, assertions); |
EgorBo
commented
Jun 12, 2026
Will try to re-implement from scratch later |
Closes#105333
Closes#111567
Most optimizing compilers use KnownBits to track ranges, e.g. LLVM, MSVC vs tracking Min-Max values.
We have
Rangewich is 32-bit only and will likely require lots of efforts to make it 64-bit, fix casts between 32-bit and 64-bit, fight with all kinds of overflows and correctness issues and still it's only good fo representing continues ranges.KnownBits looks like this:
My estimate it can bring 500k-600k diffs for win-x64 if extended (I've seen 450k with +400 LOC), current version should be
-320kbon win-x64 with somewhat nices improvements in non-tests collections.I'm not sure it can fully replace Range just like Range can't replace KnownBits, but some usages definitely can be re-routed to KnownBits.
This PR is basically an attempt to mimic LLVM's impl
Not done:
Diffs -
-662244 byteson linux-arm64