arm64: Improve predicate instruction usage - #129506

Open
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage
Open

arm64: Improve predicate instruction usage#129506
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage

Conversation

@jonathandavies-arm

Copy link
Copy Markdown
Contributor

SVE Predicate Instructions asm diffs

Branch: upstream/sve/predicate-instruction-usage

Collection / JIT / output paths

Collection

  • MCH: artifacts/asmdiff/custom_mch/sve_predicate_instructions.mch
  • Source test DLL: artifacts/tests/coreclr/linux.arm64.Checked/JIT/opt/SVE/PredicateInstructions/PredicateInstructions.dll

JITs

  • Base: artifacts/asmdiff/builds/5656cc9c/core_root/libclrjit.so
  • Diff: artifacts/bin/coreclr/linux.arm64.Checked/libclrjit.so

Output

  • Dasm base: artifacts/spmi/asm.sve_predicate_instructions/base/
  • Dasm diff: artifacts/spmi/asm.sve_predicate_instructions/diff/
  • Log: artifacts/spmi/superpmi.2.log

Summary

3 asm diffs were found, all in PredicateInstructions.

ContextMethodCode bytesPerfScoreBytes allocated
10AndMask(Vector<short>, Vector<short>)52 -> 48 (-4)23.50 -> 21.50 (-2.00)60992 -> 60232 (-760)
11BitwiseClearMask(Vector<short>, Vector<short>)44 -> 40 (-4)18.00 -> 17.50 (-0.50)59760 -> 59544 (-216)
16TransposeEvenAndMask(Vector<short>, Vector<short>, Vector<short>)52 -> 52 (+0)24.50 -> 24.50 (+0.00)62264 -> 61360 (-904)

Notable diffs

Context 10: AndMask

Removed a redundant ptrue and now uses the first predicate as the governing predicate for and:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b+ and p0.b, p0/z, p0.b, p1.b
Context 11: BitwiseClearMask

Uses predicate bic directly instead of materializing predicate results into vectors and doing vector bic:

- mov z16.h, p0/z, #1- ptrue p0.h- cmpeq p0.h, p0/z, z0.h, z1.h+ ptrue p1.h+ cmpeq p1.h, p1/z, z0.h, z1.h+ bic p0.b, p0/z, p0.b, p1.b
mov z0.h, p0/z, #1
- bic v0.8h, v16.8h, v0.8h
Context 16: TransposeEvenAndMask

Removes one ptrue p2.h; total code size and PerfScore are unchanged:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b- cmpgt p1.h, p2/z, z1.h, z2.h+ and p0.b, p0/z, p0.b, p1.b+ ptrue p1.h+ cmpgt p1.h, p1/z, z1.h, z2.h

@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 17, 2026
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

@a74nh

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

We didn't mention this in our discussion, but, yes I think we should hold of from merging this until #128326 is complete.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_reviewed_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_recorded_worker_run_id": "29677704668",
"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": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"review_id": 4730527203
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: The motivation is real and well-evidenced. On SVE, boolean masks are naturally predicates, but the JIT currently materializes predicate results into vectors (mov z, p/z, #1) and re-compares them to zero before doing logical ops, then compares back. Recovering the predicate form lets the JIT emit and/bic/orr/eor/sel/zip1 directly on predicate registers and reuse the first predicate as the governing predicate, removing redundant ptrue/mov sequences. The PR's own SPMI diffs show real code-size and allocation wins.

Approach: The approach is reasonable and consistent with existing patterns: recognize mask-shaped operands (ConvertMaskToVector, real masks, vector zero, and constant 0/1 vectors) during gtFoldExprHWIntrinsic and in arm64 lowering, and fold to the _Predicates intrinsic variants. Importing Sve.BitwiseClear as AND(NOT(...)) and recovering bic in lowering mirrors the established AdvSimd handling. The codegen using op1Reg as the governing predicate matches the documented and Pd, Pn/z, Pn, Pm semantics.

Summary: ⚠️ Needs Changes. The direction and most of the implementation look sound, but TryLowerSvePredicateBitwiseClear ends with an unconditional return true; in its fall-through path. Since the helper now runs for every arm64 vector GT_AND, a true result when no predicate pattern matched causes the caller to break and skip the pre-existing AdvSimd ~op1 & op2 → BitwiseClear (AndNot) folding for ordinary vector ANDs. This is a functional regression outside the intended SVE-predicate scope and is not covered by the added SVE-only tests. See the inline comment for details.


Detailed Findings

❌ Correctness — Unconditional return true; disables AdvSimd AndNot folding

Flagged inline at src/coreclr/jit/lowerarmarch.cpp fall-through return. The final return true; should be return false; so that non-predicate vector ANDs fall through to the existing NI_AdvSimd_BitwiseClear/AndNot recognition in LowerHWIntrinsic. As written, that established optimization is bypassed for all arm64 vector GT_AND whose operands are not mask-shaped.

⚠️ Test coverage — No coverage for the non-predicate AndNot path

The added tests in PredicateInstructions.cs exercise only the new SVE-predicate folds (all inputs mask-shaped). None exercise an ordinary vector ~a & b / Vector*.AndNot on arm64, which is exactly the path broken by the finding above. A regression test covering that AdvSimd AndNot lowering would prevent this class of regression.

✅ Codegen and fold logic — Predicate variants look correct

The NI_Sve_And_Predicates/NI_Sve_BitwiseClear_Predicates codegen reusing op1Reg as the governing predicate matches the SVE semantics in the PR description, and the 0/1-vector-to-mask recognition in gtFoldExprHWIntrinsic is guarded to arm64 + masked HW intrinsics and rejects non-0/1 constants. TryLowerSveConvertVectorToMask correctly only elides the conversion when the source is already a mask.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 141.4 AIC · ⌖ 10.7 AIC · ⊞ 10K

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
return true;
}

return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fall-through return true; looks like a bug. When none of the three predicate patterns match (the common case for ordinary vector GT_AND), the function still returns true. The caller does:

if ((oper == GT_AND) && TryLowerSvePredicateBitwiseClear(node, &intrinsicId, &oper))
{
break;
}

so a true result breaks out of the GT_AND/GT_OR case and skips the pre-existing AdvSimd folding that turns ~op1 & op2 into NI_AdvSimd_BitwiseClear (AndNot). Because this helper is now invoked for every arm64 vector GT_AND, that established AndNot recognition is effectively disabled for all non-predicate vector ANDs whenever the operands are not mask-shaped — a functional regression well beyond the intended SVE-predicate scope.

This should almost certainly return false; here so the caller falls through to the existing AdvSimd transform. (A regression test exercising e.g. Vector128.AndNot / ~a & b on arm64 would catch this; the current SVE-only tests do not.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've fixed the return true issue.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@jonathandavies-arm I'll take a look here now that #128326 has gone in, though it looks like there's merge conflicts

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/hwintrinsicarm64.cpp
#	src/coreclr/jit/lowerarmarch.cpp
#	src/tests/JIT/opt/SVE/PredicateInstructions.cs
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

@jonathandavies-arm

Copy link
Copy Markdown
ContributorAuthor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

This should be fixed now. I'll keep an eye on the tests.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

There's a merge conflict, friendly ping @jonathandavies-arm

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/lowerarmarch.cpp

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves ARM64 SVE predicate-codegen by recognizing more mask-shaped operands (including constant 0/1 vectors), enabling additional folding to predicate forms, and tightening codegen/containment for certain predicate operations. This is primarily a JIT/codegen optimization backed by targeted SVE JIT tests.

Changes:

  • Teach folding/lowering to treat constant 0/1 vectors (and some inverted forms) as mask-shaped so more SVE ops fold to predicate variants.
  • Add lowering/codegen support to emit more efficient predicate and/bic forms (avoiding redundant ptrue) and remove redundant vector↔mask conversions.
  • Extend the SVE predicate instruction test coverage to exercise the new folding/lowering cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/tests/JIT/opt/SVE/PredicateInstructions.csAdds new SVE test methods/assertions covering 0/1 constant “mask-shaped” folding and predicate bic lowering.
src/coreclr/jit/lowerarmarch.cppAdds SVE mask-operand recognition/conversion helpers, lowers mask-shaped AND/NOT into predicate BIC, removes redundant ConvertVectorToMask, and refines embedded-mask wrapping/containment behavior.
src/coreclr/jit/lower.hDeclares new SVE-lowering helpers under FEATURE_MASKED_HW_INTRINSICS.
src/coreclr/jit/hwintrinsiccodegenarm64.cppUpdates codegen for predicate and/bic to use the first predicate as the governing predicate.
src/coreclr/jit/hwintrinsicarm64.cppAdjusts import for NI_Sve_BitwiseClear to decompose to NOT+AND for later SVE predicate lowering.
src/coreclr/jit/gentree.cppExtends HWIntrinsic folding to treat 0/1 constant vectors as mask-shaped for all-mask SVE variants (including ConditionalSelect).

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
Change-Id: I8398ba28db6207ec720b1a31e2d8c3bc3a367142
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Unfortunately looks like there's still a test failure

Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 163, 24, 1, 177, 161, 119, 75, 0, 11, 254, 0, 0, 0, 0)
left: (126, 118, 214, 86, 222, 102, 124, 252, 33, 13, 184, 112, 215, 251, 71, 199)
right: (151, 206, 181, 129, 228, 147, 128, 248, 118, 227, 192, 95, 142, 140, 109, 226)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

Change-Id: I92c28f208bd65690604d472b8546b720605cdd5b
Comment threadsrc/coreclr/jit/gentree.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit, move block comment down next to gtFoldExprHWIntrinsic's definition

Comment threadsrc/coreclr/jit/gentree.cpp Outdated
canFold &= tree->Op(i)->OperIsConvertMaskToVector();
// Fold SVE intrinsics such as And/Or/Xor/ZipLow to their
// all-mask variants when every operand is mask-shaped.
canFold &= IsAllMaskVariantOperand(tree->Op(i), simdBaseType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the transformation for this is incorrect if CreateBreakAfterMask is NOT here

Sve.And(Sve.CompareGreaterThan(a, b), Vector.Create<short>(1))

Should produce a simd vector with lanes either zero or one. But now the morph transform activates this and wraps it in "ConvertMaskToVector", causing result lanes to either be zero or allbitsset.

Comment threadsrc/coreclr/jit/hwintrinsicarm64.cpp Outdated
// in lowering instead so decompose into the individual operations
// on import

op2 = gtNewSimdUnOpNode(GT_NOT, retType, op2, simdBaseType, simdSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's unclear to me why we no longer fold here, can you explain?

(Also NIT but if possible probably better to leave these combined and fold only if intrinsic==NI_AdvSimd_BitwiseClear just to cut down code duplication)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We don’t fold the NOT in the SVE case so that lowering can recognize mask shaped operands and emit the predicate BIC form.

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
// IsSveMaskOperand:
// Return true if "op" can be represented directly as an SVE predicate.
//
static bool IsSveMaskOperand(GenTree* op)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be combined with IsAllMaskVariantOperand from gentree

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've combined them


node->ResetHWIntrinsicId(NI_Sve_BitwiseClear_Predicates, op1, op2);
node->gtType = TYP_MASK;
*intrinsicId = NI_Sve_BitwiseClear_Predicates;

@dhartglassMSFTdhartglassMSFTSep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The following code should trigger a INS_invalid assert, trying to do codegen for BitwiseClear_Predicates with floating point basetype:

 [MethodImpl(MethodImplOptions.NoInlining)]
private static Vector<float> ReproFloat(Vector<float> left, Vector<float> right)
{
Vector<float> firstMask = Sve.CompareLessThan(left, right);
Vector<float> secondMask = Sve.ZipLow(
Sve.CompareGreaterThan(left, right),
Sve.CompareEqual(left, right));
return firstMask & ~secondMask;
}

Maybe the hw intrinsic list table just needs a new entries for BitwiseClear_Predicates for fp type, or just reject floats if they're not allowed

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added the floating point mappings to the intrinsics.

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

Labels

arch-arm64area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@jonathandavies-arm@dhartglassMSFT@a74nh@JulieLeeMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

arm64: Improve predicate instruction usage - #129506

Open
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage
Open

arm64: Improve predicate instruction usage#129506
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage

Conversation

@jonathandavies-arm

Copy link
Copy Markdown
Contributor

SVE Predicate Instructions asm diffs

Branch: upstream/sve/predicate-instruction-usage

Collection / JIT / output paths

Collection

  • MCH: artifacts/asmdiff/custom_mch/sve_predicate_instructions.mch
  • Source test DLL: artifacts/tests/coreclr/linux.arm64.Checked/JIT/opt/SVE/PredicateInstructions/PredicateInstructions.dll

JITs

  • Base: artifacts/asmdiff/builds/5656cc9c/core_root/libclrjit.so
  • Diff: artifacts/bin/coreclr/linux.arm64.Checked/libclrjit.so

Output

  • Dasm base: artifacts/spmi/asm.sve_predicate_instructions/base/
  • Dasm diff: artifacts/spmi/asm.sve_predicate_instructions/diff/
  • Log: artifacts/spmi/superpmi.2.log

Summary

3 asm diffs were found, all in PredicateInstructions.

ContextMethodCode bytesPerfScoreBytes allocated
10AndMask(Vector<short>, Vector<short>)52 -> 48 (-4)23.50 -> 21.50 (-2.00)60992 -> 60232 (-760)
11BitwiseClearMask(Vector<short>, Vector<short>)44 -> 40 (-4)18.00 -> 17.50 (-0.50)59760 -> 59544 (-216)
16TransposeEvenAndMask(Vector<short>, Vector<short>, Vector<short>)52 -> 52 (+0)24.50 -> 24.50 (+0.00)62264 -> 61360 (-904)

Notable diffs

Context 10: AndMask

Removed a redundant ptrue and now uses the first predicate as the governing predicate for and:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b+ and p0.b, p0/z, p0.b, p1.b
Context 11: BitwiseClearMask

Uses predicate bic directly instead of materializing predicate results into vectors and doing vector bic:

- mov z16.h, p0/z, #1- ptrue p0.h- cmpeq p0.h, p0/z, z0.h, z1.h+ ptrue p1.h+ cmpeq p1.h, p1/z, z0.h, z1.h+ bic p0.b, p0/z, p0.b, p1.b
mov z0.h, p0/z, #1
- bic v0.8h, v16.8h, v0.8h
Context 16: TransposeEvenAndMask

Removes one ptrue p2.h; total code size and PerfScore are unchanged:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b- cmpgt p1.h, p2/z, z1.h, z2.h+ and p0.b, p0/z, p0.b, p1.b+ ptrue p1.h+ cmpgt p1.h, p1/z, z1.h, z2.h

@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 17, 2026
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

@a74nh

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

We didn't mention this in our discussion, but, yes I think we should hold of from merging this until #128326 is complete.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_reviewed_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_recorded_worker_run_id": "29677704668",
"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": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"review_id": 4730527203
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: The motivation is real and well-evidenced. On SVE, boolean masks are naturally predicates, but the JIT currently materializes predicate results into vectors (mov z, p/z, #1) and re-compares them to zero before doing logical ops, then compares back. Recovering the predicate form lets the JIT emit and/bic/orr/eor/sel/zip1 directly on predicate registers and reuse the first predicate as the governing predicate, removing redundant ptrue/mov sequences. The PR's own SPMI diffs show real code-size and allocation wins.

Approach: The approach is reasonable and consistent with existing patterns: recognize mask-shaped operands (ConvertMaskToVector, real masks, vector zero, and constant 0/1 vectors) during gtFoldExprHWIntrinsic and in arm64 lowering, and fold to the _Predicates intrinsic variants. Importing Sve.BitwiseClear as AND(NOT(...)) and recovering bic in lowering mirrors the established AdvSimd handling. The codegen using op1Reg as the governing predicate matches the documented and Pd, Pn/z, Pn, Pm semantics.

Summary: ⚠️ Needs Changes. The direction and most of the implementation look sound, but TryLowerSvePredicateBitwiseClear ends with an unconditional return true; in its fall-through path. Since the helper now runs for every arm64 vector GT_AND, a true result when no predicate pattern matched causes the caller to break and skip the pre-existing AdvSimd ~op1 & op2 → BitwiseClear (AndNot) folding for ordinary vector ANDs. This is a functional regression outside the intended SVE-predicate scope and is not covered by the added SVE-only tests. See the inline comment for details.


Detailed Findings

❌ Correctness — Unconditional return true; disables AdvSimd AndNot folding

Flagged inline at src/coreclr/jit/lowerarmarch.cpp fall-through return. The final return true; should be return false; so that non-predicate vector ANDs fall through to the existing NI_AdvSimd_BitwiseClear/AndNot recognition in LowerHWIntrinsic. As written, that established optimization is bypassed for all arm64 vector GT_AND whose operands are not mask-shaped.

⚠️ Test coverage — No coverage for the non-predicate AndNot path

The added tests in PredicateInstructions.cs exercise only the new SVE-predicate folds (all inputs mask-shaped). None exercise an ordinary vector ~a & b / Vector*.AndNot on arm64, which is exactly the path broken by the finding above. A regression test covering that AdvSimd AndNot lowering would prevent this class of regression.

✅ Codegen and fold logic — Predicate variants look correct

The NI_Sve_And_Predicates/NI_Sve_BitwiseClear_Predicates codegen reusing op1Reg as the governing predicate matches the SVE semantics in the PR description, and the 0/1-vector-to-mask recognition in gtFoldExprHWIntrinsic is guarded to arm64 + masked HW intrinsics and rejects non-0/1 constants. TryLowerSveConvertVectorToMask correctly only elides the conversion when the source is already a mask.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 141.4 AIC · ⌖ 10.7 AIC · ⊞ 10K

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
return true;
}

return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fall-through return true; looks like a bug. When none of the three predicate patterns match (the common case for ordinary vector GT_AND), the function still returns true. The caller does:

if ((oper == GT_AND) && TryLowerSvePredicateBitwiseClear(node, &intrinsicId, &oper))
{
break;
}

so a true result breaks out of the GT_AND/GT_OR case and skips the pre-existing AdvSimd folding that turns ~op1 & op2 into NI_AdvSimd_BitwiseClear (AndNot). Because this helper is now invoked for every arm64 vector GT_AND, that established AndNot recognition is effectively disabled for all non-predicate vector ANDs whenever the operands are not mask-shaped — a functional regression well beyond the intended SVE-predicate scope.

This should almost certainly return false; here so the caller falls through to the existing AdvSimd transform. (A regression test exercising e.g. Vector128.AndNot / ~a & b on arm64 would catch this; the current SVE-only tests do not.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've fixed the return true issue.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@jonathandavies-arm I'll take a look here now that #128326 has gone in, though it looks like there's merge conflicts

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/hwintrinsicarm64.cpp
#	src/coreclr/jit/lowerarmarch.cpp
#	src/tests/JIT/opt/SVE/PredicateInstructions.cs
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

@jonathandavies-arm

Copy link
Copy Markdown
ContributorAuthor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

This should be fixed now. I'll keep an eye on the tests.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

There's a merge conflict, friendly ping @jonathandavies-arm

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/lowerarmarch.cpp

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves ARM64 SVE predicate-codegen by recognizing more mask-shaped operands (including constant 0/1 vectors), enabling additional folding to predicate forms, and tightening codegen/containment for certain predicate operations. This is primarily a JIT/codegen optimization backed by targeted SVE JIT tests.

Changes:

  • Teach folding/lowering to treat constant 0/1 vectors (and some inverted forms) as mask-shaped so more SVE ops fold to predicate variants.
  • Add lowering/codegen support to emit more efficient predicate and/bic forms (avoiding redundant ptrue) and remove redundant vector↔mask conversions.
  • Extend the SVE predicate instruction test coverage to exercise the new folding/lowering cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/tests/JIT/opt/SVE/PredicateInstructions.csAdds new SVE test methods/assertions covering 0/1 constant “mask-shaped” folding and predicate bic lowering.
src/coreclr/jit/lowerarmarch.cppAdds SVE mask-operand recognition/conversion helpers, lowers mask-shaped AND/NOT into predicate BIC, removes redundant ConvertVectorToMask, and refines embedded-mask wrapping/containment behavior.
src/coreclr/jit/lower.hDeclares new SVE-lowering helpers under FEATURE_MASKED_HW_INTRINSICS.
src/coreclr/jit/hwintrinsiccodegenarm64.cppUpdates codegen for predicate and/bic to use the first predicate as the governing predicate.
src/coreclr/jit/hwintrinsicarm64.cppAdjusts import for NI_Sve_BitwiseClear to decompose to NOT+AND for later SVE predicate lowering.
src/coreclr/jit/gentree.cppExtends HWIntrinsic folding to treat 0/1 constant vectors as mask-shaped for all-mask SVE variants (including ConditionalSelect).

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
Change-Id: I8398ba28db6207ec720b1a31e2d8c3bc3a367142
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Unfortunately looks like there's still a test failure

Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 163, 24, 1, 177, 161, 119, 75, 0, 11, 254, 0, 0, 0, 0)
left: (126, 118, 214, 86, 222, 102, 124, 252, 33, 13, 184, 112, 215, 251, 71, 199)
right: (151, 206, 181, 129, 228, 147, 128, 248, 118, 227, 192, 95, 142, 140, 109, 226)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

Change-Id: I92c28f208bd65690604d472b8546b720605cdd5b
Comment threadsrc/coreclr/jit/gentree.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit, move block comment down next to gtFoldExprHWIntrinsic's definition

Comment threadsrc/coreclr/jit/gentree.cpp Outdated
canFold &= tree->Op(i)->OperIsConvertMaskToVector();
// Fold SVE intrinsics such as And/Or/Xor/ZipLow to their
// all-mask variants when every operand is mask-shaped.
canFold &= IsAllMaskVariantOperand(tree->Op(i), simdBaseType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the transformation for this is incorrect if CreateBreakAfterMask is NOT here

Sve.And(Sve.CompareGreaterThan(a, b), Vector.Create<short>(1))

Should produce a simd vector with lanes either zero or one. But now the morph transform activates this and wraps it in "ConvertMaskToVector", causing result lanes to either be zero or allbitsset.

Comment threadsrc/coreclr/jit/hwintrinsicarm64.cpp Outdated
// in lowering instead so decompose into the individual operations
// on import

op2 = gtNewSimdUnOpNode(GT_NOT, retType, op2, simdBaseType, simdSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's unclear to me why we no longer fold here, can you explain?

(Also NIT but if possible probably better to leave these combined and fold only if intrinsic==NI_AdvSimd_BitwiseClear just to cut down code duplication)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We don’t fold the NOT in the SVE case so that lowering can recognize mask shaped operands and emit the predicate BIC form.

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
// IsSveMaskOperand:
// Return true if "op" can be represented directly as an SVE predicate.
//
static bool IsSveMaskOperand(GenTree* op)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be combined with IsAllMaskVariantOperand from gentree

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've combined them


node->ResetHWIntrinsicId(NI_Sve_BitwiseClear_Predicates, op1, op2);
node->gtType = TYP_MASK;
*intrinsicId = NI_Sve_BitwiseClear_Predicates;

@dhartglassMSFTdhartglassMSFTSep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The following code should trigger a INS_invalid assert, trying to do codegen for BitwiseClear_Predicates with floating point basetype:

 [MethodImpl(MethodImplOptions.NoInlining)]
private static Vector<float> ReproFloat(Vector<float> left, Vector<float> right)
{
Vector<float> firstMask = Sve.CompareLessThan(left, right);
Vector<float> secondMask = Sve.ZipLow(
Sve.CompareGreaterThan(left, right),
Sve.CompareEqual(left, right));
return firstMask & ~secondMask;
}

Maybe the hw intrinsic list table just needs a new entries for BitwiseClear_Predicates for fp type, or just reject floats if they're not allowed

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added the floating point mappings to the intrinsics.

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

Labels

arch-arm64area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@jonathandavies-arm@dhartglassMSFT@a74nh@JulieLeeMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

arm64: Improve predicate instruction usage - #129506

Open
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage
Open

arm64: Improve predicate instruction usage#129506
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage

Conversation

@jonathandavies-arm

Copy link
Copy Markdown
Contributor

SVE Predicate Instructions asm diffs

Branch: upstream/sve/predicate-instruction-usage

Collection / JIT / output paths

Collection

  • MCH: artifacts/asmdiff/custom_mch/sve_predicate_instructions.mch
  • Source test DLL: artifacts/tests/coreclr/linux.arm64.Checked/JIT/opt/SVE/PredicateInstructions/PredicateInstructions.dll

JITs

  • Base: artifacts/asmdiff/builds/5656cc9c/core_root/libclrjit.so
  • Diff: artifacts/bin/coreclr/linux.arm64.Checked/libclrjit.so

Output

  • Dasm base: artifacts/spmi/asm.sve_predicate_instructions/base/
  • Dasm diff: artifacts/spmi/asm.sve_predicate_instructions/diff/
  • Log: artifacts/spmi/superpmi.2.log

Summary

3 asm diffs were found, all in PredicateInstructions.

ContextMethodCode bytesPerfScoreBytes allocated
10AndMask(Vector<short>, Vector<short>)52 -> 48 (-4)23.50 -> 21.50 (-2.00)60992 -> 60232 (-760)
11BitwiseClearMask(Vector<short>, Vector<short>)44 -> 40 (-4)18.00 -> 17.50 (-0.50)59760 -> 59544 (-216)
16TransposeEvenAndMask(Vector<short>, Vector<short>, Vector<short>)52 -> 52 (+0)24.50 -> 24.50 (+0.00)62264 -> 61360 (-904)

Notable diffs

Context 10: AndMask

Removed a redundant ptrue and now uses the first predicate as the governing predicate for and:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b+ and p0.b, p0/z, p0.b, p1.b
Context 11: BitwiseClearMask

Uses predicate bic directly instead of materializing predicate results into vectors and doing vector bic:

- mov z16.h, p0/z, #1- ptrue p0.h- cmpeq p0.h, p0/z, z0.h, z1.h+ ptrue p1.h+ cmpeq p1.h, p1/z, z0.h, z1.h+ bic p0.b, p0/z, p0.b, p1.b
mov z0.h, p0/z, #1
- bic v0.8h, v16.8h, v0.8h
Context 16: TransposeEvenAndMask

Removes one ptrue p2.h; total code size and PerfScore are unchanged:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b- cmpgt p1.h, p2/z, z1.h, z2.h+ and p0.b, p0/z, p0.b, p1.b+ ptrue p1.h+ cmpgt p1.h, p1/z, z1.h, z2.h

@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 17, 2026
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

@a74nh

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

We didn't mention this in our discussion, but, yes I think we should hold of from merging this until #128326 is complete.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_reviewed_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_recorded_worker_run_id": "29677704668",
"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": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"review_id": 4730527203
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: The motivation is real and well-evidenced. On SVE, boolean masks are naturally predicates, but the JIT currently materializes predicate results into vectors (mov z, p/z, #1) and re-compares them to zero before doing logical ops, then compares back. Recovering the predicate form lets the JIT emit and/bic/orr/eor/sel/zip1 directly on predicate registers and reuse the first predicate as the governing predicate, removing redundant ptrue/mov sequences. The PR's own SPMI diffs show real code-size and allocation wins.

Approach: The approach is reasonable and consistent with existing patterns: recognize mask-shaped operands (ConvertMaskToVector, real masks, vector zero, and constant 0/1 vectors) during gtFoldExprHWIntrinsic and in arm64 lowering, and fold to the _Predicates intrinsic variants. Importing Sve.BitwiseClear as AND(NOT(...)) and recovering bic in lowering mirrors the established AdvSimd handling. The codegen using op1Reg as the governing predicate matches the documented and Pd, Pn/z, Pn, Pm semantics.

Summary: ⚠️ Needs Changes. The direction and most of the implementation look sound, but TryLowerSvePredicateBitwiseClear ends with an unconditional return true; in its fall-through path. Since the helper now runs for every arm64 vector GT_AND, a true result when no predicate pattern matched causes the caller to break and skip the pre-existing AdvSimd ~op1 & op2 → BitwiseClear (AndNot) folding for ordinary vector ANDs. This is a functional regression outside the intended SVE-predicate scope and is not covered by the added SVE-only tests. See the inline comment for details.


Detailed Findings

❌ Correctness — Unconditional return true; disables AdvSimd AndNot folding

Flagged inline at src/coreclr/jit/lowerarmarch.cpp fall-through return. The final return true; should be return false; so that non-predicate vector ANDs fall through to the existing NI_AdvSimd_BitwiseClear/AndNot recognition in LowerHWIntrinsic. As written, that established optimization is bypassed for all arm64 vector GT_AND whose operands are not mask-shaped.

⚠️ Test coverage — No coverage for the non-predicate AndNot path

The added tests in PredicateInstructions.cs exercise only the new SVE-predicate folds (all inputs mask-shaped). None exercise an ordinary vector ~a & b / Vector*.AndNot on arm64, which is exactly the path broken by the finding above. A regression test covering that AdvSimd AndNot lowering would prevent this class of regression.

✅ Codegen and fold logic — Predicate variants look correct

The NI_Sve_And_Predicates/NI_Sve_BitwiseClear_Predicates codegen reusing op1Reg as the governing predicate matches the SVE semantics in the PR description, and the 0/1-vector-to-mask recognition in gtFoldExprHWIntrinsic is guarded to arm64 + masked HW intrinsics and rejects non-0/1 constants. TryLowerSveConvertVectorToMask correctly only elides the conversion when the source is already a mask.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 141.4 AIC · ⌖ 10.7 AIC · ⊞ 10K

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
return true;
}

return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fall-through return true; looks like a bug. When none of the three predicate patterns match (the common case for ordinary vector GT_AND), the function still returns true. The caller does:

if ((oper == GT_AND) && TryLowerSvePredicateBitwiseClear(node, &intrinsicId, &oper))
{
break;
}

so a true result breaks out of the GT_AND/GT_OR case and skips the pre-existing AdvSimd folding that turns ~op1 & op2 into NI_AdvSimd_BitwiseClear (AndNot). Because this helper is now invoked for every arm64 vector GT_AND, that established AndNot recognition is effectively disabled for all non-predicate vector ANDs whenever the operands are not mask-shaped — a functional regression well beyond the intended SVE-predicate scope.

This should almost certainly return false; here so the caller falls through to the existing AdvSimd transform. (A regression test exercising e.g. Vector128.AndNot / ~a & b on arm64 would catch this; the current SVE-only tests do not.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've fixed the return true issue.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@jonathandavies-arm I'll take a look here now that #128326 has gone in, though it looks like there's merge conflicts

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/hwintrinsicarm64.cpp
#	src/coreclr/jit/lowerarmarch.cpp
#	src/tests/JIT/opt/SVE/PredicateInstructions.cs
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

@jonathandavies-arm

Copy link
Copy Markdown
ContributorAuthor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

This should be fixed now. I'll keep an eye on the tests.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

There's a merge conflict, friendly ping @jonathandavies-arm

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/lowerarmarch.cpp

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves ARM64 SVE predicate-codegen by recognizing more mask-shaped operands (including constant 0/1 vectors), enabling additional folding to predicate forms, and tightening codegen/containment for certain predicate operations. This is primarily a JIT/codegen optimization backed by targeted SVE JIT tests.

Changes:

  • Teach folding/lowering to treat constant 0/1 vectors (and some inverted forms) as mask-shaped so more SVE ops fold to predicate variants.
  • Add lowering/codegen support to emit more efficient predicate and/bic forms (avoiding redundant ptrue) and remove redundant vector↔mask conversions.
  • Extend the SVE predicate instruction test coverage to exercise the new folding/lowering cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/tests/JIT/opt/SVE/PredicateInstructions.csAdds new SVE test methods/assertions covering 0/1 constant “mask-shaped” folding and predicate bic lowering.
src/coreclr/jit/lowerarmarch.cppAdds SVE mask-operand recognition/conversion helpers, lowers mask-shaped AND/NOT into predicate BIC, removes redundant ConvertVectorToMask, and refines embedded-mask wrapping/containment behavior.
src/coreclr/jit/lower.hDeclares new SVE-lowering helpers under FEATURE_MASKED_HW_INTRINSICS.
src/coreclr/jit/hwintrinsiccodegenarm64.cppUpdates codegen for predicate and/bic to use the first predicate as the governing predicate.
src/coreclr/jit/hwintrinsicarm64.cppAdjusts import for NI_Sve_BitwiseClear to decompose to NOT+AND for later SVE predicate lowering.
src/coreclr/jit/gentree.cppExtends HWIntrinsic folding to treat 0/1 constant vectors as mask-shaped for all-mask SVE variants (including ConditionalSelect).

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
Change-Id: I8398ba28db6207ec720b1a31e2d8c3bc3a367142
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Unfortunately looks like there's still a test failure

Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 163, 24, 1, 177, 161, 119, 75, 0, 11, 254, 0, 0, 0, 0)
left: (126, 118, 214, 86, 222, 102, 124, 252, 33, 13, 184, 112, 215, 251, 71, 199)
right: (151, 206, 181, 129, 228, 147, 128, 248, 118, 227, 192, 95, 142, 140, 109, 226)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

Change-Id: I92c28f208bd65690604d472b8546b720605cdd5b
Comment threadsrc/coreclr/jit/gentree.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit, move block comment down next to gtFoldExprHWIntrinsic's definition

Comment threadsrc/coreclr/jit/gentree.cpp Outdated
canFold &= tree->Op(i)->OperIsConvertMaskToVector();
// Fold SVE intrinsics such as And/Or/Xor/ZipLow to their
// all-mask variants when every operand is mask-shaped.
canFold &= IsAllMaskVariantOperand(tree->Op(i), simdBaseType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the transformation for this is incorrect if CreateBreakAfterMask is NOT here

Sve.And(Sve.CompareGreaterThan(a, b), Vector.Create<short>(1))

Should produce a simd vector with lanes either zero or one. But now the morph transform activates this and wraps it in "ConvertMaskToVector", causing result lanes to either be zero or allbitsset.

Comment threadsrc/coreclr/jit/hwintrinsicarm64.cpp Outdated
// in lowering instead so decompose into the individual operations
// on import

op2 = gtNewSimdUnOpNode(GT_NOT, retType, op2, simdBaseType, simdSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's unclear to me why we no longer fold here, can you explain?

(Also NIT but if possible probably better to leave these combined and fold only if intrinsic==NI_AdvSimd_BitwiseClear just to cut down code duplication)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We don’t fold the NOT in the SVE case so that lowering can recognize mask shaped operands and emit the predicate BIC form.

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
// IsSveMaskOperand:
// Return true if "op" can be represented directly as an SVE predicate.
//
static bool IsSveMaskOperand(GenTree* op)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be combined with IsAllMaskVariantOperand from gentree

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've combined them


node->ResetHWIntrinsicId(NI_Sve_BitwiseClear_Predicates, op1, op2);
node->gtType = TYP_MASK;
*intrinsicId = NI_Sve_BitwiseClear_Predicates;

@dhartglassMSFTdhartglassMSFTSep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The following code should trigger a INS_invalid assert, trying to do codegen for BitwiseClear_Predicates with floating point basetype:

 [MethodImpl(MethodImplOptions.NoInlining)]
private static Vector<float> ReproFloat(Vector<float> left, Vector<float> right)
{
Vector<float> firstMask = Sve.CompareLessThan(left, right);
Vector<float> secondMask = Sve.ZipLow(
Sve.CompareGreaterThan(left, right),
Sve.CompareEqual(left, right));
return firstMask & ~secondMask;
}

Maybe the hw intrinsic list table just needs a new entries for BitwiseClear_Predicates for fp type, or just reject floats if they're not allowed

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added the floating point mappings to the intrinsics.

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

Labels

arch-arm64area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@jonathandavies-arm@dhartglassMSFT@a74nh@JulieLeeMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

arm64: Improve predicate instruction usage - #129506

Open
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage
Open

arm64: Improve predicate instruction usage#129506
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage

Conversation

@jonathandavies-arm

Copy link
Copy Markdown
Contributor

SVE Predicate Instructions asm diffs

Branch: upstream/sve/predicate-instruction-usage

Collection / JIT / output paths

Collection

  • MCH: artifacts/asmdiff/custom_mch/sve_predicate_instructions.mch
  • Source test DLL: artifacts/tests/coreclr/linux.arm64.Checked/JIT/opt/SVE/PredicateInstructions/PredicateInstructions.dll

JITs

  • Base: artifacts/asmdiff/builds/5656cc9c/core_root/libclrjit.so
  • Diff: artifacts/bin/coreclr/linux.arm64.Checked/libclrjit.so

Output

  • Dasm base: artifacts/spmi/asm.sve_predicate_instructions/base/
  • Dasm diff: artifacts/spmi/asm.sve_predicate_instructions/diff/
  • Log: artifacts/spmi/superpmi.2.log

Summary

3 asm diffs were found, all in PredicateInstructions.

ContextMethodCode bytesPerfScoreBytes allocated
10AndMask(Vector<short>, Vector<short>)52 -> 48 (-4)23.50 -> 21.50 (-2.00)60992 -> 60232 (-760)
11BitwiseClearMask(Vector<short>, Vector<short>)44 -> 40 (-4)18.00 -> 17.50 (-0.50)59760 -> 59544 (-216)
16TransposeEvenAndMask(Vector<short>, Vector<short>, Vector<short>)52 -> 52 (+0)24.50 -> 24.50 (+0.00)62264 -> 61360 (-904)

Notable diffs

Context 10: AndMask

Removed a redundant ptrue and now uses the first predicate as the governing predicate for and:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b+ and p0.b, p0/z, p0.b, p1.b
Context 11: BitwiseClearMask

Uses predicate bic directly instead of materializing predicate results into vectors and doing vector bic:

- mov z16.h, p0/z, #1- ptrue p0.h- cmpeq p0.h, p0/z, z0.h, z1.h+ ptrue p1.h+ cmpeq p1.h, p1/z, z0.h, z1.h+ bic p0.b, p0/z, p0.b, p1.b
mov z0.h, p0/z, #1
- bic v0.8h, v16.8h, v0.8h
Context 16: TransposeEvenAndMask

Removes one ptrue p2.h; total code size and PerfScore are unchanged:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b- cmpgt p1.h, p2/z, z1.h, z2.h+ and p0.b, p0/z, p0.b, p1.b+ ptrue p1.h+ cmpgt p1.h, p1/z, z1.h, z2.h

@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 17, 2026
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

@a74nh

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

We didn't mention this in our discussion, but, yes I think we should hold of from merging this until #128326 is complete.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_reviewed_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_recorded_worker_run_id": "29677704668",
"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": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"review_id": 4730527203
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: The motivation is real and well-evidenced. On SVE, boolean masks are naturally predicates, but the JIT currently materializes predicate results into vectors (mov z, p/z, #1) and re-compares them to zero before doing logical ops, then compares back. Recovering the predicate form lets the JIT emit and/bic/orr/eor/sel/zip1 directly on predicate registers and reuse the first predicate as the governing predicate, removing redundant ptrue/mov sequences. The PR's own SPMI diffs show real code-size and allocation wins.

Approach: The approach is reasonable and consistent with existing patterns: recognize mask-shaped operands (ConvertMaskToVector, real masks, vector zero, and constant 0/1 vectors) during gtFoldExprHWIntrinsic and in arm64 lowering, and fold to the _Predicates intrinsic variants. Importing Sve.BitwiseClear as AND(NOT(...)) and recovering bic in lowering mirrors the established AdvSimd handling. The codegen using op1Reg as the governing predicate matches the documented and Pd, Pn/z, Pn, Pm semantics.

Summary: ⚠️ Needs Changes. The direction and most of the implementation look sound, but TryLowerSvePredicateBitwiseClear ends with an unconditional return true; in its fall-through path. Since the helper now runs for every arm64 vector GT_AND, a true result when no predicate pattern matched causes the caller to break and skip the pre-existing AdvSimd ~op1 & op2 → BitwiseClear (AndNot) folding for ordinary vector ANDs. This is a functional regression outside the intended SVE-predicate scope and is not covered by the added SVE-only tests. See the inline comment for details.


Detailed Findings

❌ Correctness — Unconditional return true; disables AdvSimd AndNot folding

Flagged inline at src/coreclr/jit/lowerarmarch.cpp fall-through return. The final return true; should be return false; so that non-predicate vector ANDs fall through to the existing NI_AdvSimd_BitwiseClear/AndNot recognition in LowerHWIntrinsic. As written, that established optimization is bypassed for all arm64 vector GT_AND whose operands are not mask-shaped.

⚠️ Test coverage — No coverage for the non-predicate AndNot path

The added tests in PredicateInstructions.cs exercise only the new SVE-predicate folds (all inputs mask-shaped). None exercise an ordinary vector ~a & b / Vector*.AndNot on arm64, which is exactly the path broken by the finding above. A regression test covering that AdvSimd AndNot lowering would prevent this class of regression.

✅ Codegen and fold logic — Predicate variants look correct

The NI_Sve_And_Predicates/NI_Sve_BitwiseClear_Predicates codegen reusing op1Reg as the governing predicate matches the SVE semantics in the PR description, and the 0/1-vector-to-mask recognition in gtFoldExprHWIntrinsic is guarded to arm64 + masked HW intrinsics and rejects non-0/1 constants. TryLowerSveConvertVectorToMask correctly only elides the conversion when the source is already a mask.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 141.4 AIC · ⌖ 10.7 AIC · ⊞ 10K

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
return true;
}

return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fall-through return true; looks like a bug. When none of the three predicate patterns match (the common case for ordinary vector GT_AND), the function still returns true. The caller does:

if ((oper == GT_AND) && TryLowerSvePredicateBitwiseClear(node, &intrinsicId, &oper))
{
break;
}

so a true result breaks out of the GT_AND/GT_OR case and skips the pre-existing AdvSimd folding that turns ~op1 & op2 into NI_AdvSimd_BitwiseClear (AndNot). Because this helper is now invoked for every arm64 vector GT_AND, that established AndNot recognition is effectively disabled for all non-predicate vector ANDs whenever the operands are not mask-shaped — a functional regression well beyond the intended SVE-predicate scope.

This should almost certainly return false; here so the caller falls through to the existing AdvSimd transform. (A regression test exercising e.g. Vector128.AndNot / ~a & b on arm64 would catch this; the current SVE-only tests do not.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've fixed the return true issue.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@jonathandavies-arm I'll take a look here now that #128326 has gone in, though it looks like there's merge conflicts

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/hwintrinsicarm64.cpp
#	src/coreclr/jit/lowerarmarch.cpp
#	src/tests/JIT/opt/SVE/PredicateInstructions.cs
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

@jonathandavies-arm

Copy link
Copy Markdown
ContributorAuthor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

This should be fixed now. I'll keep an eye on the tests.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

There's a merge conflict, friendly ping @jonathandavies-arm

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/lowerarmarch.cpp

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves ARM64 SVE predicate-codegen by recognizing more mask-shaped operands (including constant 0/1 vectors), enabling additional folding to predicate forms, and tightening codegen/containment for certain predicate operations. This is primarily a JIT/codegen optimization backed by targeted SVE JIT tests.

Changes:

  • Teach folding/lowering to treat constant 0/1 vectors (and some inverted forms) as mask-shaped so more SVE ops fold to predicate variants.
  • Add lowering/codegen support to emit more efficient predicate and/bic forms (avoiding redundant ptrue) and remove redundant vector↔mask conversions.
  • Extend the SVE predicate instruction test coverage to exercise the new folding/lowering cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/tests/JIT/opt/SVE/PredicateInstructions.csAdds new SVE test methods/assertions covering 0/1 constant “mask-shaped” folding and predicate bic lowering.
src/coreclr/jit/lowerarmarch.cppAdds SVE mask-operand recognition/conversion helpers, lowers mask-shaped AND/NOT into predicate BIC, removes redundant ConvertVectorToMask, and refines embedded-mask wrapping/containment behavior.
src/coreclr/jit/lower.hDeclares new SVE-lowering helpers under FEATURE_MASKED_HW_INTRINSICS.
src/coreclr/jit/hwintrinsiccodegenarm64.cppUpdates codegen for predicate and/bic to use the first predicate as the governing predicate.
src/coreclr/jit/hwintrinsicarm64.cppAdjusts import for NI_Sve_BitwiseClear to decompose to NOT+AND for later SVE predicate lowering.
src/coreclr/jit/gentree.cppExtends HWIntrinsic folding to treat 0/1 constant vectors as mask-shaped for all-mask SVE variants (including ConditionalSelect).

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
Change-Id: I8398ba28db6207ec720b1a31e2d8c3bc3a367142
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Unfortunately looks like there's still a test failure

Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 163, 24, 1, 177, 161, 119, 75, 0, 11, 254, 0, 0, 0, 0)
left: (126, 118, 214, 86, 222, 102, 124, 252, 33, 13, 184, 112, 215, 251, 71, 199)
right: (151, 206, 181, 129, 228, 147, 128, 248, 118, 227, 192, 95, 142, 140, 109, 226)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

Change-Id: I92c28f208bd65690604d472b8546b720605cdd5b
Comment threadsrc/coreclr/jit/gentree.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit, move block comment down next to gtFoldExprHWIntrinsic's definition

Comment threadsrc/coreclr/jit/gentree.cpp Outdated
canFold &= tree->Op(i)->OperIsConvertMaskToVector();
// Fold SVE intrinsics such as And/Or/Xor/ZipLow to their
// all-mask variants when every operand is mask-shaped.
canFold &= IsAllMaskVariantOperand(tree->Op(i), simdBaseType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the transformation for this is incorrect if CreateBreakAfterMask is NOT here

Sve.And(Sve.CompareGreaterThan(a, b), Vector.Create<short>(1))

Should produce a simd vector with lanes either zero or one. But now the morph transform activates this and wraps it in "ConvertMaskToVector", causing result lanes to either be zero or allbitsset.

Comment threadsrc/coreclr/jit/hwintrinsicarm64.cpp Outdated
// in lowering instead so decompose into the individual operations
// on import

op2 = gtNewSimdUnOpNode(GT_NOT, retType, op2, simdBaseType, simdSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's unclear to me why we no longer fold here, can you explain?

(Also NIT but if possible probably better to leave these combined and fold only if intrinsic==NI_AdvSimd_BitwiseClear just to cut down code duplication)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We don’t fold the NOT in the SVE case so that lowering can recognize mask shaped operands and emit the predicate BIC form.

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
// IsSveMaskOperand:
// Return true if "op" can be represented directly as an SVE predicate.
//
static bool IsSveMaskOperand(GenTree* op)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be combined with IsAllMaskVariantOperand from gentree

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've combined them


node->ResetHWIntrinsicId(NI_Sve_BitwiseClear_Predicates, op1, op2);
node->gtType = TYP_MASK;
*intrinsicId = NI_Sve_BitwiseClear_Predicates;

@dhartglassMSFTdhartglassMSFTSep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The following code should trigger a INS_invalid assert, trying to do codegen for BitwiseClear_Predicates with floating point basetype:

 [MethodImpl(MethodImplOptions.NoInlining)]
private static Vector<float> ReproFloat(Vector<float> left, Vector<float> right)
{
Vector<float> firstMask = Sve.CompareLessThan(left, right);
Vector<float> secondMask = Sve.ZipLow(
Sve.CompareGreaterThan(left, right),
Sve.CompareEqual(left, right));
return firstMask & ~secondMask;
}

Maybe the hw intrinsic list table just needs a new entries for BitwiseClear_Predicates for fp type, or just reject floats if they're not allowed

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added the floating point mappings to the intrinsics.

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

Labels

arch-arm64area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@jonathandavies-arm@dhartglassMSFT@a74nh@JulieLeeMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

arm64: Improve predicate instruction usage - #129506

Open
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage
Open

arm64: Improve predicate instruction usage#129506
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage

Conversation

@jonathandavies-arm

Copy link
Copy Markdown
Contributor

SVE Predicate Instructions asm diffs

Branch: upstream/sve/predicate-instruction-usage

Collection / JIT / output paths

Collection

  • MCH: artifacts/asmdiff/custom_mch/sve_predicate_instructions.mch
  • Source test DLL: artifacts/tests/coreclr/linux.arm64.Checked/JIT/opt/SVE/PredicateInstructions/PredicateInstructions.dll

JITs

  • Base: artifacts/asmdiff/builds/5656cc9c/core_root/libclrjit.so
  • Diff: artifacts/bin/coreclr/linux.arm64.Checked/libclrjit.so

Output

  • Dasm base: artifacts/spmi/asm.sve_predicate_instructions/base/
  • Dasm diff: artifacts/spmi/asm.sve_predicate_instructions/diff/
  • Log: artifacts/spmi/superpmi.2.log

Summary

3 asm diffs were found, all in PredicateInstructions.

ContextMethodCode bytesPerfScoreBytes allocated
10AndMask(Vector<short>, Vector<short>)52 -> 48 (-4)23.50 -> 21.50 (-2.00)60992 -> 60232 (-760)
11BitwiseClearMask(Vector<short>, Vector<short>)44 -> 40 (-4)18.00 -> 17.50 (-0.50)59760 -> 59544 (-216)
16TransposeEvenAndMask(Vector<short>, Vector<short>, Vector<short>)52 -> 52 (+0)24.50 -> 24.50 (+0.00)62264 -> 61360 (-904)

Notable diffs

Context 10: AndMask

Removed a redundant ptrue and now uses the first predicate as the governing predicate for and:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b+ and p0.b, p0/z, p0.b, p1.b
Context 11: BitwiseClearMask

Uses predicate bic directly instead of materializing predicate results into vectors and doing vector bic:

- mov z16.h, p0/z, #1- ptrue p0.h- cmpeq p0.h, p0/z, z0.h, z1.h+ ptrue p1.h+ cmpeq p1.h, p1/z, z0.h, z1.h+ bic p0.b, p0/z, p0.b, p1.b
mov z0.h, p0/z, #1
- bic v0.8h, v16.8h, v0.8h
Context 16: TransposeEvenAndMask

Removes one ptrue p2.h; total code size and PerfScore are unchanged:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b- cmpgt p1.h, p2/z, z1.h, z2.h+ and p0.b, p0/z, p0.b, p1.b+ ptrue p1.h+ cmpgt p1.h, p1/z, z1.h, z2.h

@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 17, 2026
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

@a74nh

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

We didn't mention this in our discussion, but, yes I think we should hold of from merging this until #128326 is complete.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_reviewed_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_recorded_worker_run_id": "29677704668",
"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": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"review_id": 4730527203
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: The motivation is real and well-evidenced. On SVE, boolean masks are naturally predicates, but the JIT currently materializes predicate results into vectors (mov z, p/z, #1) and re-compares them to zero before doing logical ops, then compares back. Recovering the predicate form lets the JIT emit and/bic/orr/eor/sel/zip1 directly on predicate registers and reuse the first predicate as the governing predicate, removing redundant ptrue/mov sequences. The PR's own SPMI diffs show real code-size and allocation wins.

Approach: The approach is reasonable and consistent with existing patterns: recognize mask-shaped operands (ConvertMaskToVector, real masks, vector zero, and constant 0/1 vectors) during gtFoldExprHWIntrinsic and in arm64 lowering, and fold to the _Predicates intrinsic variants. Importing Sve.BitwiseClear as AND(NOT(...)) and recovering bic in lowering mirrors the established AdvSimd handling. The codegen using op1Reg as the governing predicate matches the documented and Pd, Pn/z, Pn, Pm semantics.

Summary: ⚠️ Needs Changes. The direction and most of the implementation look sound, but TryLowerSvePredicateBitwiseClear ends with an unconditional return true; in its fall-through path. Since the helper now runs for every arm64 vector GT_AND, a true result when no predicate pattern matched causes the caller to break and skip the pre-existing AdvSimd ~op1 & op2 → BitwiseClear (AndNot) folding for ordinary vector ANDs. This is a functional regression outside the intended SVE-predicate scope and is not covered by the added SVE-only tests. See the inline comment for details.


Detailed Findings

❌ Correctness — Unconditional return true; disables AdvSimd AndNot folding

Flagged inline at src/coreclr/jit/lowerarmarch.cpp fall-through return. The final return true; should be return false; so that non-predicate vector ANDs fall through to the existing NI_AdvSimd_BitwiseClear/AndNot recognition in LowerHWIntrinsic. As written, that established optimization is bypassed for all arm64 vector GT_AND whose operands are not mask-shaped.

⚠️ Test coverage — No coverage for the non-predicate AndNot path

The added tests in PredicateInstructions.cs exercise only the new SVE-predicate folds (all inputs mask-shaped). None exercise an ordinary vector ~a & b / Vector*.AndNot on arm64, which is exactly the path broken by the finding above. A regression test covering that AdvSimd AndNot lowering would prevent this class of regression.

✅ Codegen and fold logic — Predicate variants look correct

The NI_Sve_And_Predicates/NI_Sve_BitwiseClear_Predicates codegen reusing op1Reg as the governing predicate matches the SVE semantics in the PR description, and the 0/1-vector-to-mask recognition in gtFoldExprHWIntrinsic is guarded to arm64 + masked HW intrinsics and rejects non-0/1 constants. TryLowerSveConvertVectorToMask correctly only elides the conversion when the source is already a mask.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 141.4 AIC · ⌖ 10.7 AIC · ⊞ 10K

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
return true;
}

return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fall-through return true; looks like a bug. When none of the three predicate patterns match (the common case for ordinary vector GT_AND), the function still returns true. The caller does:

if ((oper == GT_AND) && TryLowerSvePredicateBitwiseClear(node, &intrinsicId, &oper))
{
break;
}

so a true result breaks out of the GT_AND/GT_OR case and skips the pre-existing AdvSimd folding that turns ~op1 & op2 into NI_AdvSimd_BitwiseClear (AndNot). Because this helper is now invoked for every arm64 vector GT_AND, that established AndNot recognition is effectively disabled for all non-predicate vector ANDs whenever the operands are not mask-shaped — a functional regression well beyond the intended SVE-predicate scope.

This should almost certainly return false; here so the caller falls through to the existing AdvSimd transform. (A regression test exercising e.g. Vector128.AndNot / ~a & b on arm64 would catch this; the current SVE-only tests do not.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've fixed the return true issue.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@jonathandavies-arm I'll take a look here now that #128326 has gone in, though it looks like there's merge conflicts

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/hwintrinsicarm64.cpp
#	src/coreclr/jit/lowerarmarch.cpp
#	src/tests/JIT/opt/SVE/PredicateInstructions.cs
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

@jonathandavies-arm

Copy link
Copy Markdown
ContributorAuthor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

This should be fixed now. I'll keep an eye on the tests.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

There's a merge conflict, friendly ping @jonathandavies-arm

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/lowerarmarch.cpp

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves ARM64 SVE predicate-codegen by recognizing more mask-shaped operands (including constant 0/1 vectors), enabling additional folding to predicate forms, and tightening codegen/containment for certain predicate operations. This is primarily a JIT/codegen optimization backed by targeted SVE JIT tests.

Changes:

  • Teach folding/lowering to treat constant 0/1 vectors (and some inverted forms) as mask-shaped so more SVE ops fold to predicate variants.
  • Add lowering/codegen support to emit more efficient predicate and/bic forms (avoiding redundant ptrue) and remove redundant vector↔mask conversions.
  • Extend the SVE predicate instruction test coverage to exercise the new folding/lowering cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/tests/JIT/opt/SVE/PredicateInstructions.csAdds new SVE test methods/assertions covering 0/1 constant “mask-shaped” folding and predicate bic lowering.
src/coreclr/jit/lowerarmarch.cppAdds SVE mask-operand recognition/conversion helpers, lowers mask-shaped AND/NOT into predicate BIC, removes redundant ConvertVectorToMask, and refines embedded-mask wrapping/containment behavior.
src/coreclr/jit/lower.hDeclares new SVE-lowering helpers under FEATURE_MASKED_HW_INTRINSICS.
src/coreclr/jit/hwintrinsiccodegenarm64.cppUpdates codegen for predicate and/bic to use the first predicate as the governing predicate.
src/coreclr/jit/hwintrinsicarm64.cppAdjusts import for NI_Sve_BitwiseClear to decompose to NOT+AND for later SVE predicate lowering.
src/coreclr/jit/gentree.cppExtends HWIntrinsic folding to treat 0/1 constant vectors as mask-shaped for all-mask SVE variants (including ConditionalSelect).

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
Change-Id: I8398ba28db6207ec720b1a31e2d8c3bc3a367142
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Unfortunately looks like there's still a test failure

Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 163, 24, 1, 177, 161, 119, 75, 0, 11, 254, 0, 0, 0, 0)
left: (126, 118, 214, 86, 222, 102, 124, 252, 33, 13, 184, 112, 215, 251, 71, 199)
right: (151, 206, 181, 129, 228, 147, 128, 248, 118, 227, 192, 95, 142, 140, 109, 226)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

Change-Id: I92c28f208bd65690604d472b8546b720605cdd5b
Comment threadsrc/coreclr/jit/gentree.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit, move block comment down next to gtFoldExprHWIntrinsic's definition

Comment threadsrc/coreclr/jit/gentree.cpp Outdated
canFold &= tree->Op(i)->OperIsConvertMaskToVector();
// Fold SVE intrinsics such as And/Or/Xor/ZipLow to their
// all-mask variants when every operand is mask-shaped.
canFold &= IsAllMaskVariantOperand(tree->Op(i), simdBaseType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the transformation for this is incorrect if CreateBreakAfterMask is NOT here

Sve.And(Sve.CompareGreaterThan(a, b), Vector.Create<short>(1))

Should produce a simd vector with lanes either zero or one. But now the morph transform activates this and wraps it in "ConvertMaskToVector", causing result lanes to either be zero or allbitsset.

Comment threadsrc/coreclr/jit/hwintrinsicarm64.cpp Outdated
// in lowering instead so decompose into the individual operations
// on import

op2 = gtNewSimdUnOpNode(GT_NOT, retType, op2, simdBaseType, simdSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's unclear to me why we no longer fold here, can you explain?

(Also NIT but if possible probably better to leave these combined and fold only if intrinsic==NI_AdvSimd_BitwiseClear just to cut down code duplication)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We don’t fold the NOT in the SVE case so that lowering can recognize mask shaped operands and emit the predicate BIC form.

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
// IsSveMaskOperand:
// Return true if "op" can be represented directly as an SVE predicate.
//
static bool IsSveMaskOperand(GenTree* op)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be combined with IsAllMaskVariantOperand from gentree

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've combined them


node->ResetHWIntrinsicId(NI_Sve_BitwiseClear_Predicates, op1, op2);
node->gtType = TYP_MASK;
*intrinsicId = NI_Sve_BitwiseClear_Predicates;

@dhartglassMSFTdhartglassMSFTSep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The following code should trigger a INS_invalid assert, trying to do codegen for BitwiseClear_Predicates with floating point basetype:

 [MethodImpl(MethodImplOptions.NoInlining)]
private static Vector<float> ReproFloat(Vector<float> left, Vector<float> right)
{
Vector<float> firstMask = Sve.CompareLessThan(left, right);
Vector<float> secondMask = Sve.ZipLow(
Sve.CompareGreaterThan(left, right),
Sve.CompareEqual(left, right));
return firstMask & ~secondMask;
}

Maybe the hw intrinsic list table just needs a new entries for BitwiseClear_Predicates for fp type, or just reject floats if they're not allowed

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added the floating point mappings to the intrinsics.

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

Labels

arch-arm64area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@jonathandavies-arm@dhartglassMSFT@a74nh@JulieLeeMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

arm64: Improve predicate instruction usage - #129506

Open
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage
Open

arm64: Improve predicate instruction usage#129506
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage

Conversation

@jonathandavies-arm

Copy link
Copy Markdown
Contributor

SVE Predicate Instructions asm diffs

Branch: upstream/sve/predicate-instruction-usage

Collection / JIT / output paths

Collection

  • MCH: artifacts/asmdiff/custom_mch/sve_predicate_instructions.mch
  • Source test DLL: artifacts/tests/coreclr/linux.arm64.Checked/JIT/opt/SVE/PredicateInstructions/PredicateInstructions.dll

JITs

  • Base: artifacts/asmdiff/builds/5656cc9c/core_root/libclrjit.so
  • Diff: artifacts/bin/coreclr/linux.arm64.Checked/libclrjit.so

Output

  • Dasm base: artifacts/spmi/asm.sve_predicate_instructions/base/
  • Dasm diff: artifacts/spmi/asm.sve_predicate_instructions/diff/
  • Log: artifacts/spmi/superpmi.2.log

Summary

3 asm diffs were found, all in PredicateInstructions.

ContextMethodCode bytesPerfScoreBytes allocated
10AndMask(Vector<short>, Vector<short>)52 -> 48 (-4)23.50 -> 21.50 (-2.00)60992 -> 60232 (-760)
11BitwiseClearMask(Vector<short>, Vector<short>)44 -> 40 (-4)18.00 -> 17.50 (-0.50)59760 -> 59544 (-216)
16TransposeEvenAndMask(Vector<short>, Vector<short>, Vector<short>)52 -> 52 (+0)24.50 -> 24.50 (+0.00)62264 -> 61360 (-904)

Notable diffs

Context 10: AndMask

Removed a redundant ptrue and now uses the first predicate as the governing predicate for and:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b+ and p0.b, p0/z, p0.b, p1.b
Context 11: BitwiseClearMask

Uses predicate bic directly instead of materializing predicate results into vectors and doing vector bic:

- mov z16.h, p0/z, #1- ptrue p0.h- cmpeq p0.h, p0/z, z0.h, z1.h+ ptrue p1.h+ cmpeq p1.h, p1/z, z0.h, z1.h+ bic p0.b, p0/z, p0.b, p1.b
mov z0.h, p0/z, #1
- bic v0.8h, v16.8h, v0.8h
Context 16: TransposeEvenAndMask

Removes one ptrue p2.h; total code size and PerfScore are unchanged:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b- cmpgt p1.h, p2/z, z1.h, z2.h+ and p0.b, p0/z, p0.b, p1.b+ ptrue p1.h+ cmpgt p1.h, p1/z, z1.h, z2.h

@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 17, 2026
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

@a74nh

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

We didn't mention this in our discussion, but, yes I think we should hold of from merging this until #128326 is complete.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_reviewed_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_recorded_worker_run_id": "29677704668",
"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": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"review_id": 4730527203
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: The motivation is real and well-evidenced. On SVE, boolean masks are naturally predicates, but the JIT currently materializes predicate results into vectors (mov z, p/z, #1) and re-compares them to zero before doing logical ops, then compares back. Recovering the predicate form lets the JIT emit and/bic/orr/eor/sel/zip1 directly on predicate registers and reuse the first predicate as the governing predicate, removing redundant ptrue/mov sequences. The PR's own SPMI diffs show real code-size and allocation wins.

Approach: The approach is reasonable and consistent with existing patterns: recognize mask-shaped operands (ConvertMaskToVector, real masks, vector zero, and constant 0/1 vectors) during gtFoldExprHWIntrinsic and in arm64 lowering, and fold to the _Predicates intrinsic variants. Importing Sve.BitwiseClear as AND(NOT(...)) and recovering bic in lowering mirrors the established AdvSimd handling. The codegen using op1Reg as the governing predicate matches the documented and Pd, Pn/z, Pn, Pm semantics.

Summary: ⚠️ Needs Changes. The direction and most of the implementation look sound, but TryLowerSvePredicateBitwiseClear ends with an unconditional return true; in its fall-through path. Since the helper now runs for every arm64 vector GT_AND, a true result when no predicate pattern matched causes the caller to break and skip the pre-existing AdvSimd ~op1 & op2 → BitwiseClear (AndNot) folding for ordinary vector ANDs. This is a functional regression outside the intended SVE-predicate scope and is not covered by the added SVE-only tests. See the inline comment for details.


Detailed Findings

❌ Correctness — Unconditional return true; disables AdvSimd AndNot folding

Flagged inline at src/coreclr/jit/lowerarmarch.cpp fall-through return. The final return true; should be return false; so that non-predicate vector ANDs fall through to the existing NI_AdvSimd_BitwiseClear/AndNot recognition in LowerHWIntrinsic. As written, that established optimization is bypassed for all arm64 vector GT_AND whose operands are not mask-shaped.

⚠️ Test coverage — No coverage for the non-predicate AndNot path

The added tests in PredicateInstructions.cs exercise only the new SVE-predicate folds (all inputs mask-shaped). None exercise an ordinary vector ~a & b / Vector*.AndNot on arm64, which is exactly the path broken by the finding above. A regression test covering that AdvSimd AndNot lowering would prevent this class of regression.

✅ Codegen and fold logic — Predicate variants look correct

The NI_Sve_And_Predicates/NI_Sve_BitwiseClear_Predicates codegen reusing op1Reg as the governing predicate matches the SVE semantics in the PR description, and the 0/1-vector-to-mask recognition in gtFoldExprHWIntrinsic is guarded to arm64 + masked HW intrinsics and rejects non-0/1 constants. TryLowerSveConvertVectorToMask correctly only elides the conversion when the source is already a mask.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 141.4 AIC · ⌖ 10.7 AIC · ⊞ 10K

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
return true;
}

return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fall-through return true; looks like a bug. When none of the three predicate patterns match (the common case for ordinary vector GT_AND), the function still returns true. The caller does:

if ((oper == GT_AND) && TryLowerSvePredicateBitwiseClear(node, &intrinsicId, &oper))
{
break;
}

so a true result breaks out of the GT_AND/GT_OR case and skips the pre-existing AdvSimd folding that turns ~op1 & op2 into NI_AdvSimd_BitwiseClear (AndNot). Because this helper is now invoked for every arm64 vector GT_AND, that established AndNot recognition is effectively disabled for all non-predicate vector ANDs whenever the operands are not mask-shaped — a functional regression well beyond the intended SVE-predicate scope.

This should almost certainly return false; here so the caller falls through to the existing AdvSimd transform. (A regression test exercising e.g. Vector128.AndNot / ~a & b on arm64 would catch this; the current SVE-only tests do not.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've fixed the return true issue.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@jonathandavies-arm I'll take a look here now that #128326 has gone in, though it looks like there's merge conflicts

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/hwintrinsicarm64.cpp
#	src/coreclr/jit/lowerarmarch.cpp
#	src/tests/JIT/opt/SVE/PredicateInstructions.cs
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

@jonathandavies-arm

Copy link
Copy Markdown
ContributorAuthor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

This should be fixed now. I'll keep an eye on the tests.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

There's a merge conflict, friendly ping @jonathandavies-arm

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/lowerarmarch.cpp

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves ARM64 SVE predicate-codegen by recognizing more mask-shaped operands (including constant 0/1 vectors), enabling additional folding to predicate forms, and tightening codegen/containment for certain predicate operations. This is primarily a JIT/codegen optimization backed by targeted SVE JIT tests.

Changes:

  • Teach folding/lowering to treat constant 0/1 vectors (and some inverted forms) as mask-shaped so more SVE ops fold to predicate variants.
  • Add lowering/codegen support to emit more efficient predicate and/bic forms (avoiding redundant ptrue) and remove redundant vector↔mask conversions.
  • Extend the SVE predicate instruction test coverage to exercise the new folding/lowering cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/tests/JIT/opt/SVE/PredicateInstructions.csAdds new SVE test methods/assertions covering 0/1 constant “mask-shaped” folding and predicate bic lowering.
src/coreclr/jit/lowerarmarch.cppAdds SVE mask-operand recognition/conversion helpers, lowers mask-shaped AND/NOT into predicate BIC, removes redundant ConvertVectorToMask, and refines embedded-mask wrapping/containment behavior.
src/coreclr/jit/lower.hDeclares new SVE-lowering helpers under FEATURE_MASKED_HW_INTRINSICS.
src/coreclr/jit/hwintrinsiccodegenarm64.cppUpdates codegen for predicate and/bic to use the first predicate as the governing predicate.
src/coreclr/jit/hwintrinsicarm64.cppAdjusts import for NI_Sve_BitwiseClear to decompose to NOT+AND for later SVE predicate lowering.
src/coreclr/jit/gentree.cppExtends HWIntrinsic folding to treat 0/1 constant vectors as mask-shaped for all-mask SVE variants (including ConditionalSelect).

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
Change-Id: I8398ba28db6207ec720b1a31e2d8c3bc3a367142
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Unfortunately looks like there's still a test failure

Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 163, 24, 1, 177, 161, 119, 75, 0, 11, 254, 0, 0, 0, 0)
left: (126, 118, 214, 86, 222, 102, 124, 252, 33, 13, 184, 112, 215, 251, 71, 199)
right: (151, 206, 181, 129, 228, 147, 128, 248, 118, 227, 192, 95, 142, 140, 109, 226)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

Change-Id: I92c28f208bd65690604d472b8546b720605cdd5b
Comment threadsrc/coreclr/jit/gentree.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit, move block comment down next to gtFoldExprHWIntrinsic's definition

Comment threadsrc/coreclr/jit/gentree.cpp Outdated
canFold &= tree->Op(i)->OperIsConvertMaskToVector();
// Fold SVE intrinsics such as And/Or/Xor/ZipLow to their
// all-mask variants when every operand is mask-shaped.
canFold &= IsAllMaskVariantOperand(tree->Op(i), simdBaseType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the transformation for this is incorrect if CreateBreakAfterMask is NOT here

Sve.And(Sve.CompareGreaterThan(a, b), Vector.Create<short>(1))

Should produce a simd vector with lanes either zero or one. But now the morph transform activates this and wraps it in "ConvertMaskToVector", causing result lanes to either be zero or allbitsset.

Comment threadsrc/coreclr/jit/hwintrinsicarm64.cpp Outdated
// in lowering instead so decompose into the individual operations
// on import

op2 = gtNewSimdUnOpNode(GT_NOT, retType, op2, simdBaseType, simdSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's unclear to me why we no longer fold here, can you explain?

(Also NIT but if possible probably better to leave these combined and fold only if intrinsic==NI_AdvSimd_BitwiseClear just to cut down code duplication)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We don’t fold the NOT in the SVE case so that lowering can recognize mask shaped operands and emit the predicate BIC form.

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
// IsSveMaskOperand:
// Return true if "op" can be represented directly as an SVE predicate.
//
static bool IsSveMaskOperand(GenTree* op)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be combined with IsAllMaskVariantOperand from gentree

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've combined them


node->ResetHWIntrinsicId(NI_Sve_BitwiseClear_Predicates, op1, op2);
node->gtType = TYP_MASK;
*intrinsicId = NI_Sve_BitwiseClear_Predicates;

@dhartglassMSFTdhartglassMSFTSep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The following code should trigger a INS_invalid assert, trying to do codegen for BitwiseClear_Predicates with floating point basetype:

 [MethodImpl(MethodImplOptions.NoInlining)]
private static Vector<float> ReproFloat(Vector<float> left, Vector<float> right)
{
Vector<float> firstMask = Sve.CompareLessThan(left, right);
Vector<float> secondMask = Sve.ZipLow(
Sve.CompareGreaterThan(left, right),
Sve.CompareEqual(left, right));
return firstMask & ~secondMask;
}

Maybe the hw intrinsic list table just needs a new entries for BitwiseClear_Predicates for fp type, or just reject floats if they're not allowed

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added the floating point mappings to the intrinsics.

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

Labels

arch-arm64area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@jonathandavies-arm@dhartglassMSFT@a74nh@JulieLeeMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

arm64: Improve predicate instruction usage - #129506

Open
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage
Open

arm64: Improve predicate instruction usage#129506
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage

Conversation

@jonathandavies-arm

Copy link
Copy Markdown
Contributor

SVE Predicate Instructions asm diffs

Branch: upstream/sve/predicate-instruction-usage

Collection / JIT / output paths

Collection

  • MCH: artifacts/asmdiff/custom_mch/sve_predicate_instructions.mch
  • Source test DLL: artifacts/tests/coreclr/linux.arm64.Checked/JIT/opt/SVE/PredicateInstructions/PredicateInstructions.dll

JITs

  • Base: artifacts/asmdiff/builds/5656cc9c/core_root/libclrjit.so
  • Diff: artifacts/bin/coreclr/linux.arm64.Checked/libclrjit.so

Output

  • Dasm base: artifacts/spmi/asm.sve_predicate_instructions/base/
  • Dasm diff: artifacts/spmi/asm.sve_predicate_instructions/diff/
  • Log: artifacts/spmi/superpmi.2.log

Summary

3 asm diffs were found, all in PredicateInstructions.

ContextMethodCode bytesPerfScoreBytes allocated
10AndMask(Vector<short>, Vector<short>)52 -> 48 (-4)23.50 -> 21.50 (-2.00)60992 -> 60232 (-760)
11BitwiseClearMask(Vector<short>, Vector<short>)44 -> 40 (-4)18.00 -> 17.50 (-0.50)59760 -> 59544 (-216)
16TransposeEvenAndMask(Vector<short>, Vector<short>, Vector<short>)52 -> 52 (+0)24.50 -> 24.50 (+0.00)62264 -> 61360 (-904)

Notable diffs

Context 10: AndMask

Removed a redundant ptrue and now uses the first predicate as the governing predicate for and:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b+ and p0.b, p0/z, p0.b, p1.b
Context 11: BitwiseClearMask

Uses predicate bic directly instead of materializing predicate results into vectors and doing vector bic:

- mov z16.h, p0/z, #1- ptrue p0.h- cmpeq p0.h, p0/z, z0.h, z1.h+ ptrue p1.h+ cmpeq p1.h, p1/z, z0.h, z1.h+ bic p0.b, p0/z, p0.b, p1.b
mov z0.h, p0/z, #1
- bic v0.8h, v16.8h, v0.8h
Context 16: TransposeEvenAndMask

Removes one ptrue p2.h; total code size and PerfScore are unchanged:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b- cmpgt p1.h, p2/z, z1.h, z2.h+ and p0.b, p0/z, p0.b, p1.b+ ptrue p1.h+ cmpgt p1.h, p1/z, z1.h, z2.h

@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 17, 2026
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

@a74nh

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

We didn't mention this in our discussion, but, yes I think we should hold of from merging this until #128326 is complete.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_reviewed_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_recorded_worker_run_id": "29677704668",
"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": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"review_id": 4730527203
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: The motivation is real and well-evidenced. On SVE, boolean masks are naturally predicates, but the JIT currently materializes predicate results into vectors (mov z, p/z, #1) and re-compares them to zero before doing logical ops, then compares back. Recovering the predicate form lets the JIT emit and/bic/orr/eor/sel/zip1 directly on predicate registers and reuse the first predicate as the governing predicate, removing redundant ptrue/mov sequences. The PR's own SPMI diffs show real code-size and allocation wins.

Approach: The approach is reasonable and consistent with existing patterns: recognize mask-shaped operands (ConvertMaskToVector, real masks, vector zero, and constant 0/1 vectors) during gtFoldExprHWIntrinsic and in arm64 lowering, and fold to the _Predicates intrinsic variants. Importing Sve.BitwiseClear as AND(NOT(...)) and recovering bic in lowering mirrors the established AdvSimd handling. The codegen using op1Reg as the governing predicate matches the documented and Pd, Pn/z, Pn, Pm semantics.

Summary: ⚠️ Needs Changes. The direction and most of the implementation look sound, but TryLowerSvePredicateBitwiseClear ends with an unconditional return true; in its fall-through path. Since the helper now runs for every arm64 vector GT_AND, a true result when no predicate pattern matched causes the caller to break and skip the pre-existing AdvSimd ~op1 & op2 → BitwiseClear (AndNot) folding for ordinary vector ANDs. This is a functional regression outside the intended SVE-predicate scope and is not covered by the added SVE-only tests. See the inline comment for details.


Detailed Findings

❌ Correctness — Unconditional return true; disables AdvSimd AndNot folding

Flagged inline at src/coreclr/jit/lowerarmarch.cpp fall-through return. The final return true; should be return false; so that non-predicate vector ANDs fall through to the existing NI_AdvSimd_BitwiseClear/AndNot recognition in LowerHWIntrinsic. As written, that established optimization is bypassed for all arm64 vector GT_AND whose operands are not mask-shaped.

⚠️ Test coverage — No coverage for the non-predicate AndNot path

The added tests in PredicateInstructions.cs exercise only the new SVE-predicate folds (all inputs mask-shaped). None exercise an ordinary vector ~a & b / Vector*.AndNot on arm64, which is exactly the path broken by the finding above. A regression test covering that AdvSimd AndNot lowering would prevent this class of regression.

✅ Codegen and fold logic — Predicate variants look correct

The NI_Sve_And_Predicates/NI_Sve_BitwiseClear_Predicates codegen reusing op1Reg as the governing predicate matches the SVE semantics in the PR description, and the 0/1-vector-to-mask recognition in gtFoldExprHWIntrinsic is guarded to arm64 + masked HW intrinsics and rejects non-0/1 constants. TryLowerSveConvertVectorToMask correctly only elides the conversion when the source is already a mask.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 141.4 AIC · ⌖ 10.7 AIC · ⊞ 10K

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
return true;
}

return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fall-through return true; looks like a bug. When none of the three predicate patterns match (the common case for ordinary vector GT_AND), the function still returns true. The caller does:

if ((oper == GT_AND) && TryLowerSvePredicateBitwiseClear(node, &intrinsicId, &oper))
{
break;
}

so a true result breaks out of the GT_AND/GT_OR case and skips the pre-existing AdvSimd folding that turns ~op1 & op2 into NI_AdvSimd_BitwiseClear (AndNot). Because this helper is now invoked for every arm64 vector GT_AND, that established AndNot recognition is effectively disabled for all non-predicate vector ANDs whenever the operands are not mask-shaped — a functional regression well beyond the intended SVE-predicate scope.

This should almost certainly return false; here so the caller falls through to the existing AdvSimd transform. (A regression test exercising e.g. Vector128.AndNot / ~a & b on arm64 would catch this; the current SVE-only tests do not.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've fixed the return true issue.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@jonathandavies-arm I'll take a look here now that #128326 has gone in, though it looks like there's merge conflicts

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/hwintrinsicarm64.cpp
#	src/coreclr/jit/lowerarmarch.cpp
#	src/tests/JIT/opt/SVE/PredicateInstructions.cs
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

@jonathandavies-arm

Copy link
Copy Markdown
ContributorAuthor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

This should be fixed now. I'll keep an eye on the tests.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

There's a merge conflict, friendly ping @jonathandavies-arm

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/lowerarmarch.cpp

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves ARM64 SVE predicate-codegen by recognizing more mask-shaped operands (including constant 0/1 vectors), enabling additional folding to predicate forms, and tightening codegen/containment for certain predicate operations. This is primarily a JIT/codegen optimization backed by targeted SVE JIT tests.

Changes:

  • Teach folding/lowering to treat constant 0/1 vectors (and some inverted forms) as mask-shaped so more SVE ops fold to predicate variants.
  • Add lowering/codegen support to emit more efficient predicate and/bic forms (avoiding redundant ptrue) and remove redundant vector↔mask conversions.
  • Extend the SVE predicate instruction test coverage to exercise the new folding/lowering cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/tests/JIT/opt/SVE/PredicateInstructions.csAdds new SVE test methods/assertions covering 0/1 constant “mask-shaped” folding and predicate bic lowering.
src/coreclr/jit/lowerarmarch.cppAdds SVE mask-operand recognition/conversion helpers, lowers mask-shaped AND/NOT into predicate BIC, removes redundant ConvertVectorToMask, and refines embedded-mask wrapping/containment behavior.
src/coreclr/jit/lower.hDeclares new SVE-lowering helpers under FEATURE_MASKED_HW_INTRINSICS.
src/coreclr/jit/hwintrinsiccodegenarm64.cppUpdates codegen for predicate and/bic to use the first predicate as the governing predicate.
src/coreclr/jit/hwintrinsicarm64.cppAdjusts import for NI_Sve_BitwiseClear to decompose to NOT+AND for later SVE predicate lowering.
src/coreclr/jit/gentree.cppExtends HWIntrinsic folding to treat 0/1 constant vectors as mask-shaped for all-mask SVE variants (including ConditionalSelect).

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
Change-Id: I8398ba28db6207ec720b1a31e2d8c3bc3a367142
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Unfortunately looks like there's still a test failure

Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 163, 24, 1, 177, 161, 119, 75, 0, 11, 254, 0, 0, 0, 0)
left: (126, 118, 214, 86, 222, 102, 124, 252, 33, 13, 184, 112, 215, 251, 71, 199)
right: (151, 206, 181, 129, 228, 147, 128, 248, 118, 227, 192, 95, 142, 140, 109, 226)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

Change-Id: I92c28f208bd65690604d472b8546b720605cdd5b
Comment threadsrc/coreclr/jit/gentree.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit, move block comment down next to gtFoldExprHWIntrinsic's definition

Comment threadsrc/coreclr/jit/gentree.cpp Outdated
canFold &= tree->Op(i)->OperIsConvertMaskToVector();
// Fold SVE intrinsics such as And/Or/Xor/ZipLow to their
// all-mask variants when every operand is mask-shaped.
canFold &= IsAllMaskVariantOperand(tree->Op(i), simdBaseType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the transformation for this is incorrect if CreateBreakAfterMask is NOT here

Sve.And(Sve.CompareGreaterThan(a, b), Vector.Create<short>(1))

Should produce a simd vector with lanes either zero or one. But now the morph transform activates this and wraps it in "ConvertMaskToVector", causing result lanes to either be zero or allbitsset.

Comment threadsrc/coreclr/jit/hwintrinsicarm64.cpp Outdated
// in lowering instead so decompose into the individual operations
// on import

op2 = gtNewSimdUnOpNode(GT_NOT, retType, op2, simdBaseType, simdSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's unclear to me why we no longer fold here, can you explain?

(Also NIT but if possible probably better to leave these combined and fold only if intrinsic==NI_AdvSimd_BitwiseClear just to cut down code duplication)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We don’t fold the NOT in the SVE case so that lowering can recognize mask shaped operands and emit the predicate BIC form.

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
// IsSveMaskOperand:
// Return true if "op" can be represented directly as an SVE predicate.
//
static bool IsSveMaskOperand(GenTree* op)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be combined with IsAllMaskVariantOperand from gentree

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've combined them


node->ResetHWIntrinsicId(NI_Sve_BitwiseClear_Predicates, op1, op2);
node->gtType = TYP_MASK;
*intrinsicId = NI_Sve_BitwiseClear_Predicates;

@dhartglassMSFTdhartglassMSFTSep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The following code should trigger a INS_invalid assert, trying to do codegen for BitwiseClear_Predicates with floating point basetype:

 [MethodImpl(MethodImplOptions.NoInlining)]
private static Vector<float> ReproFloat(Vector<float> left, Vector<float> right)
{
Vector<float> firstMask = Sve.CompareLessThan(left, right);
Vector<float> secondMask = Sve.ZipLow(
Sve.CompareGreaterThan(left, right),
Sve.CompareEqual(left, right));
return firstMask & ~secondMask;
}

Maybe the hw intrinsic list table just needs a new entries for BitwiseClear_Predicates for fp type, or just reject floats if they're not allowed

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added the floating point mappings to the intrinsics.

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

Labels

arch-arm64area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@jonathandavies-arm@dhartglassMSFT@a74nh@JulieLeeMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

arm64: Improve predicate instruction usage - #129506

Open
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage
Open

arm64: Improve predicate instruction usage#129506
jonathandavies-arm wants to merge 16 commits into
dotnet:mainfrom
jonathandavies-arm:upstream/sve/predicate-instruction-usage

Conversation

@jonathandavies-arm

Copy link
Copy Markdown
Contributor

SVE Predicate Instructions asm diffs

Branch: upstream/sve/predicate-instruction-usage

Collection / JIT / output paths

Collection

  • MCH: artifacts/asmdiff/custom_mch/sve_predicate_instructions.mch
  • Source test DLL: artifacts/tests/coreclr/linux.arm64.Checked/JIT/opt/SVE/PredicateInstructions/PredicateInstructions.dll

JITs

  • Base: artifacts/asmdiff/builds/5656cc9c/core_root/libclrjit.so
  • Diff: artifacts/bin/coreclr/linux.arm64.Checked/libclrjit.so

Output

  • Dasm base: artifacts/spmi/asm.sve_predicate_instructions/base/
  • Dasm diff: artifacts/spmi/asm.sve_predicate_instructions/diff/
  • Log: artifacts/spmi/superpmi.2.log

Summary

3 asm diffs were found, all in PredicateInstructions.

ContextMethodCode bytesPerfScoreBytes allocated
10AndMask(Vector<short>, Vector<short>)52 -> 48 (-4)23.50 -> 21.50 (-2.00)60992 -> 60232 (-760)
11BitwiseClearMask(Vector<short>, Vector<short>)44 -> 40 (-4)18.00 -> 17.50 (-0.50)59760 -> 59544 (-216)
16TransposeEvenAndMask(Vector<short>, Vector<short>, Vector<short>)52 -> 52 (+0)24.50 -> 24.50 (+0.00)62264 -> 61360 (-904)

Notable diffs

Context 10: AndMask

Removed a redundant ptrue and now uses the first predicate as the governing predicate for and:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b+ and p0.b, p0/z, p0.b, p1.b
Context 11: BitwiseClearMask

Uses predicate bic directly instead of materializing predicate results into vectors and doing vector bic:

- mov z16.h, p0/z, #1- ptrue p0.h- cmpeq p0.h, p0/z, z0.h, z1.h+ ptrue p1.h+ cmpeq p1.h, p1/z, z0.h, z1.h+ bic p0.b, p0/z, p0.b, p1.b
mov z0.h, p0/z, #1
- bic v0.8h, v16.8h, v0.8h
Context 16: TransposeEvenAndMask

Removes one ptrue p2.h; total code size and PerfScore are unchanged:

- ptrue p2.h- and p0.b, p2/z, p0.b, p1.b- cmpgt p1.h, p2/z, z1.h, z2.h+ and p0.b, p0/z, p0.b, p1.b+ ptrue p1.h+ cmpgt p1.h, p1/z, z1.h, z2.h

@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 17, 2026
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Jun 17, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

@a74nh

Copy link
Copy Markdown
Contributor

@a74nh does this change depend on the predicate-discussion in #128326 , or am I misremembering our conversation?

We didn't mention this in our discussion, but, yes I think we should hold of from merging this until #128326 is complete.

@github-actions

github-actionsBot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Workflow state for the Holistic Review Orchestrator.

{
"version": 5,
"last_dispatched_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_reviewed_commit": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "1af2b9fda524303a30c5d14ac47bbf5510e06102",
"last_recorded_worker_run_id": "29677704668",
"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": "35eb3b6ab6b6b2f3a184e525eaf24e704c2c926f",
"review_id": 4730527203
}
]
}

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holistic Review

Motivation: The motivation is real and well-evidenced. On SVE, boolean masks are naturally predicates, but the JIT currently materializes predicate results into vectors (mov z, p/z, #1) and re-compares them to zero before doing logical ops, then compares back. Recovering the predicate form lets the JIT emit and/bic/orr/eor/sel/zip1 directly on predicate registers and reuse the first predicate as the governing predicate, removing redundant ptrue/mov sequences. The PR's own SPMI diffs show real code-size and allocation wins.

Approach: The approach is reasonable and consistent with existing patterns: recognize mask-shaped operands (ConvertMaskToVector, real masks, vector zero, and constant 0/1 vectors) during gtFoldExprHWIntrinsic and in arm64 lowering, and fold to the _Predicates intrinsic variants. Importing Sve.BitwiseClear as AND(NOT(...)) and recovering bic in lowering mirrors the established AdvSimd handling. The codegen using op1Reg as the governing predicate matches the documented and Pd, Pn/z, Pn, Pm semantics.

Summary: ⚠️ Needs Changes. The direction and most of the implementation look sound, but TryLowerSvePredicateBitwiseClear ends with an unconditional return true; in its fall-through path. Since the helper now runs for every arm64 vector GT_AND, a true result when no predicate pattern matched causes the caller to break and skip the pre-existing AdvSimd ~op1 & op2 → BitwiseClear (AndNot) folding for ordinary vector ANDs. This is a functional regression outside the intended SVE-predicate scope and is not covered by the added SVE-only tests. See the inline comment for details.


Detailed Findings

❌ Correctness — Unconditional return true; disables AdvSimd AndNot folding

Flagged inline at src/coreclr/jit/lowerarmarch.cpp fall-through return. The final return true; should be return false; so that non-predicate vector ANDs fall through to the existing NI_AdvSimd_BitwiseClear/AndNot recognition in LowerHWIntrinsic. As written, that established optimization is bypassed for all arm64 vector GT_AND whose operands are not mask-shaped.

⚠️ Test coverage — No coverage for the non-predicate AndNot path

The added tests in PredicateInstructions.cs exercise only the new SVE-predicate folds (all inputs mask-shaped). None exercise an ordinary vector ~a & b / Vector*.AndNot on arm64, which is exactly the path broken by the finding above. A regression test covering that AdvSimd AndNot lowering would prevent this class of regression.

✅ Codegen and fold logic — Predicate variants look correct

The NI_Sve_And_Predicates/NI_Sve_BitwiseClear_Predicates codegen reusing op1Reg as the governing predicate matches the SVE semantics in the PR description, and the 0/1-vector-to-mask recognition in gtFoldExprHWIntrinsic is guarded to arm64 + masked HW intrinsics and rejects non-0/1 constants. TryLowerSveConvertVectorToMask correctly only elides the conversion when the source is already a mask.

Note

This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.

Generated by Holistic Review · 141.4 AIC · ⌖ 10.7 AIC · ⊞ 10K

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
return true;
}

return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fall-through return true; looks like a bug. When none of the three predicate patterns match (the common case for ordinary vector GT_AND), the function still returns true. The caller does:

if ((oper == GT_AND) && TryLowerSvePredicateBitwiseClear(node, &intrinsicId, &oper))
{
break;
}

so a true result breaks out of the GT_AND/GT_OR case and skips the pre-existing AdvSimd folding that turns ~op1 & op2 into NI_AdvSimd_BitwiseClear (AndNot). Because this helper is now invoked for every arm64 vector GT_AND, that established AndNot recognition is effectively disabled for all non-predicate vector ANDs whenever the operands are not mask-shaped — a functional regression well beyond the intended SVE-predicate scope.

This should almost certainly return false; here so the caller falls through to the existing AdvSimd transform. (A regression test exercising e.g. Vector128.AndNot / ~a & b on arm64 would catch this; the current SVE-only tests do not.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've fixed the return true issue.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

@jonathandavies-arm I'll take a look here now that #128326 has gone in, though it looks like there's merge conflicts

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/hwintrinsicarm64.cpp
#	src/coreclr/jit/lowerarmarch.cpp
#	src/tests/JIT/opt/SVE/PredicateInstructions.cs
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

@jonathandavies-arm

Copy link
Copy Markdown
ContributorAuthor

Failures in conditionalselect tests

error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_byte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ushort() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_uint() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_ulong() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_sbyte() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_short() has failed.
error : Test ____w_1_s_src_tests_JIT_HardwareIntrinsics_Arm_Sve_Sve_ro::JIT.HardwareIntrinsics.Arm._Sve.Program.Sve_CreateBreakPropagateMask_long() has failed.
Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 0, 0, 0, 104, 70, 174, 0, 67, 0, 134, 240, 53, 189, 13)
left: (238, 200, 173, 46, 225, 123, 187, 87, 221, 76, 202, 176, 135, 211, 49, 163)
right: (164, 121, 67, 158, 115, 24, 36, 113, 253, 7, 51, 237, 3, 122, 156, 158)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

This should be fixed now. I'll keep an eye on the tests.

@dhartglassMSFT

Copy link
Copy Markdown
Contributor

There's a merge conflict, friendly ping @jonathandavies-arm

…e-instruction-usage
# Conflicts:
#	src/coreclr/jit/lowerarmarch.cpp

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves ARM64 SVE predicate-codegen by recognizing more mask-shaped operands (including constant 0/1 vectors), enabling additional folding to predicate forms, and tightening codegen/containment for certain predicate operations. This is primarily a JIT/codegen optimization backed by targeted SVE JIT tests.

Changes:

  • Teach folding/lowering to treat constant 0/1 vectors (and some inverted forms) as mask-shaped so more SVE ops fold to predicate variants.
  • Add lowering/codegen support to emit more efficient predicate and/bic forms (avoiding redundant ptrue) and remove redundant vector↔mask conversions.
  • Extend the SVE predicate instruction test coverage to exercise the new folding/lowering cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/tests/JIT/opt/SVE/PredicateInstructions.csAdds new SVE test methods/assertions covering 0/1 constant “mask-shaped” folding and predicate bic lowering.
src/coreclr/jit/lowerarmarch.cppAdds SVE mask-operand recognition/conversion helpers, lowers mask-shaped AND/NOT into predicate BIC, removes redundant ConvertVectorToMask, and refines embedded-mask wrapping/containment behavior.
src/coreclr/jit/lower.hDeclares new SVE-lowering helpers under FEATURE_MASKED_HW_INTRINSICS.
src/coreclr/jit/hwintrinsiccodegenarm64.cppUpdates codegen for predicate and/bic to use the first predicate as the governing predicate.
src/coreclr/jit/hwintrinsicarm64.cppAdjusts import for NI_Sve_BitwiseClear to decompose to NOT+AND for later SVE predicate lowering.
src/coreclr/jit/gentree.cppExtends HWIntrinsic folding to treat 0/1 constant vectors as mask-shaped for all-mask SVE variants (including ConditionalSelect).

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
Change-Id: I8398ba28db6207ec720b1a31e2d8c3bc3a367142
@dhartglassMSFT

Copy link
Copy Markdown
Contributor

Unfortunately looks like there's still a test failure

Sve.CreateBreakPropagateMask<Byte>(Vector<Byte>, Vector<Byte>): ConditionalSelectScenario_TrueValue failed:
mask: (0, 0, 163, 24, 1, 177, 161, 119, 75, 0, 11, 254, 0, 0, 0, 0)
left: (126, 118, 214, 86, 222, 102, 124, 252, 33, 13, 184, 112, 215, 251, 71, 199)
right: (151, 206, 181, 129, 228, 147, 128, 248, 118, 227, 192, 95, 142, 140, 109, 226)
falseOp: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
result: (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255)

Change-Id: I92c28f208bd65690604d472b8546b720605cdd5b
Comment threadsrc/coreclr/jit/gentree.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit, move block comment down next to gtFoldExprHWIntrinsic's definition

Comment threadsrc/coreclr/jit/gentree.cpp Outdated
canFold &= tree->Op(i)->OperIsConvertMaskToVector();
// Fold SVE intrinsics such as And/Or/Xor/ZipLow to their
// all-mask variants when every operand is mask-shaped.
canFold &= IsAllMaskVariantOperand(tree->Op(i), simdBaseType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the transformation for this is incorrect if CreateBreakAfterMask is NOT here

Sve.And(Sve.CompareGreaterThan(a, b), Vector.Create<short>(1))

Should produce a simd vector with lanes either zero or one. But now the morph transform activates this and wraps it in "ConvertMaskToVector", causing result lanes to either be zero or allbitsset.

Comment threadsrc/coreclr/jit/hwintrinsicarm64.cpp Outdated
// in lowering instead so decompose into the individual operations
// on import

op2 = gtNewSimdUnOpNode(GT_NOT, retType, op2, simdBaseType, simdSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's unclear to me why we no longer fold here, can you explain?

(Also NIT but if possible probably better to leave these combined and fold only if intrinsic==NI_AdvSimd_BitwiseClear just to cut down code duplication)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

We don’t fold the NOT in the SVE case so that lowering can recognize mask shaped operands and emit the predicate BIC form.

Comment threadsrc/coreclr/jit/lowerarmarch.cpp Outdated
// IsSveMaskOperand:
// Return true if "op" can be represented directly as an SVE predicate.
//
static bool IsSveMaskOperand(GenTree* op)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should be combined with IsAllMaskVariantOperand from gentree

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I've combined them


node->ResetHWIntrinsicId(NI_Sve_BitwiseClear_Predicates, op1, op2);
node->gtType = TYP_MASK;
*intrinsicId = NI_Sve_BitwiseClear_Predicates;

@dhartglassMSFTdhartglassMSFTSep 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The following code should trigger a INS_invalid assert, trying to do codegen for BitwiseClear_Predicates with floating point basetype:

 [MethodImpl(MethodImplOptions.NoInlining)]
private static Vector<float> ReproFloat(Vector<float> left, Vector<float> right)
{
Vector<float> firstMask = Sve.CompareLessThan(left, right);
Vector<float> secondMask = Sve.ZipLow(
Sve.CompareGreaterThan(left, right),
Sve.CompareEqual(left, right));
return firstMask & ~secondMask;
}

Maybe the hw intrinsic list table just needs a new entries for BitwiseClear_Predicates for fp type, or just reject floats if they're not allowed

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I added the floating point mappings to the intrinsics.

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

Labels

arch-arm64area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@jonathandavies-arm@dhartglassMSFT@a74nh@JulieLeeMSFT