[llvm] a059b29 - [SPIR-V] Allow intrinsics with aggregate return type to reach GlobalISel (#108893)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 26 01:57:06 PDT 2024
Author: Vyacheslav Levytskyy
Date: 2024-09-26T10:57:02+02:00
New Revision: a059b29930d046a2426be15c58421ee8971ec11c
URL: https://github.com/llvm/llvm-project/commit/a059b29930d046a2426be15c58421ee8971ec11c
DIFF: https://github.com/llvm/llvm-project/commit/a059b29930d046a2426be15c58421ee8971ec11c.diff
LOG: [SPIR-V] Allow intrinsics with aggregate return type to reach GlobalISel (#108893)
Two main goals of this PR are:
* to support "Arithmetic with Overflow" intrinsics, including the
special case when those intrinsics are being generated by the
CodeGenPrepare pass during translations with optimization;
* to redirect intrinsics with aggregate return type to be lowered via
GlobalISel operations instead of SPIRV-specific unfolding/lowering (see
https://github.com/llvm/llvm-project/pull/95012).
There is a new test case
`llvm/test/CodeGen/SPIRV/passes/translate-aggregate-uaddo.ll` that
describes and checks the general logics of the translation.
This PR continues a series of PRs aimed to identify and fix flaws in
code emission, to improve pass rates for the mode with expensive checks
set on (see https://github.com/llvm/llvm-project/pull/101732,
https://github.com/llvm/llvm-project/pull/104104,
https://github.com/llvm/llvm-project/pull/106966), having in mind the
ultimate goal of proceeding towards the non-experimental status of
SPIR-V Backend.
The reproducers are:
1) consider `llc -O3 -mtriple=spirv64-unknown-unknown ...` with:
```
define spir_func i32 @foo(i32 %a, ptr addrspace(4) %p) {
entry:
br label %l1
l1:
%e = phi i32 [ %a, %entry ], [ %i, %body ]
%i = add nsw i32 %e, 1
%fl = icmp eq i32 %i, 0
br i1 %fl, label %exit, label %body
body:
store i8 42, ptr addrspace(4) %p
br label %l1
exit:
ret i32 %i
}
```
2) consider `llc -O0 -mtriple=spirv64-unknown-unknown ...` with:
```
define spir_func i32 @foo(i32 %a, ptr addrspace(4) %p) {
entry:
br label %l1
l1: ; preds = %body, %entry
%e = phi i32 [ %a, %entry ], [ %math, %body ]
%0 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 %e, i32 1)
%math = extractvalue { i32, i1 } %0, 0
%ov = extractvalue { i32, i1 } %0, 1
br i1 %ov, label %exit, label %body
body: ; preds = %l1
store i8 42, ptr addrspace(4) %p, align 1
br label %l1
exit: ; preds = %l1
ret i32 %math
}
```
Added:
llvm/test/CodeGen/SPIRV/llvm-intrinsics/smul.with.overflow.ll
llvm/test/CodeGen/SPIRV/llvm-intrinsics/uadd.with.overflow.ll
llvm/test/CodeGen/SPIRV/llvm-intrinsics/usub.with.overflow.ll
llvm/test/CodeGen/SPIRV/optimizations/add-check-overflow.ll
llvm/test/CodeGen/SPIRV/passes/translate-aggregate-uaddo.ll
Modified:
llvm/docs/SPIRVUsage.rst
llvm/include/llvm/IR/IntrinsicsSPIRV.td
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
llvm/lib/Target/SPIRV/SPIRVUtils.cpp
llvm/lib/Target/SPIRV/SPIRVUtils.h
llvm/test/CodeGen/SPIRV/llvm-intrinsics/umul.with.overflow.ll
Removed:
################################################################################
diff --git a/llvm/docs/SPIRVUsage.rst b/llvm/docs/SPIRVUsage.rst
index 0f0b21fb237703..bb12b05246afba 100644
--- a/llvm/docs/SPIRVUsage.rst
+++ b/llvm/docs/SPIRVUsage.rst
@@ -275,6 +275,10 @@ SPIR-V backend, along with their descriptions and argument details.
- None
- `[Type, Vararg]`
- Assigns names to types or values, enhancing readability and debuggability of SPIR-V code. Not emitted directly but used for metadata enrichment.
+ * - `int_spv_value_md`
+ - None
+ - `[Metadata]`
+ - Assigns a set of attributes (such as name and data type) to a value that is the argument of the associated `llvm.fake.use` intrinsic call. The latter is used as a mean to map virtual registers created by IRTranslator to the original value.
* - `int_spv_assign_decoration`
- None
- `[Type, Metadata]`
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 7ac479f31386f9..c5c60963ed6fda 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -15,6 +15,7 @@ let TargetPrefix = "spv" in {
def int_spv_assign_ptr_type : Intrinsic<[], [llvm_any_ty, llvm_metadata_ty, llvm_i32_ty], [ImmArg<ArgIndex<2>>]>;
def int_spv_assign_name : Intrinsic<[], [llvm_any_ty, llvm_vararg_ty]>;
def int_spv_assign_decoration : Intrinsic<[], [llvm_any_ty, llvm_metadata_ty]>;
+ def int_spv_value_md : Intrinsic<[], [llvm_metadata_ty]>;
def int_spv_track_constant : Intrinsic<[llvm_any_ty], [llvm_any_ty, llvm_metadata_ty]>;
def int_spv_init_global : Intrinsic<[], [llvm_any_ty, llvm_any_ty]>;
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 86be79cbb5e7ff..415b5d99695f0d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -188,6 +188,21 @@ bool isConvergenceIntrinsic(const Instruction *I) {
II->getIntrinsicID() == Intrinsic::experimental_convergence_loop ||
II->getIntrinsicID() == Intrinsic::experimental_convergence_anchor;
}
+
+bool allowEmitFakeUse(const Value *Arg) {
+ if (const auto *II = dyn_cast<IntrinsicInst>(Arg))
+ if (Function *F = II->getCalledFunction())
+ if (F->getName().starts_with("llvm.spv."))
+ return false;
+ if (dyn_cast<AtomicCmpXchgInst>(Arg) || dyn_cast<InsertValueInst>(Arg) ||
+ dyn_cast<UndefValue>(Arg))
+ return false;
+ if (const auto *LI = dyn_cast<LoadInst>(Arg))
+ if (LI->getType()->isAggregateType())
+ return false;
+ return true;
+}
+
} // namespace
char SPIRVEmitIntrinsics::ID = 0;
@@ -283,8 +298,20 @@ static inline Type *reconstructType(SPIRVGlobalRegistry *GR, Value *Op) {
void SPIRVEmitIntrinsics::buildAssignType(IRBuilder<> &B, Type *Ty,
Value *Arg) {
Value *OfType = PoisonValue::get(Ty);
- CallInst *AssignCI = buildIntrWithMD(Intrinsic::spv_assign_type,
- {Arg->getType()}, OfType, Arg, {}, B);
+ CallInst *AssignCI = nullptr;
+ if (Arg->getType()->isAggregateType() && Ty->isAggregateType() &&
+ allowEmitFakeUse(Arg)) {
+ LLVMContext &Ctx = Arg->getContext();
+ SmallVector<Metadata *, 2> ArgMDs{
+ MDNode::get(Ctx, ValueAsMetadata::getConstant(OfType)),
+ MDString::get(Ctx, Arg->getName())};
+ B.CreateIntrinsic(Intrinsic::spv_value_md, {},
+ {MetadataAsValue::get(Ctx, MDTuple::get(Ctx, ArgMDs))});
+ AssignCI = B.CreateIntrinsic(Intrinsic::fake_use, {}, {Arg});
+ } else {
+ AssignCI = buildIntrWithMD(Intrinsic::spv_assign_type, {Arg->getType()},
+ OfType, Arg, {}, B);
+ }
GR->addAssignPtrTypeInstr(Arg, AssignCI);
}
@@ -1268,6 +1295,8 @@ Instruction *SPIRVEmitIntrinsics::visitInsertValueInst(InsertValueInst &I) {
}
Instruction *SPIRVEmitIntrinsics::visitExtractValueInst(ExtractValueInst &I) {
+ if (I.getAggregateOperand()->getType()->isAggregateType())
+ return &I;
IRBuilder<> B(I.getParent());
B.SetInsertPoint(&I);
SmallVector<Value *> Args;
@@ -1534,7 +1563,7 @@ void SPIRVEmitIntrinsics::processInstrAfterVisit(Instruction *I,
I->setOperand(OpNo, NewOp);
}
}
- if (I->hasName()) {
+ if (I->hasName() && !I->getType()->isAggregateType()) {
reportFatalOnTokenType(I);
setInsertPointAfterDef(B, I);
std::vector<Value *> Args = {I};
diff --git a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
index cad2bf96adf33e..92f95418624fe9 100644
--- a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
+++ b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.h
@@ -55,6 +55,8 @@ class SPIRVGlobalRegistry {
// created during substitution of aggregate arguments
// (see `SPIRVPrepareFunctions::removeAggregateTypesFromSignature()`)
DenseMap<Value *, Type *> MutatedAggRet;
+ // map an instruction to its value's attributes (type, name)
+ DenseMap<MachineInstr *, std::pair<Type *, std::string>> ValueAttrs;
// Look for an equivalent of the newType in the map. Return the equivalent
// if it's found, otherwise insert newType to the map and return the type.
@@ -188,6 +190,21 @@ class SPIRVGlobalRegistry {
return It == MutatedAggRet.end() ? nullptr : It->second;
}
+ // A registry of value's attributes (type, name)
+ // - Add a record.
+ void addValueAttrs(MachineInstr *Key, std::pair<Type *, std::string> Val) {
+ ValueAttrs[Key] = Val;
+ }
+ // - Find a record.
+ bool findValueAttrs(const MachineInstr *Key, Type *&Ty, StringRef &Name) {
+ auto It = ValueAttrs.find(Key);
+ if (It == ValueAttrs.end())
+ return false;
+ Ty = It->second.first;
+ Name = It->second.second;
+ return true;
+ }
+
// Deduced element types of untyped pointers and composites:
// - Add a record to the map of deduced element types.
void addDeducedElementType(Value *Val, Type *Ty) { DeducedElTys[Val] = Ty; }
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td b/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
index 51bacb00b1c515..fe45be4daba650 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
+++ b/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
@@ -519,8 +519,8 @@ def OpMatrixTimesMatrix: BinOp<"OpMatrixTimesMatrix", 146>;
def OpOuterProduct: BinOp<"OpOuterProduct", 147>;
def OpDot: BinOp<"OpDot", 148>;
-def OpIAddCarry: BinOpTyped<"OpIAddCarry", 149, iID, addc>;
-def OpISubBorrow: BinOpTyped<"OpISubBorrow", 150, iID, subc>;
+defm OpIAddCarry: BinOpTypedGen<"OpIAddCarry", 149, addc, 0, 1>;
+defm OpISubBorrow: BinOpTypedGen<"OpISubBorrow", 150, subc, 0, 1>;
def OpUMulExtended: BinOp<"OpUMulExtended", 151>;
def OpSMulExtended: BinOp<"OpSMulExtended", 152>;
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index e475810f92f717..43c92f24a0ad1d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -169,6 +169,9 @@ class SPIRVInstructionSelector : public InstructionSelector {
bool selectFloatDot(Register ResVReg, const SPIRVType *ResType,
MachineInstr &I) const;
+ bool selectOverflowArith(Register ResVReg, const SPIRVType *ResType,
+ MachineInstr &I, unsigned Opcode) const;
+
bool selectIntegerDot(Register ResVReg, const SPIRVType *ResType,
MachineInstr &I) const;
@@ -386,11 +389,22 @@ bool SPIRVInstructionSelector::select(MachineInstr &I) {
return false;
}
+static bool mayApplyGenericSelection(unsigned Opcode) {
+ switch (Opcode) {
+ case TargetOpcode::G_CONSTANT:
+ return false;
+ case TargetOpcode::G_SADDO:
+ case TargetOpcode::G_SSUBO:
+ return true;
+ }
+ return isTypeFoldingSupported(Opcode);
+}
+
bool SPIRVInstructionSelector::spvSelect(Register ResVReg,
const SPIRVType *ResType,
MachineInstr &I) const {
const unsigned Opcode = I.getOpcode();
- if (isTypeFoldingSupported(Opcode) && Opcode != TargetOpcode::G_CONSTANT)
+ if (mayApplyGenericSelection(Opcode))
return selectImpl(I, *CoverageInfo);
switch (Opcode) {
case TargetOpcode::G_CONSTANT:
@@ -567,6 +581,21 @@ bool SPIRVInstructionSelector::spvSelect(Register ResVReg,
case TargetOpcode::G_USUBSAT:
return selectExtInst(ResVReg, ResType, I, CL::u_sub_sat);
+ case TargetOpcode::G_UADDO:
+ return selectOverflowArith(ResVReg, ResType, I,
+ ResType->getOpcode() == SPIRV::OpTypeVector
+ ? SPIRV::OpIAddCarryV
+ : SPIRV::OpIAddCarryS);
+ case TargetOpcode::G_USUBO:
+ return selectOverflowArith(ResVReg, ResType, I,
+ ResType->getOpcode() == SPIRV::OpTypeVector
+ ? SPIRV::OpISubBorrowV
+ : SPIRV::OpISubBorrowS);
+ case TargetOpcode::G_UMULO:
+ return selectOverflowArith(ResVReg, ResType, I, SPIRV::OpUMulExtended);
+ case TargetOpcode::G_SMULO:
+ return selectOverflowArith(ResVReg, ResType, I, SPIRV::OpSMulExtended);
+
case TargetOpcode::G_SEXT:
return selectExt(ResVReg, ResType, I, true);
case TargetOpcode::G_ANYEXT:
@@ -1056,6 +1085,71 @@ bool SPIRVInstructionSelector::selectFence(MachineInstr &I) const {
.constrainAllUses(TII, TRI, RBI);
}
+bool SPIRVInstructionSelector::selectOverflowArith(Register ResVReg,
+ const SPIRVType *ResType,
+ MachineInstr &I,
+ unsigned Opcode) const {
+ Type *ResTy = nullptr;
+ StringRef ResName;
+ if (!GR.findValueAttrs(&I, ResTy, ResName))
+ report_fatal_error(
+ "Not enough info to select the arithmetic with overflow instruction");
+ if (!ResTy || !ResTy->isStructTy())
+ report_fatal_error("Expect struct type result for the arithmetic "
+ "with overflow instruction");
+ // "Result Type must be from OpTypeStruct. The struct must have two members,
+ // and the two members must be the same type."
+ Type *ResElemTy = cast<StructType>(ResTy)->getElementType(0);
+ ResTy = StructType::create(SmallVector<Type *, 2>{ResElemTy, ResElemTy});
+ // Build SPIR-V types and constant(s) if needed.
+ MachineIRBuilder MIRBuilder(I);
+ SPIRVType *StructType = GR.getOrCreateSPIRVType(
+ ResTy, MIRBuilder, SPIRV::AccessQualifier::ReadWrite, false);
+ assert(I.getNumDefs() > 1 && "Not enought operands");
+ SPIRVType *BoolType = GR.getOrCreateSPIRVBoolType(I, TII);
+ unsigned N = GR.getScalarOrVectorComponentCount(ResType);
+ if (N > 1)
+ BoolType = GR.getOrCreateSPIRVVectorType(BoolType, N, I, TII);
+ Register BoolTypeReg = GR.getSPIRVTypeID(BoolType);
+ Register ZeroReg = buildZerosVal(ResType, I);
+ // A new virtual register to store the result struct.
+ Register StructVReg = MRI->createGenericVirtualRegister(LLT::scalar(64));
+ MRI->setRegClass(StructVReg, &SPIRV::IDRegClass);
+ // Build the result name if needed.
+ if (ResName.size() > 0)
+ buildOpName(StructVReg, ResName, MIRBuilder);
+ // Build the arithmetic with overflow instruction.
+ MachineBasicBlock &BB = *I.getParent();
+ auto MIB =
+ BuildMI(BB, MIRBuilder.getInsertPt(), I.getDebugLoc(), TII.get(Opcode))
+ .addDef(StructVReg)
+ .addUse(GR.getSPIRVTypeID(StructType));
+ for (unsigned i = I.getNumDefs(); i < I.getNumOperands(); ++i)
+ MIB.addUse(I.getOperand(i).getReg());
+ bool Status = MIB.constrainAllUses(TII, TRI, RBI);
+ // Build instructions to extract fields of the instruction's result.
+ // A new virtual register to store the higher part of the result struct.
+ Register HigherVReg = MRI->createGenericVirtualRegister(LLT::scalar(64));
+ MRI->setRegClass(HigherVReg, &SPIRV::iIDRegClass);
+ for (unsigned i = 0; i < I.getNumDefs(); ++i) {
+ auto MIB =
+ BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpCompositeExtract))
+ .addDef(i == 1 ? HigherVReg : I.getOperand(i).getReg())
+ .addUse(GR.getSPIRVTypeID(ResType))
+ .addUse(StructVReg)
+ .addImm(i);
+ Status &= MIB.constrainAllUses(TII, TRI, RBI);
+ }
+ // Build boolean value from the higher part.
+ Status &= BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpINotEqual))
+ .addDef(I.getOperand(1).getReg())
+ .addUse(BoolTypeReg)
+ .addUse(HigherVReg)
+ .addUse(ZeroReg)
+ .constrainAllUses(TII, TRI, RBI);
+ return Status;
+}
+
bool SPIRVInstructionSelector::selectAtomicCmpXchg(Register ResVReg,
const SPIRVType *ResType,
MachineInstr &I) const {
@@ -2460,6 +2554,9 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
}
case Intrinsic::spv_step:
return selectStep(ResVReg, ResType, I);
+ case Intrinsic::spv_value_md:
+ // ignore the intrinsic
+ break;
default: {
std::string DiagMsg;
raw_string_ostream OS(DiagMsg);
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
index 9fe4d8a16bc32a..de9c495d4cbacc 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizerInfo.cpp
@@ -287,7 +287,8 @@ SPIRVLegalizerInfo::SPIRVLegalizerInfo(const SPIRVSubtarget &ST) {
// TODO: add proper legalization rules.
getActionDefinitionsBuilder(G_ATOMIC_CMPXCHG).alwaysLegal();
- getActionDefinitionsBuilder({G_UADDO, G_USUBO, G_SMULO, G_UMULO})
+ getActionDefinitionsBuilder(
+ {G_UADDO, G_SADDO, G_USUBO, G_SSUBO, G_UMULO, G_SMULO})
.alwaysLegal();
// FP conversions.
diff --git a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
index cd0aff1a518439..42f3ded336f951 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPreLegalizer.cpp
@@ -376,7 +376,13 @@ Register insertAssignInstr(Register Reg, Type *Ty, SPIRVType *SpvType,
.addUse(NewReg)
.addUse(GR->getSPIRVTypeID(SpvType))
.setMIFlags(Flags);
- Def->getOperand(0).setReg(NewReg);
+ for (unsigned I = 0, E = Def->getNumDefs(); I != E; ++I) {
+ MachineOperand &MO = Def->getOperand(I);
+ if (MO.getReg() == Reg) {
+ MO.setReg(NewReg);
+ break;
+ }
+ }
return NewReg;
}
@@ -460,6 +466,25 @@ generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
Def->getOpcode() != SPIRV::ASSIGN_TYPE)
insertAssignInstr(Reg, Ty, nullptr, GR, MIB, MF.getRegInfo());
ToErase.push_back(&MI);
+ } else if (MIOp == TargetOpcode::FAKE_USE && MI.getNumOperands() > 0) {
+ MachineInstr *MdMI = MI.getPrevNode();
+ if (MdMI && isSpvIntrinsic(*MdMI, Intrinsic::spv_value_md)) {
+ // It's an internal service info from before IRTranslator passes.
+ MachineInstr *Def = getVRegDef(MRI, MI.getOperand(0).getReg());
+ for (unsigned I = 1, E = MI.getNumOperands(); I != E && Def; ++I)
+ if (getVRegDef(MRI, MI.getOperand(I).getReg()) != Def)
+ Def = nullptr;
+ if (Def) {
+ const MDNode *MD = MdMI->getOperand(1).getMetadata();
+ StringRef ValueName =
+ cast<MDString>(MD->getOperand(1))->getString();
+ const MDNode *TypeMD = cast<MDNode>(MD->getOperand(0));
+ Type *ValueTy = getMDOperandAsType(TypeMD, 0);
+ GR->addValueAttrs(Def, std::make_pair(ValueTy, ValueName.str()));
+ }
+ ToErase.push_back(MdMI);
+ }
+ ToErase.push_back(&MI);
} else if (MIOp == TargetOpcode::G_CONSTANT ||
MIOp == TargetOpcode::G_FCONSTANT ||
MIOp == TargetOpcode::G_BUILD_VECTOR) {
diff --git a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
index eb5139ac5383a9..1872b238d1077a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVPrepareFunctions.cpp
@@ -342,30 +342,6 @@ static void lowerFunnelShifts(IntrinsicInst *FSHIntrinsic) {
FSHIntrinsic->setCalledFunction(FSHFunc);
}
-static void buildUMulWithOverflowFunc(Function *UMulFunc) {
- // The function body is already created.
- if (!UMulFunc->empty())
- return;
-
- BasicBlock *EntryBB = BasicBlock::Create(UMulFunc->getParent()->getContext(),
- "entry", UMulFunc);
- IRBuilder<> IRB(EntryBB);
- // Build the actual unsigned multiplication logic with the overflow
- // indication. Do unsigned multiplication Mul = A * B. Then check
- // if unsigned division Div = Mul / A is not equal to B. If so,
- // then overflow has happened.
- Value *Mul = IRB.CreateNUWMul(UMulFunc->getArg(0), UMulFunc->getArg(1));
- Value *Div = IRB.CreateUDiv(Mul, UMulFunc->getArg(0));
- Value *Overflow = IRB.CreateICmpNE(UMulFunc->getArg(0), Div);
-
- // umul.with.overflow intrinsic return a structure, where the first element
- // is the multiplication result, and the second is an overflow bit.
- Type *StructTy = UMulFunc->getReturnType();
- Value *Agg = IRB.CreateInsertValue(PoisonValue::get(StructTy), Mul, {0});
- Value *Res = IRB.CreateInsertValue(Agg, Overflow, {1});
- IRB.CreateRet(Res);
-}
-
static void lowerExpectAssume(IntrinsicInst *II) {
// If we cannot use the SPV_KHR_expect_assume extension, then we need to
// ignore the intrinsic and move on. It should be removed later on by LLVM.
@@ -407,20 +383,6 @@ static bool toSpvOverloadedIntrinsic(IntrinsicInst *II, Intrinsic::ID NewID,
return true;
}
-static void lowerUMulWithOverflow(IntrinsicInst *UMulIntrinsic) {
- // Get a separate function - otherwise, we'd have to rework the CFG of the
- // current one. Then simply replace the intrinsic uses with a call to the new
- // function.
- Module *M = UMulIntrinsic->getModule();
- FunctionType *UMulFuncTy = UMulIntrinsic->getFunctionType();
- Type *FSHLRetTy = UMulFuncTy->getReturnType();
- const std::string FuncName = lowerLLVMIntrinsicName(UMulIntrinsic);
- Function *UMulFunc =
- getOrCreateFunction(M, FSHLRetTy, UMulFuncTy->params(), FuncName);
- buildUMulWithOverflowFunc(UMulFunc);
- UMulIntrinsic->setCalledFunction(UMulFunc);
-}
-
// Substitutes calls to LLVM intrinsics with either calls to SPIR-V intrinsics
// or calls to proper generated functions. Returns True if F was modified.
bool SPIRVPrepareFunctions::substituteIntrinsicCalls(Function *F) {
@@ -444,10 +406,6 @@ bool SPIRVPrepareFunctions::substituteIntrinsicCalls(Function *F) {
lowerFunnelShifts(II);
Changed = true;
break;
- case Intrinsic::umul_with_overflow:
- lowerUMulWithOverflow(II);
- Changed = true;
- break;
case Intrinsic::assume:
case Intrinsic::expect: {
const SPIRVSubtarget &STI = TM.getSubtarget<SPIRVSubtarget>(*F);
@@ -478,9 +436,13 @@ bool SPIRVPrepareFunctions::substituteIntrinsicCalls(Function *F) {
// noted in 'spv.cloned_funcs' metadata for later restoration.
Function *
SPIRVPrepareFunctions::removeAggregateTypesFromSignature(Function *F) {
+ bool IsRetAggr = F->getReturnType()->isAggregateType();
+ // Allow intrinsics with aggregate return type to reach GlobalISel
+ if (F->isIntrinsic() && IsRetAggr)
+ return F;
+
IRBuilder<> B(F->getContext());
- bool IsRetAggr = F->getReturnType()->isAggregateType();
bool HasAggrArg =
std::any_of(F->arg_begin(), F->arg_end(), [](Argument &Arg) {
return Arg.getType()->isAggregateType();
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index 2680bd66f01e15..3640188670d15c 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -614,4 +614,11 @@ bool sortBlocks(Function &F) {
return Modified;
}
+MachineInstr *getVRegDef(MachineRegisterInfo &MRI, Register Reg) {
+ MachineInstr *MaybeDef = MRI.getVRegDef(Reg);
+ if (MaybeDef && MaybeDef->getOpcode() == SPIRV::ASSIGN_TYPE)
+ MaybeDef = MRI.getVRegDef(MaybeDef->getOperand(1).getReg());
+ return MaybeDef;
+}
+
} // namespace llvm
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.h b/llvm/lib/Target/SPIRV/SPIRVUtils.h
index 7c7616000d22b9..0d9b238db1403d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.h
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.h
@@ -315,5 +315,7 @@ inline const Type *unifyPtrType(const Type *Ty) {
return toTypedPointer(const_cast<Type *>(Ty));
}
+MachineInstr *getVRegDef(MachineRegisterInfo &MRI, Register Reg);
+
} // namespace llvm
#endif // LLVM_LIB_TARGET_SPIRV_SPIRVUTILS_H
diff --git a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/smul.with.overflow.ll b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/smul.with.overflow.ll
new file mode 100644
index 00000000000000..2281ccf52bbb4e
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/smul.with.overflow.ll
@@ -0,0 +1,89 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[Char:.*]] = OpTypeInt 8 0
+; CHECK-DAG: %[[Void:.*]] = OpTypeVoid
+; CHECK-DAG: %[[PtrChar:.*]] = OpTypePointer Function %[[Char]]
+; CHECK-DAG: %[[StructChar:.*]] = OpTypeStruct %[[Char]] %[[Char]]
+; CHECK-DAG: %[[ZeroChar:.*]] = OpConstant %[[Char]] 0
+; CHECK-DAG: %[[Int:.*]] = OpTypeInt 32 0
+; CHECK-DAG: %[[PtrInt:.*]] = OpTypePointer Function %[[Int]]
+; CHECK-DAG: %[[StructInt:.*]] = OpTypeStruct %[[Int]] %[[Int]]
+; CHECK-DAG: %[[ZeroInt:.*]] = OpConstant %[[Int]] 0
+; CHECK-DAG: %[[Bool:.*]] = OpTypeBool
+; CHECK-DAG: %[[V2Bool:.*]] = OpTypeVector %[[Bool]] 2
+; CHECK-DAG: %[[Long:.*]] = OpTypeInt 64 0
+; CHECK-DAG: %[[V2Long:.*]] = OpTypeVector %[[Long]] 2
+; CHECK-DAG: %[[PtrV2Long:.*]] = OpTypePointer Function %[[V2Long]]
+; CHECK-DAG: %[[StructV2Long:.*]] = OpTypeStruct %[[V2Long]] %[[V2Long]]
+; CHECK-DAG: %[[ZeroV2Long:.*]] = OpConstantNull %[[V2Long]]
+
+; CHECK: OpFunction
+; CHECK: %[[A:.*]] = OpFunctionParameter %[[Char]]
+; CHECK: %[[B:.*]] = OpFunctionParameter %[[Char]]
+; CHECK: %[[Ptr:.*]] = OpFunctionParameter %[[PtrChar]]
+; CHECK: %[[Struct:.*]] = OpSMulExtended %[[StructChar]] %[[A]] %[[B]]
+; CHECK: %[[Val:.*]] = OpCompositeExtract %[[Char]] %[[Struct]] 0
+; CHECK: %[[Over:.*]] = OpCompositeExtract %[[Char]] %[[Struct]] 1
+; CHECK: %[[IsOver:.*]] = OpINotEqual %[[Bool]] %[[Over]] %[[ZeroChar]]
+; CHECK: %[[Res:.*]] = OpSelect %[[Char]] %[[IsOver]] %[[ZeroChar]] %[[Val]]
+; CHECK: OpStore %[[Ptr]] %[[Res]] Aligned 1
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_i8(i8 zeroext %a, i8 zeroext %b, ptr nocapture %c) local_unnamed_addr {
+entry:
+ %umul = tail call { i8, i1 } @llvm.smul.with.overflow.i8(i8 %a, i8 %b)
+ %cmp = extractvalue { i8, i1 } %umul, 1
+ %umul.value = extractvalue { i8, i1 } %umul, 0
+ %storemerge = select i1 %cmp, i8 0, i8 %umul.value
+ store i8 %storemerge, ptr %c, align 1
+ ret void
+}
+
+; CHECK: OpFunction
+; CHECK: %[[A2:.*]] = OpFunctionParameter %[[Int]]
+; CHECK: %[[B2:.*]] = OpFunctionParameter %[[Int]]
+; CHECK: %[[Ptr2:.*]] = OpFunctionParameter %[[PtrInt]]
+; CHECK: %[[Struct2:.*]] = OpSMulExtended %[[StructInt]] %[[B2]] %[[A2]]
+; CHECK: %[[Val2:.*]] = OpCompositeExtract %[[Int]] %[[Struct2]] 0
+; CHECK: %[[Over2:.*]] = OpCompositeExtract %[[Int]] %[[Struct2]] 1
+; CHECK: %[[IsOver2:.*]] = OpINotEqual %[[Bool]] %[[Over2]] %[[ZeroInt]]
+; CHECK: %[[Res2:.*]] = OpSelect %[[Int]] %[[IsOver2]] %[[ZeroInt]] %[[Val2]]
+; CHECK: OpStore %[[Ptr2]] %[[Res2]] Aligned 4
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_i32(i32 %a, i32 %b, ptr nocapture %c) local_unnamed_addr {
+entry:
+ %umul = tail call { i32, i1 } @llvm.smul.with.overflow.i32(i32 %b, i32 %a)
+ %umul.val = extractvalue { i32, i1 } %umul, 0
+ %umul.ov = extractvalue { i32, i1 } %umul, 1
+ %spec.select = select i1 %umul.ov, i32 0, i32 %umul.val
+ store i32 %spec.select, ptr %c, align 4
+ ret void
+}
+
+; CHECK: OpFunction
+; CHECK: %[[A3:.*]] = OpFunctionParameter %[[V2Long]]
+; CHECK: %[[B3:.*]] = OpFunctionParameter %[[V2Long]]
+; CHECK: %[[Ptr3:.*]] = OpFunctionParameter %[[PtrV2Long]]
+; CHECK: %[[Struct3:.*]] = OpSMulExtended %[[StructV2Long]] %[[A3]] %[[B3]]
+; CHECK: %[[Val3:.*]] = OpCompositeExtract %[[V2Long]] %[[Struct3]] 0
+; CHECK: %[[Over3:.*]] = OpCompositeExtract %[[V2Long]] %[[Struct3]] 1
+; CHECK: %[[IsOver3:.*]] = OpINotEqual %[[V2Bool]] %[[Over3]] %[[ZeroV2Long]]
+; CHECK: %[[Res3:.*]] = OpSelect %[[V2Long]] %[[IsOver3]] %[[ZeroV2Long]] %[[Val3]]
+; CHECK: OpStore %[[Ptr3]] %[[Res3]] Aligned 16
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_v2i64(<2 x i64> %a, <2 x i64> %b, ptr %p) nounwind {
+ %umul = call {<2 x i64>, <2 x i1>} @llvm.smul.with.overflow.v2i64(<2 x i64> %a, <2 x i64> %b)
+ %umul.val = extractvalue {<2 x i64>, <2 x i1>} %umul, 0
+ %umul.ov = extractvalue {<2 x i64>, <2 x i1>} %umul, 1
+ %zero = alloca <2 x i64>, align 16
+ %spec.select = select <2 x i1> %umul.ov, <2 x i64> <i64 0, i64 0>, <2 x i64> %umul.val
+ store <2 x i64> %spec.select, ptr %p
+ ret void
+}
+
+declare {i8, i1} @llvm.smul.with.overflow.i8(i8, i8)
+declare {i32, i1} @llvm.smul.with.overflow.i32(i32, i32)
+declare {<2 x i64>, <2 x i1>} @llvm.smul.with.overflow.v2i64(<2 x i64>, <2 x i64>)
diff --git a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/uadd.with.overflow.ll b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/uadd.with.overflow.ll
new file mode 100644
index 00000000000000..cecd6f60655dcf
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/uadd.with.overflow.ll
@@ -0,0 +1,89 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[Char:.*]] = OpTypeInt 8 0
+; CHECK-DAG: %[[Void:.*]] = OpTypeVoid
+; CHECK-DAG: %[[PtrChar:.*]] = OpTypePointer Function %[[Char]]
+; CHECK-DAG: %[[StructChar:.*]] = OpTypeStruct %[[Char]] %[[Char]]
+; CHECK-DAG: %[[ZeroChar:.*]] = OpConstant %[[Char]] 0
+; CHECK-DAG: %[[Int:.*]] = OpTypeInt 32 0
+; CHECK-DAG: %[[PtrInt:.*]] = OpTypePointer Function %[[Int]]
+; CHECK-DAG: %[[StructInt:.*]] = OpTypeStruct %[[Int]] %[[Int]]
+; CHECK-DAG: %[[ZeroInt:.*]] = OpConstant %[[Int]] 0
+; CHECK-DAG: %[[Bool:.*]] = OpTypeBool
+; CHECK-DAG: %[[V2Bool:.*]] = OpTypeVector %[[Bool]] 2
+; CHECK-DAG: %[[Long:.*]] = OpTypeInt 64 0
+; CHECK-DAG: %[[V2Long:.*]] = OpTypeVector %[[Long]] 2
+; CHECK-DAG: %[[PtrV2Long:.*]] = OpTypePointer Function %[[V2Long]]
+; CHECK-DAG: %[[StructV2Long:.*]] = OpTypeStruct %[[V2Long]] %[[V2Long]]
+; CHECK-DAG: %[[ZeroV2Long:.*]] = OpConstantNull %[[V2Long]]
+
+; CHECK: OpFunction
+; CHECK: %[[A:.*]] = OpFunctionParameter %[[Char]]
+; CHECK: %[[B:.*]] = OpFunctionParameter %[[Char]]
+; CHECK: %[[Ptr:.*]] = OpFunctionParameter %[[PtrChar]]
+; CHECK: %[[Struct:.*]] = OpIAddCarry %[[StructChar]] %[[A]] %[[B]]
+; CHECK: %[[Val:.*]] = OpCompositeExtract %[[Char]] %[[Struct]] 0
+; CHECK: %[[Over:.*]] = OpCompositeExtract %[[Char]] %[[Struct]] 1
+; CHECK: %[[IsOver:.*]] = OpINotEqual %[[Bool]] %[[Over]] %[[ZeroChar]]
+; CHECK: %[[Res:.*]] = OpSelect %[[Char]] %[[IsOver]] %[[ZeroChar]] %[[Val]]
+; CHECK: OpStore %[[Ptr]] %[[Res]] Aligned 1
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_i8(i8 zeroext %a, i8 zeroext %b, ptr nocapture %c) local_unnamed_addr {
+entry:
+ %umul = tail call { i8, i1 } @llvm.uadd.with.overflow.i8(i8 %a, i8 %b)
+ %cmp = extractvalue { i8, i1 } %umul, 1
+ %umul.value = extractvalue { i8, i1 } %umul, 0
+ %storemerge = select i1 %cmp, i8 0, i8 %umul.value
+ store i8 %storemerge, ptr %c, align 1
+ ret void
+}
+
+; CHECK: OpFunction
+; CHECK: %[[A2:.*]] = OpFunctionParameter %[[Int]]
+; CHECK: %[[B2:.*]] = OpFunctionParameter %[[Int]]
+; CHECK: %[[Ptr2:.*]] = OpFunctionParameter %[[PtrInt]]
+; CHECK: %[[Struct2:.*]] = OpIAddCarry %[[StructInt]] %[[B2]] %[[A2]]
+; CHECK: %[[Val2:.*]] = OpCompositeExtract %[[Int]] %[[Struct2]] 0
+; CHECK: %[[Over2:.*]] = OpCompositeExtract %[[Int]] %[[Struct2]] 1
+; CHECK: %[[IsOver2:.*]] = OpINotEqual %[[Bool]] %[[Over2]] %[[ZeroInt]]
+; CHECK: %[[Res2:.*]] = OpSelect %[[Int]] %[[IsOver2]] %[[ZeroInt]] %[[Val2]]
+; CHECK: OpStore %[[Ptr2]] %[[Res2]] Aligned 4
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_i32(i32 %a, i32 %b, ptr nocapture %c) local_unnamed_addr {
+entry:
+ %umul = tail call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 %b, i32 %a)
+ %umul.val = extractvalue { i32, i1 } %umul, 0
+ %umul.ov = extractvalue { i32, i1 } %umul, 1
+ %spec.select = select i1 %umul.ov, i32 0, i32 %umul.val
+ store i32 %spec.select, ptr %c, align 4
+ ret void
+}
+
+; CHECK: OpFunction
+; CHECK: %[[A3:.*]] = OpFunctionParameter %[[V2Long]]
+; CHECK: %[[B3:.*]] = OpFunctionParameter %[[V2Long]]
+; CHECK: %[[Ptr3:.*]] = OpFunctionParameter %[[PtrV2Long]]
+; CHECK: %[[Struct3:.*]] = OpIAddCarry %[[StructV2Long]] %[[A3]] %[[B3]]
+; CHECK: %[[Val3:.*]] = OpCompositeExtract %[[V2Long]] %[[Struct3]] 0
+; CHECK: %[[Over3:.*]] = OpCompositeExtract %[[V2Long]] %[[Struct3]] 1
+; CHECK: %[[IsOver3:.*]] = OpINotEqual %[[V2Bool]] %[[Over3]] %[[ZeroV2Long]]
+; CHECK: %[[Res3:.*]] = OpSelect %[[V2Long]] %[[IsOver3]] %[[ZeroV2Long]] %[[Val3]]
+; CHECK: OpStore %[[Ptr3]] %[[Res3]] Aligned 16
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_v2i64(<2 x i64> %a, <2 x i64> %b, ptr %p) nounwind {
+ %umul = call {<2 x i64>, <2 x i1>} @llvm.uadd.with.overflow.v2i64(<2 x i64> %a, <2 x i64> %b)
+ %umul.val = extractvalue {<2 x i64>, <2 x i1>} %umul, 0
+ %umul.ov = extractvalue {<2 x i64>, <2 x i1>} %umul, 1
+ %zero = alloca <2 x i64>, align 16
+ %spec.select = select <2 x i1> %umul.ov, <2 x i64> <i64 0, i64 0>, <2 x i64> %umul.val
+ store <2 x i64> %spec.select, ptr %p
+ ret void
+}
+
+declare {i8, i1} @llvm.uadd.with.overflow.i8(i8, i8)
+declare {i32, i1} @llvm.uadd.with.overflow.i32(i32, i32)
+declare {<2 x i64>, <2 x i1>} @llvm.uadd.with.overflow.v2i64(<2 x i64>, <2 x i64>)
diff --git a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/umul.with.overflow.ll b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/umul.with.overflow.ll
index c34771bf381ea9..7113dd692f6ac4 100644
--- a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/umul.with.overflow.ll
+++ b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/umul.with.overflow.ll
@@ -1,54 +1,89 @@
-; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
-; CHECK-SPIRV: OpName %[[#NAME_UMUL_FUNC_8:]] "spirv.llvm_umul_with_overflow_i8"
-; CHECK-SPIRV: OpName %[[#NAME_UMUL_FUNC_32:]] "spirv.llvm_umul_with_overflow_i32"
-; CHECK-SPIRV: OpName %[[#NAME_UMUL_FUNC_VEC_I64:]] "spirv.llvm_umul_with_overflow_v2i64"
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
-define dso_local spir_func void @_Z4foo8hhPh(i8 zeroext %a, i8 zeroext %b, i8* nocapture %c) local_unnamed_addr {
+; CHECK-DAG: %[[Char:.*]] = OpTypeInt 8 0
+; CHECK-DAG: %[[Void:.*]] = OpTypeVoid
+; CHECK-DAG: %[[PtrChar:.*]] = OpTypePointer Function %[[Char]]
+; CHECK-DAG: %[[StructChar:.*]] = OpTypeStruct %[[Char]] %[[Char]]
+; CHECK-DAG: %[[ZeroChar:.*]] = OpConstant %[[Char]] 0
+; CHECK-DAG: %[[Int:.*]] = OpTypeInt 32 0
+; CHECK-DAG: %[[PtrInt:.*]] = OpTypePointer Function %[[Int]]
+; CHECK-DAG: %[[StructInt:.*]] = OpTypeStruct %[[Int]] %[[Int]]
+; CHECK-DAG: %[[ZeroInt:.*]] = OpConstant %[[Int]] 0
+; CHECK-DAG: %[[Bool:.*]] = OpTypeBool
+; CHECK-DAG: %[[V2Bool:.*]] = OpTypeVector %[[Bool]] 2
+; CHECK-DAG: %[[Long:.*]] = OpTypeInt 64 0
+; CHECK-DAG: %[[V2Long:.*]] = OpTypeVector %[[Long]] 2
+; CHECK-DAG: %[[PtrV2Long:.*]] = OpTypePointer Function %[[V2Long]]
+; CHECK-DAG: %[[StructV2Long:.*]] = OpTypeStruct %[[V2Long]] %[[V2Long]]
+; CHECK-DAG: %[[ZeroV2Long:.*]] = OpConstantNull %[[V2Long]]
+
+; CHECK: OpFunction
+; CHECK: %[[A:.*]] = OpFunctionParameter %[[Char]]
+; CHECK: %[[B:.*]] = OpFunctionParameter %[[Char]]
+; CHECK: %[[Ptr:.*]] = OpFunctionParameter %[[PtrChar]]
+; CHECK: %[[Struct:.*]] = OpUMulExtended %[[StructChar]] %[[A]] %[[B]]
+; CHECK: %[[Val:.*]] = OpCompositeExtract %[[Char]] %[[Struct]] 0
+; CHECK: %[[Over:.*]] = OpCompositeExtract %[[Char]] %[[Struct]] 1
+; CHECK: %[[IsOver:.*]] = OpINotEqual %[[Bool]] %[[Over]] %[[ZeroChar]]
+; CHECK: %[[Res:.*]] = OpSelect %[[Char]] %[[IsOver]] %[[ZeroChar]] %[[Val]]
+; CHECK: OpStore %[[Ptr]] %[[Res]] Aligned 1
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_i8(i8 zeroext %a, i8 zeroext %b, ptr nocapture %c) local_unnamed_addr {
entry:
- ; CHECK-SPIRV: %[[#]] = OpFunctionCall %[[#]] %[[#NAME_UMUL_FUNC_8]]
%umul = tail call { i8, i1 } @llvm.umul.with.overflow.i8(i8 %a, i8 %b)
%cmp = extractvalue { i8, i1 } %umul, 1
%umul.value = extractvalue { i8, i1 } %umul, 0
%storemerge = select i1 %cmp, i8 0, i8 %umul.value
- store i8 %storemerge, i8* %c, align 1
+ store i8 %storemerge, ptr %c, align 1
ret void
}
-define dso_local spir_func void @_Z5foo32jjPj(i32 %a, i32 %b, i32* nocapture %c) local_unnamed_addr {
+; CHECK: OpFunction
+; CHECK: %[[A2:.*]] = OpFunctionParameter %[[Int]]
+; CHECK: %[[B2:.*]] = OpFunctionParameter %[[Int]]
+; CHECK: %[[Ptr2:.*]] = OpFunctionParameter %[[PtrInt]]
+; CHECK: %[[Struct2:.*]] = OpUMulExtended %[[StructInt]] %[[B2]] %[[A2]]
+; CHECK: %[[Val2:.*]] = OpCompositeExtract %[[Int]] %[[Struct2]] 0
+; CHECK: %[[Over2:.*]] = OpCompositeExtract %[[Int]] %[[Struct2]] 1
+; CHECK: %[[IsOver2:.*]] = OpINotEqual %[[Bool]] %[[Over2]] %[[ZeroInt]]
+; CHECK: %[[Res2:.*]] = OpSelect %[[Int]] %[[IsOver2]] %[[ZeroInt]] %[[Val2]]
+; CHECK: OpStore %[[Ptr2]] %[[Res2]] Aligned 4
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_i32(i32 %a, i32 %b, ptr nocapture %c) local_unnamed_addr {
entry:
- ; CHECK-SPIRV: %[[#]] = OpFunctionCall %[[#]] %[[#NAME_UMUL_FUNC_32]]
%umul = tail call { i32, i1 } @llvm.umul.with.overflow.i32(i32 %b, i32 %a)
%umul.val = extractvalue { i32, i1 } %umul, 0
%umul.ov = extractvalue { i32, i1 } %umul, 1
%spec.select = select i1 %umul.ov, i32 0, i32 %umul.val
- store i32 %spec.select, i32* %c, align 4
+ store i32 %spec.select, ptr %c, align 4
ret void
}
-define dso_local spir_func void @umulo_v2i64(<2 x i64> %a, <2 x i64> %b, <2 x i64>* %p) nounwind {
- ; CHECK-SPIRV: %[[#]] = OpFunctionCall %[[#]] %[[#NAME_UMUL_FUNC_VEC_I64]]
+; CHECK: OpFunction
+; CHECK: %[[A3:.*]] = OpFunctionParameter %[[V2Long]]
+; CHECK: %[[B3:.*]] = OpFunctionParameter %[[V2Long]]
+; CHECK: %[[Ptr3:.*]] = OpFunctionParameter %[[PtrV2Long]]
+; CHECK: %[[Struct3:.*]] = OpUMulExtended %[[StructV2Long]] %[[A3]] %[[B3]]
+; CHECK: %[[Val3:.*]] = OpCompositeExtract %[[V2Long]] %[[Struct3]] 0
+; CHECK: %[[Over3:.*]] = OpCompositeExtract %[[V2Long]] %[[Struct3]] 1
+; CHECK: %[[IsOver3:.*]] = OpINotEqual %[[V2Bool]] %[[Over3]] %[[ZeroV2Long]]
+; CHECK: %[[Res3:.*]] = OpSelect %[[V2Long]] %[[IsOver3]] %[[ZeroV2Long]] %[[Val3]]
+; CHECK: OpStore %[[Ptr3]] %[[Res3]] Aligned 16
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_v2i64(<2 x i64> %a, <2 x i64> %b, ptr %p) nounwind {
%umul = call {<2 x i64>, <2 x i1>} @llvm.umul.with.overflow.v2i64(<2 x i64> %a, <2 x i64> %b)
%umul.val = extractvalue {<2 x i64>, <2 x i1>} %umul, 0
%umul.ov = extractvalue {<2 x i64>, <2 x i1>} %umul, 1
%zero = alloca <2 x i64>, align 16
%spec.select = select <2 x i1> %umul.ov, <2 x i64> <i64 0, i64 0>, <2 x i64> %umul.val
- store <2 x i64> %spec.select, <2 x i64>* %p
+ store <2 x i64> %spec.select, ptr %p
ret void
}
-; CHECK-SPIRV: %[[#NAME_UMUL_FUNC_8]] = OpFunction %[[#]]
-; CHECK-SPIRV: %[[#VAR_A:]] = OpFunctionParameter %[[#]]
-; CHECK-SPIRV: %[[#VAR_B:]] = OpFunctionParameter %[[#]]
-; CHECK-SPIRV: %[[#MUL_RES:]] = OpIMul %[[#]] %[[#VAR_A]] %[[#VAR_B]]
-; CHECK-SPIRV: %[[#DIV_RES:]] = OpUDiv %[[#]] %[[#MUL_RES]] %[[#VAR_A]]
-; CHECK-SPIRV: %[[#CMP_RES:]] = OpINotEqual %[[#]] %[[#VAR_A]] %[[#DIV_RES]]
-; CHECK-SPIRV: %[[#INSERT_RES:]] = OpCompositeInsert %[[#]] %[[#MUL_RES]]
-; CHECK-SPIRV: %[[#INSERT_RES_1:]] = OpCompositeInsert %[[#]] %[[#CMP_RES]] %[[#INSERT_RES]]
-; CHECK-SPIRV: OpReturnValue %[[#INSERT_RES_1]]
-
-declare { i8, i1 } @llvm.umul.with.overflow.i8(i8, i8)
-
-declare { i32, i1 } @llvm.umul.with.overflow.i32(i32, i32)
-
+declare {i8, i1} @llvm.umul.with.overflow.i8(i8, i8)
+declare {i32, i1} @llvm.umul.with.overflow.i32(i32, i32)
declare {<2 x i64>, <2 x i1>} @llvm.umul.with.overflow.v2i64(<2 x i64>, <2 x i64>)
diff --git a/llvm/test/CodeGen/SPIRV/llvm-intrinsics/usub.with.overflow.ll b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/usub.with.overflow.ll
new file mode 100644
index 00000000000000..963dd70f606b6e
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/llvm-intrinsics/usub.with.overflow.ll
@@ -0,0 +1,89 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: %[[Char:.*]] = OpTypeInt 8 0
+; CHECK-DAG: %[[Void:.*]] = OpTypeVoid
+; CHECK-DAG: %[[PtrChar:.*]] = OpTypePointer Function %[[Char]]
+; CHECK-DAG: %[[StructChar:.*]] = OpTypeStruct %[[Char]] %[[Char]]
+; CHECK-DAG: %[[ZeroChar:.*]] = OpConstant %[[Char]] 0
+; CHECK-DAG: %[[Int:.*]] = OpTypeInt 32 0
+; CHECK-DAG: %[[PtrInt:.*]] = OpTypePointer Function %[[Int]]
+; CHECK-DAG: %[[StructInt:.*]] = OpTypeStruct %[[Int]] %[[Int]]
+; CHECK-DAG: %[[ZeroInt:.*]] = OpConstant %[[Int]] 0
+; CHECK-DAG: %[[Bool:.*]] = OpTypeBool
+; CHECK-DAG: %[[V2Bool:.*]] = OpTypeVector %[[Bool]] 2
+; CHECK-DAG: %[[Long:.*]] = OpTypeInt 64 0
+; CHECK-DAG: %[[V2Long:.*]] = OpTypeVector %[[Long]] 2
+; CHECK-DAG: %[[PtrV2Long:.*]] = OpTypePointer Function %[[V2Long]]
+; CHECK-DAG: %[[StructV2Long:.*]] = OpTypeStruct %[[V2Long]] %[[V2Long]]
+; CHECK-DAG: %[[ZeroV2Long:.*]] = OpConstantNull %[[V2Long]]
+
+; CHECK: OpFunction
+; CHECK: %[[A:.*]] = OpFunctionParameter %[[Char]]
+; CHECK: %[[B:.*]] = OpFunctionParameter %[[Char]]
+; CHECK: %[[Ptr:.*]] = OpFunctionParameter %[[PtrChar]]
+; CHECK: %[[Struct:.*]] = OpISubBorrow %[[StructChar]] %[[A]] %[[B]]
+; CHECK: %[[Val:.*]] = OpCompositeExtract %[[Char]] %[[Struct]] 0
+; CHECK: %[[Over:.*]] = OpCompositeExtract %[[Char]] %[[Struct]] 1
+; CHECK: %[[IsOver:.*]] = OpINotEqual %[[Bool]] %[[Over]] %[[ZeroChar]]
+; CHECK: %[[Res:.*]] = OpSelect %[[Char]] %[[IsOver]] %[[ZeroChar]] %[[Val]]
+; CHECK: OpStore %[[Ptr]] %[[Res]] Aligned 1
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_i8(i8 zeroext %a, i8 zeroext %b, ptr nocapture %c) local_unnamed_addr {
+entry:
+ %umul = tail call { i8, i1 } @llvm.usub.with.overflow.i8(i8 %a, i8 %b)
+ %cmp = extractvalue { i8, i1 } %umul, 1
+ %umul.value = extractvalue { i8, i1 } %umul, 0
+ %storemerge = select i1 %cmp, i8 0, i8 %umul.value
+ store i8 %storemerge, ptr %c, align 1
+ ret void
+}
+
+; CHECK: OpFunction
+; CHECK: %[[A2:.*]] = OpFunctionParameter %[[Int]]
+; CHECK: %[[B2:.*]] = OpFunctionParameter %[[Int]]
+; CHECK: %[[Ptr2:.*]] = OpFunctionParameter %[[PtrInt]]
+; CHECK: %[[Struct2:.*]] = OpISubBorrow %[[StructInt]] %[[B2]] %[[A2]]
+; CHECK: %[[Val2:.*]] = OpCompositeExtract %[[Int]] %[[Struct2]] 0
+; CHECK: %[[Over2:.*]] = OpCompositeExtract %[[Int]] %[[Struct2]] 1
+; CHECK: %[[IsOver2:.*]] = OpINotEqual %[[Bool]] %[[Over2]] %[[ZeroInt]]
+; CHECK: %[[Res2:.*]] = OpSelect %[[Int]] %[[IsOver2]] %[[ZeroInt]] %[[Val2]]
+; CHECK: OpStore %[[Ptr2]] %[[Res2]] Aligned 4
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_i32(i32 %a, i32 %b, ptr nocapture %c) local_unnamed_addr {
+entry:
+ %umul = tail call { i32, i1 } @llvm.usub.with.overflow.i32(i32 %b, i32 %a)
+ %umul.val = extractvalue { i32, i1 } %umul, 0
+ %umul.ov = extractvalue { i32, i1 } %umul, 1
+ %spec.select = select i1 %umul.ov, i32 0, i32 %umul.val
+ store i32 %spec.select, ptr %c, align 4
+ ret void
+}
+
+; CHECK: OpFunction
+; CHECK: %[[A3:.*]] = OpFunctionParameter %[[V2Long]]
+; CHECK: %[[B3:.*]] = OpFunctionParameter %[[V2Long]]
+; CHECK: %[[Ptr3:.*]] = OpFunctionParameter %[[PtrV2Long]]
+; CHECK: %[[Struct3:.*]] = OpISubBorrow %[[StructV2Long]] %[[A3]] %[[B3]]
+; CHECK: %[[Val3:.*]] = OpCompositeExtract %[[V2Long]] %[[Struct3]] 0
+; CHECK: %[[Over3:.*]] = OpCompositeExtract %[[V2Long]] %[[Struct3]] 1
+; CHECK: %[[IsOver3:.*]] = OpINotEqual %[[V2Bool]] %[[Over3]] %[[ZeroV2Long]]
+; CHECK: %[[Res3:.*]] = OpSelect %[[V2Long]] %[[IsOver3]] %[[ZeroV2Long]] %[[Val3]]
+; CHECK: OpStore %[[Ptr3]] %[[Res3]] Aligned 16
+; CHECK: OpReturn
+define dso_local spir_func void @umulo_v2i64(<2 x i64> %a, <2 x i64> %b, ptr %p) nounwind {
+ %umul = call {<2 x i64>, <2 x i1>} @llvm.usub.with.overflow.v2i64(<2 x i64> %a, <2 x i64> %b)
+ %umul.val = extractvalue {<2 x i64>, <2 x i1>} %umul, 0
+ %umul.ov = extractvalue {<2 x i64>, <2 x i1>} %umul, 1
+ %zero = alloca <2 x i64>, align 16
+ %spec.select = select <2 x i1> %umul.ov, <2 x i64> <i64 0, i64 0>, <2 x i64> %umul.val
+ store <2 x i64> %spec.select, ptr %p
+ ret void
+}
+
+declare {i8, i1} @llvm.usub.with.overflow.i8(i8, i8)
+declare {i32, i1} @llvm.usub.with.overflow.i32(i32, i32)
+declare {<2 x i64>, <2 x i1>} @llvm.usub.with.overflow.v2i64(<2 x i64>, <2 x i64>)
diff --git a/llvm/test/CodeGen/SPIRV/optimizations/add-check-overflow.ll b/llvm/test/CodeGen/SPIRV/optimizations/add-check-overflow.ll
new file mode 100644
index 00000000000000..1a630f77a44c5d
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/optimizations/add-check-overflow.ll
@@ -0,0 +1,56 @@
+; This test aims to check ability to support "Arithmetic with Overflow" intrinsics
+; in the special case when those intrinsics are being generated by the CodeGenPrepare;
+; pass during translations with optimization (note -O3 in llc arguments).
+
+; RUN: llc -O3 -mtriple=spirv32-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O3 -mtriple=spirv32-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; RUN: llc -O3 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O3 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: OpName %[[Val:.*]] "math"
+; CHECK-DAG: OpName %[[IsOver:.*]] "ov"
+; CHECK-DAG: %[[Int:.*]] = OpTypeInt 32 0
+; CHECK-DAG: %[[Char:.*]] = OpTypeInt 8 0
+; CHECK-DAG: %[[PtrChar:.*]] = OpTypePointer Generic %[[Char]]
+; CHECK-DAG: %[[Bool:.*]] = OpTypeBool
+; CHECK-DAG: %[[Struct:.*]] = OpTypeStruct %[[Int]] %[[Int]]
+; CHECK-DAG: %[[Const1:.*]] = OpConstant %[[Int]] 1
+; CHECK-DAG: %[[Const42:.*]] = OpConstant %[[Char]] 42
+; CHECK-DAG: %[[Zero:.*]] = OpConstantNull %[[Int]]
+
+; CHECK: OpFunction
+; CHECK: %[[A:.*]] = OpFunctionParameter %[[Int]]
+; CHECK: %[[Ptr:.*]] = OpFunctionParameter %[[PtrChar]]
+; CHECK: %[[#]] = OpLabel
+; CHECK: OpBranch %[[#]]
+; CHECK: %[[#]] = OpLabel
+; CHECK: %[[PhiRes:.*]] = OpPhi %[[Int]] %[[A]] %[[#]] %[[Val]] %[[#]]
+; CHECK: %[[AggRes:.*]] = OpIAddCarry %[[Struct]] %[[PhiRes]] %[[Const1]]
+; CHECK: %[[Val]] = OpCompositeExtract %[[Int]] %[[AggRes]] 0
+; CHECK: %[[Over:.*]] = OpCompositeExtract %[[Int]] %[[AggRes]] 1
+; CHECK: %[[IsOver]] = OpINotEqual %[[Bool:.*]] %[[Over]] %[[Zero]]
+; CHECK: OpBranchConditional %[[IsOver]] %[[#]] %[[#]]
+; CHECK: OpStore %[[Ptr]] %[[Const42]] Aligned 1
+; CHECK: OpBranch %[[#]]
+; CHECK: %[[#]] = OpLabel
+; CHECK: OpReturnValue %[[Val]]
+; CHECK: OpFunctionEnd
+
+define spir_func i32 @foo(i32 %a, ptr addrspace(4) %p) {
+entry:
+ br label %l1
+
+body:
+ store i8 42, ptr addrspace(4) %p
+ br label %l1
+
+l1:
+ %e = phi i32 [ %a, %entry ], [ %i, %body ]
+ %i = add nsw i32 %e, 1
+ %fl = icmp eq i32 %i, 0
+ br i1 %fl, label %exit, label %body
+
+exit:
+ ret i32 %i
+}
diff --git a/llvm/test/CodeGen/SPIRV/passes/translate-aggregate-uaddo.ll b/llvm/test/CodeGen/SPIRV/passes/translate-aggregate-uaddo.ll
new file mode 100644
index 00000000000000..cd4d9325c76599
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/passes/translate-aggregate-uaddo.ll
@@ -0,0 +1,64 @@
+; This test shows how value attributes are being passed during
diff erent translation steps.
+; See also test/CodeGen/SPIRV/optimizations/add-check-overflow.ll.
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -print-after=prepare-functions 2>&1 | FileCheck %s --check-prefix=CHECK-PREPARE
+; Intrinsics with aggregate return type are not substituted/removed.
+; CHECK-PREPARE: @llvm.uadd.with.overflow.i32
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -print-after=emit-intrinsics 2>&1 | FileCheck %s --check-prefix=CHECK-IR
+; Aggregate data are wrapped into @llvm.fake.use(),
+; and their attributes are packed into a metadata for @llvm.spv.value.md().
+; CHECK-IR: %[[R1:.*]] = call { i32, i1 } @llvm.uadd.with.overflow.i32
+; CHECK-IR: call void @llvm.spv.value.md(metadata !0)
+; CHECK-IR: call void (...) @llvm.fake.use({ i32, i1 } %[[R1]])
+; CHECK-IR: %math = extractvalue { i32, i1 } %[[R1]], 0
+; CHECK-IR: %ov = extractvalue { i32, i1 } %[[R1]], 1
+; Type/Name attributes of the value.
+; CHECK-IR: !0 = !{{[{]}}!1, !""{{[}]}}
+; Origin data type of the value.
+; CHECK-IR: !1 = !{{[{]}}{{[{]}} i32, i1 {{[}]}} poison{{[}]}}
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -print-after=irtranslator 2>&1 | FileCheck %s --check-prefix=CHECK-GMIR
+; Required info succeeded to get through IRTranslator.
+; CHECK-GMIR: %[[phires:.*]]:_(s32) = G_PHI
+; CHECK-GMIR: %[[math:.*]]:id(s32), %[[ov:.*]]:_(s1) = G_UADDO %[[phires]]:_, %[[#]]:_
+; CHECK-GMIR: G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.spv.value.md), !0
+; CHECK-GMIR: FAKE_USE %[[math]]:id(s32), %[[ov]]:_(s1)
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -print-after=spirv-prelegalizer 2>&1 | FileCheck %s --check-prefix=CHECK-PRE
+; Internal service instructions are consumed.
+; CHECK-PRE: G_UADDO
+; CHECK-PRE-NO: llvm.spv.value.md
+; CHECK-PRE-NO: FAKE_USE
+
+; RUN: llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -print-after=instruction-select 2>&1 | FileCheck %s --check-prefix=CHECK-ISEL
+; Names and types are restored and correctly encoded. Correct instruction selection is completed.
+; CHECK-ISEL-DAG: %[[int32:.*]]:type = OpTypeInt 32, 0
+; CHECK-ISEL-DAG: %[[struct:.*]]:type = OpTypeStruct %[[int32]]:type, %[[int32]]:type
+; CHECK-ISEL-DAG: %[[bool:.*]]:type = OpTypeBool
+; CHECK-ISEL-DAG: %[[zero32:.*]]:iid = OpConstantNull %[[int32]]:type
+; CHECK-ISEL-DAG: %[[res:.*]]:iid = OpIAddCarryS %[[struct]]:type
+; CHECK-ISEL-DAG: %[[math:.*]]:id = OpCompositeExtract %[[int32]]:type, %[[res]]:iid, 0
+; CHECK-ISEL-DAG: %[[ov32:.*]]:iid = OpCompositeExtract %[[int32]]:type, %[[res]]:iid, 1
+; CHECK-ISEL-DAG: %[[ov:.*]]:iid = OpINotEqual %[[bool]]:type, %[[ov32]]:iid, %[[zero32:.*]]:iid
+; CHECK-ISEL-DAG: OpName %[[math]]:id, 1752457581, 0
+; CHECK-ISEL-DAG: OpName %[[ov]]:iid, 30319
+
+define spir_func i32 @foo(i32 %a, ptr addrspace(4) %p) {
+entry:
+ br label %l1
+
+l1: ; preds = %body, %entry
+ %e = phi i32 [ %a, %entry ], [ %math, %body ]
+ %0 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 %e, i32 1)
+ %math = extractvalue { i32, i1 } %0, 0
+ %ov = extractvalue { i32, i1 } %0, 1
+ br i1 %ov, label %exit, label %body
+
+body: ; preds = %l1
+ store i8 42, ptr addrspace(4) %p, align 1
+ br label %l1
+
+exit: ; preds = %l1
+ ret i32 %math
+}
More information about the llvm-commits
mailing list