[llvm] [SPIRV] Fix trunc nonstandard int types (PR #191393)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Apr 14 05:24:09 PDT 2026
https://github.com/idubinov updated https://github.com/llvm/llvm-project/pull/191393
>From 14b7b36538451da38dd9421e27e0b9879a6ac9ca Mon Sep 17 00:00:00 2001
From: idubinov <igor.dubinov at amd.com>
Date: Fri, 10 Apr 2026 06:02:55 -0500
Subject: [PATCH 01/11] fix nonstandart trunc
---
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 65 ++++++++++++++++---
.../CodeGen/SPIRV/trunc-nonstd-bitwidth.ll | 38 ++++++++++-
2 files changed, 93 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index aead1ce735c49..a6a0feec6c959 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -397,25 +397,40 @@ static unsigned widenBitWidthToNextPow2(unsigned BitWidth) {
return std::min(std::max(1u << Log2_32_Ceil(BitWidth), 8u), 128u);
}
-static void widenScalarType(Register Reg, MachineRegisterInfo &MRI) {
+static unsigned widenScalarType(Register Reg, MachineRegisterInfo &MRI) {
+ // returns original size or 0 if no change
LLT RegType = MRI.getType(Reg);
if (!RegType.isScalar())
- return;
+ return 0;
unsigned CurrentWidth = RegType.getScalarSizeInBits();
unsigned NewWidth = widenBitWidthToNextPow2(CurrentWidth);
- if (NewWidth != CurrentWidth)
+ if (NewWidth != CurrentWidth){
MRI.setType(Reg, LLT::scalar(NewWidth));
+ return CurrentWidth;
+ }
+ return 0;
}
-static void widenCImmType(MachineOperand &MOP) {
+static unsigned widenCImmType(MachineOperand &MOP) {
+ // returns original size or 0 if no change
const ConstantInt *CImmVal = MOP.getCImm();
unsigned CurrentWidth = CImmVal->getBitWidth();
unsigned NewWidth = widenBitWidthToNextPow2(CurrentWidth);
- if (NewWidth != CurrentWidth) {
- // Replace the immediate value with the widened version
- MOP.setCImm(ConstantInt::get(CImmVal->getType()->getContext(),
- CImmVal->getValue().zextOrTrunc(NewWidth)));
- }
+ if (NewWidth == CurrentWidth)
+ return 0;
+
+ // Replace the immediate value with the widened version
+ MOP.setCImm(ConstantInt::get(CImmVal->getType()->getContext(),
+ CImmVal->getValue().zextOrTrunc(NewWidth)));
+ return CurrentWidth;
+}
+
+static unsigned widenOperand(MachineOperand &MOP, MachineRegisterInfo &MRI) {
+ // returns original size or 0 if no change
+ if (MOP.isReg())
+ return widenScalarType(MOP.getReg(), MRI);
+ else if (MOP.isCImm())
+ return widenCImmType(MOP);
}
static void setInsertPtAfterDef(MachineIRBuilder &MIB, MachineInstr *Def) {
@@ -486,6 +501,38 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
ST->canUseExtension(SPIRV::Extension::SPV_KHR_bit_instructions) ||
ST->canUseExtension(SPIRV::Extension::SPV_INTEL_int4);
+ if (!IsExtendedInts) {
+ // Some instructions' behavior relies on register size, e.g. G_TRUNC. Process them before general register widening.
+ for (MachineBasicBlock &MBB : MF) {
+ for (MachineInstr &MI: MBB) {
+ unsigned MIOp = MI.getOpcode();
+ if (MIOp == TargetOpcode::G_TRUNC) {
+ assert(MI.getNumOperands() == 2);
+
+ widenOperand(MI.getOperand(1), MRI); // SRC
+
+ unsigned OriginalDstWidth = widenOperand(MI.getOperand(0), MRI);
+ if (OriginalDstWidth != 0) {
+ // Dst was widened - replace G_TRUNC with G_AND & appropriate mask.
+ Register DstReg = MI.getOperand(0).getReg();
+ Register SrcReg = MI.getOperand(1).getReg();
+ unsigned NewDstWidth = MRI.getType(DstReg).getScalarSizeInBits();
+
+ // Create mask constant with lower OriginalDstWidth bits set.
+ MIB.setInsertPt(MBB, MI.getIterator());
+ APInt Mask = APInt::getLowBitsSet(NewDstWidth, OriginalDstWidth);
+ auto MaskReg = MIB.buildConstant(LLT::scalar(NewDstWidth), Mask);
+
+ // Replace G_TRUNC with G_AND.
+ MI.setDesc(ST->getInstrInfo()->get(TargetOpcode::G_AND));
+ MI.getOperand(1).setReg(SrcReg);
+ MI.addOperand(MachineOperand::CreateReg(MaskReg.getReg(0), false));
+ }
+ }
+ }
+ }
+ }
+
for (MachineBasicBlock *MBB : post_order(&MF)) {
if (MBB->empty())
continue;
diff --git a/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll b/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
index 07af684fe0b48..bedc3b59089f8 100644
--- a/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
+++ b/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
@@ -17,6 +17,8 @@
; CHECK-DAG: %[[#PtrStruct:]] = OpTypePointer CrossWorkgroup %[[#Struct]]
; CHECK-EXT-DAG: %[[#Int40:]] = OpTypeInt 40 0
; CHECK-EXT-DAG: %[[#Int50:]] = OpTypeInt 50 0
+; CHECK-EXT-DAG: %[[#Int24:]] = OpTypeInt 24 0
+; CHECK-EXT-DAG: %[[#Int5:]] = OpTypeInt 5 0
; CHECK-NOEXT-DAG: %[[#Int40:]] = OpTypeInt 64 0
; CHECK-DAG: %[[#PtrInt40:]] = OpTypePointer CrossWorkgroup %[[#Int40]]
@@ -38,7 +40,27 @@
; CHECK-EXT: %[[#Tq:]] = OpUConvert %[[#Int40]] %[[#Q]]
; CHECK-EXT: OpStore %[[#QArg]] %[[#Tq]]
-; CHECK-NOEXT: OpStore %[[#QArg]] %[[#Q]]
+; CHECK-NOEXT: %[[#TqNoext:]] = OpBitwiseAnd %[[#]] %[[#Q]] %[[#]]
+; CHECK-NOEXT: OpStore %[[#QArg]] %[[#TqNoext]]
+
+; Test 3: trunc to small non-standard width (i64 -> i24)
+; CHECK: OpFunction
+; CHECK: %[[#T3Arg:]] = OpFunctionParameter
+; CHECK: %[[#T3Val:]] = OpFunctionParameter
+; CHECK-EXT: %[[#T3Tr:]] = OpUConvert %[[#Int24]] %[[#T3Val]]
+; CHECK-EXT: OpStore %[[#T3Arg]] %[[#T3Tr]]
+; CHECK-NOEXT: %[[#T3Noext:]] = OpBitwiseAnd %[[#]] %[[#T3Val]] %[[#]]
+; CHECK-NOEXT: OpStore %[[#T3Arg]] %[[#T3Noext]]
+
+; Test 4: trunc to i5 (non-power-of-2, < 8 bits)
+; In NOEXT mode, i5 widens to i8, so mask with 0x1F
+; CHECK: OpFunction
+; CHECK: %[[#T4Arg:]] = OpFunctionParameter
+; CHECK: %[[#T4Val:]] = OpFunctionParameter
+; CHECK-EXT: %[[#T4Tr:]] = OpUConvert %[[#Int5]] %[[#T4Val]]
+; CHECK-EXT: OpStore %[[#T4Arg]] %[[#T4Tr]]
+; CHECK-NOEXT: %[[#T4Noext:]] = OpBitwiseAnd %[[#]] %[[#T4Val]] %[[#]]
+; CHECK-NOEXT: OpStore %[[#T4Arg]] %[[#T4Noext]]
%struct = type <{ i32, i8, [3 x i8] }>
@@ -54,3 +76,17 @@ define spir_kernel void @bar(ptr addrspace(1) %qarg, i50 %q) {
store i40 %tq, ptr addrspace(1) %qarg
ret void
}
+
+; trunc to small non-standard width (i64 -> i24)
+define spir_kernel void @trunc_to_i24(ptr addrspace(1) %arg, i64 %val) {
+ %tr = trunc i64 %val to i24
+ store i24 %tr, ptr addrspace(1) %arg
+ ret void
+}
+
+; trunc to i5 (non-power-of-2, < 8 bits)
+define spir_kernel void @trunc_to_i5(ptr addrspace(1) %arg, i16 %val) {
+ %tr = trunc i16 %val to i5
+ store i5 %tr, ptr addrspace(1) %arg
+ ret void
+}
>From d9b3cc2a70accb8ae77f4466aef32ab8b25d47c0 Mon Sep 17 00:00:00 2001
From: idubinov <igor.dubinov at amd.com>
Date: Fri, 10 Apr 2026 06:04:14 -0500
Subject: [PATCH 02/11] code format
---
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index a6a0feec6c959..3bba1836b4ff4 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -404,7 +404,7 @@ static unsigned widenScalarType(Register Reg, MachineRegisterInfo &MRI) {
return 0;
unsigned CurrentWidth = RegType.getScalarSizeInBits();
unsigned NewWidth = widenBitWidthToNextPow2(CurrentWidth);
- if (NewWidth != CurrentWidth){
+ if (NewWidth != CurrentWidth) {
MRI.setType(Reg, LLT::scalar(NewWidth));
return CurrentWidth;
}
@@ -416,7 +416,7 @@ static unsigned widenCImmType(MachineOperand &MOP) {
const ConstantInt *CImmVal = MOP.getCImm();
unsigned CurrentWidth = CImmVal->getBitWidth();
unsigned NewWidth = widenBitWidthToNextPow2(CurrentWidth);
- if (NewWidth == CurrentWidth)
+ if (NewWidth == CurrentWidth)
return 0;
// Replace the immediate value with the widened version
@@ -502,15 +502,16 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
ST->canUseExtension(SPIRV::Extension::SPV_INTEL_int4);
if (!IsExtendedInts) {
- // Some instructions' behavior relies on register size, e.g. G_TRUNC. Process them before general register widening.
+ // Some instructions' behavior relies on register size, e.g. G_TRUNC.
+ // Process them before general register widening.
for (MachineBasicBlock &MBB : MF) {
- for (MachineInstr &MI: MBB) {
+ for (MachineInstr &MI : MBB) {
unsigned MIOp = MI.getOpcode();
if (MIOp == TargetOpcode::G_TRUNC) {
assert(MI.getNumOperands() == 2);
-
+
widenOperand(MI.getOperand(1), MRI); // SRC
-
+
unsigned OriginalDstWidth = widenOperand(MI.getOperand(0), MRI);
if (OriginalDstWidth != 0) {
// Dst was widened - replace G_TRUNC with G_AND & appropriate mask.
>From ed8ba8bad7fa514e106de17d9a93042fba1c9b66 Mon Sep 17 00:00:00 2001
From: idubinov <idubinov at amd.com>
Date: Mon, 13 Apr 2026 11:35:55 +0200
Subject: [PATCH 03/11] Apply suggestions from code review
Co-authored-by: Marcos Maronas <mmaronas at amd.com>
---
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 10 +++++-----
llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll | 10 +++++-----
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 3bba1836b4ff4..118c30090b783 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -398,7 +398,7 @@ static unsigned widenBitWidthToNextPow2(unsigned BitWidth) {
}
static unsigned widenScalarType(Register Reg, MachineRegisterInfo &MRI) {
- // returns original size or 0 if no change
+ // Returns original size or 0 if no change.
LLT RegType = MRI.getType(Reg);
if (!RegType.isScalar())
return 0;
@@ -412,21 +412,21 @@ static unsigned widenScalarType(Register Reg, MachineRegisterInfo &MRI) {
}
static unsigned widenCImmType(MachineOperand &MOP) {
- // returns original size or 0 if no change
+ // Returns original size or 0 if no change.
const ConstantInt *CImmVal = MOP.getCImm();
unsigned CurrentWidth = CImmVal->getBitWidth();
unsigned NewWidth = widenBitWidthToNextPow2(CurrentWidth);
if (NewWidth == CurrentWidth)
return 0;
- // Replace the immediate value with the widened version
+ // Replace the immediate value with the widened version.
MOP.setCImm(ConstantInt::get(CImmVal->getType()->getContext(),
CImmVal->getValue().zextOrTrunc(NewWidth)));
return CurrentWidth;
}
static unsigned widenOperand(MachineOperand &MOP, MachineRegisterInfo &MRI) {
- // returns original size or 0 if no change
+ // Returns original size or 0 if no change.
if (MOP.isReg())
return widenScalarType(MOP.getReg(), MRI);
else if (MOP.isCImm())
@@ -510,7 +510,7 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
if (MIOp == TargetOpcode::G_TRUNC) {
assert(MI.getNumOperands() == 2);
- widenOperand(MI.getOperand(1), MRI); // SRC
+ widenOperand(MI.getOperand(1), MRI); // SRC.
unsigned OriginalDstWidth = widenOperand(MI.getOperand(0), MRI);
if (OriginalDstWidth != 0) {
diff --git a/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll b/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
index bedc3b59089f8..744db268ed25a 100644
--- a/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
+++ b/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
@@ -43,7 +43,7 @@
; CHECK-NOEXT: %[[#TqNoext:]] = OpBitwiseAnd %[[#]] %[[#Q]] %[[#]]
; CHECK-NOEXT: OpStore %[[#QArg]] %[[#TqNoext]]
-; Test 3: trunc to small non-standard width (i64 -> i24)
+; Test 3: trunc to small non-standard width (i64 -> i24).
; CHECK: OpFunction
; CHECK: %[[#T3Arg:]] = OpFunctionParameter
; CHECK: %[[#T3Val:]] = OpFunctionParameter
@@ -52,8 +52,8 @@
; CHECK-NOEXT: %[[#T3Noext:]] = OpBitwiseAnd %[[#]] %[[#T3Val]] %[[#]]
; CHECK-NOEXT: OpStore %[[#T3Arg]] %[[#T3Noext]]
-; Test 4: trunc to i5 (non-power-of-2, < 8 bits)
-; In NOEXT mode, i5 widens to i8, so mask with 0x1F
+; Test 4: trunc to i5 (non-power-of-2, < 8 bits).
+; In NOEXT mode, i5 widens to i8, so mask with 0x1F.
; CHECK: OpFunction
; CHECK: %[[#T4Arg:]] = OpFunctionParameter
; CHECK: %[[#T4Val:]] = OpFunctionParameter
@@ -77,14 +77,14 @@ define spir_kernel void @bar(ptr addrspace(1) %qarg, i50 %q) {
ret void
}
-; trunc to small non-standard width (i64 -> i24)
+; Trunc to small non-standard width (i64 -> i24).
define spir_kernel void @trunc_to_i24(ptr addrspace(1) %arg, i64 %val) {
%tr = trunc i64 %val to i24
store i24 %tr, ptr addrspace(1) %arg
ret void
}
-; trunc to i5 (non-power-of-2, < 8 bits)
+; Trunc to i5 (non-power-of-2, < 8 bits).
define spir_kernel void @trunc_to_i5(ptr addrspace(1) %arg, i16 %val) {
%tr = trunc i16 %val to i5
store i5 %tr, ptr addrspace(1) %arg
>From bdf233384bc2978af27f8317e186663699ad19b8 Mon Sep 17 00:00:00 2001
From: idubinov <igor.dubinov at amd.com>
Date: Mon, 13 Apr 2026 04:45:06 -0500
Subject: [PATCH 04/11] fix build warning
---
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 118c30090b783..56cef3e21ba0e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -429,8 +429,9 @@ static unsigned widenOperand(MachineOperand &MOP, MachineRegisterInfo &MRI) {
// Returns original size or 0 if no change.
if (MOP.isReg())
return widenScalarType(MOP.getReg(), MRI);
- else if (MOP.isCImm())
+ if (MOP.isCImm())
return widenCImmType(MOP);
+ return 0;
}
static void setInsertPtAfterDef(MachineIRBuilder &MIB, MachineInstr *Def) {
>From 6d47373ca5d48ebace375ab04430d5d56b3d644b Mon Sep 17 00:00:00 2001
From: idubinov <igor.dubinov at amd.com>
Date: Mon, 13 Apr 2026 04:58:38 -0500
Subject: [PATCH 05/11] Address review
---
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 46 ++++++++++-----------
1 file changed, 21 insertions(+), 25 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 56cef3e21ba0e..36f10b0c24d10 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -503,34 +503,30 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
ST->canUseExtension(SPIRV::Extension::SPV_INTEL_int4);
if (!IsExtendedInts) {
- // Some instructions' behavior relies on register size, e.g. G_TRUNC.
- // Process them before general register widening.
+ // G_TRUNC rely on register size. The general register widening changes G_TRUNC instruction behaviour. Process G_TRUNK before operators widening.
for (MachineBasicBlock &MBB : MF) {
for (MachineInstr &MI : MBB) {
unsigned MIOp = MI.getOpcode();
- if (MIOp == TargetOpcode::G_TRUNC) {
- assert(MI.getNumOperands() == 2);
-
- widenOperand(MI.getOperand(1), MRI); // SRC.
-
- unsigned OriginalDstWidth = widenOperand(MI.getOperand(0), MRI);
- if (OriginalDstWidth != 0) {
- // Dst was widened - replace G_TRUNC with G_AND & appropriate mask.
- Register DstReg = MI.getOperand(0).getReg();
- Register SrcReg = MI.getOperand(1).getReg();
- unsigned NewDstWidth = MRI.getType(DstReg).getScalarSizeInBits();
-
- // Create mask constant with lower OriginalDstWidth bits set.
- MIB.setInsertPt(MBB, MI.getIterator());
- APInt Mask = APInt::getLowBitsSet(NewDstWidth, OriginalDstWidth);
- auto MaskReg = MIB.buildConstant(LLT::scalar(NewDstWidth), Mask);
-
- // Replace G_TRUNC with G_AND.
- MI.setDesc(ST->getInstrInfo()->get(TargetOpcode::G_AND));
- MI.getOperand(1).setReg(SrcReg);
- MI.addOperand(MachineOperand::CreateReg(MaskReg.getReg(0), false));
- }
- }
+ if (MIOp != TargetOpcode::G_TRUNC)
+ continue;
+ assert(MI.getNumOperands() == 2);
+
+ unsigned OriginalDstWidth = widenOperand(MI.getOperand(0), MRI);
+ if (OriginalDstWidth == 0)
+ continue;
+ // Dst was widened - replace G_TRUNC with G_AND & appropriate mask.
+ Register DstReg = MI.getOperand(0).getReg();
+ Register SrcReg = MI.getOperand(1).getReg();
+ unsigned NewDstWidth = MRI.getType(DstReg).getScalarSizeInBits();
+
+ // Create mask constant with lower OriginalDstWidth bits set.
+ MIB.setInsertPt(MBB, MI.getIterator());
+ APInt Mask = APInt::getLowBitsSet(NewDstWidth, OriginalDstWidth);
+ auto MaskReg = MIB.buildConstant(LLT::scalar(NewDstWidth), Mask);
+
+ MI.setDesc(ST->getInstrInfo()->get(TargetOpcode::G_AND));
+ MI.getOperand(1).setReg(SrcReg);
+ MI.addOperand(MachineOperand::CreateReg(MaskReg.getReg(0), false));
}
}
}
>From b35d9e1cc6526158840cbb170eb7cf866b6ba67b Mon Sep 17 00:00:00 2001
From: idubinov <igor.dubinov at amd.com>
Date: Mon, 13 Apr 2026 06:52:07 -0500
Subject: [PATCH 06/11] comments + code format
---
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 36f10b0c24d10..e1f4bdfd04af6 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -503,7 +503,10 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
ST->canUseExtension(SPIRV::Extension::SPV_INTEL_int4);
if (!IsExtendedInts) {
- // G_TRUNC rely on register size. The general register widening changes G_TRUNC instruction behaviour. Process G_TRUNK before operators widening.
+ // G_TRUNC rely on DST register size. The general register widening changes
+ // G_TRUNC instruction behaviour in case of change of DSR register size.
+ // Process widening of G_TRUNK separately, before general operators
+ // widening.
for (MachineBasicBlock &MBB : MF) {
for (MachineInstr &MI : MBB) {
unsigned MIOp = MI.getOpcode();
@@ -514,19 +517,21 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
unsigned OriginalDstWidth = widenOperand(MI.getOperand(0), MRI);
if (OriginalDstWidth == 0)
continue;
- // Dst was widened - replace G_TRUNC with G_AND & appropriate mask.
+
+ // DST was widened - replace G_TRUNC with G_AND & mask to preserve
+ // truncation semantics.
Register DstReg = MI.getOperand(0).getReg();
- Register SrcReg = MI.getOperand(1).getReg();
unsigned NewDstWidth = MRI.getType(DstReg).getScalarSizeInBits();
- // Create mask constant with lower OriginalDstWidth bits set.
MIB.setInsertPt(MBB, MI.getIterator());
APInt Mask = APInt::getLowBitsSet(NewDstWidth, OriginalDstWidth);
auto MaskReg = MIB.buildConstant(LLT::scalar(NewDstWidth), Mask);
MI.setDesc(ST->getInstrInfo()->get(TargetOpcode::G_AND));
- MI.getOperand(1).setReg(SrcReg);
+ // MI.getOperand(1) is the same.
MI.addOperand(MachineOperand::CreateReg(MaskReg.getReg(0), false));
+
+ // SRC0 will be correctly widened during general register widening.
}
}
}
>From 402b1d9a9de67aedc4fd0bd2f0dccf84bab341f3 Mon Sep 17 00:00:00 2001
From: idubinov <igor.dubinov at amd.com>
Date: Mon, 13 Apr 2026 10:13:35 -0500
Subject: [PATCH 07/11] update tests
---
.../CodeGen/SPIRV/trunc-nonstd-bitwidth.ll | 109 ++++++++----------
1 file changed, 51 insertions(+), 58 deletions(-)
diff --git a/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll b/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
index 744db268ed25a..4952ad1956490 100644
--- a/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
+++ b/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
@@ -1,49 +1,50 @@
-; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-NOEXT
-; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-NOEXT
+; RUN: %if spirv-tools %{ llc -O0 -verify-machineinstrs -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
-; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s --spirv-ext=+SPV_ALTERA_arbitrary_precision_integers -o - | FileCheck %s --check-prefixes=CHECK,CHECK-EXT
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv64-unknown-unknown %s --spirv-ext=+SPV_ALTERA_arbitrary_precision_integers -o - | FileCheck %s --check-prefixes=CHECK,CHECK-EXT
+; RUN: %if spirv-tools %{ llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s --spirv-ext=+SPV_ALTERA_arbitrary_precision_integers -o - -filetype=obj | spirv-val %}
-; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-NOEXT
-; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-NOEXT
+; RUN: %if spirv-tools %{ llc -O0 -verify-machineinstrs -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
-; RUN: llc -O0 -mtriple=spirv32-unknown-unknown %s --spirv-ext=+SPV_ALTERA_arbitrary_precision_integers -o - | FileCheck %s --check-prefixes=CHECK,CHECK-EXT
-; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s --spirv-ext=+SPV_ALTERA_arbitrary_precision_integers -o - -filetype=obj | spirv-val %}
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv32-unknown-unknown %s --spirv-ext=+SPV_ALTERA_arbitrary_precision_integers -o - | FileCheck %s --check-prefixes=CHECK,CHECK-EXT
+; RUN: %if spirv-tools %{ llc -verify-machineinstrs -O0 -mtriple=spirv32-unknown-unknown %s --spirv-ext=+SPV_ALTERA_arbitrary_precision_integers -o - -filetype=obj | spirv-val %}
-; TODO: This test currently fails with LLVM_ENABLE_EXPENSIVE_CHECKS enabled
-; XFAIL: expensive_checks
-
-; CHECK-DAG: OpName %[[#Struct:]] "struct"
-; CHECK-DAG: %[[#Struct]] = OpTypeStruct %[[#]] %[[#]] %[[#]]
-; CHECK-DAG: %[[#PtrStruct:]] = OpTypePointer CrossWorkgroup %[[#Struct]]
; CHECK-EXT-DAG: %[[#Int40:]] = OpTypeInt 40 0
; CHECK-EXT-DAG: %[[#Int50:]] = OpTypeInt 50 0
; CHECK-EXT-DAG: %[[#Int24:]] = OpTypeInt 24 0
+; CHECK-DAG: %[[#Int32:]] = OpTypeInt 32 0
; CHECK-EXT-DAG: %[[#Int5:]] = OpTypeInt 5 0
; CHECK-NOEXT-DAG: %[[#Int40:]] = OpTypeInt 64 0
-; CHECK-DAG: %[[#PtrInt40:]] = OpTypePointer CrossWorkgroup %[[#Int40]]
+; expected i40 to be widened in i64, i64->i64 should be properly handled
; CHECK: OpFunction
-; CHECK: %[[#Arg:]] = OpFunctionParameter
-; CHECK-EXT: %[[#Tr:]] = OpUConvert %[[#Int40]] %[[#R:]]
-; CHECK-EXT: %[[#Store:]] = OpInBoundsPtrAccessChain %[[#PtrStruct]] %[[#Arg]] %[[#]]
-; CHECK-EXT: %[[#StoreAsInt40:]] = OpBitcast %[[#PtrInt40]] %[[#Store]]
-; CHECK-EXT: OpStore %[[#StoreAsInt40]] %[[#Tr]]
-
-; CHECK-NOEXT: %[[#Store:]] = OpInBoundsPtrAccessChain %[[#PtrStruct]] %[[#Arg]] %[[#]]
-; CHECK-NOEXT: %[[#StoreAsInt40:]] = OpBitcast %[[#PtrInt40]] %[[#Store]]
-; CHECK-NOEXT: OpStore %[[#StoreAsInt40]] %[[#R:]]
+; CHECK: %[[#T1Arg:]] = OpFunctionParameter
+; CHECK: %[[#T1Val:]] = OpFunctionParameter
+; CHECK-EXT: %[[#T1Tr:]] = OpUConvert %[[#Int40]] %[[#T1Val]]
+; CHECK-EXT: OpStore %[[#T1Arg]] %[[#T1Tr]]
+; CHECK-NOEXT: %[[#T1Noext:]] = OpBitwiseAnd %[[#]] %[[#T1Val]] %[[#]]
+; CHECK-NOEXT: OpStore %[[#T1Arg]] %[[#T1Noext]]
+define spir_kernel void @trunc_i64_to_i40(ptr addrspace(1) %arg, i64 %val) {
+ %tr = trunc i64 %val to i40
+ store i40 %tr, ptr addrspace(1) %arg
+ ret void
+}
; CHECK: OpFunction
+; CHECK: %[[#T2Arg:]] = OpFunctionParameter
+; CHECK: %[[#T2Val:]] = OpFunctionParameter
+; CHECK-EXT: %[[#T2Tr:]] = OpUConvert %[[#Int24]] %[[#T2Val]]
+; CHECK-EXT: OpStore %[[#T2Arg]] %[[#T2Tr]]
+; CHECK-NOEXT: %[[#T2Noext:]] = OpBitwiseAnd %[[#]] %[[#T2Val]] %[[#]]
+; CHECK-NOEXT: OpStore %[[#T2Arg]] %[[#T2Noext]]
+define spir_kernel void @trunc_i50_to_i24(ptr addrspace(1) %arg, i50 %val) {
+ %tr = trunc i50 %val to i24
+ store i24 %tr, ptr addrspace(1) %arg
+ ret void
+}
-; CHECK: %[[#QArg:]] = OpFunctionParameter
-; CHECK: %[[#Q:]] = OpFunctionParameter
-; CHECK-EXT: %[[#Tq:]] = OpUConvert %[[#Int40]] %[[#Q]]
-; CHECK-EXT: OpStore %[[#QArg]] %[[#Tq]]
-
-; CHECK-NOEXT: %[[#TqNoext:]] = OpBitwiseAnd %[[#]] %[[#Q]] %[[#]]
-; CHECK-NOEXT: OpStore %[[#QArg]] %[[#TqNoext]]
-; Test 3: trunc to small non-standard width (i64 -> i24).
; CHECK: OpFunction
; CHECK: %[[#T3Arg:]] = OpFunctionParameter
; CHECK: %[[#T3Val:]] = OpFunctionParameter
@@ -51,9 +52,13 @@
; CHECK-EXT: OpStore %[[#T3Arg]] %[[#T3Tr]]
; CHECK-NOEXT: %[[#T3Noext:]] = OpBitwiseAnd %[[#]] %[[#T3Val]] %[[#]]
; CHECK-NOEXT: OpStore %[[#T3Arg]] %[[#T3Noext]]
+define spir_kernel void @trunc_i64_to_i24(ptr addrspace(1) %arg, i64 %val) {
+ %tr = trunc i64 %val to i24
+ store i24 %tr, ptr addrspace(1) %arg
+ ret void
+}
-; Test 4: trunc to i5 (non-power-of-2, < 8 bits).
-; In NOEXT mode, i5 widens to i8, so mask with 0x1F.
+; In NOEXT mode, i5 widens to i8
; CHECK: OpFunction
; CHECK: %[[#T4Arg:]] = OpFunctionParameter
; CHECK: %[[#T4Val:]] = OpFunctionParameter
@@ -61,32 +66,20 @@
; CHECK-EXT: OpStore %[[#T4Arg]] %[[#T4Tr]]
; CHECK-NOEXT: %[[#T4Noext:]] = OpBitwiseAnd %[[#]] %[[#T4Val]] %[[#]]
; CHECK-NOEXT: OpStore %[[#T4Arg]] %[[#T4Noext]]
-
-%struct = type <{ i32, i8, [3 x i8] }>
-
-define spir_kernel void @foo(ptr addrspace(1) %arg, i64 %r) {
- %tr = trunc i64 %r to i40
- %addr = getelementptr inbounds %struct, ptr addrspace(1) %arg, i64 0
- store i40 %tr, ptr addrspace(1) %addr
- ret void
-}
-
-define spir_kernel void @bar(ptr addrspace(1) %qarg, i50 %q) {
- %tq = trunc i50 %q to i40
- store i40 %tq, ptr addrspace(1) %qarg
- ret void
-}
-
-; Trunc to small non-standard width (i64 -> i24).
-define spir_kernel void @trunc_to_i24(ptr addrspace(1) %arg, i64 %val) {
- %tr = trunc i64 %val to i24
- store i24 %tr, ptr addrspace(1) %arg
- ret void
-}
-
-; Trunc to i5 (non-power-of-2, < 8 bits).
-define spir_kernel void @trunc_to_i5(ptr addrspace(1) %arg, i16 %val) {
+define spir_kernel void @trunc_i16_to_i5(ptr addrspace(1) %arg, i16 %val) {
%tr = trunc i16 %val to i5
store i5 %tr, ptr addrspace(1) %arg
ret void
}
+
+; The result should be same in both cases - no widening
+; CHECK: OpFunction
+; CHECK: %[[#T5Arg:]] = OpFunctionParameter
+; CHECK: %[[#T5Val:]] = OpFunctionParameter
+; CHECK: %[[#T5Tr:]] = OpUConvert %[[#Int32]] %[[#T5Val]]
+; CHECK-EXT: OpStore %[[#T5Arg]] %[[#T5Tr]]
+define spir_kernel void @trunc_i64_to_i32(ptr addrspace(1) %arg, i64 %val) {
+ %tr = trunc i64 %val to i32
+ store i32 %tr, ptr addrspace(1) %arg
+ ret void
+}
\ No newline at end of file
>From 003871e893eb979a1f317ceb8e9ddb1c8194da81 Mon Sep 17 00:00:00 2001
From: idubinov <igor.dubinov at amd.com>
Date: Mon, 13 Apr 2026 10:14:14 -0500
Subject: [PATCH 08/11] fix incorrect widening in case of different dst and src
sizes
---
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 73 ++++++++++++---------
1 file changed, 41 insertions(+), 32 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index e1f4bdfd04af6..7f110254abd52 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -397,41 +397,25 @@ static unsigned widenBitWidthToNextPow2(unsigned BitWidth) {
return std::min(std::max(1u << Log2_32_Ceil(BitWidth), 8u), 128u);
}
-static unsigned widenScalarType(Register Reg, MachineRegisterInfo &MRI) {
- // Returns original size or 0 if no change.
+static void widenScalarType(Register Reg, MachineRegisterInfo &MRI) {
LLT RegType = MRI.getType(Reg);
if (!RegType.isScalar())
- return 0;
+ return;
unsigned CurrentWidth = RegType.getScalarSizeInBits();
unsigned NewWidth = widenBitWidthToNextPow2(CurrentWidth);
- if (NewWidth != CurrentWidth) {
+ if (NewWidth != CurrentWidth)
MRI.setType(Reg, LLT::scalar(NewWidth));
- return CurrentWidth;
- }
- return 0;
}
-static unsigned widenCImmType(MachineOperand &MOP) {
- // Returns original size or 0 if no change.
+static void widenCImmType(MachineOperand &MOP) {
const ConstantInt *CImmVal = MOP.getCImm();
unsigned CurrentWidth = CImmVal->getBitWidth();
unsigned NewWidth = widenBitWidthToNextPow2(CurrentWidth);
- if (NewWidth == CurrentWidth)
- return 0;
-
- // Replace the immediate value with the widened version.
- MOP.setCImm(ConstantInt::get(CImmVal->getType()->getContext(),
- CImmVal->getValue().zextOrTrunc(NewWidth)));
- return CurrentWidth;
-}
-
-static unsigned widenOperand(MachineOperand &MOP, MachineRegisterInfo &MRI) {
- // Returns original size or 0 if no change.
- if (MOP.isReg())
- return widenScalarType(MOP.getReg(), MRI);
- if (MOP.isCImm())
- return widenCImmType(MOP);
- return 0;
+ if (NewWidth != CurrentWidth) {
+ // Replace the immediate value with the widened version
+ MOP.setCImm(ConstantInt::get(CImmVal->getType()->getContext(),
+ CImmVal->getValue().zextOrTrunc(NewWidth)));
+ }
}
static void setInsertPtAfterDef(MachineIRBuilder &MIB, MachineInstr *Def) {
@@ -513,19 +497,44 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
if (MIOp != TargetOpcode::G_TRUNC)
continue;
assert(MI.getNumOperands() == 2);
+ assert(MI.getOperand(0).isReg());
+ assert(MI.getOperand(1).isReg());
+
+ Register DstReg = MI.getOperand(0).getReg();
+ Register SrcReg = MI.getOperand(1).getReg();
+
+ if (!MRI.getType(DstReg).isScalar()) {
+ assert(!MRI.getType(SrcReg).isScalar());
+ continue;
+ }
+
+ unsigned OriginalDstWidth = MRI.getType(DstReg).getScalarSizeInBits();
+ unsigned OriginalSrcWidth = MRI.getType(SrcReg).getScalarSizeInBits();
+ assert(OriginalDstWidth < OriginalSrcWidth && "Due to LLVM spec.");
- unsigned OriginalDstWidth = widenOperand(MI.getOperand(0), MRI);
- if (OriginalDstWidth == 0)
+ unsigned NewDstWidth = widenBitWidthToNextPow2(OriginalDstWidth);
+ unsigned NewSrcWidth = widenBitWidthToNextPow2(OriginalSrcWidth);
+
+ // No Dst width change -> no semantics change, use default widening
+ if (OriginalDstWidth == NewDstWidth)
continue;
+ // Src width should be equal Dst width, use bigger of them
+ unsigned NewWidth = std::max(NewDstWidth, NewSrcWidth);
+
+ if (OriginalSrcWidth != NewWidth)
+ MRI.setType(SrcReg, LLT::scalar(NewWidth));
+
+ if (OriginalDstWidth == NewWidth)
+ continue;
+
+ MRI.setType(DstReg, LLT::scalar(NewWidth));
+
// DST was widened - replace G_TRUNC with G_AND & mask to preserve
// truncation semantics.
- Register DstReg = MI.getOperand(0).getReg();
- unsigned NewDstWidth = MRI.getType(DstReg).getScalarSizeInBits();
-
MIB.setInsertPt(MBB, MI.getIterator());
- APInt Mask = APInt::getLowBitsSet(NewDstWidth, OriginalDstWidth);
- auto MaskReg = MIB.buildConstant(LLT::scalar(NewDstWidth), Mask);
+ APInt Mask = APInt::getLowBitsSet(NewWidth, OriginalDstWidth);
+ auto MaskReg = MIB.buildConstant(LLT::scalar(NewWidth), Mask);
MI.setDesc(ST->getInstrInfo()->get(TargetOpcode::G_AND));
// MI.getOperand(1) is the same.
>From 16ef697cad1564fd37411c25eddd0c0e37f79bb2 Mon Sep 17 00:00:00 2001
From: idubinov <igor.dubinov at amd.com>
Date: Mon, 13 Apr 2026 10:29:04 -0500
Subject: [PATCH 09/11] remove unused code and improve comments
---
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 7f110254abd52..e2fc1e728f651 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -503,6 +503,7 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
Register DstReg = MI.getOperand(0).getReg();
Register SrcReg = MI.getOperand(1).getReg();
+ // TODO: handle vector types.
if (!MRI.getType(DstReg).isScalar()) {
assert(!MRI.getType(SrcReg).isScalar());
continue;
@@ -515,32 +516,28 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
unsigned NewDstWidth = widenBitWidthToNextPow2(OriginalDstWidth);
unsigned NewSrcWidth = widenBitWidthToNextPow2(OriginalSrcWidth);
- // No Dst width change -> no semantics change, use default widening
+ // No Dst width change -> no semantics change, use default widening.
if (OriginalDstWidth == NewDstWidth)
continue;
- // Src width should be equal Dst width, use bigger of them
+ // DST will be widened - replace G_TRUNC with G_AND & mask to preserve
+ // truncation semantics.
+ // G_AND: Src's width should be equal Dst width, use bigger of them.
unsigned NewWidth = std::max(NewDstWidth, NewSrcWidth);
+ // TODO: Check dependencies, this Src widening can poison register size
+ // of other instructions.
if (OriginalSrcWidth != NewWidth)
MRI.setType(SrcReg, LLT::scalar(NewWidth));
- if (OriginalDstWidth == NewWidth)
- continue;
-
MRI.setType(DstReg, LLT::scalar(NewWidth));
- // DST was widened - replace G_TRUNC with G_AND & mask to preserve
- // truncation semantics.
MIB.setInsertPt(MBB, MI.getIterator());
APInt Mask = APInt::getLowBitsSet(NewWidth, OriginalDstWidth);
auto MaskReg = MIB.buildConstant(LLT::scalar(NewWidth), Mask);
MI.setDesc(ST->getInstrInfo()->get(TargetOpcode::G_AND));
- // MI.getOperand(1) is the same.
MI.addOperand(MachineOperand::CreateReg(MaskReg.getReg(0), false));
-
- // SRC0 will be correctly widened during general register widening.
}
}
}
>From 8ca6b56012d6d298d5599c7782d96701a63163d3 Mon Sep 17 00:00:00 2001
From: idubinov <igor.dubinov at amd.com>
Date: Mon, 13 Apr 2026 10:29:04 -0500
Subject: [PATCH 10/11] remove unused code and improve comments
---
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 18 +++++++-----------
1 file changed, 7 insertions(+), 11 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 7f110254abd52..f5e68b9242782 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -503,6 +503,7 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
Register DstReg = MI.getOperand(0).getReg();
Register SrcReg = MI.getOperand(1).getReg();
+ // TODO: handle vector types.
if (!MRI.getType(DstReg).isScalar()) {
assert(!MRI.getType(SrcReg).isScalar());
continue;
@@ -510,37 +511,32 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
unsigned OriginalDstWidth = MRI.getType(DstReg).getScalarSizeInBits();
unsigned OriginalSrcWidth = MRI.getType(SrcReg).getScalarSizeInBits();
- assert(OriginalDstWidth < OriginalSrcWidth && "Due to LLVM spec.");
unsigned NewDstWidth = widenBitWidthToNextPow2(OriginalDstWidth);
unsigned NewSrcWidth = widenBitWidthToNextPow2(OriginalSrcWidth);
- // No Dst width change -> no semantics change, use default widening
+ // No Dst width change -> no semantics change, use default widening.
if (OriginalDstWidth == NewDstWidth)
continue;
- // Src width should be equal Dst width, use bigger of them
+ // DST will be widened - replace G_TRUNC with G_AND & mask to preserve
+ // truncation semantics.
+ // G_AND: Src's width should be equal Dst width, use bigger of them.
unsigned NewWidth = std::max(NewDstWidth, NewSrcWidth);
+ // TODO: Check dependencies, this Src widening can poison register size
+ // of other instructions.
if (OriginalSrcWidth != NewWidth)
MRI.setType(SrcReg, LLT::scalar(NewWidth));
- if (OriginalDstWidth == NewWidth)
- continue;
-
MRI.setType(DstReg, LLT::scalar(NewWidth));
- // DST was widened - replace G_TRUNC with G_AND & mask to preserve
- // truncation semantics.
MIB.setInsertPt(MBB, MI.getIterator());
APInt Mask = APInt::getLowBitsSet(NewWidth, OriginalDstWidth);
auto MaskReg = MIB.buildConstant(LLT::scalar(NewWidth), Mask);
MI.setDesc(ST->getInstrInfo()->get(TargetOpcode::G_AND));
- // MI.getOperand(1) is the same.
MI.addOperand(MachineOperand::CreateReg(MaskReg.getReg(0), false));
-
- // SRC0 will be correctly widened during general register widening.
}
}
}
>From 4263edd46ffb8adff2ae229ada4f21020a36d4d6 Mon Sep 17 00:00:00 2001
From: idubinov <igor.dubinov at amd.com>
Date: Tue, 14 Apr 2026 07:23:20 -0500
Subject: [PATCH 11/11] fix different size of dst and src
---
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp | 56 ++++++++++++-------
.../CodeGen/SPIRV/trunc-nonstd-bitwidth.ll | 43 +++++++-------
2 files changed, 54 insertions(+), 45 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index a2a9a737d17ac..9df5d18221a6d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -487,10 +487,24 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
ST->canUseExtension(SPIRV::Extension::SPV_INTEL_int4);
if (!IsExtendedInts) {
- // G_TRUNC rely on DST register size. The general register widening changes
- // G_TRUNC instruction behaviour in case of change of DSR register size.
- // Process widening of G_TRUNK separately, before general operators
- // widening.
+ // Without arbitrary precision integer extensions, SPIR-V only supports
+ // integer widths of 8, 16, 32, 64. Non-standard widths (e.g., i24, i40)
+ // must be widened to the next power of two.
+ //
+ // G_TRUNC requires special handling because its semantics depend on the
+ // original destination width. For example:
+ // %dst:s24 = G_TRUNC %src:s64
+ // After widening s24 to s32, we cannot simply do:
+ // %dst:s32 = G_TRUNC %src:s64
+ // because this would keep 32 bits instead of 24. Instead, we insert a
+ // G_AND to mask the value to the original width:
+ // %mask:s64 = G_CONSTANT 0xFFFFFF ; 24-bit mask
+ // %masked:s64 = G_AND %src:s64, %mask
+ // %dst:s32 = G_TRUNC %masked:s64
+ // If src and dst widen to the same size, G_TRUNC is replaced entirely:
+ // %mask:s64 = G_CONSTANT 0xFFFFFFFFFF ; 40-bit mask
+ // %dst:s64 = G_AND %src:s64, %mask
+ SmallVector<MachineInstr *, 8> TruncToRemove;
for (MachineBasicBlock &MBB : MF) {
for (MachineInstr &MI : MBB) {
unsigned MIOp = MI.getOpcode();
@@ -515,30 +529,30 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
unsigned NewDstWidth = widenBitWidthToNextPow2(OriginalDstWidth);
unsigned NewSrcWidth = widenBitWidthToNextPow2(OriginalSrcWidth);
- // No Dst width change -> no semantics change, use default widening.
+ // No Dst width change means no truncation semantics change.
if (OriginalDstWidth == NewDstWidth)
continue;
- // DST will be widened - replace G_TRUNC with G_AND & mask to preserve
- // truncation semantics.
- // G_AND: Src's width should be equal Dst width, use bigger of them.
- unsigned NewWidth = std::max(NewDstWidth, NewSrcWidth);
-
- // TODO: Check dependencies, this Src widening can change register size
- // of other instruction's Dst which can change behaviour.
- if (OriginalSrcWidth != NewWidth)
- MRI.setType(SrcReg, LLT::scalar(NewWidth));
-
- MRI.setType(DstReg, LLT::scalar(NewWidth));
+ MRI.setType(SrcReg, LLT::scalar(NewSrcWidth));
+ MRI.setType(DstReg, LLT::scalar(NewDstWidth));
MIB.setInsertPt(MBB, MI.getIterator());
- APInt Mask = APInt::getLowBitsSet(NewWidth, OriginalDstWidth);
- auto MaskReg = MIB.buildConstant(LLT::scalar(NewWidth), Mask);
-
- MI.setDesc(ST->getInstrInfo()->get(TargetOpcode::G_AND));
- MI.addOperand(MachineOperand::CreateReg(MaskReg.getReg(0), false));
+ APInt Mask = APInt::getLowBitsSet(NewSrcWidth, OriginalDstWidth);
+ auto MaskReg = MIB.buildConstant(LLT::scalar(NewSrcWidth), Mask);
+ Register MaskedReg =
+ MRI.createGenericVirtualRegister(LLT::scalar(NewSrcWidth));
+ MIB.buildAnd(MaskedReg, SrcReg, MaskReg);
+
+ if (NewSrcWidth == NewDstWidth) {
+ MRI.replaceRegWith(DstReg, MaskedReg);
+ TruncToRemove.push_back(&MI);
+ } else {
+ MI.getOperand(1).setReg(MaskedReg);
+ }
}
}
+ for (MachineInstr *MI : TruncToRemove)
+ MI->eraseFromParent();
}
for (MachineBasicBlock *MBB : post_order(&MF)) {
diff --git a/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll b/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
index 4952ad1956490..04d643624902b 100644
--- a/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
+++ b/llvm/test/CodeGen/SPIRV/trunc-nonstd-bitwidth.ll
@@ -13,31 +13,35 @@
; CHECK-EXT-DAG: %[[#Int40:]] = OpTypeInt 40 0
; CHECK-EXT-DAG: %[[#Int50:]] = OpTypeInt 50 0
; CHECK-EXT-DAG: %[[#Int24:]] = OpTypeInt 24 0
-; CHECK-DAG: %[[#Int32:]] = OpTypeInt 32 0
; CHECK-EXT-DAG: %[[#Int5:]] = OpTypeInt 5 0
-; CHECK-NOEXT-DAG: %[[#Int40:]] = OpTypeInt 64 0
+; CHECK-NOEXT-DAG: %[[#Int64:]] = OpTypeInt 64 0
+; CHECK-NOEXT-DAG: %[[#Int32:]] = OpTypeInt 32 0
+; CHECK-NOEXT-DAG: %[[#Int16:]] = OpTypeInt 16 0
+; CHECK-NOEXT-DAG: %[[#Int8:]] = OpTypeInt 8 0
-; expected i40 to be widened in i64, i64->i64 should be properly handled
+; Test i64 -> i40: both widen to i64, G_TRUNC replaced with G_AND
; CHECK: OpFunction
; CHECK: %[[#T1Arg:]] = OpFunctionParameter
; CHECK: %[[#T1Val:]] = OpFunctionParameter
; CHECK-EXT: %[[#T1Tr:]] = OpUConvert %[[#Int40]] %[[#T1Val]]
; CHECK-EXT: OpStore %[[#T1Arg]] %[[#T1Tr]]
-; CHECK-NOEXT: %[[#T1Noext:]] = OpBitwiseAnd %[[#]] %[[#T1Val]] %[[#]]
-; CHECK-NOEXT: OpStore %[[#T1Arg]] %[[#T1Noext]]
+; CHECK-NOEXT: %[[#T1And:]] = OpBitwiseAnd %[[#Int64]] %[[#T1Val]] %[[#]]
+; CHECK-NOEXT: OpStore %[[#T1Arg]] %[[#T1And]]
define spir_kernel void @trunc_i64_to_i40(ptr addrspace(1) %arg, i64 %val) {
%tr = trunc i64 %val to i40
store i40 %tr, ptr addrspace(1) %arg
ret void
}
+; Test i50 -> i24: src widens to i64, dst widens to i32, need OpUConvert after OpBitwiseAnd
; CHECK: OpFunction
; CHECK: %[[#T2Arg:]] = OpFunctionParameter
; CHECK: %[[#T2Val:]] = OpFunctionParameter
; CHECK-EXT: %[[#T2Tr:]] = OpUConvert %[[#Int24]] %[[#T2Val]]
; CHECK-EXT: OpStore %[[#T2Arg]] %[[#T2Tr]]
-; CHECK-NOEXT: %[[#T2Noext:]] = OpBitwiseAnd %[[#]] %[[#T2Val]] %[[#]]
-; CHECK-NOEXT: OpStore %[[#T2Arg]] %[[#T2Noext]]
+; CHECK-NOEXT: %[[#T2And:]] = OpBitwiseAnd %[[#Int64]] %[[#T2Val]] %[[#]]
+; CHECK-NOEXT: %[[#T2Conv:]] = OpUConvert %[[#Int32]] %[[#T2And]]
+; CHECK-NOEXT: OpStore %[[#T2Arg]] %[[#T2Conv]]
define spir_kernel void @trunc_i50_to_i24(ptr addrspace(1) %arg, i50 %val) {
%tr = trunc i50 %val to i24
store i24 %tr, ptr addrspace(1) %arg
@@ -45,41 +49,32 @@ define spir_kernel void @trunc_i50_to_i24(ptr addrspace(1) %arg, i50 %val) {
}
+; Test i64 -> i24: src stays i64, dst widens to i32, need OpUConvert after OpBitwiseAnd
; CHECK: OpFunction
; CHECK: %[[#T3Arg:]] = OpFunctionParameter
; CHECK: %[[#T3Val:]] = OpFunctionParameter
; CHECK-EXT: %[[#T3Tr:]] = OpUConvert %[[#Int24]] %[[#T3Val]]
; CHECK-EXT: OpStore %[[#T3Arg]] %[[#T3Tr]]
-; CHECK-NOEXT: %[[#T3Noext:]] = OpBitwiseAnd %[[#]] %[[#T3Val]] %[[#]]
-; CHECK-NOEXT: OpStore %[[#T3Arg]] %[[#T3Noext]]
+; CHECK-NOEXT: %[[#T3And:]] = OpBitwiseAnd %[[#Int64]] %[[#T3Val]] %[[#]]
+; CHECK-NOEXT: %[[#T3Conv:]] = OpUConvert %[[#Int32]] %[[#T3And]]
+; CHECK-NOEXT: OpStore %[[#T3Arg]] %[[#T3Conv]]
define spir_kernel void @trunc_i64_to_i24(ptr addrspace(1) %arg, i64 %val) {
%tr = trunc i64 %val to i24
store i24 %tr, ptr addrspace(1) %arg
ret void
}
-; In NOEXT mode, i5 widens to i8
+; Test i16 -> i5: src stays i16, dst widens to i8, need OpUConvert after OpBitwiseAnd
; CHECK: OpFunction
; CHECK: %[[#T4Arg:]] = OpFunctionParameter
; CHECK: %[[#T4Val:]] = OpFunctionParameter
; CHECK-EXT: %[[#T4Tr:]] = OpUConvert %[[#Int5]] %[[#T4Val]]
; CHECK-EXT: OpStore %[[#T4Arg]] %[[#T4Tr]]
-; CHECK-NOEXT: %[[#T4Noext:]] = OpBitwiseAnd %[[#]] %[[#T4Val]] %[[#]]
-; CHECK-NOEXT: OpStore %[[#T4Arg]] %[[#T4Noext]]
+; CHECK-NOEXT: %[[#T4And:]] = OpBitwiseAnd %[[#Int16]] %[[#T4Val]] %[[#]]
+; CHECK-NOEXT: %[[#T4Conv:]] = OpUConvert %[[#Int8]] %[[#T4And]]
+; CHECK-NOEXT: OpStore %[[#T4Arg]] %[[#T4Conv]]
define spir_kernel void @trunc_i16_to_i5(ptr addrspace(1) %arg, i16 %val) {
%tr = trunc i16 %val to i5
store i5 %tr, ptr addrspace(1) %arg
ret void
}
-
-; The result should be same in both cases - no widening
-; CHECK: OpFunction
-; CHECK: %[[#T5Arg:]] = OpFunctionParameter
-; CHECK: %[[#T5Val:]] = OpFunctionParameter
-; CHECK: %[[#T5Tr:]] = OpUConvert %[[#Int32]] %[[#T5Val]]
-; CHECK-EXT: OpStore %[[#T5Arg]] %[[#T5Tr]]
-define spir_kernel void @trunc_i64_to_i32(ptr addrspace(1) %arg, i64 %val) {
- %tr = trunc i64 %val to i32
- store i32 %tr, ptr addrspace(1) %arg
- ret void
-}
\ No newline at end of file
More information about the llvm-commits
mailing list