[llvm] [SimplifyCFG] Optimize switches on constant globals loads (PR #229035)
Ömer Sinan Ağacan via llvm-commits
llvm-commits at lists.llvm.org
Mon Oct 5 03:16:55 PDT 2026
https://github.com/osa1 created https://github.com/llvm/llvm-project/pull/229035
This introduces a new `SimplifyCFG` fold that transforms switches on loads from constant tables into loads from a new table, eliminating the switch.
The motivating example is in the new test `switch-to-table-1.ll`.
The fold is deliberately tried in every `simplifycfg` run to allow more inlining after early runs of `simplifycfg`. When run, the fold converts a `switch` to (in the worst case) a single `load` per PHI at the destination of the `switch`. When the destination doesn't have any other incoming edges other than the `switch`, the PHIs disappear completely. In all cases, the function usually gets smaller, which allows more inlining.
(This fold reuses the `SwitchReplacement` code from the existing `SimplifyCFG` folds, so it can also replace a PHI at the destination with a sequence of arithmetic instructions or with a register-sized bitmap lookup.)
Code size impact of this pass comes from new globals, which we limit as follows:
- When optimizing for size, we never create a new global, and when replacing the original one, we never replace it with a larger global.
- In all cases: we limit the number of new globals introduced to 3 per `switch`.
>From ddf1389d56da0dd67880ac327ac7b2cd6a4adf29 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= <omer at osa1.net>
Date: Thu, 24 Sep 2026 13:50:14 +0100
Subject: [PATCH] [SimplifyCFG] Optimize switches on constant globals loads
This introduces a new `SimplifyCFG` fold that transforms switches on loads from
constant tables into loads from a new table, eliminating the switch.
The motivating example is in the new test `switch-to-table-1.ll`.
The fold is deliberately tried in every `simplifycfg` run to allow more
inlining after early runs of `simplifycfg`. When run, the fold converts a
`switch` to (in the worst case) a single `load` per PHI at the destination of
the `switch`. When the destination doesn't have any other incoming edges other
than the `switch`, the PHIs disappear completely. In all cases, the function
usually gets smaller, which allows more inlining.
(This fold reuses the `SwitchReplacement` code from the existing `SimplifyCFG`
folds, so it can also replace a PHI at the destination with a sequence of
arithmetic instructions or with a register-sized bitmap lookup.)
Code size impact of this pass comes from new globals, which we limit as
follows:
- When optimizing for size, we never create a new global, and when replacing
the original one, we never replace it with a larger global.
- In all cases: we limit the number of new globals introduced to 3 per
`switch`.
---
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 215 ++++++++++++++++++
.../SimplifyCFG/switch-to-table-1.ll | 48 ++++
.../SimplifyCFG/switch-to-table-10.ll | 64 ++++++
.../SimplifyCFG/switch-to-table-11.ll | 92 ++++++++
.../SimplifyCFG/switch-to-table-12.ll | 117 ++++++++++
.../SimplifyCFG/switch-to-table-2.ll | 56 +++++
.../SimplifyCFG/switch-to-table-3.ll | 47 ++++
.../SimplifyCFG/switch-to-table-4.ll | 48 ++++
.../SimplifyCFG/switch-to-table-5.ll | 92 ++++++++
.../SimplifyCFG/switch-to-table-6.ll | 41 ++++
.../SimplifyCFG/switch-to-table-7.ll | 62 +++++
.../SimplifyCFG/switch-to-table-8.ll | 66 ++++++
.../SimplifyCFG/switch-to-table-9.ll | 59 +++++
.../SimplifyCFG/switch-to-table-hang.ll | 43 ++++
14 files changed, 1050 insertions(+)
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-1.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-10.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-11.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-12.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-2.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-3.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-4.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-5.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-6.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-7.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-8.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-9.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-to-table-hang.ll
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 7b31b2ec37a66..d3038bf2d91d6 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -205,6 +205,17 @@ static cl::opt<unsigned> MaxJumpThreadingLiveBlocks(
cl::desc("Limit number of blocks a define in a threaded block is allowed "
"to be live in"));
+static cl::opt<unsigned> MaxConstTableSwitchElts(
+ "max-const-table-switch-elts", cl::Hidden, cl::init(32),
+ cl::desc(
+ "Limit number of const table elements to consider before folding the "
+ "table into a switch"));
+
+static cl::opt<unsigned> MaxLookupTablesPerConstTableSwitch(
+ "max-lookup-tables-per-const-table-switch", cl::Hidden, cl::init(3),
+ cl::desc("Limit number of lookup tables introduced per const table lookup "
+ "switch"));
+
extern cl::opt<bool> ProfcheckDisableMetadataFixes;
} // end namespace llvm
@@ -218,6 +229,8 @@ STATISTIC(
NumLookupTablesHoles,
"Number of switch instructions turned into lookup tables (holes checked)");
STATISTIC(NumTableCmpReuses, "Number of reused switch table lookup compares");
+STATISTIC(NumSwitchOnConstTableLoad,
+ "Number of switches on a constant table load folded into the table");
STATISTIC(NumFoldValueComparisonIntoPredecessors,
"Number of value comparisons folded into predecessor basic blocks");
STATISTIC(NumFoldBranchToCommonDest,
@@ -7046,6 +7059,8 @@ class SwitchReplacement {
/// Return true if a suitable switch replacement was found.
bool isValid() const { return Kind != InvalidKind; }
+ const Constant *getLookupTableInitializer() { return Initializer; }
+
private:
// Depending on the switch, there are different alternatives.
enum {
@@ -7569,6 +7584,202 @@ static void reuseTableCompare(
}
}
+static bool isOnlyUsedByLoad(const GlobalVariable *GV, const LoadInst *LI) {
+ SmallVector<const User *, 8> Worklist(GV->users());
+ while (!Worklist.empty()) {
+ const User *U = Worklist.pop_back_val();
+ if (U == LI)
+ continue;
+ if (isa<GEPOperator>(U)) {
+ append_range(Worklist, U->users());
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+/// If the switch is on a load from a const global and the switch branches only
+/// update PHIs in a common destination block with constants, transform the
+/// original const global to globals with the constants passed to the PHIs, and
+/// convert the PHIs to reads from the globals.
+/// See `SimpifyCFG/switch-to-table-N.ll` tests for examples.
+static bool foldSwitchOnConstTableLoad(SwitchInst *SI, IRBuilder<> &Builder,
+ DomTreeUpdater *DTU,
+ const DataLayout &DL,
+ const TargetTransformInfo &TTI,
+ bool ConvertSwitchToLookupTable) {
+ auto *LI = dyn_cast<LoadInst>(SI->getCondition());
+ if (!LI || LI->isVolatile())
+ return false;
+
+ // Check the underlying object before decomposing the pointer:
+ // `decomposeLinearExpression` doesn't terminate on cyclic GEPs, which can
+ // appear in blocks that become unreachable during SimplifyCFG.
+ auto *GV =
+ dyn_cast<GlobalVariable>(getUnderlyingObject(LI->getPointerOperand()));
+ if (!GV || !GV->isConstant() || !GV->hasDefinitiveInitializer())
+ return false;
+
+ // Load offset = LIOperand.Offset + (LIOperand.Index * LIOperand.Scale)
+ LinearExpression LIOperand =
+ decomposeLinearExpression(DL, LI->getPointerOperand());
+
+ if (!LIOperand.Flags.isInBounds()) {
+ // We could still do this if
+ // - Scales of the original table and the new tables are the same.
+ // - Offset is 0.
+ // We'd also need to generate `inbounds` only if the original GEP has one.
+ // (currently `replaceSwitch` generates `inbounds` always)
+ return false;
+ }
+
+ if (LIOperand.BasePtr != GV || !LIOperand.Index)
+ return false;
+
+ // Because we map indices of the old global to indices of the new global
+ // directly, we can't handle the cases where the offset skips an element or
+ // more.
+ // Single ult below as shorthand for `0 <= offset && offset < scale`.
+ if (!LIOperand.Offset.ult(LIOperand.Scale))
+ return false;
+
+ Type *LoadEltTy = LI->getType();
+ const TypeSize GVSize = DL.getTypeAllocSize(GV->getValueType());
+ const TypeSize LoadSize = DL.getTypeStoreSize(LoadEltTy);
+
+ // Check that we can make at least one load.
+ if (LIOperand.Offset.getZExtValue() + LoadSize > GVSize)
+ return false;
+
+ Constant *Init = GV->getInitializer();
+ BasicBlock *CommonDest = nullptr;
+
+ // Maps loaded elements to the values of PHIs at the destination block
+ // (`CommonDest`) of the `switch`.
+ using ResultsTy = SmallVector<std::pair<PHINode *, Constant *>, 4>;
+ SmallVector<ResultsTy, 64> EltResults;
+
+ const uint64_t Scale = LIOperand.Scale.getZExtValue();
+
+ // `Offset + LoadSize <= GVSize` checked above.
+ const uint64_t NumElts =
+ ((GVSize.getFixedValue() - LIOperand.Offset.getZExtValue() - LoadSize) /
+ Scale) +
+ 1;
+
+ if (NumElts > MaxConstTableSwitchElts)
+ return false;
+
+ // NB. We check above that offset < scale, so we don't have to consider
+ // negative indices here and we can start at offset.
+ for (uint64_t I = 0; I < NumElts; I += 1) {
+ APInt Offset = LIOperand.Offset + (I * Scale);
+
+ auto *Elt = dyn_cast_or_null<ConstantInt>(
+ ConstantFoldLoadFromConst(Init, LoadEltTy, Offset, DL));
+
+ if (!Elt)
+ return false;
+
+ auto Case = SI->findCaseValue(Elt);
+
+ ResultsTy Results;
+ if (!getCaseResults(SI, Elt, Case->getCaseSuccessor(), &CommonDest, Results,
+ DL))
+ return false;
+ EltResults.push_back(std::move(Results));
+ }
+
+ // PHIs at the destination that'll be reading a lookup table.
+ // NB. We rely on EltResults having the the PHIs in the same order each of the
+ // results vector here and below.
+ SmallVector<PHINode *, 4> PHIs;
+ for (const auto &[PHI, _] : EltResults.front())
+ PHIs.push_back(PHI);
+
+ // Create replacements for the PHIs at the destination.
+ Function *Fn = SI->getFunction();
+ Module &M = *Fn->getParent();
+ Type *IndexTy = LIOperand.Index->getType();
+ SmallVector<SwitchReplacement, 4> Replacements;
+ for (auto [PhiIdx, PHI] : enumerate(PHIs)) {
+ SmallVector<std::pair<ConstantInt *, Constant *>, 64> Values;
+ for (auto [I, Results] : enumerate(EltResults))
+ Values.emplace_back(cast<ConstantInt>(ConstantInt::get(IndexTy, I)),
+ Results[PhiIdx].second);
+ Constant *Poison = PoisonValue::get(PHI->getType());
+ SwitchReplacement Replacement(
+ M, NumElts, cast<ConstantInt>(ConstantInt::get(IndexTy, 0)), Values,
+ Poison, DL, TTI, Fn->getName());
+ if (!Replacement.isValid())
+ return false;
+ Replacements.emplace_back(Replacement);
+ }
+
+ // When optimizing for size make sure we don't introduce more global data than
+ // we remove.
+ if (Fn->hasOptSize()) {
+ uint64_t AddedBytes = 0;
+ for (auto &R : Replacements) {
+ if (R.isLookupTable()) {
+ AddedBytes +=
+ DL.getTypeAllocSize(R.getLookupTableInitializer()->getType())
+ .getFixedValue();
+ }
+ }
+ uint64_t RemovedBytes =
+ GV->hasLocalLinkage() && isOnlyUsedByLoad(GV, LI) && LI->hasOneUser()
+ ? GVSize.getFixedValue()
+ : 0;
+ if (AddedBytes > RemovedBytes)
+ return false;
+ }
+
+ // In all optimization levels: limit number of lookup tables introduced per
+ // switch.
+ unsigned NumLookupTables = 0;
+ for (auto &Replacement : Replacements) {
+ if (Replacement.isLookupTable()) {
+ NumLookupTables += 1;
+ }
+ }
+ if (NumLookupTables > MaxLookupTablesPerConstTableSwitch)
+ return false;
+
+ // Branch to a new block that reads the results from the new globals and jumps
+ // to `CommonDest`.
+ BasicBlock *BB = SI->getParent();
+ BasicBlock *LookupBB =
+ BasicBlock::Create(M.getContext(), "switch.lookup", Fn, CommonDest);
+ Builder.SetInsertPoint(SI);
+ Builder.CreateBr(LookupBB);
+ Builder.SetInsertPoint(LookupBB);
+ for (auto [PHI, Replacement] : zip(PHIs, Replacements))
+ PHI->addIncoming(
+ Replacement.replaceSwitch(LIOperand.Index, Builder, DL, Fn), LookupBB);
+ Builder.CreateBr(CommonDest);
+
+ // Remove the switch.
+ std::vector<DominatorTree::UpdateType> Updates;
+ SmallPtrSet<BasicBlock *, 8> RemovedSuccessors;
+ for (BasicBlock *Succ : successors(SI)) {
+ Succ->removePredecessor(BB, /*KeepOneInputPHIs=*/true);
+ if (RemovedSuccessors.insert(Succ).second)
+ Updates.push_back({DominatorTree::Delete, BB, Succ});
+ }
+ SI->eraseFromParent();
+
+ if (DTU) {
+ Updates.push_back({DominatorTree::Insert, BB, LookupBB});
+ Updates.push_back({DominatorTree::Insert, LookupBB, CommonDest});
+ DTU->applyUpdates(Updates);
+ }
+
+ ++NumSwitchOnConstTableLoad;
+ return true;
+}
+
/// If the switch is only used to initialize one or more phi nodes in a common
/// successor block with different constant values, replace the switch with
/// lookup tables.
@@ -8637,6 +8848,10 @@ bool SimplifyCFGOpt::simplifySwitch(SwitchInst *SI, IRBuilder<> &Builder) {
if (Options.ForwardSwitchCondToPhi && forwardSwitchConditionToPHI(SI))
return requestResimplify();
+ if (foldSwitchOnConstTableLoad(SI, Builder, DTU, DL, TTI,
+ Options.ConvertSwitchToLookupTable))
+ return requestResimplify();
+
// The conversion of switches to arithmetic or lookup table is disabled in
// the early optimization pipeline, as it may lose information or make the
// resulting code harder to analyze.
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-1.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-1.ll
new file mode 100644
index 0000000000000..e19e654de68b5
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-1.ll
@@ -0,0 +1,48 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; elems = [ 0, 5, 6, 1, 7, 8, 2, 9, 10, 3, 11, 12, 4, 4, 4, 4, 13, 14, 15, 16, 17, 18 ]
+; indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 ]
+ at table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+
+;.
+; CHECK: @table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+; CHECK: @switch.table.test1 = private unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\03\03\03\03\03\03\0D\0E\0F\10\09\0A", align 1
+;.
+define i8 @test1(i64 %idx) {
+; CHECK-LABEL: define i8 @test1(
+; CHECK-SAME: i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, ptr @table1, i64 [[IDX]]
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT: [[SWITCH_GEP:%.*]] = getelementptr inbounds [22 x i8], ptr @switch.table.test1, i64 0, i64 [[IDX]]
+; CHECK-NEXT: [[SWITCH_LOAD:%.*]] = load i8, ptr [[SWITCH_GEP]], align 1
+; CHECK-NEXT: ret i8 [[SWITCH_LOAD]]
+;
+entry:
+ %gep = getelementptr inbounds i8, ptr @table1, i64 %idx
+ %v = load i8, ptr %gep, align 1
+ switch i8 %v, label %sw.default [
+ i8 4, label %exit
+ i8 12, label %exit
+ i8 11, label %exit
+ i8 18, label %sw.bb1
+ i8 17, label %sw.bb2
+ ]
+
+sw.bb1:
+ br label %exit
+
+sw.bb2:
+ br label %exit
+
+sw.default:
+ br label %exit
+
+exit:
+ %r = phi i8 [ %v, %sw.default ], [ 9, %sw.bb2 ], [ 10, %sw.bb1 ], [ 3, %entry ], [ 3, %entry ], [ 3, %entry ]
+ ret i8 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-10.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-10.ll
new file mode 100644
index 0000000000000..cfa6105b7f4dc
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-10.ll
@@ -0,0 +1,64 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; Similar to switch-to-table-9.ll, but the extra argument to the PHI is not constant.
+
+; elems = [ 0, 5, 6, 1, 7, 8, 2, 9, 10, 3, 11, 12, 4, 4, 4, 4, 13, 14, 15, 16, 17, 18 ]
+; indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 ]
+ at table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+
+;.
+; CHECK: @table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+; CHECK: @switch.table.test_exra_phi_argument_variable = private unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\03\03\03\03\03\03\0D\0E\0F\10\09\0A", align 1
+;.
+define i8 @test_exra_phi_argument_variable(i1 %c, i64 %i, ptr %p, ptr %q) {
+; CHECK-LABEL: define i8 @test_exra_phi_argument_variable(
+; CHECK-SAME: i1 [[C:%.*]], i64 [[I:%.*]], ptr [[P:%.*]], ptr [[Q:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[GEP0:%.*]] = getelementptr inbounds nuw i8, ptr @table1, i64 [[I]]
+; CHECK-NEXT: [[V0:%.*]] = load i8, ptr [[GEP0]], align 1
+; CHECK-NEXT: [[I1:%.*]] = add i64 [[I]], 1
+; CHECK-NEXT: br i1 [[C]], label %[[LOOKUP:.*]], label %[[EXIT:.*]]
+; CHECK: [[LOOKUP]]:
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds nuw i8, ptr @table1, i64 [[I1]]
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT: [[SWITCH_GEP:%.*]] = getelementptr inbounds [22 x i8], ptr @switch.table.test_exra_phi_argument_variable, i64 0, i64 [[I1]]
+; CHECK-NEXT: [[SWITCH_LOAD:%.*]] = load i8, ptr [[SWITCH_GEP]], align 1
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[R:%.*]] = phi i8 [ [[V0]], %[[ENTRY]] ], [ [[SWITCH_LOAD]], %[[LOOKUP]] ]
+; CHECK-NEXT: ret i8 [[R]]
+;
+entry:
+ %gep0 = getelementptr inbounds nuw i8, ptr @table1, i64 %i
+ %v0 = load i8, ptr %gep0
+ %i1 = add i64 %i, 1
+ br i1 %c, label %lookup, label %exit
+
+lookup:
+ %gep = getelementptr inbounds nuw i8, ptr @table1, i64 %i1
+ %v = load i8, ptr %gep
+ switch i8 %v, label %default [
+ i8 4, label %exit
+ i8 12, label %exit
+ i8 11, label %exit
+ i8 18, label %bb1
+ i8 17, label %bb2
+ ]
+
+bb1:
+ br label %exit
+
+bb2:
+ br label %exit
+
+default:
+ br label %exit
+
+exit:
+ %r = phi i8 [ %v0, %entry ], [ 3, %lookup ], [ 3, %lookup ], [ 3, %lookup ], [ 10, %bb1 ], [ 9, %bb2 ], [ %v, %default ]
+ ret i8 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-11.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-11.ll
new file mode 100644
index 0000000000000..a009213d40bf4
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-11.ll
@@ -0,0 +1,92 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; This could potentially be implemented in a different pass: `%p.phi` takes the
+; same value in the paths from the `switch`, so the table can be kept as-is and
+; the PHI can be turned into a two-argument one:
+; %p.phi = phi ptr [ %q %entry ], [ %p, %lookup ]
+; Note that even a full `default<O3>` doesn't do this optimization.
+
+; elems = [ 0, 5, 6, 1, 7, 8, 2, 9, 10, 3, 11, 12, 4, 4, 4, 4, 13, 14, 15, 16, 17, 18 ]
+; indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 ]
+ at table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+
+;.
+; CHECK: @table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+;.
+define i8 @test_missed_optimization(i1 %c, i64 %i, ptr %p, ptr %q) {
+; CHECK-LABEL: define i8 @test_missed_optimization(
+; CHECK-SAME: i1 [[C:%.*]], i64 [[I:%.*]], ptr [[P:%.*]], ptr [[Q:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br i1 [[C]], label %[[LOOKUP:.*]], label %[[EXIT:.*]]
+; CHECK: [[LOOKUP]]:
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds nuw i8, ptr @table1, i64 [[I]]
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT: switch i8 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT: i8 4, label %[[EXIT]]
+; CHECK-NEXT: i8 12, label %[[EXIT]]
+; CHECK-NEXT: i8 11, label %[[EXIT]]
+; CHECK-NEXT: i8 18, label %[[SW_BB1:.*]]
+; CHECK-NEXT: i8 17, label %[[SW_BB2:.*]]
+; CHECK-NEXT: ]
+; CHECK: [[SW_BB1]]:
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[SW_BB2]]:
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[DEFAULT]]:
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[R:%.*]] = phi i8 [ 0, %[[ENTRY]] ], [ 3, %[[LOOKUP]] ], [ 3, %[[LOOKUP]] ], [ 3, %[[LOOKUP]] ], [ 10, %[[SW_BB1]] ], [ 9, %[[SW_BB2]] ], [ [[V]], %[[DEFAULT]] ]
+; CHECK-NEXT: [[P_PHI:%.*]] = phi ptr [ [[Q]], %[[ENTRY]] ], [ [[P]], %[[LOOKUP]] ], [ [[P]], %[[LOOKUP]] ], [ [[P]], %[[LOOKUP]] ], [ [[P]], %[[SW_BB1]] ], [ [[P]], %[[SW_BB2]] ], [ [[P]], %[[DEFAULT]] ]
+; CHECK-NEXT: store i8 [[R]], ptr [[P_PHI]], align 1
+; CHECK-NEXT: ret i8 [[R]]
+;
+entry:
+ br i1 %c, label %lookup, label %exit
+
+lookup:
+ %gep = getelementptr inbounds nuw i8, ptr @table1, i64 %i
+ %v = load i8, ptr %gep
+ switch i8 %v, label %default [
+ i8 4, label %exit
+ i8 12, label %exit
+ i8 11, label %exit
+ i8 18, label %sw.bb1
+ i8 17, label %sw.bb2
+ ]
+
+sw.bb1:
+ br label %exit
+
+sw.bb2:
+ br label %exit
+
+default:
+ br label %exit
+
+exit:
+ %r = phi i8
+ [ 0, %entry ],
+ [ 3, %lookup ],
+ [ 3, %lookup ],
+ [ 3, %lookup ],
+ [ 10, %sw.bb1 ],
+ [ 9, %sw.bb2 ],
+ [ %v, %default ]
+
+ %p.phi = phi ptr
+ [ %q, %entry ],
+ [ %p, %lookup ],
+ [ %p, %lookup ],
+ [ %p, %lookup ],
+ [ %p, %sw.bb1 ],
+ [ %p, %sw.bb2 ],
+ [ %p, %default ]
+
+ store i8 %r, ptr %p.phi
+
+ ret i8 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-12.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-12.ll
new file mode 100644
index 0000000000000..f0631480f32b2
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-12.ll
@@ -0,0 +1,117 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; Test the default limit on the number of lookup tables introduced per switch
+; (`-max-lookup-tables-per-const-table-switch`, 3 by default): three PHIs at
+; the destination are folded into three tables, four PHIs are not folded.
+
+; elems = [ 0, 5, 6, 1, 7, 8, 2, 9, 10, 3, 11, 12, 4, 4, 4, 4, 13, 14, 15, 16, 17, 18 ]
+; indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 ]
+ at table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+
+;.
+; CHECK: @table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+; CHECK: @switch.table.three_phis = private unnamed_addr constant [22 x i8] c"aaaaaaaaaaa\14\0A\0A\0A\0Aaaaa\1Ea", align 4
+; CHECK: @switch.table.three_phis.1 = private unnamed_addr constant [22 x i8] c"AAAAAAAAAAA\03\07\07\07\07AAAA\09A", align 4
+; CHECK: @switch.table.three_phis.2 = private unnamed_addr constant [22 x i8] c"22222222222\08\01\01\01\012222\022", align 4
+;.
+define i32 @three_phis(i64 %idx) {
+; CHECK-LABEL: define i32 @three_phis(
+; CHECK-SAME: i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, ptr @table1, i64 [[IDX]]
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT: [[SWITCH_GEP:%.*]] = getelementptr inbounds [22 x i8], ptr @switch.table.three_phis, i64 0, i64 [[IDX]]
+; CHECK-NEXT: [[SWITCH_LOAD:%.*]] = load i8, ptr [[SWITCH_GEP]], align 1
+; CHECK-NEXT: [[SWITCH_EXT:%.*]] = zext i8 [[SWITCH_LOAD]] to i32
+; CHECK-NEXT: [[SWITCH_GEP1:%.*]] = getelementptr inbounds [22 x i8], ptr @switch.table.three_phis.1, i64 0, i64 [[IDX]]
+; CHECK-NEXT: [[SWITCH_LOAD2:%.*]] = load i8, ptr [[SWITCH_GEP1]], align 1
+; CHECK-NEXT: [[SWITCH_EXT3:%.*]] = zext i8 [[SWITCH_LOAD2]] to i32
+; CHECK-NEXT: [[SWITCH_GEP4:%.*]] = getelementptr inbounds [22 x i8], ptr @switch.table.three_phis.2, i64 0, i64 [[IDX]]
+; CHECK-NEXT: [[SWITCH_LOAD5:%.*]] = load i8, ptr [[SWITCH_GEP4]], align 1
+; CHECK-NEXT: [[SWITCH_EXT6:%.*]] = zext i8 [[SWITCH_LOAD5]] to i32
+; CHECK-NEXT: [[AB:%.*]] = add i32 [[SWITCH_EXT]], [[SWITCH_EXT3]]
+; CHECK-NEXT: [[R:%.*]] = add i32 [[AB]], [[SWITCH_EXT6]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+entry:
+ %gep = getelementptr inbounds i8, ptr @table1, i64 %idx
+ %v = load i8, ptr %gep, align 1
+ switch i8 %v, label %def [
+ i8 4, label %c0
+ i8 12, label %c1
+ i8 17, label %c2
+ ]
+c0:
+ br label %end
+c1:
+ br label %end
+c2:
+ br label %end
+def:
+ br label %end
+end:
+ %a = phi i32 [ 10, %c0 ], [ 20, %c1 ], [ 30, %c2 ], [ 97, %def ]
+ %b = phi i32 [ 7, %c0 ], [ 3, %c1 ], [ 9, %c2 ], [ 65, %def ]
+ %c = phi i32 [ 1, %c0 ], [ 8, %c1 ], [ 2, %c2 ], [ 50, %def ]
+ %ab = add i32 %a, %b
+ %r = add i32 %ab, %c
+ ret i32 %r
+}
+
+define i32 @four_phis(i64 %idx) {
+; CHECK-LABEL: define i32 @four_phis(
+; CHECK-SAME: i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, ptr @table1, i64 [[IDX]]
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT: switch i8 [[V]], label %[[DEF:.*]] [
+; CHECK-NEXT: i8 4, label %[[END:.*]]
+; CHECK-NEXT: i8 12, label %[[C1:.*]]
+; CHECK-NEXT: i8 17, label %[[C2:.*]]
+; CHECK-NEXT: ]
+; CHECK: [[C1]]:
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[C2]]:
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[DEF]]:
+; CHECK-NEXT: br label %[[END]]
+; CHECK: [[END]]:
+; CHECK-NEXT: [[A:%.*]] = phi i32 [ 97, %[[DEF]] ], [ 20, %[[C1]] ], [ 30, %[[C2]] ], [ 10, %[[ENTRY]] ]
+; CHECK-NEXT: [[B:%.*]] = phi i32 [ 65, %[[DEF]] ], [ 3, %[[C1]] ], [ 9, %[[C2]] ], [ 7, %[[ENTRY]] ]
+; CHECK-NEXT: [[C:%.*]] = phi i32 [ 50, %[[DEF]] ], [ 8, %[[C1]] ], [ 2, %[[C2]] ], [ 1, %[[ENTRY]] ]
+; CHECK-NEXT: [[D:%.*]] = phi i32 [ 33, %[[DEF]] ], [ 11, %[[C1]] ], [ 6, %[[C2]] ], [ 40, %[[ENTRY]] ]
+; CHECK-NEXT: [[AB:%.*]] = add i32 [[A]], [[B]]
+; CHECK-NEXT: [[CD:%.*]] = add i32 [[C]], [[D]]
+; CHECK-NEXT: [[R:%.*]] = add i32 [[AB]], [[CD]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+entry:
+ %gep = getelementptr inbounds i8, ptr @table1, i64 %idx
+ %v = load i8, ptr %gep, align 1
+ switch i8 %v, label %def [
+ i8 4, label %c0
+ i8 12, label %c1
+ i8 17, label %c2
+ ]
+c0:
+ br label %end
+c1:
+ br label %end
+c2:
+ br label %end
+def:
+ br label %end
+end:
+ %a = phi i32 [ 10, %c0 ], [ 20, %c1 ], [ 30, %c2 ], [ 97, %def ]
+ %b = phi i32 [ 7, %c0 ], [ 3, %c1 ], [ 9, %c2 ], [ 65, %def ]
+ %c = phi i32 [ 1, %c0 ], [ 8, %c1 ], [ 2, %c2 ], [ 50, %def ]
+ %d = phi i32 [ 40, %c0 ], [ 11, %c1 ], [ 6, %c2 ], [ 33, %def ]
+ %ab = add i32 %a, %b
+ %cd = add i32 %c, %d
+ %r = add i32 %ab, %cd
+ ret i32 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-2.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-2.ll
new file mode 100644
index 0000000000000..d69d97bbe6fcd
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-2.ll
@@ -0,0 +1,56 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; Similar to switch-to-table-1.ll, but update multiple PHIs at the destination.
+
+; elems = [ 0, 5, 6, 1, 7, 8, 2, 9, 10, 3, 11, 12, 4, 4, 4, 4, 13, 14, 15, 16, 17, 18 ]
+; indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 ]
+ at table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+
+;.
+; CHECK: @table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+; CHECK: @switch.table.test2 = private unnamed_addr constant [22 x i8] c"aaaaaaaaaaa\14\0A\0A\0A\0Aaaaa\1Ea", align 4
+; CHECK: @switch.table.test2.1 = private unnamed_addr constant [22 x i8] c"AAAAAAAAAAA\03\07\07\07\07AAAA\09A", align 2
+;.
+define i32 @test2(i64 %idx, ptr %out) {
+; CHECK-LABEL: define i32 @test2(
+; CHECK-SAME: i64 [[IDX:%.*]], ptr [[OUT:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, ptr @table1, i64 [[IDX]]
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT: [[SWITCH_GEP:%.*]] = getelementptr inbounds [22 x i8], ptr @switch.table.test2, i64 0, i64 [[IDX]]
+; CHECK-NEXT: [[SWITCH_LOAD:%.*]] = load i8, ptr [[SWITCH_GEP]], align 1
+; CHECK-NEXT: [[SWITCH_EXT:%.*]] = zext i8 [[SWITCH_LOAD]] to i32
+; CHECK-NEXT: [[SWITCH_GEP1:%.*]] = getelementptr inbounds [22 x i8], ptr @switch.table.test2.1, i64 0, i64 [[IDX]]
+; CHECK-NEXT: [[SWITCH_LOAD2:%.*]] = load i8, ptr [[SWITCH_GEP1]], align 1
+; CHECK-NEXT: [[SWITCH_EXT3:%.*]] = zext i8 [[SWITCH_LOAD2]] to i16
+; CHECK-NEXT: [[BZ:%.*]] = zext i16 [[SWITCH_EXT3]] to i32
+; CHECK-NEXT: [[R:%.*]] = add i32 [[SWITCH_EXT]], [[BZ]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+entry:
+ %gep = getelementptr inbounds i8, ptr @table1, i64 %idx
+ %v = load i8, ptr %gep, align 1
+ switch i8 %v, label %def [
+ i8 4, label %c0
+ i8 12, label %c1
+ i8 17, label %c2
+ ]
+c0:
+ br label %end
+c1:
+ br label %end
+c2:
+ br label %end
+def:
+ br label %end
+end:
+ %a = phi i32 [ 10, %c0 ], [ 20, %c1 ], [ 30, %c2 ], [ 97, %def ]
+ %b = phi i16 [ 7, %c0 ], [ 3, %c1 ], [ 9, %c2 ], [ 65, %def ]
+ %bz = zext i16 %b to i32
+ %r = add i32 %a, %bz
+ ret i32 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-3.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-3.ll
new file mode 100644
index 0000000000000..656ee2045c7f3
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-3.ll
@@ -0,0 +1,47 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; Same as switch-to-table-1.ll, but with a `switch` with a value that's not in the original table.
+
+; elems = [ 0, 5, 6, 1, 7, 8, 2, 9, 10, 3, 11, 12, 4, 4, 4, 4, 13, 14, 15, 16, 17, 18 ]
+; indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 ]
+ at table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+
+;.
+; CHECK: @table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+; CHECK: @switch.table.test3 = private unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\0A\0A\0A\0A\0D\0E\0F\10\11\12", align 1
+;.
+define i8 @test3(i64 %idx) {
+; CHECK-LABEL: define i8 @test3(
+; CHECK-SAME: i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, ptr @table1, i64 [[IDX]]
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT: [[SWITCH_GEP:%.*]] = getelementptr inbounds [22 x i8], ptr @switch.table.test3, i64 0, i64 [[IDX]]
+; CHECK-NEXT: [[SWITCH_LOAD:%.*]] = load i8, ptr [[SWITCH_GEP]], align 1
+; CHECK-NEXT: ret i8 [[SWITCH_LOAD]]
+;
+entry:
+ %gep = getelementptr inbounds i8, ptr @table1, i64 %idx
+ %v = load i8, ptr %gep, align 1
+ switch i8 %v, label %sw.default [
+ i8 4, label %sw.bb1
+ i8 99, label %sw.bb2 ; not in the global
+ ]
+
+sw.bb1:
+ br label %exit
+
+sw.bb2:
+ br label %exit
+
+sw.default:
+ br label %exit
+
+exit:
+ %r = phi i8 [ %v, %sw.default ], [ 10, %sw.bb1 ], [ 123, %sw.bb2 ]
+ ret i8 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-4.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-4.ll
new file mode 100644
index 0000000000000..b061496c5e9ee
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-4.ll
@@ -0,0 +1,48 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; Read a larger value than the global's element size.
+
+; elems = [ 0, 5, 6, 1, 7, 8, 2, 9, 10, 3, 11, 12, 4, 4, 4, 4, 13, 14, 15, 16, 17, 18 ]
+; indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 ]
+ at table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+
+;.
+; CHECK: @table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+; CHECK: @switch.table.test4 = private unnamed_addr constant [5 x i32] [i32 17171712, i32 151128071, i32 202048266, i32 67372036, i32 269422093], align 4
+;.
+define i32 @test4(i64 %idx) {
+; CHECK-LABEL: define i32 @test4(
+; CHECK-SAME: i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i32, ptr @table1, i64 [[IDX]]
+; CHECK-NEXT: [[V:%.*]] = load i32, ptr [[GEP]], align 1
+; CHECK-NEXT: [[SWITCH_GEP:%.*]] = getelementptr inbounds [5 x i32], ptr @switch.table.test4, i64 0, i64 [[IDX]]
+; CHECK-NEXT: [[SWITCH_LOAD:%.*]] = load i32, ptr [[SWITCH_GEP]], align 4
+; CHECK-NEXT: ret i32 [[SWITCH_LOAD]]
+;
+entry:
+ %gep = getelementptr inbounds i32, ptr @table1, i64 %idx
+ %v = load i32, ptr %gep, align 1
+ switch i32 %v, label %sw.default [
+ i32 0, label %sw.bb1
+ i32 5, label %sw.bb2
+ i32 10000000, label %sw.bb2
+ ]
+
+sw.bb1:
+ br label %exit
+
+sw.bb2:
+ br label %exit
+
+sw.default:
+ br label %exit
+
+exit:
+ %r = phi i32 [ %v, %sw.default ], [ 10, %sw.bb1 ], [ 123, %sw.bb2 ]
+ ret i32 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-5.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-5.ll
new file mode 100644
index 0000000000000..d21908b481e2e
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-5.ll
@@ -0,0 +1,92 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; When offset is negative or larger than the same we currently don't transform.
+
+ at table2 = internal unnamed_addr constant [4 x i8] c"\00\01\02\03"
+
+;.
+; CHECK: @table2 = internal unnamed_addr constant [4 x i8] c"\00\01\02\03"
+;.
+define i8 @pos_off(i64 %idx) {
+; CHECK-LABEL: define i8 @pos_off(
+; CHECK-SAME: i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[G:%.*]] = getelementptr inbounds i8, ptr @table2, i64 [[IDX]]
+; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds i8, ptr [[G]], i64 1
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[P]], align 1
+; CHECK-NEXT: switch i8 [[V]], label %[[D:.*]] [
+; CHECK-NEXT: i8 1, label %[[E:.*]]
+; CHECK-NEXT: i8 2, label %[[B:.*]]
+; CHECK-NEXT: i8 3, label %[[C:.*]]
+; CHECK-NEXT: ]
+; CHECK: [[B]]:
+; CHECK-NEXT: br label %[[E]]
+; CHECK: [[C]]:
+; CHECK-NEXT: br label %[[E]]
+; CHECK: [[D]]:
+; CHECK-NEXT: br label %[[E]]
+; CHECK: [[E]]:
+; CHECK-NEXT: [[R:%.*]] = phi i8 [ 0, %[[D]] ], [ 20, %[[B]] ], [ 30, %[[C]] ], [ 10, %[[ENTRY]] ]
+; CHECK-NEXT: ret i8 [[R]]
+;
+entry:
+ %g = getelementptr inbounds i8, ptr @table2, i64 %idx
+ %p = getelementptr inbounds i8, ptr %g, i64 1
+ %v = load i8, ptr %p
+ switch i8 %v, label %d [
+ i8 1, label %a
+ i8 2, label %b
+ i8 3, label %c
+ ]
+a: br label %e
+b: br label %e
+c: br label %e
+d: br label %e
+e:
+ %r = phi i8 [10, %a], [20, %b], [30, %c], [0, %d]
+ ret i8 %r
+}
+
+define i8 @neg_off(i64 %idx) {
+; CHECK-LABEL: define i8 @neg_off(
+; CHECK-SAME: i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[G:%.*]] = getelementptr inbounds i8, ptr @table2, i64 [[IDX]]
+; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds i8, ptr [[G]], i64 -1
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[P]], align 1
+; CHECK-NEXT: switch i8 [[V]], label %[[D:.*]] [
+; CHECK-NEXT: i8 1, label %[[E:.*]]
+; CHECK-NEXT: i8 2, label %[[B:.*]]
+; CHECK-NEXT: i8 3, label %[[C:.*]]
+; CHECK-NEXT: ]
+; CHECK: [[B]]:
+; CHECK-NEXT: br label %[[E]]
+; CHECK: [[C]]:
+; CHECK-NEXT: br label %[[E]]
+; CHECK: [[D]]:
+; CHECK-NEXT: br label %[[E]]
+; CHECK: [[E]]:
+; CHECK-NEXT: [[R:%.*]] = phi i8 [ 0, %[[D]] ], [ 20, %[[B]] ], [ 30, %[[C]] ], [ 10, %[[ENTRY]] ]
+; CHECK-NEXT: ret i8 [[R]]
+;
+entry:
+ %g = getelementptr inbounds i8, ptr @table2, i64 %idx
+ %p = getelementptr inbounds i8, ptr %g, i64 -1
+ %v = load i8, ptr %p
+ switch i8 %v, label %d [
+ i8 1, label %a
+ i8 2, label %b
+ i8 3, label %c
+ ]
+a: br label %e
+b: br label %e
+c: br label %e
+d: br label %e
+e:
+ %r = phi i8 [10, %a], [20, %b], [30, %c], [0, %d]
+ ret i8 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-6.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-6.ll
new file mode 100644
index 0000000000000..7d475d6574549
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-6.ll
@@ -0,0 +1,41 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; Byte 1 of each element is 1, 2, 3, 4; byte 0 is 0x10, 0x20, 0x30, 0x40.
+ at table_i32 = internal unnamed_addr constant [4 x i32] [i32 u0x0110, i32 u0x0220, i32 u0x0330, i32 u0x0440]
+
+;.
+; CHECK: @table_i32 = internal unnamed_addr constant [4 x i32] [i32 272, i32 544, i32 816, i32 1088]
+; CHECK: @switch.table.off_within_stride = private unnamed_addr constant [4 x i8] c"\0A\14\1E\00", align 1
+;.
+define i8 @off_within_stride(i64 %idx) {
+; CHECK-LABEL: define i8 @off_within_stride(
+; CHECK-SAME: i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[G:%.*]] = getelementptr inbounds i32, ptr @table_i32, i64 [[IDX]]
+; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds i8, ptr [[G]], i64 1
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[P]], align 1
+; CHECK-NEXT: [[SWITCH_GEP:%.*]] = getelementptr inbounds [4 x i8], ptr @switch.table.off_within_stride, i64 0, i64 [[IDX]]
+; CHECK-NEXT: [[SWITCH_LOAD:%.*]] = load i8, ptr [[SWITCH_GEP]], align 1
+; CHECK-NEXT: ret i8 [[SWITCH_LOAD]]
+;
+entry:
+ %g = getelementptr inbounds i32, ptr @table_i32, i64 %idx
+ %p = getelementptr inbounds i8, ptr %g, i64 1
+ %v = load i8, ptr %p
+ switch i8 %v, label %d [
+ i8 1, label %a
+ i8 2, label %b
+ i8 3, label %c
+ ]
+a: br label %e
+b: br label %e
+c: br label %e
+d: br label %e
+e:
+ %r = phi i8 [10, %a], [20, %b], [30, %c], [0, %d]
+ ret i8 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-7.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-7.ll
new file mode 100644
index 0000000000000..04cd45364d7f5
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-7.ll
@@ -0,0 +1,62 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; A regression test: load is out of bounds and the program is UB. This used to
+; cause a crash.
+
+ at table_test_load_size_bug = constant [1 x i32] [i32 0]
+
+;.
+; CHECK: @table_test_load_size_bug = constant [1 x i32] zeroinitializer
+;.
+define i32 @test_load_size_bug(i64 %idx) {
+; CHECK-LABEL: define i32 @test_load_size_bug(
+; CHECK-SAME: i64 [[IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i32, ptr @table_test_load_size_bug, i64 [[IDX]]
+; CHECK-NEXT: [[P:%.*]] = getelementptr inbounds i8, ptr [[GEP]], i64 2
+; CHECK-NEXT: [[V:%.*]] = load i32, ptr [[P]], align 4
+; CHECK-NEXT: switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT: i32 0, label %[[EXIT:.*]]
+; CHECK-NEXT: i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT: i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT: ]
+; CHECK: [[CASE1]]:
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[CASE2]]:
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[DEFAULT]]:
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[R:%.*]] = phi i32 [ 30, %[[DEFAULT]] ], [ 20, %[[CASE1]] ], [ 40, %[[CASE2]] ], [ 10, %[[ENTRY]] ]
+; CHECK-NEXT: ret i32 [[R]]
+;
+entry:
+ %gep = getelementptr inbounds i32, ptr @table_test_load_size_bug, i64 %idx
+ %p = getelementptr inbounds i8, ptr %gep, i64 2
+ %v = load i32, ptr %p
+ switch i32 %v, label %default [
+ i32 0, label %case0
+ i32 1, label %case1
+ i32 2, label %case2
+ ]
+
+case0:
+ br label %exit
+
+case1:
+ br label %exit
+
+case2:
+ br label %exit
+
+default:
+ br label %exit
+
+exit:
+ %r = phi i32 [ 10, %case0 ], [ 20, %case1 ], [ 40, %case2 ], [ 30, %default ]
+ ret i32 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-8.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-8.ll
new file mode 100644
index 0000000000000..a39ae7a0bc82d
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-8.ll
@@ -0,0 +1,66 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; `load` instruction have another use other than the `switch`, so under
+; `optsize` we don't create a new global as the old one would stay too,
+; increasing binary size.
+
+ at table_optsize_extra_use = internal constant [8 x i8] c"\00\01\02\03\00\01\02\03"
+
+;.
+; CHECK: @table_optsize_extra_use = internal constant [8 x i8] c"\00\01\02\03\00\01\02\03"
+;.
+define i8 @optsize_load_extra_use(i64 %idx, ptr %out) optsize {
+; CHECK-LABEL: define i8 @optsize_load_extra_use(
+; CHECK-SAME: i64 [[IDX:%.*]], ptr [[OUT:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i8, ptr @table_optsize_extra_use, i64 [[IDX]]
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT: store i8 [[V]], ptr [[OUT]], align 1
+; CHECK-NEXT: switch i8 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT: i8 0, label %[[EXIT:.*]]
+; CHECK-NEXT: i8 1, label %[[CASE1:.*]]
+; CHECK-NEXT: i8 2, label %[[CASE2:.*]]
+; CHECK-NEXT: ]
+; CHECK: [[CASE1]]:
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[CASE2]]:
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[DEFAULT]]:
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[R:%.*]] = phi i8 [ 7, %[[DEFAULT]] ], [ 30, %[[CASE1]] ], [ 20, %[[CASE2]] ], [ 10, %[[ENTRY]] ]
+; CHECK-NEXT: ret i8 [[R]]
+;
+entry:
+ %gep = getelementptr inbounds i8, ptr @table_optsize_extra_use, i64 %idx
+ %v = load i8, ptr %gep
+ store i8 %v, ptr %out
+ switch i8 %v, label %default [
+ i8 0, label %case0
+ i8 1, label %case1
+ i8 2, label %case2
+ ]
+
+case0:
+ br label %exit
+
+case1:
+ br label %exit
+
+case2:
+ br label %exit
+
+default:
+ br label %exit
+
+exit:
+ %r = phi i8 [ 10, %case0 ], [ 30, %case1 ], [ 20, %case2 ], [ 7, %default ]
+ ret i8 %r
+}
+;.
+; CHECK: attributes #[[ATTR0]] = { optsize }
+;.
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-9.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-9.ll
new file mode 100644
index 0000000000000..9c1a54d08b99c
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-9.ll
@@ -0,0 +1,59 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5
+; RUN: opt -S -passes='simplifycfg<switch-to-lookup>' < %s | FileCheck %s
+
+; Test converting `switch` instructions that index a global and update the read
+; value into single loads from an updated table.
+
+; Test extra PHI arguments at the destination block.
+
+; elems = [ 0, 5, 6, 1, 7, 8, 2, 9, 10, 3, 11, 12, 4, 4, 4, 4, 13, 14, 15, 16, 17, 18 ]
+; indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 ]
+ at table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+
+
+;.
+; CHECK: @table1 = internal unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\0B\0C\04\04\04\04\0D\0E\0F\10\11\12", align 1
+; CHECK: @switch.table.test_exra_phi_argument = private unnamed_addr constant [22 x i8] c"\00\05\06\01\07\08\02\09\0A\03\03\03\03\03\03\03\0D\0E\0F\10\09\0A", align 1
+;.
+define i8 @test_exra_phi_argument(i1 %c, i64 %i, i8 %x, ptr %p, ptr %q) {
+; CHECK-LABEL: define i8 @test_exra_phi_argument(
+; CHECK-SAME: i1 [[C:%.*]], i64 [[I:%.*]], i8 [[X:%.*]], ptr [[P:%.*]], ptr [[Q:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br i1 [[C]], label %[[LOOKUP:.*]], label %[[EXIT:.*]]
+; CHECK: [[LOOKUP]]:
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds nuw i8, ptr @table1, i64 [[I]]
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT: [[SWITCH_GEP:%.*]] = getelementptr inbounds [22 x i8], ptr @switch.table.test_exra_phi_argument, i64 0, i64 [[I]]
+; CHECK-NEXT: [[SWITCH_LOAD:%.*]] = load i8, ptr [[SWITCH_GEP]], align 1
+; CHECK-NEXT: br label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[R:%.*]] = phi i8 [ [[X]], %[[ENTRY]] ], [ [[SWITCH_LOAD]], %[[LOOKUP]] ]
+; CHECK-NEXT: ret i8 [[R]]
+;
+entry:
+ br i1 %c, label %lookup, label %exit
+
+lookup:
+ %gep = getelementptr inbounds nuw i8, ptr @table1, i64 %i
+ %v = load i8, ptr %gep
+ switch i8 %v, label %default [
+ i8 4, label %exit
+ i8 12, label %exit
+ i8 11, label %exit
+ i8 18, label %bb1
+ i8 17, label %bb2
+ ]
+
+bb1:
+ br label %exit
+
+bb2:
+ br label %exit
+
+default:
+ br label %exit
+
+exit:
+ %r = phi i8 [ %x, %entry ], [ 3, %lookup ], [ 3, %lookup ], [ 3, %lookup ], [ 10, %bb1 ], [ 9, %bb2 ], [ %v, %default ]
+ ret i8 %r
+}
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-to-table-hang.ll b/llvm/test/Transforms/SimplifyCFG/switch-to-table-hang.ll
new file mode 100644
index 0000000000000..2a34db94bc327
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-to-table-hang.ll
@@ -0,0 +1,43 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -S -passes=simplifycfg < %s | FileCheck %s
+
+; Reduced test case from an actual program: `ConstantFoldTerminator` creates
+; the following cyclic GEP in an unreachable block:
+;
+; %p.next = getelementptr i8, ptr %p.next, i64 1
+;
+; Which causes `decomposeLinearExpression` to loop.
+
+declare void @g()
+
+define void @f(ptr %base) {
+; CHECK-LABEL: @f(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %m
+
+m:
+ %c = phi i1 [ true, %entry ]
+ br i1 %c, label %exit, label %loop
+
+loop:
+ %p = phi ptr [ %base, %m ], [ %p.next, %latch ]
+ %v = load i8, ptr %p
+ switch i8 %v, label %side [
+ i8 0, label %latch
+ i8 1, label %latch
+ ]
+
+side:
+ call void @g()
+ br label %latch
+
+latch:
+ %p.next = getelementptr i8, ptr %p, i64 1
+ br label %loop
+
+exit:
+ ret void
+}
More information about the llvm-commits
mailing list