[llvm] [SPIR-V] Handle ASM with multiple outputs (PR #187128)

Alex Voicu via llvm-commits llvm-commits at lists.llvm.org
Sun Apr 5 17:24:25 PDT 2026


https://github.com/AlexVlx updated https://github.com/llvm/llvm-project/pull/187128

>From f951184fb039c24a9b25b59399d5c1a28cef13a7 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Wed, 17 Dec 2025 01:34:41 +0000
Subject: [PATCH 01/21] Handle `COPY` when selecting `UtoPtr` as the
 `SpecConstantOp` op.

---
 llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp         | 7 ++++---
 .../CodeGen/SPIRV/transcoding/ConvertPtrInGlobalInit.ll    | 3 ++-
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 2e4563795e8f0..aae025602b763 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -1099,6 +1099,7 @@ bool SPIRVInstructionSelector::spvSelect(Register ResVReg,
              UseEnd = MRI->use_instr_end();
          UseIt != UseEnd; UseIt = std::next(UseIt)) {
       if ((*UseIt).getOpcode() == TargetOpcode::G_GLOBAL_VALUE ||
+          (*UseIt).getOpcode() == SPIRV::OpSpecConstantOp ||
           (*UseIt).getOpcode() == SPIRV::OpVariable) {
         IsGVInit = true;
         break;
@@ -1407,9 +1408,9 @@ bool SPIRVInstructionSelector::selectUnOp(Register ResVReg,
              MRI->def_instr_begin(SrcReg);
          DefIt != MRI->def_instr_end(); DefIt = std::next(DefIt)) {
       unsigned DefOpCode = DefIt->getOpcode();
-      if (DefOpCode == SPIRV::ASSIGN_TYPE) {
-        // We need special handling to look through the type assignment and see
-        // if this is a constant or a global
+      if (DefOpCode == SPIRV::ASSIGN_TYPE || DefOpCode == TargetOpcode::COPY) {
+        // We need special handling to look through the type assignment or the
+        // COPY pseudo-op and see if this is a constant or a global
         if (auto *VRD = getVRegDef(*MRI, DefIt->getOperand(1).getReg()))
           DefOpCode = VRD->getOpcode();
       }
diff --git a/llvm/test/CodeGen/SPIRV/transcoding/ConvertPtrInGlobalInit.ll b/llvm/test/CodeGen/SPIRV/transcoding/ConvertPtrInGlobalInit.ll
index f397030c7bdb1..23aaa5573a83c 100644
--- a/llvm/test/CodeGen/SPIRV/transcoding/ConvertPtrInGlobalInit.ll
+++ b/llvm/test/CodeGen/SPIRV/transcoding/ConvertPtrInGlobalInit.ll
@@ -12,11 +12,11 @@
 ; CHECK: %[[VtblTy:[0-9]+]] = OpTypeStruct %[[ArrTy]] %[[ArrTy]] %[[ArrTy]] %[[ArrTy]] %[[ArrTy]]
 ; CHECK: %[[Int64Ty:[0-9]+]] = OpTypeInt 64 0
 ; CHECK: %[[GlobVtblPtrTy:[0-9]+]] = OpTypePointer CrossWorkgroup %[[VtblTy]]
+; CHECK: %[[Const184:[0-9]+]] = OpConstant %[[Int64Ty]] 184
 ; CHECK: %[[ConstMinus184:[0-9]+]] = OpConstant %[[Int64Ty]] 18446744073709551432
 ; CHECK: %[[ConstMinus16:[0-9]+]] = OpConstant %[[Int64Ty]] 18446744073709551600
 ; CHECK: %[[Const168:[0-9]+]] = OpConstant %[[Int64Ty]] 168
 ; CHECK: %[[Nullptr:[0-9]+]] = OpConstantNull %[[GlobInt8PtrTy]]
-; CHECK: %[[Const184:[0-9]+]] = OpConstant %[[Int64Ty]] 184
 ; CHECK: %[[Const184toPtr:[0-9]+]] = OpSpecConstantOp %[[GlobInt8PtrTy]] ConvertUToPtr %[[Const184]]
 ; CHECK: %[[Const168toPtr:[0-9]+]] = OpSpecConstantOp %[[GlobInt8PtrTy]] ConvertUToPtr %[[Const168]]
 ; CHECK: %[[ConstMinus16toPtr:[0-9]+]] = OpSpecConstantOp %[[GlobInt8PtrTy]] ConvertUToPtr %[[ConstMinus16]]
@@ -33,6 +33,7 @@
                                                             [5 x ptr addrspace(1)] [ptr addrspace(1) inttoptr (i64 184 to ptr addrspace(1)), ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null],
                                                             [5 x ptr addrspace(1)] [ptr addrspace(1) inttoptr (i64 168 to ptr addrspace(1)), ptr addrspace(1) inttoptr (i64 -16 to ptr addrspace(1)), ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null],
                                                             [5 x ptr addrspace(1)] [ptr addrspace(1) inttoptr (i64 -184 to ptr addrspace(1)), ptr addrspace(1) inttoptr (i64 -184 to ptr addrspace(1)), ptr addrspace(1) null, ptr addrspace(1) null, ptr addrspace(1) null] }
+ at VTT = linkonce_odr unnamed_addr addrspace(1) constant [1 x ptr addrspace(1)][ptr addrspace(1) getelementptr inbounds inrange(-24, 16) ({ [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)], [5 x ptr addrspace(1)] }, ptr addrspace(1) @vtable, i32 0, i32 4, i32 3)]
 
 define linkonce_odr spir_func void @foo(ptr addrspace(4) %this) {
 entry:

>From 86cf09343456da3273e52d89592dbe23e792bc32 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Thu, 8 Jan 2026 14:33:07 +0000
Subject: [PATCH 02/21] Update
 llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp

Co-authored-by: Marcos Maronas <marcos.maronas at intel.com>
---
 llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 964c555883b85..100057f6d1a39 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -1412,7 +1412,7 @@ bool SPIRVInstructionSelector::selectUnOp(Register ResVReg,
       unsigned DefOpCode = DefIt->getOpcode();
       if (DefOpCode == SPIRV::ASSIGN_TYPE || DefOpCode == TargetOpcode::COPY) {
         // We need special handling to look through the type assignment or the
-        // COPY pseudo-op and see if this is a constant or a global
+        // COPY pseudo-op and see if this is a constant or a global.
         if (auto *VRD = getVRegDef(*MRI, DefIt->getOperand(1).getReg()))
           DefOpCode = VRD->getOpcode();
       }

>From 87a88c667d44c0996a391184b2930451d43ded69 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Sat, 10 Jan 2026 02:48:27 +0000
Subject: [PATCH 03/21] Sneak `externally_initialized` via `HostAccessINTEL`.

---
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 10 ++++++++
 .../CodeGen/SPIRV/externally-initialized.ll   | 25 +++++++++++++++++++
 2 files changed, 35 insertions(+)
 create mode 100644 llvm/test/CodeGen/SPIRV/externally-initialized.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 100057f6d1a39..265e9b49ae52d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -4791,6 +4791,16 @@ bool SPIRVInstructionSelector::selectGlobalValue(
   Register Reg = GR.buildGlobalVariable(
       ResVReg, ResType, GlobalIdent, GV, StorageClass, Init,
       GlobalVar->isConstant(), LnkType, MIRBuilder, true);
+  // TODO: For AMDGCN, we pipe externally_initialized through via
+  // HostAccessINTEL, with ReadWrite (3) access, which is we then handle during
+  // reverse translation. We should remove this once SPIR-V gains the ability to
+  // express the concept.
+  if (GlobalVar->isExternallyInitialized() &&
+      STI.getTargetTriple().getVendor() == Triple::AMD) {
+    buildOpDecorate(Reg, MIRBuilder, SPIRV::Decoration::HostAccessINTEL, {3u});
+    MachineInstrBuilder MIB(*MF, --MIRBuilder.getInsertPt());
+    addStringImm(GV->getName(), MIB);
+  }
   return Reg.isValid();
 }
 
diff --git a/llvm/test/CodeGen/SPIRV/externally-initialized.ll b/llvm/test/CodeGen/SPIRV/externally-initialized.ll
new file mode 100644
index 0000000000000..ac547417359d3
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/externally-initialized.ll
@@ -0,0 +1,25 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-amd-amdhsa %s -o - | FileCheck %s --check-prefix=CHECK-AMDGCNSPIRV
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-amd-amdhsa %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-SPIRV: OpName %[[#G:]] "G"
+; CHECK-SPIRV-NOT: OpDecorate %[[#G]] ReferencedIndirectlyINTEL
+; CHECK-SPIRV-DAG: %[[#G]] = OpVariable
+
+; CHECK-AMDGCNSPIRV: OpExtension "SPV_INTEL_global_variable_host_access"
+; CHECK-AMDGCNSPIRV: OpName %[[#G:]] "G"
+; CHECK-AMDGCNSPIRV: OpDecorate %[[#G]] HostAccessINTEL 3 "G"
+; CHECK-AMDGCNSPIRV-DAG: %[[#G]] = OpVariable
+
+
+ at G = external addrspace(1) externally_initialized global i32
+
+define spir_func i32 @foo() {
+  %r = load i32, ptr addrspace(1) @G
+  ret i32 %r
+}

>From 004c880ac2a51fc9628a0ac1f3801f2451c48dbe Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Mon, 12 Jan 2026 15:14:47 +0200
Subject: [PATCH 04/21] Apply review suggestion.

---
 llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 265e9b49ae52d..84813905df1fe 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -4797,7 +4797,8 @@ bool SPIRVInstructionSelector::selectGlobalValue(
   // express the concept.
   if (GlobalVar->isExternallyInitialized() &&
       STI.getTargetTriple().getVendor() == Triple::AMD) {
-    buildOpDecorate(Reg, MIRBuilder, SPIRV::Decoration::HostAccessINTEL, {3u});
+    constexpr unsigned ReadWriteINTEL = 3u;
+    buildOpDecorate(Reg, MIRBuilder, SPIRV::Decoration::HostAccessINTEL, {ReadWriteINTEL});
     MachineInstrBuilder MIB(*MF, --MIRBuilder.getInsertPt());
     addStringImm(GV->getName(), MIB);
   }

>From 8b1cdf0690a58aa405167126eea4e1f0a01538e6 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Mon, 12 Jan 2026 18:08:38 +0200
Subject: [PATCH 05/21] Fix formatting.

---
 llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 84813905df1fe..98b5bfd678135 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -4798,7 +4798,8 @@ bool SPIRVInstructionSelector::selectGlobalValue(
   if (GlobalVar->isExternallyInitialized() &&
       STI.getTargetTriple().getVendor() == Triple::AMD) {
     constexpr unsigned ReadWriteINTEL = 3u;
-    buildOpDecorate(Reg, MIRBuilder, SPIRV::Decoration::HostAccessINTEL, {ReadWriteINTEL});
+    buildOpDecorate(Reg, MIRBuilder, SPIRV::Decoration::HostAccessINTEL,
+                    {ReadWriteINTEL});
     MachineInstrBuilder MIB(*MF, --MIRBuilder.getInsertPt());
     addStringImm(GV->getName(), MIB);
   }

>From ead8ee95634ed8513c1464b69bb5f7d682fd327b Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Tue, 17 Mar 2026 20:55:47 +0000
Subject: [PATCH 06/21] Handle ASM with multiple outputs.

---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp |  6 +-
 llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp   | 79 +++++++++++--------
 .../Target/SPIRV/SPIRVPrepareFunctions.cpp    | 27 +++++--
 llvm/lib/Target/SPIRV/SPIRVUtils.cpp          | 33 ++++++++
 llvm/lib/Target/SPIRV/SPIRVUtils.h            |  3 +
 .../SPV_INTEL_inline_assembly/inline_asm.ll   | 17 ++++
 6 files changed, 123 insertions(+), 42 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 131b56e92b8be..b77cc6fc84d29 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -1634,11 +1634,11 @@ Instruction *SPIRVEmitIntrinsics::visitCallInst(CallInst &Call) {
   if (!Call.isInlineAsm())
     return &Call;
 
-  const InlineAsm *IA = cast<InlineAsm>(Call.getCalledOperand());
   LLVMContext &Ctx = CurrF->getContext();
 
-  Constant *TyC = UndefValue::get(IA->getFunctionType());
-  MDString *ConstraintString = MDString::get(Ctx, IA->getConstraintString());
+  Constant *TyC = UndefValue::get(SPIRV::getOriginalFunctionType(Call));
+  MDString *ConstraintString =
+      MDString::get(Ctx, SPIRV::getOriginalAsmConstraints(Call));
   SmallVector<Value *> Args = {
       buildMD(TyC),
       MetadataAsValue::get(Ctx, MDNode::get(Ctx, ConstraintString))};
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index aead1ce735c49..12a326235934e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -41,6 +41,12 @@ void SPIRVPreLegalizer::getAnalysisUsage(AnalysisUsage &AU) const {
   MachineFunctionPass::getAnalysisUsage(AU);
 }
 
+static inline void invalidateAndEraseMI(SPIRVGlobalRegistry *GR,
+                                        MachineInstr *MI) {
+  GR->invalidateMachineInstr(MI);
+  MI->eraseFromParent();
+}
+
 static void
 addConstantsToTrack(MachineFunction &MF, SPIRVGlobalRegistry *GR,
                     const SPIRVSubtarget &STI,
@@ -126,13 +132,10 @@ addConstantsToTrack(MachineFunction &MF, SPIRVGlobalRegistry *GR,
     if (!MRI.getRegClassOrNull(Reg) && RC)
       MRI.setRegClass(Reg, RC);
     MRI.replaceRegWith(MI->getOperand(0).getReg(), Reg);
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
-  for (MachineInstr *MI : ToEraseComposites) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
+    invalidateAndEraseMI(GR, MI);
   }
+  for (MachineInstr *MI : ToEraseComposites)
+    invalidateAndEraseMI(GR, MI);
 }
 
 static void foldConstantsIntoIntrinsics(MachineFunction &MF,
@@ -151,10 +154,8 @@ static void foldConstantsIntoIntrinsics(MachineFunction &MF,
       }
       ToErase.push_back(&MI);
     }
-    for (MachineInstr *MI : ToErase) {
-      GR->invalidateMachineInstr(MI);
-      MI->eraseFromParent();
-    }
+    for (MachineInstr *MI : ToErase)
+      invalidateAndEraseMI(GR, MI);
     ToErase.clear();
   }
 }
@@ -235,10 +236,8 @@ static void lowerBitcasts(MachineFunction &MF, SPIRVGlobalRegistry *GR,
       ToErase.push_back(&MI);
     }
   }
-  for (MachineInstr *MI : ToErase) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
+  for (MachineInstr *MI : ToErase)
+    invalidateAndEraseMI(GR, MI);
 }
 
 static void insertBitcasts(MachineFunction &MF, SPIRVGlobalRegistry *GR,
@@ -279,10 +278,8 @@ static void insertBitcasts(MachineFunction &MF, SPIRVGlobalRegistry *GR,
       }
     }
   }
-  for (MachineInstr *MI : ToErase) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
+  for (MachineInstr *MI : ToErase)
+    invalidateAndEraseMI(GR, MI);
 }
 
 // Translating GV, IRTranslator sometimes generates following IR:
@@ -623,8 +620,7 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
     auto It = RegsAlreadyAddedToDT.find(MI);
     if (It != RegsAlreadyAddedToDT.end())
       MRI.replaceRegWith(MI->getOperand(0).getReg(), It->second);
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
+    invalidateAndEraseMI(GR, MI);
   }
 
   // Address the case when IRTranslator introduces instructions with new
@@ -759,11 +755,31 @@ insertInlineAsmProcess(MachineFunction &MF, SPIRVGlobalRegistry *GR,
                        .addUse(AsmReg);
     for (unsigned IntrIdx = 3; IntrIdx < I1->getNumOperands(); ++IntrIdx)
       AsmCall.addUse(I1->getOperand(IntrIdx).getReg());
+
+    // IRTranslator gets a bit confused when lowering inline ASM with multiple,
+    // outputs (which we have to spoof as single i32 return), and inserts a
+    // spurious COPY & TRUNC as registers are assumed to be i64; we have to
+    // clean that up here to prevent an erroneous cast on a struct to get
+    // lowered into SPIR-V
+    if (FTy->getReturnType()->isStructTy()) {
+      MachineInstr &Copy = *++I2->getIterator();
+      MachineInstr &Trunc = *++Copy.getIterator();
+
+      assert(Copy.isCopy() && Trunc.getOpcode() == TargetOpcode::G_TRUNC &&
+             "Unexpected successors to inline ASM with multiple outputs!");
+
+      Register TruncReg = Trunc.defs().begin()->getReg();
+      MRI.replaceRegWith(TruncReg, DefReg);
+      // for (auto It = ++Trunc.getIterator();
+      //      isSpvIntrinsic(*It, Intrinsic::spv_extractv); ++It)
+      //   if (It->getOperand(2).getReg() == TruncReg)
+      //     It->getOperand(2).setReg(DefReg);
+      invalidateAndEraseMI(GR, &Trunc);
+      invalidateAndEraseMI(GR, &Copy);
+    }
   }
-  for (MachineInstr *MI : ToProcess) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
+  for (MachineInstr *MI : ToProcess)
+    invalidateAndEraseMI(GR, MI);
 }
 
 static void insertInlineAsm(MachineFunction &MF, SPIRVGlobalRegistry *GR,
@@ -830,10 +846,8 @@ static void insertSpirvDecorations(MachineFunction &MF, SPIRVGlobalRegistry *GR,
       ToErase.push_back(&MI);
     }
   }
-  for (MachineInstr *MI : ToErase) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
+  for (MachineInstr *MI : ToErase)
+    invalidateAndEraseMI(GR, MI);
 }
 
 // LLVM allows the switches to use registers as cases, while SPIR-V required
@@ -883,10 +897,8 @@ static void cleanupHelperInstructions(MachineFunction &MF,
     }
   }
 
-  for (MachineInstr *MI : ToEraseMI) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
+  for (MachineInstr *MI : ToEraseMI)
+    invalidateAndEraseMI(GR, MI);
 }
 
 // Find all usages of G_BLOCK_ADDR in our intrinsics and replace those
@@ -988,8 +1000,7 @@ static void processBlockAddr(MachineFunction &MF, SPIRVGlobalRegistry *GR,
           ConstantExpr::getIntToPtr(Replacement, BA->getType()));
       BA->destroyConstant();
     }
-    GR->invalidateMachineInstr(BlockAddrI);
-    BlockAddrI->eraseFromParent();
+    invalidateAndEraseMI(GR, BlockAddrI);
   }
 }
 
diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 06b39800d2f06..61c517af82972 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -507,7 +507,8 @@ bool SPIRVPrepareFunctions::substituteIntrinsicCalls(Function *F) {
 static void
 addFunctionTypeMutation(NamedMDNode *NMD,
                         SmallVector<std::pair<int, Type *>> ChangedTys,
-                        StringRef Name) {
+                        StringRef Name,
+                        StringRef AsmConstraints = "") {
 
   LLVMContext &Ctx = NMD->getParent()->getContext();
   Type *I32Ty = IntegerType::getInt32Ty(Ctx);
@@ -519,8 +520,11 @@ addFunctionTypeMutation(NamedMDNode *NMD,
         Ctx, {ConstantAsMetadata::get(ConstantInt::get(I32Ty, CTy.first, true)),
               ValueAsMetadata::get(Constant::getNullValue(CTy.second))});
   });
+  if (!AsmConstraints.empty())
+    MDArgs.push_back(MDNode::get(Ctx, MDString::get(Ctx, AsmConstraints)));
   NMD->addOperand(MDNode::get(Ctx, MDArgs));
 }
+
 // Returns F if aggregate argument/return types are not present or cloned F
 // function with the types replaced by i32 types. The change in types is
 // noted in 'spv.cloned_funcs' metadata for later restoration.
@@ -592,9 +596,11 @@ SPIRVPrepareFunctions::removeAggregateTypesFromSignature(Function *F) {
   return NewF;
 }
 
-// Mutates indirect callsites iff if aggregate argument/return types are present
-// with the types replaced by i32 types. The change in types is noted in
-// 'spv.mutated_callsites' metadata for later restoration.
+// Mutates indirect and inline ASM callsites iff if aggregate argument/return
+// types are present with the types replaced by i32 types. The change in types
+// is noted in 'spv.mutated_callsites' metadata for later restoration. For ASM
+// we also have to mutate the constraint string as IRTranslator tries to handle
+// multiple outputs and expects an aggregate return type in their presence.
 bool SPIRVPrepareFunctions::removeAggregateTypesFromCalls(Function *F) {
   if (F->isDeclaration() || F->isIntrinsic())
     return false;
@@ -643,9 +649,20 @@ bool SPIRVPrepareFunctions::removeAggregateTypesFromCalls(Function *F) {
       CB->setName("spv.named_mutated_callsite." + F->getName() + "." +
                   CB->getName());
 
+    StringRef MaybeConstraints;
+    if (auto *ASM = dyn_cast<InlineAsm>(CB->getCalledOperand())) {
+      MaybeConstraints = ASM->getConstraintString();
+      // We should only have one =r return for the made up ASM type.
+      CB->setCalledOperand(InlineAsm::get(
+          NewFnTy, ASM->getAsmString(),
+          ASM->getConstraintString().substr(MaybeConstraints.find_last_of('=')),
+          ASM->hasSideEffects(), ASM->isAlignStack(), ASM->getDialect(),
+          ASM->canThrow()));
+    }
+
     addFunctionTypeMutation(
         F->getParent()->getOrInsertNamedMetadata("spv.mutated_callsites"),
-        std::move(ChangedTypes), CB->getName());
+        std::move(ChangedTypes), CB->getName(), MaybeConstraints);
   }
 
   for (auto &&[CB, NewFTy] : Calls) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 4a25886614098..25726b5d0ea9d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -79,6 +79,32 @@ static FunctionType *extractFunctionTypeFromMetadata(NamedMDNode *NMD,
   return FunctionType::get(RetTy, PTys, FTy->isVarArg());
 }
 
+static StringRef extractAsmConstraintsFromMetadata(NamedMDNode *NMD,
+                                                   StringRef Constraints,
+                                                   StringRef Name) {
+  // TODO: unify the extractors.
+  if (!NMD)
+    return Constraints;
+
+  auto It = find_if(NMD->operands(), [Name](MDNode *N) {
+    if (auto *MDS = dyn_cast_or_null<MDString>(N->getOperand(0)))
+      return MDS->getString() == Name;
+    return false;
+  });
+
+  if (It == NMD->op_end())
+    return Constraints;
+
+  // By convention, the constraints string is stored in the final MD operand.
+  MDNode *MD = dyn_cast<MDNode>((*It)->getOperand((*It)->getNumOperands() - 1));
+  assert(MD && "MDNode operand is expected");
+
+  if (auto *MDS = dyn_cast<MDString>(MD->getOperand(0)))
+    Constraints = MDS->getString();
+
+  return Constraints;
+}
+
 FunctionType *getOriginalFunctionType(const Function &F) {
   return extractFunctionTypeFromMetadata(
       F.getParent()->getNamedMetadata("spv.cloned_funcs"), F.getFunctionType(),
@@ -90,6 +116,13 @@ FunctionType *getOriginalFunctionType(const CallBase &CB) {
       CB.getModule()->getNamedMetadata("spv.mutated_callsites"),
       CB.getFunctionType(), CB.getName());
 }
+
+StringRef getOriginalAsmConstraints(const CallBase &CB) {
+  return extractAsmConstraintsFromMetadata(
+    CB.getModule()->getNamedMetadata("spv.mutated_callsites"),
+    cast<InlineAsm>(CB.getCalledOperand())->getConstraintString(),
+    CB.getName());
+}
 } // Namespace SPIRV
 
 // The following functions are used to add these string literals as a series of
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index d541ead5ac22c..c7b807f97d13c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -167,6 +167,9 @@ struct FPFastMathDefaultInfoVector
 // during the translation to cope with aggregate flattening etc.
 FunctionType *getOriginalFunctionType(const Function &F);
 FunctionType *getOriginalFunctionType(const CallBase &CB);
+// This handles retrieving the original ASM constraints, which we had to spoof
+// into having a single output.
+StringRef getOriginalAsmConstraints(const CallBase &CB);
 } // namespace SPIRV
 
 // Add the given string as a series of integer operand, inserting null
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
index 91286d5bf32e8..805669e6eae3b 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
@@ -16,6 +16,7 @@
 ; CHECK-DAG: %[[#HalfTy:]] = OpTypeFloat 16
 ; CHECK-DAG: %[[#FloatTy:]] = OpTypeFloat 32
 ; CHECK-DAG: %[[#DoubleTy:]] = OpTypeFloat 64
+; CHECK-DAG: %[[#StructTy:]] = OpTypeStruct %[[#Int32Ty]] %[[#FloatTy]] %[[#HalfTy]]
 
 ; CHECK-DAG: OpTypeFunction %[[#VoidTy]] %[[#]] %[[#]] %[[#]] %[[#Int64Ty]]
 ; CHECK-DAG: %[[#Fun1Ty:]] = OpTypeFunction %[[#VoidTy]]
@@ -26,6 +27,7 @@
 ; CHECK-DAG: %[[#Fun6Ty:]] = OpTypeFunction %[[#Int8Ty]] %[[#FloatTy]] %[[#Int32Ty]] %[[#Int8Ty]]
 ; CHECK-DAG: %[[#Fun7Ty:]] = OpTypeFunction %[[#Int64Ty]] %[[#Int64Ty]] %[[#Int32Ty]] %[[#Int8Ty]]
 ; CHECK-DAG: %[[#Fun8Ty:]] = OpTypeFunction %[[#VoidTy]] %[[#Int32Ty]] %[[#DoubleTy]]
+; CHECK-DAG: %[[#Fun9Ty:]] = OpTypeFunction %[[#StructTy]] %[[#Int32Ty]] %[[#FloatTy]] %[[#HalfTy]]
 
 ; CHECK-DAG: %[[#Const2:]] = OpConstant %[[#FloatTy]] 2
 ; CHECK-DAG: %[[#Const123:]] = OpConstant %[[#Int32Ty]] 123
@@ -45,6 +47,7 @@
 ; CHECK-DAG: %[[#Asm9:]] = OpAsmINTEL %[[#Int64Ty]] %[[#Fun7Ty]] %[[#Dialect]] "icmdext $0 $3 $1 $2" "=r,r,r,r"
 ; CHECK-DAG: %[[#Asm10:]] = OpAsmINTEL %[[#VoidTy]] %[[#Fun8Ty]] %[[#Dialect]] "constcmd $0 $1" "r,r"
 ; CHECK-DAG: %[[#Asm11:]] = OpAsmINTEL %[[#VoidTy]] %[[#Fun8Ty]] %[[#Dialect]] "constcmd $0 $1" "i,i"
+; CHECK-DAG: %[[#Asm12:]] = OpAsmINTEL %[[#StructTy]] %[[#Fun9Ty]] %[[#Dialect]] "cmdext $0 $4 $5\n cmdext $2 $5 $6\n cmdext $3 $4 $6" "=&r,=&r,=&r,r,r,r"
 ; CHECK-NO: OpAsmINTEL
 
 ; CHECK: OpFunction
@@ -59,8 +62,14 @@
 ; CHECK: OpAsmCallINTEL %[[#Int64Ty]] %[[#Asm9]] %[[#]] %[[#]] %[[#]]
 ; CHECK: OpAsmCallINTEL %[[#VoidTy]] %[[#Asm10]] %[[#Const123]] %[[#Const42]]
 ; CHECK: OpAsmCallINTEL %[[#VoidTy]] %[[#Asm11]] %[[#Const123]] %[[#Const42]]
+; CHECK: %[[#StructRet:]] = OpAsmCallINTEL %[[#StructTy]] %[[#Asm12]]
+; CHECK-NEXT: OpCompositeExtract %[[#Int32Ty]] %[[#StructRet]] 0
+; CHECK-NEXT: OpCompositeExtract %[[#FloatTy]] %[[#StructRet]] 1
+; CHECK-NEXT: OpCompositeExtract %[[#HalfTy]] %[[#StructRet]] 2
 ; CHECK-NO: OpAsmCallINTEL
 
+target triple = "spirv64-unknown-unknown"
+
 define spir_kernel void @foo(ptr addrspace(1) %_arg_int, ptr addrspace(1) %_arg_float, ptr addrspace(1) %_arg_half, i64 %_lng) {
   %i1 = load i32, ptr addrspace(1) %_arg_int
   %i2 = load i8, ptr addrspace(1) %_arg_int
@@ -89,5 +98,13 @@ define spir_kernel void @foo(ptr addrspace(1) %_arg_int, ptr addrspace(1) %_arg_
   ; inline asm: constant arguments, misc constraints
   call void asm "constcmd $0 $1", "r,r"(i32 123, double 42.0)
   call void asm "constcmd $0 $1", "i,i"(i32 123, double 42.0)
+  ; inline asm: multiple outputs, hence aggregate return
+  %res_struct = call { i32, float, half } asm sideeffect "cmdext $0 $4 $5\0A cmdext $2 $5 $6\0A cmdext $3 $4 $6", "=&r,=&r,=&r,r,r,r"(i32 %i1, float %f1, half %h1)
+  %asmresult = extractvalue { i32, float, half } %res_struct, 0
+  %asmresult3 = extractvalue { i32, float, half } %res_struct, 1
+  %asmresult4 = extractvalue { i32, float, half } %res_struct, 2
+  store i32 %asmresult, ptr addrspace(1) %_arg_int
+  store float %asmresult3, ptr addrspace(1) %_arg_float
+  store half %asmresult4, ptr addrspace(1) %_arg_half
   ret void
 }

>From 75a092f4c23670693b80cd8883ce8c7ad1c4ea85 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Tue, 17 Mar 2026 21:03:40 +0000
Subject: [PATCH 07/21] Fix formatting.

---
 llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp | 3 +--
 llvm/lib/Target/SPIRV/SPIRVUtils.cpp            | 6 +++---
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 61c517af82972..f2c436798083f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -507,8 +507,7 @@ bool SPIRVPrepareFunctions::substituteIntrinsicCalls(Function *F) {
 static void
 addFunctionTypeMutation(NamedMDNode *NMD,
                         SmallVector<std::pair<int, Type *>> ChangedTys,
-                        StringRef Name,
-                        StringRef AsmConstraints = "") {
+                        StringRef Name, StringRef AsmConstraints = "") {
 
   LLVMContext &Ctx = NMD->getParent()->getContext();
   Type *I32Ty = IntegerType::getInt32Ty(Ctx);
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 25726b5d0ea9d..be91650b59654 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -119,9 +119,9 @@ FunctionType *getOriginalFunctionType(const CallBase &CB) {
 
 StringRef getOriginalAsmConstraints(const CallBase &CB) {
   return extractAsmConstraintsFromMetadata(
-    CB.getModule()->getNamedMetadata("spv.mutated_callsites"),
-    cast<InlineAsm>(CB.getCalledOperand())->getConstraintString(),
-    CB.getName());
+      CB.getModule()->getNamedMetadata("spv.mutated_callsites"),
+      cast<InlineAsm>(CB.getCalledOperand())->getConstraintString(),
+      CB.getName());
 }
 } // Namespace SPIRV
 

>From 341c285258444edb4b14b6452947b0272311ce1e Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Wed, 18 Mar 2026 12:43:14 +0000
Subject: [PATCH 08/21] Update llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Juan Manuel Martinez Caamaño <jmartinezcaamao at gmail.com>
---
 llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index f2c436798083f..350fee89614ef 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -654,7 +654,7 @@ bool SPIRVPrepareFunctions::removeAggregateTypesFromCalls(Function *F) {
       // We should only have one =r return for the made up ASM type.
       CB->setCalledOperand(InlineAsm::get(
           NewFnTy, ASM->getAsmString(),
-          ASM->getConstraintString().substr(MaybeConstraints.find_last_of('=')),
+          MaybeConstraints.substr(MaybeConstraints.find_last_of('=')),
           ASM->hasSideEffects(), ASM->isAlignStack(), ASM->getDialect(),
           ASM->canThrow()));
     }

>From e0f029189cf086619337ca56f87541d2dfd863c0 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Thu, 19 Mar 2026 00:57:37 +0000
Subject: [PATCH 09/21] Remove leftover.

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

diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 12a326235934e..456a618effdc2 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -770,10 +770,6 @@ insertInlineAsmProcess(MachineFunction &MF, SPIRVGlobalRegistry *GR,
 
       Register TruncReg = Trunc.defs().begin()->getReg();
       MRI.replaceRegWith(TruncReg, DefReg);
-      // for (auto It = ++Trunc.getIterator();
-      //      isSpvIntrinsic(*It, Intrinsic::spv_extractv); ++It)
-      //   if (It->getOperand(2).getReg() == TruncReg)
-      //     It->getOperand(2).setReg(DefReg);
       invalidateAndEraseMI(GR, &Trunc);
       invalidateAndEraseMI(GR, &Copy);
     }

>From d440d1aaab3d3c0fd3b26557099cf19f3597fae7 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Thu, 19 Mar 2026 00:59:02 +0000
Subject: [PATCH 10/21] Remove more leftovers.

---
 .../SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll    | 2 --
 1 file changed, 2 deletions(-)

diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
index 805669e6eae3b..ff6b6dec42ff3 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
@@ -68,8 +68,6 @@
 ; CHECK-NEXT: OpCompositeExtract %[[#HalfTy]] %[[#StructRet]] 2
 ; CHECK-NO: OpAsmCallINTEL
 
-target triple = "spirv64-unknown-unknown"
-
 define spir_kernel void @foo(ptr addrspace(1) %_arg_int, ptr addrspace(1) %_arg_float, ptr addrspace(1) %_arg_half, i64 %_lng) {
   %i1 = load i32, ptr addrspace(1) %_arg_int
   %i2 = load i8, ptr addrspace(1) %_arg_int

>From ae59e40b87874318e03a54f5e0b8f430f82ef3ce Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Fri, 20 Mar 2026 23:00:10 +0200
Subject: [PATCH 11/21] Generalise constraint string mutation to handle
 indirect operands.

---
 .../Target/SPIRV/SPIRVPrepareFunctions.cpp    | 29 ++++++++++++++-----
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 350fee89614ef..1533e0f8e472d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -595,6 +595,21 @@ SPIRVPrepareFunctions::removeAggregateTypesFromSignature(Function *F) {
   return NewF;
 }
 
+static std::string fixMultiOutputConstraintString(StringRef Constraints) {
+  // We should only have one =r return for the made up ASM type.
+  SmallVector<StringRef> Tmp;
+  SplitString(Constraints, Tmp, ",");
+  std::string SafeConstraints("=r,");
+  for (unsigned I = 0u; I != Tmp.size() - 1; ++I) {
+    if (Tmp[I].starts_with('=') && isalnum(Tmp[I][1]))
+      continue;
+    SafeConstraints.append(Tmp[I]).append({','});
+  }
+  SafeConstraints.append(Tmp.back());
+
+  return SafeConstraints;
+}
+
 // Mutates indirect and inline ASM callsites iff if aggregate argument/return
 // types are present with the types replaced by i32 types. The change in types
 // is noted in 'spv.mutated_callsites' metadata for later restoration. For ASM
@@ -648,20 +663,18 @@ bool SPIRVPrepareFunctions::removeAggregateTypesFromCalls(Function *F) {
       CB->setName("spv.named_mutated_callsite." + F->getName() + "." +
                   CB->getName());
 
-    StringRef MaybeConstraints;
+    std::string Constraints;
     if (auto *ASM = dyn_cast<InlineAsm>(CB->getCalledOperand())) {
-      MaybeConstraints = ASM->getConstraintString();
-      // We should only have one =r return for the made up ASM type.
+      Constraints = fixMultiOutputConstraintString(ASM->getConstraintString());
+
       CB->setCalledOperand(InlineAsm::get(
-          NewFnTy, ASM->getAsmString(),
-          MaybeConstraints.substr(MaybeConstraints.find_last_of('=')),
-          ASM->hasSideEffects(), ASM->isAlignStack(), ASM->getDialect(),
-          ASM->canThrow()));
+          NewFnTy, ASM->getAsmString(), Constraints, ASM->hasSideEffects(),
+          ASM->isAlignStack(), ASM->getDialect(), ASM->canThrow()));
     }
 
     addFunctionTypeMutation(
         F->getParent()->getOrInsertNamedMetadata("spv.mutated_callsites"),
-        std::move(ChangedTypes), CB->getName(), MaybeConstraints);
+        std::move(ChangedTypes), CB->getName(), Constraints);
   }
 
   for (auto &&[CB, NewFTy] : Calls) {

>From 23c5cafecae2e38aafbe19b724d5de3980529bba Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Fri, 20 Mar 2026 23:04:18 +0200
Subject: [PATCH 12/21] Handle memory constraints.

---
 llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp b/llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp
index 2fcb71939322a..4469e3943204c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp
@@ -136,8 +136,12 @@ TargetLowering::ConstraintType
 SPIRVTargetLowering::getConstraintType(StringRef Constraint) const {
   // SPIR-V represents inline assembly via OpAsmINTEL where constraints are
   // passed through as literals defined by client API. Return C_RegisterClass
-  // for any constraint since SPIR-V does not distinguish between register,
-  // immediate, or memory operands at this level.
+  // for non-memory constraints since SPIR-V does not distinguish between register,
+  // immediate, or memory operands at this level. We do have to return C_Memory
+  // for memory constraints as otherwise IRTranslator gets confused trying to
+  // allocate registers for them.
+  if (Constraint == "m")
+    return C_Memory;
   return C_RegisterClass;
 }
 

>From d4ef5c16d1c1baffb640a69ff5daef7d812e7fb8 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Tue, 17 Mar 2026 20:55:47 +0000
Subject: [PATCH 13/21] Handle ASM with multiple outputs.

---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp |  6 +-
 llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp   | 79 +++++++++++--------
 .../Target/SPIRV/SPIRVPrepareFunctions.cpp    | 27 +++++--
 llvm/lib/Target/SPIRV/SPIRVUtils.cpp          | 33 ++++++++
 llvm/lib/Target/SPIRV/SPIRVUtils.h            |  3 +
 .../SPV_INTEL_inline_assembly/inline_asm.ll   | 17 ++++
 6 files changed, 123 insertions(+), 42 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 9c2af100ad64e..3857c7248ca54 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -1661,11 +1661,11 @@ Instruction *SPIRVEmitIntrinsics::visitCallInst(CallInst &Call) {
   if (!Call.isInlineAsm())
     return &Call;
 
-  const InlineAsm *IA = cast<InlineAsm>(Call.getCalledOperand());
   LLVMContext &Ctx = CurrF->getContext();
 
-  Constant *TyC = UndefValue::get(IA->getFunctionType());
-  MDString *ConstraintString = MDString::get(Ctx, IA->getConstraintString());
+  Constant *TyC = UndefValue::get(SPIRV::getOriginalFunctionType(Call));
+  MDString *ConstraintString =
+      MDString::get(Ctx, SPIRV::getOriginalAsmConstraints(Call));
   SmallVector<Value *> Args = {
       buildMD(TyC),
       MetadataAsValue::get(Ctx, MDNode::get(Ctx, ConstraintString))};
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index aead1ce735c49..12a326235934e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -41,6 +41,12 @@ void SPIRVPreLegalizer::getAnalysisUsage(AnalysisUsage &AU) const {
   MachineFunctionPass::getAnalysisUsage(AU);
 }
 
+static inline void invalidateAndEraseMI(SPIRVGlobalRegistry *GR,
+                                        MachineInstr *MI) {
+  GR->invalidateMachineInstr(MI);
+  MI->eraseFromParent();
+}
+
 static void
 addConstantsToTrack(MachineFunction &MF, SPIRVGlobalRegistry *GR,
                     const SPIRVSubtarget &STI,
@@ -126,13 +132,10 @@ addConstantsToTrack(MachineFunction &MF, SPIRVGlobalRegistry *GR,
     if (!MRI.getRegClassOrNull(Reg) && RC)
       MRI.setRegClass(Reg, RC);
     MRI.replaceRegWith(MI->getOperand(0).getReg(), Reg);
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
-  for (MachineInstr *MI : ToEraseComposites) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
+    invalidateAndEraseMI(GR, MI);
   }
+  for (MachineInstr *MI : ToEraseComposites)
+    invalidateAndEraseMI(GR, MI);
 }
 
 static void foldConstantsIntoIntrinsics(MachineFunction &MF,
@@ -151,10 +154,8 @@ static void foldConstantsIntoIntrinsics(MachineFunction &MF,
       }
       ToErase.push_back(&MI);
     }
-    for (MachineInstr *MI : ToErase) {
-      GR->invalidateMachineInstr(MI);
-      MI->eraseFromParent();
-    }
+    for (MachineInstr *MI : ToErase)
+      invalidateAndEraseMI(GR, MI);
     ToErase.clear();
   }
 }
@@ -235,10 +236,8 @@ static void lowerBitcasts(MachineFunction &MF, SPIRVGlobalRegistry *GR,
       ToErase.push_back(&MI);
     }
   }
-  for (MachineInstr *MI : ToErase) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
+  for (MachineInstr *MI : ToErase)
+    invalidateAndEraseMI(GR, MI);
 }
 
 static void insertBitcasts(MachineFunction &MF, SPIRVGlobalRegistry *GR,
@@ -279,10 +278,8 @@ static void insertBitcasts(MachineFunction &MF, SPIRVGlobalRegistry *GR,
       }
     }
   }
-  for (MachineInstr *MI : ToErase) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
+  for (MachineInstr *MI : ToErase)
+    invalidateAndEraseMI(GR, MI);
 }
 
 // Translating GV, IRTranslator sometimes generates following IR:
@@ -623,8 +620,7 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
     auto It = RegsAlreadyAddedToDT.find(MI);
     if (It != RegsAlreadyAddedToDT.end())
       MRI.replaceRegWith(MI->getOperand(0).getReg(), It->second);
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
+    invalidateAndEraseMI(GR, MI);
   }
 
   // Address the case when IRTranslator introduces instructions with new
@@ -759,11 +755,31 @@ insertInlineAsmProcess(MachineFunction &MF, SPIRVGlobalRegistry *GR,
                        .addUse(AsmReg);
     for (unsigned IntrIdx = 3; IntrIdx < I1->getNumOperands(); ++IntrIdx)
       AsmCall.addUse(I1->getOperand(IntrIdx).getReg());
+
+    // IRTranslator gets a bit confused when lowering inline ASM with multiple,
+    // outputs (which we have to spoof as single i32 return), and inserts a
+    // spurious COPY & TRUNC as registers are assumed to be i64; we have to
+    // clean that up here to prevent an erroneous cast on a struct to get
+    // lowered into SPIR-V
+    if (FTy->getReturnType()->isStructTy()) {
+      MachineInstr &Copy = *++I2->getIterator();
+      MachineInstr &Trunc = *++Copy.getIterator();
+
+      assert(Copy.isCopy() && Trunc.getOpcode() == TargetOpcode::G_TRUNC &&
+             "Unexpected successors to inline ASM with multiple outputs!");
+
+      Register TruncReg = Trunc.defs().begin()->getReg();
+      MRI.replaceRegWith(TruncReg, DefReg);
+      // for (auto It = ++Trunc.getIterator();
+      //      isSpvIntrinsic(*It, Intrinsic::spv_extractv); ++It)
+      //   if (It->getOperand(2).getReg() == TruncReg)
+      //     It->getOperand(2).setReg(DefReg);
+      invalidateAndEraseMI(GR, &Trunc);
+      invalidateAndEraseMI(GR, &Copy);
+    }
   }
-  for (MachineInstr *MI : ToProcess) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
+  for (MachineInstr *MI : ToProcess)
+    invalidateAndEraseMI(GR, MI);
 }
 
 static void insertInlineAsm(MachineFunction &MF, SPIRVGlobalRegistry *GR,
@@ -830,10 +846,8 @@ static void insertSpirvDecorations(MachineFunction &MF, SPIRVGlobalRegistry *GR,
       ToErase.push_back(&MI);
     }
   }
-  for (MachineInstr *MI : ToErase) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
+  for (MachineInstr *MI : ToErase)
+    invalidateAndEraseMI(GR, MI);
 }
 
 // LLVM allows the switches to use registers as cases, while SPIR-V required
@@ -883,10 +897,8 @@ static void cleanupHelperInstructions(MachineFunction &MF,
     }
   }
 
-  for (MachineInstr *MI : ToEraseMI) {
-    GR->invalidateMachineInstr(MI);
-    MI->eraseFromParent();
-  }
+  for (MachineInstr *MI : ToEraseMI)
+    invalidateAndEraseMI(GR, MI);
 }
 
 // Find all usages of G_BLOCK_ADDR in our intrinsics and replace those
@@ -988,8 +1000,7 @@ static void processBlockAddr(MachineFunction &MF, SPIRVGlobalRegistry *GR,
           ConstantExpr::getIntToPtr(Replacement, BA->getType()));
       BA->destroyConstant();
     }
-    GR->invalidateMachineInstr(BlockAddrI);
-    BlockAddrI->eraseFromParent();
+    invalidateAndEraseMI(GR, BlockAddrI);
   }
 }
 
diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index a3b44ad6d31d5..9645f6a6e802d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -510,7 +510,8 @@ bool SPIRVPrepareFunctions::substituteIntrinsicCalls(Function *F) {
 static void
 addFunctionTypeMutation(NamedMDNode *NMD,
                         SmallVector<std::pair<int, Type *>> ChangedTys,
-                        StringRef Name) {
+                        StringRef Name,
+                        StringRef AsmConstraints = "") {
 
   LLVMContext &Ctx = NMD->getParent()->getContext();
   Type *I32Ty = IntegerType::getInt32Ty(Ctx);
@@ -522,8 +523,11 @@ addFunctionTypeMutation(NamedMDNode *NMD,
         Ctx, {ConstantAsMetadata::get(ConstantInt::get(I32Ty, CTy.first, true)),
               ValueAsMetadata::get(Constant::getNullValue(CTy.second))});
   });
+  if (!AsmConstraints.empty())
+    MDArgs.push_back(MDNode::get(Ctx, MDString::get(Ctx, AsmConstraints)));
   NMD->addOperand(MDNode::get(Ctx, MDArgs));
 }
+
 // Returns F if aggregate argument/return types are not present or cloned F
 // function with the types replaced by i32 types. The change in types is
 // noted in 'spv.cloned_funcs' metadata for later restoration.
@@ -595,9 +599,11 @@ SPIRVPrepareFunctions::removeAggregateTypesFromSignature(Function *F) {
   return NewF;
 }
 
-// Mutates indirect callsites iff if aggregate argument/return types are present
-// with the types replaced by i32 types. The change in types is noted in
-// 'spv.mutated_callsites' metadata for later restoration.
+// Mutates indirect and inline ASM callsites iff if aggregate argument/return
+// types are present with the types replaced by i32 types. The change in types
+// is noted in 'spv.mutated_callsites' metadata for later restoration. For ASM
+// we also have to mutate the constraint string as IRTranslator tries to handle
+// multiple outputs and expects an aggregate return type in their presence.
 bool SPIRVPrepareFunctions::removeAggregateTypesFromCalls(Function *F) {
   if (F->isDeclaration() || F->isIntrinsic())
     return false;
@@ -646,9 +652,20 @@ bool SPIRVPrepareFunctions::removeAggregateTypesFromCalls(Function *F) {
       CB->setName("spv.named_mutated_callsite." + F->getName() + "." +
                   CB->getName());
 
+    StringRef MaybeConstraints;
+    if (auto *ASM = dyn_cast<InlineAsm>(CB->getCalledOperand())) {
+      MaybeConstraints = ASM->getConstraintString();
+      // We should only have one =r return for the made up ASM type.
+      CB->setCalledOperand(InlineAsm::get(
+          NewFnTy, ASM->getAsmString(),
+          ASM->getConstraintString().substr(MaybeConstraints.find_last_of('=')),
+          ASM->hasSideEffects(), ASM->isAlignStack(), ASM->getDialect(),
+          ASM->canThrow()));
+    }
+
     addFunctionTypeMutation(
         F->getParent()->getOrInsertNamedMetadata("spv.mutated_callsites"),
-        std::move(ChangedTypes), CB->getName());
+        std::move(ChangedTypes), CB->getName(), MaybeConstraints);
   }
 
   for (auto &&[CB, NewFTy] : Calls) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index f8e3e27ca289b..61eaf08afbd81 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -79,6 +79,32 @@ static FunctionType *extractFunctionTypeFromMetadata(NamedMDNode *NMD,
   return FunctionType::get(RetTy, PTys, FTy->isVarArg());
 }
 
+static StringRef extractAsmConstraintsFromMetadata(NamedMDNode *NMD,
+                                                   StringRef Constraints,
+                                                   StringRef Name) {
+  // TODO: unify the extractors.
+  if (!NMD)
+    return Constraints;
+
+  auto It = find_if(NMD->operands(), [Name](MDNode *N) {
+    if (auto *MDS = dyn_cast_or_null<MDString>(N->getOperand(0)))
+      return MDS->getString() == Name;
+    return false;
+  });
+
+  if (It == NMD->op_end())
+    return Constraints;
+
+  // By convention, the constraints string is stored in the final MD operand.
+  MDNode *MD = dyn_cast<MDNode>((*It)->getOperand((*It)->getNumOperands() - 1));
+  assert(MD && "MDNode operand is expected");
+
+  if (auto *MDS = dyn_cast<MDString>(MD->getOperand(0)))
+    Constraints = MDS->getString();
+
+  return Constraints;
+}
+
 FunctionType *getOriginalFunctionType(const Function &F) {
   return extractFunctionTypeFromMetadata(
       F.getParent()->getNamedMetadata("spv.cloned_funcs"), F.getFunctionType(),
@@ -90,6 +116,13 @@ FunctionType *getOriginalFunctionType(const CallBase &CB) {
       CB.getModule()->getNamedMetadata("spv.mutated_callsites"),
       CB.getFunctionType(), CB.getName());
 }
+
+StringRef getOriginalAsmConstraints(const CallBase &CB) {
+  return extractAsmConstraintsFromMetadata(
+    CB.getModule()->getNamedMetadata("spv.mutated_callsites"),
+    cast<InlineAsm>(CB.getCalledOperand())->getConstraintString(),
+    CB.getName());
+}
 } // Namespace SPIRV
 
 // The following functions are used to add these string literals as a series of
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index d541ead5ac22c..c7b807f97d13c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -167,6 +167,9 @@ struct FPFastMathDefaultInfoVector
 // during the translation to cope with aggregate flattening etc.
 FunctionType *getOriginalFunctionType(const Function &F);
 FunctionType *getOriginalFunctionType(const CallBase &CB);
+// This handles retrieving the original ASM constraints, which we had to spoof
+// into having a single output.
+StringRef getOriginalAsmConstraints(const CallBase &CB);
 } // namespace SPIRV
 
 // Add the given string as a series of integer operand, inserting null
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
index 91286d5bf32e8..805669e6eae3b 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
@@ -16,6 +16,7 @@
 ; CHECK-DAG: %[[#HalfTy:]] = OpTypeFloat 16
 ; CHECK-DAG: %[[#FloatTy:]] = OpTypeFloat 32
 ; CHECK-DAG: %[[#DoubleTy:]] = OpTypeFloat 64
+; CHECK-DAG: %[[#StructTy:]] = OpTypeStruct %[[#Int32Ty]] %[[#FloatTy]] %[[#HalfTy]]
 
 ; CHECK-DAG: OpTypeFunction %[[#VoidTy]] %[[#]] %[[#]] %[[#]] %[[#Int64Ty]]
 ; CHECK-DAG: %[[#Fun1Ty:]] = OpTypeFunction %[[#VoidTy]]
@@ -26,6 +27,7 @@
 ; CHECK-DAG: %[[#Fun6Ty:]] = OpTypeFunction %[[#Int8Ty]] %[[#FloatTy]] %[[#Int32Ty]] %[[#Int8Ty]]
 ; CHECK-DAG: %[[#Fun7Ty:]] = OpTypeFunction %[[#Int64Ty]] %[[#Int64Ty]] %[[#Int32Ty]] %[[#Int8Ty]]
 ; CHECK-DAG: %[[#Fun8Ty:]] = OpTypeFunction %[[#VoidTy]] %[[#Int32Ty]] %[[#DoubleTy]]
+; CHECK-DAG: %[[#Fun9Ty:]] = OpTypeFunction %[[#StructTy]] %[[#Int32Ty]] %[[#FloatTy]] %[[#HalfTy]]
 
 ; CHECK-DAG: %[[#Const2:]] = OpConstant %[[#FloatTy]] 2
 ; CHECK-DAG: %[[#Const123:]] = OpConstant %[[#Int32Ty]] 123
@@ -45,6 +47,7 @@
 ; CHECK-DAG: %[[#Asm9:]] = OpAsmINTEL %[[#Int64Ty]] %[[#Fun7Ty]] %[[#Dialect]] "icmdext $0 $3 $1 $2" "=r,r,r,r"
 ; CHECK-DAG: %[[#Asm10:]] = OpAsmINTEL %[[#VoidTy]] %[[#Fun8Ty]] %[[#Dialect]] "constcmd $0 $1" "r,r"
 ; CHECK-DAG: %[[#Asm11:]] = OpAsmINTEL %[[#VoidTy]] %[[#Fun8Ty]] %[[#Dialect]] "constcmd $0 $1" "i,i"
+; CHECK-DAG: %[[#Asm12:]] = OpAsmINTEL %[[#StructTy]] %[[#Fun9Ty]] %[[#Dialect]] "cmdext $0 $4 $5\n cmdext $2 $5 $6\n cmdext $3 $4 $6" "=&r,=&r,=&r,r,r,r"
 ; CHECK-NO: OpAsmINTEL
 
 ; CHECK: OpFunction
@@ -59,8 +62,14 @@
 ; CHECK: OpAsmCallINTEL %[[#Int64Ty]] %[[#Asm9]] %[[#]] %[[#]] %[[#]]
 ; CHECK: OpAsmCallINTEL %[[#VoidTy]] %[[#Asm10]] %[[#Const123]] %[[#Const42]]
 ; CHECK: OpAsmCallINTEL %[[#VoidTy]] %[[#Asm11]] %[[#Const123]] %[[#Const42]]
+; CHECK: %[[#StructRet:]] = OpAsmCallINTEL %[[#StructTy]] %[[#Asm12]]
+; CHECK-NEXT: OpCompositeExtract %[[#Int32Ty]] %[[#StructRet]] 0
+; CHECK-NEXT: OpCompositeExtract %[[#FloatTy]] %[[#StructRet]] 1
+; CHECK-NEXT: OpCompositeExtract %[[#HalfTy]] %[[#StructRet]] 2
 ; CHECK-NO: OpAsmCallINTEL
 
+target triple = "spirv64-unknown-unknown"
+
 define spir_kernel void @foo(ptr addrspace(1) %_arg_int, ptr addrspace(1) %_arg_float, ptr addrspace(1) %_arg_half, i64 %_lng) {
   %i1 = load i32, ptr addrspace(1) %_arg_int
   %i2 = load i8, ptr addrspace(1) %_arg_int
@@ -89,5 +98,13 @@ define spir_kernel void @foo(ptr addrspace(1) %_arg_int, ptr addrspace(1) %_arg_
   ; inline asm: constant arguments, misc constraints
   call void asm "constcmd $0 $1", "r,r"(i32 123, double 42.0)
   call void asm "constcmd $0 $1", "i,i"(i32 123, double 42.0)
+  ; inline asm: multiple outputs, hence aggregate return
+  %res_struct = call { i32, float, half } asm sideeffect "cmdext $0 $4 $5\0A cmdext $2 $5 $6\0A cmdext $3 $4 $6", "=&r,=&r,=&r,r,r,r"(i32 %i1, float %f1, half %h1)
+  %asmresult = extractvalue { i32, float, half } %res_struct, 0
+  %asmresult3 = extractvalue { i32, float, half } %res_struct, 1
+  %asmresult4 = extractvalue { i32, float, half } %res_struct, 2
+  store i32 %asmresult, ptr addrspace(1) %_arg_int
+  store float %asmresult3, ptr addrspace(1) %_arg_float
+  store half %asmresult4, ptr addrspace(1) %_arg_half
   ret void
 }

>From 26179873668ad70298b4b2928bee36544ab9a9dd Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Tue, 17 Mar 2026 21:03:40 +0000
Subject: [PATCH 14/21] Fix formatting.

---
 llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp | 3 +--
 llvm/lib/Target/SPIRV/SPIRVUtils.cpp            | 6 +++---
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 9645f6a6e802d..192c6cd4824fe 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -510,8 +510,7 @@ bool SPIRVPrepareFunctions::substituteIntrinsicCalls(Function *F) {
 static void
 addFunctionTypeMutation(NamedMDNode *NMD,
                         SmallVector<std::pair<int, Type *>> ChangedTys,
-                        StringRef Name,
-                        StringRef AsmConstraints = "") {
+                        StringRef Name, StringRef AsmConstraints = "") {
 
   LLVMContext &Ctx = NMD->getParent()->getContext();
   Type *I32Ty = IntegerType::getInt32Ty(Ctx);
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 61eaf08afbd81..4dfcad79d1914 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -119,9 +119,9 @@ FunctionType *getOriginalFunctionType(const CallBase &CB) {
 
 StringRef getOriginalAsmConstraints(const CallBase &CB) {
   return extractAsmConstraintsFromMetadata(
-    CB.getModule()->getNamedMetadata("spv.mutated_callsites"),
-    cast<InlineAsm>(CB.getCalledOperand())->getConstraintString(),
-    CB.getName());
+      CB.getModule()->getNamedMetadata("spv.mutated_callsites"),
+      cast<InlineAsm>(CB.getCalledOperand())->getConstraintString(),
+      CB.getName());
 }
 } // Namespace SPIRV
 

>From a0076bd1d52c0d1b5329c5c1740239276d230ece Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Wed, 18 Mar 2026 12:43:14 +0000
Subject: [PATCH 15/21] Update llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Juan Manuel Martinez Caamaño <jmartinezcaamao at gmail.com>
---
 llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 192c6cd4824fe..4a76754b8a4f1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -657,7 +657,7 @@ bool SPIRVPrepareFunctions::removeAggregateTypesFromCalls(Function *F) {
       // We should only have one =r return for the made up ASM type.
       CB->setCalledOperand(InlineAsm::get(
           NewFnTy, ASM->getAsmString(),
-          ASM->getConstraintString().substr(MaybeConstraints.find_last_of('=')),
+          MaybeConstraints.substr(MaybeConstraints.find_last_of('=')),
           ASM->hasSideEffects(), ASM->isAlignStack(), ASM->getDialect(),
           ASM->canThrow()));
     }

>From 96712f4a88286cd5193edb8544443f3742aefa98 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Thu, 19 Mar 2026 00:57:37 +0000
Subject: [PATCH 16/21] Remove leftover.

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

diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 12a326235934e..456a618effdc2 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -770,10 +770,6 @@ insertInlineAsmProcess(MachineFunction &MF, SPIRVGlobalRegistry *GR,
 
       Register TruncReg = Trunc.defs().begin()->getReg();
       MRI.replaceRegWith(TruncReg, DefReg);
-      // for (auto It = ++Trunc.getIterator();
-      //      isSpvIntrinsic(*It, Intrinsic::spv_extractv); ++It)
-      //   if (It->getOperand(2).getReg() == TruncReg)
-      //     It->getOperand(2).setReg(DefReg);
       invalidateAndEraseMI(GR, &Trunc);
       invalidateAndEraseMI(GR, &Copy);
     }

>From 6dba483255cdfb96e32fc3b18dca9ac8f120e6cb Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Thu, 19 Mar 2026 00:59:02 +0000
Subject: [PATCH 17/21] Remove more leftovers.

---
 .../SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll    | 2 --
 1 file changed, 2 deletions(-)

diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
index 805669e6eae3b..ff6b6dec42ff3 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
@@ -68,8 +68,6 @@
 ; CHECK-NEXT: OpCompositeExtract %[[#HalfTy]] %[[#StructRet]] 2
 ; CHECK-NO: OpAsmCallINTEL
 
-target triple = "spirv64-unknown-unknown"
-
 define spir_kernel void @foo(ptr addrspace(1) %_arg_int, ptr addrspace(1) %_arg_float, ptr addrspace(1) %_arg_half, i64 %_lng) {
   %i1 = load i32, ptr addrspace(1) %_arg_int
   %i2 = load i8, ptr addrspace(1) %_arg_int

>From c711eaf921b4b4a71240585a0faea0eb252d1fea Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Fri, 20 Mar 2026 23:00:10 +0200
Subject: [PATCH 18/21] Generalise constraint string mutation to handle
 indirect operands.

---
 .../Target/SPIRV/SPIRVPrepareFunctions.cpp    | 29 ++++++++++++++-----
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 4a76754b8a4f1..813f943afb5c4 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -598,6 +598,21 @@ SPIRVPrepareFunctions::removeAggregateTypesFromSignature(Function *F) {
   return NewF;
 }
 
+static std::string fixMultiOutputConstraintString(StringRef Constraints) {
+  // We should only have one =r return for the made up ASM type.
+  SmallVector<StringRef> Tmp;
+  SplitString(Constraints, Tmp, ",");
+  std::string SafeConstraints("=r,");
+  for (unsigned I = 0u; I != Tmp.size() - 1; ++I) {
+    if (Tmp[I].starts_with('=') && isalnum(Tmp[I][1]))
+      continue;
+    SafeConstraints.append(Tmp[I]).append({','});
+  }
+  SafeConstraints.append(Tmp.back());
+
+  return SafeConstraints;
+}
+
 // Mutates indirect and inline ASM callsites iff if aggregate argument/return
 // types are present with the types replaced by i32 types. The change in types
 // is noted in 'spv.mutated_callsites' metadata for later restoration. For ASM
@@ -651,20 +666,18 @@ bool SPIRVPrepareFunctions::removeAggregateTypesFromCalls(Function *F) {
       CB->setName("spv.named_mutated_callsite." + F->getName() + "." +
                   CB->getName());
 
-    StringRef MaybeConstraints;
+    std::string Constraints;
     if (auto *ASM = dyn_cast<InlineAsm>(CB->getCalledOperand())) {
-      MaybeConstraints = ASM->getConstraintString();
-      // We should only have one =r return for the made up ASM type.
+      Constraints = fixMultiOutputConstraintString(ASM->getConstraintString());
+
       CB->setCalledOperand(InlineAsm::get(
-          NewFnTy, ASM->getAsmString(),
-          MaybeConstraints.substr(MaybeConstraints.find_last_of('=')),
-          ASM->hasSideEffects(), ASM->isAlignStack(), ASM->getDialect(),
-          ASM->canThrow()));
+          NewFnTy, ASM->getAsmString(), Constraints, ASM->hasSideEffects(),
+          ASM->isAlignStack(), ASM->getDialect(), ASM->canThrow()));
     }
 
     addFunctionTypeMutation(
         F->getParent()->getOrInsertNamedMetadata("spv.mutated_callsites"),
-        std::move(ChangedTypes), CB->getName(), MaybeConstraints);
+        std::move(ChangedTypes), CB->getName(), Constraints);
   }
 
   for (auto &&[CB, NewFTy] : Calls) {

>From 6df2813b766b47d39e7c93572aaae426ef324be7 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Fri, 20 Mar 2026 23:04:18 +0200
Subject: [PATCH 19/21] Handle memory constraints.

---
 llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp b/llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp
index 2fcb71939322a..4469e3943204c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVISelLowering.cpp
@@ -136,8 +136,12 @@ TargetLowering::ConstraintType
 SPIRVTargetLowering::getConstraintType(StringRef Constraint) const {
   // SPIR-V represents inline assembly via OpAsmINTEL where constraints are
   // passed through as literals defined by client API. Return C_RegisterClass
-  // for any constraint since SPIR-V does not distinguish between register,
-  // immediate, or memory operands at this level.
+  // for non-memory constraints since SPIR-V does not distinguish between register,
+  // immediate, or memory operands at this level. We do have to return C_Memory
+  // for memory constraints as otherwise IRTranslator gets confused trying to
+  // allocate registers for them.
+  if (Constraint == "m")
+    return C_Memory;
   return C_RegisterClass;
 }
 

>From 06711cf56b9713aa839468f525a325414f0513c6 Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Mon, 6 Apr 2026 01:11:58 +0100
Subject: [PATCH 20/21] Handle memory operands, fix broken handling of
 constraint strings for multiple output cases.

---
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp |  6 ++-
 llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp   | 39 ++++++++++---------
 .../Target/SPIRV/SPIRVPrepareFunctions.cpp    |  8 ++--
 .../SPV_INTEL_inline_assembly/inline_asm.ll   | 23 ++++++++++-
 4 files changed, 52 insertions(+), 24 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 3857c7248ca54..df6b2d785368b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -1662,7 +1662,11 @@ Instruction *SPIRVEmitIntrinsics::visitCallInst(CallInst &Call) {
     return &Call;
 
   LLVMContext &Ctx = CurrF->getContext();
-
+  // TODO: this does not retain elementtype info for memory constraints, which
+  //       in turn means that we lower them into pointers to i8, rather than
+  //       pointers to elementtype; this can be fixed during reverse translation
+  //       but we should correct it here, possibly by tweaking the function
+  //       type to take TypedPointerType args.
   Constant *TyC = UndefValue::get(SPIRV::getOriginalFunctionType(Call));
   MDString *ConstraintString =
       MDString::get(Ctx, SPIRV::getOriginalAsmConstraints(Call));
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index 456a618effdc2..e31f50a7b737e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -672,8 +672,7 @@ collectInlineAsmInstrOperands(MachineInstr *MI,
     if (MO.isReg() && MO.isDef()) {
       if (!Ops)
         return MO.getReg();
-      else
-        DefReg = MO.getReg();
+      DefReg = MO.getReg();
     } else if (Ops) {
       Ops->push_back(Idx);
     }
@@ -756,22 +755,26 @@ insertInlineAsmProcess(MachineFunction &MF, SPIRVGlobalRegistry *GR,
     for (unsigned IntrIdx = 3; IntrIdx < I1->getNumOperands(); ++IntrIdx)
       AsmCall.addUse(I1->getOperand(IntrIdx).getReg());
 
-    // IRTranslator gets a bit confused when lowering inline ASM with multiple,
-    // outputs (which we have to spoof as single i32 return), and inserts a
-    // spurious COPY & TRUNC as registers are assumed to be i64; we have to
-    // clean that up here to prevent an erroneous cast on a struct to get
-    // lowered into SPIR-V
-    if (FTy->getReturnType()->isStructTy()) {
-      MachineInstr &Copy = *++I2->getIterator();
-      MachineInstr &Trunc = *++Copy.getIterator();
-
-      assert(Copy.isCopy() && Trunc.getOpcode() == TargetOpcode::G_TRUNC &&
-             "Unexpected successors to inline ASM with multiple outputs!");
-
-      Register TruncReg = Trunc.defs().begin()->getReg();
-      MRI.replaceRegWith(TruncReg, DefReg);
-      invalidateAndEraseMI(GR, &Trunc);
-      invalidateAndEraseMI(GR, &Copy);
+    // IRTranslator gets a bit confused when lowering inline ASM with outputs
+    // and inserts a spurious COPY & TRUNC as registers are assumed to be i64;
+    // we have to clean that up here to prevent erroneous trunc casts either on
+    // a struct (for multiple outputs) or same width integers to get lowered
+    // into SPIR-V
+    if (MRI.hasOneUse(DefReg)) {
+      MachineInstr &CopyMI = *MRI.use_instr_begin(DefReg);
+      if (CopyMI.getOpcode() == TargetOpcode::COPY) {
+        Register CopyDst = CopyMI.getOperand(0).getReg();
+        if (MRI.hasOneUse(CopyDst)) {
+          MachineInstr &TruncMI = *MRI.use_instr_begin(CopyDst);
+          if (TruncMI.getOpcode() == TargetOpcode::G_TRUNC) {
+            MRI.setType(DefReg, GR->getRegType(RetType));
+            Register TruncReg = TruncMI.defs().begin()->getReg();
+            MRI.replaceRegWith(TruncReg, DefReg);
+            invalidateAndEraseMI(GR, &TruncMI);
+            invalidateAndEraseMI(GR, &CopyMI);
+          }
+        }
+      }
     }
   }
   for (MachineInstr *MI : ToProcess)
diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 813f943afb5c4..01f10e3348efc 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -604,7 +604,8 @@ static std::string fixMultiOutputConstraintString(StringRef Constraints) {
   SplitString(Constraints, Tmp, ",");
   std::string SafeConstraints("=r,");
   for (unsigned I = 0u; I != Tmp.size() - 1; ++I) {
-    if (Tmp[I].starts_with('=') && isalnum(Tmp[I][1]))
+    if (Tmp[I].starts_with('=') &&
+        (Tmp[I][1] == '&' || isalnum(Tmp[I][1])))
       continue;
     SafeConstraints.append(Tmp[I]).append({','});
   }
@@ -668,10 +669,11 @@ bool SPIRVPrepareFunctions::removeAggregateTypesFromCalls(Function *F) {
 
     std::string Constraints;
     if (auto *ASM = dyn_cast<InlineAsm>(CB->getCalledOperand())) {
-      Constraints = fixMultiOutputConstraintString(ASM->getConstraintString());
+      Constraints = ASM->getConstraintString();
 
       CB->setCalledOperand(InlineAsm::get(
-          NewFnTy, ASM->getAsmString(), Constraints, ASM->hasSideEffects(),
+          NewFnTy, ASM->getAsmString(),
+          fixMultiOutputConstraintString(Constraints), ASM->hasSideEffects(),
           ASM->isAlignStack(), ASM->getDialect(), ASM->canThrow()));
     }
 
diff --git a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
index ff6b6dec42ff3..30d45e54c038d 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/SPV_INTEL_inline_assembly/inline_asm.ll
@@ -17,6 +17,8 @@
 ; CHECK-DAG: %[[#FloatTy:]] = OpTypeFloat 32
 ; CHECK-DAG: %[[#DoubleTy:]] = OpTypeFloat 64
 ; CHECK-DAG: %[[#StructTy:]] = OpTypeStruct %[[#Int32Ty]] %[[#FloatTy]] %[[#HalfTy]]
+; CHECK-DAG: %[[#StructTy1:]] = OpTypeStruct %[[#Int32Ty]] %[[#FloatTy]]
+; CHECK-DAG: %[[#Int8PtrTy:]] = OpTypePointer CrossWorkgroup %[[#Int8Ty]]
 
 ; CHECK-DAG: OpTypeFunction %[[#VoidTy]] %[[#]] %[[#]] %[[#]] %[[#Int64Ty]]
 ; CHECK-DAG: %[[#Fun1Ty:]] = OpTypeFunction %[[#VoidTy]]
@@ -28,6 +30,8 @@
 ; CHECK-DAG: %[[#Fun7Ty:]] = OpTypeFunction %[[#Int64Ty]] %[[#Int64Ty]] %[[#Int32Ty]] %[[#Int8Ty]]
 ; CHECK-DAG: %[[#Fun8Ty:]] = OpTypeFunction %[[#VoidTy]] %[[#Int32Ty]] %[[#DoubleTy]]
 ; CHECK-DAG: %[[#Fun9Ty:]] = OpTypeFunction %[[#StructTy]] %[[#Int32Ty]] %[[#FloatTy]] %[[#HalfTy]]
+; CHECK-DAG: %[[#Fun10Ty:]] = OpTypeFunction %[[#Int32Ty]] %[[#Int8PtrTy]] %[[#Int32Ty]] %[[#Int8PtrTy]]
+; CHECK-DAG: %[[#Fun11Ty:]] = OpTypeFunction %[[#StructTy1]] %[[#Int8PtrTy]] %[[#Int32Ty]] %[[#FloatTy]]
 
 ; CHECK-DAG: %[[#Const2:]] = OpConstant %[[#FloatTy]] 2
 ; CHECK-DAG: %[[#Const123:]] = OpConstant %[[#Int32Ty]] 123
@@ -47,7 +51,9 @@
 ; CHECK-DAG: %[[#Asm9:]] = OpAsmINTEL %[[#Int64Ty]] %[[#Fun7Ty]] %[[#Dialect]] "icmdext $0 $3 $1 $2" "=r,r,r,r"
 ; CHECK-DAG: %[[#Asm10:]] = OpAsmINTEL %[[#VoidTy]] %[[#Fun8Ty]] %[[#Dialect]] "constcmd $0 $1" "r,r"
 ; CHECK-DAG: %[[#Asm11:]] = OpAsmINTEL %[[#VoidTy]] %[[#Fun8Ty]] %[[#Dialect]] "constcmd $0 $1" "i,i"
-; CHECK-DAG: %[[#Asm12:]] = OpAsmINTEL %[[#StructTy]] %[[#Fun9Ty]] %[[#Dialect]] "cmdext $0 $4 $5\n cmdext $2 $5 $6\n cmdext $3 $4 $6" "=&r,=&r,=&r,r,r,r"
+; CHECK-DAG: %[[#Asm12:]] = OpAsmINTEL %[[#StructTy]] %[[#Fun9Ty]] %[[#Dialect]] "cmdext $0 $4 $5\n cmdext $1 $5 $6\n cmdext $2 $4 $6" "=&r,=&r,=&r,r,r,r"
+; CHECK-DAG: %[[#Asm13:]] = OpAsmINTEL %[[#Int32Ty]] %[[#Fun10Ty]] %[[#Dialect]] "icmdext $0 $2 $3"
+; CHECK-DAG: %[[#Asm14:]] = OpAsmINTEL %[[#StructTy1]] %[[#Fun11Ty]] %[[#Dialect]] "cmdext $0 $3 $4\n cmdext $1 $3 $4\n $2 $3 $4" "=r,=&r,=*m, r, r"
 ; CHECK-NO: OpAsmINTEL
 
 ; CHECK: OpFunction
@@ -66,6 +72,10 @@
 ; CHECK-NEXT: OpCompositeExtract %[[#Int32Ty]] %[[#StructRet]] 0
 ; CHECK-NEXT: OpCompositeExtract %[[#FloatTy]] %[[#StructRet]] 1
 ; CHECK-NEXT: OpCompositeExtract %[[#HalfTy]] %[[#StructRet]] 2
+; CHECK: OpAsmCallINTEL %[[#Int32Ty]] %[[#Asm13]]
+; CHECK: %[[#StructRet1:]] = OpAsmCallINTEL %[[#StructTy1]] %[[#Asm14]]
+; CHECK-NEXT: OpCompositeExtract %[[#Int32Ty]] %[[#StructRet1]] 0
+; CHECK-NEXT: OpCompositeExtract %[[#FloatTy]] %[[#StructRet1]] 1
 ; CHECK-NO: OpAsmCallINTEL
 
 define spir_kernel void @foo(ptr addrspace(1) %_arg_int, ptr addrspace(1) %_arg_float, ptr addrspace(1) %_arg_half, i64 %_lng) {
@@ -97,12 +107,21 @@ define spir_kernel void @foo(ptr addrspace(1) %_arg_int, ptr addrspace(1) %_arg_
   call void asm "constcmd $0 $1", "r,r"(i32 123, double 42.0)
   call void asm "constcmd $0 $1", "i,i"(i32 123, double 42.0)
   ; inline asm: multiple outputs, hence aggregate return
-  %res_struct = call { i32, float, half } asm sideeffect "cmdext $0 $4 $5\0A cmdext $2 $5 $6\0A cmdext $3 $4 $6", "=&r,=&r,=&r,r,r,r"(i32 %i1, float %f1, half %h1)
+  %res_struct = call { i32, float, half } asm sideeffect "cmdext $0 $4 $5\0A cmdext $1 $5 $6\0A cmdext $2 $4 $6", "=&r,=&r,=&r,r,r,r"(i32 %i1, float %f1, half %h1)
   %asmresult = extractvalue { i32, float, half } %res_struct, 0
   %asmresult3 = extractvalue { i32, float, half } %res_struct, 1
   %asmresult4 = extractvalue { i32, float, half } %res_struct, 2
   store i32 %asmresult, ptr addrspace(1) %_arg_int
   store float %asmresult3, ptr addrspace(1) %_arg_float
   store half %asmresult4, ptr addrspace(1) %_arg_half
+  ; inline asm: two outputs but one is a mem operand
+  %asmtmp = call i32 asm sideeffect "icmdext $0 $2 $3", "=&r,=*m,r,*m,~{memory}"(ptr addrspace(1) elementtype(i32) %_arg_int, i32 %i1, ptr addrspace(1) elementtype(float) %_arg_float)
+  store i32 %asmtmp, ptr addrspace(1) %_arg_int
+  ; inline asm: multiple outputs out of which one is a mem operand
+  %res_struct1 = call { i32, float } asm sideeffect "cmdext $0 $3 $4\0A cmdext $1 $3 $4\0A $2 $3 $4", "=r,=&r,=*m, r, r"(ptr addrspace(1) elementtype(i32) %_arg_int, i32 %i1, float %f1)
+  %asmresult1 = extractvalue { i32, float } %res_struct1, 0
+  %asmresult2 = extractvalue { i32, float } %res_struct1, 1
+  store i32 %asmresult1, ptr addrspace(1) %_arg_int
+  store float %asmresult2, ptr addrspace(1) %_arg_float
   ret void
 }

>From 3b07cb1c8680c75a69cc86f57200c2a7ea76799f Mon Sep 17 00:00:00 2001
From: Alex Voicu <alexandru.voicu at amd.com>
Date: Mon, 6 Apr 2026 01:22:15 +0100
Subject: [PATCH 21/21] Fix formatting.

---
 llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index 01f10e3348efc..a6572ae3c0f20 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -604,8 +604,7 @@ static std::string fixMultiOutputConstraintString(StringRef Constraints) {
   SplitString(Constraints, Tmp, ",");
   std::string SafeConstraints("=r,");
   for (unsigned I = 0u; I != Tmp.size() - 1; ++I) {
-    if (Tmp[I].starts_with('=') &&
-        (Tmp[I][1] == '&' || isalnum(Tmp[I][1])))
+    if (Tmp[I].starts_with('=') && (Tmp[I][1] == '&' || isalnum(Tmp[I][1])))
       continue;
     SafeConstraints.append(Tmp[I]).append({','});
   }



More information about the llvm-commits mailing list