[llvm] [SimplifyCFG] Preserve !unpredictable when forming a switch (PR #221580)
Timur Baidusenov via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 6 08:10:03 PDT 2026
https://github.com/bai-tim created https://github.com/llvm/llvm-project/pull/221580
SimplifyCFG turns a chain of equality comparisons into a switch in two places, performValueComparisonIntoPredecessorFolding() and simplifyBranchOnICmpChain(), and neither propagated !unpredictable. The hint was therefore dropped even though it is valid on a switch and the reverse transform, simplifySwitchOfCmpIntrinsic(), already preserves it.
Propagate the metadata in both. Folding a comparison into a predecessor merges two decisions into a single switch, so the result is only marked unpredictable when both of the folded comparisons were. That also makes chains longer than two blocks work: the fold runs once per predecessor and reads the metadata back off the switch it built on the previous iteration.
simplifyBranchOnICmpChain() also emits a range check instead of a switch when the case values are contiguous. That branch is an exact replacement for the original one, so it inherits the metadata unconditionally, and the flag now survives into the machine IR.
Fixes #42658.
>From 15d1c94222d55fb6a50803fa6eee4f3b274e505b Mon Sep 17 00:00:00 2001
From: Timur Baidusenov <timurbaidusenov at gmail.com>
Date: Sun, 6 Sep 2026 17:07:40 +0300
Subject: [PATCH] [SimplifyCFG] Preserve !unpredictable when forming a switch
SimplifyCFG turns a chain of equality comparisons into a switch in two
places, performValueComparisonIntoPredecessorFolding() and
simplifyBranchOnICmpChain(), and neither propagated !unpredictable. The
hint was therefore dropped even though it is valid on a switch and the
reverse transform, simplifySwitchOfCmpIntrinsic(), already preserves it.
Propagate the metadata in both. Folding a comparison into a predecessor
merges two decisions into a single switch, so the result is only marked
unpredictable when both of the folded comparisons were. That also makes
chains longer than two blocks work: the fold runs once per predecessor
and reads the metadata back off the switch it built on the previous
iteration.
simplifyBranchOnICmpChain() also emits a range check instead of a switch
when the case values are contiguous. That branch is an exact replacement
for the original one, so it inherits the metadata unconditionally, and
the flag now survives into the machine IR:
unpredictable JCC_1 %bb.2, 7, implicit $eflags
Fixes #42658.
Assisted-by: Claude (Anthropic)
---
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 10 +
.../preserve-unpredictable-switch-create.ll | 180 ++++++++++++++++++
2 files changed, 190 insertions(+)
create mode 100644 llvm/test/Transforms/SimplifyCFG/preserve-unpredictable-switch-create.ll
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 2a3a88713920b..cc2c769b25ce9 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1428,6 +1428,12 @@ bool SimplifyCFGOpt::performValueComparisonIntoPredecessorFolding(
setFittedBranchWeights(*NewSI, Weights, /*IsExpected=*/false,
/*ElideAllZero=*/true);
+ // The new switch is only known to be unpredictable if both of the comparisons
+ // it was built from were unpredictable.
+ if (MDNode *Unpredictable = PTI->getMetadata(LLVMContext::MD_unpredictable))
+ if (TI->hasMetadata(LLVMContext::MD_unpredictable))
+ NewSI->setMetadata(LLVMContext::MD_unpredictable, Unpredictable);
+
eraseTerminatorAndDCECond(PTI);
// Okay, last check. If BB is still a successor of PSI, then we must
@@ -5532,10 +5538,14 @@ bool SimplifyCFGOpt::simplifyBranchOnICmpChain(CondBrInst *BI,
CondBrInst *NewBI = Builder.CreateCondBr(Cond, EdgeBB, DefaultBB);
if (HasProfile)
setBranchWeights(*NewBI, BranchWeights, /*IsExpected=*/false);
+ if (MDNode *Unpredictable = BI->getMetadata(LLVMContext::MD_unpredictable))
+ NewBI->setMetadata(LLVMContext::MD_unpredictable, Unpredictable);
// We don't need to update PHI nodes since we don't add any new edges.
} else {
// Create the new switch instruction now.
SwitchInst *New = Builder.CreateSwitch(CompVal, DefaultBB, Values.size());
+ if (MDNode *Unpredictable = BI->getMetadata(LLVMContext::MD_unpredictable))
+ New->setMetadata(LLVMContext::MD_unpredictable, Unpredictable);
if (HasProfile) {
// We know the weight of the default case. We don't know the weight of the
// other cases, but rather than completely lose profiling info, we split
diff --git a/llvm/test/Transforms/SimplifyCFG/preserve-unpredictable-switch-create.ll b/llvm/test/Transforms/SimplifyCFG/preserve-unpredictable-switch-create.ll
new file mode 100644
index 0000000000000..3155d399d03e5
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/preserve-unpredictable-switch-create.ll
@@ -0,0 +1,180 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -S < %s | FileCheck %s
+
+declare void @f0()
+declare void @f1()
+declare void @f2()
+
+;; Folding a chain of unpredictable equality comparisons into a switch keeps the
+;; switch unpredictable.
+define void @fold_into_pred_unpredictable(i32 %x) {
+; CHECK-LABEL: define void @fold_into_pred_unpredictable(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: switch i32 [[X]], label %[[C:.*]] [
+; CHECK-NEXT: i32 0, label %[[A:.*]]
+; CHECK-NEXT: i32 1, label %[[B:.*]]
+; CHECK-NEXT: ], !unpredictable [[META0:![0-9]+]]
+; CHECK: [[A]]:
+; CHECK-NEXT: call void @f0()
+; CHECK-NEXT: br label %[[EXIT:.*]]
+; CHECK: [[B]]:
+; CHECK-NEXT: call void @f1()
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[C]]:
+; CHECK-NEXT: call void @f2()
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ %c0 = icmp eq i32 %x, 0
+ br i1 %c0, label %a, label %next, !unpredictable !0
+
+next:
+ %c1 = icmp eq i32 %x, 1
+ br i1 %c1, label %b, label %c, !unpredictable !0
+
+a:
+ call void @f0()
+ br label %exit
+
+b:
+ call void @f1()
+ br label %exit
+
+c:
+ call void @f2()
+ br label %exit
+
+exit:
+ ret void
+}
+
+;; If only one of the folded comparisons is unpredictable, nothing is known
+;; about the resulting switch.
+define void @fold_into_pred_partly_unpredictable(i32 %x) {
+; CHECK-LABEL: define void @fold_into_pred_partly_unpredictable(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: switch i32 [[X]], label %[[C:.*]] [
+; CHECK-NEXT: i32 0, label %[[A:.*]]
+; CHECK-NEXT: i32 1, label %[[B:.*]]
+; CHECK-NEXT: ]{{$}}
+; CHECK: [[A]]:
+; CHECK-NEXT: call void @f0()
+; CHECK-NEXT: br label %[[EXIT:.*]]
+; CHECK: [[B]]:
+; CHECK-NEXT: call void @f1()
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[C]]:
+; CHECK-NEXT: call void @f2()
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ %c0 = icmp eq i32 %x, 0
+ br i1 %c0, label %a, label %next, !unpredictable !0
+
+next:
+ %c1 = icmp eq i32 %x, 1
+ br i1 %c1, label %b, label %c
+
+a:
+ call void @f0()
+ br label %exit
+
+b:
+ call void @f1()
+ br label %exit
+
+c:
+ call void @f2()
+ br label %exit
+
+exit:
+ ret void
+}
+
+;; An unpredictable branch on a chain of equality comparisons becomes an
+;; unpredictable switch.
+define void @icmp_chain_to_switch(i32 %x) {
+; CHECK-LABEL: define void @icmp_chain_to_switch(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: switch i32 [[X]], label %[[B:.*]] [
+; CHECK-NEXT: i32 9, label %[[A:.*]]
+; CHECK-NEXT: i32 5, label %[[A]]
+; CHECK-NEXT: i32 1, label %[[A]]
+; CHECK-NEXT: ], !unpredictable [[META0]]
+; CHECK: [[A]]:
+; CHECK-NEXT: call void @f0()
+; CHECK-NEXT: br label %[[EXIT:.*]]
+; CHECK: [[B]]:
+; CHECK-NEXT: call void @f1()
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ %c0 = icmp eq i32 %x, 1
+ %c1 = icmp eq i32 %x, 5
+ %c2 = icmp eq i32 %x, 9
+ %o0 = or i1 %c0, %c1
+ %o1 = or i1 %o0, %c2
+ br i1 %o1, label %a, label %b, !unpredictable !0
+
+a:
+ call void @f0()
+ br label %exit
+
+b:
+ call void @f1()
+ br label %exit
+
+exit:
+ ret void
+}
+
+;; The same chain over a contiguous range becomes an unpredictable range check.
+define void @icmp_chain_to_range_check(i32 %x) {
+; CHECK-LABEL: define void @icmp_chain_to_range_check(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = add i32 [[X]], -1
+; CHECK-NEXT: [[TMP1:%.*]] = icmp ult i32 [[TMP0]], 3
+; CHECK-NEXT: br i1 [[TMP1]], label %[[A:.*]], label %[[B:.*]], !unpredictable [[META0]]
+; CHECK: [[A]]:
+; CHECK-NEXT: call void @f0()
+; CHECK-NEXT: br label %[[EXIT:.*]]
+; CHECK: [[B]]:
+; CHECK-NEXT: call void @f1()
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ %c0 = icmp eq i32 %x, 1
+ %c1 = icmp eq i32 %x, 2
+ %c2 = icmp eq i32 %x, 3
+ %o0 = or i1 %c0, %c1
+ %o1 = or i1 %o0, %c2
+ br i1 %o1, label %a, label %b, !unpredictable !0
+
+a:
+ call void @f0()
+ br label %exit
+
+b:
+ call void @f1()
+ br label %exit
+
+exit:
+ ret void
+}
+
+!0 = !{}
+;.
+; CHECK: [[META0]] = !{}
+;.
More information about the llvm-commits
mailing list