[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