[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