[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