[llvm] [SPIR-V] Add OpSMulExtended and OpUMulExtended builtin support (PR #187474)
Arseniy Obolenskiy via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 16 06:36:44 PDT 2026
https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/187474
>From ec7ba1d78723fec36c12e7e87c3d25590d9729f0 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Thu, 19 Mar 2026 11:41:49 +0100
Subject: [PATCH 1/6] [SPIR-V] Add OpSMulExtended and OpUMulExtended builtin
support
---
llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp | 50 +++++++
llvm/lib/Target/SPIRV/SPIRVBuiltins.td | 5 +
.../CodeGen/SPIRV/smulextended-builtin.ll | 125 ++++++++++++++++++
.../CodeGen/SPIRV/umulextended-builtin.ll | 125 ++++++++++++++++++
4 files changed, 305 insertions(+)
create mode 100644 llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
create mode 100644 llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
index f9a9127446013..6abf50f59a03f 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
@@ -1996,6 +1996,54 @@ static bool generateICarryBorrowInst(const SPIRV::IncomingCall *Call,
return true;
}
+// We expect a builtin
+// Name(ptr sret([RetType]) %result, Type %operand1, Type %operand2)
+// where %result is a pointer to where the result of the builtin execution
+// is to be stored, and generate the following instructions:
+// Res = Opcode RetType Operand1 Operand2
+// OpStore RetVariable Res
+static bool generateMulExtendedInst(const SPIRV::IncomingCall *Call,
+ MachineIRBuilder &MIRBuilder,
+ SPIRVGlobalRegistry *GR) {
+ const SPIRV::DemangledBuiltin *Builtin = Call->Builtin;
+ unsigned Opcode =
+ SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode;
+ assert((Opcode == SPIRV::OpUMulExtended || Opcode == SPIRV::OpSMulExtended) &&
+ "Expected OpUMulExtended or OpSMulExtended");
+
+ Register SRetReg = Call->Arguments[0];
+ SPIRVTypeInst PtrRetType = GR->getSPIRVTypeForVReg(SRetReg);
+ SPIRVTypeInst RetType = GR->getPointeeType(PtrRetType);
+ if (!RetType)
+ report_fatal_error("The first parameter must be a pointer");
+ if (RetType->getOpcode() != SPIRV::OpTypeStruct)
+ report_fatal_error("Expected struct type result for the extended "
+ "multiplication builtins");
+
+ SPIRVTypeInst OpType1 = GR->getSPIRVTypeForVReg(Call->Arguments[1]);
+ SPIRVTypeInst OpType2 = GR->getSPIRVTypeForVReg(Call->Arguments[2]);
+ if (!OpType1 || !OpType2 || OpType1 != OpType2)
+ report_fatal_error("Operands must have the same type");
+
+ MachineRegisterInfo *MRI = MIRBuilder.getMRI();
+ Register ResReg = MRI->createVirtualRegister(&SPIRV::iIDRegClass);
+ if (const TargetRegisterClass *DstRC =
+ MRI->getRegClassOrNull(Call->Arguments[1])) {
+ MRI->setRegClass(ResReg, DstRC);
+ MRI->setType(ResReg, MRI->getType(Call->Arguments[1]));
+ } else {
+ MRI->setType(ResReg, LLT::scalar(64));
+ }
+ GR->assignSPIRVTypeToVReg(RetType, ResReg, MIRBuilder.getMF());
+ MIRBuilder.buildInstr(Opcode)
+ .addDef(ResReg)
+ .addUse(GR->getSPIRVTypeID(RetType))
+ .addUse(Call->Arguments[1])
+ .addUse(Call->Arguments[2]);
+ MIRBuilder.buildInstr(SPIRV::OpStore).addUse(SRetReg).addUse(ResReg);
+ return true;
+}
+
static bool generateGetQueryInst(const SPIRV::IncomingCall *Call,
MachineIRBuilder &MIRBuilder,
SPIRVGlobalRegistry *GR) {
@@ -3236,6 +3284,8 @@ std::optional<bool> lowerBuiltin(const StringRef DemangledCall,
return generateWaveInst(Call.get(), MIRBuilder, GR);
case SPIRV::ICarryBorrow:
return generateICarryBorrowInst(Call.get(), MIRBuilder, GR);
+ case SPIRV::MulExtended:
+ return generateMulExtendedInst(Call.get(), MIRBuilder, GR);
case SPIRV::GetQuery:
return generateGetQueryInst(Call.get(), MIRBuilder, GR);
case SPIRV::ImageSizeQuery:
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.td b/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
index eb1b6c3185e7d..b30db2b9f718d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
@@ -65,6 +65,7 @@ def CastToPtr : BuiltinGroup;
def Construct : BuiltinGroup;
def CoopMatr : BuiltinGroup;
def ICarryBorrow : BuiltinGroup;
+def MulExtended : BuiltinGroup;
def ExtendedBitOps : BuiltinGroup;
def BindlessINTEL : BuiltinGroup;
def TernaryBitwiseINTEL : BuiltinGroup;
@@ -676,6 +677,10 @@ defm : DemangledNativeBuiltin<"__spirv_ControlBarrier", OpenCL_std, Barrier, 3,
defm : DemangledNativeBuiltin<"__spirv_IAddCarry", OpenCL_std, ICarryBorrow, 3, 3, OpIAddCarryS>;
defm : DemangledNativeBuiltin<"__spirv_ISubBorrow", OpenCL_std, ICarryBorrow, 3, 3, OpISubBorrowS>;
+// MulExtended builtin records:
+defm : DemangledNativeBuiltin<"__spirv_UMulExtended", OpenCL_std, MulExtended, 3, 3, OpUMulExtended>;
+defm : DemangledNativeBuiltin<"__spirv_SMulExtended", OpenCL_std, MulExtended, 3, 3, OpSMulExtended>;
+
// cl_intel_split_work_group_barrier
defm : DemangledNativeBuiltin<"intel_work_group_barrier_arrive", OpenCL_std, Barrier, 1, 2, OpControlBarrierArriveINTEL>;
defm : DemangledNativeBuiltin<"__spirv_ControlBarrierArriveINTEL", OpenCL_std, Barrier, 3, 3, OpControlBarrierArriveINTEL>;
diff --git a/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll b/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
new file mode 100644
index 0000000000000..f8c632526ee23
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
@@ -0,0 +1,125 @@
+; RUN: llc -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 %}
+
+%i8struct = type {i8, i8}
+%i16struct = type {i16, i16}
+%i32struct = type {i32, i32}
+%i64struct = type {i64, i64}
+%vecstruct = type {<4 x i32>, <4 x i32>}
+
+; CHECK-SPIRV-DAG: [[uchar:%[a-z0-9_]+]] = OpTypeInt 8
+; CHECK-SPIRV-DAG: [[ushort:%[a-z0-9_]+]] = OpTypeInt 16
+; CHECK-SPIRV-DAG: [[uint:%[a-z0-9_]+]] = OpTypeInt 32
+; CHECK-SPIRV-DAG: [[ulong:%[a-z0-9_]+]] = OpTypeInt 64
+; CHECK-SPIRV-DAG: [[void:%[a-z0-9_]+]] = OpTypeVoid
+; CHECK-SPIRV-DAG: [[i8struct:%[a-z0-9_]+]] = OpTypeStruct [[uchar]] [[uchar]]
+; CHECK-SPIRV-DAG: [[_ptr_Function_i8struct:%[a-z0-9_]+]] = OpTypePointer Function [[i8struct]]
+; CHECK-SPIRV-DAG: [[i16struct:%[a-z0-9_]+]] = OpTypeStruct [[ushort]] [[ushort]]
+; CHECK-SPIRV-DAG: [[_ptr_Function_i16struct:%[a-z0-9_]+]] = OpTypePointer Function [[i16struct]]
+; CHECK-SPIRV-DAG: [[i32struct:%[a-z0-9_]+]] = OpTypeStruct [[uint]] [[uint]]
+; CHECK-SPIRV-DAG: [[_ptr_Function_i32struct:%[a-z0-9_]+]] = OpTypePointer Function [[i32struct]]
+; CHECK-SPIRV-DAG: [[i64struct:%[a-z0-9_]+]] = OpTypeStruct [[ulong]] [[ulong]]
+; CHECK-SPIRV-DAG: [[_ptr_Function_i64struct:%[a-z0-9_]+]] = OpTypePointer Function [[i64struct]]
+; CHECK-SPIRV-DAG: [[v4uint:%[a-z0-9_]+]] = OpTypeVector [[uint]] 4
+; CHECK-SPIRV-DAG: [[vecstruct:%[a-z0-9_]+]] = OpTypeStruct [[v4uint]] [[v4uint]]
+; CHECK-SPIRV-DAG: [[_ptr_Function_vecstruct:%[a-z0-9_]+]] = OpTypePointer Function [[vecstruct]]
+; CHECK-SPIRV-DAG: [[_ptr_Generic_i32struct:%[a-z0-9_]+]] = OpTypePointer Generic [[i32struct]]
+
+define spir_func void @test_builtin_smulextcc(i8 %a, i8 %b) {
+ entry:
+ %0 = alloca %i8struct
+ call void @_Z20__spirv_SMulExtendedcc(ptr sret (%i8struct) %0, i8 %a, i8 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
+; CHECK-SPIRV: [[b:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
+; CHECK-SPIRV: [[entry:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_11:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i8struct]] Function
+; CHECK-SPIRV: [[var_12:%[a-z0-9_]+]] = OpSMulExtended [[i8struct]] [[a]] [[b]]
+; CHECK-SPIRV: OpStore [[var_11]] [[var_12]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV: OpFunctionEnd
+
+define spir_func void @test_builtin_smulextss(i16 %a, i16 %b) {
+ entry:
+ %0 = alloca %i16struct
+ call void @_Z20__spirv_SMulExtendedss(ptr sret (%i16struct) %0, i16 %a, i16 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
+; CHECK-SPIRV: [[b_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
+; CHECK-SPIRV: [[entry_0:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_21:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i16struct]] Function
+; CHECK-SPIRV: [[var_22:%[a-z0-9_]+]] = OpSMulExtended [[i16struct]] [[a_0]] [[b_0]]
+; CHECK-SPIRV: OpStore [[var_21]] [[var_22]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV: OpFunctionEnd
+
+define spir_func void @test_builtin_smulextii(i32 %a, i32 %b) {
+ entry:
+ %0 = alloca %i32struct
+ call void @_Z20__spirv_SMulExtendedii(ptr sret (%i32struct) %0, i32 %a, i32 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV: [[b_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV: [[entry_1:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_31:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct]] Function
+; CHECK-SPIRV: [[var_32:%[a-z0-9_]+]] = OpSMulExtended [[i32struct]] [[a_1]] [[b_1]]
+; CHECK-SPIRV: OpStore [[var_31]] [[var_32]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV: OpFunctionEnd
+
+define spir_func void @test_builtin_smulextll(i64 %a, i64 %b) {
+ entry:
+ %0 = alloca %i64struct
+ call void @_Z20__spirv_SMulExtendedll(ptr sret (%i64struct) %0, i64 %a, i64 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
+; CHECK-SPIRV: [[b_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
+; CHECK-SPIRV: [[entry_2:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_41:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i64struct]] Function
+; CHECK-SPIRV: [[var_42:%[a-z0-9_]+]] = OpSMulExtended [[i64struct]] [[a_2]] [[b_2]]
+; CHECK-SPIRV: OpStore [[var_41]] [[var_42]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV: OpFunctionEnd
+
+define spir_func void @test_builtin_smulextDv4_xS_(<4 x i32> %a, <4 x i32> %b) {
+ entry:
+ %0 = alloca %vecstruct
+ call void @_Z20__spirv_SMulExtendedDv4_iS_(ptr sret (%vecstruct) %0, <4 x i32> %a, <4 x i32> %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
+; CHECK-SPIRV: [[b_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
+; CHECK-SPIRV: [[entry_3:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_51:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_vecstruct]] Function
+; CHECK-SPIRV: [[var_52:%[a-z0-9_]+]] = OpSMulExtended [[vecstruct]] [[a_3]] [[b_3]]
+; CHECK-SPIRV: OpStore [[var_51]] [[var_52]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV: OpFunctionEnd
+
+%struct.anon = type { i32, i32 }
+
+define spir_func void @test_builtin_smulext_anon(i32 %a, i32 %b) {
+ entry:
+ %0 = alloca %struct.anon
+ %1 = addrspacecast ptr %0 to ptr addrspace(4)
+ call spir_func void @_Z20__spirv_SMulExtendedIiiE4anonIT_T0_ES1_S2_(ptr addrspace(4) sret(%struct.anon) align 4 %1, i32 %a, i32 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_4:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV: [[b_4:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV: [[entry_4:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_59:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct]] Function
+; CHECK-SPIRV: [[var_61:%[a-z0-9_]+]] = OpPtrCastToGeneric [[_ptr_Generic_i32struct]] [[var_59]]
+; CHECK-SPIRV: [[var_62:%[a-z0-9_]+]] = OpSMulExtended [[i32struct]] [[a_4]] [[b_4]]
+; CHECK-SPIRV: OpStore [[var_61]] [[var_62]]
+
+declare void @_Z20__spirv_SMulExtendedIiiE4anonIT_T0_ES1_S2_(ptr addrspace(4) sret(%struct.anon) align 4, i32, i32)
+declare void @_Z20__spirv_SMulExtendedcc(ptr sret(%i8struct), i8, i8)
+declare void @_Z20__spirv_SMulExtendedss(ptr sret(%i16struct), i16, i16)
+declare void @_Z20__spirv_SMulExtendedii(ptr sret(%i32struct), i32, i32)
+declare void @_Z20__spirv_SMulExtendedll(ptr sret(%i64struct), i64, i64)
+declare void @_Z20__spirv_SMulExtendedDv4_iS_(ptr sret (%vecstruct), <4 x i32>, <4 x i32>)
diff --git a/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll b/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
new file mode 100644
index 0000000000000..1f7980b2e5c47
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
@@ -0,0 +1,125 @@
+; RUN: llc -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 %}
+
+%i8struct = type {i8, i8}
+%i16struct = type {i16, i16}
+%i32struct = type {i32, i32}
+%i64struct = type {i64, i64}
+%vecstruct = type {<4 x i32>, <4 x i32>}
+
+; CHECK-SPIRV-DAG: [[uchar:%[a-z0-9_]+]] = OpTypeInt 8
+; CHECK-SPIRV-DAG: [[ushort:%[a-z0-9_]+]] = OpTypeInt 16
+; CHECK-SPIRV-DAG: [[uint:%[a-z0-9_]+]] = OpTypeInt 32
+; CHECK-SPIRV-DAG: [[ulong:%[a-z0-9_]+]] = OpTypeInt 64
+; CHECK-SPIRV-DAG: [[void:%[a-z0-9_]+]] = OpTypeVoid
+; CHECK-SPIRV-DAG: [[i8struct:%[a-z0-9_]+]] = OpTypeStruct [[uchar]] [[uchar]]
+; CHECK-SPIRV-DAG: [[_ptr_Function_i8struct:%[a-z0-9_]+]] = OpTypePointer Function [[i8struct]]
+; CHECK-SPIRV-DAG: [[i16struct:%[a-z0-9_]+]] = OpTypeStruct [[ushort]] [[ushort]]
+; CHECK-SPIRV-DAG: [[_ptr_Function_i16struct:%[a-z0-9_]+]] = OpTypePointer Function [[i16struct]]
+; CHECK-SPIRV-DAG: [[i32struct:%[a-z0-9_]+]] = OpTypeStruct [[uint]] [[uint]]
+; CHECK-SPIRV-DAG: [[_ptr_Function_i32struct:%[a-z0-9_]+]] = OpTypePointer Function [[i32struct]]
+; CHECK-SPIRV-DAG: [[i64struct:%[a-z0-9_]+]] = OpTypeStruct [[ulong]] [[ulong]]
+; CHECK-SPIRV-DAG: [[_ptr_Function_i64struct:%[a-z0-9_]+]] = OpTypePointer Function [[i64struct]]
+; CHECK-SPIRV-DAG: [[v4uint:%[a-z0-9_]+]] = OpTypeVector [[uint]] 4
+; CHECK-SPIRV-DAG: [[vecstruct:%[a-z0-9_]+]] = OpTypeStruct [[v4uint]] [[v4uint]]
+; CHECK-SPIRV-DAG: [[_ptr_Function_vecstruct:%[a-z0-9_]+]] = OpTypePointer Function [[vecstruct]]
+; CHECK-SPIRV-DAG: [[_ptr_Generic_i32struct:%[a-z0-9_]+]] = OpTypePointer Generic [[i32struct]]
+
+define spir_func void @test_builtin_umulextcc(i8 %a, i8 %b) {
+ entry:
+ %0 = alloca %i8struct
+ call void @_Z20__spirv_UMulExtendedcc(ptr sret (%i8struct) %0, i8 %a, i8 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
+; CHECK-SPIRV: [[b:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
+; CHECK-SPIRV: [[entry:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_11:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i8struct]] Function
+; CHECK-SPIRV: [[var_12:%[a-z0-9_]+]] = OpUMulExtended [[i8struct]] [[a]] [[b]]
+; CHECK-SPIRV: OpStore [[var_11]] [[var_12]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV: OpFunctionEnd
+
+define spir_func void @test_builtin_umulextss(i16 %a, i16 %b) {
+ entry:
+ %0 = alloca %i16struct
+ call void @_Z20__spirv_UMulExtendedss(ptr sret (%i16struct) %0, i16 %a, i16 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
+; CHECK-SPIRV: [[b_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
+; CHECK-SPIRV: [[entry_0:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_21:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i16struct]] Function
+; CHECK-SPIRV: [[var_22:%[a-z0-9_]+]] = OpUMulExtended [[i16struct]] [[a_0]] [[b_0]]
+; CHECK-SPIRV: OpStore [[var_21]] [[var_22]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV: OpFunctionEnd
+
+define spir_func void @test_builtin_umulextii(i32 %a, i32 %b) {
+ entry:
+ %0 = alloca %i32struct
+ call void @_Z20__spirv_UMulExtendedii(ptr sret (%i32struct) %0, i32 %a, i32 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV: [[b_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV: [[entry_1:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_31:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct]] Function
+; CHECK-SPIRV: [[var_32:%[a-z0-9_]+]] = OpUMulExtended [[i32struct]] [[a_1]] [[b_1]]
+; CHECK-SPIRV: OpStore [[var_31]] [[var_32]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV: OpFunctionEnd
+
+define spir_func void @test_builtin_umulextll(i64 %a, i64 %b) {
+ entry:
+ %0 = alloca %i64struct
+ call void @_Z20__spirv_UMulExtendedll(ptr sret (%i64struct) %0, i64 %a, i64 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
+; CHECK-SPIRV: [[b_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
+; CHECK-SPIRV: [[entry_2:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_41:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i64struct]] Function
+; CHECK-SPIRV: [[var_42:%[a-z0-9_]+]] = OpUMulExtended [[i64struct]] [[a_2]] [[b_2]]
+; CHECK-SPIRV: OpStore [[var_41]] [[var_42]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV: OpFunctionEnd
+
+define spir_func void @test_builtin_umulextDv4_xS_(<4 x i32> %a, <4 x i32> %b) {
+ entry:
+ %0 = alloca %vecstruct
+ call void @_Z20__spirv_UMulExtendedDv4_iS_(ptr sret (%vecstruct) %0, <4 x i32> %a, <4 x i32> %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
+; CHECK-SPIRV: [[b_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
+; CHECK-SPIRV: [[entry_3:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_51:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_vecstruct]] Function
+; CHECK-SPIRV: [[var_52:%[a-z0-9_]+]] = OpUMulExtended [[vecstruct]] [[a_3]] [[b_3]]
+; CHECK-SPIRV: OpStore [[var_51]] [[var_52]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV: OpFunctionEnd
+
+%struct.anon = type { i32, i32 }
+
+define spir_func void @test_builtin_umulext_anon(i32 %a, i32 %b) {
+ entry:
+ %0 = alloca %struct.anon
+ %1 = addrspacecast ptr %0 to ptr addrspace(4)
+ call spir_func void @_Z20__spirv_UMulExtendedIiiE4anonIT_T0_ES1_S2_(ptr addrspace(4) sret(%struct.anon) align 4 %1, i32 %a, i32 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_4:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV: [[b_4:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV: [[entry_4:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_59:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct]] Function
+; CHECK-SPIRV: [[var_61:%[a-z0-9_]+]] = OpPtrCastToGeneric [[_ptr_Generic_i32struct]] [[var_59]]
+; CHECK-SPIRV: [[var_62:%[a-z0-9_]+]] = OpUMulExtended [[i32struct]] [[a_4]] [[b_4]]
+; CHECK-SPIRV: OpStore [[var_61]] [[var_62]]
+
+declare void @_Z20__spirv_UMulExtendedIiiE4anonIT_T0_ES1_S2_(ptr addrspace(4) sret(%struct.anon) align 4, i32, i32)
+declare void @_Z20__spirv_UMulExtendedcc(ptr sret(%i8struct), i8, i8)
+declare void @_Z20__spirv_UMulExtendedss(ptr sret(%i16struct), i16, i16)
+declare void @_Z20__spirv_UMulExtendedii(ptr sret(%i32struct), i32, i32)
+declare void @_Z20__spirv_UMulExtendedll(ptr sret(%i64struct), i64, i64)
+declare void @_Z20__spirv_UMulExtendedDv4_iS_(ptr sret (%vecstruct), <4 x i32>, <4 x i32>)
>From f5bec4eef93fea51a0b81d6e64736c6869e05613 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Thu, 19 Mar 2026 17:26:19 +0100
Subject: [PATCH 2/6] Address review comments
---
llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp | 31 ++--
llvm/lib/Target/SPIRV/SPIRVBuiltins.td | 4 +-
.../CodeGen/SPIRV/smulextended-builtin.ll | 145 +++++++-----------
.../CodeGen/SPIRV/umulextended-builtin.ll | 145 +++++++-----------
4 files changed, 129 insertions(+), 196 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
index 6abf50f59a03f..0f00cfed326b4 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
@@ -1997,11 +1997,10 @@ static bool generateICarryBorrowInst(const SPIRV::IncomingCall *Call,
}
// We expect a builtin
-// Name(ptr sret([RetType]) %result, Type %operand1, Type %operand2)
-// where %result is a pointer to where the result of the builtin execution
-// is to be stored, and generate the following instructions:
+// RetType Name(Type %operand1, Type %operand2)
+// where RetType is a struct with two members of the same type as the operands,
+// and generate the following instruction:
// Res = Opcode RetType Operand1 Operand2
-// OpStore RetVariable Res
static bool generateMulExtendedInst(const SPIRV::IncomingCall *Call,
MachineIRBuilder &MIRBuilder,
SPIRVGlobalRegistry *GR) {
@@ -2011,36 +2010,28 @@ static bool generateMulExtendedInst(const SPIRV::IncomingCall *Call,
assert((Opcode == SPIRV::OpUMulExtended || Opcode == SPIRV::OpSMulExtended) &&
"Expected OpUMulExtended or OpSMulExtended");
- Register SRetReg = Call->Arguments[0];
- SPIRVTypeInst PtrRetType = GR->getSPIRVTypeForVReg(SRetReg);
- SPIRVTypeInst RetType = GR->getPointeeType(PtrRetType);
- if (!RetType)
- report_fatal_error("The first parameter must be a pointer");
- if (RetType->getOpcode() != SPIRV::OpTypeStruct)
+ SPIRVTypeInst RetType = Call->ReturnType;
+ if (!RetType || RetType->getOpcode() != SPIRV::OpTypeStruct)
report_fatal_error("Expected struct type result for the extended "
"multiplication builtins");
- SPIRVTypeInst OpType1 = GR->getSPIRVTypeForVReg(Call->Arguments[1]);
- SPIRVTypeInst OpType2 = GR->getSPIRVTypeForVReg(Call->Arguments[2]);
+ SPIRVTypeInst OpType1 = GR->getSPIRVTypeForVReg(Call->Arguments[0]);
+ SPIRVTypeInst OpType2 = GR->getSPIRVTypeForVReg(Call->Arguments[1]);
if (!OpType1 || !OpType2 || OpType1 != OpType2)
report_fatal_error("Operands must have the same type");
MachineRegisterInfo *MRI = MIRBuilder.getMRI();
- Register ResReg = MRI->createVirtualRegister(&SPIRV::iIDRegClass);
+ Register ResReg = Call->ReturnRegister;
if (const TargetRegisterClass *DstRC =
- MRI->getRegClassOrNull(Call->Arguments[1])) {
+ MRI->getRegClassOrNull(Call->Arguments[0])) {
MRI->setRegClass(ResReg, DstRC);
- MRI->setType(ResReg, MRI->getType(Call->Arguments[1]));
- } else {
- MRI->setType(ResReg, LLT::scalar(64));
}
GR->assignSPIRVTypeToVReg(RetType, ResReg, MIRBuilder.getMF());
MIRBuilder.buildInstr(Opcode)
.addDef(ResReg)
.addUse(GR->getSPIRVTypeID(RetType))
- .addUse(Call->Arguments[1])
- .addUse(Call->Arguments[2]);
- MIRBuilder.buildInstr(SPIRV::OpStore).addUse(SRetReg).addUse(ResReg);
+ .addUse(Call->Arguments[0])
+ .addUse(Call->Arguments[1]);
return true;
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.td b/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
index b30db2b9f718d..20fc3ff5bd29a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
@@ -678,8 +678,8 @@ defm : DemangledNativeBuiltin<"__spirv_IAddCarry", OpenCL_std, ICarryBorrow, 3,
defm : DemangledNativeBuiltin<"__spirv_ISubBorrow", OpenCL_std, ICarryBorrow, 3, 3, OpISubBorrowS>;
// MulExtended builtin records:
-defm : DemangledNativeBuiltin<"__spirv_UMulExtended", OpenCL_std, MulExtended, 3, 3, OpUMulExtended>;
-defm : DemangledNativeBuiltin<"__spirv_SMulExtended", OpenCL_std, MulExtended, 3, 3, OpSMulExtended>;
+defm : DemangledNativeBuiltin<"__spirv_UMulExtended", OpenCL_std, MulExtended, 2, 2, OpUMulExtended>;
+defm : DemangledNativeBuiltin<"__spirv_SMulExtended", OpenCL_std, MulExtended, 2, 2, OpSMulExtended>;
// cl_intel_split_work_group_barrier
defm : DemangledNativeBuiltin<"intel_work_group_barrier_arrive", OpenCL_std, Barrier, 1, 2, OpControlBarrierArriveINTEL>;
diff --git a/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll b/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
index f8c632526ee23..87ab7154b54fe 100644
--- a/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
+++ b/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
@@ -11,115 +11,86 @@
; CHECK-SPIRV-DAG: [[ushort:%[a-z0-9_]+]] = OpTypeInt 16
; CHECK-SPIRV-DAG: [[uint:%[a-z0-9_]+]] = OpTypeInt 32
; CHECK-SPIRV-DAG: [[ulong:%[a-z0-9_]+]] = OpTypeInt 64
-; CHECK-SPIRV-DAG: [[void:%[a-z0-9_]+]] = OpTypeVoid
; CHECK-SPIRV-DAG: [[i8struct:%[a-z0-9_]+]] = OpTypeStruct [[uchar]] [[uchar]]
-; CHECK-SPIRV-DAG: [[_ptr_Function_i8struct:%[a-z0-9_]+]] = OpTypePointer Function [[i8struct]]
; CHECK-SPIRV-DAG: [[i16struct:%[a-z0-9_]+]] = OpTypeStruct [[ushort]] [[ushort]]
-; CHECK-SPIRV-DAG: [[_ptr_Function_i16struct:%[a-z0-9_]+]] = OpTypePointer Function [[i16struct]]
; CHECK-SPIRV-DAG: [[i32struct:%[a-z0-9_]+]] = OpTypeStruct [[uint]] [[uint]]
-; CHECK-SPIRV-DAG: [[_ptr_Function_i32struct:%[a-z0-9_]+]] = OpTypePointer Function [[i32struct]]
; CHECK-SPIRV-DAG: [[i64struct:%[a-z0-9_]+]] = OpTypeStruct [[ulong]] [[ulong]]
-; CHECK-SPIRV-DAG: [[_ptr_Function_i64struct:%[a-z0-9_]+]] = OpTypePointer Function [[i64struct]]
; CHECK-SPIRV-DAG: [[v4uint:%[a-z0-9_]+]] = OpTypeVector [[uint]] 4
; CHECK-SPIRV-DAG: [[vecstruct:%[a-z0-9_]+]] = OpTypeStruct [[v4uint]] [[v4uint]]
-; CHECK-SPIRV-DAG: [[_ptr_Function_vecstruct:%[a-z0-9_]+]] = OpTypePointer Function [[vecstruct]]
-; CHECK-SPIRV-DAG: [[_ptr_Generic_i32struct:%[a-z0-9_]+]] = OpTypePointer Generic [[i32struct]]
-define spir_func void @test_builtin_smulextcc(i8 %a, i8 %b) {
+define spir_func %i8struct @test_builtin_smulextcc(i8 %a, i8 %b) {
entry:
- %0 = alloca %i8struct
- call void @_Z20__spirv_SMulExtendedcc(ptr sret (%i8struct) %0, i8 %a, i8 %b)
- ret void
+ %0 = call %i8struct @_Z20__spirv_SMulExtendedcc(i8 %a, i8 %b)
+ ret %i8struct %0
}
-; CHECK-SPIRV: [[a:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
-; CHECK-SPIRV: [[b:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
-; CHECK-SPIRV: [[entry:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_11:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i8struct]] Function
-; CHECK-SPIRV: [[var_12:%[a-z0-9_]+]] = OpSMulExtended [[i8struct]] [[a]] [[b]]
-; CHECK-SPIRV: OpStore [[var_11]] [[var_12]]
-; CHECK-SPIRV: OpReturn
-; CHECK-SPIRV: OpFunctionEnd
+; CHECK-SPIRV: [[a:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
+; CHECK-SPIRV-NEXT: [[b:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
+; CHECK-SPIRV-NEXT: [[entry:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res:%[a-z0-9_]+]] = OpSMulExtended [[i8struct]] [[a]] [[b]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-define spir_func void @test_builtin_smulextss(i16 %a, i16 %b) {
+define spir_func %i16struct @test_builtin_smulextss(i16 %a, i16 %b) {
entry:
- %0 = alloca %i16struct
- call void @_Z20__spirv_SMulExtendedss(ptr sret (%i16struct) %0, i16 %a, i16 %b)
- ret void
+ %0 = call %i16struct @_Z20__spirv_SMulExtendedss(i16 %a, i16 %b)
+ ret %i16struct %0
}
-; CHECK-SPIRV: [[a_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
-; CHECK-SPIRV: [[b_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
-; CHECK-SPIRV: [[entry_0:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_21:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i16struct]] Function
-; CHECK-SPIRV: [[var_22:%[a-z0-9_]+]] = OpSMulExtended [[i16struct]] [[a_0]] [[b_0]]
-; CHECK-SPIRV: OpStore [[var_21]] [[var_22]]
-; CHECK-SPIRV: OpReturn
-; CHECK-SPIRV: OpFunctionEnd
+; CHECK-SPIRV: [[a_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
+; CHECK-SPIRV-NEXT: [[b_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
+; CHECK-SPIRV-NEXT: [[entry_0:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res_0:%[a-z0-9_]+]] = OpSMulExtended [[i16struct]] [[a_0]] [[b_0]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res_0]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-define spir_func void @test_builtin_smulextii(i32 %a, i32 %b) {
+define spir_func %i32struct @test_builtin_smulextii(i32 %a, i32 %b) {
entry:
- %0 = alloca %i32struct
- call void @_Z20__spirv_SMulExtendedii(ptr sret (%i32struct) %0, i32 %a, i32 %b)
- ret void
+ %0 = call %i32struct @_Z20__spirv_SMulExtendedii(i32 %a, i32 %b)
+ ret %i32struct %0
}
-; CHECK-SPIRV: [[a_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
-; CHECK-SPIRV: [[b_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
-; CHECK-SPIRV: [[entry_1:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_31:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct]] Function
-; CHECK-SPIRV: [[var_32:%[a-z0-9_]+]] = OpSMulExtended [[i32struct]] [[a_1]] [[b_1]]
-; CHECK-SPIRV: OpStore [[var_31]] [[var_32]]
-; CHECK-SPIRV: OpReturn
-; CHECK-SPIRV: OpFunctionEnd
+; CHECK-SPIRV: [[a_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV-NEXT: [[b_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV-NEXT: [[entry_1:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res_1:%[a-z0-9_]+]] = OpSMulExtended [[i32struct]] [[a_1]] [[b_1]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res_1]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-define spir_func void @test_builtin_smulextll(i64 %a, i64 %b) {
+define spir_func %i64struct @test_builtin_smulextll(i64 %a, i64 %b) {
entry:
- %0 = alloca %i64struct
- call void @_Z20__spirv_SMulExtendedll(ptr sret (%i64struct) %0, i64 %a, i64 %b)
- ret void
+ %0 = call %i64struct @_Z20__spirv_SMulExtendedll(i64 %a, i64 %b)
+ ret %i64struct %0
}
-; CHECK-SPIRV: [[a_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
-; CHECK-SPIRV: [[b_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
-; CHECK-SPIRV: [[entry_2:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_41:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i64struct]] Function
-; CHECK-SPIRV: [[var_42:%[a-z0-9_]+]] = OpSMulExtended [[i64struct]] [[a_2]] [[b_2]]
-; CHECK-SPIRV: OpStore [[var_41]] [[var_42]]
-; CHECK-SPIRV: OpReturn
-; CHECK-SPIRV: OpFunctionEnd
+; CHECK-SPIRV: [[a_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
+; CHECK-SPIRV-NEXT: [[b_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
+; CHECK-SPIRV-NEXT: [[entry_2:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res_2:%[a-z0-9_]+]] = OpSMulExtended [[i64struct]] [[a_2]] [[b_2]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res_2]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-define spir_func void @test_builtin_smulextDv4_xS_(<4 x i32> %a, <4 x i32> %b) {
+define spir_func %vecstruct @test_builtin_smulextDv4_xS_(<4 x i32> %a, <4 x i32> %b) {
entry:
- %0 = alloca %vecstruct
- call void @_Z20__spirv_SMulExtendedDv4_iS_(ptr sret (%vecstruct) %0, <4 x i32> %a, <4 x i32> %b)
- ret void
+ %0 = call %vecstruct @_Z20__spirv_SMulExtendedDv4_iS_(<4 x i32> %a, <4 x i32> %b)
+ ret %vecstruct %0
}
-; CHECK-SPIRV: [[a_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
-; CHECK-SPIRV: [[b_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
-; CHECK-SPIRV: [[entry_3:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_51:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_vecstruct]] Function
-; CHECK-SPIRV: [[var_52:%[a-z0-9_]+]] = OpSMulExtended [[vecstruct]] [[a_3]] [[b_3]]
-; CHECK-SPIRV: OpStore [[var_51]] [[var_52]]
-; CHECK-SPIRV: OpReturn
-; CHECK-SPIRV: OpFunctionEnd
+; CHECK-SPIRV: [[a_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
+; CHECK-SPIRV-NEXT: [[b_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
+; CHECK-SPIRV-NEXT: [[entry_3:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res_3:%[a-z0-9_]+]] = OpSMulExtended [[vecstruct]] [[a_3]] [[b_3]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res_3]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-%struct.anon = type { i32, i32 }
-
-define spir_func void @test_builtin_smulext_anon(i32 %a, i32 %b) {
+define spir_func %i32struct @test_builtin_smulext_same_arg(i32 %a) {
entry:
- %0 = alloca %struct.anon
- %1 = addrspacecast ptr %0 to ptr addrspace(4)
- call spir_func void @_Z20__spirv_SMulExtendedIiiE4anonIT_T0_ES1_S2_(ptr addrspace(4) sret(%struct.anon) align 4 %1, i32 %a, i32 %b)
- ret void
+ %0 = call %i32struct @_Z20__spirv_SMulExtendedii(i32 %a, i32 %a)
+ ret %i32struct %0
}
-; CHECK-SPIRV: [[a_4:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
-; CHECK-SPIRV: [[b_4:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
-; CHECK-SPIRV: [[entry_4:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_59:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct]] Function
-; CHECK-SPIRV: [[var_61:%[a-z0-9_]+]] = OpPtrCastToGeneric [[_ptr_Generic_i32struct]] [[var_59]]
-; CHECK-SPIRV: [[var_62:%[a-z0-9_]+]] = OpSMulExtended [[i32struct]] [[a_4]] [[b_4]]
-; CHECK-SPIRV: OpStore [[var_61]] [[var_62]]
+; CHECK-SPIRV: [[a_4:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV-NEXT: [[entry_4:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res_4:%[a-z0-9_]+]] = OpSMulExtended [[i32struct]] [[a_4]] [[a_4]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res_4]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-declare void @_Z20__spirv_SMulExtendedIiiE4anonIT_T0_ES1_S2_(ptr addrspace(4) sret(%struct.anon) align 4, i32, i32)
-declare void @_Z20__spirv_SMulExtendedcc(ptr sret(%i8struct), i8, i8)
-declare void @_Z20__spirv_SMulExtendedss(ptr sret(%i16struct), i16, i16)
-declare void @_Z20__spirv_SMulExtendedii(ptr sret(%i32struct), i32, i32)
-declare void @_Z20__spirv_SMulExtendedll(ptr sret(%i64struct), i64, i64)
-declare void @_Z20__spirv_SMulExtendedDv4_iS_(ptr sret (%vecstruct), <4 x i32>, <4 x i32>)
+declare %i8struct @_Z20__spirv_SMulExtendedcc(i8, i8)
+declare %i16struct @_Z20__spirv_SMulExtendedss(i16, i16)
+declare %i32struct @_Z20__spirv_SMulExtendedii(i32, i32)
+declare %i64struct @_Z20__spirv_SMulExtendedll(i64, i64)
+declare %vecstruct @_Z20__spirv_SMulExtendedDv4_iS_(<4 x i32>, <4 x i32>)
diff --git a/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll b/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
index 1f7980b2e5c47..aaa1e07c32650 100644
--- a/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
+++ b/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
@@ -11,115 +11,86 @@
; CHECK-SPIRV-DAG: [[ushort:%[a-z0-9_]+]] = OpTypeInt 16
; CHECK-SPIRV-DAG: [[uint:%[a-z0-9_]+]] = OpTypeInt 32
; CHECK-SPIRV-DAG: [[ulong:%[a-z0-9_]+]] = OpTypeInt 64
-; CHECK-SPIRV-DAG: [[void:%[a-z0-9_]+]] = OpTypeVoid
; CHECK-SPIRV-DAG: [[i8struct:%[a-z0-9_]+]] = OpTypeStruct [[uchar]] [[uchar]]
-; CHECK-SPIRV-DAG: [[_ptr_Function_i8struct:%[a-z0-9_]+]] = OpTypePointer Function [[i8struct]]
; CHECK-SPIRV-DAG: [[i16struct:%[a-z0-9_]+]] = OpTypeStruct [[ushort]] [[ushort]]
-; CHECK-SPIRV-DAG: [[_ptr_Function_i16struct:%[a-z0-9_]+]] = OpTypePointer Function [[i16struct]]
; CHECK-SPIRV-DAG: [[i32struct:%[a-z0-9_]+]] = OpTypeStruct [[uint]] [[uint]]
-; CHECK-SPIRV-DAG: [[_ptr_Function_i32struct:%[a-z0-9_]+]] = OpTypePointer Function [[i32struct]]
; CHECK-SPIRV-DAG: [[i64struct:%[a-z0-9_]+]] = OpTypeStruct [[ulong]] [[ulong]]
-; CHECK-SPIRV-DAG: [[_ptr_Function_i64struct:%[a-z0-9_]+]] = OpTypePointer Function [[i64struct]]
; CHECK-SPIRV-DAG: [[v4uint:%[a-z0-9_]+]] = OpTypeVector [[uint]] 4
; CHECK-SPIRV-DAG: [[vecstruct:%[a-z0-9_]+]] = OpTypeStruct [[v4uint]] [[v4uint]]
-; CHECK-SPIRV-DAG: [[_ptr_Function_vecstruct:%[a-z0-9_]+]] = OpTypePointer Function [[vecstruct]]
-; CHECK-SPIRV-DAG: [[_ptr_Generic_i32struct:%[a-z0-9_]+]] = OpTypePointer Generic [[i32struct]]
-define spir_func void @test_builtin_umulextcc(i8 %a, i8 %b) {
+define spir_func %i8struct @test_builtin_umulextcc(i8 %a, i8 %b) {
entry:
- %0 = alloca %i8struct
- call void @_Z20__spirv_UMulExtendedcc(ptr sret (%i8struct) %0, i8 %a, i8 %b)
- ret void
+ %0 = call %i8struct @_Z20__spirv_UMulExtendedcc(i8 %a, i8 %b)
+ ret %i8struct %0
}
-; CHECK-SPIRV: [[a:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
-; CHECK-SPIRV: [[b:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
-; CHECK-SPIRV: [[entry:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_11:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i8struct]] Function
-; CHECK-SPIRV: [[var_12:%[a-z0-9_]+]] = OpUMulExtended [[i8struct]] [[a]] [[b]]
-; CHECK-SPIRV: OpStore [[var_11]] [[var_12]]
-; CHECK-SPIRV: OpReturn
-; CHECK-SPIRV: OpFunctionEnd
+; CHECK-SPIRV: [[a:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
+; CHECK-SPIRV-NEXT: [[b:%[a-z0-9_]+]] = OpFunctionParameter [[uchar]]
+; CHECK-SPIRV-NEXT: [[entry:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res:%[a-z0-9_]+]] = OpUMulExtended [[i8struct]] [[a]] [[b]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-define spir_func void @test_builtin_umulextss(i16 %a, i16 %b) {
+define spir_func %i16struct @test_builtin_umulextss(i16 %a, i16 %b) {
entry:
- %0 = alloca %i16struct
- call void @_Z20__spirv_UMulExtendedss(ptr sret (%i16struct) %0, i16 %a, i16 %b)
- ret void
+ %0 = call %i16struct @_Z20__spirv_UMulExtendedss(i16 %a, i16 %b)
+ ret %i16struct %0
}
-; CHECK-SPIRV: [[a_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
-; CHECK-SPIRV: [[b_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
-; CHECK-SPIRV: [[entry_0:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_21:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i16struct]] Function
-; CHECK-SPIRV: [[var_22:%[a-z0-9_]+]] = OpUMulExtended [[i16struct]] [[a_0]] [[b_0]]
-; CHECK-SPIRV: OpStore [[var_21]] [[var_22]]
-; CHECK-SPIRV: OpReturn
-; CHECK-SPIRV: OpFunctionEnd
+; CHECK-SPIRV: [[a_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
+; CHECK-SPIRV-NEXT: [[b_0:%[a-z0-9_]+]] = OpFunctionParameter [[ushort]]
+; CHECK-SPIRV-NEXT: [[entry_0:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res_0:%[a-z0-9_]+]] = OpUMulExtended [[i16struct]] [[a_0]] [[b_0]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res_0]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-define spir_func void @test_builtin_umulextii(i32 %a, i32 %b) {
+define spir_func %i32struct @test_builtin_umulextii(i32 %a, i32 %b) {
entry:
- %0 = alloca %i32struct
- call void @_Z20__spirv_UMulExtendedii(ptr sret (%i32struct) %0, i32 %a, i32 %b)
- ret void
+ %0 = call %i32struct @_Z20__spirv_UMulExtendedii(i32 %a, i32 %b)
+ ret %i32struct %0
}
-; CHECK-SPIRV: [[a_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
-; CHECK-SPIRV: [[b_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
-; CHECK-SPIRV: [[entry_1:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_31:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct]] Function
-; CHECK-SPIRV: [[var_32:%[a-z0-9_]+]] = OpUMulExtended [[i32struct]] [[a_1]] [[b_1]]
-; CHECK-SPIRV: OpStore [[var_31]] [[var_32]]
-; CHECK-SPIRV: OpReturn
-; CHECK-SPIRV: OpFunctionEnd
+; CHECK-SPIRV: [[a_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV-NEXT: [[b_1:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV-NEXT: [[entry_1:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res_1:%[a-z0-9_]+]] = OpUMulExtended [[i32struct]] [[a_1]] [[b_1]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res_1]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-define spir_func void @test_builtin_umulextll(i64 %a, i64 %b) {
+define spir_func %i64struct @test_builtin_umulextll(i64 %a, i64 %b) {
entry:
- %0 = alloca %i64struct
- call void @_Z20__spirv_UMulExtendedll(ptr sret (%i64struct) %0, i64 %a, i64 %b)
- ret void
+ %0 = call %i64struct @_Z20__spirv_UMulExtendedll(i64 %a, i64 %b)
+ ret %i64struct %0
}
-; CHECK-SPIRV: [[a_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
-; CHECK-SPIRV: [[b_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
-; CHECK-SPIRV: [[entry_2:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_41:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i64struct]] Function
-; CHECK-SPIRV: [[var_42:%[a-z0-9_]+]] = OpUMulExtended [[i64struct]] [[a_2]] [[b_2]]
-; CHECK-SPIRV: OpStore [[var_41]] [[var_42]]
-; CHECK-SPIRV: OpReturn
-; CHECK-SPIRV: OpFunctionEnd
+; CHECK-SPIRV: [[a_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
+; CHECK-SPIRV-NEXT: [[b_2:%[a-z0-9_]+]] = OpFunctionParameter [[ulong]]
+; CHECK-SPIRV-NEXT: [[entry_2:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res_2:%[a-z0-9_]+]] = OpUMulExtended [[i64struct]] [[a_2]] [[b_2]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res_2]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-define spir_func void @test_builtin_umulextDv4_xS_(<4 x i32> %a, <4 x i32> %b) {
+define spir_func %vecstruct @test_builtin_umulextDv4_xS_(<4 x i32> %a, <4 x i32> %b) {
entry:
- %0 = alloca %vecstruct
- call void @_Z20__spirv_UMulExtendedDv4_iS_(ptr sret (%vecstruct) %0, <4 x i32> %a, <4 x i32> %b)
- ret void
+ %0 = call %vecstruct @_Z20__spirv_UMulExtendedDv4_iS_(<4 x i32> %a, <4 x i32> %b)
+ ret %vecstruct %0
}
-; CHECK-SPIRV: [[a_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
-; CHECK-SPIRV: [[b_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
-; CHECK-SPIRV: [[entry_3:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_51:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_vecstruct]] Function
-; CHECK-SPIRV: [[var_52:%[a-z0-9_]+]] = OpUMulExtended [[vecstruct]] [[a_3]] [[b_3]]
-; CHECK-SPIRV: OpStore [[var_51]] [[var_52]]
-; CHECK-SPIRV: OpReturn
-; CHECK-SPIRV: OpFunctionEnd
+; CHECK-SPIRV: [[a_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
+; CHECK-SPIRV-NEXT: [[b_3:%[a-z0-9_]+]] = OpFunctionParameter [[v4uint]]
+; CHECK-SPIRV-NEXT: [[entry_3:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res_3:%[a-z0-9_]+]] = OpUMulExtended [[vecstruct]] [[a_3]] [[b_3]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res_3]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-%struct.anon = type { i32, i32 }
-
-define spir_func void @test_builtin_umulext_anon(i32 %a, i32 %b) {
+define spir_func %i32struct @test_builtin_umulext_same_arg(i32 %a) {
entry:
- %0 = alloca %struct.anon
- %1 = addrspacecast ptr %0 to ptr addrspace(4)
- call spir_func void @_Z20__spirv_UMulExtendedIiiE4anonIT_T0_ES1_S2_(ptr addrspace(4) sret(%struct.anon) align 4 %1, i32 %a, i32 %b)
- ret void
+ %0 = call %i32struct @_Z20__spirv_UMulExtendedii(i32 %a, i32 %a)
+ ret %i32struct %0
}
-; CHECK-SPIRV: [[a_4:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
-; CHECK-SPIRV: [[b_4:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
-; CHECK-SPIRV: [[entry_4:%[a-z0-9_]+]] = OpLabel
-; CHECK-SPIRV: [[var_59:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct]] Function
-; CHECK-SPIRV: [[var_61:%[a-z0-9_]+]] = OpPtrCastToGeneric [[_ptr_Generic_i32struct]] [[var_59]]
-; CHECK-SPIRV: [[var_62:%[a-z0-9_]+]] = OpUMulExtended [[i32struct]] [[a_4]] [[b_4]]
-; CHECK-SPIRV: OpStore [[var_61]] [[var_62]]
+; CHECK-SPIRV: [[a_4:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV-NEXT: [[entry_4:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV-NEXT: [[res_4:%[a-z0-9_]+]] = OpUMulExtended [[i32struct]] [[a_4]] [[a_4]]
+; CHECK-SPIRV-NEXT: OpReturnValue [[res_4]]
+; CHECK-SPIRV-NEXT: OpFunctionEnd
-declare void @_Z20__spirv_UMulExtendedIiiE4anonIT_T0_ES1_S2_(ptr addrspace(4) sret(%struct.anon) align 4, i32, i32)
-declare void @_Z20__spirv_UMulExtendedcc(ptr sret(%i8struct), i8, i8)
-declare void @_Z20__spirv_UMulExtendedss(ptr sret(%i16struct), i16, i16)
-declare void @_Z20__spirv_UMulExtendedii(ptr sret(%i32struct), i32, i32)
-declare void @_Z20__spirv_UMulExtendedll(ptr sret(%i64struct), i64, i64)
-declare void @_Z20__spirv_UMulExtendedDv4_iS_(ptr sret (%vecstruct), <4 x i32>, <4 x i32>)
+declare %i8struct @_Z20__spirv_UMulExtendedcc(i8, i8)
+declare %i16struct @_Z20__spirv_UMulExtendedss(i16, i16)
+declare %i32struct @_Z20__spirv_UMulExtendedii(i32, i32)
+declare %i64struct @_Z20__spirv_UMulExtendedll(i64, i64)
+declare %vecstruct @_Z20__spirv_UMulExtendedDv4_iS_(<4 x i32>, <4 x i32>)
>From 59bce2fdbb4129feff7db17fad0ff24faa391d8f Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Thu, 19 Mar 2026 17:29:15 +0100
Subject: [PATCH 3/6] Add return type internals check
---
llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
index 0f00cfed326b4..233cea5063688 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
@@ -2014,11 +2014,22 @@ static bool generateMulExtendedInst(const SPIRV::IncomingCall *Call,
if (!RetType || RetType->getOpcode() != SPIRV::OpTypeStruct)
report_fatal_error("Expected struct type result for the extended "
"multiplication builtins");
+ if (RetType->getNumOperands() != 3)
+ report_fatal_error("Expected struct with exactly two members for the "
+ "extended multiplication builtins");
+ SPIRVTypeInst Member0Type =
+ GR->getSPIRVTypeForVReg(RetType->getOperand(1).getReg());
+ SPIRVTypeInst Member1Type =
+ GR->getSPIRVTypeForVReg(RetType->getOperand(2).getReg());
+ if (!Member0Type || !Member1Type || Member0Type != Member1Type)
+ report_fatal_error("Both struct members must be the same type");
SPIRVTypeInst OpType1 = GR->getSPIRVTypeForVReg(Call->Arguments[0]);
SPIRVTypeInst OpType2 = GR->getSPIRVTypeForVReg(Call->Arguments[1]);
if (!OpType1 || !OpType2 || OpType1 != OpType2)
report_fatal_error("Operands must have the same type");
+ if (OpType1 != Member0Type)
+ report_fatal_error("Operand type must match the struct member type");
MachineRegisterInfo *MRI = MIRBuilder.getMRI();
Register ResReg = Call->ReturnRegister;
>From b30e54659f076d05adcbb32b367232d5bafd25fe Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Tue, 24 Mar 2026 11:08:45 +0100
Subject: [PATCH 4/6] Support sret option as well
---
llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp | 75 ++++++++++++++-----
llvm/lib/Target/SPIRV/SPIRVBuiltins.td | 4 +-
.../CodeGen/SPIRV/smulextended-builtin.ll | 16 ++++
.../CodeGen/SPIRV/umulextended-builtin.ll | 18 +++++
4 files changed, 94 insertions(+), 19 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
index 233cea5063688..5922590c39029 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
@@ -1996,11 +1996,18 @@ static bool generateICarryBorrowInst(const SPIRV::IncomingCall *Call,
return true;
}
-// We expect a builtin
+// We expect a builtin in one of two forms:
+//
+// (1) sret convention (3 arguments):
+// void Name(ptr sret([RetType]) %result, Type %operand1, Type %operand2)
+// => Res = Opcode RetType Operand1 Operand2
+// OpStore %result Res
+//
+// (2) direct return convention (2 arguments):
// RetType Name(Type %operand1, Type %operand2)
-// where RetType is a struct with two members of the same type as the operands,
-// and generate the following instruction:
-// Res = Opcode RetType Operand1 Operand2
+// => Res = Opcode RetType Operand1 Operand2
+//
+// RetType is a struct with two members of the same type as the operands.
static bool generateMulExtendedInst(const SPIRV::IncomingCall *Call,
MachineIRBuilder &MIRBuilder,
SPIRVGlobalRegistry *GR) {
@@ -2010,7 +2017,22 @@ static bool generateMulExtendedInst(const SPIRV::IncomingCall *Call,
assert((Opcode == SPIRV::OpUMulExtended || Opcode == SPIRV::OpSMulExtended) &&
"Expected OpUMulExtended or OpSMulExtended");
- SPIRVTypeInst RetType = Call->ReturnType;
+ const bool IsSret =
+ !Call->ReturnType || Call->ReturnType->getOpcode() == SPIRV::OpTypeVoid;
+ Register Op1Reg = IsSret ? Call->Arguments[1] : Call->Arguments[0];
+ Register Op2Reg = IsSret ? Call->Arguments[2] : Call->Arguments[1];
+
+ SPIRVTypeInst RetType;
+ if (IsSret) {
+ Register SRetReg = Call->Arguments[0];
+ SPIRVTypeInst PtrRetType = GR->getSPIRVTypeForVReg(SRetReg);
+ RetType = GR->getPointeeType(PtrRetType);
+ if (!RetType)
+ report_fatal_error("The first parameter must be a pointer");
+ } else {
+ RetType = Call->ReturnType;
+ }
+
if (!RetType || RetType->getOpcode() != SPIRV::OpTypeStruct)
report_fatal_error("Expected struct type result for the extended "
"multiplication builtins");
@@ -2024,25 +2046,44 @@ static bool generateMulExtendedInst(const SPIRV::IncomingCall *Call,
if (!Member0Type || !Member1Type || Member0Type != Member1Type)
report_fatal_error("Both struct members must be the same type");
- SPIRVTypeInst OpType1 = GR->getSPIRVTypeForVReg(Call->Arguments[0]);
- SPIRVTypeInst OpType2 = GR->getSPIRVTypeForVReg(Call->Arguments[1]);
+ SPIRVTypeInst OpType1 = GR->getSPIRVTypeForVReg(Op1Reg);
+ SPIRVTypeInst OpType2 = GR->getSPIRVTypeForVReg(Op2Reg);
if (!OpType1 || !OpType2 || OpType1 != OpType2)
report_fatal_error("Operands must have the same type");
if (OpType1 != Member0Type)
report_fatal_error("Operand type must match the struct member type");
MachineRegisterInfo *MRI = MIRBuilder.getMRI();
- Register ResReg = Call->ReturnRegister;
- if (const TargetRegisterClass *DstRC =
- MRI->getRegClassOrNull(Call->Arguments[0])) {
- MRI->setRegClass(ResReg, DstRC);
+
+ if (IsSret) {
+ Register ResReg = MRI->createVirtualRegister(&SPIRV::iIDRegClass);
+ if (const TargetRegisterClass *DstRC = MRI->getRegClassOrNull(Op1Reg)) {
+ MRI->setRegClass(ResReg, DstRC);
+ MRI->setType(ResReg, MRI->getType(Op1Reg));
+ } else {
+ MRI->setType(ResReg, LLT::scalar(64));
+ }
+ GR->assignSPIRVTypeToVReg(RetType, ResReg, MIRBuilder.getMF());
+ MIRBuilder.buildInstr(Opcode)
+ .addDef(ResReg)
+ .addUse(GR->getSPIRVTypeID(RetType))
+ .addUse(Op1Reg)
+ .addUse(Op2Reg);
+ MIRBuilder.buildInstr(SPIRV::OpStore)
+ .addUse(Call->Arguments[0])
+ .addUse(ResReg);
+ } else {
+ Register ResReg = Call->ReturnRegister;
+ if (const TargetRegisterClass *DstRC = MRI->getRegClassOrNull(Op1Reg)) {
+ MRI->setRegClass(ResReg, DstRC);
+ }
+ GR->assignSPIRVTypeToVReg(RetType, ResReg, MIRBuilder.getMF());
+ MIRBuilder.buildInstr(Opcode)
+ .addDef(ResReg)
+ .addUse(GR->getSPIRVTypeID(RetType))
+ .addUse(Op1Reg)
+ .addUse(Op2Reg);
}
- GR->assignSPIRVTypeToVReg(RetType, ResReg, MIRBuilder.getMF());
- MIRBuilder.buildInstr(Opcode)
- .addDef(ResReg)
- .addUse(GR->getSPIRVTypeID(RetType))
- .addUse(Call->Arguments[0])
- .addUse(Call->Arguments[1]);
return true;
}
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.td b/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
index 20fc3ff5bd29a..44b8105e14fc9 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
@@ -678,8 +678,8 @@ defm : DemangledNativeBuiltin<"__spirv_IAddCarry", OpenCL_std, ICarryBorrow, 3,
defm : DemangledNativeBuiltin<"__spirv_ISubBorrow", OpenCL_std, ICarryBorrow, 3, 3, OpISubBorrowS>;
// MulExtended builtin records:
-defm : DemangledNativeBuiltin<"__spirv_UMulExtended", OpenCL_std, MulExtended, 2, 2, OpUMulExtended>;
-defm : DemangledNativeBuiltin<"__spirv_SMulExtended", OpenCL_std, MulExtended, 2, 2, OpSMulExtended>;
+defm : DemangledNativeBuiltin<"__spirv_UMulExtended", OpenCL_std, MulExtended, 2, 3, OpUMulExtended>;
+defm : DemangledNativeBuiltin<"__spirv_SMulExtended", OpenCL_std, MulExtended, 2, 3, OpSMulExtended>;
// cl_intel_split_work_group_barrier
defm : DemangledNativeBuiltin<"intel_work_group_barrier_arrive", OpenCL_std, Barrier, 1, 2, OpControlBarrierArriveINTEL>;
diff --git a/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll b/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
index 87ab7154b54fe..3d452e6b9c972 100644
--- a/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
+++ b/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
@@ -18,6 +18,21 @@
; CHECK-SPIRV-DAG: [[v4uint:%[a-z0-9_]+]] = OpTypeVector [[uint]] 4
; CHECK-SPIRV-DAG: [[vecstruct:%[a-z0-9_]+]] = OpTypeStruct [[v4uint]] [[v4uint]]
+define spir_func void @test_builtin_smulext_sret(i32 %a, i32 %b) {
+ entry:
+ %0 = alloca %i32struct
+ call void @__spirv_SMulExtended(ptr sret (%i32struct) %0, i32 %a, i32 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_s:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV-NEXT: [[b_s:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV-NEXT: [[entry_s:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_s:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct:%[a-z0-9_]+]] Function
+; CHECK-SPIRV: [[res_s:%[a-z0-9_]+]] = OpSMulExtended [[i32struct]] [[a_s]] [[b_s]]
+; CHECK-SPIRV-NEXT: OpStore [[var_s]] [[res_s]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV-NEXT: OpFunctionEnd
+
define spir_func %i8struct @test_builtin_smulextcc(i8 %a, i8 %b) {
entry:
%0 = call %i8struct @_Z20__spirv_SMulExtendedcc(i8 %a, i8 %b)
@@ -94,3 +109,4 @@ declare %i16struct @_Z20__spirv_SMulExtendedss(i16, i16)
declare %i32struct @_Z20__spirv_SMulExtendedii(i32, i32)
declare %i64struct @_Z20__spirv_SMulExtendedll(i64, i64)
declare %vecstruct @_Z20__spirv_SMulExtendedDv4_iS_(<4 x i32>, <4 x i32>)
+declare void @__spirv_SMulExtended(ptr sret (%i32struct), i32, i32)
diff --git a/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll b/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
index aaa1e07c32650..071d1909ca030 100644
--- a/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
+++ b/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
@@ -18,6 +18,23 @@
; CHECK-SPIRV-DAG: [[v4uint:%[a-z0-9_]+]] = OpTypeVector [[uint]] 4
; CHECK-SPIRV-DAG: [[vecstruct:%[a-z0-9_]+]] = OpTypeStruct [[v4uint]] [[v4uint]]
+; The sret test is placed first because its unmangled name causes it to be
+; emitted before the mangled-name functions in the SPIR-V output.
+define spir_func void @test_builtin_umulext_sret(i32 %a, i32 %b) {
+ entry:
+ %0 = alloca %i32struct
+ call void @__spirv_UMulExtended(ptr sret (%i32struct) %0, i32 %a, i32 %b)
+ ret void
+}
+; CHECK-SPIRV: [[a_s:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV-NEXT: [[b_s:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV-NEXT: [[entry_s:%[a-z0-9_]+]] = OpLabel
+; CHECK-SPIRV: [[var_s:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct:%[a-z0-9_]+]] Function
+; CHECK-SPIRV: [[res_s:%[a-z0-9_]+]] = OpUMulExtended [[i32struct]] [[a_s]] [[b_s]]
+; CHECK-SPIRV-NEXT: OpStore [[var_s]] [[res_s]]
+; CHECK-SPIRV: OpReturn
+; CHECK-SPIRV-NEXT: OpFunctionEnd
+
define spir_func %i8struct @test_builtin_umulextcc(i8 %a, i8 %b) {
entry:
%0 = call %i8struct @_Z20__spirv_UMulExtendedcc(i8 %a, i8 %b)
@@ -94,3 +111,4 @@ declare %i16struct @_Z20__spirv_UMulExtendedss(i16, i16)
declare %i32struct @_Z20__spirv_UMulExtendedii(i32, i32)
declare %i64struct @_Z20__spirv_UMulExtendedll(i64, i64)
declare %vecstruct @_Z20__spirv_UMulExtendedDv4_iS_(<4 x i32>, <4 x i32>)
+declare void @__spirv_UMulExtended(ptr sret (%i32struct), i32, i32)
>From dfc550fa7391fe85529e6b9ffd3dd7587210b151 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Fri, 27 Mar 2026 17:33:49 +0100
Subject: [PATCH 5/6] Address review comments
---
llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp | 63 +++++++++----------
.../CodeGen/SPIRV/smulextended-builtin.ll | 2 +-
.../CodeGen/SPIRV/umulextended-builtin.ll | 2 +-
3 files changed, 30 insertions(+), 37 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
index 5922590c39029..f49dedc5dd349 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
@@ -1941,6 +1941,28 @@ static bool generateWaveInst(const SPIRV::IncomingCall *Call,
/* isConst= */ false, /* LinkageType= */ std::nullopt);
}
+// Build a SPIR-V instruction with struct return via sret pointer:
+// Res = Opcode RetType Op1 Op2
+// OpStore SRetReg Res
+static void buildSRetInst(unsigned Opcode, Register SRetReg, Register Op1Reg,
+ Register Op2Reg, SPIRVTypeInst RetType,
+ MachineIRBuilder &MIRBuilder,
+ SPIRVGlobalRegistry *GR) {
+ MachineRegisterInfo *MRI = MIRBuilder.getMRI();
+ Register ResReg = MRI->createVirtualRegister(&SPIRV::iIDRegClass);
+ if (const TargetRegisterClass *DstRC = MRI->getRegClassOrNull(Op1Reg)) {
+ MRI->setRegClass(ResReg, DstRC);
+ MRI->setType(ResReg, MRI->getType(Op1Reg));
+ }
+ GR->assignSPIRVTypeToVReg(RetType, ResReg, MIRBuilder.getMF());
+ MIRBuilder.buildInstr(Opcode)
+ .addDef(ResReg)
+ .addUse(GR->getSPIRVTypeID(RetType))
+ .addUse(Op1Reg)
+ .addUse(Op2Reg);
+ MIRBuilder.buildInstr(SPIRV::OpStore).addUse(SRetReg).addUse(ResReg);
+}
+
// We expect a builtin
// Name(ptr sret([RetType]) %result, Type %operand1, Type %operand1)
// where %result is a pointer to where the result of the builtin execution
@@ -1977,22 +1999,8 @@ static bool generateICarryBorrowInst(const SPIRV::IncomingCall *Call,
break;
}
- MachineRegisterInfo *MRI = MIRBuilder.getMRI();
- Register ResReg = MRI->createVirtualRegister(&SPIRV::iIDRegClass);
- if (const TargetRegisterClass *DstRC =
- MRI->getRegClassOrNull(Call->Arguments[1])) {
- MRI->setRegClass(ResReg, DstRC);
- MRI->setType(ResReg, MRI->getType(Call->Arguments[1]));
- } else {
- MRI->setType(ResReg, LLT::scalar(64));
- }
- GR->assignSPIRVTypeToVReg(RetType, ResReg, MIRBuilder.getMF());
- MIRBuilder.buildInstr(Opcode)
- .addDef(ResReg)
- .addUse(GR->getSPIRVTypeID(RetType))
- .addUse(Call->Arguments[1])
- .addUse(Call->Arguments[2]);
- MIRBuilder.buildInstr(SPIRV::OpStore).addUse(SRetReg).addUse(ResReg);
+ buildSRetInst(Opcode, SRetReg, Call->Arguments[1], Call->Arguments[2],
+ RetType, MIRBuilder, GR);
return true;
}
@@ -2022,7 +2030,7 @@ static bool generateMulExtendedInst(const SPIRV::IncomingCall *Call,
Register Op1Reg = IsSret ? Call->Arguments[1] : Call->Arguments[0];
Register Op2Reg = IsSret ? Call->Arguments[2] : Call->Arguments[1];
- SPIRVTypeInst RetType;
+ SPIRVTypeInst RetType = nullptr;
if (IsSret) {
Register SRetReg = Call->Arguments[0];
SPIRVTypeInst PtrRetType = GR->getSPIRVTypeForVReg(SRetReg);
@@ -2053,26 +2061,11 @@ static bool generateMulExtendedInst(const SPIRV::IncomingCall *Call,
if (OpType1 != Member0Type)
report_fatal_error("Operand type must match the struct member type");
- MachineRegisterInfo *MRI = MIRBuilder.getMRI();
-
if (IsSret) {
- Register ResReg = MRI->createVirtualRegister(&SPIRV::iIDRegClass);
- if (const TargetRegisterClass *DstRC = MRI->getRegClassOrNull(Op1Reg)) {
- MRI->setRegClass(ResReg, DstRC);
- MRI->setType(ResReg, MRI->getType(Op1Reg));
- } else {
- MRI->setType(ResReg, LLT::scalar(64));
- }
- GR->assignSPIRVTypeToVReg(RetType, ResReg, MIRBuilder.getMF());
- MIRBuilder.buildInstr(Opcode)
- .addDef(ResReg)
- .addUse(GR->getSPIRVTypeID(RetType))
- .addUse(Op1Reg)
- .addUse(Op2Reg);
- MIRBuilder.buildInstr(SPIRV::OpStore)
- .addUse(Call->Arguments[0])
- .addUse(ResReg);
+ buildSRetInst(Opcode, Call->Arguments[0], Op1Reg, Op2Reg, RetType,
+ MIRBuilder, GR);
} else {
+ MachineRegisterInfo *MRI = MIRBuilder.getMRI();
Register ResReg = Call->ReturnRegister;
if (const TargetRegisterClass *DstRC = MRI->getRegClassOrNull(Op1Reg)) {
MRI->setRegClass(ResReg, DstRC);
diff --git a/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll b/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
index 3d452e6b9c972..57c11f6285d30 100644
--- a/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
+++ b/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
@@ -1,4 +1,4 @@
-; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; 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 %}
%i8struct = type {i8, i8}
diff --git a/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll b/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
index 071d1909ca030..2d310c9b0c786 100644
--- a/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
+++ b/llvm/test/CodeGen/SPIRV/umulextended-builtin.ll
@@ -1,4 +1,4 @@
-; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; 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 %}
%i8struct = type {i8, i8}
>From 10abbe3c487913f090b6e004e59ae21910c31195 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <gooddoog at student.su>
Date: Thu, 16 Apr 2026 15:36:32 +0200
Subject: [PATCH 6/6] Update llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
Co-authored-by: Dmitry Sidorov <dsidorov at amd.com>
---
llvm/test/CodeGen/SPIRV/smulextended-builtin.ll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll b/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
index 57c11f6285d30..3ba8842ce4486 100644
--- a/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
+++ b/llvm/test/CodeGen/SPIRV/smulextended-builtin.ll
@@ -24,7 +24,7 @@ define spir_func void @test_builtin_smulext_sret(i32 %a, i32 %b) {
call void @__spirv_SMulExtended(ptr sret (%i32struct) %0, i32 %a, i32 %b)
ret void
}
-; CHECK-SPIRV: [[a_s:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
+; CHECK-SPIRV: [[#a_s:]] = OpFunctionParameter [[#uint]]
; CHECK-SPIRV-NEXT: [[b_s:%[a-z0-9_]+]] = OpFunctionParameter [[uint]]
; CHECK-SPIRV-NEXT: [[entry_s:%[a-z0-9_]+]] = OpLabel
; CHECK-SPIRV: [[var_s:%[a-z0-9_]+]] = OpVariable [[_ptr_Function_i32struct:%[a-z0-9_]+]] Function
More information about the llvm-commits
mailing list