blob: e02264293c55f31916bf24086d18419b12b6b661 [file] [log] [blame]
//===- 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 <cstring>
using namespace llvm;
using namespace SPIRV;
using namespace OCLUtil;
namespace SPIRV {
static cl::opt<std::string>
MangledAtomicTypeNamePrefix("spirv-atomic-prefix",
cl::desc("Mangled atomic type name prefix"), cl::init("U7_Atomic"));
class SPIRVToOCL20: public ModulePass,
public InstVisitor<SPIRVToOCL20> {
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<Value *> &Args){
auto getArg = [=](unsigned I){
return cast<ConstantInt>(Args[I])->getZExtValue();
};
auto MScope = static_cast<Scope>(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<OCLScopeKind>(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<StructType>(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<Constant*, 4> 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<Value *> &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<PointerType>(Args[Ptr]->getType())->getElementType();
assert(ValueTy->isIntegerTy());
Args.push_back(llvm::ConstantInt::get(ValueTy, 1));
}
Args[ScopeIdx] = mapUInt(M, cast<ConstantInt>(Args[ScopeIdx]),
[](unsigned I) { return rmap<OCLScopeKind>(static_cast<Scope>(I));});
for (size_t I = 0; I < NumOrder; ++I)
Args[OrderIdx + I] = mapUInt(M, cast<ConstantInt>(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<Instruction*>(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<Value *> &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<spv::GroupOperation>(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<Value *> &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<Value *> &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<PointerType>(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<ZExtInst>(Cast) && !isa<SExtInst>(Cast) &&
!isa<TruncInst>(Cast) && !isa<FPTruncInst>(Cast) &&
!isa<FPExtInst>(Cast) && !isa<FPToUIInst>(Cast) &&
!isa<FPToSIInst>(Cast) && !isa<UIToFPInst>(Cast) &&
!isa<SIToFPInst>(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<FPToUIInst>(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<ZExtInst>(Cast) || isa<UIToFPInst>(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();
}