blob: 9e4c8888d15a9bd3f303a973c7979d1a36b86319 [file] [log] [blame]
//===- OCLTypeToSPIRV.cpp - Adapt types from OCL for SPIRV ------*- 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 adaptation of OCL types for SPIRV.
//
// It first maps kernel arguments of OCL opaque types to SPIR-V type, then
// propagates the mapping to the uses of the kernel arguments.
//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "cltytospv"
#include "OCLTypeToSPIRV.h"
#include "SPIRVInternal.h"
#include "OCLUtil.h"
#include "llvm/Pass.h"
#include "llvm/PassSupport.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include <set>
#include <iterator>
using namespace llvm;
using namespace SPIRV;
using namespace OCLUtil;
namespace SPIRV {
char OCLTypeToSPIRV::ID = 0;
OCLTypeToSPIRV::OCLTypeToSPIRV()
:ModulePass(ID), M(nullptr), Ctx(nullptr) {
initializeOCLTypeToSPIRVPass(*PassRegistry::getPassRegistry());
}
void
OCLTypeToSPIRV::getAnalysisUsage(AnalysisUsage& AU) const {
AU.setPreservesAll();
}
bool
OCLTypeToSPIRV::runOnModule(Module& Module) {
DEBUG(dbgs() << "Enter OCLTypeToSPIRV:\n");
M = &Module;
Ctx = &M->getContext();
auto Src = getSPIRVSource(&Module);
if (std::get<0>(Src) != spv::SourceLanguageOpenCL_C)
return false;
for (auto &F:Module.functions())
adaptArgumentsByMetadata(&F);
adaptArgumentsBySamplerUse(Module);
while (!WorkSet.empty()) {
Function *F = *WorkSet.begin();
WorkSet.erase(WorkSet.begin());
adaptFunction(F);
}
return false;
}
void
OCLTypeToSPIRV::addAdaptedType(Value *V, Type *T) {
DEBUG(dbgs() << "[add adapted type] ";
V->printAsOperand(dbgs(), true, M);
dbgs() << " => " << *T << '\n');
AdaptedTy[V] = T;
}
void
OCLTypeToSPIRV::addWork(Function *F) {
DEBUG(dbgs() << "[add work] ";
F->printAsOperand(dbgs(), true, M);
dbgs() << '\n');
WorkSet.insert(F);
}
/// Find index of \param V as argument of function call \param CI.
static unsigned
getArgIndex(CallInst *CI, Value *V) {
for (unsigned AI = 0, AE = CI->getNumArgOperands(); AI != AE; ++AI) {
if (CI->getArgOperand(AI) == V)
return AI;
}
llvm_unreachable("Not argument of function call");
}
/// Find index of \param V as argument of function call \param CI.
static unsigned
getArgIndex(Function *F, Value *V) {
auto A = F->arg_begin(), E = F->arg_end();
for (unsigned I = 0; A != E; ++I, ++A) {
if (static_cast<Argument*>(A) == V)
return I;
}
llvm_unreachable("Not argument of function");
}
/// Get i-th argument of a function.
static Argument*
getArg(Function *F, unsigned I) {
auto AI = F->arg_begin();
std::advance(AI, I);
return static_cast<Argument*>(AI);
}
/// Create a new function type if \param F has arguments in AdaptedTy, and
/// propagates the adapted arguments to functions called by \param F.
void
OCLTypeToSPIRV::adaptFunction(Function *F) {
DEBUG(dbgs() << "\n[work on function] ";
F->printAsOperand(dbgs(), true, M);
dbgs() << '\n');
assert (AdaptedTy.count(F) == 0);
std::vector<Type*> ArgTys;
bool Changed = false;
for (auto &I:F->args()) {
auto Loc = AdaptedTy.find(&I);
auto Found = (Loc != AdaptedTy.end());
Changed |= Found;
ArgTys.push_back (Found ? Loc->second : I.getType());
if (Found) {
for (auto U:I.users()) {
if (auto CI = dyn_cast<CallInst>(U)) {
auto ArgIndex = getArgIndex(CI, &I);
auto CF = CI->getCalledFunction();
if (AdaptedTy.count(CF) == 0) {
addAdaptedType(getArg(CF, ArgIndex), Loc->second);
addWork(CF);
}
}
}
}
}
if (!Changed)
return;
auto FT = F->getFunctionType();
FT = FunctionType::get(FT->getReturnType(), ArgTys, FT->isVarArg());
addAdaptedType(F, FT);
}
MDNode *
OCLTypeToSPIRV::getArgAccessQualifierMetadata(Function *F) {
return getArgMetadata(F, SPIR_MD_KERNEL_ARG_ACCESS_QUAL);
}
MDNode *
OCLTypeToSPIRV::getKernelMetadata(Function *F) {
NamedMDNode *KernelMDs = M->getNamedMetadata(SPIR_MD_KERNELS);
if (!KernelMDs)
return nullptr;
for (unsigned I = 0, E = KernelMDs->getNumOperands(); I < E; ++I) {
MDNode *KernelMD = KernelMDs->getOperand(I);
if (KernelMD->getNumOperands() == 0)
continue;
Function *Kernel = mdconst::dyn_extract<Function>(KernelMD->getOperand(0));
if (Kernel == F)
return KernelMD;
}
return nullptr;
}
MDNode *
OCLTypeToSPIRV::getArgMetadata(Function *F, const std::string &MDName) {
auto KernelMD = getKernelMetadata(F);
if (!KernelMD)
return nullptr;
for (unsigned MI = 1, ME = KernelMD->getNumOperands(); MI < ME; ++MI) {
MDNode *MD = dyn_cast<MDNode>(KernelMD->getOperand(MI));
if (!MD)
continue;
MDString *NameMD = dyn_cast<MDString>(MD->getOperand(0));
if (!NameMD)
continue;
StringRef Name = NameMD->getString();
if (Name == MDName) {
return MD;
}
}
return nullptr;
}
MDNode *
OCLTypeToSPIRV::getArgBaseTypeMetadata(Function *F) {
return getArgMetadata(F, SPIR_MD_KERNEL_ARG_BASE_TYPE);
}
// Handle functions with sampler arguments that don't get called by
// a kernel function.
void OCLTypeToSPIRV::adaptArgumentsBySamplerUse(Module &M) {
SmallPtrSet<Function *, 5> Processed;
std::function<void(Function *, unsigned)> TraceArg = [&](Function *F,
unsigned Idx) {
// If we have cycles in the call graph in the future, bail out
// if we've already processed this function.
if (Processed.insert(F).second == false)
return;
for (auto U : F->users()) {
auto *CI = dyn_cast<CallInst>(U);
if (!CI)
continue;
auto SamplerArg = CI->getArgOperand(Idx);
if (!isa<Argument>(SamplerArg) ||
AdaptedTy.count(SamplerArg) != 0) // Already traced this, move on.
continue;
if (isSPIRVType(SamplerArg->getType(), kSPIRVTypeName::Sampler))
return;
addAdaptedType(SamplerArg, getSamplerType(&M));
auto Caller = cast<Argument>(SamplerArg)->getParent();
addWork(Caller);
TraceArg(Caller, getArgIndex(Caller, SamplerArg));
}
};
for (auto &F : M) {
if (!F.empty()) // not decl
continue;
auto MangledName = F.getName();
std::string DemangledName;
if (!oclIsBuiltin(MangledName, &DemangledName, false))
continue;
if (DemangledName.find(kSPIRVName::SampledImage) == std::string::npos)
continue;
TraceArg(&F, 1);
}
}
/// Go through all kernel functions, get access qualifier for image and pipe
/// types and use them to map the function arguments to the SPIR-V type.
/// ToDo: Map other OpenCL opaque types to SPIR-V types.
void
OCLTypeToSPIRV::adaptArgumentsByMetadata(Function* F) {
auto TypeMD = getArgBaseTypeMetadata(F);
if (!TypeMD)
return;
bool Changed = false;
auto FT = F->getFunctionType();
auto PI = FT->param_begin();
auto Arg = F->arg_begin();
for (unsigned I = 1, E = TypeMD->getNumOperands(); I != E;
++I, ++PI, ++ Arg) {
auto OCLTyStr = getMDOperandAsString(TypeMD, I);
auto NewTy = *PI;
if (OCLTyStr == OCL_TYPE_NAME_SAMPLER_T && !NewTy->isStructTy()) {
addAdaptedType(static_cast<Argument*>(Arg), getSamplerType(M));
Changed = true;
} else if (isPointerToOpaqueStructType(NewTy)) {
auto STName = NewTy->getPointerElementType()->getStructName();
if (STName.startswith(kSPR2TypeName::ImagePrefix) ||
STName == kSPR2TypeName::Pipe) {
auto Ty = STName.str();
auto AccMD = getArgAccessQualifierMetadata(F);
assert(AccMD && "Invalid access qualifier metadata");
auto AccStr = getMDOperandAsString(AccMD, I);
addAdaptedType(static_cast<Argument*>(Arg), getOrCreateOpaquePtrType(M,
mapOCLTypeNameToSPIRV(Ty, AccStr)));
Changed = true;
}
}
}
if (Changed)
addWork(F);
}
// OCL sampler, image and pipe type need to be regularized before converting
// to SPIRV types.
//
// OCL sampler type is represented as i32 in LLVM, however in SPIRV it is
// represented as OpTypeSampler. Also LLVM uses the same pipe type to
// represent pipe types with different underlying data types, however
// in SPIRV they are different types. OCL image and pipie types do not
// encode access qualifier, which is part of SPIRV types for image and pipe.
//
// The function types in LLVM need to be regularized before translating
// to SPIRV function types:
//
// sampler type as i32 -> opencl.sampler_t opaque type
// opencl.pipe_t opaque type with underlying opencl type x and access
// qualifier y -> opencl.pipe_t.x.y opaque type
// opencl.image_x opaque type with access qualifier y ->
// opencl.image_x.y opaque type
//
// The converter relies on kernel_arg_base_type to identify the sampler
// type, the underlying data type of pipe type, and access qualifier for
// image and pipe types. The FE is responsible to generate the correct
// kernel_arg_base_type metadata.
//
// Alternatively,the FE may choose to use opencl.sampler_t to represent
// sampler type, use opencl.pipe_t.x.y to represent pipe type with underlying
// opencl data type x and access qualifier y, and use opencl.image_x.y to
// represent image_x type with access qualifier y.
//
Type *
OCLTypeToSPIRV::getAdaptedType(Value *V) {
auto Loc = AdaptedTy.find(V);
if (Loc != AdaptedTy.end())
return Loc->second;
if(auto F = dyn_cast<Function>(V))
return F->getFunctionType();
return V->getType();
}
}
INITIALIZE_PASS(OCLTypeToSPIRV, "cltytospv", "Adapt OCL types for SPIR-V",
false, true)
ModulePass *llvm::createOCLTypeToSPIRV() {
return new OCLTypeToSPIRV();
}