[llvm] [SPIR-V] Fix bit count intrinsics on narrow integer types (PR #223358)

Arseniy Obolenskiy via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 15 11:49:43 PDT 2026


https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/223358

>From 6f54519b295b9f276df063f079dbe971bd896c6d Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Mon, 14 Sep 2026 13:05:11 +0200
Subject: [PATCH 1/2] [SPIR-V] Fix bit count intrinsics on narrow integer types

Widening i24 to i32 relabels the type without clearing the high bits, so ctlz, cttz and ctpop counted them too
---
 llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp   | 159 +++++++++++++++---
 .../CodeGen/SPIRV/llvm-intrinsics/ctlz.ll     |  64 ++++++-
 .../CodeGen/SPIRV/llvm-intrinsics/ctpop.ll    |  44 +++++
 .../CodeGen/SPIRV/llvm-intrinsics/cttz.ll     |  46 ++++-
 4 files changed, 282 insertions(+), 31 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index e383e70ccf05b..bca58beba0298 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -18,6 +18,7 @@
 #include "llvm/ADT/PostOrderIterator.h"
 #include "llvm/CodeGen/GlobalISel/CSEInfo.h"
 #include "llvm/CodeGen/GlobalISel/GISelValueTracking.h"
+#include "llvm/CodeGen/GlobalISel/MIPatternMatch.h"
 #include "llvm/CodeGen/MachineFunctionAnalysisManager.h"
 #include "llvm/CodeGen/MachinePassManager.h"
 #include "llvm/IR/Analysis.h"
@@ -30,6 +31,7 @@
 #define DEBUG_TYPE "spirv-prelegalizer"
 
 using namespace llvm;
+using namespace llvm::MIPatternMatch;
 
 namespace {
 class SPIRVPreLegalizerLegacy : public MachineFunctionPass {
@@ -430,6 +432,18 @@ static unsigned widenBitWidthToNextPow2(unsigned BitWidth) {
   return std::min(std::max<unsigned>(PowerOf2Ceil(BitWidth), 8u), 128u);
 }
 
+static std::optional<unsigned>
+getNarrowScalarWidth(Register Reg, const MachineRegisterInfo &MRI) {
+  LLT Ty = MRI.getType(Reg);
+  if (!Ty.isScalar())
+    return std::nullopt;
+  unsigned W = Ty.getScalarSizeInBits();
+  // <= and not == because widenBitWidthToNextPow2 caps at 128.
+  if (widenBitWidthToNextPow2(W) <= W)
+    return std::nullopt;
+  return W;
+}
+
 static void widenScalarType(Register Reg, MachineRegisterInfo &MRI) {
   LLT RegType = MRI.getType(Reg);
   if (!RegType.isScalar())
@@ -525,33 +539,52 @@ static bool isSignSensitiveOp(const MachineInstr &MI) {
   }
 }
 
-struct SignSensitiveWideningInfo {
-  // Width before widening of each value-operand vreg (one entry per vreg).
+struct NarrowWideningInfo {
+  // Width before widening of each sign-sensitive value-operand vreg (one entry
+  // per vreg).
   DenseMap<Register, unsigned> OrigWidth;
-  // Ops whose value operand(s) need replacing, ordered for reproducible vreg
-  // numbering.
-  SmallVector<MachineInstr *> Worklist;
+  // Sign-sensitive ops whose value operand(s) need replacing, ordered for
+  // reproducible vreg numbering.
+  SmallVector<MachineInstr *> SignSensitiveWorklist;
+  // Keyed by instruction, not vreg: G_TRUNC handling can replace the source.
+  SmallVector<std::pair<MachineInstr *, unsigned>> BitCountWorklist;
 };
 
-// Collect sign-sensitive ops with narrow scalar value operands and their
+// G_CTTZ_ZERO_POISON is absent because its low bits are known non-zero, G_CTLS
+// because the backend does not select it.
+static bool isWidthSensitiveBitCountOp(unsigned Opcode) {
+  switch (Opcode) {
+  case TargetOpcode::G_CTLZ:
+  case TargetOpcode::G_CTLZ_ZERO_POISON:
+  case TargetOpcode::G_CTTZ:
+  case TargetOpcode::G_CTPOP:
+    return true;
+  default:
+    return false;
+  }
+}
+
+// Collect ops whose semantics depend on the operand width along with their
 // pre-widening widths, before later passes retype those vregs to pow2 LLTs
 // and the original width is no longer recoverable.
-static SignSensitiveWideningInfo
-recordSignSensitiveOperandWidths(MachineFunction &MF,
-                                 MachineRegisterInfo &MRI) {
-  SignSensitiveWideningInfo Info;
+static NarrowWideningInfo recordNarrowOperandWidths(MachineFunction &MF,
+                                                    MachineRegisterInfo &MRI) {
+  NarrowWideningInfo Info;
   auto RecordIfNarrow = [&](Register Reg) {
-    LLT Ty = MRI.getType(Reg);
-    if (!Ty.isScalar())
+    std::optional<unsigned> W = getNarrowScalarWidth(Reg, MRI);
+    if (!W)
       return false;
-    unsigned W = Ty.getScalarSizeInBits();
-    if (widenBitWidthToNextPow2(W) == W)
-      return false;
-    Info.OrigWidth.try_emplace(Reg, W);
+    Info.OrigWidth.try_emplace(Reg, *W);
     return true;
   };
   for (MachineBasicBlock &MBB : MF) {
     for (MachineInstr &MI : MBB) {
+      if (isWidthSensitiveBitCountOp(MI.getOpcode())) {
+        if (std::optional<unsigned> W =
+                getNarrowScalarWidth(MI.getOperand(1).getReg(), MRI))
+          Info.BitCountWorklist.emplace_back(&MI, *W);
+        continue;
+      }
       if (!isSignSensitiveOp(MI))
         continue;
       // Value operands are the trailing two, past any def or predicate.
@@ -563,7 +596,7 @@ recordSignSensitiveOperandWidths(MachineFunction &MF,
       bool NeedsRewrite = RecordIfNarrow(LHS.getReg());
       NeedsRewrite = RecordIfNarrow(RHS.getReg()) || NeedsRewrite;
       if (NeedsRewrite)
-        Info.Worklist.push_back(&MI);
+        Info.SignSensitiveWorklist.push_back(&MI);
     }
   }
   return Info;
@@ -573,7 +606,7 @@ recordSignSensitiveOperandWidths(MachineFunction &MF,
 // operand whose original width was narrower than the widened pow2 width and
 // retype the operand's vreg LLT in place to the widened width.
 //
-// Info must have been populated by recordSignSensitiveOperandWidths before
+// Info must have been populated by recordNarrowOperandWidths before
 // other passes retyped the vregs; otherwise the narrow widths needed here
 // are lost.
 //
@@ -581,7 +614,7 @@ recordSignSensitiveOperandWidths(MachineFunction &MF,
 static void widenSignSensitiveOps(MachineFunction &MF, SPIRVGlobalRegistry *GR,
                                   MachineIRBuilder &MIB,
                                   MachineRegisterInfo &MRI,
-                                  const SignSensitiveWideningInfo &Info) {
+                                  const NarrowWideningInfo &Info) {
   // Emit G_SEXT_INREG from Reg's recorded narrow width; retypes Reg to the
   // widened width and returns the sign-extended vreg.
   auto SignExtendReg = [&](Register Reg, unsigned OldW,
@@ -601,7 +634,7 @@ static void widenSignSensitiveOps(MachineFunction &MF, SPIRVGlobalRegistry *GR,
   // TODO: when the same narrow vreg feeds multiple sign-sensitive ops (e.g.
   // sdiv %x, %y and srem %x, %y), emit one shared G_SEXT_INREG instead of one
   // per use.
-  for (MachineInstr *MI : Info.Worklist) {
+  for (MachineInstr *MI : Info.SignSensitiveWorklist) {
     unsigned N = MI->getNumOperands();
     MachineOperand &LHS = MI->getOperand(N - 2);
     MachineOperand &RHS = MI->getOperand(N - 1);
@@ -620,6 +653,76 @@ static void widenSignSensitiveOps(MachineFunction &MF, SPIRVGlobalRegistry *GR,
   }
 }
 
+// LegalizerHelper::widenScalar has the same cases but cannot be reached: the
+// relabel retypes every narrow scalar to a pow2 LLT, so no illegal narrow type
+// ever reaches the legalizer.
+//
+// TODO: handle vector operands.
+static void widenBitCountOps(SPIRVGlobalRegistry *GR, MachineIRBuilder &MIB,
+                             MachineRegisterInfo &MRI,
+                             const NarrowWideningInfo &Info) {
+  for (auto [MI, OldWidth] : Info.BitCountWorklist) {
+    Register SrcReg = MI->getOperand(1).getReg();
+    unsigned NewWidth = widenBitWidthToNextPow2(OldWidth);
+    LLT NewTy = LLT::scalar(NewWidth);
+    widenScalarType(SrcReg, MRI);
+    MIB.setInstrAndDebugLoc(*MI);
+    SPIRVTypeInst SpvTy = GR->getOrCreateSPIRVIntegerType(NewWidth, MIB);
+
+    // The G_TRUNC lowering masks its result to the narrow width, so a source
+    // coming from it needs no second mask.
+    APInt Cst;
+    bool HighBitsAlreadyZero =
+        mi_match(SrcReg, MRI, m_GAnd(m_Reg(), m_ICst(Cst))) &&
+        Cst.isSubsetOf(APInt::getLowBitsSet(Cst.getBitWidth(), OldWidth));
+    auto ClearHighBits = [&](unsigned Width) -> Register {
+      if (HighBitsAlreadyZero)
+        return SrcReg;
+      Register Masked = createVirtualRegister(SpvTy, GR, MIB);
+      MIB.buildZExtInReg(Masked, SrcReg, Width);
+      return Masked;
+    };
+
+    Register Input;
+    switch (MI->getOpcode()) {
+    case TargetOpcode::G_CTLZ_ZERO_POISON: {
+      // Shifting up to the widened MSB moves the poison out too, so no
+      // adjustment.
+      Input = createVirtualRegister(SpvTy, GR, MIB);
+      auto Diff = MIB.buildConstant(NewTy, NewWidth - OldWidth);
+      MIB.buildShl(Input, SrcReg, Diff);
+      break;
+    }
+    case TargetOpcode::G_CTTZ: {
+      // Keeps an all-zero narrow value counting exactly OldWidth zeros.
+      Input = createVirtualRegister(SpvTy, GR, MIB);
+      auto TopBit =
+          MIB.buildConstant(NewTy, APInt::getOneBitSet(NewWidth, OldWidth));
+      MIB.buildOr(Input, SrcReg, TopBit);
+      break;
+    }
+    case TargetOpcode::G_CTPOP:
+      Input = ClearHighBits(OldWidth);
+      break;
+    case TargetOpcode::G_CTLZ: {
+      // Clearing the extra bits adds leading zeros the count has to drop.
+      Input = ClearHighBits(OldWidth);
+      Register DstReg = MI->getOperand(0).getReg();
+      widenScalarType(DstReg, MRI);
+      Register Count = createVirtualRegister(SpvTy, GR, MIB);
+      MI->getOperand(0).setReg(Count);
+      setInsertPtAfterDef(MIB, MI);
+      auto Diff = MIB.buildConstant(NewTy, NewWidth - OldWidth);
+      MIB.buildSub(DstReg, Count, Diff);
+      break;
+    }
+    default:
+      llvm_unreachable("unexpected width-sensitive bit-count opcode");
+    }
+    MI->getOperand(1).setReg(Input);
+  }
+}
+
 static void
 generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
                      MachineIRBuilder MIB,
@@ -643,11 +746,10 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
     // integer widths of 8, 16, 32, 64. Non-standard widths (e.g., i24, i40)
     // must be widened to the next power of two.
     //
-    // Record the original widths of sign-sensitive operands before either
+    // Record the original widths of width-sensitive operands before either
     // the G_TRUNC handling or the general widening loop retypes vregs, then
     // rewrite those ops after G_TRUNC processing using the recorded widths.
-    SignSensitiveWideningInfo SignSensitiveInfo =
-        recordSignSensitiveOperandWidths(MF, MRI);
+    NarrowWideningInfo WideningInfo = recordNarrowOperandWidths(MF, MRI);
 
     // G_TRUNC requires special handling because its semantics depend on the
     // original destination width. For example:
@@ -718,11 +820,11 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
         if (NewSrcWidth == NewDstWidth) {
           // Rekey OrigWidth from DstReg to MaskedReg so widenSignSensitiveOps
           // still sees the narrow original width after replaceRegWith.
-          if (auto It = SignSensitiveInfo.OrigWidth.find(DstReg);
-              It != SignSensitiveInfo.OrigWidth.end()) {
+          if (auto It = WideningInfo.OrigWidth.find(DstReg);
+              It != WideningInfo.OrigWidth.end()) {
             unsigned W = It->second;
-            SignSensitiveInfo.OrigWidth.erase(It);
-            SignSensitiveInfo.OrigWidth.try_emplace(MaskedReg, W);
+            WideningInfo.OrigWidth.erase(It);
+            WideningInfo.OrigWidth.try_emplace(MaskedReg, W);
           }
           MRI.replaceRegWith(DstReg, MaskedReg);
           TruncToRemove.push_back(&MI);
@@ -734,7 +836,8 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
     for (MachineInstr *MI : TruncToRemove)
       MI->eraseFromParent();
 
-    widenSignSensitiveOps(MF, GR, MIB, MRI, SignSensitiveInfo);
+    widenSignSensitiveOps(MF, GR, MIB, MRI, WideningInfo);
+    widenBitCountOps(GR, MIB, MRI, WideningInfo);
   }
 
   for (MachineBasicBlock *MBB : post_order(&MF)) {
diff --git a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/ctlz.ll b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/ctlz.ll
index 7c14c2b1fc887..a571a1c401ece 100644
--- a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/ctlz.ll
+++ b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/ctlz.ll
@@ -2,8 +2,14 @@
 ; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 
 ; CHECK: %[[#extinst_id:]] = OpExtInstImport "OpenCL.std"
+; CHECK-DAG: %[[#i32:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#i64:]] = OpTypeInt 64 0
+; CHECK-DAG: %[[#mask24:]] = OpConstant %[[#i32]] 16777215
+; CHECK-DAG: %[[#mask40:]] = OpConstant %[[#i64]] 1099511627775
+; CHECK-DAG: %[[#diff24:]] = OpConstant %[[#i32]] 8
+; CHECK-DAG: %[[#diff40:]] = OpConstant %[[#i64]] 24
 
-; CHECK: OpFunction
+; CHECK: %[[#]] = OpFunction
 ; CHECK: %[[#]] = OpExtInst %[[#]] %[[#extinst_id]] clz
 ; CHECK: OpFunctionEnd
 
@@ -14,3 +20,59 @@ entry:
 }
 
 declare i32 @llvm.ctlz.i32(i32, i1 immarg)
+
+; CHECK: %[[#]] = OpFunction
+; CHECK: %[[#sum24:]] = OpIAdd %[[#i32]]
+; CHECK: %[[#masked24:]] = OpBitwiseAnd %[[#i32]] %[[#sum24]] %[[#mask24]]
+; CHECK: %[[#count24:]] = OpExtInst %[[#i32]] %[[#extinst_id]] clz %[[#masked24]]
+; CHECK: %[[#result24:]] = OpISub %[[#i32]] %[[#count24]] %[[#diff24]]
+; CHECK: OpReturnValue %[[#result24]]
+
+define spir_func i24 @ctlz_i24(i32 %x) {
+  %narrow = trunc i32 %x to i24
+  %sum = add i24 %narrow, 1
+  %count = call i24 @llvm.ctlz.i24(i24 %sum, i1 false)
+  ret i24 %count
+}
+
+; CHECK: %[[#]] = OpFunction
+; CHECK: %[[#shift24:]] = OpShiftLeftLogical %[[#i32]] %[[#]] %[[#diff24]]
+; CHECK: %[[#poison24:]] = OpExtInst %[[#i32]] %[[#extinst_id]] clz %[[#shift24]]
+; CHECK: OpReturnValue %[[#poison24]]
+
+define spir_func i24 @ctlz_i24_zero_poison(i32 %x) {
+  %narrow = trunc i32 %x to i24
+  %count = call i24 @llvm.ctlz.i24(i24 %narrow, i1 true)
+  ret i24 %count
+}
+
+; CHECK: %[[#]] = OpFunction
+; CHECK: %[[#sum40:]] = OpIAdd %[[#i64]]
+; CHECK: %[[#masked40:]] = OpBitwiseAnd %[[#i64]] %[[#sum40]] %[[#mask40]]
+; CHECK: %[[#count40:]] = OpExtInst %[[#i64]] %[[#extinst_id]] clz %[[#masked40]]
+; CHECK: %[[#result40:]] = OpISub %[[#i64]] %[[#count40]] %[[#diff40]]
+; CHECK: OpReturnValue %[[#result40]]
+
+define spir_func i40 @ctlz_i40(i64 %x) {
+  %narrow = trunc i64 %x to i40
+  %sum = add i40 %narrow, 1
+  %count = call i40 @llvm.ctlz.i40(i40 %sum, i1 false)
+  ret i40 %count
+}
+
+; The high bits of an i24 parameter are genuinely undefined, so the mask is
+; what makes the count correct.
+; CHECK: %[[#]] = OpFunction
+; CHECK: %[[#maskedarg:]] = OpBitwiseAnd %[[#i32]] %[[#]] %[[#mask24]]
+; CHECK: %[[#countarg:]] = OpExtInst %[[#i32]] %[[#extinst_id]] clz %[[#maskedarg]]
+; CHECK: %[[#resultarg:]] = OpISub %[[#i32]] %[[#countarg]] %[[#diff24]]
+; CHECK: OpReturnValue %[[#resultarg]]
+
+define spir_func i24 @ctlz_i24_arg(i24 %x) {
+  %count = call i24 @llvm.ctlz.i24(i24 %x, i1 false)
+  ret i24 %count
+}
+
+declare i24 @llvm.ctlz.i24(i24, i1 immarg)
+
+declare i40 @llvm.ctlz.i40(i40, i1 immarg)
diff --git a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/ctpop.ll b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/ctpop.ll
index be1e7c9d20e94..d0d7460ecab2e 100644
--- a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/ctpop.ll
+++ b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/ctpop.ll
@@ -1,6 +1,11 @@
 ; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-linux %s -o - | FileCheck %s
 ; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-linux %s -o - -filetype=obj | spirv-val %}
 
+; CHECK-DAG: %[[#i32:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#mask24:]] = OpConstant %[[#i32]] 16777215
+; CHECK-DAG: %[[#i64:]] = OpTypeInt 64 0
+; CHECK-DAG: %[[#mask40:]] = OpConstant %[[#i64]] 1099511627775
+
 ; CHECK: %[[#]] = OpBitCount %[[#]] %[[#]]
 ; CHECK: %[[#]] = OpBitCount %[[#]] %[[#]]
 ; CHECK: %[[#]] = OpBitCount %[[#]] %[[#]]
@@ -30,10 +35,49 @@ entry:
   ret void
 }
 
+; CHECK: %[[#]] = OpFunction
+; CHECK: %[[#sum:]] = OpIAdd %[[#i32]]
+; CHECK: %[[#masked24:]] = OpBitwiseAnd %[[#i32]] %[[#sum]] %[[#mask24]]
+; CHECK: %[[#count24:]] = OpBitCount %[[#i32]] %[[#masked24]]
+; CHECK: OpReturnValue %[[#count24]]
+
+define spir_func i24 @test_i24(i32 %x) {
+  %narrow = trunc i32 %x to i24
+  %sum = add i24 %narrow, 1
+  %count = tail call i24 @llvm.ctpop.i24(i24 %sum)
+  ret i24 %count
+}
+
+; The high bits of an i24 parameter are genuinely undefined, so the mask is
+; what makes the count correct.
+; CHECK: %[[#]] = OpFunction
+; CHECK: %[[#maskedarg:]] = OpBitwiseAnd %[[#i32]] %[[#]] %[[#mask24]]
+; CHECK: %[[#countarg:]] = OpBitCount %[[#i32]] %[[#maskedarg]]
+; CHECK: OpReturnValue %[[#countarg]]
+
+define spir_func i24 @test_i24_arg(i24 %x) {
+  %count = tail call i24 @llvm.ctpop.i24(i24 %x)
+  ret i24 %count
+}
+
+; CHECK: %[[#]] = OpFunction
+; CHECK: %[[#masked40:]] = OpBitwiseAnd %[[#i64]] %[[#]] %[[#mask40]]
+; CHECK: %[[#count40:]] = OpBitCount %[[#i64]] %[[#masked40]]
+; CHECK: OpReturnValue %[[#count40]]
+
+define spir_func i40 @test_i40_arg(i40 %x) {
+  %count = tail call i40 @llvm.ctpop.i40(i40 %x)
+  ret i40 %count
+}
+
 declare i8 @llvm.ctpop.i8(i8)
 
 declare i16 @llvm.ctpop.i16(i16)
 
+declare i24 @llvm.ctpop.i24(i24)
+
+declare i40 @llvm.ctpop.i40(i40)
+
 declare i32 @llvm.ctpop.i32(i32)
 
 declare i64 @llvm.ctpop.i64(i64)
diff --git a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/cttz.ll b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/cttz.ll
index 2358044e9bd21..8a38c4f860410 100644
--- a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/cttz.ll
+++ b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/cttz.ll
@@ -2,8 +2,12 @@
 ; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
 
 ; CHECK: %[[#extinst_id:]] = OpExtInstImport "OpenCL.std"
+; CHECK-DAG: %[[#i32:]] = OpTypeInt 32 0
+; CHECK-DAG: %[[#topbit24:]] = OpConstant %[[#i32]] 16777216
+; CHECK-DAG: %[[#i64:]] = OpTypeInt 64 0
+; CHECK-DAG: %[[#topbit40:]] = OpConstant %[[#i64]] 1099511627776
 
-; CHECK: OpFunction
+; CHECK: %[[#]] = OpFunction
 ; CHECK: %[[#]] = OpExtInst %[[#]] %[[#extinst_id]] ctz
 ; CHECK: OpFunctionEnd
 
@@ -13,7 +17,7 @@ entry:
   ret i32 %0
 }
 
-; CHECK: OpFunction
+; CHECK: %[[#]] = OpFunction
 ; CHECK: %[[#]] = OpExtInst %[[#]] %[[#extinst_id]] ctz
 ; CHECK: OpFunctionEnd
 
@@ -23,6 +27,44 @@ entry:
   ret <4 x i32> %0
 }
 
+; Setting bit 24 keeps an all-zero i24 input counting 24 trailing zeros.
+; CHECK: %[[#]] = OpFunction
+; CHECK: %[[#orred24:]] = OpBitwiseOr %[[#i32]] %[[#]] %[[#topbit24]]
+; CHECK: %[[#count24:]] = OpExtInst %[[#i32]] %[[#extinst_id]] ctz %[[#orred24]]
+; CHECK: OpReturnValue %[[#count24]]
+
+define spir_func i24 @TestCtzI24(i32 %x) {
+  %narrow = trunc i32 %x to i24
+  %count = call i24 @llvm.cttz.i24(i24 %narrow, i1 false)
+  ret i24 %count
+}
+
+; The zero-poison form needs no fixup: its low bits are known non-zero.
+; CHECK: %[[#]] = OpFunction
+; CHECK-NOT: OpBitwiseOr
+; CHECK: %[[#poison24:]] = OpExtInst %[[#i32]] %[[#extinst_id]] ctz %[[#]]
+; CHECK: OpReturnValue %[[#poison24]]
+
+define spir_func i24 @TestCtzI24ZeroPoison(i32 %x) {
+  %narrow = trunc i32 %x to i24
+  %count = call i24 @llvm.cttz.i24(i24 %narrow, i1 true)
+  ret i24 %count
+}
+
+; CHECK: %[[#]] = OpFunction
+; CHECK: %[[#orred40:]] = OpBitwiseOr %[[#i64]] %[[#]] %[[#topbit40]]
+; CHECK: %[[#count40:]] = OpExtInst %[[#i64]] %[[#extinst_id]] ctz %[[#orred40]]
+; CHECK: OpReturnValue %[[#count40]]
+
+define spir_func i40 @TestCtzI40(i40 %x) {
+  %count = call i40 @llvm.cttz.i40(i40 %x, i1 false)
+  ret i40 %count
+}
+
+declare i24 @llvm.cttz.i24(i24, i1 immarg)
+
+declare i40 @llvm.cttz.i40(i40, i1 immarg)
+
 declare i32 @llvm.cttz.i32(i32, i1 immarg)
 
 declare <4 x i32> @llvm.cttz.v4i32(<4 x i32>, i1 immarg)

>From 16683cda3c5200692d99019d0a38f7043fbeb84d Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Tue, 15 Sep 2026 20:49:32 +0200
Subject: [PATCH 2/2] const

---
 llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index bca58beba0298..6e39e626a61a4 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -567,8 +567,8 @@ static bool isWidthSensitiveBitCountOp(unsigned Opcode) {
 // Collect ops whose semantics depend on the operand width along with their
 // pre-widening widths, before later passes retype those vregs to pow2 LLTs
 // and the original width is no longer recoverable.
-static NarrowWideningInfo recordNarrowOperandWidths(MachineFunction &MF,
-                                                    MachineRegisterInfo &MRI) {
+static NarrowWideningInfo
+recordNarrowOperandWidths(MachineFunction &MF, const MachineRegisterInfo &MRI) {
   NarrowWideningInfo Info;
   auto RecordIfNarrow = [&](Register Reg) {
     std::optional<unsigned> W = getNarrowScalarWidth(Reg, MRI);



More information about the llvm-commits mailing list