[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