//===- OCL20ToSPIRV.cpp - Transform OCL20 to SPIR-V builtins -----*- C++ -*-===// // // The LLVM/SPIRV Translator // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // // Copyright (c) 2014 Advanced Micro Devices, Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal with the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimers. // Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimers in the documentation // and/or other materials provided with the distribution. // Neither the names of Advanced Micro Devices, Inc., nor the names of its // contributors may be used to endorse or promote products derived from this // Software without specific prior written permission. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH // THE SOFTWARE. // //===----------------------------------------------------------------------===// // // This file implements translation of OCL20 builtin functions. // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "cl20tospv" #include "SPIRVInternal.h" #include "OCLUtil.h" #include "OCLTypeToSPIRV.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/IR/InstVisitor.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Verifier.h" #include "llvm/Pass.h" #include "llvm/PassSupport.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" #include <set> using namespace llvm; using namespace SPIRV; using namespace OCLUtil; namespace SPIRV { static size_t getOCLCpp11AtomicMaxNumOps(StringRef Name) { return StringSwitch<size_t>(Name) .Cases("load", "flag_test_and_set", "flag_clear", 3) .Cases("store", "exchange", 4) .StartsWith("compare_exchange", 6) .StartsWith("fetch", 4) .Default(0); } class OCL20ToSPIRV: public ModulePass, public InstVisitor<OCL20ToSPIRV> { public: OCL20ToSPIRV():ModulePass(ID), M(nullptr), Ctx(nullptr), CLVer(0) { initializeOCL20ToSPIRVPass(*PassRegistry::getPassRegistry()); } virtual bool runOnModule(Module &M); void getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequired<OCLTypeToSPIRV>(); } virtual void visitCallInst(CallInst &CI); /// Transform barrier/work_group_barrier/sub_group_barrier /// to __spirv_ControlBarrier. /// barrier(flag) => /// __spirv_ControlBarrier(workgroup, workgroup, map(flag)) /// work_group_barrier(scope, flag) => /// __spirv_ControlBarrier(workgroup, map(scope), map(flag)) /// sub_group_barrier(scope, flag) => /// __spirv_ControlBarrier(subgroup, map(scope), map(flag)) void visitCallBarrier(CallInst *CI); /// Erase useless convert functions. /// \return true if the call instruction is erased. bool eraseUselessConvert(CallInst *Call, const std::string &MangledName, const std::string &DeMangledName); /// Transform convert_ to /// __spirv_{CastOpName}_R{TargeTyName}{_sat}{_rt[p|n|z|e]} void visitCallConvert(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform async_work_group{_strided}_copy. /// async_work_group_copy(dst, src, n, event) /// => async_work_group_strided_copy(dst, src, n, 1, event) /// async_work_group_strided_copy(dst, src, n, stride, event) /// => __spirv_AsyncGroupCopy(ScopeWorkGroup, dst, src, n, stride, event) void visitCallAsyncWorkGroupCopy(CallInst *CI, const std::string &DemangledName); /// Transform OCL builtin function to SPIR-V builtin function. void transBuiltin(CallInst *CI, OCLBuiltinTransInfo &Info); /// Transform OCL work item builtin functions to SPIR-V builtin variables. void transWorkItemBuiltinsToVariables(); /// Transform atomic_work_item_fence/mem_fence to __spirv_MemoryBarrier. /// func(flag, order, scope) => /// __spirv_MemoryBarrier(map(scope), map(flag)|map(order)) void transMemoryBarrier(CallInst *CI, AtomicWorkItemFenceLiterals); /// Transform all to __spirv_Op(All|Any). Note that the types mismatch so // some extra code is emitted to convert between the two. void visitCallAllAny(spv::Op OC, CallInst *CI); /// Transform atomic_* to __spirv_Atomic*. /// atomic_x(ptr_arg, args, order, scope) => /// __spirv_AtomicY(ptr_arg, map(order), map(scope), args) void transAtomicBuiltin(CallInst *CI, OCLBuiltinTransInfo &Info); /// Transform atomic_work_item_fence to __spirv_MemoryBarrier. /// atomic_work_item_fence(flag, order, scope) => /// __spirv_MemoryBarrier(map(scope), map(flag)|map(order)) void visitCallAtomicWorkItemFence(CallInst *CI); /// Transform atomic_compare_exchange call. /// In atomic_compare_exchange, the expected value parameter is a pointer. /// However in SPIR-V it is a value. The transformation adds a load /// instruction, result of which is passed to atomic_compare_exchange as /// argument. /// The transformation adds a store instruction after the call, to update the /// value in expected with the value pointed to by object. Though, it is not /// necessary in case they are equal, this approach makes result code simpler. /// Also ICmp instruction is added, because the call must return result of /// comparison. /// \returns the call instruction of atomic_compare_exchange_strong. CallInst *visitCallAtomicCmpXchg(CallInst *CI, const std::string &DemangledName); /// Transform atomic_init. /// atomic_init(p, x) => store p, x void visitCallAtomicInit(CallInst *CI); /// Transform legacy OCL 1.x atomic builtins to SPIR-V builtins for extensions /// cl_khr_int64_base_atomics /// cl_khr_int64_extended_atomics /// Do nothing if the called function is not a legacy atomic builtin. void visitCallAtomicLegacy(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform OCL 2.0 C++11 atomic builtins to SPIR-V builtins. /// Do nothing if the called function is not a C++11 atomic builtin. void visitCallAtomicCpp11(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform OCL builtin function to SPIR-V builtin function. /// Assuming there is a simple name mapping without argument changes. /// Should be called at last. void visitCallBuiltinSimple(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform get_image_{width|height|depth|dim}. /// get_image_xxx(...) => /// dimension = __spirv_ImageQuerySizeLod_R{ReturnType}(...); /// return dimension.{x|y|z}; void visitCallGetImageSize(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform {work|sub}_group_x => /// __spirv_{OpName} /// /// Special handling of work_group_broadcast. /// work_group_broadcast(a, x, y, z) /// => /// __spirv_GroupBroadcast(a, vec3(x, y, z)) void visitCallGroupBuiltin(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform mem_fence to __spirv_MemoryBarrier. /// mem_fence(flag) => __spirv_MemoryBarrier(Workgroup, map(flag)) void visitCallMemFence(CallInst *CI); void visitCallNDRange(CallInst *CI, const std::string &DemangledName); /// Transform OCL pipe builtin function to SPIR-V pipe builtin function. void visitCallPipeBuiltin(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform read_image with sampler arguments. /// read_image(image, sampler, ...) => /// sampled_image = __spirv_SampledImage(image, sampler); /// return __spirv_ImageSampleExplicitLod_R{ReturnType}(sampled_image, ...); void visitCallReadImageWithSampler(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform read_image with msaa image arguments. /// Sample argument must be acoded as Image Operand. void visitCallReadImageMSAA(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform {read|write}_image without sampler arguments. void visitCallReadWriteImage(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform to_{global|local|private}. /// /// T* a = ...; /// addr T* b = to_addr(a); /// => /// i8* x = cast<i8*>(a); /// addr i8* y = __spirv_GenericCastToPtr_ToAddr(x); /// addr T* b = cast<addr T*>(y); void visitCallToAddr(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform return type of relatinal built-in functions like isnan, isfinite /// to boolean values. void visitCallRelational(CallInst *CI, const std::string &DemangledName); /// Transform vector load/store functions to SPIR-V extended builtin /// functions /// {vload|vstore{a}}{_half}{n}{_rte|_rtz|_rtp|_rtn} => /// __spirv_ocl_{ExtendedInstructionOpCodeName}__R{ReturnType} void visitCallVecLoadStore(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transforms get_mem_fence built-in to SPIR-V function and aligns result values with SPIR 1.2. /// get_mem_fence(ptr) => __spirv_GenericPtrMemSemantics /// GenericPtrMemSemantics valid values are 0x100, 0x200 and 0x300, where is /// SPIR 1.2 defines them as 0x1, 0x2 and 0x3, so this function adjusts /// GenericPtrMemSemantics results to SPIR 1.2 values. void visitCallGetFence(CallInst *CI, StringRef MangledName, const std::string& DemangledName); /// Transforms OpDot instructions with a scalar type to a fmul instruction void visitCallDot(CallInst *CI); /// Fixes for built-in functions with vector+scalar arguments that are /// translated to the SPIR-V instructions where all arguments must have the /// same type. void visitCallScalToVec(CallInst *CI, StringRef MangledName, const std::string &DemangledName); /// Transform get_image_channel_{order|data_type} built-in functions to /// __spirv_ocl_{ImageQueryOrder|ImageQueryFormat} void visitCallGetImageChannel(CallInst *CI, StringRef MangledName, const std::string &DemangledName, unsigned int Offset); void visitDbgInfoIntrinsic(DbgInfoIntrinsic &I){ I.dropAllReferences(); I.eraseFromParent(); } static char ID; private: Module *M; LLVMContext *Ctx; unsigned CLVer; /// OpenCL version as major*10+minor std::set<Value *> ValuesToDelete; ConstantInt *addInt32(int I) { return getInt32(M, I); } ConstantInt *addSizet(uint64_t I) { return getSizet(M, I); } /// Get vector width from OpenCL vload* function name. SPIRVWord getVecLoadWidth(const std::string& DemangledName) { SPIRVWord Width = 0; if (DemangledName == "vloada_half") Width = 1; else { unsigned Loc = 5; if (DemangledName.find("vload_half") == 0) Loc = 10; else if (DemangledName.find("vloada_half") == 0) Loc = 11; std::stringstream SS(DemangledName.substr(Loc)); SS >> Width; } return Width; } /// Transform OpenCL vload/vstore function name. void transVecLoadStoreName(std::string& DemangledName, const std::string &Stem, bool AlwaysN) { auto HalfStem = Stem + "_half"; auto HalfStemR = HalfStem + "_r"; if (!AlwaysN && DemangledName == HalfStem) return; if (!AlwaysN && DemangledName.find(HalfStemR) == 0) { DemangledName = HalfStemR; return; } if (DemangledName.find(HalfStem) == 0) { auto OldName = DemangledName; DemangledName = HalfStem + "n"; if (OldName.find("_r") != std::string::npos) DemangledName += "_r"; return; } if (DemangledName.find(Stem) == 0) { DemangledName = Stem + "n"; return; } } }; char OCL20ToSPIRV::ID = 0; bool OCL20ToSPIRV::runOnModule(Module& Module) { M = &Module; Ctx = &M->getContext(); auto Src = getSPIRVSource(&Module); if (std::get<0>(Src) != spv::SourceLanguageOpenCL_C) return false; CLVer = std::get<1>(Src); if (CLVer > kOCLVer::CL20) return false; DEBUG(dbgs() << "Enter OCL20ToSPIRV:\n"); transWorkItemBuiltinsToVariables(); visit(*M); for (auto &I:ValuesToDelete) if (auto Inst = dyn_cast<Instruction>(I)) Inst->eraseFromParent(); for (auto &I:ValuesToDelete) if (auto GV = dyn_cast<GlobalValue>(I)) GV->eraseFromParent(); DEBUG(dbgs() << "After OCL20ToSPIRV:\n" << *M); std::string Err; raw_string_ostream ErrorOS(Err); if (verifyModule(*M, &ErrorOS)){ DEBUG(errs() << "Fails to verify module: " << ErrorOS.str()); } return true; } // The order of handling OCL builtin functions is important. // Workgroup functions need to be handled before pipe functions since // there are functions fall into both categories. void OCL20ToSPIRV::visitCallInst(CallInst& CI) { DEBUG(dbgs() << "[visistCallInst] " << CI << '\n'); auto F = CI.getCalledFunction(); if (!F) return; auto MangledName = F->getName(); std::string DemangledName; if (!oclIsBuiltin(MangledName, &DemangledName)) return; DEBUG(dbgs() << "DemangledName: " << DemangledName << '\n'); if (DemangledName.find(kOCLBuiltinName::NDRangePrefix) == 0) { visitCallNDRange(&CI, DemangledName); return; } if (DemangledName == kOCLBuiltinName::All) { visitCallAllAny(OpAll, &CI); return; } if (DemangledName == kOCLBuiltinName::Any) { visitCallAllAny(OpAny, &CI); return; } if (DemangledName.find(kOCLBuiltinName::AsyncWorkGroupCopy) == 0 || DemangledName.find(kOCLBuiltinName::AsyncWorkGroupStridedCopy) == 0) { visitCallAsyncWorkGroupCopy(&CI, DemangledName); return; } if (DemangledName.find(kOCLBuiltinName::AtomicPrefix) == 0 || DemangledName.find(kOCLBuiltinName::AtomPrefix) == 0) { auto PCI = &CI; if (DemangledName == kOCLBuiltinName::AtomicInit) { visitCallAtomicInit(PCI); return; } if (DemangledName == kOCLBuiltinName::AtomicWorkItemFence) { visitCallAtomicWorkItemFence(PCI); return; } if (DemangledName == kOCLBuiltinName::AtomicCmpXchgWeak || DemangledName == kOCLBuiltinName::AtomicCmpXchgStrong || DemangledName == kOCLBuiltinName::AtomicCmpXchgWeakExplicit || DemangledName == kOCLBuiltinName::AtomicCmpXchgStrongExplicit) { assert(CLVer == kOCLVer::CL20 && "Wrong version of OpenCL"); PCI = visitCallAtomicCmpXchg(PCI, DemangledName); } visitCallAtomicLegacy(PCI, MangledName, DemangledName); visitCallAtomicCpp11(PCI, MangledName, DemangledName); return; } if (DemangledName.find(kOCLBuiltinName::ConvertPrefix) == 0) { visitCallConvert(&CI, MangledName, DemangledName); return; } if (DemangledName == kOCLBuiltinName::GetImageWidth || DemangledName == kOCLBuiltinName::GetImageHeight || DemangledName == kOCLBuiltinName::GetImageDepth || DemangledName == kOCLBuiltinName::GetImageDim || DemangledName == kOCLBuiltinName::GetImageArraySize) { visitCallGetImageSize(&CI, MangledName, DemangledName); return; } if ((DemangledName.find(kOCLBuiltinName::WorkGroupPrefix) == 0 && DemangledName != kOCLBuiltinName::WorkGroupBarrier) || DemangledName == kOCLBuiltinName::WaitGroupEvent || (DemangledName.find(kOCLBuiltinName::SubGroupPrefix) == 0 && DemangledName != kOCLBuiltinName::SubGroupBarrier)) { visitCallGroupBuiltin(&CI, MangledName, DemangledName); return; } if (DemangledName.find(kOCLBuiltinName::Pipe) != std::string::npos) { visitCallPipeBuiltin(&CI, MangledName, DemangledName); return; } if (DemangledName == kOCLBuiltinName::MemFence) { visitCallMemFence(&CI); return; } if (DemangledName.find(kOCLBuiltinName::ReadImage) == 0) { if (MangledName.find(kMangledName::Sampler) != StringRef::npos) { visitCallReadImageWithSampler(&CI, MangledName, DemangledName); return; } if (MangledName.find("msaa") != StringRef::npos) { visitCallReadImageMSAA(&CI, MangledName, DemangledName); return; } } if (DemangledName.find(kOCLBuiltinName::ReadImage) == 0 || DemangledName.find(kOCLBuiltinName::WriteImage) == 0) { visitCallReadWriteImage(&CI, MangledName, DemangledName); return; } if (DemangledName == kOCLBuiltinName::ToGlobal || DemangledName == kOCLBuiltinName::ToLocal || DemangledName == kOCLBuiltinName::ToPrivate) { visitCallToAddr(&CI, MangledName, DemangledName); return; } if (DemangledName.find(kOCLBuiltinName::VLoadPrefix) == 0 || DemangledName.find(kOCLBuiltinName::VStorePrefix) == 0) { visitCallVecLoadStore(&CI, MangledName, DemangledName); return; } if (DemangledName == kOCLBuiltinName::IsFinite || DemangledName == kOCLBuiltinName::IsInf || DemangledName == kOCLBuiltinName::IsNan || DemangledName == kOCLBuiltinName::IsNormal || DemangledName == kOCLBuiltinName::Signbit) { visitCallRelational(&CI, DemangledName); return; } if (DemangledName == kOCLBuiltinName::WorkGroupBarrier || DemangledName == kOCLBuiltinName::Barrier) { visitCallBarrier(&CI); return; } if (DemangledName == kOCLBuiltinName::GetFence) { visitCallGetFence(&CI, MangledName, DemangledName); return; } if (DemangledName == kOCLBuiltinName::Dot && !(CI.getOperand(0)->getType()->isVectorTy())) { visitCallDot(&CI); return; } if (DemangledName == kOCLBuiltinName::FMin || DemangledName == kOCLBuiltinName::FMax || DemangledName == kOCLBuiltinName::Min || DemangledName == kOCLBuiltinName::Max || DemangledName == kOCLBuiltinName::Step || DemangledName == kOCLBuiltinName::SmoothStep || DemangledName == kOCLBuiltinName::Clamp || DemangledName == kOCLBuiltinName::Mix) { visitCallScalToVec(&CI, MangledName, DemangledName); return; } if (DemangledName == kOCLBuiltinName::GetImageChannelDataType) { visitCallGetImageChannel(&CI, MangledName, DemangledName, OCLImageChannelDataTypeOffset); return; } if (DemangledName == kOCLBuiltinName::GetImageChannelOrder) { visitCallGetImageChannel(&CI, MangledName, DemangledName, OCLImageChannelOrderOffset); return; } visitCallBuiltinSimple(&CI, MangledName, DemangledName); } void OCL20ToSPIRV::visitCallNDRange(CallInst *CI, const std::string &DemangledName) { assert(DemangledName.find(kOCLBuiltinName::NDRangePrefix) == 0); std::string lenStr = DemangledName.substr(8, 1); auto Len = atoi(lenStr.c_str()); assert (Len >= 1 && Len <= 3); // SPIR-V ndrange structure requires 3 members in the following order: // global work offset // global work size // local work size // The arguments need to add missing members. AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstSPIRV(M, CI, [=](CallInst *, std::vector<Value *> &Args){ for (size_t I = 1, E = Args.size(); I != E; ++I) Args[I] = getScalarOrArray(Args[I], Len, CI); switch (Args.size()) { case 2: { // Has global work size. auto T = Args[1]->getType(); auto C = getScalarOrArrayConstantInt(CI, T, Len, 0); Args.push_back(C); Args.push_back(C); } break; case 3: { // Has global and local work size. auto T = Args[1]->getType(); Args.push_back(getScalarOrArrayConstantInt(CI, T, Len, 0)); } break; case 4: { // Move offset arg to the end auto OffsetPos = Args.begin() + 1; Value* OffsetVal = *OffsetPos; Args.erase(OffsetPos); Args.push_back(OffsetVal); } break; default: assert(0 && "Invalid number of arguments"); } // Translate ndrange_ND into differently named SPIR-V decorated functions because // they have array arugments of different dimension which mangled the same way. return getSPIRVFuncName(OpBuildNDRange, "_" + lenStr + "D"); }, &Attrs); } void OCL20ToSPIRV::visitCallAsyncWorkGroupCopy(CallInst* CI, const std::string &DemangledName) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstSPIRV(M, CI, [=](CallInst *, std::vector<Value *> &Args){ if (DemangledName == OCLUtil::kOCLBuiltinName::AsyncWorkGroupCopy) { Args.insert(Args.begin()+3, addSizet(1)); } Args.insert(Args.begin(), addInt32(ScopeWorkgroup)); return getSPIRVFuncName(OpGroupAsyncCopy); }, &Attrs); } CallInst * OCL20ToSPIRV::visitCallAtomicCmpXchg(CallInst* CI, const std::string& DemangledName) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); Value *Expected = nullptr; CallInst *NewCI = nullptr; mutateCallInstOCL(M, CI, [&](CallInst * CI, std::vector<Value *> &Args, Type *&RetTy){ Expected = Args[1]; // temporary save second argument. Args[1] = new LoadInst(Args[1], "exp", false, CI); RetTy = Args[2]->getType(); assert(Args[0]->getType()->getPointerElementType()->isIntegerTy() && Args[1]->getType()->isIntegerTy() && Args[2]->getType()->isIntegerTy() && "In SPIR-V 1.0 arguments of OpAtomicCompareExchange must be " "an integer type scalars"); return kOCLBuiltinName::AtomicCmpXchgStrong; }, [&](CallInst *NCI)->Instruction * { NewCI = NCI; Instruction* Store = new StoreInst(NCI, Expected, NCI->getNextNode()); return new ICmpInst(Store->getNextNode(), CmpInst::ICMP_EQ, NCI, NCI->getArgOperand(1)); }, &Attrs); return NewCI; } void OCL20ToSPIRV::visitCallAtomicInit(CallInst* CI) { auto ST = new StoreInst(CI->getArgOperand(1), CI->getArgOperand(0), CI); ST->takeName(CI); CI->dropAllReferences(); CI->eraseFromParent(); } void OCL20ToSPIRV::visitCallAllAny(spv::Op OC, CallInst* CI) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); auto Args = getArguments(CI); assert(Args.size() == 1); auto *ArgTy = Args[0]->getType(); auto Zero = Constant::getNullValue(Args[0]->getType()); auto *Cmp = CmpInst::Create(CmpInst::ICmp, CmpInst::ICMP_SLT, Args[0], Zero, "cast", CI); if (!isa<VectorType>(ArgTy)) { auto *Cast = CastInst::CreateZExtOrBitCast(Cmp, Type::getInt32Ty(*Ctx), "", Cmp->getNextNode()); CI->replaceAllUsesWith(Cast); CI->eraseFromParent(); } else { mutateCallInstSPIRV( M, CI, [&](CallInst *, std::vector<Value *> &Args, Type *&Ret) { Args[0] = Cmp; Ret = Type::getInt1Ty(*Ctx); return getSPIRVFuncName(OC); }, [&](CallInst *CI) -> Instruction * { return CastInst::CreateZExtOrBitCast(CI, Type::getInt32Ty(*Ctx), "", CI->getNextNode()); }, &Attrs); } } void OCL20ToSPIRV::visitCallAtomicWorkItemFence(CallInst* CI) { transMemoryBarrier(CI, getAtomicWorkItemFenceLiterals(CI)); } void OCL20ToSPIRV::visitCallMemFence(CallInst* CI) { transMemoryBarrier(CI, std::make_tuple( cast<ConstantInt>(CI->getArgOperand(0))->getZExtValue(), OCLMO_relaxed, OCLMS_work_group)); } void OCL20ToSPIRV::transMemoryBarrier(CallInst* CI, AtomicWorkItemFenceLiterals Lit) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstSPIRV(M, CI, [=](CallInst *, std::vector<Value *> &Args){ Args.resize(2); Args[0] = addInt32(map<Scope>(std::get<2>(Lit))); Args[1] = addInt32(mapOCLMemSemanticToSPIRV(std::get<0>(Lit), std::get<1>(Lit))); return getSPIRVFuncName(OpMemoryBarrier); }, &Attrs); } void OCL20ToSPIRV::visitCallAtomicLegacy(CallInst* CI, StringRef MangledName, const std::string& DemangledName) { StringRef Stem = DemangledName; if (Stem.startswith("atom_")) Stem = Stem.drop_front(strlen("atom_")); else if (Stem.startswith("atomic_")) Stem = Stem.drop_front(strlen("atomic_")); else return; std::string Sign; std::string Postfix; std::string Prefix; if (Stem == "add" || Stem == "sub" || Stem == "and" || Stem == "or" || Stem == "xor" || Stem == "min" || Stem == "max") { if ((Stem == "min" || Stem == "max") && isMangledTypeUnsigned(MangledName.back())) Sign = 'u'; Prefix = "fetch_"; Postfix = "_explicit"; } else if (Stem == "xchg") { Stem = "exchange"; Postfix = "_explicit"; } else if (Stem == "cmpxchg") { Stem = "compare_exchange_strong"; Postfix = "_explicit"; } else if (Stem == "inc" || Stem == "dec") { // do nothing } else return; OCLBuiltinTransInfo Info; Info.UniqName = "atomic_" + Prefix + Sign + Stem.str() + Postfix; std::vector<int> PostOps; PostOps.push_back(OCLLegacyAtomicMemOrder); if (Stem.startswith("compare_exchange")) PostOps.push_back(OCLLegacyAtomicMemOrder); PostOps.push_back(OCLLegacyAtomicMemScope); Info.PostProc = [=](std::vector<Value *> &Ops){ for (auto &I:PostOps){ Ops.push_back(addInt32(I)); } }; transAtomicBuiltin(CI, Info); } void OCL20ToSPIRV::visitCallAtomicCpp11(CallInst* CI, StringRef MangledName, const std::string& DemangledName) { StringRef Stem = DemangledName; if (Stem.startswith("atomic_")) Stem = Stem.drop_front(strlen("atomic_")); else return; std::string NewStem = Stem; std::vector<int> PostOps; if (Stem.startswith("store") || Stem.startswith("load") || Stem.startswith("exchange") || Stem.startswith("compare_exchange") || Stem.startswith("fetch") || Stem.startswith("flag")) { if ((Stem.startswith("fetch_min") || Stem.startswith("fetch_max")) && containsUnsignedAtomicType(MangledName)) NewStem.insert(NewStem.begin() + strlen("fetch_"), 'u'); if (!Stem.endswith("_explicit")) { NewStem = NewStem + "_explicit"; PostOps.push_back(OCLMO_seq_cst); if (Stem.startswith("compare_exchange")) PostOps.push_back(OCLMO_seq_cst); PostOps.push_back(OCLMS_device); } else { auto MaxOps = getOCLCpp11AtomicMaxNumOps( Stem.drop_back(strlen("_explicit"))); if (CI->getNumArgOperands() < MaxOps) PostOps.push_back(OCLMS_device); } } else if (Stem == "work_item_fence") { // do nothing } else return; OCLBuiltinTransInfo Info; Info.UniqName = std::string("atomic_") + NewStem; Info.PostProc = [=](std::vector<Value *> &Ops){ for (auto &I:PostOps){ Ops.push_back(addInt32(I)); } }; transAtomicBuiltin(CI, Info); } void OCL20ToSPIRV::transAtomicBuiltin(CallInst* CI, OCLBuiltinTransInfo& Info) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstSPIRV(M, CI, [=](CallInst * CI, std::vector<Value *> &Args){ Info.PostProc(Args); // Order of args in OCL20: // object, 0-2 other args, 1-2 order, scope const size_t NumOrder = getAtomicBuiltinNumMemoryOrderArgs(Info.UniqName); const size_t ArgsCount = Args.size(); const size_t ScopeIdx = ArgsCount - 1; const size_t OrderIdx = ScopeIdx - NumOrder; Args[ScopeIdx] = mapUInt(M, cast<ConstantInt>(Args[ScopeIdx]), [](unsigned I){ return map<Scope>(static_cast<OCLScopeKind>(I)); }); for (size_t I = 0; I < NumOrder; ++I) Args[OrderIdx + I] = mapUInt(M, cast<ConstantInt>(Args[OrderIdx + I]), [](unsigned Ord) { return mapOCLMemSemanticToSPIRV(0, static_cast<OCLMemOrderKind>(Ord)); }); // Order of args in SPIR-V: // object, scope, 1-2 order, 0-2 other args std::swap(Args[1], Args[ScopeIdx]); if(OrderIdx > 2) { // For atomic_compare_exchange the swap above puts Comparator/Expected // argument just where it should be, so don't move the last argument then. int offset = Info.UniqName.find("atomic_compare_exchange") == 0 ? 1 : 0; std::rotate(Args.begin() + 2, Args.begin() + OrderIdx, Args.end() - offset); } return getSPIRVFuncName(OCLSPIRVBuiltinMap::map(Info.UniqName)); }, &Attrs); } void OCL20ToSPIRV::visitCallBarrier(CallInst* CI) { auto Lit = getBarrierLiterals(CI); AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstSPIRV(M, CI, [=](CallInst *, std::vector<Value *> &Args){ Args.resize(3); Args[0] = addInt32(map<Scope>(std::get<2>(Lit))); Args[1] = addInt32(map<Scope>(std::get<1>(Lit))); Args[2] = addInt32(mapOCLMemFenceFlagToSPIRV(std::get<0>(Lit))); return getSPIRVFuncName(OpControlBarrier); }, &Attrs); } void OCL20ToSPIRV::visitCallConvert(CallInst* CI, StringRef MangledName, const std::string& DemangledName) { if (eraseUselessConvert(CI, MangledName, DemangledName)) return; Op OC = OpNop; auto TargetTy = CI->getType(); auto SrcTy = CI->getArgOperand(0)->getType(); if (isa<VectorType>(TargetTy)) TargetTy = TargetTy->getVectorElementType(); if (isa<VectorType>(SrcTy)) SrcTy = SrcTy->getVectorElementType(); auto IsTargetInt = isa<IntegerType>(TargetTy); std::string TargetTyName = DemangledName.substr( strlen(kOCLBuiltinName::ConvertPrefix)); auto FirstUnderscoreLoc = TargetTyName.find('_'); if (FirstUnderscoreLoc != std::string::npos) TargetTyName = TargetTyName.substr(0, FirstUnderscoreLoc); TargetTyName = std::string("_R") + TargetTyName; std::string Sat = DemangledName.find("_sat") != std::string::npos ? "_sat" : ""; auto TargetSigned = DemangledName[8] != 'u'; if (isa<IntegerType>(SrcTy)) { bool Signed = isLastFuncParamSigned(MangledName); if (IsTargetInt) { if (!Sat.empty() && TargetSigned != Signed) { OC = Signed ? OpSatConvertSToU : OpSatConvertUToS; Sat = ""; } else OC = Signed ? OpSConvert : OpUConvert; } else OC = Signed ? OpConvertSToF : OpConvertUToF; } else { if (IsTargetInt) { OC = TargetSigned ? OpConvertFToS : OpConvertFToU; } else OC = OpFConvert; } auto Loc = DemangledName.find("_rt"); std::string Rounding; if (Loc != std::string::npos && !(isa<IntegerType>(SrcTy) && IsTargetInt)) { Rounding = DemangledName.substr(Loc, 4); } AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstSPIRV(M, CI, [=](CallInst *, std::vector<Value *> &Args){ return getSPIRVFuncName(OC, TargetTyName + Sat + Rounding); }, &Attrs); } void OCL20ToSPIRV::visitCallGroupBuiltin(CallInst* CI, StringRef MangledName, const std::string& OrigDemangledName) { auto F = CI->getCalledFunction(); std::vector<int> PreOps; std::string DemangledName = OrigDemangledName; if (DemangledName == kOCLBuiltinName::WorkGroupBarrier) return; if (DemangledName == kOCLBuiltinName::WaitGroupEvent) { PreOps.push_back(ScopeWorkgroup); } else if (DemangledName.find(kOCLBuiltinName::WorkGroupPrefix) == 0) { DemangledName.erase(0, strlen(kOCLBuiltinName::WorkPrefix)); PreOps.push_back(ScopeWorkgroup); } else if (DemangledName.find(kOCLBuiltinName::SubGroupPrefix) == 0) { DemangledName.erase(0, strlen(kOCLBuiltinName::SubPrefix)); PreOps.push_back(ScopeSubgroup); } else return; if (DemangledName != kOCLBuiltinName::WaitGroupEvent) { StringRef GroupOp = DemangledName; GroupOp = GroupOp.drop_front(strlen(kSPIRVName::GroupPrefix)); SPIRSPIRVGroupOperationMap::foreach_conditional([&](const std::string &S, SPIRVGroupOperationKind G){ if (!GroupOp.startswith(S)) return true; // continue PreOps.push_back(G); StringRef Op = GroupOp.drop_front(S.size() + 1); assert(!Op.empty() && "Invalid OpenCL group builtin function"); char OpTyC = 0; auto NeedSign = Op == "max" || Op == "min"; auto OpTy = F->getReturnType(); if (OpTy->isFloatingPointTy()) OpTyC = 'f'; else if (OpTy->isIntegerTy()) { if (!NeedSign) OpTyC = 'i'; else { if (isLastFuncParamSigned(F->getName())) OpTyC = 's'; else OpTyC = 'u'; } } else llvm_unreachable("Invalid OpenCL group builtin argument type"); DemangledName = std::string(kSPIRVName::GroupPrefix) + OpTyC + Op.str(); return false; // break out of loop }); } bool IsGroupAllAny = (DemangledName.find("_all") != std::string::npos || DemangledName.find("_any") != std::string::npos); auto Consts = getInt32(M, PreOps); OCLBuiltinTransInfo Info; if (IsGroupAllAny) Info.RetTy = Type::getInt1Ty(*Ctx); Info.UniqName = DemangledName; Info.PostProc = [=](std::vector<Value *> &Ops) { if (IsGroupAllAny) { IRBuilder<> IRB(CI); Ops[0] = IRB.CreateICmpNE(Ops[0], ConstantInt::get(Type::getInt32Ty(*Ctx), 0)); } size_t E = Ops.size(); if (DemangledName == "group_broadcast" && E > 2) { assert(E == 3 || E == 4); makeVector(CI, Ops, std::make_pair(Ops.begin() + 1, Ops.end())); } Ops.insert(Ops.begin(), Consts.begin(), Consts.end()); }; transBuiltin(CI, Info); } void OCL20ToSPIRV::transBuiltin(CallInst* CI, OCLBuiltinTransInfo& Info) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); Op OC = OpNop; unsigned ExtOp = ~0U; if (StringRef(Info.UniqName).startswith(kSPIRVName::Prefix)) return; if (OCLSPIRVBuiltinMap::find(Info.UniqName, &OC)) Info.UniqName = getSPIRVFuncName(OC); else if ((ExtOp = getExtOp(Info.MangledName, Info.UniqName)) != ~0U) Info.UniqName = getSPIRVExtFuncName(SPIRVEIS_OpenCL, ExtOp); else return; if (!Info.RetTy) mutateCallInstSPIRV(M, CI, [=](CallInst *, std::vector<Value *> &Args) { Info.PostProc(Args); return Info.UniqName + Info.Postfix; }, &Attrs); else mutateCallInstSPIRV( M, CI, [=](CallInst *, std::vector<Value *> &Args, Type *&RetTy) { Info.PostProc(Args); RetTy = Info.RetTy; return Info.UniqName + Info.Postfix; }, [=](CallInst *NewCI) -> Instruction * { if (NewCI->getType()->isIntegerTy() && CI->getType()->isIntegerTy()) return CastInst::CreateIntegerCast(NewCI, CI->getType(), Info.isRetSigned, "", CI); else return CastInst::CreatePointerBitCastOrAddrSpaceCast( NewCI, CI->getType(), "", CI); }, &Attrs); } void OCL20ToSPIRV::visitCallPipeBuiltin(CallInst* CI, StringRef MangledName, const std::string& DemangledName) { std::string NewName = DemangledName; // Transform OpenCL read_pipe/write_pipe builtin function names // with reserve_id argument to reserved_read_pipe/reserved_write_pipe. if ((DemangledName.find(kOCLBuiltinName::ReadPipe) == 0 || DemangledName.find(kOCLBuiltinName::WritePipe) == 0) && CI->getNumArgOperands() > 4) NewName = std::string(kSPIRVName::ReservedPrefix) + DemangledName; OCLBuiltinTransInfo Info; Info.UniqName = NewName; transBuiltin(CI, Info); } void OCL20ToSPIRV::visitCallReadImageMSAA(CallInst *CI, StringRef MangledName, const std::string &DemangledName) { assert(MangledName.find("msaa") != StringRef::npos); AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstSPIRV( M, CI, [=](CallInst *, std::vector<Value *> &Args) { Args.insert(Args.begin() + 2, getInt32(M, ImageOperandsSampleMask)); return getSPIRVFuncName(OpImageRead, std::string(kSPIRVPostfix::ExtDivider) + getPostfixForReturnType(CI)); }, &Attrs); } void OCL20ToSPIRV::visitCallReadImageWithSampler( CallInst *CI, StringRef MangledName, const std::string &DemangledName) { assert (MangledName.find(kMangledName::Sampler) != StringRef::npos); AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); bool isRetScalar = !CI->getType()->isVectorTy(); mutateCallInstSPIRV( M, CI, [=](CallInst *, std::vector<Value *> &Args, Type *&Ret) { auto ImageTy = getAnalysis<OCLTypeToSPIRV>().getAdaptedType(Args[0]); if (isOCLImageType(ImageTy)) ImageTy = getSPIRVImageTypeFromOCL(M, ImageTy); auto SampledImgTy = getSPIRVTypeByChangeBaseTypeName( M, ImageTy, kSPIRVTypeName::Image, kSPIRVTypeName::SampledImg); Value *SampledImgArgs[] = {Args[0], Args[1]}; auto SampledImg = addCallInstSPIRV( M, getSPIRVFuncName(OpSampledImage), SampledImgTy, SampledImgArgs, nullptr, CI, kSPIRVName::TempSampledImage); Args[0] = SampledImg; Args.erase(Args.begin() + 1, Args.begin() + 2); switch (Args.size()) { case 2: // no lod Args.push_back(getInt32(M, ImageOperandsMask::ImageOperandsLodMask)); Args.push_back(getFloat32(M, 0.f)); break; case 3: // explicit lod Args.insert(Args.begin() + 2, getInt32(M, ImageOperandsMask::ImageOperandsLodMask)); break; case 4: // gradient Args.insert(Args.begin() + 2, getInt32(M, ImageOperandsMask::ImageOperandsGradMask)); break; default: assert(0 && "read_image* with unhandled number of args!"); } // SPIR-V intruction always returns 4-element vector if (isRetScalar) Ret = VectorType::get(Ret, 4); return getSPIRVFuncName(OpImageSampleExplicitLod, std::string(kSPIRVPostfix::ExtDivider) + getPostfixForReturnType(Ret)); }, [&](CallInst *CI) -> Instruction * { if (isRetScalar) return ExtractElementInst::Create(CI, getSizet(M, 0), "", CI->getNextNode()); return CI; }, &Attrs); } void OCL20ToSPIRV::visitCallGetImageSize(CallInst* CI, StringRef MangledName, const std::string& DemangledName) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); StringRef TyName; SmallVector<StringRef, 4> SubStrs; auto IsImg = isOCLImageType(CI->getArgOperand(0)->getType(), &TyName); (void)IsImg; // prevent warning about unused variable in NDEBUG build assert(IsImg); std::string ImageTyName = TyName.str(); if (hasAccessQualifiedName(TyName)) ImageTyName.erase(ImageTyName.size() - 5, 3); auto Desc = map<SPIRVTypeImageDescriptor>(ImageTyName); unsigned Dim = getImageDimension(Desc.Dim) + Desc.Arrayed; assert(Dim > 0 && "Invalid image dimension."); mutateCallInstSPIRV(M, CI, [&](CallInst *, std::vector<Value *> &Args, Type *&Ret){ assert(Args.size() == 1); Ret = CI->getType()->isIntegerTy(64) ? Type::getInt64Ty(*Ctx) : Type::getInt32Ty(*Ctx); if (Dim > 1) Ret = VectorType::get(Ret, Dim); if (Desc.Dim == DimBuffer) return getSPIRVFuncName(OpImageQuerySize, CI->getType()); else { Args.push_back(getInt32(M, 0)); return getSPIRVFuncName(OpImageQuerySizeLod, CI->getType()); } }, [&](CallInst *NCI)->Instruction * { if (Dim == 1) return NCI; if (DemangledName == kOCLBuiltinName::GetImageDim) { if (Desc.Dim == Dim3D) { auto ZeroVec = ConstantVector::getSplat(3, Constant::getNullValue(NCI->getType()->getVectorElementType())); Constant *Index[] = {getInt32(M, 0), getInt32(M, 1), getInt32(M, 2), getInt32(M, 3)}; return new ShuffleVectorInst(NCI, ZeroVec, ConstantVector::get(Index), "", CI); } else if (Desc.Dim == Dim2D && Desc.Arrayed) { Constant *Index[] = {getInt32(M, 0), getInt32(M, 1)}; Constant *mask = ConstantVector::get(Index); return new ShuffleVectorInst(NCI, UndefValue::get(NCI->getType()), mask, NCI->getName(), CI); } return NCI; } unsigned I = StringSwitch<unsigned>(DemangledName) .Case(kOCLBuiltinName::GetImageWidth, 0) .Case(kOCLBuiltinName::GetImageHeight, 1) .Case(kOCLBuiltinName::GetImageDepth, 2) .Case(kOCLBuiltinName::GetImageArraySize, Dim - 1); return ExtractElementInst::Create(NCI, getUInt32(M, I), "", NCI->getNextNode()); }, &Attrs); } /// Remove trivial conversion functions bool OCL20ToSPIRV::eraseUselessConvert(CallInst *CI, const std::string &MangledName, const std::string &DemangledName) { auto TargetTy = CI->getType(); auto SrcTy = CI->getArgOperand(0)->getType(); if (isa<VectorType>(TargetTy)) TargetTy = TargetTy->getVectorElementType(); if (isa<VectorType>(SrcTy)) SrcTy = SrcTy->getVectorElementType(); if (TargetTy == SrcTy) { if (isa<IntegerType>(TargetTy) && DemangledName.find("_sat") != std::string::npos && isLastFuncParamSigned(MangledName) != (DemangledName[8] != 'u')) return false; CI->getArgOperand(0)->takeName(CI); SPIRVDBG(dbgs() << "[regularizeOCLConvert] " << *CI << " <- " << *CI->getArgOperand(0) << '\n'); CI->replaceAllUsesWith(CI->getArgOperand(0)); ValuesToDelete.insert(CI); ValuesToDelete.insert(CI->getCalledFunction()); return true; } return false; } void OCL20ToSPIRV::visitCallBuiltinSimple(CallInst* CI, StringRef MangledName, const std::string& DemangledName) { OCLBuiltinTransInfo Info; Info.MangledName = MangledName.str(); Info.UniqName = DemangledName; transBuiltin(CI, Info); } /// Translates OCL work-item builtin functions to SPIRV builtin variables. /// Function like get_global_id(i) -> x = load GlobalInvocationId; extract x, i /// Function like get_work_dim() -> load WorkDim void OCL20ToSPIRV::transWorkItemBuiltinsToVariables() { DEBUG(dbgs() << "Enter transWorkItemBuiltinsToVariables\n"); std::vector<Function *> WorkList; for (auto I = M->begin(), E = M->end(); I != E; ++I) { std::string DemangledName; if (!oclIsBuiltin(I->getName(), &DemangledName)) continue; DEBUG(dbgs() << "Function demangled name: " << DemangledName << '\n'); std::string BuiltinVarName; SPIRVBuiltinVariableKind BVKind; if (!SPIRSPIRVBuiltinVariableMap::find(DemangledName, &BVKind)) continue; BuiltinVarName = std::string(kSPIRVName::Prefix) + SPIRVBuiltInNameMap::map(BVKind); DEBUG(dbgs() << "builtin variable name: " << BuiltinVarName << '\n'); bool IsVec = I->getFunctionType()->getNumParams() > 0; Type *GVType = IsVec ? VectorType::get(I->getReturnType(),3) : I->getReturnType(); auto BV = new GlobalVariable(*M, GVType, true, GlobalValue::ExternalLinkage, nullptr, BuiltinVarName, 0, GlobalVariable::NotThreadLocal, SPIRAS_Constant); std::vector<Instruction *> InstList; for (auto UI = I->user_begin(), UE = I->user_end(); UI != UE; ++UI) { auto CI = dyn_cast<CallInst>(*UI); assert(CI && "invalid instruction"); Value * NewValue = new LoadInst(BV, "", CI); DEBUG(dbgs() << "Transform: " << *CI << " => " << *NewValue << '\n'); if (IsVec) { NewValue = ExtractElementInst::Create(NewValue, CI->getArgOperand(0), "", CI); DEBUG(dbgs() << *NewValue << '\n'); } NewValue->takeName(CI); CI->replaceAllUsesWith(NewValue); InstList.push_back(CI); } for (auto &Inst:InstList) { Inst->dropAllReferences(); Inst->removeFromParent(); } WorkList.push_back(static_cast<Function*>(I)); } for (auto &I:WorkList) { I->dropAllReferences(); I->removeFromParent(); } } void OCL20ToSPIRV::visitCallReadWriteImage(CallInst* CI, StringRef MangledName, const std::string& DemangledName) { OCLBuiltinTransInfo Info; if (DemangledName.find(kOCLBuiltinName::ReadImage) == 0) Info.UniqName = kOCLBuiltinName::ReadImage; if (DemangledName.find(kOCLBuiltinName::WriteImage) == 0) { Info.UniqName = kOCLBuiltinName::WriteImage; Info.PostProc = [&](std::vector<Value*> &Args) { if (Args.size() == 4) // write with lod { auto Lod = Args[2]; Args.erase(Args.begin() + 2); Args.push_back(getInt32(M, ImageOperandsMask::ImageOperandsLodMask)); Args.push_back(Lod); } }; } transBuiltin(CI, Info); } void OCL20ToSPIRV::visitCallToAddr(CallInst* CI, StringRef MangledName, const std::string &DemangledName) { auto AddrSpace = static_cast<SPIRAddressSpace>( CI->getType()->getPointerAddressSpace()); OCLBuiltinTransInfo Info; Info.UniqName = DemangledName; Info.Postfix = std::string(kSPIRVPostfix::Divider) + "To" + SPIRAddrSpaceCapitalizedNameMap::map(AddrSpace); auto StorageClass = addInt32(SPIRSPIRVAddrSpaceMap::map(AddrSpace)); Info.RetTy = getInt8PtrTy(cast<PointerType>(CI->getType())); Info.PostProc = [=](std::vector<Value *> &Ops){ auto P = Ops.back(); Ops.pop_back(); Ops.push_back(castToInt8Ptr(P, CI)); Ops.push_back(StorageClass); }; transBuiltin(CI, Info); } void OCL20ToSPIRV::visitCallRelational(CallInst *CI, const std::string &DemangledName) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); Op OC = OpNop; OCLSPIRVBuiltinMap::find(DemangledName, &OC); std::string SPIRVName = getSPIRVFuncName(OC); mutateCallInstSPIRV( M, CI, [=](CallInst *, std::vector<Value *> &Args, Type *&Ret) { Ret = Type::getInt1Ty(*Ctx); if (CI->getOperand(0)->getType()->isVectorTy()) Ret = VectorType::get( Type::getInt1Ty(*Ctx), CI->getOperand(0)->getType()->getVectorNumElements()); return SPIRVName; }, [=](CallInst *NewCI) -> Instruction * { Value *False = nullptr, *True = nullptr; if (NewCI->getType()->isVectorTy()) { Type *IntTy = Type::getInt32Ty(*Ctx); if (cast<VectorType>(NewCI->getOperand(0)->getType()) ->getElementType() ->isDoubleTy()) IntTy = Type::getInt64Ty(*Ctx); if (cast<VectorType>(NewCI->getOperand(0)->getType()) ->getElementType() ->isHalfTy()) IntTy = Type::getInt16Ty(*Ctx); Type *VTy = VectorType::get(IntTy, NewCI->getType()->getVectorNumElements()); False = Constant::getNullValue(VTy); True = Constant::getAllOnesValue(VTy); } else { False = getInt32(M, 0); True = getInt32(M, 1); } return SelectInst::Create(NewCI, True, False, "", NewCI->getNextNode()); }, &Attrs); } void OCL20ToSPIRV::visitCallVecLoadStore(CallInst* CI, StringRef MangledName, const std::string& OrigDemangledName) { std::vector<int> PreOps; std::string DemangledName = OrigDemangledName; if (DemangledName.find(kOCLBuiltinName::VLoadPrefix) == 0 && DemangledName != kOCLBuiltinName::VLoadHalf) { SPIRVWord Width = getVecLoadWidth(DemangledName); SPIRVDBG(spvdbgs() << "[visitCallVecLoadStore] DemangledName: " << DemangledName << " Width: " << Width << '\n'); PreOps.push_back(Width); } else if (DemangledName.find(kOCLBuiltinName::RoundingPrefix) != std::string::npos) { auto R = SPIRSPIRVFPRoundingModeMap::map(DemangledName.substr( DemangledName.find(kOCLBuiltinName::RoundingPrefix) + 1, 3)); PreOps.push_back(R); } if (DemangledName.find(kOCLBuiltinName::VLoadAPrefix) == 0) transVecLoadStoreName(DemangledName, kOCLBuiltinName::VLoadAPrefix, true); else transVecLoadStoreName(DemangledName, kOCLBuiltinName::VLoadPrefix, false); if (DemangledName.find(kOCLBuiltinName::VStoreAPrefix) == 0) transVecLoadStoreName(DemangledName, kOCLBuiltinName::VStoreAPrefix, true); else transVecLoadStoreName(DemangledName, kOCLBuiltinName::VStorePrefix, false); auto Consts = getInt32(M, PreOps); OCLBuiltinTransInfo Info; Info.MangledName = MangledName; Info.UniqName = DemangledName; if (DemangledName.find(kOCLBuiltinName::VLoadPrefix) == 0) Info.Postfix = std::string(kSPIRVPostfix::ExtDivider) + getPostfixForReturnType(CI); Info.PostProc = [=](std::vector<Value *> &Ops){ Ops.insert(Ops.end(), Consts.begin(), Consts.end()); }; transBuiltin(CI, Info); } void OCL20ToSPIRV::visitCallGetFence(CallInst *CI, StringRef MangledName, const std::string &DemangledName) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); Op OC = OpNop; OCLSPIRVBuiltinMap::find(DemangledName, &OC); std::string SPIRVName = getSPIRVFuncName(OC); mutateCallInstSPIRV(M, CI, [=](CallInst *, std::vector<Value *> &Args, Type *&Ret) { return SPIRVName; }, [=](CallInst *NewCI) -> Instruction * { return BinaryOperator::CreateLShr(NewCI, getInt32(M, 8), "", CI); }, &Attrs); } void OCL20ToSPIRV::visitCallDot(CallInst *CI) { IRBuilder<> Builder(CI); Value *FMulVal = Builder.CreateFMul(CI->getOperand(0), CI->getOperand(1)); CI->replaceAllUsesWith(FMulVal); CI->dropAllReferences(); CI->removeFromParent(); } void OCL20ToSPIRV::visitCallScalToVec(CallInst *CI, StringRef MangledName, const std::string &DemangledName) { // Check if all arguments have the same type - it's simple case. auto Uniform = true; auto IsArg0Vector = isa<VectorType>(CI->getOperand(0)->getType()); for (unsigned I = 1, E = CI->getNumArgOperands(); Uniform && (I != E); ++I) { Uniform = isa<VectorType>(CI->getOperand(I)->getType()) == IsArg0Vector; } if (Uniform) { visitCallBuiltinSimple(CI, MangledName, DemangledName); return; } std::vector<unsigned int> VecPos; std::vector<unsigned int> ScalarPos; if (DemangledName == kOCLBuiltinName::FMin || DemangledName == kOCLBuiltinName::FMax || DemangledName == kOCLBuiltinName::Min || DemangledName == kOCLBuiltinName::Max) { VecPos.push_back(0); ScalarPos.push_back(1); } else if (DemangledName == kOCLBuiltinName::Clamp) { VecPos.push_back(0); ScalarPos.push_back(1); ScalarPos.push_back(2); } else if (DemangledName == kOCLBuiltinName::Mix) { VecPos.push_back(0); VecPos.push_back(1); ScalarPos.push_back(2); } else if (DemangledName == kOCLBuiltinName::Step) { VecPos.push_back(1); ScalarPos.push_back(0); } else if (DemangledName == kOCLBuiltinName::SmoothStep) { VecPos.push_back(2); ScalarPos.push_back(0); ScalarPos.push_back(1); } AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstSPIRV( M, CI, [=](CallInst *, std::vector<Value *> &Args) { Args.resize(VecPos.size() + ScalarPos.size()); for (auto I : VecPos) { Args[I] = CI->getOperand(I); } auto VecArgWidth = CI->getOperand(VecPos[0])->getType()->getVectorNumElements(); for (auto I : ScalarPos) { Instruction *Inst = InsertElementInst::Create( UndefValue::get(CI->getOperand(VecPos[0])->getType()), CI->getOperand(I), getInt32(M, 0), "", CI); Value *NewVec = new ShuffleVectorInst( Inst, UndefValue::get(CI->getOperand(VecPos[0])->getType()), ConstantVector::getSplat(VecArgWidth, getInt32(M, 0)), "", CI); Args[I] = NewVec; } return getSPIRVExtFuncName(SPIRVEIS_OpenCL, getExtOp(MangledName, DemangledName)); }, &Attrs); } void OCL20ToSPIRV::visitCallGetImageChannel(CallInst *CI, StringRef MangledName, const std::string &DemangledName, unsigned int Offset) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); Op OC = OpNop; OCLSPIRVBuiltinMap::find(DemangledName, &OC); std::string SPIRVName = getSPIRVFuncName(OC); mutateCallInstSPIRV(M, CI, [=](CallInst *, std::vector<Value *> &Args, Type *&Ret) { return SPIRVName; }, [=](CallInst *NewCI) -> Instruction * { return BinaryOperator::CreateAdd( NewCI, getInt32(M, Offset), "", CI); }, &Attrs); } } INITIALIZE_PASS_BEGIN(OCL20ToSPIRV, "cl20tospv", "Transform OCL 2.0 to SPIR-V", false, false) INITIALIZE_PASS_DEPENDENCY(OCLTypeToSPIRV) INITIALIZE_PASS_END(OCL20ToSPIRV, "cl20tospv", "Transform OCL 2.0 to SPIR-V", false, false) ModulePass *llvm::createOCL20ToSPIRV() { return new OCL20ToSPIRV(); }