Uh oh!
There was an error while loading. Please reload this page.
arm64: Optimize ARM64 compare mask ExtractMostSignificantBits consumers - #129688
Conversation
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
EgorBo
commented
Jun 22, 2026
Isn't it the same as #126790 ? |
a74nh
commented
Jun 24, 2026
EgorBo
commented
Jun 24, 2026
@tannergooding Optimized all We still have a few places where we call Compare + EMSB instead of explicit IndexOf APIs, that is what my other PR tries to do: #126841 There will be a few places where we won't be able to replaces EMSB with IndexOf (e.g. iterators over multiple matches), though. But I don't think this PR does a good job for them either judging by the diffs - Basically, +600 LOC of JIT changes for 1 use-case (context) in benchmarks. |
If you want to make this transformation more useful, you need to borrow the assertionprop.cpp changes from #126790 - |
Teach assertion propagation on ARM64 to recognize Vector64/128 ExtractMostSignificantBits inputs whose value numbers represent per-element boolean masks. The helper recognizes comparison masks, all-zero/all-bits constants, boolean-preserving operations, and reaching PHI values, and marks the EMSB node with a HW intrinsic flag. Consume the flag in rationalization so existing ExtractMostSignificantBits rewrites can handle mask values that have flowed through locals, while keeping unsupported element types filtered out. Add coverage for a comparison mask stored in a local before PopCount(ExtractMostSignificantBits()).
EgorBo
commented
Jun 25, 2026
A lot bigger diffs now. |
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.
Uh oh!
There was an error while loading. Please reload this page.
- Share the ARM64 compare-mask intrinsic helper between assertion propagation and rationalization. - Document the widened unsigned compare-mask base type check and missing assertion-prop compiler argument. - Include the zero-or-all-bits-set hardware intrinsic flag in GenTree comparison. - Reuse the shuffle index-type helper when normalizing compare-mask base types. - Extend the compare-mask ExtractMostSignificantBits zero-count rewrite to cover LeadingZeroCount. - Add ARM64 ExtractMostSignificantBits LeadingZeroCount regression coverage. Change-Id: I074f573d5fabfe8b99ec6679267fe485f2209fd7
EgorBo
left a comment
There was a problem hiding this comment.
LGTM, thanks. Perhaps @tannergooding @dotnet/jit-contrib want to take a look at the rationilizer impl?
EgorBo
commented
Jul 8, 2026
@jonathandavies-arm CI build failures are related |
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.
Workflow state for the Holistic Review Orchestrator. {
"version": 5,
"last_dispatched_commit": "acabdd938d618f866d47deb0b29e52a70d7fdf5e",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "1d85f408cee362c3b693e8b2ccba114b976b317d",
"last_reviewed_commit": "acabdd938d618f866d47deb0b29e52a70d7fdf5e",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "1d85f408cee362c3b693e8b2ccba114b976b317d",
"last_recorded_worker_run_id": "29681922766",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "acabdd938d618f866d47deb0b29e52a70d7fdf5e",
"review_id": 4730551207
}
]
} |
There was a problem hiding this comment.
Holistic Review
Motivation: Justified. On ARM64 Vector*.ExtractMostSignificantBits has no single instruction and is emulated with a long sequence; when the input is a comparison mask (all-bits-set or zero per element) and the extracted bitmask is only consumed by == 0/!= 0, PopCount, or Leading/TrailingZeroCount, the full bitmask never needs to be materialized. The SuperPMI diffs (-480 bytes, -28% PerfScore over 39 contexts) confirm a real win.
Approach: Sound and idiomatic for the JIT. It recognizes comparison masks via IsVectorPerElementMask (extended to accept vector compares where intrinsicSimdBaseType element size >= the requested base type, valid since a larger-element all-set/zero mask reinterpreted at a smaller element size is still a per-element mask), propagates the fact onto single-def locals through reaching-VN analysis in optAssertionProp_HWIntrinsic, and rewrites the consumer in rationalize into horizontal MaxAcross/AddAcross/MinAcross reductions (with pairwise fallbacks for the 2-lane Vector64<uint> case that lacks a 2S across form). I hand-checked the index/sentinel encodings for the zero-count rewrites (Trailing: index+1/sentinel 33, then - 1; Leading: 31-index/sentinel 32) and they reproduce the scalar TrailingZeroCount/LeadingZeroCount results, including the 32 zero-mask case.
Summary: IsVectorPerElementMask widening rule and the local-mask metadata lifetime. See non-blocking notes below.
Detailed Findings
✅ Correctness of rewrites and mask recognition
The == 0/!= 0, PopCount, and Leading/TrailingZeroCount rewrites each verify IsHWIntrinsicCmpMaskExtractMsb before transforming, normalize the base type to unsigned, and cast the reduced scalar as unsigned to TYP_INT. The zero-count sentinel/index vectors correctly preserve the scalar semantics for the all-zero mask. gentree.cpp and valuenum.cpp keep their two IsVectorPerElementMask implementations in sync as the in-code comment requires.
✅ Relaxed assert in IsVectorPerElementMask
Replacing the assert(varTypeIsSIMD(...))/size asserts with early return false (gentree.cpp:33782) is appropriate now that callers pass through GT_LCL_VAR locals and reaching-VN types that may not match, avoiding false asserts while preserving the negative answer.
✅ Test quality
ExtractMostSignificantBits.cs combines runtime value assertions with ARM64-FULL-LINE disasm checks across byte/ushort/int and both Vector128 and Vector64, plus a ...ViaLocal variant that exercises the new single-def local-mask propagation path and an explicit zero-mask index case. This is strong, targeted coverage for the new paths.
💡 Duplicated pairwise-reduction scaffolding (follow-up, non-blocking)
The Vector64<uint> spill-clone-and-pairwise block (MakeDummyUse → ReplaceWithLclVar → gtClone → *Pairwise) is repeated almost verbatim in RewriteHWIntrinsicCmpMaskExtractMsb, ...PopCount, and ...ZeroCount, differing only by the reduction intrinsic. Extracting a small helper parameterized by the across/pairwise intrinsic pair would reduce the risk of the three copies drifting. Maintainability suggestion, not a blocker.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 192.2 AIC · ⌖ 15 AIC · ⊞ 10K
EgorBo
commented
Jul 30, 2026
@tannergooding does it look good? |
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.
tannergooding
commented
Jul 30, 2026
The changes overall look reasonable, but I had some questions and feedback. |
- Generalize SIMD mask fact propagation beyond Arm64 - Limit the Arm64 compare-mask fallback and document its temporary behavior - Simplify unsigned base-type normalization and explain the 64-bit exclusion - Pass the compiler to per-element mask checks so local metadata is preserved

See discussion at #121981 (comment)
Diffs