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

Alex Voicu via llvm-commits llvm-commits at lists.llvm.org
Wed Mar 18 17:59:20 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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



More information about the llvm-commits mailing list