//===- SPIRVToOCL20.cpp - Transform SPIR-V builtins to OCL20 builtins-------===// // // 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 transform SPIR-V builtins to OCL 2.0 builtins. // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "spvtocl20" #include "SPIRVInternal.h" #include "OCLUtil.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/IR/InstVisitor.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Verifier.h" #include "llvm/Pass.h" #include "llvm/PassSupport.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" #include using namespace llvm; using namespace SPIRV; using namespace OCLUtil; namespace SPIRV { static cl::opt MangledAtomicTypeNamePrefix("spirv-atomic-prefix", cl::desc("Mangled atomic type name prefix"), cl::init("U7_Atomic")); class SPIRVToOCL20: public ModulePass, public InstVisitor { public: SPIRVToOCL20():ModulePass(ID), M(nullptr), Ctx(nullptr) { initializeSPIRVToOCL20Pass(*PassRegistry::getPassRegistry()); } virtual bool runOnModule(Module &M); void visitCallInst(CallInst &CI); // SPIR-V reader should translate vector casts into OCL built-ins because // such conversions are not defined neither by OpenCL C/C++ nor // by SPIR 1.2/2.0 standards. So, it is safer to convert such casts into // appropriate calls to conversion built-ins defined by the standards. void visitCastInst(CastInst &CI); /// Transform __spirv_ImageQuerySize[Lod] into vector of the same lenght /// containing {[get_image_width | get_image_dim], get_image_array_size} /// for all images except image1d_t which is always converted into /// get_image_width returning scalar result. void visitCallSPRIVImageQuerySize(CallInst *CI); /// Transform __spirv_Atomic* to atomic_*. /// __spirv_Atomic*(atomic_op, scope, sema, ops, ...) => /// atomic_*(atomic_op, ops, ..., order(sema), map(scope)) void visitCallSPIRVAtomicBuiltin(CallInst *CI, Op OC); /// Transform __spirv_Group* to {work_group|sub_group}_*. /// /// Special handling of work_group_broadcast. /// __spirv_GroupBroadcast(a, vec3(x, y, z)) /// => /// work_group_broadcast(a, x, y, z) /// /// Transform OpenCL group builtin function names from group_ /// to workgroup_ and sub_group_. /// Insert group operation part: reduce_/inclusive_scan_/exclusive_scan_ /// Transform the operation part: /// fadd/iadd/sadd => add /// fmax/smax => max /// fmin/smin => min /// Keep umax/umin unchanged. void visitCallSPIRVGroupBuiltin(CallInst *CI, Op OC); /// Transform __spirv_MemoryBarrier to atomic_work_item_fence. /// __spirv_MemoryBarrier(scope, sema) => /// atomic_work_item_fence(flag(sema), order(sema), map(scope)) void visitCallSPIRVMemoryBarrier(CallInst *CI); /// Transform __spirv_{PipeOpName} to OCL pipe builtin functions. void visitCallSPIRVPipeBuiltin(CallInst *CI, Op OC); /// Transform __spirv_* builtins to OCL 2.0 builtins. /// No change with arguments. void visitCallSPIRVBuiltin(CallInst *CI, Op OC); /// Translate mangled atomic type name: "atomic_" => /// MangledAtomicTypeNamePrefix void translateMangledAtomicTypeName(); /// Get prefix work_/sub_ for OCL group builtin functions. /// Assuming the first argument of \param CI is a constant integer for /// workgroup/subgroup scope enums. std::string getGroupBuiltinPrefix(CallInst *CI); static char ID; private: Module *M; LLVMContext *Ctx; }; char SPIRVToOCL20::ID = 0; bool SPIRVToOCL20::runOnModule(Module& Module) { M = &Module; Ctx = &M->getContext(); visit(*M); translateMangledAtomicTypeName(); eraseUselessFunctions(&Module); DEBUG(dbgs() << "After SPIRVToOCL20:\n" << *M); std::string Err; raw_string_ostream ErrorOS(Err); if (verifyModule(*M, &ErrorOS)){ DEBUG(errs() << "Fails to verify module: " << ErrorOS.str()); } return true; } void SPIRVToOCL20::visitCallInst(CallInst& CI) { DEBUG(dbgs() << "[visistCallInst] " << CI << '\n'); auto F = CI.getCalledFunction(); if (!F) return; auto MangledName = F->getName(); std::string DemangledName; Op OC = OpNop; if (!oclIsBuiltin(MangledName, &DemangledName) || (OC = getSPIRVFuncOC(DemangledName)) == OpNop) return; DEBUG(dbgs() << "DemangledName = " << DemangledName.c_str() << '\n' << "OpCode = " << OC << '\n'); if (OC == OpImageQuerySize || OC == OpImageQuerySizeLod) { visitCallSPRIVImageQuerySize(&CI); return; } if (OC == OpMemoryBarrier) { visitCallSPIRVMemoryBarrier(&CI); return; } if (isAtomicOpCode(OC)) { visitCallSPIRVAtomicBuiltin(&CI, OC); return; } if (isGroupOpCode(OC)) { visitCallSPIRVGroupBuiltin(&CI, OC); return; } if (isPipeOpCode(OC)) { visitCallSPIRVPipeBuiltin(&CI, OC); return; } if (OCLSPIRVBuiltinMap::rfind(OC)) visitCallSPIRVBuiltin(&CI, OC); } void SPIRVToOCL20::visitCallSPIRVMemoryBarrier(CallInst* CI) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstOCL(M, CI, [=](CallInst *, std::vector &Args){ auto getArg = [=](unsigned I){ return cast(Args[I])->getZExtValue(); }; auto MScope = static_cast(getArg(0)); auto Sema = mapSPIRVMemSemanticToOCL(getArg(1)); Args.resize(3); Args[0] = getInt32(M, Sema.first); Args[1] = getInt32(M, Sema.second); Args[2] = getInt32(M, rmap(MScope)); return kOCLBuiltinName::AtomicWorkItemFence; }, &Attrs); } void SPIRVToOCL20::visitCallSPRIVImageQuerySize(CallInst *CI) { Function * func = CI->getCalledFunction(); // Get image type Type * argTy = func->getFunctionType()->getParamType(0); assert(argTy->isPointerTy() && "argument must be a pointer to opaque structure"); StructType * imgTy = cast(argTy->getPointerElementType()); assert(imgTy->isOpaque() && "image type must be an opaque structure"); StringRef imgTyName = imgTy->getName(); assert(imgTyName.startswith("opencl.image") && "not an OCL image type"); unsigned imgDim = 0; bool imgArray = false; if (imgTyName.startswith("opencl.image1d")) { imgDim = 1; } else if (imgTyName.startswith("opencl.image2d")) { imgDim = 2; } else if (imgTyName.startswith("opencl.image3d")) { imgDim = 3; } assert(imgDim != 0 && "unexpected image dimensionality"); if (imgTyName.count("_array_") != 0) { imgArray = true; } AttributeSet attributes = CI->getCalledFunction()->getAttributes(); BuiltinFuncMangleInfo mangle; Type * int32Ty = Type::getInt32Ty(*Ctx); Instruction * getImageSize = nullptr; if (imgDim == 1) { // OpImageQuerySize from non-arrayed 1d image is always translated // into get_image_width returning scalar argument getImageSize = addCallInst(M, kOCLBuiltinName::GetImageWidth, int32Ty, CI->getArgOperand(0), &attributes, CI, &mangle, CI->getName(), false); // The width of integer type returning by OpImageQuerySize[Lod] may // differ from i32 if (CI->getType()->getScalarType() != int32Ty) { getImageSize = CastInst::CreateIntegerCast(getImageSize, CI->getType()->getScalarType(), false, CI->getName(), CI); } } else { assert((imgDim == 2 || imgDim == 3) && "invalid image type"); assert(CI->getType()->isVectorTy() && "this code can handle vector result type only"); // get_image_dim returns int2 and int4 for 2d and 3d images respecitvely. const unsigned imgDimRetEls = imgDim == 2 ? 2 : 4; VectorType * retTy = VectorType::get(int32Ty, imgDimRetEls); getImageSize = addCallInst(M, kOCLBuiltinName::GetImageDim, retTy, CI->getArgOperand(0), &attributes, CI, &mangle, CI->getName(), false); // The width of integer type returning by OpImageQuerySize[Lod] may // differ from i32 if (CI->getType()->getScalarType() != int32Ty) { getImageSize = CastInst::CreateIntegerCast(getImageSize, VectorType::get(CI->getType()->getScalarType(), getImageSize->getType()->getVectorNumElements()), false, CI->getName(), CI); } } if (imgArray || imgDim == 3) { assert(CI->getType()->isVectorTy() && "OpImageQuerySize[Lod] must return vector for arrayed and 3d images"); const unsigned imgQuerySizeRetEls = CI->getType()->getVectorNumElements(); if (imgDim == 1) { // get_image_width returns scalar result while OpImageQuerySize // for image1d_array_t returns <2 x i32> vector. assert(imgQuerySizeRetEls == 2 && "OpImageQuerySize[Lod] must return <2 x iN> vector type"); getImageSize = InsertElementInst::Create(UndefValue::get(CI->getType()), getImageSize, ConstantInt::get(int32Ty, 0), CI->getName(), CI); } else { // get_image_dim and OpImageQuerySize returns different vector // types for arrayed and 3d images. SmallVector maskEls; for(unsigned idx = 0; idx < imgQuerySizeRetEls; ++idx) maskEls.push_back(ConstantInt::get(int32Ty, idx)); Constant * mask = ConstantVector::get(maskEls); getImageSize = new ShuffleVectorInst(getImageSize, UndefValue::get(getImageSize->getType()), mask, CI->getName(), CI); } } if (imgArray) { assert((imgDim == 1 || imgDim == 2) && "invalid image array type"); // Insert get_image_array_size to the last position of the resulting vector. Type * sizeTy = Type::getIntNTy(*Ctx, M->getDataLayout().getPointerSizeInBits(0)); Instruction * getImageArraySize = addCallInst(M, kOCLBuiltinName::GetImageArraySize, sizeTy, CI->getArgOperand(0), &attributes, CI, &mangle, CI->getName(), false); // The width of integer type returning by OpImageQuerySize[Lod] may // differ from size_t which is returned by get_image_array_size if (getImageArraySize->getType() != CI->getType()->getScalarType()) { getImageArraySize = CastInst::CreateIntegerCast(getImageArraySize, CI->getType()->getScalarType(), false, CI->getName(), CI); } getImageSize = InsertElementInst::Create(getImageSize, getImageArraySize, ConstantInt::get(int32Ty, CI->getType()->getVectorNumElements() - 1), CI->getName(), CI); } assert(getImageSize && "must not be null"); CI->replaceAllUsesWith(getImageSize); CI->eraseFromParent(); } void SPIRVToOCL20::visitCallSPIRVAtomicBuiltin(CallInst* CI, Op OC) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); Instruction * pInsertBefore = CI; mutateCallInstOCL(M, CI, [=](CallInst *, std::vector &Args, Type *& RetTy){ auto Ptr = findFirstPtr(Args); auto Name = OCLSPIRVBuiltinMap::rmap(OC); auto NumOrder = getAtomicBuiltinNumMemoryOrderArgs(Name); auto ScopeIdx = Ptr + 1; auto OrderIdx = Ptr + 2; if (OC == OpAtomicIIncrement || OC == OpAtomicIDecrement) { // Since OpenCL 1.2 atomic_inc and atomic_dec builtins don't have, memory // scope and memory order syntax, and OpenCL 2.0 doesn't have such // builtins, therefore we translate these instructions to // atomic_fetch_add_explicit and atomic_fetch_sub_explicit OpenCL 2.0 // builtins with "operand" argument = 1. Name = OCLSPIRVBuiltinMap::rmap(OC == OpAtomicIIncrement ? OpAtomicIAdd: OpAtomicISub); Type* ValueTy = cast(Args[Ptr]->getType())->getElementType(); assert(ValueTy->isIntegerTy()); Args.push_back(llvm::ConstantInt::get(ValueTy, 1)); } Args[ScopeIdx] = mapUInt(M, cast(Args[ScopeIdx]), [](unsigned I) { return rmap(static_cast(I));}); for (size_t I = 0; I < NumOrder; ++I) Args[OrderIdx + I] = mapUInt(M, cast(Args[OrderIdx + I]), [](unsigned Ord) { return mapSPIRVMemOrderToOCL(Ord); }); std::swap(Args[ScopeIdx], Args.back()); if(OC == OpAtomicCompareExchange || OC == OpAtomicCompareExchangeWeak) { // OpAtomicCompareExchange[Weak] semantics is different from // atomic_compare_exchange_[strong|weak] semantics as well as // arguments order. // OCL built-ins returns boolean value and stores a new/original // value by pointer passed as 2nd argument (aka expected) while SPIR-V // instructions returns this new/original value as a resulting value. AllocaInst *pExpected = new AllocaInst(CI->getType(), "expected", static_cast(pInsertBefore->getParent()->getParent()->getEntryBlock().getFirstInsertionPt())); pExpected->setAlignment(CI->getType()->getScalarSizeInBits() / 8); new StoreInst(Args[1], pExpected, pInsertBefore); Args[1] = pExpected; std::swap(Args[3], Args[4]); std::swap(Args[2], Args[3]); RetTy = Type::getInt1Ty(*Ctx); } return Name; }, [=](CallInst * CI) -> Instruction * { if(OC == OpAtomicCompareExchange || OC == OpAtomicCompareExchangeWeak) { // OCL built-ins atomic_compare_exchange_[strong|weak] return boolean value. So, // to obtain the same value as SPIR-V instruction is returning it has to be loaded // from the memory where 'expected' value is stored. This memory must contain the // needed value after a call to OCL built-in is completed. LoadInst * pOriginal = new LoadInst(CI->getArgOperand(1), "original", pInsertBefore); return pOriginal; } // For other built-ins the return values match. return CI; }, &Attrs); } void SPIRVToOCL20::visitCallSPIRVBuiltin(CallInst* CI, Op OC) { AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstOCL(M, CI, [=](CallInst *, std::vector &Args){ return OCLSPIRVBuiltinMap::rmap(OC); }, &Attrs); } void SPIRVToOCL20::visitCallSPIRVGroupBuiltin(CallInst* CI, Op OC) { auto DemangledName = OCLSPIRVBuiltinMap::rmap(OC); assert(DemangledName.find(kSPIRVName::GroupPrefix) == 0); std::string Prefix = getGroupBuiltinPrefix(CI); bool HasGroupOperation = hasGroupOperation(OC); if (!HasGroupOperation) { DemangledName = Prefix + DemangledName; } else { auto GO = getArgAs(CI, 1); StringRef Op = DemangledName; Op = Op.drop_front(strlen(kSPIRVName::GroupPrefix)); bool Unsigned = Op.front() == 'u'; if (!Unsigned) Op = Op.drop_front(1); DemangledName = Prefix + kSPIRVName::GroupPrefix + SPIRSPIRVGroupOperationMap::rmap(GO) + '_' + Op.str(); } AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstOCL(M, CI, [=](CallInst *, std::vector &Args){ Args.erase(Args.begin(), Args.begin() + (HasGroupOperation ? 2 : 1)); if (OC == OpGroupBroadcast) expandVector(CI, Args, 1); return DemangledName; }, &Attrs); } void SPIRVToOCL20::visitCallSPIRVPipeBuiltin(CallInst* CI, Op OC) { switch(OC) { case OpReservedReadPipe: OC = OpReadPipe; break; case OpReservedWritePipe: OC = OpWritePipe; break; default: // Do nothing. break; } auto DemangledName = OCLSPIRVBuiltinMap::rmap(OC); bool HasScope = DemangledName.find(kSPIRVName::GroupPrefix) == 0; if (HasScope) DemangledName = getGroupBuiltinPrefix(CI) + DemangledName; AttributeSet Attrs = CI->getCalledFunction()->getAttributes(); mutateCallInstOCL(M, CI, [=](CallInst *, std::vector &Args){ if (HasScope) Args.erase(Args.begin(), Args.begin() + 1); if (!(OC == OpReadPipe || OC == OpWritePipe || OC == OpReservedReadPipe || OC == OpReservedWritePipe)) return DemangledName; auto &P = Args[Args.size() - 3]; auto T = P->getType(); assert(isa(T)); auto ET = T->getPointerElementType(); if (!ET->isIntegerTy(8) || T->getPointerAddressSpace() != SPIRAS_Generic) { auto NewTy = PointerType::getInt8PtrTy(*Ctx, SPIRAS_Generic); P = CastInst::CreatePointerBitCastOrAddrSpaceCast(P, NewTy, "", CI); } return DemangledName; }, &Attrs); } void SPIRVToOCL20::translateMangledAtomicTypeName() { for (auto &I:M->functions()) { if (!I.hasName()) continue; std::string MangledName = I.getName(); std::string DemangledName; if (!oclIsBuiltin(MangledName, &DemangledName) || DemangledName.find(kOCLBuiltinName::AtomPrefix) != 0) continue; auto Loc = MangledName.find(kOCLBuiltinName::AtomPrefix); Loc = MangledName.find(kMangledName::AtomicPrefixInternal, Loc); MangledName.replace(Loc, strlen(kMangledName::AtomicPrefixInternal), MangledAtomicTypeNamePrefix); I.setName(MangledName); } } std::string SPIRVToOCL20::getGroupBuiltinPrefix(CallInst* CI) { std::string Prefix; auto ES = getArgAsScope(CI, 0); switch(ES) { case ScopeWorkgroup: Prefix = kOCLBuiltinName::WorkPrefix; break; case ScopeSubgroup: Prefix = kOCLBuiltinName::SubPrefix; break; default: llvm_unreachable("Invalid execution scope"); } return Prefix; } void SPIRVToOCL20::visitCastInst(CastInst &Cast) { if(!isa(Cast) && !isa(Cast) && !isa(Cast) && !isa(Cast) && !isa(Cast) && !isa(Cast) && !isa(Cast) && !isa(Cast) && !isa(Cast)) return; Type const* srcTy = Cast.getSrcTy(); Type * dstVecTy = Cast.getDestTy(); // Leave scalar casts as is. Skip boolean vector casts becase there // are no suitable OCL built-ins. if(!dstVecTy->isVectorTy() || srcTy->getScalarSizeInBits() == 1 || dstVecTy->getScalarSizeInBits() == 1) return; // Assemble built-in name -> convert_gentypeN std::string castBuiltInName(kOCLBuiltinName::ConvertPrefix); // Check if this is 'floating point -> unsigned integer' cast castBuiltInName += mapLLVMTypeToOCLType(dstVecTy, !isa(Cast)); // Replace LLVM conversion instruction with call to conversion built-in BuiltinFuncMangleInfo mangle; // It does matter if the source is unsigned integer or not. SExt is for // signed source, ZExt and UIToFPInst are for unsigned source. if(isa(Cast) || isa(Cast)) mangle.addUnsignedArg(0); AttributeSet attributes; CallInst *call = addCallInst(M, castBuiltInName, dstVecTy, Cast.getOperand(0), &attributes, &Cast, &mangle, Cast.getName(), false); Cast.replaceAllUsesWith(call); Cast.eraseFromParent(); } } // namespace SPIRV INITIALIZE_PASS(SPIRVToOCL20, "spvtoocl20", "Translate SPIR-V builtins to OCL 2.0 builtins", false, false) ModulePass *llvm::createSPIRVToOCL20() { return new SPIRVToOCL20(); }