Files
RedBear-OS/local/recipes/dev/spirv-llvm-translator/source/lib/SPIRV/SPIRVToOCL20.cpp
T

302 lines
12 KiB
C++

//===- 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.
//
//===----------------------------------------------------------------------===//
#include "OCLUtil.h"
#include "SPIRVToOCL.h"
#include "llvm/IR/Verifier.h"
#define DEBUG_TYPE "spvtocl20"
namespace SPIRV {
char SPIRVToOCL20Legacy::ID = 0;
bool SPIRVToOCL20Legacy::runOnModule(Module &Module) {
return SPIRVToOCL20Base::runSPIRVToOCL(Module);
}
bool SPIRVToOCL20Base::runSPIRVToOCL(Module &Module) {
M = &Module;
Ctx = &M->getContext();
// Lower builtin variables to builtin calls first.
lowerBuiltinVariablesToCalls(M);
translateOpaqueTypes();
visit(*M);
postProcessBuiltinsReturningStruct(M);
postProcessBuiltinsWithArrayArguments(M);
eraseUselessFunctions(&Module);
LLVM_DEBUG(dbgs() << "After SPIRVToOCL20:\n" << *M);
std::string Err;
raw_string_ostream ErrorOS(Err);
if (verifyModule(*M, &ErrorOS)) {
LLVM_DEBUG(errs() << "Fails to verify module: " << ErrorOS.str());
}
return true;
}
void SPIRVToOCL20Base::visitCallSPIRVMemoryBarrier(CallInst *CI) {
Value *MemScope =
SPIRV::transSPIRVMemoryScopeIntoOCLMemoryScope(CI->getArgOperand(0), CI);
Value *MemFenceFlags = SPIRV::transSPIRVMemorySemanticsIntoOCLMemFenceFlags(
CI->getArgOperand(1), CI);
Value *MemOrder = SPIRV::transSPIRVMemorySemanticsIntoOCLMemoryOrder(
CI->getArgOperand(1), CI);
mutateCallInst(CI, kOCLBuiltinName::AtomicWorkItemFence)
.setArgs({MemFenceFlags, MemOrder, MemScope});
}
void SPIRVToOCL20Base::visitCallSPIRVControlBarrier(CallInst *CI) {
auto GetArg = [=](unsigned I) {
return cast<ConstantInt>(CI->getArgOperand(I))->getZExtValue();
};
auto ExecScope = static_cast<Scope>(GetArg(0));
Value *MemScope =
SPIRV::transSPIRVMemoryScopeIntoOCLMemoryScope(CI->getArgOperand(1), CI);
Value *MemFenceFlags = SPIRV::transSPIRVMemorySemanticsIntoOCLMemFenceFlags(
CI->getArgOperand(2), CI);
mutateCallInst(CI, ExecScope == ScopeWorkgroup
? kOCLBuiltinName::WorkGroupBarrier
: kOCLBuiltinName::SubGroupBarrier)
.setArgs({MemFenceFlags, MemScope});
}
void SPIRVToOCL20Base::visitCallSPIRVSplitBarrierINTEL(CallInst *CI, Op OC) {
Value *MemScope =
SPIRV::transSPIRVMemoryScopeIntoOCLMemoryScope(CI->getArgOperand(1), CI);
Value *MemFenceFlags = SPIRV::transSPIRVMemorySemanticsIntoOCLMemFenceFlags(
CI->getArgOperand(2), CI);
mutateCallInst(CI, OCLSPIRVBuiltinMap::rmap(OC))
.setArgs({MemFenceFlags, MemScope});
}
std::string SPIRVToOCL20Base::mapFPAtomicName(Op OC) {
assert(isFPAtomicOpCode(OC) && "Not intended to handle other opcodes than "
"AtomicF{Add/Min/Max}EXT!");
switch (OC) {
case OpAtomicFAddEXT:
return "atomic_fetch_add_explicit";
case OpAtomicFMinEXT:
return "atomic_fetch_min_explicit";
case OpAtomicFMaxEXT:
return "atomic_fetch_max_explicit";
default:
llvm_unreachable("Unsupported opcode!");
}
}
void SPIRVToOCL20Base::mutateAtomicName(CallInst *CI, Op OC) {
// Map fp atomic instructions to regular OpenCL built-ins.
mutateCallInst(CI, isFPAtomicOpCode(OC) ? mapFPAtomicName(OC)
: OCLSPIRVBuiltinMap::rmap(OC));
}
void SPIRVToOCL20Base::visitCallSPIRVAtomicBuiltin(CallInst *CI, Op OC) {
CallInst *CIG = mutateCommonAtomicArguments(CI, OC);
switch (OC) {
case OpAtomicIIncrement:
case OpAtomicIDecrement:
visitCallSPIRVAtomicIncDec(CIG, OC);
break;
case OpAtomicCompareExchange:
case OpAtomicCompareExchangeWeak:
visitCallSPIRVAtomicCmpExchg(CIG);
break;
default:
mutateAtomicName(CIG, OC);
}
}
void SPIRVToOCL20Base::visitCallSPIRVAtomicIncDec(CallInst *CI, Op OC) {
// Since OpenCL 2.0 doesn't have atomic_inc and atomic_dec builtins, we
// translate these instructions to atomic_fetch_add_explicit and
// atomic_fetch_sub_explicit OpenCL 2.0 builtins with "operand" argument = 1.
auto Name = OCLSPIRVBuiltinMap::rmap(OC == OpAtomicIIncrement ? OpAtomicIAdd
: OpAtomicISub);
Type *ValueTy = CI->getType();
assert(ValueTy->isIntegerTy());
mutateCallInst(CI, Name).insertArg(1, ConstantInt::get(ValueTy, 1));
}
CallInst *SPIRVToOCL20Base::mutateCommonAtomicArguments(CallInst *CI, Op OC) {
std::string Name;
// Map fp atomic instructions to regular OpenCL built-ins.
if (isFPAtomicOpCode(OC))
Name = mapFPAtomicName(OC);
else
Name = OCLSPIRVBuiltinMap::rmap(OC);
auto Ptr = findFirstPtr(CI->args());
auto NumOrder = getSPIRVAtomicBuiltinNumMemoryOrderArgs(OC);
auto ScopeIdx = Ptr + 1;
auto OrderIdx = Ptr + 2;
auto Mutator = mutateCallInst(CI, Name);
Mutator.mapArgs([=](IRBuilder<> &Builder, Value *PtrArg, Type *PtrArgTy) {
if (auto *TypedPtrTy = dyn_cast<TypedPointerType>(PtrArgTy)) {
if (TypedPtrTy->getAddressSpace() != SPIRAS_Generic) {
Type *ElementTy = TypedPtrTy->getElementType();
Type *FixedPtr = PointerType::get(CI->getContext(), SPIRAS_Generic);
PtrArg = Builder.CreateAddrSpaceCast(PtrArg, FixedPtr,
PtrArg->getName() + ".as");
PtrArgTy = TypedPointerType::get(ElementTy, SPIRAS_Generic);
}
}
return std::make_pair(PtrArg, PtrArgTy);
});
Mutator.mapArg(ScopeIdx, [=](Value *Arg) {
return SPIRV::transSPIRVMemoryScopeIntoOCLMemoryScope(Arg, CI);
});
for (size_t I = 0; I < NumOrder; ++I) {
Mutator.mapArg(OrderIdx + I, [=](Value *Arg) {
return SPIRV::transSPIRVMemorySemanticsIntoOCLMemoryOrder(Arg, CI);
});
}
Mutator.moveArg(Mutator.arg_size() - 1, ScopeIdx + 1);
Mutator.moveArg(ScopeIdx, Mutator.arg_size() - 1);
return cast<CallInst>(Mutator.getMutated());
}
void SPIRVToOCL20Base::visitCallSPIRVAtomicCmpExchg(CallInst *CI) {
Type *MemTy = CI->getType();
// OpAtomicCompareExchange[Weak] semantics is different from
// atomic_compare_exchange_strong 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(
MemTy, M->getDataLayout().getAllocaAddrSpace(), "expected",
CI->getParent()->getParent()->getEntryBlock().getFirstInsertionPt());
PExpected->setAlignment(Align(MemTy->getScalarSizeInBits() / 8));
// Tail call implies that the callee doesn't access alloca from the caller.
// The newly created alloca invalidates the tail call semantics.
CI->setTailCall(false);
// OpAtomicCompareExchangeWeak is not "weak" at all, but instead has the same
// semantics as OpAtomicCompareExchange.
mutateCallInst(CI, "atomic_compare_exchange_strong_explicit")
.mapArg(1,
[=](IRBuilder<> &Builder, Value *Expected) {
Builder.CreateStore(Expected, PExpected);
unsigned AddrSpc = SPIRAS_Generic;
Type *PtrTyAS =
PointerType::get(Expected->getContext(), AddrSpc);
Value *V = Builder.CreateAddrSpaceCast(
PExpected, PtrTyAS, PExpected->getName() + ".as");
return std::make_pair(V, TypedPointerType::get(MemTy, AddrSpc));
})
.moveArg(4, 2)
.changeReturnType(Type::getInt1Ty(*Ctx), [=](IRBuilder<> &Builder,
CallInst *NewCI) {
// 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.
return Builder.CreateLoad(MemTy, NewCI->getArgOperand(1), "original");
});
}
void SPIRVToOCL20Base::visitCallSPIRVEnqueueKernel(CallInst *CI, Op OC) {
bool HasVaargs = CI->arg_size() > 10;
bool HasEvents = true;
Value *EventRet = CI->getArgOperand(5);
if (isa<ConstantPointerNull>(EventRet)) {
Value *NumEvents = CI->getArgOperand(3);
if (isa<ConstantInt>(NumEvents)) {
ConstantInt *NE = cast<ConstantInt>(NumEvents);
HasEvents = NE->getZExtValue() != 0;
}
}
StringRef FName = "";
if (!HasVaargs && !HasEvents)
FName = "__enqueue_kernel_basic";
else if (!HasVaargs && HasEvents)
FName = "__enqueue_kernel_basic_events";
else if (HasVaargs && !HasEvents)
FName = "__enqueue_kernel_varargs";
else
FName = "__enqueue_kernel_events_varargs";
auto Mutator = mutateCallInst(CI, FName.str());
Mutator.mapArg(6, [=](IRBuilder<> &Builder, Value *Invoke) {
Value *Replace = CastInst::CreatePointerBitCastOrAddrSpaceCast(
Invoke, Builder.getPtrTy(SPIRAS_Generic), "", CI->getIterator());
return std::make_pair(
Replace, TypedPointerType::get(Builder.getInt8Ty(), SPIRAS_Generic));
});
if (!HasVaargs) {
// Remove arguments at indices 8 (Param Size), 9 (Param Align)
Mutator.removeArgs(8, 2);
} else {
// GEP to array of sizes of local arguments
Mutator.moveArg(10, 8);
Type *Int32Ty = Type::getInt32Ty(*Ctx);
size_t NumLocalArgs = Mutator.arg_size() - 10;
Mutator.insertArg(8, ConstantInt::get(Int32Ty, NumLocalArgs));
// Mark all SPIRV-specific arguments as removed
Mutator.removeArgs(10, Mutator.arg_size() - 10);
}
if (!HasEvents) {
// Remove arguments at indices 3 (Num Events), 4 (Wait Events), 5 (Ret
// Event).
Mutator.removeArgs(3, 3);
}
}
} // namespace SPIRV
INITIALIZE_PASS(SPIRVToOCL20Legacy, "spvtoocl20",
"Translate SPIR-V builtins to OCL 2.0 builtins", false, false)
ModulePass *llvm::createSPIRVToOCL20Legacy() {
return new SPIRVToOCL20Legacy();
}