tablegen(LLVM IntrinsicsPowerPC.h -gen-intrinsic-enums -intrinsic-prefix=ppc)
tablegen(LLVM IntrinsicsR600.h -gen-intrinsic-enums -intrinsic-prefix=r600)
tablegen(LLVM IntrinsicsRISCV.h -gen-intrinsic-enums -intrinsic-prefix=riscv)
+tablegen(LLVM IntrinsicsSPIRV.h -gen-intrinsic-enums -intrinsic-prefix=spv)
tablegen(LLVM IntrinsicsS390.h -gen-intrinsic-enums -intrinsic-prefix=s390)
tablegen(LLVM IntrinsicsWebAssembly.h -gen-intrinsic-enums -intrinsic-prefix=wasm)
tablegen(LLVM IntrinsicsX86.h -gen-intrinsic-enums -intrinsic-prefix=x86)
include "llvm/IR/IntrinsicsSystemZ.td"
include "llvm/IR/IntrinsicsWebAssembly.td"
include "llvm/IR/IntrinsicsRISCV.td"
+include "llvm/IR/IntrinsicsSPIRV.td"
include "llvm/IR/IntrinsicsVE.td"
--- /dev/null
+//===- IntrinsicsSPIRV.td - Defines SPIRV intrinsics -------*- tablegen -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines all of the SPIRV-specific intrinsics.
+//
+//===----------------------------------------------------------------------===//
+
+let TargetPrefix = "spv" in {
+ def int_spv_assign_type : Intrinsic<[], [llvm_any_ty, llvm_metadata_ty]>;
+ def int_spv_assign_name : Intrinsic<[], [llvm_any_ty, llvm_vararg_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]>;
+ def int_spv_unref_global : Intrinsic<[], [llvm_any_ty]>;
+
+ def int_spv_gep : Intrinsic<[llvm_anyptr_ty], [llvm_i1_ty, llvm_any_ty, llvm_vararg_ty], [ImmArg<ArgIndex<0>>]>;
+ def int_spv_load : Intrinsic<[llvm_i32_ty], [llvm_anyptr_ty, llvm_i16_ty, llvm_i8_ty], [ImmArg<ArgIndex<1>>, ImmArg<ArgIndex<2>>]>;
+ def int_spv_store : Intrinsic<[], [llvm_i32_ty, llvm_anyptr_ty, llvm_i16_ty, llvm_i8_ty], [ImmArg<ArgIndex<2>>, ImmArg<ArgIndex<3>>]>;
+ def int_spv_extractv : Intrinsic<[llvm_any_ty], [llvm_i32_ty, llvm_vararg_ty]>;
+ def int_spv_insertv : Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_any_ty, llvm_vararg_ty]>;
+ def int_spv_extractelt : Intrinsic<[llvm_any_ty], [llvm_any_ty, llvm_anyint_ty]>;
+ def int_spv_insertelt : Intrinsic<[llvm_any_ty], [llvm_any_ty, llvm_any_ty, llvm_anyint_ty]>;
+ def int_spv_const_composite : Intrinsic<[llvm_i32_ty], [llvm_vararg_ty]>;
+ def int_spv_bitcast : Intrinsic<[llvm_any_ty], [llvm_any_ty]>;
+ def int_spv_switch : Intrinsic<[], [llvm_any_ty, llvm_vararg_ty]>;
+}
add_llvm_target(SPIRVCodeGen
SPIRVAsmPrinter.cpp
SPIRVCallLowering.cpp
+ SPIRVEmitIntrinsics.cpp
SPIRVGlobalRegistry.cpp
SPIRVInstrInfo.cpp
SPIRVInstructionSelector.cpp
SPIRVLegalizerInfo.cpp
SPIRVMCInstLower.cpp
SPIRVModuleAnalysis.cpp
+ SPIRVPreLegalizer.cpp
SPIRVRegisterBankInfo.cpp
SPIRVRegisterInfo.cpp
SPIRVSubtarget.cpp
class InstructionSelector;
class RegisterBankInfo;
+FunctionPass *createSPIRVPreLegalizerPass();
+FunctionPass *createSPIRVEmitIntrinsicsPass(SPIRVTargetMachine *TM);
InstructionSelector *
createSPIRVInstructionSelector(const SPIRVTargetMachine &TM,
const SPIRVSubtarget &Subtarget,
const RegisterBankInfo &RBI);
void initializeSPIRVModuleAnalysisPass(PassRegistry &);
+void initializeSPIRVPreLegalizerPass(PassRegistry &);
+void initializeSPIRVEmitIntrinsicsPass(PassRegistry &);
} // namespace llvm
#endif // LLVM_LIB_TARGET_SPIRV_SPIRV_H
--- /dev/null
+//===-- SPIRVEmitIntrinsics.cpp - emit SPIRV intrinsics ---------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// The pass emits SPIRV intrinsics keeping essential high-level information for
+// the translation of LLVM IR to SPIR-V.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SPIRV.h"
+#include "SPIRVTargetMachine.h"
+#include "SPIRVUtils.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/InstVisitor.h"
+#include "llvm/IR/IntrinsicsSPIRV.h"
+
+#include <queue>
+
+// This pass performs the following transformation on LLVM IR level required
+// for the following translation to SPIR-V:
+// - replaces direct usages of aggregate constants with target-specific
+// intrinsics;
+// - replaces aggregates-related instructions (extract/insert, ld/st, etc)
+// with a target-specific intrinsics;
+// - emits intrinsics for the global variable initializers since IRTranslator
+// doesn't handle them and it's not very convenient to translate them
+// ourselves;
+// - emits intrinsics to keep track of the string names assigned to the values;
+// - emits intrinsics to keep track of constants (this is necessary to have an
+// LLVM IR constant after the IRTranslation is completed) for their further
+// deduplication;
+// - emits intrinsics to keep track of original LLVM types of the values
+// to be able to emit proper SPIR-V types eventually.
+//
+// TODO: consider removing spv.track.constant in favor of spv.assign.type.
+
+using namespace llvm;
+
+namespace llvm {
+void initializeSPIRVEmitIntrinsicsPass(PassRegistry &);
+} // namespace llvm
+
+namespace {
+class SPIRVEmitIntrinsics
+ : public FunctionPass,
+ public InstVisitor<SPIRVEmitIntrinsics, Instruction *> {
+ SPIRVTargetMachine *TM = nullptr;
+ IRBuilder<> *IRB = nullptr;
+ Function *F = nullptr;
+ bool TrackConstants = true;
+ DenseMap<Instruction *, Constant *> AggrConsts;
+ DenseSet<Instruction *> AggrStores;
+ void preprocessCompositeConstants();
+ CallInst *buildIntrWithMD(Intrinsic::ID IntrID, ArrayRef<Type *> Types,
+ Value *Arg, Value *Arg2) {
+ ConstantAsMetadata *CM = ValueAsMetadata::getConstant(Arg);
+ MDTuple *TyMD = MDNode::get(F->getContext(), CM);
+ MetadataAsValue *VMD = MetadataAsValue::get(F->getContext(), TyMD);
+ return IRB->CreateIntrinsic(IntrID, {Types}, {Arg2, VMD});
+ }
+ void replaceMemInstrUses(Instruction *Old, Instruction *New);
+ void processInstrAfterVisit(Instruction *I);
+ void insertAssignTypeIntrs(Instruction *I);
+ void processGlobalValue(GlobalVariable &GV);
+
+public:
+ static char ID;
+ SPIRVEmitIntrinsics() : FunctionPass(ID) {
+ initializeSPIRVEmitIntrinsicsPass(*PassRegistry::getPassRegistry());
+ }
+ SPIRVEmitIntrinsics(SPIRVTargetMachine *_TM) : FunctionPass(ID), TM(_TM) {
+ initializeSPIRVEmitIntrinsicsPass(*PassRegistry::getPassRegistry());
+ }
+ Instruction *visitInstruction(Instruction &I) { return &I; }
+ Instruction *visitSwitchInst(SwitchInst &I);
+ Instruction *visitGetElementPtrInst(GetElementPtrInst &I);
+ Instruction *visitBitCastInst(BitCastInst &I);
+ Instruction *visitInsertElementInst(InsertElementInst &I);
+ Instruction *visitExtractElementInst(ExtractElementInst &I);
+ Instruction *visitInsertValueInst(InsertValueInst &I);
+ Instruction *visitExtractValueInst(ExtractValueInst &I);
+ Instruction *visitLoadInst(LoadInst &I);
+ Instruction *visitStoreInst(StoreInst &I);
+ Instruction *visitAllocaInst(AllocaInst &I);
+ bool runOnFunction(Function &F) override;
+};
+} // namespace
+
+char SPIRVEmitIntrinsics::ID = 0;
+
+INITIALIZE_PASS(SPIRVEmitIntrinsics, "emit-intrinsics", "SPIRV emit intrinsics",
+ false, false)
+
+static inline bool isAssignTypeInstr(const Instruction *I) {
+ return isa<IntrinsicInst>(I) &&
+ cast<IntrinsicInst>(I)->getIntrinsicID() == Intrinsic::spv_assign_type;
+}
+
+static bool isMemInstrToReplace(Instruction *I) {
+ return isa<StoreInst>(I) || isa<LoadInst>(I) || isa<InsertValueInst>(I) ||
+ isa<ExtractValueInst>(I);
+}
+
+static bool isAggrToReplace(const Value *V) {
+ return isa<ConstantAggregate>(V) || isa<ConstantDataArray>(V) ||
+ (isa<ConstantAggregateZero>(V) && !V->getType()->isVectorTy());
+}
+
+static void setInsertPointSkippingPhis(IRBuilder<> &B, Instruction *I) {
+ if (isa<PHINode>(I))
+ B.SetInsertPoint(I->getParent(), I->getParent()->getFirstInsertionPt());
+ else
+ B.SetInsertPoint(I);
+}
+
+static bool requireAssignType(Instruction *I) {
+ IntrinsicInst *Intr = dyn_cast<IntrinsicInst>(I);
+ if (Intr) {
+ switch (Intr->getIntrinsicID()) {
+ case Intrinsic::invariant_start:
+ case Intrinsic::invariant_end:
+ return false;
+ }
+ }
+ return true;
+}
+
+void SPIRVEmitIntrinsics::replaceMemInstrUses(Instruction *Old,
+ Instruction *New) {
+ while (!Old->user_empty()) {
+ auto *U = Old->user_back();
+ if (isMemInstrToReplace(U) || isa<ReturnInst>(U)) {
+ U->replaceUsesOfWith(Old, New);
+ } else if (isAssignTypeInstr(U)) {
+ IRB->SetInsertPoint(U);
+ SmallVector<Value *, 2> Args = {New, U->getOperand(1)};
+ IRB->CreateIntrinsic(Intrinsic::spv_assign_type, {New->getType()}, Args);
+ U->eraseFromParent();
+ } else {
+ llvm_unreachable("illegal aggregate intrinsic user");
+ }
+ }
+ Old->eraseFromParent();
+}
+
+void SPIRVEmitIntrinsics::preprocessCompositeConstants() {
+ std::queue<Instruction *> Worklist;
+ for (auto &I : instructions(F))
+ Worklist.push(&I);
+
+ while (!Worklist.empty()) {
+ auto *I = Worklist.front();
+ assert(I);
+ bool KeepInst = false;
+ for (const auto &Op : I->operands()) {
+ auto BuildCompositeIntrinsic = [&KeepInst, &Worklist, &I, &Op,
+ this](Constant *AggrC,
+ ArrayRef<Value *> Args) {
+ IRB->SetInsertPoint(I);
+ auto *CCI =
+ IRB->CreateIntrinsic(Intrinsic::spv_const_composite, {}, {Args});
+ Worklist.push(CCI);
+ I->replaceUsesOfWith(Op, CCI);
+ KeepInst = true;
+ AggrConsts[CCI] = AggrC;
+ };
+
+ if (auto *AggrC = dyn_cast<ConstantAggregate>(Op)) {
+ SmallVector<Value *> Args(AggrC->op_begin(), AggrC->op_end());
+ BuildCompositeIntrinsic(AggrC, Args);
+ } else if (auto *AggrC = dyn_cast<ConstantDataArray>(Op)) {
+ SmallVector<Value *> Args;
+ for (unsigned i = 0; i < AggrC->getNumElements(); ++i)
+ Args.push_back(AggrC->getElementAsConstant(i));
+ BuildCompositeIntrinsic(AggrC, Args);
+ } else if (isa<ConstantAggregateZero>(Op) &&
+ !Op->getType()->isVectorTy()) {
+ auto *AggrC = cast<ConstantAggregateZero>(Op);
+ SmallVector<Value *> Args(AggrC->op_begin(), AggrC->op_end());
+ BuildCompositeIntrinsic(AggrC, Args);
+ }
+ }
+ if (!KeepInst)
+ Worklist.pop();
+ }
+}
+
+Instruction *SPIRVEmitIntrinsics::visitSwitchInst(SwitchInst &I) {
+ SmallVector<Value *, 4> Args;
+ for (auto &Op : I.operands())
+ if (Op.get()->getType()->isSized())
+ Args.push_back(Op);
+ IRB->CreateIntrinsic(Intrinsic::spv_switch, {I.getOperand(0)->getType()},
+ {Args});
+ return &I;
+}
+
+Instruction *SPIRVEmitIntrinsics::visitGetElementPtrInst(GetElementPtrInst &I) {
+ SmallVector<Type *, 2> Types = {I.getType(), I.getOperand(0)->getType()};
+ SmallVector<Value *, 4> Args;
+ Args.push_back(IRB->getInt1(I.isInBounds()));
+ for (auto &Op : I.operands())
+ Args.push_back(Op);
+ auto *NewI = IRB->CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args});
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ return NewI;
+}
+
+Instruction *SPIRVEmitIntrinsics::visitBitCastInst(BitCastInst &I) {
+ SmallVector<Type *, 2> Types = {I.getType(), I.getOperand(0)->getType()};
+ SmallVector<Value *> Args(I.op_begin(), I.op_end());
+ auto *NewI = IRB->CreateIntrinsic(Intrinsic::spv_bitcast, {Types}, {Args});
+ std::string InstName = I.hasName() ? I.getName().str() : "";
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ NewI->setName(InstName);
+ return NewI;
+}
+
+Instruction *SPIRVEmitIntrinsics::visitInsertElementInst(InsertElementInst &I) {
+ SmallVector<Type *, 4> Types = {I.getType(), I.getOperand(0)->getType(),
+ I.getOperand(1)->getType(),
+ I.getOperand(2)->getType()};
+ SmallVector<Value *> Args(I.op_begin(), I.op_end());
+ auto *NewI = IRB->CreateIntrinsic(Intrinsic::spv_insertelt, {Types}, {Args});
+ std::string InstName = I.hasName() ? I.getName().str() : "";
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ NewI->setName(InstName);
+ return NewI;
+}
+
+Instruction *
+SPIRVEmitIntrinsics::visitExtractElementInst(ExtractElementInst &I) {
+ SmallVector<Type *, 3> Types = {I.getType(), I.getVectorOperandType(),
+ I.getIndexOperand()->getType()};
+ SmallVector<Value *, 2> Args = {I.getVectorOperand(), I.getIndexOperand()};
+ auto *NewI = IRB->CreateIntrinsic(Intrinsic::spv_extractelt, {Types}, {Args});
+ std::string InstName = I.hasName() ? I.getName().str() : "";
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ NewI->setName(InstName);
+ return NewI;
+}
+
+Instruction *SPIRVEmitIntrinsics::visitInsertValueInst(InsertValueInst &I) {
+ SmallVector<Type *, 1> Types = {I.getInsertedValueOperand()->getType()};
+ SmallVector<Value *> Args;
+ for (auto &Op : I.operands())
+ if (isa<UndefValue>(Op))
+ Args.push_back(UndefValue::get(IRB->getInt32Ty()));
+ else
+ Args.push_back(Op);
+ for (auto &Op : I.indices())
+ Args.push_back(IRB->getInt32(Op));
+ Instruction *NewI =
+ IRB->CreateIntrinsic(Intrinsic::spv_insertv, {Types}, {Args});
+ replaceMemInstrUses(&I, NewI);
+ return NewI;
+}
+
+Instruction *SPIRVEmitIntrinsics::visitExtractValueInst(ExtractValueInst &I) {
+ SmallVector<Value *> Args;
+ for (auto &Op : I.operands())
+ Args.push_back(Op);
+ for (auto &Op : I.indices())
+ Args.push_back(IRB->getInt32(Op));
+ auto *NewI =
+ IRB->CreateIntrinsic(Intrinsic::spv_extractv, {I.getType()}, {Args});
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ return NewI;
+}
+
+Instruction *SPIRVEmitIntrinsics::visitLoadInst(LoadInst &I) {
+ if (!I.getType()->isAggregateType())
+ return &I;
+ TrackConstants = false;
+ const auto *TLI = TM->getSubtargetImpl()->getTargetLowering();
+ MachineMemOperand::Flags Flags =
+ TLI->getLoadMemOperandFlags(I, F->getParent()->getDataLayout());
+ auto *NewI =
+ IRB->CreateIntrinsic(Intrinsic::spv_load, {I.getOperand(0)->getType()},
+ {I.getPointerOperand(), IRB->getInt16(Flags),
+ IRB->getInt8(I.getAlignment())});
+ replaceMemInstrUses(&I, NewI);
+ return NewI;
+}
+
+Instruction *SPIRVEmitIntrinsics::visitStoreInst(StoreInst &I) {
+ if (!AggrStores.contains(&I))
+ return &I;
+ TrackConstants = false;
+ const auto *TLI = TM->getSubtargetImpl()->getTargetLowering();
+ MachineMemOperand::Flags Flags =
+ TLI->getStoreMemOperandFlags(I, F->getParent()->getDataLayout());
+ auto *PtrOp = I.getPointerOperand();
+ auto *NewI =
+ IRB->CreateIntrinsic(Intrinsic::spv_store, {PtrOp->getType()},
+ {I.getValueOperand(), PtrOp, IRB->getInt16(Flags),
+ IRB->getInt8(I.getAlignment())});
+ I.eraseFromParent();
+ return NewI;
+}
+
+Instruction *SPIRVEmitIntrinsics::visitAllocaInst(AllocaInst &I) {
+ TrackConstants = false;
+ return &I;
+}
+
+void SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV) {
+ // Skip special artifical variable llvm.global.annotations.
+ if (GV.getName() == "llvm.global.annotations")
+ return;
+ if (GV.hasInitializer() && !isa<UndefValue>(GV.getInitializer())) {
+ Constant *Init = GV.getInitializer();
+ Type *Ty = isAggrToReplace(Init) ? IRB->getInt32Ty() : Init->getType();
+ Constant *Const = isAggrToReplace(Init) ? IRB->getInt32(1) : Init;
+ auto *InitInst = IRB->CreateIntrinsic(Intrinsic::spv_init_global,
+ {GV.getType(), Ty}, {&GV, Const});
+ InitInst->setArgOperand(1, Init);
+ }
+ if ((!GV.hasInitializer() || isa<UndefValue>(GV.getInitializer())) &&
+ GV.getNumUses() == 0)
+ IRB->CreateIntrinsic(Intrinsic::spv_unref_global, GV.getType(), &GV);
+}
+
+void SPIRVEmitIntrinsics::insertAssignTypeIntrs(Instruction *I) {
+ Type *Ty = I->getType();
+ if (!Ty->isVoidTy() && requireAssignType(I)) {
+ setInsertPointSkippingPhis(*IRB, I->getNextNode());
+ Type *TypeToAssign = Ty;
+ if (auto *II = dyn_cast<IntrinsicInst>(I)) {
+ if (II->getIntrinsicID() == Intrinsic::spv_const_composite) {
+ auto t = AggrConsts.find(II);
+ assert(t != AggrConsts.end());
+ TypeToAssign = t->second->getType();
+ }
+ }
+ Constant *Const = Constant::getNullValue(TypeToAssign);
+ buildIntrWithMD(Intrinsic::spv_assign_type, {Ty}, Const, I);
+ }
+ for (const auto &Op : I->operands()) {
+ if (isa<ConstantPointerNull>(Op) || isa<UndefValue>(Op) ||
+ // Check GetElementPtrConstantExpr case.
+ (isa<ConstantExpr>(Op) && isa<GEPOperator>(Op))) {
+ IRB->SetInsertPoint(I);
+ buildIntrWithMD(Intrinsic::spv_assign_type, {Op->getType()}, Op, Op);
+ }
+ }
+ // StoreInst's operand type can be changed in the next stage so we need to
+ // store it in the set.
+ if (isa<StoreInst>(I) &&
+ cast<StoreInst>(I)->getValueOperand()->getType()->isAggregateType())
+ AggrStores.insert(I);
+}
+
+void SPIRVEmitIntrinsics::processInstrAfterVisit(Instruction *I) {
+ auto *II = dyn_cast<IntrinsicInst>(I);
+ if (II && II->getIntrinsicID() == Intrinsic::spv_const_composite &&
+ TrackConstants) {
+ IRB->SetInsertPoint(I->getNextNode());
+ Type *Ty = IRB->getInt32Ty();
+ auto t = AggrConsts.find(I);
+ assert(t != AggrConsts.end());
+ auto *NewOp =
+ buildIntrWithMD(Intrinsic::spv_track_constant, {Ty, Ty}, t->second, I);
+ I->replaceAllUsesWith(NewOp);
+ NewOp->setArgOperand(0, I);
+ }
+ for (const auto &Op : I->operands()) {
+ if ((isa<ConstantAggregateZero>(Op) && Op->getType()->isVectorTy()) ||
+ isa<PHINode>(I) || isa<SwitchInst>(I))
+ TrackConstants = false;
+ if (isa<ConstantData>(Op) && TrackConstants) {
+ unsigned OpNo = Op.getOperandNo();
+ if (II && ((II->getIntrinsicID() == Intrinsic::spv_gep && OpNo == 0) ||
+ (II->paramHasAttr(OpNo, Attribute::ImmArg))))
+ continue;
+ IRB->SetInsertPoint(I);
+ auto *NewOp = buildIntrWithMD(Intrinsic::spv_track_constant,
+ {Op->getType(), Op->getType()}, Op, Op);
+ I->setOperand(OpNo, NewOp);
+ }
+ }
+ if (I->hasName()) {
+ setInsertPointSkippingPhis(*IRB, I->getNextNode());
+ std::vector<Value *> Args = {I};
+ addStringImm(I->getName(), *IRB, Args);
+ IRB->CreateIntrinsic(Intrinsic::spv_assign_name, {I->getType()}, Args);
+ }
+}
+
+bool SPIRVEmitIntrinsics::runOnFunction(Function &Func) {
+ if (Func.isDeclaration())
+ return false;
+ F = &Func;
+ IRB = new IRBuilder<>(Func.getContext());
+ AggrConsts.clear();
+ AggrStores.clear();
+
+ IRB->SetInsertPoint(&Func.getEntryBlock().front());
+
+ for (auto &GV : Func.getParent()->globals())
+ processGlobalValue(GV);
+
+ preprocessCompositeConstants();
+ SmallVector<Instruction *> Worklist;
+ for (auto &I : instructions(Func))
+ Worklist.push_back(&I);
+
+ for (auto &I : Worklist)
+ insertAssignTypeIntrs(I);
+
+ for (auto *I : Worklist) {
+ TrackConstants = true;
+ if (!I->getType()->isVoidTy() || isa<StoreInst>(I))
+ IRB->SetInsertPoint(I->getNextNode());
+ I = visit(*I);
+ processInstrAfterVisit(I);
+ }
+ return true;
+}
+
+FunctionPass *llvm::createSPIRVEmitIntrinsicsPass(SPIRVTargetMachine *TM) {
+ return new SPIRVEmitIntrinsics(TM);
+}
SPIRVType *SpirvType =
getOrCreateSPIRVType(Type, MIRBuilder, AccessQual, EmitIR);
- assignSPIRVTypeToVReg(SpirvType, VReg, MIRBuilder);
+ assignSPIRVTypeToVReg(SpirvType, VReg, MIRBuilder.getMF());
return SpirvType;
}
void SPIRVGlobalRegistry::assignSPIRVTypeToVReg(SPIRVType *SpirvType,
Register VReg,
- MachineIRBuilder &MIRBuilder) {
- VRegToTypeMap[&MIRBuilder.getMF()][VReg] = SpirvType;
+ MachineFunction &MF) {
+ VRegToTypeMap[&MF][VReg] = SpirvType;
}
static Register createTypeVReg(MachineIRBuilder &MIRBuilder) {
if (Reg != ResVReg) {
LLT RegLLTy = LLT::pointer(MRI->getType(ResVReg).getAddressSpace(), 32);
MRI->setType(Reg, RegLLTy);
- assignSPIRVTypeToVReg(BaseType, Reg, MIRBuilder);
+ assignSPIRVTypeToVReg(BaseType, Reg, MIRBuilder.getMF());
}
// If it's a global variable with name, output OpName for it.
return getOpTypeFunction(RetTy, ParamTypes, MIRBuilder);
}
if (auto PType = dyn_cast<PointerType>(Ty)) {
- Type *ElemType = PType->getPointerElementType();
-
- // Some OpenCL and SPIRV builtins like image2d_t are passed in as pointers,
- // but should be treated as custom types like OpTypeImage.
- assert(!isa<StructType>(ElemType) && "Unsupported StructType pointer");
-
- // Otherwise, treat it as a regular pointer type.
+ SPIRVType *SpvElementType;
+ // At the moment, all opaque pointers correspond to i8 element type.
+ // TODO: change the implementation once opaque pointers are supported
+ // in the SPIR-V specification.
+ if (PType->isOpaque()) {
+ SpvElementType = getOrCreateSPIRVIntegerType(8, MIRBuilder);
+ } else {
+ Type *ElemType = PType->getNonOpaquePointerElementType();
+ // TODO: support OpenCL and SPIRV builtins like image2d_t that are passed
+ // as pointers, but should be treated as custom types like OpTypeImage.
+ assert(!isa<StructType>(ElemType) && "Unsupported StructType pointer");
+
+ // Otherwise, treat it as a regular pointer type.
+ SpvElementType = getOrCreateSPIRVType(
+ ElemType, MIRBuilder, SPIRV::AccessQualifier::ReadWrite, EmitIR);
+ }
auto SC = addressSpaceToStorageClass(PType->getAddressSpace());
- SPIRVType *SpvElementType = getOrCreateSPIRVType(
- ElemType, MIRBuilder, SPIRV::AccessQualifier::ReadWrite, EmitIR);
return getOpTypePointer(SC, SpvElementType, MIRBuilder);
}
llvm_unreachable("Unable to convert LLVM type to SPIRVType");
// In cases where the SPIR-V type is already known, this function can be
// used to map it to the given VReg via an ASSIGN_TYPE instruction.
void assignSPIRVTypeToVReg(SPIRVType *Type, Register VReg,
- MachineIRBuilder &MIRBuilder);
+ MachineFunction &MF);
// Either generate a new OpTypeXXX instruction or return an existing one
// corresponding to the given LLVM IR type.
#include "llvm/CodeGen/GlobalISel/InstructionSelectorImpl.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
+#include "llvm/IR/IntrinsicsSPIRV.h"
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "spirv-isel"
MachineInstr &I) const;
bool selectIntrinsic(Register ResVReg, const SPIRVType *ResType,
MachineInstr &I) const;
+ bool selectExtractVal(Register ResVReg, const SPIRVType *ResType,
+ MachineInstr &I) const;
+ bool selectInsertVal(Register ResVReg, const SPIRVType *ResType,
+ MachineInstr &I) const;
+ bool selectExtractElt(Register ResVReg, const SPIRVType *ResType,
+ MachineInstr &I) const;
+ bool selectInsertElt(Register ResVReg, const SPIRVType *ResType,
+ MachineInstr &I) const;
+ bool selectGEP(Register ResVReg, const SPIRVType *ResType,
+ MachineInstr &I) const;
bool selectFrameIndex(Register ResVReg, const SPIRVType *ResType,
MachineInstr &I) const;
.constrainAllUses(TII, TRI, RBI);
}
+static bool isImm(const MachineOperand &MO, MachineRegisterInfo *MRI) {
+ assert(MO.isReg());
+ const SPIRVType *TypeInst = MRI->getVRegDef(MO.getReg());
+ if (TypeInst->getOpcode() != SPIRV::ASSIGN_TYPE)
+ return false;
+ assert(TypeInst->getOperand(1).isReg());
+ MachineInstr *ImmInst = MRI->getVRegDef(TypeInst->getOperand(1).getReg());
+ return ImmInst->getOpcode() == TargetOpcode::G_CONSTANT;
+}
+
+static int64_t foldImm(const MachineOperand &MO, MachineRegisterInfo *MRI) {
+ const SPIRVType *TypeInst = MRI->getVRegDef(MO.getReg());
+ MachineInstr *ImmInst = MRI->getVRegDef(TypeInst->getOperand(1).getReg());
+ assert(ImmInst->getOpcode() == TargetOpcode::G_CONSTANT);
+ return ImmInst->getOperand(1).getCImm()->getZExtValue();
+}
+
+bool SPIRVInstructionSelector::selectInsertVal(Register ResVReg,
+ const SPIRVType *ResType,
+ MachineInstr &I) const {
+ MachineBasicBlock &BB = *I.getParent();
+ return BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpCompositeInsert))
+ .addDef(ResVReg)
+ .addUse(GR.getSPIRVTypeID(ResType))
+ // object to insert
+ .addUse(I.getOperand(3).getReg())
+ // composite to insert into
+ .addUse(I.getOperand(2).getReg())
+ // TODO: support arbitrary number of indices
+ .addImm(foldImm(I.getOperand(4), MRI))
+ .constrainAllUses(TII, TRI, RBI);
+}
+
+bool SPIRVInstructionSelector::selectExtractVal(Register ResVReg,
+ const SPIRVType *ResType,
+ MachineInstr &I) const {
+ MachineBasicBlock &BB = *I.getParent();
+ return BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpCompositeExtract))
+ .addDef(ResVReg)
+ .addUse(GR.getSPIRVTypeID(ResType))
+ .addUse(I.getOperand(2).getReg())
+ // TODO: support arbitrary number of indices
+ .addImm(foldImm(I.getOperand(3), MRI))
+ .constrainAllUses(TII, TRI, RBI);
+}
+
+bool SPIRVInstructionSelector::selectInsertElt(Register ResVReg,
+ const SPIRVType *ResType,
+ MachineInstr &I) const {
+ if (isImm(I.getOperand(4), MRI))
+ return selectInsertVal(ResVReg, ResType, I);
+ MachineBasicBlock &BB = *I.getParent();
+ return BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpVectorInsertDynamic))
+ .addDef(ResVReg)
+ .addUse(GR.getSPIRVTypeID(ResType))
+ .addUse(I.getOperand(2).getReg())
+ .addUse(I.getOperand(3).getReg())
+ .addUse(I.getOperand(4).getReg())
+ .constrainAllUses(TII, TRI, RBI);
+}
+
+bool SPIRVInstructionSelector::selectExtractElt(Register ResVReg,
+ const SPIRVType *ResType,
+ MachineInstr &I) const {
+ if (isImm(I.getOperand(3), MRI))
+ return selectExtractVal(ResVReg, ResType, I);
+ MachineBasicBlock &BB = *I.getParent();
+ return BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpVectorExtractDynamic))
+ .addDef(ResVReg)
+ .addUse(GR.getSPIRVTypeID(ResType))
+ .addUse(I.getOperand(2).getReg())
+ .addUse(I.getOperand(3).getReg())
+ .constrainAllUses(TII, TRI, RBI);
+}
+
+bool SPIRVInstructionSelector::selectGEP(Register ResVReg,
+ const SPIRVType *ResType,
+ MachineInstr &I) const {
+ // In general we should also support OpAccessChain instrs here (i.e. not
+ // PtrAccessChain) but SPIRV-LLVM Translator doesn't emit them at all and so
+ // do we to stay compliant with its test and more importantly consumers.
+ unsigned Opcode = I.getOperand(2).getImm() ? SPIRV::OpInBoundsPtrAccessChain
+ : SPIRV::OpPtrAccessChain;
+ auto Res = BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(Opcode))
+ .addDef(ResVReg)
+ .addUse(GR.getSPIRVTypeID(ResType))
+ // Object to get a pointer to.
+ .addUse(I.getOperand(3).getReg());
+ // Adding indices.
+ for (unsigned i = 4; i < I.getNumExplicitOperands(); ++i)
+ Res.addUse(I.getOperand(i).getReg());
+ return Res.constrainAllUses(TII, TRI, RBI);
+}
+
bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
const SPIRVType *ResType,
MachineInstr &I) const {
- llvm_unreachable("Intrinsic selection not implemented");
+ MachineBasicBlock &BB = *I.getParent();
+ switch (I.getIntrinsicID()) {
+ case Intrinsic::spv_load:
+ return selectLoad(ResVReg, ResType, I);
+ break;
+ case Intrinsic::spv_store:
+ return selectStore(I);
+ break;
+ case Intrinsic::spv_extractv:
+ return selectExtractVal(ResVReg, ResType, I);
+ break;
+ case Intrinsic::spv_insertv:
+ return selectInsertVal(ResVReg, ResType, I);
+ break;
+ case Intrinsic::spv_extractelt:
+ return selectExtractElt(ResVReg, ResType, I);
+ break;
+ case Intrinsic::spv_insertelt:
+ return selectInsertElt(ResVReg, ResType, I);
+ break;
+ case Intrinsic::spv_gep:
+ return selectGEP(ResVReg, ResType, I);
+ break;
+ case Intrinsic::spv_unref_global:
+ case Intrinsic::spv_init_global: {
+ MachineInstr *MI = MRI->getVRegDef(I.getOperand(1).getReg());
+ MachineInstr *Init = I.getNumExplicitOperands() > 2
+ ? MRI->getVRegDef(I.getOperand(2).getReg())
+ : nullptr;
+ assert(MI);
+ return selectGlobalValue(MI->getOperand(0).getReg(), *MI, Init);
+ } break;
+ case Intrinsic::spv_const_composite: {
+ // If no values are attached, the composite is null constant.
+ bool IsNull = I.getNumExplicitDefs() + 1 == I.getNumExplicitOperands();
+ unsigned Opcode =
+ IsNull ? SPIRV::OpConstantNull : SPIRV::OpConstantComposite;
+ auto MIB = BuildMI(BB, I, I.getDebugLoc(), TII.get(Opcode))
+ .addDef(ResVReg)
+ .addUse(GR.getSPIRVTypeID(ResType));
+ // skip type MD node we already used when generated assign.type for this
+ if (!IsNull) {
+ for (unsigned i = I.getNumExplicitDefs() + 1;
+ i < I.getNumExplicitOperands(); ++i) {
+ MIB.addUse(I.getOperand(i).getReg());
+ }
+ }
+ return MIB.constrainAllUses(TII, TRI, RBI);
+ } break;
+ case Intrinsic::spv_assign_name: {
+ auto MIB = BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpName));
+ MIB.addUse(I.getOperand(I.getNumExplicitDefs() + 1).getReg());
+ for (unsigned i = I.getNumExplicitDefs() + 2;
+ i < I.getNumExplicitOperands(); ++i) {
+ MIB.addImm(I.getOperand(i).getImm());
+ }
+ return MIB.constrainAllUses(TII, TRI, RBI);
+ } break;
+ case Intrinsic::spv_switch: {
+ auto MIB = BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpSwitch));
+ for (unsigned i = 1; i < I.getNumExplicitOperands(); ++i) {
+ if (I.getOperand(i).isReg())
+ MIB.addReg(I.getOperand(i).getReg());
+ else if (I.getOperand(i).isCImm())
+ addNumImm(I.getOperand(i).getCImm()->getValue(), MIB);
+ else if (I.getOperand(i).isMBB())
+ MIB.addMBB(I.getOperand(i).getMBB());
+ else
+ llvm_unreachable("Unexpected OpSwitch operand");
+ }
+ return MIB.constrainAllUses(TII, TRI, RBI);
+ } break;
+ default:
+ llvm_unreachable("Intrinsic selection not implemented");
+ }
+ return true;
}
bool SPIRVInstructionSelector::selectFrameIndex(Register ResVReg,
MachineRegisterInfo &MRI,
SPIRVGlobalRegistry *GR) {
Register ConvReg = MRI.createGenericVirtualRegister(ConvTy);
- GR->assignSPIRVTypeToVReg(SpirvType, ConvReg, Helper.MIRBuilder);
+ GR->assignSPIRVTypeToVReg(SpirvType, ConvReg, Helper.MIRBuilder.getMF());
Helper.MIRBuilder.buildInstr(TargetOpcode::G_PTRTOINT)
.addDef(ConvReg)
.addUse(Reg);
--- /dev/null
+//===-- SPIRVPreLegalizer.cpp - prepare IR for legalization -----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// The pass prepares IR for legalization: it assigns SPIR-V types to registers
+// and removes intrinsics which holded these types during IR translation.
+// Also it processes constants and registers them in GR to avoid duplication.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SPIRV.h"
+#include "SPIRVGlobalRegistry.h"
+#include "SPIRVSubtarget.h"
+#include "SPIRVUtils.h"
+#include "llvm/ADT/PostOrderIterator.h"
+#include "llvm/Analysis/OptimizationRemarkEmitter.h"
+#include "llvm/IR/Attributes.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/IR/IntrinsicsSPIRV.h"
+#include "llvm/Target/TargetIntrinsicInfo.h"
+
+#define DEBUG_TYPE "spirv-prelegalizer"
+
+using namespace llvm;
+
+namespace {
+class SPIRVPreLegalizer : public MachineFunctionPass {
+public:
+ static char ID;
+ SPIRVPreLegalizer() : MachineFunctionPass(ID) {
+ initializeSPIRVPreLegalizerPass(*PassRegistry::getPassRegistry());
+ }
+ bool runOnMachineFunction(MachineFunction &MF) override;
+};
+} // namespace
+
+static bool isSpvIntrinsic(MachineInstr &MI, Intrinsic::ID IntrinsicID) {
+ if (MI.getOpcode() == TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS &&
+ MI.getIntrinsicID() == IntrinsicID)
+ return true;
+ return false;
+}
+
+static void foldConstantsIntoIntrinsics(MachineFunction &MF) {
+ SmallVector<MachineInstr *, 10> ToErase;
+ MachineRegisterInfo &MRI = MF.getRegInfo();
+ const unsigned AssignNameOperandShift = 2;
+ for (MachineBasicBlock &MBB : MF) {
+ for (MachineInstr &MI : MBB) {
+ if (!isSpvIntrinsic(MI, Intrinsic::spv_assign_name))
+ continue;
+ unsigned NumOp = MI.getNumExplicitDefs() + AssignNameOperandShift;
+ while (MI.getOperand(NumOp).isReg()) {
+ MachineOperand &MOp = MI.getOperand(NumOp);
+ MachineInstr *ConstMI = MRI.getVRegDef(MOp.getReg());
+ assert(ConstMI->getOpcode() == TargetOpcode::G_CONSTANT);
+ MI.removeOperand(NumOp);
+ MI.addOperand(MachineOperand::CreateImm(
+ ConstMI->getOperand(1).getCImm()->getZExtValue()));
+ if (MRI.use_empty(ConstMI->getOperand(0).getReg()))
+ ToErase.push_back(ConstMI);
+ }
+ }
+ }
+ for (MachineInstr *MI : ToErase)
+ MI->eraseFromParent();
+}
+
+static void insertBitcasts(MachineFunction &MF, SPIRVGlobalRegistry *GR,
+ MachineIRBuilder MIB) {
+ SmallVector<MachineInstr *, 10> ToErase;
+ for (MachineBasicBlock &MBB : MF) {
+ for (MachineInstr &MI : MBB) {
+ if (!isSpvIntrinsic(MI, Intrinsic::spv_bitcast))
+ continue;
+ assert(MI.getOperand(2).isReg());
+ MIB.setInsertPt(*MI.getParent(), MI);
+ MIB.buildBitcast(MI.getOperand(0).getReg(), MI.getOperand(2).getReg());
+ ToErase.push_back(&MI);
+ }
+ }
+ for (MachineInstr *MI : ToErase)
+ MI->eraseFromParent();
+}
+
+// Translating GV, IRTranslator sometimes generates following IR:
+// %1 = G_GLOBAL_VALUE
+// %2 = COPY %1
+// %3 = G_ADDRSPACE_CAST %2
+// New registers have no SPIRVType and no register class info.
+//
+// Set SPIRVType for GV, propagate it from GV to other instructions,
+// also set register classes.
+static SPIRVType *propagateSPIRVType(MachineInstr *MI, SPIRVGlobalRegistry *GR,
+ MachineRegisterInfo &MRI,
+ MachineIRBuilder &MIB) {
+ SPIRVType *SpirvTy = nullptr;
+ assert(MI && "Machine instr is expected");
+ if (MI->getOperand(0).isReg()) {
+ Register Reg = MI->getOperand(0).getReg();
+ SpirvTy = GR->getSPIRVTypeForVReg(Reg);
+ if (!SpirvTy) {
+ switch (MI->getOpcode()) {
+ case TargetOpcode::G_CONSTANT: {
+ MIB.setInsertPt(*MI->getParent(), MI);
+ Type *Ty = MI->getOperand(1).getCImm()->getType();
+ SpirvTy = GR->getOrCreateSPIRVType(Ty, MIB);
+ break;
+ }
+ case TargetOpcode::G_GLOBAL_VALUE: {
+ MIB.setInsertPt(*MI->getParent(), MI);
+ Type *Ty = MI->getOperand(1).getGlobal()->getType();
+ SpirvTy = GR->getOrCreateSPIRVType(Ty, MIB);
+ break;
+ }
+ case TargetOpcode::G_TRUNC:
+ case TargetOpcode::G_ADDRSPACE_CAST:
+ case TargetOpcode::COPY: {
+ MachineOperand &Op = MI->getOperand(1);
+ MachineInstr *Def = Op.isReg() ? MRI.getVRegDef(Op.getReg()) : nullptr;
+ if (Def)
+ SpirvTy = propagateSPIRVType(Def, GR, MRI, MIB);
+ break;
+ }
+ default:
+ break;
+ }
+ if (SpirvTy)
+ GR->assignSPIRVTypeToVReg(SpirvTy, Reg, MIB.getMF());
+ if (!MRI.getRegClassOrNull(Reg))
+ MRI.setRegClass(Reg, &SPIRV::IDRegClass);
+ }
+ }
+ return SpirvTy;
+}
+
+// Insert ASSIGN_TYPE instuction between Reg and its definition, set NewReg as
+// a dst of the definition, assign SPIRVType to both registers. If SpirvTy is
+// provided, use it as SPIRVType in ASSIGN_TYPE, otherwise create it from Ty.
+// TODO: maybe move to SPIRVUtils.
+static Register insertAssignInstr(Register Reg, Type *Ty, SPIRVType *SpirvTy,
+ SPIRVGlobalRegistry *GR,
+ MachineIRBuilder &MIB,
+ MachineRegisterInfo &MRI) {
+ MachineInstr *Def = MRI.getVRegDef(Reg);
+ assert((Ty || SpirvTy) && "Either LLVM or SPIRV type is expected.");
+ MIB.setInsertPt(*Def->getParent(),
+ (Def->getNextNode() ? Def->getNextNode()->getIterator()
+ : Def->getParent()->end()));
+ Register NewReg = MRI.createGenericVirtualRegister(MRI.getType(Reg));
+ if (auto *RC = MRI.getRegClassOrNull(Reg))
+ MRI.setRegClass(NewReg, RC);
+ SpirvTy = SpirvTy ? SpirvTy : GR->getOrCreateSPIRVType(Ty, MIB);
+ GR->assignSPIRVTypeToVReg(SpirvTy, Reg, MIB.getMF());
+ // This is to make it convenient for Legalizer to get the SPIRVType
+ // when processing the actual MI (i.e. not pseudo one).
+ GR->assignSPIRVTypeToVReg(SpirvTy, NewReg, MIB.getMF());
+ MIB.buildInstr(SPIRV::ASSIGN_TYPE)
+ .addDef(Reg)
+ .addUse(NewReg)
+ .addUse(GR->getSPIRVTypeID(SpirvTy));
+ Def->getOperand(0).setReg(NewReg);
+ MRI.setRegClass(Reg, &SPIRV::ANYIDRegClass);
+ return NewReg;
+}
+
+static void generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR,
+ MachineIRBuilder MIB) {
+ MachineRegisterInfo &MRI = MF.getRegInfo();
+ SmallVector<MachineInstr *, 10> ToErase;
+
+ for (MachineBasicBlock *MBB : post_order(&MF)) {
+ if (MBB->empty())
+ continue;
+
+ bool ReachedBegin = false;
+ for (auto MII = std::prev(MBB->end()), Begin = MBB->begin();
+ !ReachedBegin;) {
+ MachineInstr &MI = *MII;
+
+ if (isSpvIntrinsic(MI, Intrinsic::spv_assign_type)) {
+ Register Reg = MI.getOperand(1).getReg();
+ Type *Ty = getMDOperandAsType(MI.getOperand(2).getMetadata(), 0);
+ MachineInstr *Def = MRI.getVRegDef(Reg);
+ assert(Def && "Expecting an instruction that defines the register");
+ // G_GLOBAL_VALUE already has type info.
+ if (Def->getOpcode() != TargetOpcode::G_GLOBAL_VALUE)
+ insertAssignInstr(Reg, Ty, nullptr, GR, MIB, MF.getRegInfo());
+ ToErase.push_back(&MI);
+ } else if (MI.getOpcode() == TargetOpcode::G_CONSTANT ||
+ MI.getOpcode() == TargetOpcode::G_FCONSTANT ||
+ MI.getOpcode() == TargetOpcode::G_BUILD_VECTOR) {
+ // %rc = G_CONSTANT ty Val
+ // ===>
+ // %cty = OpType* ty
+ // %rctmp = G_CONSTANT ty Val
+ // %rc = ASSIGN_TYPE %rctmp, %cty
+ Register Reg = MI.getOperand(0).getReg();
+ if (MRI.hasOneUse(Reg)) {
+ MachineInstr &UseMI = *MRI.use_instr_begin(Reg);
+ if (isSpvIntrinsic(UseMI, Intrinsic::spv_assign_type) ||
+ isSpvIntrinsic(UseMI, Intrinsic::spv_assign_name))
+ continue;
+ }
+ Type *Ty = nullptr;
+ if (MI.getOpcode() == TargetOpcode::G_CONSTANT)
+ Ty = MI.getOperand(1).getCImm()->getType();
+ else if (MI.getOpcode() == TargetOpcode::G_FCONSTANT)
+ Ty = MI.getOperand(1).getFPImm()->getType();
+ else {
+ assert(MI.getOpcode() == TargetOpcode::G_BUILD_VECTOR);
+ Type *ElemTy = nullptr;
+ MachineInstr *ElemMI = MRI.getVRegDef(MI.getOperand(1).getReg());
+ assert(ElemMI);
+
+ if (ElemMI->getOpcode() == TargetOpcode::G_CONSTANT)
+ ElemTy = ElemMI->getOperand(1).getCImm()->getType();
+ else if (ElemMI->getOpcode() == TargetOpcode::G_FCONSTANT)
+ ElemTy = ElemMI->getOperand(1).getFPImm()->getType();
+ else
+ llvm_unreachable("Unexpected opcode");
+ unsigned NumElts =
+ MI.getNumExplicitOperands() - MI.getNumExplicitDefs();
+ Ty = VectorType::get(ElemTy, NumElts, false);
+ }
+ insertAssignInstr(Reg, Ty, nullptr, GR, MIB, MRI);
+ } else if (MI.getOpcode() == TargetOpcode::G_TRUNC ||
+ MI.getOpcode() == TargetOpcode::G_GLOBAL_VALUE ||
+ MI.getOpcode() == TargetOpcode::COPY ||
+ MI.getOpcode() == TargetOpcode::G_ADDRSPACE_CAST) {
+ propagateSPIRVType(&MI, GR, MRI, MIB);
+ }
+
+ if (MII == Begin)
+ ReachedBegin = true;
+ else
+ --MII;
+ }
+ }
+ for (MachineInstr *MI : ToErase)
+ MI->eraseFromParent();
+}
+
+static std::pair<Register, unsigned>
+createNewIdReg(Register ValReg, unsigned Opcode, MachineRegisterInfo &MRI,
+ const SPIRVGlobalRegistry &GR) {
+ LLT NewT = LLT::scalar(32);
+ SPIRVType *SpvType = GR.getSPIRVTypeForVReg(ValReg);
+ assert(SpvType && "VReg is expected to have SPIRV type");
+ bool IsFloat = SpvType->getOpcode() == SPIRV::OpTypeFloat;
+ bool IsVectorFloat =
+ SpvType->getOpcode() == SPIRV::OpTypeVector &&
+ GR.getSPIRVTypeForVReg(SpvType->getOperand(1).getReg())->getOpcode() ==
+ SPIRV::OpTypeFloat;
+ IsFloat |= IsVectorFloat;
+ auto GetIdOp = IsFloat ? SPIRV::GET_fID : SPIRV::GET_ID;
+ auto DstClass = IsFloat ? &SPIRV::fIDRegClass : &SPIRV::IDRegClass;
+ if (MRI.getType(ValReg).isPointer()) {
+ NewT = LLT::pointer(0, 32);
+ GetIdOp = SPIRV::GET_pID;
+ DstClass = &SPIRV::pIDRegClass;
+ } else if (MRI.getType(ValReg).isVector()) {
+ NewT = LLT::fixed_vector(2, NewT);
+ GetIdOp = IsFloat ? SPIRV::GET_vfID : SPIRV::GET_vID;
+ DstClass = IsFloat ? &SPIRV::vfIDRegClass : &SPIRV::vIDRegClass;
+ }
+ Register IdReg = MRI.createGenericVirtualRegister(NewT);
+ MRI.setRegClass(IdReg, DstClass);
+ return {IdReg, GetIdOp};
+}
+
+static void processInstr(MachineInstr &MI, MachineIRBuilder &MIB,
+ MachineRegisterInfo &MRI, SPIRVGlobalRegistry *GR) {
+ unsigned Opc = MI.getOpcode();
+ assert(MI.getNumDefs() > 0 && MRI.hasOneUse(MI.getOperand(0).getReg()));
+ MachineInstr &AssignTypeInst =
+ *(MRI.use_instr_begin(MI.getOperand(0).getReg()));
+ auto NewReg = createNewIdReg(MI.getOperand(0).getReg(), Opc, MRI, *GR).first;
+ AssignTypeInst.getOperand(1).setReg(NewReg);
+ MI.getOperand(0).setReg(NewReg);
+ MIB.setInsertPt(*MI.getParent(),
+ (MI.getNextNode() ? MI.getNextNode()->getIterator()
+ : MI.getParent()->end()));
+ for (auto &Op : MI.operands()) {
+ if (!Op.isReg() || Op.isDef())
+ continue;
+ auto IdOpInfo = createNewIdReg(Op.getReg(), Opc, MRI, *GR);
+ MIB.buildInstr(IdOpInfo.second).addDef(IdOpInfo.first).addUse(Op.getReg());
+ Op.setReg(IdOpInfo.first);
+ }
+}
+
+// Defined in SPIRVLegalizerInfo.cpp.
+extern bool isTypeFoldingSupported(unsigned Opcode);
+
+static void processInstrsWithTypeFolding(MachineFunction &MF,
+ SPIRVGlobalRegistry *GR,
+ MachineIRBuilder MIB) {
+ MachineRegisterInfo &MRI = MF.getRegInfo();
+ for (MachineBasicBlock &MBB : MF) {
+ for (MachineInstr &MI : MBB) {
+ if (isTypeFoldingSupported(MI.getOpcode()))
+ processInstr(MI, MIB, MRI, GR);
+ }
+ }
+}
+
+static void processSwitches(MachineFunction &MF, SPIRVGlobalRegistry *GR,
+ MachineIRBuilder MIB) {
+ DenseMap<Register, SmallDenseMap<uint64_t, MachineBasicBlock *>>
+ SwitchRegToMBB;
+ DenseMap<Register, MachineBasicBlock *> DefaultMBBs;
+ DenseSet<Register> SwitchRegs;
+ MachineRegisterInfo &MRI = MF.getRegInfo();
+ // Before IRTranslator pass, spv_switch calls are inserted before each
+ // switch instruction. IRTranslator lowers switches to ICMP+CBr+Br triples.
+ // A switch with two cases may be translated to this MIR sequesnce:
+ // intrinsic(@llvm.spv.switch), %CmpReg, %Const0, %Const1
+ // %Dst0 = G_ICMP intpred(eq), %CmpReg, %Const0
+ // G_BRCOND %Dst0, %bb.2
+ // G_BR %bb.5
+ // bb.5.entry:
+ // %Dst1 = G_ICMP intpred(eq), %CmpReg, %Const1
+ // G_BRCOND %Dst1, %bb.3
+ // G_BR %bb.4
+ // bb.2.sw.bb:
+ // ...
+ // bb.3.sw.bb1:
+ // ...
+ // bb.4.sw.epilog:
+ // ...
+ // Walk MIs and collect information about destination MBBs to update
+ // spv_switch call. We assume that all spv_switch precede corresponding ICMPs.
+ for (MachineBasicBlock &MBB : MF) {
+ for (MachineInstr &MI : MBB) {
+ if (isSpvIntrinsic(MI, Intrinsic::spv_switch)) {
+ assert(MI.getOperand(1).isReg());
+ Register Reg = MI.getOperand(1).getReg();
+ SwitchRegs.insert(Reg);
+ // Set the first successor as default MBB to support empty switches.
+ DefaultMBBs[Reg] = *MBB.succ_begin();
+ }
+ // Process only ICMPs that relate to spv_switches.
+ if (MI.getOpcode() == TargetOpcode::G_ICMP && MI.getOperand(2).isReg() &&
+ SwitchRegs.contains(MI.getOperand(2).getReg())) {
+ assert(MI.getOperand(0).isReg() && MI.getOperand(1).isPredicate() &&
+ MI.getOperand(3).isReg());
+ Register Dst = MI.getOperand(0).getReg();
+ // Set type info for destination register of switch's ICMP instruction.
+ if (GR->getSPIRVTypeForVReg(Dst) == nullptr) {
+ MIB.setInsertPt(*MI.getParent(), MI);
+ Type *LLVMTy = IntegerType::get(MF.getFunction().getContext(), 1);
+ SPIRVType *SpirvTy = GR->getOrCreateSPIRVType(LLVMTy, MIB);
+ MRI.setRegClass(Dst, &SPIRV::IDRegClass);
+ GR->assignSPIRVTypeToVReg(SpirvTy, Dst, MIB.getMF());
+ }
+ Register CmpReg = MI.getOperand(2).getReg();
+ MachineOperand &PredOp = MI.getOperand(1);
+ const auto CC = static_cast<CmpInst::Predicate>(PredOp.getPredicate());
+ assert(CC == CmpInst::ICMP_EQ && MRI.hasOneUse(Dst) &&
+ MRI.hasOneDef(CmpReg));
+ uint64_t Val = getIConstVal(MI.getOperand(3).getReg(), &MRI);
+ MachineInstr *CBr = MRI.use_begin(Dst)->getParent();
+ assert(CBr->getOpcode() == SPIRV::G_BRCOND &&
+ CBr->getOperand(1).isMBB());
+ SwitchRegToMBB[CmpReg][Val] = CBr->getOperand(1).getMBB();
+ // The next MI is always BR to either the next case or the default.
+ MachineInstr *NextMI = CBr->getNextNode();
+ assert(NextMI->getOpcode() == SPIRV::G_BR &&
+ NextMI->getOperand(0).isMBB());
+ MachineBasicBlock *NextMBB = NextMI->getOperand(0).getMBB();
+ assert(NextMBB != nullptr);
+ // The default MBB is not started by ICMP with switch's cmp register.
+ if (NextMBB->front().getOpcode() != SPIRV::G_ICMP ||
+ (NextMBB->front().getOperand(2).isReg() &&
+ NextMBB->front().getOperand(2).getReg() != CmpReg))
+ DefaultMBBs[CmpReg] = NextMBB;
+ }
+ }
+ }
+ // Modify spv_switch's operands by collected values. For the example above,
+ // the result will be like this:
+ // intrinsic(@llvm.spv.switch), %CmpReg, %bb.4, i32 0, %bb.2, i32 1, %bb.3
+ // Note that ICMP+CBr+Br sequences are not removed, but ModuleAnalysis marks
+ // them as skipped and AsmPrinter does not output them.
+ for (MachineBasicBlock &MBB : MF) {
+ for (MachineInstr &MI : MBB) {
+ if (!isSpvIntrinsic(MI, Intrinsic::spv_switch))
+ continue;
+ assert(MI.getOperand(1).isReg());
+ Register Reg = MI.getOperand(1).getReg();
+ unsigned NumOp = MI.getNumExplicitOperands();
+ SmallVector<const ConstantInt *, 3> Vals;
+ SmallVector<MachineBasicBlock *, 3> MBBs;
+ for (unsigned i = 2; i < NumOp; i++) {
+ Register CReg = MI.getOperand(i).getReg();
+ uint64_t Val = getIConstVal(CReg, &MRI);
+ MachineInstr *ConstInstr = getDefInstrMaybeConstant(CReg, &MRI);
+ Vals.push_back(ConstInstr->getOperand(1).getCImm());
+ MBBs.push_back(SwitchRegToMBB[Reg][Val]);
+ }
+ for (unsigned i = MI.getNumExplicitOperands() - 1; i > 1; i--)
+ MI.removeOperand(i);
+ MI.addOperand(MachineOperand::CreateMBB(DefaultMBBs[Reg]));
+ for (unsigned i = 0; i < Vals.size(); i++) {
+ MI.addOperand(MachineOperand::CreateCImm(Vals[i]));
+ MI.addOperand(MachineOperand::CreateMBB(MBBs[i]));
+ }
+ }
+ }
+}
+
+bool SPIRVPreLegalizer::runOnMachineFunction(MachineFunction &MF) {
+ // Initialize the type registry.
+ const SPIRVSubtarget &ST = MF.getSubtarget<SPIRVSubtarget>();
+ SPIRVGlobalRegistry *GR = ST.getSPIRVGlobalRegistry();
+ GR->setCurrentFunc(MF);
+ MachineIRBuilder MIB(MF);
+ foldConstantsIntoIntrinsics(MF);
+ insertBitcasts(MF, GR, MIB);
+ generateAssignInstrs(MF, GR, MIB);
+ processInstrsWithTypeFolding(MF, GR, MIB);
+ processSwitches(MF, GR, MIB);
+
+ return true;
+}
+
+INITIALIZE_PASS(SPIRVPreLegalizer, DEBUG_TYPE, "SPIRV pre legalizer", false,
+ false)
+
+char SPIRVPreLegalizer::ID = 0;
+
+FunctionPass *llvm::createSPIRVPreLegalizerPass() {
+ return new SPIRVPreLegalizer();
+}
}
static std::string computeDataLayout(const Triple &TT) {
- std::string DataLayout = "e-m:e";
-
const auto Arch = TT.getArch();
if (Arch == Triple::spirv32)
- DataLayout += "-p:32:32";
- else if (Arch == Triple::spirv64)
- DataLayout += "-p:64:64";
- return DataLayout;
+ return "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-"
+ "v96:128-v192:256-v256:256-v512:512-v1024:1024";
+ return "e-i64:64-v16:16-v24:32-v32:32-v48:64-"
+ "v96:128-v192:256-v256:256-v512:512-v1024:1024";
}
static Reloc::Model getEffectiveRelocModel(Optional<Reloc::Model> RM) {
void addISelPrepare() override;
bool addIRTranslator() override;
+ void addPreLegalizeMachineIR() override;
bool addLegalizeMachineIR() override;
bool addRegBankSelect() override;
bool addGlobalInstructionSelect() override;
void SPIRVPassConfig::addIRPasses() { TargetPassConfig::addIRPasses(); }
-void SPIRVPassConfig::addISelPrepare() { TargetPassConfig::addISelPrepare(); }
+void SPIRVPassConfig::addISelPrepare() {
+ addPass(createSPIRVEmitIntrinsicsPass(&getTM<SPIRVTargetMachine>()));
+ TargetPassConfig::addISelPrepare();
+}
bool SPIRVPassConfig::addIRTranslator() {
addPass(new IRTranslator(getOptLevel()));
return false;
}
+void SPIRVPassConfig::addPreLegalizeMachineIR() {
+ addPass(createSPIRVPreLegalizerPass());
+}
+
// Use a default legalizer.
bool SPIRVPassConfig::addLegalizeMachineIR() {
addPass(new Legalizer());
#include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/IR/IntrinsicsSPIRV.h"
using namespace llvm;
return SPIRV::MemorySemantics::None;
}
}
+
+MachineInstr *getDefInstrMaybeConstant(Register &ConstReg,
+ const MachineRegisterInfo *MRI) {
+ MachineInstr *ConstInstr = MRI->getVRegDef(ConstReg);
+ if (ConstInstr->getOpcode() == TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS &&
+ ConstInstr->getIntrinsicID() == Intrinsic::spv_track_constant) {
+ ConstReg = ConstInstr->getOperand(2).getReg();
+ ConstInstr = MRI->getVRegDef(ConstReg);
+ } else if (ConstInstr->getOpcode() == SPIRV::ASSIGN_TYPE) {
+ ConstReg = ConstInstr->getOperand(1).getReg();
+ ConstInstr = MRI->getVRegDef(ConstReg);
+ }
+ return ConstInstr;
+}
+
+uint64_t getIConstVal(Register ConstReg, const MachineRegisterInfo *MRI) {
+ const MachineInstr *MI = getDefInstrMaybeConstant(ConstReg, MRI);
+ assert(MI && MI->getOpcode() == TargetOpcode::G_CONSTANT);
+ return MI->getOperand(1).getCImm()->getValue().getZExtValue();
+}
+
+Type *getMDOperandAsType(const MDNode *N, unsigned I) {
+ return cast<ValueAsMetadata>(N->getOperand(I))->getType();
+}
llvm::SPIRV::MemorySemantics
getMemSemanticsForStorageClass(llvm::SPIRV::StorageClass SC);
+
+// Find def instruction for the given ConstReg, walking through
+// spv_track_constant and ASSIGN_TYPE instructions. Updates ConstReg by def
+// of OpConstant instruction.
+llvm::MachineInstr *
+getDefInstrMaybeConstant(llvm::Register &ConstReg,
+ const llvm::MachineRegisterInfo *MRI);
+
+// Get constant integer value of the given ConstReg.
+uint64_t getIConstVal(llvm::Register ConstReg,
+ const llvm::MachineRegisterInfo *MRI);
+
+// Get type of i-th operand of the metadata node.
+llvm::Type *getMDOperandAsType(const llvm::MDNode *N, unsigned I);
#endif // LLVM_LIB_TARGET_SPIRV_SPIRVUTILS_H
--- /dev/null
+; RUN: llc -O0 %s -o - | FileCheck %s
+
+; NOTE: This does not check for structured control-flow operations.
+
+target triple = "spirv32-unknown-unknown"
+
+; CHECK-DAG: OpName [[FOO:%.+]] "foo"
+; CHECK-DAG: OpName [[BAR:%.+]] "bar"
+
+; CHECK-DAG: [[I32:%.+]] = OpTypeInt 32
+; CHECK-DAG: [[BOOL:%.+]] = OpTypeBool
+
+declare i32 @foo()
+declare i32 @bar()
+
+define i32 @test_if(i32 %a, i32 %b) {
+entry:
+ %cond = icmp eq i32 %a, %b
+ br i1 %cond, label %true_label, label %false_label
+
+true_label:
+ %v1 = call i32 @foo()
+ br label %merge_label
+
+false_label:
+ %v2 = call i32 @bar()
+ br label %merge_label
+
+merge_label:
+ %v = phi i32 [%v1, %true_label], [%v2, %false_label]
+ ret i32 %v
+}
+
+; CHECK: OpFunction
+; CHECK: [[A:%.+]] = OpFunctionParameter [[I32]]
+; CHECK: [[B:%.+]] = OpFunctionParameter [[I32]]
+
+; CHECK: [[ENTRY:%.+]] = OpLabel
+; CHECK: [[COND:%.+]] = OpIEqual [[BOOL]] [[A]] [[B]]
+; CHECK: OpBranchConditional [[COND]] [[TRUE_LABEL:%.+]] [[FALSE_LABEL:%.+]]
+
+; CHECK: [[TRUE_LABEL]] = OpLabel
+; CHECK: [[V1:%.+]] = OpFunctionCall [[I32]] [[FOO]]
+; CHECK: OpBranch [[MERGE_LABEL:%.+]]
+
+; CHECK: [[FALSE_LABEL]] = OpLabel
+; CHECK: [[V2:%.+]] = OpFunctionCall [[I32]] [[BAR]]
+; CHECK: OpBranch [[MERGE_LABEL]]
+
+; CHECK: [[MERGE_LABEL]] = OpLabel
+; CHECK-NEXT: [[V:%.+]] = OpPhi [[I32]] [[V1]] [[TRUE_LABEL]] [[V2]] [[FALSE_LABEL]]
+; CHECK: OpReturnValue [[V]]
+; CHECK-NEXT: OpFunctionEnd
--- /dev/null
+; RUN: llc -O0 %s -o - | FileCheck %s
+
+target triple = "spirv32-unknown-unknown"
+
+; CHECK-DAG: OpName [[BAR:%.+]] "bar"
+; CHECK-DAG: OpName [[FOO:%.+]] "foo"
+; CHECK-DAG: OpName [[GOO:%.+]] "goo"
+
+; CHECK: [[INT:%.+]] = OpTypeInt 32
+; CHECK-DAG: [[STACK_PTR:%.+]] = OpTypePointer Function [[INT]]
+; CHECK-DAG: [[GLOBAL_PTR:%.+]] = OpTypePointer CrossWorkgroup [[INT]]
+; CHECK-DAG: [[FN1:%.+]] = OpTypeFunction [[INT]] [[INT]]
+; CHECK-DAG: [[FN2:%.+]] = OpTypeFunction [[INT]] [[INT]] [[GLOBAL_PTR]]
+
+define i32 @bar(i32 %a) {
+ %p = alloca i32
+ store i32 %a, i32* %p
+ %b = load i32, i32* %p
+ ret i32 %b
+}
+
+; CHECK: [[BAR]] = OpFunction [[INT]] None [[FN1]]
+; CHECK: [[A:%.+]] = OpFunctionParameter [[INT]]
+; CHECK: OpLabel
+; CHECK: [[P:%.+]] = OpVariable [[STACK_PTR]] Function
+; CHECK: OpStore [[P]] [[A]]
+; CHECK: [[B:%.+]] = OpLoad [[INT]] [[P]]
+; CHECK: OpReturnValue [[B]]
+; CHECK: OpFunctionEnd
+
+
+define i32 @foo(i32 %a) {
+ %p = alloca i32
+ store volatile i32 %a, i32* %p
+ %b = load volatile i32, i32* %p
+ ret i32 %b
+}
+
+; CHECK: [[FOO]] = OpFunction [[INT]] None [[FN1]]
+; CHECK: [[A:%.+]] = OpFunctionParameter [[INT]]
+; CHECK: OpLabel
+; CHECK: [[P:%.+]] = OpVariable [[STACK_PTR]] Function
+; CHECK: OpStore [[P]] [[A]] Volatile
+; CHECK: [[B:%.+]] = OpLoad [[INT]] [[P]] Volatile
+; CHECK: OpReturnValue [[B]]
+; CHECK: OpFunctionEnd
+
+
+; Test load and store in global address space.
+define i32 @goo(i32 %a, i32 addrspace(1)* %p) {
+ store i32 %a, i32 addrspace(1)* %p
+ %b = load i32, i32 addrspace(1)* %p
+ ret i32 %b
+}
+
+; CHECK: [[GOO]] = OpFunction [[INT]] None [[FN2]]
+; CHECK: [[A:%.+]] = OpFunctionParameter [[INT]]
+; CHECK: [[P:%.+]] = OpFunctionParameter [[GLOBAL_PTR]]
+; CHECK: OpLabel
+; CHECK: OpStore [[P]] [[A]]
+; CHECK: [[B:%.+]] = OpLoad [[INT]] [[P]]
+; CHECK: OpReturnValue [[B]]
+; CHECK: OpFunctionEnd
; CHECK-DAG: OpName [[FN6:%.+]] "fn6"
; CHECK-DAG: OpName [[FN7:%.+]] "fn7"
-; CHECK-NOT: DAG-FENCE
-
; Types:
; CHECK: [[VOID:%.+]] = OpTypeVoid
; CHECK: [[FN:%.+]] = OpTypeFunction [[VOID]]
--- /dev/null
+; RUN: llc %s -o - | FileCheck %s
+
+target triple = "spirv32-unknown-unknown"
+
+; CHECK-DAG: OpName [[ADD:%.*]] "test_add"
+; CHECK-DAG: OpName [[SUB:%.*]] "test_sub"
+; CHECK-DAG: OpName [[MIN:%.*]] "test_min"
+; CHECK-DAG: OpName [[MAX:%.*]] "test_max"
+; CHECK-DAG: OpName [[UMIN:%.*]] "test_umin"
+; CHECK-DAG: OpName [[UMAX:%.*]] "test_umax"
+; CHECK-DAG: OpName [[AND:%.*]] "test_and"
+; CHECK-DAG: OpName [[OR:%.*]] "test_or"
+; CHECK-DAG: OpName [[XOR:%.*]] "test_xor"
+
+; CHECK-DAG: [[I32Ty:%.*]] = OpTypeInt 32 0
+; Device scope is encoded with constant 1
+; CHECK-DAG: [[SCOPE:%.*]] = OpConstant [[I32Ty]] 1
+; "monotonic" maps to the relaxed memory semantics, encoded with constant 0
+; CHECK-DAG: [[RELAXED:%.*]] = OpConstantNull [[I32Ty]]
+
+; CHECK: [[ADD]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicIAdd [[I32Ty]] [[A]] [[SCOPE]] [[RELAXED]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_add(i32* %ptr, i32 %val) {
+ %r = atomicrmw add i32* %ptr, i32 %val monotonic
+ ret i32 %r
+}
+
+; CHECK: [[SUB]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicISub [[I32Ty]] [[A]] [[SCOPE]] [[RELAXED]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_sub(i32* %ptr, i32 %val) {
+ %r = atomicrmw sub i32* %ptr, i32 %val monotonic
+ ret i32 %r
+}
+
+; CHECK: [[MIN]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicSMin [[I32Ty]] [[A]] [[SCOPE]] [[RELAXED]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_min(i32* %ptr, i32 %val) {
+ %r = atomicrmw min i32* %ptr, i32 %val monotonic
+ ret i32 %r
+}
+
+; CHECK: [[MAX]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicSMax [[I32Ty]] [[A]] [[SCOPE]] [[RELAXED]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_max(i32* %ptr, i32 %val) {
+ %r = atomicrmw max i32* %ptr, i32 %val monotonic
+ ret i32 %r
+}
+
+; CHECK: [[UMIN]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicUMin [[I32Ty]] [[A]] [[SCOPE]] [[RELAXED]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_umin(i32* %ptr, i32 %val) {
+ %r = atomicrmw umin i32* %ptr, i32 %val monotonic
+ ret i32 %r
+}
+
+; CHECK: [[UMAX]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicUMax [[I32Ty]] [[A]] [[SCOPE]] [[RELAXED]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_umax(i32* %ptr, i32 %val) {
+ %r = atomicrmw umax i32* %ptr, i32 %val monotonic
+ ret i32 %r
+}
+
+; CHECK: [[AND]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicAnd [[I32Ty]] [[A]] [[SCOPE]] [[RELAXED]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_and(i32* %ptr, i32 %val) {
+ %r = atomicrmw and i32* %ptr, i32 %val monotonic
+ ret i32 %r
+}
+
+; CHECK: [[OR]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicOr [[I32Ty]] [[A]] [[SCOPE]] [[RELAXED]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_or(i32* %ptr, i32 %val) {
+ %r = atomicrmw or i32* %ptr, i32 %val monotonic
+ ret i32 %r
+}
+
+; CHECK: [[XOR]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicXor [[I32Ty]] [[A]] [[SCOPE]] [[RELAXED]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_xor(i32* %ptr, i32 %val) {
+ %r = atomicrmw xor i32* %ptr, i32 %val monotonic
+ ret i32 %r
+}
--- /dev/null
+; RUN: llc %s -o - | FileCheck %s
+
+target triple = "spirv32-unknown-unknown"
+
+; CHECK-DAG: OpName [[ADD:%.*]] "test_add"
+; CHECK-DAG: OpName [[SUB:%.*]] "test_sub"
+; CHECK-DAG: OpName [[MIN:%.*]] "test_min"
+; CHECK-DAG: OpName [[MAX:%.*]] "test_max"
+; CHECK-DAG: OpName [[UMIN:%.*]] "test_umin"
+; CHECK-DAG: OpName [[UMAX:%.*]] "test_umax"
+; CHECK-DAG: OpName [[AND:%.*]] "test_and"
+; CHECK-DAG: OpName [[OR:%.*]] "test_or"
+; CHECK-DAG: OpName [[XOR:%.*]] "test_xor"
+
+; CHECK-DAG: [[I32Ty:%.*]] = OpTypeInt 32 0
+; Device scope is encoded with constant 1
+; CHECK-DAG: [[SCOPE:%.*]] = OpConstant [[I32Ty]] 1
+; "acq_rel" maps to the constant 8
+; CHECK-DAG: [[ACQREL:%.*]] = OpConstant [[I32Ty]] 8
+
+; CHECK: [[ADD]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicIAdd [[I32Ty]] [[A]] [[SCOPE]] [[ACQREL]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_add(i32* %ptr, i32 %val) {
+ %r = atomicrmw add i32* %ptr, i32 %val acq_rel
+ ret i32 %r
+}
+
+; CHECK: [[SUB]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicISub [[I32Ty]] [[A]] [[SCOPE]] [[ACQREL]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_sub(i32* %ptr, i32 %val) {
+ %r = atomicrmw sub i32* %ptr, i32 %val acq_rel
+ ret i32 %r
+}
+
+; CHECK: [[MIN]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicSMin [[I32Ty]] [[A]] [[SCOPE]] [[ACQREL]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_min(i32* %ptr, i32 %val) {
+ %r = atomicrmw min i32* %ptr, i32 %val acq_rel
+ ret i32 %r
+}
+
+; CHECK: [[MAX]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicSMax [[I32Ty]] [[A]] [[SCOPE]] [[ACQREL]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_max(i32* %ptr, i32 %val) {
+ %r = atomicrmw max i32* %ptr, i32 %val acq_rel
+ ret i32 %r
+}
+
+; CHECK: [[UMIN]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicUMin [[I32Ty]] [[A]] [[SCOPE]] [[ACQREL]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_umin(i32* %ptr, i32 %val) {
+ %r = atomicrmw umin i32* %ptr, i32 %val acq_rel
+ ret i32 %r
+}
+
+; CHECK: [[UMAX]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicUMax [[I32Ty]] [[A]] [[SCOPE]] [[ACQREL]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_umax(i32* %ptr, i32 %val) {
+ %r = atomicrmw umax i32* %ptr, i32 %val acq_rel
+ ret i32 %r
+}
+
+; CHECK: [[AND]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicAnd [[I32Ty]] [[A]] [[SCOPE]] [[ACQREL]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_and(i32* %ptr, i32 %val) {
+ %r = atomicrmw and i32* %ptr, i32 %val acq_rel
+ ret i32 %r
+}
+
+; CHECK: [[OR]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicOr [[I32Ty]] [[A]] [[SCOPE]] [[ACQREL]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_or(i32* %ptr, i32 %val) {
+ %r = atomicrmw or i32* %ptr, i32 %val acq_rel
+ ret i32 %r
+}
+
+; CHECK: [[XOR]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicXor [[I32Ty]] [[A]] [[SCOPE]] [[ACQREL]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_xor(i32* %ptr, i32 %val) {
+ %r = atomicrmw xor i32* %ptr, i32 %val acq_rel
+ ret i32 %r
+}
--- /dev/null
+; RUN: llc %s -o - | FileCheck %s
+
+target triple = "spirv32-unknown-unknown"
+
+; CHECK-DAG: OpName [[ADD:%.*]] "test_add"
+; CHECK-DAG: OpName [[SUB:%.*]] "test_sub"
+; CHECK-DAG: OpName [[MIN:%.*]] "test_min"
+; CHECK-DAG: OpName [[MAX:%.*]] "test_max"
+; CHECK-DAG: OpName [[UMIN:%.*]] "test_umin"
+; CHECK-DAG: OpName [[UMAX:%.*]] "test_umax"
+; CHECK-DAG: OpName [[AND:%.*]] "test_and"
+; CHECK-DAG: OpName [[OR:%.*]] "test_or"
+; CHECK-DAG: OpName [[XOR:%.*]] "test_xor"
+
+; CHECK-DAG: [[I32Ty:%.*]] = OpTypeInt 32 0
+; Device scope is encoded with constant 1
+; CHECK-DAG: [[SCOPE:%.*]] = OpConstant [[I32Ty]] 1
+; "sequentially consistent" maps to constant 16
+; CHECK-DAG: [[SEQ:%.*]] = OpConstant [[I32Ty]] 16
+
+; CHECK: [[ADD]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicIAdd [[I32Ty]] [[A]] [[SCOPE]] [[SEQ]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_add(i32* %ptr, i32 %val) {
+ %r = atomicrmw add i32* %ptr, i32 %val seq_cst
+ ret i32 %r
+}
+
+; CHECK: [[SUB]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicISub [[I32Ty]] [[A]] [[SCOPE]] [[SEQ]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_sub(i32* %ptr, i32 %val) {
+ %r = atomicrmw sub i32* %ptr, i32 %val seq_cst
+ ret i32 %r
+}
+
+; CHECK: [[MIN]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicSMin [[I32Ty]] [[A]] [[SCOPE]] [[SEQ]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_min(i32* %ptr, i32 %val) {
+ %r = atomicrmw min i32* %ptr, i32 %val seq_cst
+ ret i32 %r
+}
+
+; CHECK: [[MAX]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicSMax [[I32Ty]] [[A]] [[SCOPE]] [[SEQ]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_max(i32* %ptr, i32 %val) {
+ %r = atomicrmw max i32* %ptr, i32 %val seq_cst
+ ret i32 %r
+}
+
+; CHECK: [[UMIN]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicUMin [[I32Ty]] [[A]] [[SCOPE]] [[SEQ]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_umin(i32* %ptr, i32 %val) {
+ %r = atomicrmw umin i32* %ptr, i32 %val seq_cst
+ ret i32 %r
+}
+
+; CHECK: [[UMAX]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicUMax [[I32Ty]] [[A]] [[SCOPE]] [[SEQ]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_umax(i32* %ptr, i32 %val) {
+ %r = atomicrmw umax i32* %ptr, i32 %val seq_cst
+ ret i32 %r
+}
+
+; CHECK: [[AND]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicAnd [[I32Ty]] [[A]] [[SCOPE]] [[SEQ]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_and(i32* %ptr, i32 %val) {
+ %r = atomicrmw and i32* %ptr, i32 %val seq_cst
+ ret i32 %r
+}
+
+; CHECK: [[OR]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicOr [[I32Ty]] [[A]] [[SCOPE]] [[SEQ]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_or(i32* %ptr, i32 %val) {
+ %r = atomicrmw or i32* %ptr, i32 %val seq_cst
+ ret i32 %r
+}
+
+; CHECK: [[XOR]] = OpFunction [[I32Ty]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpAtomicXor [[I32Ty]] [[A]] [[SCOPE]] [[SEQ]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @test_xor(i32* %ptr, i32 %val) {
+ %r = atomicrmw xor i32* %ptr, i32 %val seq_cst
+ ret i32 %r
+}
--- /dev/null
+; RUN: llc %s -o - | FileCheck %s
+
+target triple = "spirv32-unknown-unknown"
+
+; CHECK-DAG: OpName [[UEQ:%.*]] "test_ueq"
+; CHECK-DAG: OpName [[OEQ:%.*]] "test_oeq"
+; CHECK-DAG: OpName [[UNE:%.*]] "test_une"
+; CHECK-DAG: OpName [[ONE:%.*]] "test_one"
+; CHECK-DAG: OpName [[ULT:%.*]] "test_ult"
+; CHECK-DAG: OpName [[OLT:%.*]] "test_olt"
+; CHECK-DAG: OpName [[ULE:%.*]] "test_ule"
+; CHECK-DAG: OpName [[OLE:%.*]] "test_ole"
+; CHECK-DAG: OpName [[UGT:%.*]] "test_ugt"
+; CHECK-DAG: OpName [[OGT:%.*]] "test_ogt"
+; CHECK-DAG: OpName [[UGE:%.*]] "test_uge"
+; CHECK-DAG: OpName [[OGE:%.*]] "test_oge"
+; CHECK-DAG: OpName [[UNO:%.*]] "test_uno"
+; CHECK-DAG: OpName [[ORD:%.*]] "test_ord"
+
+; CHECK-DAG: OpName [[v3UEQ:%.*]] "test_v3_ueq"
+; CHECK-DAG: OpName [[v3OEQ:%.*]] "test_v3_oeq"
+; CHECK-DAG: OpName [[v3UNE:%.*]] "test_v3_une"
+; CHECK-DAG: OpName [[v3ONE:%.*]] "test_v3_one"
+; CHECK-DAG: OpName [[v3ULT:%.*]] "test_v3_ult"
+; CHECK-DAG: OpName [[v3OLT:%.*]] "test_v3_olt"
+; CHECK-DAG: OpName [[v3ULE:%.*]] "test_v3_ule"
+; CHECK-DAG: OpName [[v3OLE:%.*]] "test_v3_ole"
+; CHECK-DAG: OpName [[v3UGT:%.*]] "test_v3_ugt"
+; CHECK-DAG: OpName [[v3OGT:%.*]] "test_v3_ogt"
+; CHECK-DAG: OpName [[v3UGE:%.*]] "test_v3_uge"
+; CHECK-DAG: OpName [[v3OGE:%.*]] "test_v3_oge"
+; CHECK-DAG: OpName [[v3UNO:%.*]] "test_v3_uno"
+; CHECK-DAG: OpName [[v3ORD:%.*]] "test_v3_ord"
+
+; CHECK: [[UEQ]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ueq(float %a, float %b) {
+ %r = fcmp ueq float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[OEQ]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_oeq(float %a, float %b) {
+ %r = fcmp oeq float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[UNE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordNotEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_une(float %a, float %b) {
+ %r = fcmp une float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[ONE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdNotEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_one(float %a, float %b) {
+ %r = fcmp one float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[ULT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordLessThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ult(float %a, float %b) {
+ %r = fcmp ult float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[OLT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdLessThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_olt(float %a, float %b) {
+ %r = fcmp olt float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[ULE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordLessThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ule(float %a, float %b) {
+ %r = fcmp ule float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[OLE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdLessThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ole(float %a, float %b) {
+ %r = fcmp ole float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[UGT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordGreaterThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ugt(float %a, float %b) {
+ %r = fcmp ugt float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[OGT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdGreaterThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ogt(float %a, float %b) {
+ %r = fcmp ogt float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[UGE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordGreaterThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_uge(float %a, float %b) {
+ %r = fcmp uge float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[OGE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdGreaterThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_oge(float %a, float %b) {
+ %r = fcmp oge float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[ORD]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpOrdered {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ord(float %a, float %b) {
+ %r = fcmp ord float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[UNO]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpUnordered {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_uno(float %a, float %b) {
+ %r = fcmp uno float %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[v3UEQ]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ueq(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp ueq <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3OEQ]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_oeq(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp oeq <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3UNE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordNotEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_une(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp une <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3ONE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdNotEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_one(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp one <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3ULT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordLessThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ult(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp ult <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3OLT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdLessThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_olt(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp olt <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3ULE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordLessThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ule(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp ule <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3OLE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdLessThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ole(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp ole <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3UGT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordGreaterThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ugt(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp ugt <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3OGT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdGreaterThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ogt(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp ogt <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3UGE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFUnordGreaterThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_uge(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp uge <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3OGE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpFOrdGreaterThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_oge(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp oge <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3ORD]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpOrdered {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ord(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp ord <3 x float> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3UNO]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpUnordered {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_uno(<3 x float> %a, <3 x float> %b) {
+ %r = fcmp uno <3 x float> %a, %b
+ ret <3 x i1> %r
+}
--- /dev/null
+; RUN: llc %s -o - | FileCheck %s
+
+target triple = "spirv32-unknown-unknown"
+
+; CHECK-DAG: OpName [[TRUNC32_16:%.*]] "f32tof16"
+; CHECK-DAG: OpName [[EXT16_32:%.*]] "f16tof32"
+
+; CHECK-DAG: OpName [[TRUNC32_16v3:%.*]] "f32tof16v3"
+; CHECK-DAG: OpName [[EXT16_32v3:%.*]] "f16tof32v3"
+
+; CHECK-DAG: OpName [[F32toS32:%.*]] "f32tos32"
+; CHECK-DAG: OpName [[F32toS16:%.*]] "f32tos16"
+; CHECK-DAG: OpName [[F32toS8:%.*]] "f32tos8"
+; CHECK-DAG: OpName [[F16toS32:%.*]] "f16tos32"
+; CHECK-DAG: OpName [[F16toS16:%.*]] "f16tos16"
+; CHECK-DAG: OpName [[F16toS8:%.*]] "f16tos8"
+
+; CHECK-DAG: OpName [[F32toU32v2:%.*]] "f32tou32v2"
+; CHECK-DAG: OpName [[F32toU16v2:%.*]] "f32tou16v2"
+; CHECK-DAG: OpName [[F32toU8v2:%.*]] "f32tou8v2"
+; CHECK-DAG: OpName [[F16toU32v2:%.*]] "f16tou32v2"
+; CHECK-DAG: OpName [[F16toU16v2:%.*]] "f16tou16v2"
+; CHECK-DAG: OpName [[F16toU8v2:%.*]] "f16tou8v2"
+
+; CHECK-DAG: [[F32:%.*]] = OpTypeFloat 32
+; CHECK-DAG: [[F16:%.*]] = OpTypeFloat 16
+; CHECK-DAG: [[F32v2:%.*]] = OpTypeVector [[F32]] 2
+; CHECK-DAG: [[F16v2:%.*]] = OpTypeVector [[F16]] 2
+; CHECK-DAG: [[F32v3:%.*]] = OpTypeVector [[F32]] 3
+; CHECK-DAG: [[F16v3:%.*]] = OpTypeVector [[F16]] 3
+; CHECK-DAG: [[U32:%.*]] = OpTypeInt 32 0
+; CHECK-DAG: [[U16:%.*]] = OpTypeInt 16 0
+; CHECK-DAG: [[U8:%.*]] = OpTypeInt 8 0
+; CHECK-DAG: [[U32v2:%.*]] = OpTypeVector [[U32]] 2
+; CHECK-DAG: [[U16v2:%.*]] = OpTypeVector [[U16]] 2
+; CHECK-DAG: [[U8v2:%.*]] = OpTypeVector [[U8]] 2
+
+
+; CHECK: [[TRUNC32_16]] = OpFunction [[F16]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F32]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpFConvert [[F16]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define half @f32tof16(float %a) {
+ %r = fptrunc float %a to half
+ ret half %r
+}
+
+; CHECK: [[EXT16_32]] = OpFunction [[F32]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F16]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpFConvert [[F32]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define float @f16tof32(half %a) {
+ %r = fpext half %a to float
+ ret float %r
+}
+
+; CHECK: [[TRUNC32_16v3]] = OpFunction [[F16v3]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F32v3]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpFConvert [[F16v3]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x half> @f32tof16v3(<3 x float> %a) {
+ %r = fptrunc <3 x float> %a to <3 x half>
+ ret <3 x half> %r
+}
+
+; CHECK: [[EXT16_32v3]] = OpFunction [[F32v3]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F16v3]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpFConvert [[F32v3]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x float> @f16tof32v3(<3 x half> %a) {
+ %r = fpext <3 x half> %a to <3 x float>
+ ret <3 x float> %r
+}
+
+; CHECK: [[F32toS32]] = OpFunction [[U32]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F32]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToS [[U32]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @f32tos32(float %a) {
+ %r = fptosi float %a to i32
+ ret i32 %r
+}
+
+; CHECK: [[F32toS16]] = OpFunction [[U16]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F32]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToS [[U16]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i16 @f32tos16(float %a) {
+ %r = fptosi float %a to i16
+ ret i16 %r
+}
+
+; CHECK: [[F32toS8]] = OpFunction [[U8]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F32]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToS [[U8]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i8 @f32tos8(float %a) {
+ %r = fptosi float %a to i8
+ ret i8 %r
+}
+
+; CHECK: [[F16toS32]] = OpFunction [[U32]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F16]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToS [[U32]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @f16tos32(half %a) {
+ %r = fptosi half %a to i32
+ ret i32 %r
+}
+
+; CHECK: [[F16toS16]] = OpFunction [[U16]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F16]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToS [[U16]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i16 @f16tos16(half %a) {
+ %r = fptosi half %a to i16
+ ret i16 %r
+}
+
+; CHECK: [[F16toS8]] = OpFunction [[U8]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F16]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToS [[U8]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i8 @f16tos8(half %a) {
+ %r = fptosi half %a to i8
+ ret i8 %r
+}
+
+; CHECK: [[F32toU32v2]] = OpFunction [[U32v2]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F32v2]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToU [[U32v2]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <2 x i32> @f32tou32v2(<2 x float> %a) {
+ %r = fptoui <2 x float> %a to <2 x i32>
+ ret <2 x i32> %r
+}
+
+; CHECK: [[F32toU16v2]] = OpFunction [[U16v2]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F32v2]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToU [[U16v2]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <2 x i16> @f32tou16v2(<2 x float> %a) {
+ %r = fptoui <2 x float> %a to <2 x i16>
+ ret <2 x i16> %r
+}
+
+; CHECK: [[F32toU8v2]] = OpFunction [[U8v2]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F32v2]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToU [[U8v2]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <2 x i8> @f32tou8v2(<2 x float> %a) {
+ %r = fptoui <2 x float> %a to <2 x i8>
+ ret <2 x i8> %r
+}
+
+; CHECK: [[F16toU32v2]] = OpFunction [[U32v2]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F16v2]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToU [[U32v2]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <2 x i32> @f16tou32v2(<2 x half> %a) {
+ %r = fptoui <2 x half> %a to <2 x i32>
+ ret <2 x i32> %r
+}
+
+; CHECK: [[F16toU16v2]] = OpFunction [[U16v2]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F16v2]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToU [[U16v2]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <2 x i16> @f16tou16v2(<2 x half> %a) {
+ %r = fptoui <2 x half> %a to <2 x i16>
+ ret <2 x i16> %r
+}
+
+; CHECK: [[F16toU8v2]] = OpFunction [[U8v2]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[F16v2]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpConvertFToU [[U8v2]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <2 x i8> @f16tou8v2(<2 x half> %a) {
+ %r = fptoui <2 x half> %a to <2 x i8>
+ ret <2 x i8> %r
+}
--- /dev/null
+; RUN: llc %s -o - | FileCheck %s
+
+target triple = "spirv32-unknown-unknown"
+
+; CHECK-DAG: OpName [[EQ:%.*]] "test_eq"
+; CHECK-DAG: OpName [[NE:%.*]] "test_ne"
+; CHECK-DAG: OpName [[ULT:%.*]] "test_ult"
+; CHECK-DAG: OpName [[SLT:%.*]] "test_slt"
+; CHECK-DAG: OpName [[ULE:%.*]] "test_ule"
+; CHECK-DAG: OpName [[SLE:%.*]] "test_sle"
+; CHECK-DAG: OpName [[UGT:%.*]] "test_ugt"
+; CHECK-DAG: OpName [[SGT:%.*]] "test_sgt"
+; CHECK-DAG: OpName [[UGE:%.*]] "test_uge"
+; CHECK-DAG: OpName [[SGE:%.*]] "test_sge"
+
+; CHECK-DAG: OpName [[v3EQ:%.*]] "test_v3_eq"
+; CHECK-DAG: OpName [[v3NE:%.*]] "test_v3_ne"
+; CHECK-DAG: OpName [[v3ULT:%.*]] "test_v3_ult"
+; CHECK-DAG: OpName [[v3SLT:%.*]] "test_v3_slt"
+; CHECK-DAG: OpName [[v3ULE:%.*]] "test_v3_ule"
+; CHECK-DAG: OpName [[v3SLE:%.*]] "test_v3_sle"
+; CHECK-DAG: OpName [[v3UGT:%.*]] "test_v3_ugt"
+; CHECK-DAG: OpName [[v3SGT:%.*]] "test_v3_sgt"
+; CHECK-DAG: OpName [[v3UGE:%.*]] "test_v3_uge"
+; CHECK-DAG: OpName [[v3SGE:%.*]] "test_v3_sge"
+
+; CHECK: [[EQ]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpIEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_eq(i32 %a, i32 %b) {
+ %r = icmp eq i32 %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[NE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpINotEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ne(i32 %a, i32 %b) {
+ %r = icmp ne i32 %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[SLT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpSLessThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_slt(i32 %a, i32 %b) {
+ %r = icmp slt i32 %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[ULT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpULessThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ult(i32 %a, i32 %b) {
+ %r = icmp ult i32 %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[ULE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpULessThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ule(i32 %a, i32 %b) {
+ %r = icmp ule i32 %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[SLE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpSLessThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_sle(i32 %a, i32 %b) {
+ %r = icmp sle i32 %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[UGT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpUGreaterThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ugt(i32 %a, i32 %b) {
+ %r = icmp ugt i32 %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[SGT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpSGreaterThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_sgt(i32 %a, i32 %b) {
+ %r = icmp sgt i32 %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[UGE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpUGreaterThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_uge(i32 %a, i32 %b) {
+ %r = icmp uge i32 %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[SGE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpSGreaterThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_sge(i32 %a, i32 %b) {
+ %r = icmp sge i32 %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[v3EQ]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpIEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_eq(<3 x i32> %a, <3 x i32> %b) {
+ %r = icmp eq <3 x i32> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3NE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpINotEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ne(<3 x i32> %a, <3 x i32> %b) {
+ %r = icmp ne <3 x i32> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3SLT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpSLessThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_slt(<3 x i32> %a, <3 x i32> %b) {
+ %r = icmp slt <3 x i32> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3ULT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpULessThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ult(<3 x i32> %a, <3 x i32> %b) {
+ %r = icmp ult <3 x i32> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3ULE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpULessThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ule(<3 x i32> %a, <3 x i32> %b) {
+ %r = icmp ule <3 x i32> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3SLE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpSLessThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_sle(<3 x i32> %a, <3 x i32> %b) {
+ %r = icmp sle <3 x i32> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3UGT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpUGreaterThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_ugt(<3 x i32> %a, <3 x i32> %b) {
+ %r = icmp ugt <3 x i32> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3SGT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpSGreaterThan {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_sgt(<3 x i32> %a, <3 x i32> %b) {
+ %r = icmp sgt <3 x i32> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3UGE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpUGreaterThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_uge(<3 x i32> %a, <3 x i32> %b) {
+ %r = icmp uge <3 x i32> %a, %b
+ ret <3 x i1> %r
+}
+
+; CHECK: [[v3SGE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpSGreaterThanEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <3 x i1> @test_v3_sge(<3 x i32> %a, <3 x i32> %b) {
+ %r = icmp sge <3 x i32> %a, %b
+ ret <3 x i1> %r
+}
--- /dev/null
+; RUN: llc %s -o - | FileCheck %s
+
+target triple = "spirv32-unknown-unknown"
+
+; CHECK-DAG: OpName [[TRUNC32_16:%.*]] "i32toi16"
+; CHECK-DAG: OpName [[TRUNC32_8:%.*]] "i32toi8"
+; CHECK-DAG: OpName [[TRUNC16_8:%.*]] "i16toi8"
+; CHECK-DAG: OpName [[SEXT8_32:%.*]] "s8tos32"
+; CHECK-DAG: OpName [[SEXT8_16:%.*]] "s8tos16"
+; CHECK-DAG: OpName [[SEXT16_32:%.*]] "s16tos32"
+; CHECK-DAG: OpName [[ZEXT8_32:%.*]] "u8tou32"
+; CHECK-DAG: OpName [[ZEXT8_16:%.*]] "u8tou16"
+; CHECK-DAG: OpName [[ZEXT16_32:%.*]] "u16tou32"
+
+; CHECK-DAG: OpName [[TRUNC32_16v4:%.*]] "i32toi16v4"
+; CHECK-DAG: OpName [[TRUNC32_8v4:%.*]] "i32toi8v4"
+; CHECK-DAG: OpName [[TRUNC16_8v4:%.*]] "i16toi8v4"
+; CHECK-DAG: OpName [[SEXT8_32v4:%.*]] "s8tos32v4"
+; CHECK-DAG: OpName [[SEXT8_16v4:%.*]] "s8tos16v4"
+; CHECK-DAG: OpName [[SEXT16_32v4:%.*]] "s16tos32v4"
+; CHECK-DAG: OpName [[ZEXT8_32v4:%.*]] "u8tou32v4"
+; CHECK-DAG: OpName [[ZEXT8_16v4:%.*]] "u8tou16v4"
+; CHECK-DAG: OpName [[ZEXT16_32v4:%.*]] "u16tou32v4"
+
+; CHECK-DAG: [[U32:%.*]] = OpTypeInt 32 0
+; CHECK-DAG: [[U16:%.*]] = OpTypeInt 16 0
+; CHECK-DAG: [[U8:%.*]] = OpTypeInt 8 0
+; CHECK-DAG: [[U32v4:%.*]] = OpTypeVector [[U32]] 4
+; CHECK-DAG: [[U16v4:%.*]] = OpTypeVector [[U16]] 4
+; CHECK-DAG: [[U8v4:%.*]] = OpTypeVector [[U8]] 4
+
+
+; CHECK: [[TRUNC32_16]] = OpFunction [[U16]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U32]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U16]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i16 @i32toi16(i32 %a) {
+ %r = trunc i32 %a to i16
+ ret i16 %r
+}
+
+; CHECK: [[TRUNC32_8]] = OpFunction [[U8]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U32]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U8]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i8 @i32toi8(i32 %a) {
+ %r = trunc i32 %a to i8
+ ret i8 %r
+}
+
+; CHECK: [[TRUNC16_8]] = OpFunction [[U8]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U16]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U8]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i8 @i16toi8(i16 %a) {
+ %r = trunc i16 %a to i8
+ ret i8 %r
+}
+
+
+; CHECK: [[SEXT8_32]] = OpFunction [[U32]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U8]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpSConvert [[U32]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @s8tos32(i8 %a) {
+ %r = sext i8 %a to i32
+ ret i32 %r
+}
+
+; CHECK: [[SEXT8_16]] = OpFunction [[U16]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U8]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpSConvert [[U16]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i16 @s8tos16(i8 %a) {
+ %r = sext i8 %a to i16
+ ret i16 %r
+}
+
+; CHECK: [[SEXT16_32]] = OpFunction [[U32]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U16]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpSConvert [[U32]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @s16tos32(i16 %a) {
+ %r = sext i16 %a to i32
+ ret i32 %r
+}
+
+; CHECK: [[ZEXT8_32]] = OpFunction [[U32]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U8]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U32]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @u8tou32(i8 %a) {
+ %r = zext i8 %a to i32
+ ret i32 %r
+}
+
+; CHECK: [[ZEXT8_16]] = OpFunction [[U16]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U8]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U16]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i16 @u8tou16(i8 %a) {
+ %r = zext i8 %a to i16
+ ret i16 %r
+}
+
+; CHECK: [[ZEXT16_32]] = OpFunction [[U32]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U16]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U32]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i32 @u16tou32(i16 %a) {
+ %r = zext i16 %a to i32
+ ret i32 %r
+}
+
+; CHECK: [[TRUNC32_16v4]] = OpFunction [[U16v4]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U32v4]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U16v4]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <4 x i16> @i32toi16v4(<4 x i32> %a) {
+ %r = trunc <4 x i32> %a to <4 x i16>
+ ret <4 x i16> %r
+}
+
+; CHECK: [[TRUNC32_8v4]] = OpFunction [[U8v4]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U32v4]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U8v4]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <4 x i8> @i32toi8v4(<4 x i32> %a) {
+ %r = trunc <4 x i32> %a to <4 x i8>
+ ret <4 x i8> %r
+}
+
+; CHECK: [[TRUNC16_8v4]] = OpFunction [[U8v4]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U16v4]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U8v4]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <4 x i8> @i16toi8v4(<4 x i16> %a) {
+ %r = trunc <4 x i16> %a to <4 x i8>
+ ret <4 x i8> %r
+}
+
+
+; CHECK: [[SEXT8_32v4]] = OpFunction [[U32v4]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U8v4]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpSConvert [[U32v4]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <4 x i32> @s8tos32v4(<4 x i8> %a) {
+ %r = sext <4 x i8> %a to <4 x i32>
+ ret <4 x i32> %r
+}
+
+; CHECK: [[SEXT8_16v4]] = OpFunction [[U16v4]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U8v4]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpSConvert [[U16v4]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <4 x i16> @s8tos16v4(<4 x i8> %a) {
+ %r = sext <4 x i8> %a to <4 x i16>
+ ret <4 x i16> %r
+}
+
+; CHECK: [[SEXT16_32v4]] = OpFunction [[U32v4]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U16v4]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpSConvert [[U32v4]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <4 x i32> @s16tos32v4(<4 x i16> %a) {
+ %r = sext <4 x i16> %a to <4 x i32>
+ ret <4 x i32> %r
+}
+
+; CHECK: [[ZEXT8_32v4]] = OpFunction [[U32v4]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U8v4]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U32v4]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <4 x i32> @u8tou32v4(<4 x i8> %a) {
+ %r = zext <4 x i8> %a to <4 x i32>
+ ret <4 x i32> %r
+}
+
+; CHECK: [[ZEXT8_16v4]] = OpFunction [[U16v4]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U8v4]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U16v4]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <4 x i16> @u8tou16v4(<4 x i8> %a) {
+ %r = zext <4 x i8> %a to <4 x i16>
+ ret <4 x i16> %r
+}
+
+; CHECK: [[ZEXT16_32v4]] = OpFunction [[U32v4]]
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter [[U16v4]]
+; CHECK: OpLabel
+; CHECK: [[R:%.*]] = OpUConvert [[U32v4]] [[A]]
+; CHECK: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define <4 x i32> @u16tou32v4(<4 x i16> %a) {
+ %r = zext <4 x i16> %a to <4 x i32>
+ ret <4 x i32> %r
+}
--- /dev/null
+; RUN: llc %s -o - | FileCheck %s
+
+target triple = "spirv32-unknown-unknown"
+
+; CHECK-DAG: OpName [[EQ:%.*]] "test_eq"
+; CHECK-DAG: OpName [[NE:%.*]] "test_ne"
+; CHECK-DAG: OpName [[ULT:%.*]] "test_ult"
+; CHECK-DAG: OpName [[SLT:%.*]] "test_slt"
+; CHECK-DAG: OpName [[ULE:%.*]] "test_ule"
+; CHECK-DAG: OpName [[SLE:%.*]] "test_sle"
+; CHECK-DAG: OpName [[UGT:%.*]] "test_ugt"
+; CHECK-DAG: OpName [[SGT:%.*]] "test_sgt"
+; CHECK-DAG: OpName [[UGE:%.*]] "test_uge"
+; CHECK-DAG: OpName [[SGE:%.*]] "test_sge"
+
+; FIXME: Translator uses OpIEqual/OpINotEqual for test_eq/test_ne cases
+; CHECK: [[EQ]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpPtrEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_eq(i16* %a, i16* %b) {
+ %r = icmp eq i16* %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[NE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[R:%.*]] = OpPtrNotEqual {{%.+}} [[A]] [[B]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ne(i16* %a, i16* %b) {
+ %r = icmp ne i16* %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[SLT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[AI:%.*]] = OpConvertPtrToU {{%.+}} [[A]]
+; CHECK-NEXT: [[BI:%.*]] = OpConvertPtrToU {{%.+}} [[B]]
+; CHECK: [[R:%.*]] = OpSLessThan {{%.+}} [[AI]] [[BI]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_slt(i16* %a, i16* %b) {
+ %r = icmp slt i16* %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[ULT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[AI:%.*]] = OpConvertPtrToU {{%.+}} [[A]]
+; CHECK-NEXT: [[BI:%.*]] = OpConvertPtrToU {{%.+}} [[B]]
+; CHECK: [[R:%.*]] = OpULessThan {{%.+}} [[AI]] [[BI]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ult(i16* %a, i16* %b) {
+ %r = icmp ult i16* %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[ULE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[AI:%.*]] = OpConvertPtrToU {{%.+}} [[A]]
+; CHECK-NEXT: [[BI:%.*]] = OpConvertPtrToU {{%.+}} [[B]]
+; CHECK: [[R:%.*]] = OpULessThanEqual {{%.+}} [[AI]] [[BI]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ule(i16* %a, i16* %b) {
+ %r = icmp ule i16* %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[SLE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[AI:%.*]] = OpConvertPtrToU {{%.+}} [[A]]
+; CHECK-NEXT: [[BI:%.*]] = OpConvertPtrToU {{%.+}} [[B]]
+; CHECK: [[R:%.*]] = OpSLessThanEqual {{%.+}} [[AI]] [[BI]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_sle(i16* %a, i16* %b) {
+ %r = icmp sle i16* %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[UGT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[AI:%.*]] = OpConvertPtrToU {{%.+}} [[A]]
+; CHECK-NEXT: [[BI:%.*]] = OpConvertPtrToU {{%.+}} [[B]]
+; CHECK: [[R:%.*]] = OpUGreaterThan {{%.+}} [[AI]] [[BI]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_ugt(i16* %a, i16* %b) {
+ %r = icmp ugt i16* %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[SGT]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[AI:%.*]] = OpConvertPtrToU {{%.+}} [[A]]
+; CHECK-NEXT: [[BI:%.*]] = OpConvertPtrToU {{%.+}} [[B]]
+; CHECK: [[R:%.*]] = OpSGreaterThan {{%.+}} [[AI]] [[BI]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_sgt(i16* %a, i16* %b) {
+ %r = icmp sgt i16* %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[UGE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[AI:%.*]] = OpConvertPtrToU {{%.+}} [[A]]
+; CHECK-NEXT: [[BI:%.*]] = OpConvertPtrToU {{%.+}} [[B]]
+; CHECK: [[R:%.*]] = OpUGreaterThanEqual {{%.+}} [[AI]] [[BI]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_uge(i16* %a, i16* %b) {
+ %r = icmp uge i16* %a, %b
+ ret i1 %r
+}
+
+; CHECK: [[SGE]] = OpFunction
+; CHECK-NEXT: [[A:%.*]] = OpFunctionParameter
+; CHECK-NEXT: [[B:%.*]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[AI:%.*]] = OpConvertPtrToU {{%.+}} [[A]]
+; CHECK-NEXT: [[BI:%.*]] = OpConvertPtrToU {{%.+}} [[B]]
+; CHECK: [[R:%.*]] = OpSGreaterThanEqual {{%.+}} [[AI]] [[BI]]
+; CHECK-NEXT: OpReturnValue [[R]]
+; CHECK-NEXT: OpFunctionEnd
+define i1 @test_sge(i16* %a, i16* %b) {
+ %r = icmp sge i16* %a, %b
+ ret i1 %r
+}