[llvm] [DirectX][SimplifyCFG] Don't narrow to i8 arrays for switch tables (PR #203103)
Farzon Lotfi via llvm-commits
llvm-commits at lists.llvm.org
Thu Jun 11 10:59:06 PDT 2026
https://github.com/farzonl updated https://github.com/llvm/llvm-project/pull/203103
>From 53566b51d20fd7a8f47af4c43641897d3188ad14 Mon Sep 17 00:00:00 2001
From: Farzon Lotfi <farzon at farzon.org>
Date: Mon, 8 Jun 2026 22:11:48 -0400
Subject: [PATCH 1/2] [DirectX][SimplifyCFG] Don't narrow to i8 arrays for
switch tables fixes #202481
This change adds a TTI to disable the narrowing for targets that don't
support it like DirectX.
The `isTypeLegal` TTI was considered, instead of adding a new one, but
that was causing significant test failures that needed to be triaged
through.
---
.../llvm/Analysis/TargetTransformInfo.h | 4 ++
.../llvm/Analysis/TargetTransformInfoImpl.h | 2 +
llvm/lib/Analysis/TargetTransformInfo.cpp | 4 ++
.../DirectX/DirectXTargetTransformInfo.cpp | 6 +++
.../DirectX/DirectXTargetTransformInfo.h | 1 +
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 15 ++++---
.../switch-to-lookup-table-no-i8-narrowing.ll | 42 +++++++++++++++++++
7 files changed, 69 insertions(+), 5 deletions(-)
create mode 100644 llvm/test/CodeGen/DirectX/switch-to-lookup-table-no-i8-narrowing.ll
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfo.h b/llvm/include/llvm/Analysis/TargetTransformInfo.h
index 7d58473b81265..7ef643a288a3e 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfo.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfo.h
@@ -1025,6 +1025,10 @@ class TargetTransformInfo {
/// containing this constant value for the target.
LLVM_ABI bool shouldBuildLookupTablesForConstant(Constant *C) const;
+ /// Return true if \p Ty may be used as the element type of a switch lookup
+ /// table on this target.
+ LLVM_ABI bool isLegalLookupTableElementType(Type *Ty) const;
+
/// Return true if lookup tables should be turned into relative lookup tables.
LLVM_ABI bool shouldBuildRelLookupTables() const;
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
index 89fd4a1e7628e..b94df409dcb11 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
@@ -475,6 +475,8 @@ class LLVM_ABI TargetTransformInfoImplBase {
return true;
}
+ virtual bool isLegalLookupTableElementType(Type *Ty) const { return true; }
+
virtual bool shouldBuildRelLookupTables() const { return false; }
virtual bool useColdCCForColdCall(Function &F) const { return false; }
diff --git a/llvm/lib/Analysis/TargetTransformInfo.cpp b/llvm/lib/Analysis/TargetTransformInfo.cpp
index 856950ccae595..d8eaff3ca1c73 100644
--- a/llvm/lib/Analysis/TargetTransformInfo.cpp
+++ b/llvm/lib/Analysis/TargetTransformInfo.cpp
@@ -617,6 +617,10 @@ bool TargetTransformInfo::shouldBuildLookupTablesForConstant(
return TTIImpl->shouldBuildLookupTablesForConstant(C);
}
+bool TargetTransformInfo::isLegalLookupTableElementType(Type *Ty) const {
+ return TTIImpl->isLegalLookupTableElementType(Ty);
+}
+
bool TargetTransformInfo::shouldBuildRelLookupTables() const {
return TTIImpl->shouldBuildRelLookupTables();
}
diff --git a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
index 9952038aaf7bd..7453bd84dd1fc 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
+++ b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
@@ -44,3 +44,9 @@ bool DirectXTTIImpl::isTargetIntrinsicWithOverloadTypeAtArg(Intrinsic::ID ID,
return OpdIdx == -1;
}
}
+
+bool DirectXTTIImpl::isLegalLookupTableElementType(Type *Ty) const {
+ // DXIL does not support i8, so switch lookup tables must not be narrowed to
+ // an i8 element type.
+ return !Ty->isIntegerTy(8);
+}
diff --git a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h
index fdbbc18003419..b860324b11295 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h
+++ b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h
@@ -38,6 +38,7 @@ class DirectXTTIImpl final : public BasicTTIImplBase<DirectXTTIImpl> {
unsigned ScalarOpdIdx) const override;
bool isTargetIntrinsicWithOverloadTypeAtArg(Intrinsic::ID ID,
int OpdIdx) const override;
+ bool isLegalLookupTableElementType(Type *Ty) const override;
InstructionCost getPartialReductionCost(
unsigned Opcode, Type *InputTypeA, Type *InputTypeB, Type *AccumType,
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 4b21b20beb895..b04b0cdd39d5b 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -6780,7 +6780,8 @@ class SwitchReplacement {
SwitchReplacement(
Module &M, uint64_t TableSize, ConstantInt *Offset,
const SmallVectorImpl<std::pair<ConstantInt *, Constant *>> &Values,
- Constant *DefaultValue, const DataLayout &DL, const StringRef &FuncName);
+ Constant *DefaultValue, const DataLayout &DL,
+ const TargetTransformInfo &TTI, const StringRef &FuncName);
/// Build instructions with Builder to retrieve values using Index
/// and replace the switch.
@@ -6850,7 +6851,8 @@ class SwitchReplacement {
SwitchReplacement::SwitchReplacement(
Module &M, uint64_t TableSize, ConstantInt *Offset,
const SmallVectorImpl<std::pair<ConstantInt *, Constant *>> &Values,
- Constant *DefaultValue, const DataLayout &DL, const StringRef &FuncName)
+ Constant *DefaultValue, const DataLayout &DL,
+ const TargetTransformInfo &TTI, const StringRef &FuncName)
: DefaultValue(DefaultValue) {
assert(Values.size() && "Can't build lookup table without values!");
assert(TableSize >= Values.size() && "Can't fit values in table!");
@@ -6980,8 +6982,11 @@ SwitchReplacement::SwitchReplacement(
std::max(8u, unsigned(PowerOf2Ceil(Range.getActiveBits())));
if (NeededBitWidth < IT->getBitWidth()) {
IntegerType *DstTy = IntegerType::get(IT->getContext(), NeededBitWidth);
- for (Constant *&Value : TableContents)
- Value = ConstantFoldCastInstruction(Instruction::Trunc, Value, DstTy);
+ // Only narrow the table element type if the narrower type is legal on
+ // this target.
+ if (TTI.isLegalLookupTableElementType(DstTy))
+ for (Constant *&Value : TableContents)
+ Value = ConstantFoldCastInstruction(Instruction::Trunc, Value, DstTy);
}
}
@@ -7439,7 +7444,7 @@ static bool simplifySwitchLookup(SwitchInst *SI, IRBuilder<> &Builder,
AllHolesArePoison ? PoisonValue::get(ResultType) : DefaultResults[PHI];
StringRef FuncName = Fn->getName();
SwitchReplacement Replacement(*Fn->getParent(), TableSize, TableIndexOffset,
- ResultList, DefaultVal, DL, FuncName);
+ ResultList, DefaultVal, DL, TTI, FuncName);
PhiToReplacementMap.insert({PHI, Replacement});
}
diff --git a/llvm/test/CodeGen/DirectX/switch-to-lookup-table-no-i8-narrowing.ll b/llvm/test/CodeGen/DirectX/switch-to-lookup-table-no-i8-narrowing.ll
new file mode 100644
index 0000000000000..86259cdcb0799
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/switch-to-lookup-table-no-i8-narrowing.ll
@@ -0,0 +1,42 @@
+; RUN: opt -S -passes=simplifycfg -switch-to-lookup=true -keep-loops=false -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
+
+; The switch result values all fit in i8, so on a typical target SimplifyCFG
+; narrows the lookup table element type from i32 down to i8. DXIL/DXC does not
+; support i8, and the DirectX target transform reports i8 as an illegal type, so
+; SimplifyCFG must keep the original i32 element type instead of downcasting the
+; table to i8.
+
+; CHECK: @switch.table.test = private unnamed_addr constant [4 x i32] [i32 3, i32 1, i32 2, i32 5]
+; CHECK-NOT: @switch.table.test = private unnamed_addr constant [4 x i8]
+
+define i32 @test(i32 %i) {
+; CHECK-LABEL: define i32 @test(
+; CHECK: getelementptr inbounds [4 x i32], ptr @switch.table.test
+; CHECK: load i32, ptr
+entry:
+ switch i32 %i, label %def [
+ i32 0, label %bb0
+ i32 1, label %bb1
+ i32 2, label %bb2
+ i32 3, label %bb3
+ ]
+
+bb0:
+ br label %ret
+
+bb1:
+ br label %ret
+
+bb2:
+ br label %ret
+
+bb3:
+ br label %ret
+
+def:
+ br label %ret
+
+ret:
+ %r = phi i32 [ 3, %bb0 ], [ 1, %bb1 ], [ 2, %bb2 ], [ 5, %bb3 ], [ 0, %def ]
+ ret i32 %r
+}
>From 4f6ac6348f8b1768e51dcc3af80ea2bcc000dd94 Mon Sep 17 00:00:00 2001
From: Farzon Lotfi <farzon at farzon.org>
Date: Thu, 11 Jun 2026 13:56:08 -0400
Subject: [PATCH 2/2] update the patch to instead define the minimum lookup
table bitWidth as Eli Friedman suggested. For DirectX that means if the 6.2
feature NativeLowPrecisionMode is enabled we can support narrowing to 16 bit
integer arrays. By default though we will only narrow to 32 bit.
---
.../llvm/Analysis/TargetTransformInfo.h | 6 +--
.../llvm/Analysis/TargetTransformInfoImpl.h | 2 +-
llvm/lib/Analysis/TargetTransformInfo.cpp | 4 +-
.../DirectX/DirectXTargetTransformInfo.cpp | 9 ++--
.../DirectX/DirectXTargetTransformInfo.h | 18 +++++++-
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 10 ++---
.../switch-to-lookup-table-i16-narrowing.ll | 45 +++++++++++++++++++
.../switch-to-lookup-table-no-i8-narrowing.ll | 6 +--
8 files changed, 79 insertions(+), 21 deletions(-)
create mode 100644 llvm/test/CodeGen/DirectX/switch-to-lookup-table-i16-narrowing.ll
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfo.h b/llvm/include/llvm/Analysis/TargetTransformInfo.h
index 7ef643a288a3e..aaf5ed6de93b6 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfo.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfo.h
@@ -1025,9 +1025,9 @@ class TargetTransformInfo {
/// containing this constant value for the target.
LLVM_ABI bool shouldBuildLookupTablesForConstant(Constant *C) const;
- /// Return true if \p Ty may be used as the element type of a switch lookup
- /// table on this target.
- LLVM_ABI bool isLegalLookupTableElementType(Type *Ty) const;
+ /// Return the minimum bit width to use for integer switch lookup table
+ /// elements on this target.
+ LLVM_ABI unsigned getMinimumLookupTableEntryBitWidth() const;
/// Return true if lookup tables should be turned into relative lookup tables.
LLVM_ABI bool shouldBuildRelLookupTables() const;
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
index b94df409dcb11..d594475831b20 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
@@ -475,7 +475,7 @@ class LLVM_ABI TargetTransformInfoImplBase {
return true;
}
- virtual bool isLegalLookupTableElementType(Type *Ty) const { return true; }
+ virtual unsigned getMinimumLookupTableEntryBitWidth() const { return 8; }
virtual bool shouldBuildRelLookupTables() const { return false; }
diff --git a/llvm/lib/Analysis/TargetTransformInfo.cpp b/llvm/lib/Analysis/TargetTransformInfo.cpp
index d8eaff3ca1c73..8e0c30f26e975 100644
--- a/llvm/lib/Analysis/TargetTransformInfo.cpp
+++ b/llvm/lib/Analysis/TargetTransformInfo.cpp
@@ -617,8 +617,8 @@ bool TargetTransformInfo::shouldBuildLookupTablesForConstant(
return TTIImpl->shouldBuildLookupTablesForConstant(C);
}
-bool TargetTransformInfo::isLegalLookupTableElementType(Type *Ty) const {
- return TTIImpl->isLegalLookupTableElementType(Ty);
+unsigned TargetTransformInfo::getMinimumLookupTableEntryBitWidth() const {
+ return TTIImpl->getMinimumLookupTableEntryBitWidth();
}
bool TargetTransformInfo::shouldBuildRelLookupTables() const {
diff --git a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
index 7453bd84dd1fc..af1d7bc452126 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
+++ b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
@@ -45,8 +45,9 @@ bool DirectXTTIImpl::isTargetIntrinsicWithOverloadTypeAtArg(Intrinsic::ID ID,
}
}
-bool DirectXTTIImpl::isLegalLookupTableElementType(Type *Ty) const {
- // DXIL does not support i8, so switch lookup tables must not be narrowed to
- // an i8 element type.
- return !Ty->isIntegerTy(8);
+unsigned DirectXTTIImpl::getMinimumLookupTableEntryBitWidth() const {
+ // DXIL does not support i8, so switch lookup tables must not be narrowed
+ // below the minimum legal integer width. When native 16-bit types are
+ // enabled (-enable-16bit-types) the minimum is i16, otherwise it is i32.
+ return HasNativeLowPrecision ? 16 : 32;
}
diff --git a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h
index b860324b11295..92f96aeca2011 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h
+++ b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h
@@ -14,7 +14,10 @@
#include "DirectXSubtarget.h"
#include "DirectXTargetMachine.h"
#include "llvm/CodeGen/BasicTTIImpl.h"
+#include "llvm/IR/Constants.h"
#include "llvm/IR/Function.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/IR/Module.h"
namespace llvm {
class DirectXTTIImpl final : public BasicTTIImplBase<DirectXTTIImpl> {
@@ -25,20 +28,31 @@ class DirectXTTIImpl final : public BasicTTIImplBase<DirectXTTIImpl> {
const DirectXSubtarget *ST;
const DirectXTargetLowering *TLI;
+ // True when native 16-bit types are enabled (i.e. -enable-16bit-types was
+ // passed), indicated by the dx.nativelowprec module flag.
+ const bool HasNativeLowPrecision;
const DirectXSubtarget *getST() const { return ST; }
const DirectXTargetLowering *getTLI() const { return TLI; }
+ static bool readNativeLowPrecisionFlag(const Function &F) {
+ if (auto *Flag = mdconst::extract_or_null<ConstantInt>(
+ F.getParent()->getModuleFlag("dx.nativelowprec")))
+ return Flag->getValue().getBoolValue();
+ return false;
+ }
+
public:
explicit DirectXTTIImpl(const DirectXTargetMachine *TM, const Function &F)
: BaseT(TM, F.getDataLayout()), ST(TM->getSubtargetImpl(F)),
- TLI(ST->getTargetLowering()) {}
+ TLI(ST->getTargetLowering()),
+ HasNativeLowPrecision(readNativeLowPrecisionFlag(F)) {}
unsigned getMinVectorRegisterBitWidth() const override { return 32; }
bool isTargetIntrinsicWithScalarOpAtArg(Intrinsic::ID ID,
unsigned ScalarOpdIdx) const override;
bool isTargetIntrinsicWithOverloadTypeAtArg(Intrinsic::ID ID,
int OpdIdx) const override;
- bool isLegalLookupTableElementType(Type *Ty) const override;
+ unsigned getMinimumLookupTableEntryBitWidth() const override;
InstructionCost getPartialReductionCost(
unsigned Opcode, Type *InputTypeA, Type *InputTypeB, Type *AccumType,
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index b04b0cdd39d5b..ed580da35dff0 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -6979,14 +6979,12 @@ SwitchReplacement::SwitchReplacement(
Range = Range.unionWith(cast<ConstantInt>(Value)->getValue());
// TODO: handle sign extension as well?
unsigned NeededBitWidth =
- std::max(8u, unsigned(PowerOf2Ceil(Range.getActiveBits())));
+ std::max(TTI.getMinimumLookupTableEntryBitWidth(),
+ unsigned(PowerOf2Ceil(Range.getActiveBits())));
if (NeededBitWidth < IT->getBitWidth()) {
IntegerType *DstTy = IntegerType::get(IT->getContext(), NeededBitWidth);
- // Only narrow the table element type if the narrower type is legal on
- // this target.
- if (TTI.isLegalLookupTableElementType(DstTy))
- for (Constant *&Value : TableContents)
- Value = ConstantFoldCastInstruction(Instruction::Trunc, Value, DstTy);
+ for (Constant *&Value : TableContents)
+ Value = ConstantFoldCastInstruction(Instruction::Trunc, Value, DstTy);
}
}
diff --git a/llvm/test/CodeGen/DirectX/switch-to-lookup-table-i16-narrowing.ll b/llvm/test/CodeGen/DirectX/switch-to-lookup-table-i16-narrowing.ll
new file mode 100644
index 0000000000000..e77c87f52fb0e
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/switch-to-lookup-table-i16-narrowing.ll
@@ -0,0 +1,45 @@
+; RUN: opt -S -passes=simplifycfg -switch-to-lookup=true -keep-loops=false -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
+
+; The switch result values all fit in i8. DXIL does not support i8, but when
+; native 16-bit types are enabled (the dx.nativelowprec module flag, set by
+; -enable-16bit-types), the DirectX target transform reports a minimum lookup
+; table element width of i16. So SimplifyCFG narrows the table from i32 to i16
+; (but never to i8).
+
+; CHECK: @switch.table.test = private unnamed_addr constant [4 x i16] [i16 3, i16 1, i16 2, i16 5]
+; CHECK-NOT: @switch.table.test = private unnamed_addr constant [4 x i8]
+
+define i32 @test(i32 %i) {
+; CHECK-LABEL: define i32 @test(
+; CHECK: getelementptr inbounds [4 x i16], ptr @switch.table.test
+; CHECK: load i16, ptr
+entry:
+ switch i32 %i, label %def [
+ i32 0, label %bb0
+ i32 1, label %bb1
+ i32 2, label %bb2
+ i32 3, label %bb3
+ ]
+
+bb0:
+ br label %ret
+
+bb1:
+ br label %ret
+
+bb2:
+ br label %ret
+
+bb3:
+ br label %ret
+
+def:
+ br label %ret
+
+ret:
+ %r = phi i32 [ 3, %bb0 ], [ 1, %bb1 ], [ 2, %bb2 ], [ 5, %bb3 ], [ 0, %def ]
+ ret i32 %r
+}
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"dx.nativelowprec", i32 1}
diff --git a/llvm/test/CodeGen/DirectX/switch-to-lookup-table-no-i8-narrowing.ll b/llvm/test/CodeGen/DirectX/switch-to-lookup-table-no-i8-narrowing.ll
index 86259cdcb0799..be5831b46b3e3 100644
--- a/llvm/test/CodeGen/DirectX/switch-to-lookup-table-no-i8-narrowing.ll
+++ b/llvm/test/CodeGen/DirectX/switch-to-lookup-table-no-i8-narrowing.ll
@@ -2,9 +2,9 @@
; The switch result values all fit in i8, so on a typical target SimplifyCFG
; narrows the lookup table element type from i32 down to i8. DXIL/DXC does not
-; support i8, and the DirectX target transform reports i8 as an illegal type, so
-; SimplifyCFG must keep the original i32 element type instead of downcasting the
-; table to i8.
+; support i8, and the DirectX target transform reports a minimum lookup table
+; element width of i32 (without native 16-bit types), so SimplifyCFG must keep
+; the original i32 element type instead of downcasting the table to i8.
; CHECK: @switch.table.test = private unnamed_addr constant [4 x i32] [i32 3, i32 1, i32 2, i32 5]
; CHECK-NOT: @switch.table.test = private unnamed_addr constant [4 x i8]
More information about the llvm-commits
mailing list