[llvm] 1060a6b - [SimplifyCFG][DirectX] Honor target minimum lookup table element width (#203103)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jun 12 12:05:33 PDT 2026
Author: Farzon Lotfi
Date: 2026-06-12T15:05:28-04:00
New Revision: 1060a6be0a471106fd65a3f16937ee1c588dabed
URL: https://github.com/llvm/llvm-project/commit/1060a6be0a471106fd65a3f16937ee1c588dabed
DIFF: https://github.com/llvm/llvm-project/commit/1060a6be0a471106fd65a3f16937ee1c588dabed.diff
LOG: [SimplifyCFG][DirectX] Honor target minimum lookup table element width (#203103)
fixes #202481
This change adds a `TTI::getMinimumLookupTableEntryBitWidth()` (default
`8`) and fold it
into SimplifyCFG's `NeededBitWidth` computation so targets can prevent
unsupported
narrow lookup tables. DirectX returns 32 (or 16 with native 16-bit
types) so tables
never narrow to the unsupported i8 type.
> Assisted by Claude Opus 4.8
Added:
llvm/test/CodeGen/DirectX/switch-to-lookup-table-i16-narrowing.ll
llvm/test/CodeGen/DirectX/switch-to-lookup-table-no-i8-narrowing.ll
Modified:
llvm/include/llvm/Analysis/TargetTransformInfo.h
llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
llvm/lib/Analysis/TargetTransformInfo.cpp
llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
llvm/lib/Target/DirectX/DirectXTargetTransformInfo.h
llvm/lib/Transforms/Utils/SimplifyCFG.cpp
Removed:
################################################################################
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfo.h b/llvm/include/llvm/Analysis/TargetTransformInfo.h
index 7d58473b81265..aaf5ed6de93b6 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 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 89fd4a1e7628e..d594475831b20 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 unsigned getMinimumLookupTableEntryBitWidth() const { return 8; }
+
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..8e0c30f26e975 100644
--- a/llvm/lib/Analysis/TargetTransformInfo.cpp
+++ b/llvm/lib/Analysis/TargetTransformInfo.cpp
@@ -617,6 +617,10 @@ bool TargetTransformInfo::shouldBuildLookupTablesForConstant(
return TTIImpl->shouldBuildLookupTablesForConstant(C);
}
+unsigned TargetTransformInfo::getMinimumLookupTableEntryBitWidth() const {
+ return TTIImpl->getMinimumLookupTableEntryBitWidth();
+}
+
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..af1d7bc452126 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
+++ b/llvm/lib/Target/DirectX/DirectXTargetTransformInfo.cpp
@@ -44,3 +44,10 @@ bool DirectXTTIImpl::isTargetIntrinsicWithOverloadTypeAtArg(Intrinsic::ID ID,
return OpdIdx == -1;
}
}
+
+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 fdbbc18003419..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,19 +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;
+ 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 222d43579abc3..a3505dccea41c 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -6783,7 +6783,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.
@@ -6853,7 +6854,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,7 +6982,8 @@ 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);
for (Constant *&Value : TableContents)
@@ -7442,7 +7445,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-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
new file mode 100644
index 0000000000000..be5831b46b3e3
--- /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 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]
+
+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
+}
More information about the llvm-commits
mailing list