Skip to content
55 changes: 55 additions & 0 deletions llvm/lib/Transforms/Utils/SimplifyCFG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7540,6 +7540,58 @@ static bool reduceSwitchRange(SwitchInst *SI, IRBuilder<> &Builder,
return true;
}

/// Tries to transform the switch when the condition is umin and a constant.
/// In that case, the default branch can be replaced by the constant's branch.
/// For example:
/// switch(umin(a, 3)) {
/// case 0:
Copy link
Member

Choose a reason for hiding this comment

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

What about [4,1,2,3] or [1,2,3]?

Copy link
Member

Choose a reason for hiding this comment

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

Dead edges should be removed. Otherwise it will cause miscompilation: https://alive2.llvm.org/ce/z/Faeck4

Copy link
Member

@dianqk dianqk Oct 19, 2025

Choose a reason for hiding this comment

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

Sorry, I mean we can remove dead edges: https://alive2.llvm.org/ce/z/hC3Dbm.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this needs to be explicitly checked in the transform, we shouldn't rely on eliminateDeadSwitchCases having removed such cases (looking at the implementation, it uses known bits rather than ranges, so I think it may not eliminate all dead cases if the umin is not at a power of two boundary).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks :)
I have added a new commit which deletes cases where the value is higher than the constant

Copy link
Member

Choose a reason for hiding this comment

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

Missing test cases? I think you need add both tests of [1,2,3] that has holes and [4,1,2,3]?

/// case 1:
/// case 2:
/// case 3:
/// // ...
/// default:
/// unreachable
/// }
///
/// Transforms into:
///
/// switch(a) {
/// case 0:
/// case 1:
/// case 2:
/// default:
/// // This is case 3
/// }
static bool simplifySwitchWhenUMin(SwitchInst *SI, DomTreeUpdater *DTU) {
Value *A;
ConstantInt *Constant;

if (!match(SI->getCondition(), m_UMin(m_Value(A), m_ConstantInt(Constant))))
return false;

if (!SI->defaultDestUnreachable())
return false;

auto Case = SI->findCaseValue(Constant);

// When the case value cannot be found then `findCaseValue` returns the
// default cause. This means that there is no `case 3:` and this
// simplification fails.
if (Case == SI->case_default())
return false;

BasicBlock *Unreachable = SI->getDefaultDest();
SI->setDefaultDest(Case->getCaseSuccessor());
SI->removeCase(Case);
SI->setCondition(A);

BasicBlock *BB = SI->getParent();
if (DTU)
DTU->applyUpdates({{DominatorTree::Delete, BB, Unreachable}});

return true;
}

/// Tries to transform switch of powers of two to reduce switch range.
/// For example, switch like:
/// switch (C) { case 1: case 2: case 64: case 128: }
Expand Down Expand Up @@ -7966,6 +8018,9 @@ bool SimplifyCFGOpt::simplifySwitch(SwitchInst *SI, IRBuilder<> &Builder) {
if (simplifyDuplicateSwitchArms(SI, DTU))
return requestResimplify();

if (simplifySwitchWhenUMin(SI, DTU))
return requestResimplify();

return false;
}

Expand Down
105 changes: 105 additions & 0 deletions llvm/test/Transforms/SimplifyCFG/switch-umin.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt -S -passes=simplifycfg < %s | FileCheck %s

declare void @a()
declare void @b()
declare void @c()

define void @switch_replace_default(i32 %x) {
; CHECK-LABEL: define void @switch_replace_default(
; CHECK-SAME: i32 [[X:%.*]]) {
; CHECK-NEXT: [[MIN:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 3)
; CHECK-NEXT: switch i32 [[X]], label %[[COMMON_RET:.*]] [
; CHECK-NEXT: i32 0, label %[[CASE0:.*]]
; CHECK-NEXT: i32 1, label %[[CASE1:.*]]
; CHECK-NEXT: i32 2, label %[[CASE2:.*]]
; CHECK-NEXT: ]
; CHECK: [[COMMON_RET]]:
; CHECK-NEXT: ret void
; CHECK: [[CASE0]]:
; CHECK-NEXT: call void @a()
; CHECK-NEXT: br label %[[COMMON_RET]]
; CHECK: [[CASE1]]:
; CHECK-NEXT: call void @b()
; CHECK-NEXT: br label %[[COMMON_RET]]
; CHECK: [[CASE2]]:
; CHECK-NEXT: call void @c()
; CHECK-NEXT: br label %[[COMMON_RET]]
;
%min = call i32 @llvm.umin.i32(i32 %x, i32 3)
switch i32 %min, label %unreachable [
i32 0, label %case0
i32 1, label %case1
i32 2, label %case2
i32 3, label %case3
]

case0:
call void @a()
ret void

case1:
call void @b()
ret void

case2:
call void @c()
ret void

case3:
ret void

unreachable:
unreachable
}

define void @do_not_switch_replace_default(i32 %x, i32 %y) {
; CHECK-LABEL: define void @do_not_switch_replace_default(
; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
; CHECK-NEXT: [[MIN:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 [[Y]])
; CHECK-NEXT: switch i32 [[MIN]], label %[[UNREACHABLE:.*]] [
; CHECK-NEXT: i32 0, label %[[CASE0:.*]]
; CHECK-NEXT: i32 1, label %[[CASE1:.*]]
; CHECK-NEXT: i32 2, label %[[CASE2:.*]]
; CHECK-NEXT: i32 3, label %[[COMMON_RET:.*]]
; CHECK-NEXT: ]
; CHECK: [[COMMON_RET]]:
; CHECK-NEXT: ret void
; CHECK: [[CASE0]]:
; CHECK-NEXT: call void @a()
; CHECK-NEXT: br label %[[COMMON_RET]]
; CHECK: [[CASE1]]:
; CHECK-NEXT: call void @b()
; CHECK-NEXT: br label %[[COMMON_RET]]
; CHECK: [[CASE2]]:
; CHECK-NEXT: call void @c()
; CHECK-NEXT: br label %[[COMMON_RET]]
; CHECK: [[UNREACHABLE]]:
; CHECK-NEXT: unreachable
;
%min = call i32 @llvm.umin.i32(i32 %x, i32 %y)
switch i32 %min, label %unreachable [
i32 0, label %case0
i32 1, label %case1
i32 2, label %case2
i32 3, label %case3
]

case0:
call void @a()
ret void

case1:
call void @b()
ret void

case2:
call void @c()
ret void

case3:
ret void

unreachable:
unreachable
}