7598 lines
310 KiB
C++
7598 lines
310 KiB
C++
//===- SPIRVWriter.cpp - Converts LLVM to SPIR-V ----------------*- C++ -*-===//
|
|
//
|
|
// The LLVM/SPIR-V 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.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
/// \file
|
|
///
|
|
/// This file implements conversion of LLVM intermediate language to SPIR-V
|
|
/// binary.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "SPIRVWriter.h"
|
|
#include "LLVMToSPIRVDbgTran.h"
|
|
#include "OCLToSPIRV.h"
|
|
#include "PreprocessMetadata.h"
|
|
#include "SPIRVAsm.h"
|
|
#include "SPIRVBasicBlock.h"
|
|
#include "SPIRVEntry.h"
|
|
#include "SPIRVEnum.h"
|
|
#include "SPIRVExtInst.h"
|
|
#include "SPIRVFunction.h"
|
|
#include "SPIRVInstruction.h"
|
|
#include "SPIRVInternal.h"
|
|
#include "SPIRVLLVMUtil.h"
|
|
#include "SPIRVLowerBitCastToNonStandardType.h"
|
|
#include "SPIRVLowerBool.h"
|
|
#include "SPIRVLowerConstExpr.h"
|
|
#include "SPIRVLowerLLVMIntrinsic.h"
|
|
#include "SPIRVLowerMemmove.h"
|
|
#include "SPIRVLowerOCLBlocks.h"
|
|
#include "SPIRVMDWalker.h"
|
|
#include "SPIRVMemAliasingINTEL.h"
|
|
#include "SPIRVModule.h"
|
|
#include "SPIRVRegularizeLLVM.h"
|
|
#include "SPIRVType.h"
|
|
#include "SPIRVUtil.h"
|
|
#include "SPIRVValue.h"
|
|
#include "VectorComputeUtil.h"
|
|
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/StringSwitch.h"
|
|
#include "llvm/Analysis/LoopAnalysisManager.h"
|
|
#include "llvm/Analysis/LoopInfo.h"
|
|
#include "llvm/Analysis/ValueTracking.h"
|
|
#include "llvm/IR/Constants.h"
|
|
#include "llvm/IR/DerivedTypes.h"
|
|
#include "llvm/IR/Dominators.h"
|
|
#include "llvm/IR/Function.h"
|
|
#include "llvm/IR/InlineAsm.h"
|
|
#include "llvm/IR/InstrTypes.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/IR/IntrinsicInst.h"
|
|
#include "llvm/IR/Module.h"
|
|
#include "llvm/IR/Operator.h"
|
|
#include "llvm/IR/TypedPointerType.h"
|
|
#include "llvm/Pass.h"
|
|
#include "llvm/Passes/PassBuilder.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/TargetParser/Triple.h"
|
|
#include "llvm/Transforms/Utils/LoopSimplify.h"
|
|
#include "llvm/Transforms/Utils/Mem2Reg.h"
|
|
|
|
#include <cstdlib>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <queue>
|
|
#include <regex>
|
|
#include <set>
|
|
#include <vector>
|
|
|
|
#define DEBUG_TYPE "spirv"
|
|
|
|
using namespace llvm;
|
|
using namespace SPIRV;
|
|
using namespace OCLUtil;
|
|
|
|
namespace {
|
|
|
|
static SPIRVWord convertFloatToSPIRVWord(float F) {
|
|
union {
|
|
float F;
|
|
SPIRVWord Spir;
|
|
} FPMaxError;
|
|
FPMaxError.F = F;
|
|
return FPMaxError.Spir;
|
|
}
|
|
|
|
/// Return one of the SPIR-V 1.4 SignExtend or ZeroExtend image operands
|
|
/// for a function name, or 0 if the function does not return or
|
|
/// write an integer type.
|
|
int getImageSignZeroExt(Function *F) {
|
|
bool IsSigned = false;
|
|
bool IsUnsigned = false;
|
|
|
|
ParamSignedness RetSignedness;
|
|
SmallVector<ParamSignedness, 4> ArgSignedness;
|
|
if (!getRetParamSignedness(F, RetSignedness, ArgSignedness))
|
|
return 0;
|
|
|
|
StringRef Name = F->getName();
|
|
Name = Name.substr(Name.find(kSPIRVName::Prefix));
|
|
Name.consume_front(kSPIRVName::Prefix);
|
|
if (Name.consume_front("ImageRead") ||
|
|
Name.consume_front("ImageSampleExplicitLod")) {
|
|
if (RetSignedness == ParamSignedness::Signed)
|
|
IsSigned = true;
|
|
else if (RetSignedness == ParamSignedness::Unsigned)
|
|
IsUnsigned = true;
|
|
else if (F->getReturnType()->isIntOrIntVectorTy() &&
|
|
Name.consume_front("_R")) {
|
|
// Return type is mangled after _R, e.g. _Z23__spirv_ImageRead_Rint2li
|
|
IsSigned = isMangledTypeSigned(Name[0]);
|
|
IsUnsigned = Name.starts_with("u");
|
|
}
|
|
} else if (Name.starts_with("ImageWrite")) {
|
|
IsSigned = (ArgSignedness[2] == ParamSignedness::Signed);
|
|
IsUnsigned = (ArgSignedness[2] == ParamSignedness::Unsigned);
|
|
}
|
|
|
|
if (IsSigned)
|
|
return ImageOperandsMask::ImageOperandsSignExtendMask;
|
|
if (IsUnsigned)
|
|
return ImageOperandsMask::ImageOperandsZeroExtendMask;
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace SPIRV {
|
|
|
|
static void foreachKernelArgMD(
|
|
MDNode *MD, SPIRVFunction *BF,
|
|
std::function<void(const std::string &Str, SPIRVFunctionParameter *BA)>
|
|
Func) {
|
|
assert(BF->getNumArguments() == MD->getNumOperands() &&
|
|
"Invalid kernel metadata: Number of metadata operands and kernel "
|
|
"arguments do not match");
|
|
for (unsigned I = 0, E = MD->getNumOperands(); I != E; ++I) {
|
|
SPIRVFunctionParameter *BA = BF->getArgument(I);
|
|
Func(getMDOperandAsString(MD, I).str(), BA);
|
|
}
|
|
}
|
|
|
|
static void foreachKernelArgMD(
|
|
MDNode *MD, SPIRVFunction *BF,
|
|
std::function<void(Metadata *MDOp, SPIRVFunctionParameter *BA)> Func) {
|
|
assert(BF->getNumArguments() == MD->getNumOperands() &&
|
|
"Invalid kernel metadata: Number of metadata operands and kernel "
|
|
"arguments do not match");
|
|
for (unsigned I = 0, E = MD->getNumOperands(); I != E; ++I) {
|
|
SPIRVFunctionParameter *BA = BF->getArgument(I);
|
|
Func(MD->getOperand(I), BA);
|
|
}
|
|
}
|
|
|
|
static SPIRVMemoryModelKind getMemoryModel(Module &M) {
|
|
auto *MemoryModelMD = M.getNamedMetadata(kSPIRVMD::MemoryModel);
|
|
if (MemoryModelMD && (MemoryModelMD->getNumOperands() > 0)) {
|
|
auto *Ref0 = MemoryModelMD->getOperand(0);
|
|
if (Ref0 && Ref0->getNumOperands() > 1) {
|
|
auto &&ModelOp = Ref0->getOperand(1);
|
|
auto *ModelCI = mdconst::dyn_extract<ConstantInt>(ModelOp);
|
|
if (ModelCI && (ModelCI->getValue().getActiveBits() <= 64)) {
|
|
auto Model = static_cast<SPIRVMemoryModelKind>(ModelCI->getZExtValue());
|
|
return Model;
|
|
}
|
|
}
|
|
}
|
|
return SPIRVMemoryModelKind::MemoryModelMax;
|
|
}
|
|
|
|
static void translateSEVDecoration(Attribute Sev, SPIRVValue *Val) {
|
|
assert(Sev.isStringAttribute() &&
|
|
Sev.getKindAsString() == kVCMetadata::VCSingleElementVector);
|
|
|
|
auto *Ty = Val->getType();
|
|
assert((Ty->isTypeBool() || Ty->isTypeFloat() || Ty->isTypeInt() ||
|
|
Ty->isTypePointer()) &&
|
|
"This decoration is valid only for Scalar or Pointer types");
|
|
|
|
if (Ty->isTypePointer()) {
|
|
SPIRVWord IndirectLevelsOnElement = 0;
|
|
Sev.getValueAsString().getAsInteger(0, IndirectLevelsOnElement);
|
|
Val->addDecorate(DecorationSingleElementVectorINTEL,
|
|
IndirectLevelsOnElement);
|
|
} else
|
|
Val->addDecorate(DecorationSingleElementVectorINTEL);
|
|
}
|
|
|
|
LLVMToSPIRVBase::LLVMToSPIRVBase(SPIRVModule *SMod)
|
|
: BuiltinCallHelper(ManglingRules::None), M(nullptr), Ctx(nullptr),
|
|
BM(SMod), SrcLang(0), SrcLangVer(0) {
|
|
DbgTran = std::make_unique<LLVMToSPIRVDbgTran>(nullptr, SMod, this);
|
|
}
|
|
|
|
LLVMToSPIRVBase::~LLVMToSPIRVBase() {
|
|
for (auto *I : UnboundInst)
|
|
I->deleteValue();
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::runLLVMToSPIRV(Module &Mod) {
|
|
M = &Mod;
|
|
initialize(Mod);
|
|
CG = std::make_unique<CallGraph>(Mod);
|
|
Ctx = &M->getContext();
|
|
DbgTran->setModule(M);
|
|
assert(BM && "SPIR-V module not initialized");
|
|
translate();
|
|
return true;
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::getTranslatedValue(const Value *V) const {
|
|
auto Loc = ValueMap.find(V);
|
|
if (Loc != ValueMap.end())
|
|
return Loc->second;
|
|
return nullptr;
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::isKernel(Function *F) {
|
|
if (F->getCallingConv() == CallingConv::SPIR_KERNEL)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::isBuiltinTransToInst(Function *F) {
|
|
StringRef DemangledName;
|
|
if (!oclIsBuiltin(F->getName(), DemangledName) &&
|
|
!isDecoratedSPIRVFunc(F, DemangledName))
|
|
return false;
|
|
SPIRVDBG(spvdbgs() << "CallInst: demangled name: " << DemangledName.str()
|
|
<< '\n');
|
|
return getSPIRVFuncOC(DemangledName) != OpNop;
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::isBuiltinTransToExtInst(
|
|
Function *F, SPIRVExtInstSetKind *ExtSet, SPIRVWord *ExtOp,
|
|
SmallVectorImpl<std::string> *Dec) {
|
|
StringRef DemangledName;
|
|
if (!oclIsBuiltin(F->getName(), DemangledName))
|
|
return false;
|
|
LLVM_DEBUG(dbgs() << "[oclIsBuiltinTransToExtInst] CallInst: demangled name: "
|
|
<< DemangledName << '\n');
|
|
StringRef S = DemangledName;
|
|
if (!S.starts_with(kSPIRVName::Prefix))
|
|
return false;
|
|
S = S.drop_front(strlen(kSPIRVName::Prefix));
|
|
auto Loc = S.find(kSPIRVPostfix::Divider);
|
|
auto ExtSetName = S.substr(0, Loc);
|
|
SPIRVExtInstSetKind Set = SPIRVEIS_Count;
|
|
if (!SPIRVExtSetShortNameMap::rfind(ExtSetName.str(), &Set))
|
|
return false;
|
|
assert((Set == SPIRVEIS_OpenCL || Set == BM->getDebugInfoEIS()) &&
|
|
"Unsupported extended instruction set");
|
|
|
|
auto ExtOpName = S.substr(Loc + 1);
|
|
auto Splited = ExtOpName.split(kSPIRVPostfix::ExtDivider);
|
|
OCLExtOpKind EOC;
|
|
if (!OCLExtOpMap::rfind(Splited.first.str(), &EOC))
|
|
return false;
|
|
|
|
if (ExtSet)
|
|
*ExtSet = Set;
|
|
if (ExtOp)
|
|
*ExtOp = EOC;
|
|
if (Dec) {
|
|
SmallVector<StringRef, 2> P;
|
|
Splited.second.split(P, kSPIRVPostfix::Divider);
|
|
for (auto &I : P)
|
|
Dec->push_back(I.str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool isUniformGroupOperation(Function *F) {
|
|
auto Name = F->getName();
|
|
if (Name.contains("GroupIMulKHR") || Name.contains("GroupFMulKHR") ||
|
|
Name.contains("GroupBitwiseAndKHR") ||
|
|
Name.contains("GroupBitwiseOrKHR") ||
|
|
Name.contains("GroupBitwiseXorKHR") ||
|
|
Name.contains("GroupLogicalAndKHR") ||
|
|
Name.contains("GroupLogicalOrKHR") || Name.contains("GroupLogicalXorKHR"))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool recursiveType(const StructType *ST, const Type *Ty) {
|
|
SmallPtrSet<const StructType *, 4> Seen;
|
|
|
|
std::function<bool(const Type *Ty)> Run = [&](const Type *Ty) {
|
|
if (auto *StructTy = dyn_cast<StructType>(Ty)) {
|
|
if (StructTy == ST)
|
|
return true;
|
|
|
|
if (Seen.count(StructTy))
|
|
return false;
|
|
|
|
Seen.insert(StructTy);
|
|
|
|
return find_if(StructTy->element_begin(), StructTy->element_end(), Run) !=
|
|
StructTy->element_end();
|
|
}
|
|
|
|
if (auto *ArrayTy = dyn_cast<ArrayType>(Ty))
|
|
return Run(ArrayTy->getArrayElementType());
|
|
|
|
return false;
|
|
};
|
|
|
|
return Run(Ty);
|
|
}
|
|
|
|
// Add decoration if needed
|
|
void addFPBuiltinDecoration(SPIRVModule *BM, Instruction *Inst,
|
|
SPIRVInstruction *I) {
|
|
bool AllowFPMaxError =
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fp_max_error);
|
|
|
|
auto *II = dyn_cast_or_null<IntrinsicInst>(Inst);
|
|
if (II && II->getCalledFunction()->getName().starts_with("llvm.fpbuiltin")) {
|
|
// Add a new decoration for llvm.builtin intrinsics, if needed
|
|
if (II->getAttributes().hasFnAttr("fpbuiltin-max-error")) {
|
|
BM->getErrorLog().checkError(AllowFPMaxError, SPIRVEC_RequiresExtension,
|
|
"SPV_INTEL_fp_max_error\n");
|
|
double F = 0.0;
|
|
II->getAttributes()
|
|
.getFnAttr("fpbuiltin-max-error")
|
|
.getValueAsString()
|
|
.getAsDouble(F);
|
|
I->addDecorate(DecorationFPMaxErrorDecorationINTEL,
|
|
convertFloatToSPIRVWord(F));
|
|
}
|
|
} else if (auto *MD = Inst->getMetadata("fpmath")) {
|
|
if (!AllowFPMaxError)
|
|
return;
|
|
auto *MDVal = mdconst::dyn_extract<ConstantFP>(MD->getOperand(0));
|
|
double ValAsDouble = MDVal->getValue().convertToFloat();
|
|
I->addDecorate(DecorationFPMaxErrorDecorationINTEL,
|
|
convertFloatToSPIRVWord(ValAsDouble));
|
|
}
|
|
}
|
|
|
|
SPIRVType *LLVMToSPIRVBase::transType(Type *T) {
|
|
LLVMToSPIRVTypeMap::iterator Loc = TypeMap.find(T);
|
|
if (Loc != TypeMap.end())
|
|
return Loc->second;
|
|
|
|
SPIRVDBG(dbgs() << "[transType] " << *T << '\n');
|
|
if (T->isVoidTy())
|
|
return mapType(T, BM->addVoidType());
|
|
|
|
if (T->isIntegerTy(1))
|
|
return mapType(T, BM->addBoolType());
|
|
|
|
if (T->isIntegerTy()) {
|
|
unsigned BitWidth = T->getIntegerBitWidth();
|
|
// SPIR-V 2.16.1. Universal Validation Rules: Scalar integer types can be
|
|
// parameterized only as 32 bit, plus any additional sizes enabled by
|
|
// capabilities.
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_arbitrary_precision_integers) ||
|
|
BM->getErrorLog().checkError(
|
|
(BitWidth == 4 &&
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_int4)) ||
|
|
BitWidth == 8 || BitWidth == 16 || BitWidth == 32 ||
|
|
BitWidth == 64,
|
|
SPIRVEC_InvalidBitWidth, std::to_string(BitWidth))) {
|
|
return mapType(T, BM->addIntegerType(T->getIntegerBitWidth()));
|
|
}
|
|
}
|
|
|
|
if (T->isBFloatTy()) {
|
|
BM->getErrorLog().checkError(
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_bfloat16),
|
|
SPIRVEC_RequiresExtension,
|
|
"SPV_KHR_bfloat16\n"
|
|
"NOTE: LLVM module contains bfloat type, translation of which "
|
|
"requires this extension");
|
|
return mapType(T, BM->addFloatType(16, FPEncodingBFloat16KHR));
|
|
}
|
|
|
|
if (T->isFloatingPointTy())
|
|
return mapType(T, BM->addFloatType(T->getPrimitiveSizeInBits()));
|
|
|
|
// A pointer to image or pipe type in LLVM is translated to a SPIRV
|
|
// (non-pointer) image or pipe type.
|
|
if (T->isPointerTy()) {
|
|
auto AddrSpc = T->getPointerAddressSpace();
|
|
auto *ET = Type::getInt8Ty(T->getContext());
|
|
return transPointerType(ET, AddrSpc);
|
|
}
|
|
|
|
if (auto *TPT = dyn_cast<TypedPointerType>(T)) {
|
|
return transPointerType(TPT->getElementType(), TPT->getAddressSpace());
|
|
}
|
|
|
|
if (auto *VecTy = dyn_cast<FixedVectorType>(T)) {
|
|
unsigned NumElements = VecTy->getNumElements();
|
|
bool IsNonStandardCount =
|
|
!(NumElements == 2 || NumElements == 3 || NumElements == 4 ||
|
|
NumElements == 8 || NumElements == 16);
|
|
if (IsNonStandardCount &&
|
|
!BM->isAllowedToUseExtension(ExtensionID::SPV_EXT_long_vector) &&
|
|
!BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute)) {
|
|
BM->getErrorLog().checkError(
|
|
false, SPIRVEC_RequiresExtension,
|
|
"SPV_EXT_long_vector or SPV_INTEL_vector_compute\n"
|
|
"NOTE: LLVM module contains a vector with an unsupported number of "
|
|
"components, translation of which requires one of these extensions");
|
|
return nullptr;
|
|
}
|
|
if (VecTy->getElementType()->isPointerTy() ||
|
|
isa<TypedPointerType>(VecTy->getElementType())) {
|
|
// SPV_INTEL_masked_gather_scatter extension changes 2.16.1. Universal
|
|
// Validation Rules:
|
|
// Vector types must be parameterized only with numerical types,
|
|
// [Physical Pointer Type] types or the [OpTypeBool] type.
|
|
// Without it vector of pointers is not allowed in SPIR-V.
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_masked_gather_scatter)) {
|
|
BM->getErrorLog().checkError(
|
|
false, SPIRVEC_RequiresExtension,
|
|
"SPV_INTEL_masked_gather_scatter\n"
|
|
"NOTE: LLVM module contains vector of pointers, translation "
|
|
"of which requires this extension");
|
|
return nullptr;
|
|
}
|
|
BM->addExtension(ExtensionID::SPV_INTEL_masked_gather_scatter);
|
|
BM->addCapability(internal::CapabilityMaskedGatherScatterINTEL);
|
|
}
|
|
return mapType(T, BM->addVectorType(transType(VecTy->getElementType()),
|
|
VecTy->getNumElements()));
|
|
}
|
|
|
|
if (T->isArrayTy()) {
|
|
// SPIR-V 1.3 s3.32.6: Length is the number of elements in the array.
|
|
// It must be at least 1.
|
|
const auto ArraySize =
|
|
T->getArrayNumElements() ? T->getArrayNumElements() : 1;
|
|
Type *ElTy = T->getArrayElementType();
|
|
SPIRVType *TransType = BM->addArrayType(
|
|
transType(ElTy),
|
|
static_cast<SPIRVConstant *>(transValue(
|
|
ConstantInt::get(getSizetType(), ArraySize, false), nullptr)));
|
|
mapType(T, TransType);
|
|
if (ElTy->isPointerTy()) {
|
|
Type *ArrTy =
|
|
ArrayType::get(TypedPointerType::get(Type::getInt8Ty(*Ctx),
|
|
ElTy->getPointerAddressSpace()),
|
|
ArraySize);
|
|
LLVMToSPIRVTypeMap::iterator Loc = TypeMap.find(ArrTy);
|
|
if (Loc == TypeMap.end())
|
|
mapType(ArrTy, TransType);
|
|
}
|
|
return TransType;
|
|
}
|
|
|
|
if (T->isStructTy() && !T->isSized()) {
|
|
auto *ST = dyn_cast<StructType>(T);
|
|
(void)ST; // Silence warning
|
|
assert(!ST->getName().starts_with(kSPR2TypeName::PipeRO));
|
|
assert(!ST->getName().starts_with(kSPR2TypeName::PipeWO));
|
|
assert(!ST->getName().starts_with(kSPR2TypeName::ImagePrefix));
|
|
return mapType(T, BM->addOpaqueType(T->getStructName().str()));
|
|
}
|
|
|
|
if (auto *ST = dyn_cast<StructType>(T)) {
|
|
assert(ST->isSized());
|
|
|
|
StringRef Name;
|
|
if (ST->hasName())
|
|
Name = ST->getName();
|
|
|
|
if (Name == getSPIRVTypeName(kSPIRVTypeName::ConstantSampler))
|
|
return transSPIRVOpaqueType("spirv.Sampler", SPIRAS_Constant);
|
|
if (Name == getSPIRVTypeName(kSPIRVTypeName::ConstantPipeStorage))
|
|
return transSPIRVOpaqueType("spirv.PipeStorage", SPIRAS_Constant);
|
|
|
|
constexpr size_t MaxNumElements = MaxWordCount - SPIRVTypeStruct::FixedWC;
|
|
const size_t NumElements = ST->getNumElements();
|
|
size_t SPIRVStructNumElements = NumElements;
|
|
// In case number of elements is greater than maximum WordCount and
|
|
// SPV_INTEL_long_composites is not enabled, the error will be
|
|
// emitted by validate functionality of SPIRVTypeStruct class.
|
|
if (NumElements > MaxNumElements &&
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_long_composites)) {
|
|
SPIRVStructNumElements = MaxNumElements;
|
|
}
|
|
|
|
auto *Struct = BM->openStructType(SPIRVStructNumElements, Name.str());
|
|
mapType(T, Struct);
|
|
|
|
if (NumElements > MaxNumElements &&
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_long_composites)) {
|
|
uint64_t NumOfContinuedInstructions = NumElements / MaxNumElements - 1;
|
|
for (uint64_t J = 0; J < NumOfContinuedInstructions; J++) {
|
|
auto *Continued = BM->addTypeStructContinuedINTEL(MaxNumElements);
|
|
Struct->addContinuedInstruction(
|
|
static_cast<SPIRVTypeStruct::ContinuedInstType>(Continued));
|
|
}
|
|
uint64_t Remains = NumElements % MaxNumElements;
|
|
if (Remains) {
|
|
auto *Continued = BM->addTypeStructContinuedINTEL(Remains);
|
|
Struct->addContinuedInstruction(
|
|
static_cast<SPIRVTypeStruct::ContinuedInstType>(Continued));
|
|
}
|
|
}
|
|
|
|
SmallVector<unsigned, 4> ForwardRefs;
|
|
|
|
for (unsigned I = 0, E = T->getStructNumElements(); I != E; ++I) {
|
|
auto *ElemTy = ST->getElementType(I);
|
|
if ((isa<StructType>(ElemTy) || isa<ArrayType>(ElemTy) ||
|
|
isa<VectorType>(ElemTy) || isa<PointerType>(ElemTy)) &&
|
|
recursiveType(ST, ElemTy))
|
|
ForwardRefs.push_back(I);
|
|
else
|
|
Struct->setMemberType(I, transType(ST->getElementType(I)));
|
|
}
|
|
|
|
BM->closeStructType(Struct, ST->isPacked());
|
|
|
|
for (auto I : ForwardRefs)
|
|
Struct->setMemberType(I, transType(ST->getElementType(I)));
|
|
|
|
return Struct;
|
|
}
|
|
|
|
if (FunctionType *FT = dyn_cast<FunctionType>(T)) {
|
|
SPIRVType *RT = transType(FT->getReturnType());
|
|
std::vector<SPIRVType *> PT;
|
|
for (FunctionType::param_iterator I = FT->param_begin(),
|
|
E = FT->param_end();
|
|
I != E; ++I)
|
|
PT.push_back(transType(*I));
|
|
return mapType(T, getSPIRVFunctionType(RT, PT));
|
|
}
|
|
|
|
if (auto *TargetTy = dyn_cast<TargetExtType>(T)) {
|
|
StringRef Name = TargetTy->getName();
|
|
if (Name.consume_front(kSPIRVTypeName::PrefixAndDelim)) {
|
|
auto Opcode = SPIRVOpaqueTypeOpCodeMap::map(Name.str());
|
|
auto CastAccess = [](unsigned Val) {
|
|
return static_cast<SPIRVAccessQualifierKind>(Val);
|
|
};
|
|
switch (static_cast<size_t>(Opcode)) {
|
|
case OpTypePipe: {
|
|
auto *PipeT = BM->addPipeType();
|
|
PipeT->setPipeAcessQualifier(CastAccess(TargetTy->getIntParameter(0)));
|
|
return mapType(T, PipeT);
|
|
}
|
|
case OpTypeImage: {
|
|
auto *SampledTy = transType(TargetTy->getTypeParameter(0));
|
|
ArrayRef<unsigned> Ops = TargetTy->int_params();
|
|
SPIRVTypeImageDescriptor Desc(static_cast<SPIRVImageDimKind>(Ops[0]),
|
|
Ops[1], Ops[2], Ops[3], Ops[4], Ops[5]);
|
|
return mapType(T,
|
|
BM->addImageType(SampledTy, Desc, CastAccess(Ops[6])));
|
|
}
|
|
case OpTypeSampledImage: {
|
|
auto *ImageTy = static_cast<SPIRVTypeImage *>(transType(adjustImageType(
|
|
T, kSPIRVTypeName::SampledImg, kSPIRVTypeName::Image)));
|
|
return mapType(T, BM->addSampledImageType(ImageTy));
|
|
}
|
|
case OpTypeVmeImageINTEL: {
|
|
auto *ImageTy = static_cast<SPIRVTypeImage *>(transType(adjustImageType(
|
|
T, kSPIRVTypeName::VmeImageINTEL, kSPIRVTypeName::Image)));
|
|
return mapType(T, BM->addVmeImageINTELType(ImageTy));
|
|
}
|
|
case OpTypeQueue:
|
|
return mapType(T, BM->addQueueType());
|
|
case OpTypeDeviceEvent:
|
|
return mapType(T, BM->addDeviceEventType());
|
|
case OpTypeBufferSurfaceINTEL: {
|
|
ArrayRef<unsigned> Ops = TargetTy->int_params();
|
|
return mapType(T, BM->addBufferSurfaceINTELType(CastAccess(Ops[0])));
|
|
}
|
|
case internal::OpTypeJointMatrixINTEL: {
|
|
// The expected representation is:
|
|
// target("spirv.JointMatrixINTEL", %element_type, %rows%, %cols%,
|
|
// %layout%, %scope%, %use%,
|
|
// (optional) %element_type_interpretation%)
|
|
auto *ElemTy = transType(TargetTy->getTypeParameter(0));
|
|
ArrayRef<unsigned> Ops = TargetTy->int_params();
|
|
std::vector<SPIRVValue *> Args;
|
|
for (const auto &Op : Ops)
|
|
Args.emplace_back(transConstant(getUInt32(M, Op)));
|
|
return mapType(T, BM->addJointMatrixINTELType(ElemTy, Args));
|
|
}
|
|
case OpTypeCooperativeMatrixKHR: {
|
|
// The expected representation is:
|
|
// target("spirv.CooperativeMatrixKHR", %element_type, %scope%, %rows%,
|
|
// %cols%, %use%)
|
|
auto *ElemTy = transType(TargetTy->getTypeParameter(0));
|
|
ArrayRef<unsigned> Ops = TargetTy->int_params();
|
|
std::vector<SPIRVValue *> Args;
|
|
for (const auto &Op : Ops)
|
|
Args.emplace_back(transConstant(getUInt32(M, Op)));
|
|
return mapType(T, BM->addCooperativeMatrixKHRType(ElemTy, Args));
|
|
}
|
|
case internal::OpTypeTaskSequenceINTEL:
|
|
return mapType(T, BM->addTaskSequenceINTELType());
|
|
default:
|
|
if (isSubgroupAvcINTELTypeOpCode(Opcode))
|
|
return mapType(T, BM->addSubgroupAvcINTELType(Opcode));
|
|
return mapType(T, BM->addOpaqueGenericType(Opcode));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (T->isTokenTy()) {
|
|
BM->getErrorLog().checkError(
|
|
false, SPIRVEC_InvalidModule,
|
|
"LLVM module contains token type, which doesn't have a counterpart in "
|
|
"SPIR-V");
|
|
return nullptr;
|
|
}
|
|
|
|
llvm_unreachable("Not implemented!");
|
|
return 0;
|
|
}
|
|
|
|
SPIRVType *LLVMToSPIRVBase::transPointerType(Type *ET, unsigned AddrSpc) {
|
|
Type *T = PointerType::get(ET->getContext(), AddrSpc);
|
|
if (ET->isFunctionTy() &&
|
|
!BM->checkExtension(ExtensionID::SPV_INTEL_function_pointers,
|
|
SPIRVEC_FunctionPointers, toString(T)))
|
|
return nullptr;
|
|
|
|
std::string TypeKey = (Twine((uintptr_t)ET) + Twine(AddrSpc)).str();
|
|
auto Loc = PointeeTypeMap.find(TypeKey);
|
|
if (Loc != PointeeTypeMap.end())
|
|
return Loc->second;
|
|
|
|
// A pointer to image or pipe type in LLVM is translated to a SPIRV
|
|
// (non-pointer) image or pipe type.
|
|
auto *ST = dyn_cast<StructType>(ET);
|
|
// Lower global_device and global_host address spaces that were added in
|
|
// SYCL as part of SYCL_INTEL_usm_address_spaces extension to just global
|
|
// address space if device doesn't support SPV_INTEL_usm_storage_classes
|
|
// extension
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_usm_storage_classes) &&
|
|
((AddrSpc == SPIRAS_GlobalDevice) || (AddrSpc == SPIRAS_GlobalHost))) {
|
|
return transPointerType(ET, SPIRAS_Global);
|
|
}
|
|
// Lower function pointer address space to private if
|
|
// spirv-emit-function-ptr-addr-space is not passed
|
|
if (AddrSpc == SPIRAS_CodeSectionINTEL &&
|
|
!BM->shouldEmitFunctionPtrAddrSpace())
|
|
return transPointerType(ET, SPIRAS_Private);
|
|
if (ST && !ST->isSized()) {
|
|
Op OpCode;
|
|
StringRef STName = ST->getName();
|
|
// Workaround for non-conformant SPIR binary
|
|
if (STName == "struct._event_t") {
|
|
STName = kSPR2TypeName::Event;
|
|
ST->setName(STName);
|
|
}
|
|
|
|
std::pair<StringRef, unsigned> Key = {STName, AddrSpc};
|
|
if (auto *MappedTy = OpaqueStructMap.lookup(Key))
|
|
return MappedTy;
|
|
|
|
auto SaveType = [&](SPIRVType *MappedTy) {
|
|
OpaqueStructMap[Key] = MappedTy;
|
|
PointeeTypeMap[TypeKey] = MappedTy;
|
|
return MappedTy;
|
|
};
|
|
|
|
if (STName.starts_with(kSPR2TypeName::PipeRO) ||
|
|
STName.starts_with(kSPR2TypeName::PipeWO)) {
|
|
auto *PipeT = BM->addPipeType();
|
|
PipeT->setPipeAcessQualifier(STName.starts_with(kSPR2TypeName::PipeRO)
|
|
? AccessQualifierReadOnly
|
|
: AccessQualifierWriteOnly);
|
|
return SaveType(PipeT);
|
|
}
|
|
if (STName.starts_with(kSPR2TypeName::ImagePrefix)) {
|
|
assert(AddrSpc == SPIRAS_Global);
|
|
Type *ImageTy =
|
|
adjustImageType(TypedPointerType::get(ST, AddrSpc),
|
|
kSPIRVTypeName::Image, kSPIRVTypeName::Image);
|
|
return SaveType(transType(ImageTy));
|
|
}
|
|
if (STName == kSPR2TypeName::Sampler)
|
|
return SaveType(transType(getSPIRVType(OpTypeSampler)));
|
|
if (STName.starts_with(kSPIRVTypeName::PrefixAndDelim))
|
|
return transSPIRVOpaqueType(STName, AddrSpc);
|
|
|
|
if (STName.starts_with(kOCLSubgroupsAVCIntel::TypePrefix))
|
|
return SaveType(BM->addSubgroupAvcINTELType(
|
|
OCLSubgroupINTELTypeOpCodeMap::map(ST->getName().str())));
|
|
|
|
if (OCLOpaqueTypeOpCodeMap::find(STName.str(), &OpCode)) {
|
|
Type *RealType = getSPIRVType(OpCode);
|
|
return SaveType(transType(RealType));
|
|
}
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute)) {
|
|
if (STName.starts_with(kVCType::VCBufferSurface)) {
|
|
// VCBufferSurface always have Access Qualifier
|
|
auto Access = getAccessQualifier(STName);
|
|
return SaveType(BM->addBufferSurfaceINTELType(Access));
|
|
}
|
|
}
|
|
|
|
if (ST->isOpaque()) {
|
|
return SaveType(BM->addPointerType(
|
|
SPIRSPIRVAddrSpaceMap::map(static_cast<SPIRAddressSpace>(AddrSpc)),
|
|
transType(ET)));
|
|
}
|
|
} else {
|
|
// ET, as a recursive type, may contain exactly the same pointer T, so it
|
|
// may happen that after translation of ET we already have translated T,
|
|
// added the translated pointer to the SPIR-V module and mapped T to this
|
|
// pointer. Now we have to check PointeeTypeMap again.
|
|
auto Loc = PointeeTypeMap.find(TypeKey);
|
|
if (Loc != PointeeTypeMap.end()) {
|
|
return Loc->second;
|
|
}
|
|
|
|
SPIRVType *ElementType = nullptr;
|
|
SPIRVType *TranslatedTy = nullptr;
|
|
if (ET->isPointerTy() &&
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers)) {
|
|
TranslatedTy = BM->addPointerType(
|
|
SPIRSPIRVAddrSpaceMap::map(static_cast<SPIRAddressSpace>(AddrSpc)),
|
|
nullptr);
|
|
} else {
|
|
ElementType = transType(ET);
|
|
TranslatedTy = transPointerType(ElementType, AddrSpc);
|
|
}
|
|
PointeeTypeMap[TypeKey] = TranslatedTy;
|
|
return TranslatedTy;
|
|
}
|
|
|
|
llvm_unreachable("Not implemented!");
|
|
return nullptr;
|
|
}
|
|
|
|
SPIRVType *LLVMToSPIRVBase::transPointerType(SPIRVType *ET, unsigned AddrSpc) {
|
|
std::string TypeKey = (Twine((uintptr_t)ET) + Twine(AddrSpc)).str();
|
|
auto Loc = PointeeTypeMap.find(TypeKey);
|
|
if (Loc != PointeeTypeMap.end())
|
|
return Loc->second;
|
|
|
|
SPIRVType *TranslatedTy = nullptr;
|
|
if (AddrSpc == SPIRAS_CodeSectionINTEL &&
|
|
!BM->shouldEmitFunctionPtrAddrSpace())
|
|
return transPointerType(ET, SPIRAS_Private);
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers) &&
|
|
!(ET->isTypeArray() || ET->isTypeVector() || ET->isSPIRVOpaqueType())) {
|
|
TranslatedTy = BM->addPointerType(
|
|
SPIRSPIRVAddrSpaceMap::map(static_cast<SPIRAddressSpace>(AddrSpc)),
|
|
nullptr);
|
|
} else {
|
|
TranslatedTy = BM->addPointerType(
|
|
SPIRSPIRVAddrSpaceMap::map(static_cast<SPIRAddressSpace>(AddrSpc)), ET);
|
|
}
|
|
PointeeTypeMap[TypeKey] = TranslatedTy;
|
|
return TranslatedTy;
|
|
}
|
|
|
|
SPIRVType *LLVMToSPIRVBase::transSPIRVOpaqueType(StringRef STName,
|
|
unsigned AddrSpace) {
|
|
std::pair<StringRef, unsigned> Key = {STName, AddrSpace};
|
|
if (auto *MappedTy = OpaqueStructMap.lookup(Key))
|
|
return MappedTy;
|
|
|
|
auto SaveType = [&](SPIRVType *MappedTy) {
|
|
OpaqueStructMap[Key] = MappedTy;
|
|
return MappedTy;
|
|
};
|
|
StructType *ST = StructType::getTypeByName(M->getContext(), STName);
|
|
|
|
assert(STName.starts_with(kSPIRVTypeName::PrefixAndDelim) &&
|
|
"Invalid SPIR-V opaque type name");
|
|
SmallVector<std::string, 8> Postfixes;
|
|
auto TN = decodeSPIRVTypeName(STName, Postfixes);
|
|
if (TN == kSPIRVTypeName::Pipe) {
|
|
assert(AddrSpace == SPIRAS_Global);
|
|
assert(Postfixes.size() == 1 && "Invalid pipe type ops");
|
|
auto *PipeT = BM->addPipeType();
|
|
PipeT->setPipeAcessQualifier(
|
|
static_cast<spv::AccessQualifier>(atoi(Postfixes[0].c_str())));
|
|
return SaveType(PipeT);
|
|
} else if (TN == kSPIRVTypeName::Image) {
|
|
assert(AddrSpace == SPIRAS_Global);
|
|
// The sampled type needs to be translated through LLVM type to guarantee
|
|
// uniqueness.
|
|
auto *SampledT = transType(
|
|
getLLVMTypeForSPIRVImageSampledTypePostfix(Postfixes[0], *Ctx));
|
|
SmallVector<int, 7> Ops;
|
|
for (unsigned I = 1; I < 8; ++I)
|
|
Ops.push_back(atoi(Postfixes[I].c_str()));
|
|
SPIRVTypeImageDescriptor Desc(static_cast<SPIRVImageDimKind>(Ops[0]),
|
|
Ops[1], Ops[2], Ops[3], Ops[4], Ops[5]);
|
|
return SaveType(BM->addImageType(
|
|
SampledT, Desc, static_cast<spv::AccessQualifier>(Ops[6])));
|
|
} else if (TN == kSPIRVTypeName::SampledImg) {
|
|
return SaveType(BM->addSampledImageType(static_cast<SPIRVTypeImage *>(
|
|
transType(adjustImageType(TypedPointerType::get(ST, SPIRAS_Global),
|
|
kSPIRVTypeName::SampledImg,
|
|
kSPIRVTypeName::Image)))));
|
|
} else if (TN == kSPIRVTypeName::VmeImageINTEL) {
|
|
// This type is the same as SampledImageType, but consumed by Subgroup AVC
|
|
// Intel extension instructions.
|
|
return SaveType(BM->addVmeImageINTELType(static_cast<SPIRVTypeImage *>(
|
|
transType(adjustImageType(TypedPointerType::get(ST, SPIRAS_Global),
|
|
kSPIRVTypeName::VmeImageINTEL,
|
|
kSPIRVTypeName::Image)))));
|
|
} else if (TN == kSPIRVTypeName::Sampler)
|
|
return SaveType(BM->addSamplerType());
|
|
else if (TN == kSPIRVTypeName::DeviceEvent)
|
|
return SaveType(BM->addDeviceEventType());
|
|
else if (TN == kSPIRVTypeName::Queue)
|
|
return SaveType(BM->addQueueType());
|
|
else if (TN == kSPIRVTypeName::PipeStorage)
|
|
return SaveType(BM->addPipeStorageType());
|
|
else if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute) &&
|
|
TN == kSPIRVTypeName::BufferSurfaceINTEL) {
|
|
auto Access = getAccessQualifier(STName);
|
|
return SaveType(BM->addBufferSurfaceINTELType(Access));
|
|
} else
|
|
return SaveType(
|
|
BM->addOpaqueGenericType(SPIRVOpaqueTypeOpCodeMap::map(TN)));
|
|
}
|
|
|
|
SPIRVType *LLVMToSPIRVBase::transScavengedType(Value *V) {
|
|
if (auto *F = dyn_cast<Function>(V)) {
|
|
FunctionType *FnTy = Scavenger->getFunctionType(F);
|
|
// VarArg functions other than printf are not supported in SPIR-V. None of
|
|
// printf variants can actually reach this point because they're skipped in
|
|
// 'LLVMToSPIRVBase::translate()'. Specifically,
|
|
// 'isBuiltinTransToExtInst(&F)' returns true for printf and its variants,
|
|
// and so they are never added to 'Decls' and 'Defs'. Therefore, it's safe
|
|
// to only check here if we're dealing with a variadic function to report an
|
|
// error. To be on the safe side, an assertion is added to check printf
|
|
// never reaches this point.
|
|
assert(F->getName() != "printf");
|
|
BM->getErrorLog().checkError(!FnTy->isVarArg(),
|
|
SPIRVEC_UnsupportedVarArgFunction);
|
|
|
|
SPIRVType *RT = transType(FnTy->getReturnType());
|
|
std::vector<SPIRVType *> PT;
|
|
for (Argument &Arg : F->args()) {
|
|
assert(OCLTypeToSPIRVPtr);
|
|
Type *Ty = OCLTypeToSPIRVPtr->getAdaptedArgumentType(F, Arg.getArgNo());
|
|
if (!Ty) {
|
|
Ty = FnTy->getParamType(Arg.getArgNo());
|
|
}
|
|
PT.push_back(transType(Ty));
|
|
}
|
|
|
|
return getSPIRVFunctionType(RT, PT);
|
|
}
|
|
|
|
return transType(Scavenger->getScavengedType(V));
|
|
}
|
|
|
|
SPIRVType *
|
|
LLVMToSPIRVBase::getSPIRVFunctionType(SPIRVType *RT,
|
|
const std::vector<SPIRVType *> &Args) {
|
|
// Come up with a unique string identifier for the arguments. This is a hacky
|
|
// way of doing so, but it works.
|
|
std::string TypeKey;
|
|
llvm::raw_string_ostream TKS(TypeKey);
|
|
TKS << (uintptr_t)RT << ",";
|
|
for (SPIRVType *ArgTy : Args) {
|
|
TKS << (uintptr_t)ArgTy << ",";
|
|
}
|
|
|
|
// Create a SPIRVType for the function type. Since SPIRVModule doesn't do
|
|
// any type uniquing for SPIRVType, we have to do it ourself.
|
|
TKS.flush();
|
|
auto It = PointeeTypeMap.find(TypeKey);
|
|
if (It == PointeeTypeMap.end())
|
|
It = PointeeTypeMap.insert({TypeKey, BM->addFunctionType(RT, Args)}).first;
|
|
return It->second;
|
|
}
|
|
|
|
SPIRVFunction *LLVMToSPIRVBase::transFunctionDecl(Function *F) {
|
|
if (auto *BF = getTranslatedValue(F))
|
|
return static_cast<SPIRVFunction *>(BF);
|
|
|
|
if (F->isIntrinsic() && (!BM->isSPIRVAllowUnknownIntrinsicsEnabled() ||
|
|
isKnownIntrinsic(F->getIntrinsicID()))) {
|
|
// We should not translate LLVM intrinsics as a function
|
|
assert(none_of(F->users(),
|
|
[this](User *U) { return getTranslatedValue(U); }) &&
|
|
"LLVM intrinsics shouldn't be called in SPIRV");
|
|
return nullptr;
|
|
}
|
|
|
|
// Don't translate FP conversion translator builtins as function declarations.
|
|
auto MangledName = F->getName();
|
|
StringRef DemangledName;
|
|
if (isInternalSPIRVBuiltin(MangledName, DemangledName)) {
|
|
if (SPIRV::FPConvertToEncodingMap::find(DemangledName)) {
|
|
// Create an early exit here if none of the extensions are enabled.
|
|
// Proper checks for the required extensions will be done during TypeFloat
|
|
// generation.
|
|
if (!BM->isAllowedToUseExtension(ExtensionID::SPV_EXT_float8) &&
|
|
!BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_int4) &&
|
|
!BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_float4) &&
|
|
!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_EXT_ocp_microscaling_types)) {
|
|
std::string ErrorStr =
|
|
"One of the following extensions: "
|
|
"SPV_EXT_float8, SPV_EXT_ocp_microscaling_types, "
|
|
"SPV_INTEL_float4, SPV_INTEL_int4 should be enabled to process "
|
|
"conversion builtins";
|
|
getErrorLog().checkError(false, SPIRVEC_RequiresExtension, F, ErrorStr);
|
|
}
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
SPIRVTypeFunction *BFT =
|
|
static_cast<SPIRVTypeFunction *>(transScavengedType(F));
|
|
SPIRVFunction *BF =
|
|
static_cast<SPIRVFunction *>(mapValue(F, BM->addFunction(BFT)));
|
|
BF->setFunctionControlMask(transFunctionControlMask(F));
|
|
if (F->hasName()) {
|
|
if (isKernel(F)) {
|
|
/* strip the prefix as the runtime will be looking for this name */
|
|
std::string Prefix = kSPIRVName::EntrypointPrefix;
|
|
std::string Name = F->getName().str();
|
|
BM->setName(BF, Name.substr(Prefix.size()));
|
|
} else {
|
|
if (isUniformGroupOperation(F))
|
|
BM->getErrorLog().checkError(
|
|
BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_KHR_uniform_group_instructions),
|
|
SPIRVEC_RequiresExtension, "SPV_KHR_uniform_group_instructions\n");
|
|
BM->setName(BF, F->getName().str());
|
|
}
|
|
}
|
|
if (!isKernel(F) && F->getLinkage() != GlobalValue::InternalLinkage)
|
|
BF->setLinkageType(transLinkageType(F));
|
|
|
|
// Translate OpenCL/SYCL buffer_location metadata if it's attached to the
|
|
// translated function declaration
|
|
MDNode *BufferLocation = nullptr;
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_buffer_location))
|
|
BufferLocation = F->getMetadata("kernel_arg_buffer_location");
|
|
|
|
// Translate runtime_aligned metadata if it's attached to the translated
|
|
// function declaration
|
|
MDNode *RuntimeAligned = nullptr;
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_runtime_aligned))
|
|
RuntimeAligned = F->getMetadata("kernel_arg_runtime_aligned");
|
|
|
|
auto Attrs = F->getAttributes();
|
|
|
|
for (Function::arg_iterator I = F->arg_begin(), E = F->arg_end(); I != E;
|
|
++I) {
|
|
auto ArgNo = I->getArgNo();
|
|
SPIRVFunctionParameter *BA = BF->getArgument(ArgNo);
|
|
if (I->hasName())
|
|
BM->setName(BA, I->getName().str());
|
|
if (I->hasByValAttr())
|
|
BA->addAttr(FunctionParameterAttributeByVal);
|
|
if (I->hasNoAliasAttr())
|
|
BA->addAttr(FunctionParameterAttributeNoAlias);
|
|
if (I->hasNoCaptureAttr())
|
|
BA->addAttr(FunctionParameterAttributeNoCapture);
|
|
if (I->hasStructRetAttr())
|
|
BA->addAttr(FunctionParameterAttributeSret);
|
|
if (Attrs.hasParamAttr(ArgNo, Attribute::ReadOnly))
|
|
BA->addAttr(FunctionParameterAttributeNoWrite);
|
|
if (Attrs.hasParamAttr(ArgNo, Attribute::ReadNone))
|
|
BA->addAttr(FunctionParameterAttributeNoReadWrite);
|
|
if (Attrs.hasParamAttr(ArgNo, Attribute::ZExt))
|
|
BA->addAttr(FunctionParameterAttributeZext);
|
|
if (Attrs.hasParamAttr(ArgNo, Attribute::SExt))
|
|
BA->addAttr(FunctionParameterAttributeSext);
|
|
if (Attrs.hasParamAttr(ArgNo, Attribute::Alignment)) {
|
|
SPIRVWord AlignmentBytes = Attrs.getParamAttr(ArgNo, Attribute::Alignment)
|
|
.getAlignment()
|
|
.valueOrOne()
|
|
.value();
|
|
BA->setAlignment(AlignmentBytes);
|
|
}
|
|
if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_1) &&
|
|
Attrs.hasParamAttr(ArgNo, Attribute::Dereferenceable))
|
|
BA->addDecorate(DecorationMaxByteOffset,
|
|
Attrs.getParamAttr(ArgNo, Attribute::Dereferenceable)
|
|
.getDereferenceableBytes());
|
|
if (BufferLocation && I->getType()->isPointerTy()) {
|
|
// Order of integer numbers in MD node follows the order of function
|
|
// parameters on which we shall attach the appropriate decoration. Add
|
|
// decoration only if MD value is not negative.
|
|
int LocID = -1;
|
|
if (!isa<MDString>(BufferLocation->getOperand(ArgNo)) &&
|
|
!isa<MDNode>(BufferLocation->getOperand(ArgNo)))
|
|
LocID = getMDOperandAsInt(BufferLocation, ArgNo);
|
|
if (LocID >= 0)
|
|
BA->addDecorate(DecorationBufferLocationINTEL, LocID);
|
|
}
|
|
if (RuntimeAligned && I->getType()->isPointerTy()) {
|
|
// Order of integer numbers in MD node follows the order of function
|
|
// parameters on which we shall attach the appropriate decoration. Add
|
|
// decoration only if MD value is 1.
|
|
int IsRuntimeAligned = 0;
|
|
if (!isa<MDString>(RuntimeAligned->getOperand(ArgNo)) &&
|
|
!isa<MDNode>(RuntimeAligned->getOperand(ArgNo)))
|
|
IsRuntimeAligned = getMDOperandAsInt(RuntimeAligned, ArgNo);
|
|
if (IsRuntimeAligned == 1) {
|
|
// TODO: to replace non-conformant to the spec decoration generation
|
|
// with:
|
|
// BM->addExtension(ExtensionID::SPV_INTEL_runtime_aligned);
|
|
// BM->addCapability(CapabilityRuntimeAlignedAttributeINTEL);
|
|
// BA->addAttr(FunctionParameterAttributeRuntimeAlignedINTEL);
|
|
BA->addDecorate(internal::DecorationRuntimeAlignedINTEL,
|
|
IsRuntimeAligned);
|
|
}
|
|
}
|
|
}
|
|
if (Attrs.hasRetAttr(Attribute::ZExt))
|
|
BF->addDecorate(DecorationFuncParamAttr, FunctionParameterAttributeZext);
|
|
if (Attrs.hasRetAttr(Attribute::SExt))
|
|
BF->addDecorate(DecorationFuncParamAttr, FunctionParameterAttributeSext);
|
|
if (Attrs.hasFnAttr("referenced-indirectly")) {
|
|
assert(!isKernel(F) &&
|
|
"kernel function was marked as referenced-indirectly");
|
|
BF->addDecorate(DecorationReferencedIndirectlyINTEL);
|
|
}
|
|
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute))
|
|
transVectorComputeMetadata(F);
|
|
|
|
transFPGAFunctionMetadata(BF, F);
|
|
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_maximum_registers))
|
|
transFunctionMetadataAsExecutionMode(BF, F);
|
|
else
|
|
transFunctionMetadataAsUserSemanticDecoration(BF, F);
|
|
|
|
transAuxDataInst(BF, F);
|
|
|
|
SPIRVDBG(dbgs() << "[transFunction] " << *F << " => ";
|
|
spvdbgs() << *BF << '\n';)
|
|
return BF;
|
|
}
|
|
|
|
void LLVMToSPIRVBase::transVectorComputeMetadata(Function *F) {
|
|
using namespace VectorComputeUtil;
|
|
if (!BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute))
|
|
return;
|
|
auto *BF = static_cast<SPIRVFunction *>(getTranslatedValue(F));
|
|
assert(BF && "The SPIRVFunction pointer shouldn't be nullptr");
|
|
auto Attrs = F->getAttributes();
|
|
|
|
if (Attrs.hasFnAttr(kVCMetadata::VCStackCall))
|
|
BF->addDecorate(DecorationStackCallINTEL);
|
|
if (Attrs.hasFnAttr(kVCMetadata::VCFunction))
|
|
BF->addDecorate(DecorationVectorComputeFunctionINTEL);
|
|
|
|
if (Attrs.hasFnAttr(kVCMetadata::VCSIMTCall)) {
|
|
SPIRVWord SIMTMode = 0;
|
|
Attrs.getFnAttr(kVCMetadata::VCSIMTCall)
|
|
.getValueAsString()
|
|
.getAsInteger(0, SIMTMode);
|
|
BF->addDecorate(DecorationSIMTCallINTEL, SIMTMode);
|
|
}
|
|
|
|
if (Attrs.hasRetAttr(kVCMetadata::VCSingleElementVector))
|
|
translateSEVDecoration(
|
|
Attrs.getAttributeAtIndex(AttributeList::ReturnIndex,
|
|
kVCMetadata::VCSingleElementVector),
|
|
BF);
|
|
|
|
for (Function::arg_iterator I = F->arg_begin(), E = F->arg_end(); I != E;
|
|
++I) {
|
|
auto ArgNo = I->getArgNo();
|
|
SPIRVFunctionParameter *BA = BF->getArgument(ArgNo);
|
|
if (Attrs.hasParamAttr(ArgNo, kVCMetadata::VCArgumentIOKind)) {
|
|
SPIRVWord Kind = {};
|
|
Attrs.getParamAttr(ArgNo, kVCMetadata::VCArgumentIOKind)
|
|
.getValueAsString()
|
|
.getAsInteger(0, Kind);
|
|
BA->addDecorate(DecorationFuncParamIOKindINTEL, Kind);
|
|
}
|
|
if (Attrs.hasParamAttr(ArgNo, kVCMetadata::VCSingleElementVector))
|
|
translateSEVDecoration(
|
|
Attrs.getParamAttr(ArgNo, kVCMetadata::VCSingleElementVector), BA);
|
|
if (Attrs.hasParamAttr(ArgNo, kVCMetadata::VCMediaBlockIO)) {
|
|
assert(BA->getType()->isTypeImage() &&
|
|
"VCMediaBlockIO attribute valid only on image parameters");
|
|
BA->addDecorate(DecorationMediaBlockIOINTEL);
|
|
}
|
|
}
|
|
if (!isKernel(F) &&
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_float_controls2) &&
|
|
Attrs.hasFnAttr(kVCMetadata::VCFloatControl)) {
|
|
|
|
SPIRVWord Mode = 0;
|
|
Attrs.getFnAttr(kVCMetadata::VCFloatControl)
|
|
.getValueAsString()
|
|
.getAsInteger(0, Mode);
|
|
VCFloatTypeSizeMap::foreach (
|
|
[&](VCFloatType FloatType, unsigned TargetWidth) {
|
|
BF->addDecorate(new SPIRVDecorateFunctionDenormModeINTEL(
|
|
BF, TargetWidth, getFPDenormMode(Mode, FloatType)));
|
|
|
|
BF->addDecorate(new SPIRVDecorateFunctionRoundingModeINTEL(
|
|
BF, TargetWidth, getFPRoundingMode(Mode)));
|
|
|
|
BF->addDecorate(new SPIRVDecorateFunctionFloatingPointModeINTEL(
|
|
BF, TargetWidth, getFPOperationMode(Mode)));
|
|
});
|
|
}
|
|
}
|
|
|
|
static void transMetadataDecorations(Metadata *MD, SPIRVValue *Target);
|
|
|
|
void LLVMToSPIRVBase::transFPGAFunctionMetadata(SPIRVFunction *BF,
|
|
Function *F) {
|
|
if (MDNode *StallEnable = F->getMetadata(kSPIR2MD::StallEnable)) {
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_cluster_attributes)) {
|
|
if (getMDOperandAsInt(StallEnable, 0))
|
|
BF->addDecorate(new SPIRVDecorateStallEnableINTEL(BF));
|
|
}
|
|
}
|
|
if (MDNode *StallFree = F->getMetadata(kSPIR2MD::StallFree)) {
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_cluster_attributes)) {
|
|
if (getMDOperandAsInt(StallFree, 0)) {
|
|
BF->addDecorate(new SPIRVDecorateStallFreeINTEL(BF));
|
|
}
|
|
}
|
|
}
|
|
if (MDNode *LoopFuse = F->getMetadata(kSPIR2MD::LoopFuse)) {
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_loop_fuse)) {
|
|
size_t Depth = getMDOperandAsInt(LoopFuse, 0);
|
|
size_t Independent = getMDOperandAsInt(LoopFuse, 1);
|
|
BF->addDecorate(
|
|
new SPIRVDecorateFuseLoopsInFunctionINTEL(BF, Depth, Independent));
|
|
}
|
|
}
|
|
if (MDNode *PreferDSP = F->getMetadata(kSPIR2MD::PreferDSP)) {
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_dsp_control)) {
|
|
size_t Mode = getMDOperandAsInt(PreferDSP, 0);
|
|
MDNode *PropDSPPref = F->getMetadata(kSPIR2MD::PropDSPPref);
|
|
size_t Propagate = PropDSPPref ? getMDOperandAsInt(PropDSPPref, 0) : 0;
|
|
BF->addDecorate(new SPIRVDecorateMathOpDSPModeINTEL(BF, Mode, Propagate));
|
|
}
|
|
}
|
|
if (MDNode *InitiationInterval =
|
|
F->getMetadata(kSPIR2MD::InitiationInterval)) {
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_invocation_pipelining_attributes)) {
|
|
if (size_t Cycles = getMDOperandAsInt(InitiationInterval, 0))
|
|
BF->addDecorate(new SPIRVDecorateInitiationIntervalINTEL(BF, Cycles));
|
|
}
|
|
}
|
|
if (MDNode *MaxConcurrency = F->getMetadata(kSPIR2MD::MaxConcurrency)) {
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_invocation_pipelining_attributes)) {
|
|
size_t Invocations = getMDOperandAsInt(MaxConcurrency, 0);
|
|
BF->addDecorate(new SPIRVDecorateMaxConcurrencyINTEL(BF, Invocations));
|
|
}
|
|
}
|
|
if (MDNode *PipelineKernel = F->getMetadata(kSPIR2MD::PipelineKernel)) {
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_invocation_pipelining_attributes)) {
|
|
size_t Pipeline = getMDOperandAsInt(PipelineKernel, 0);
|
|
BF->addDecorate(new SPIRVDecoratePipelineEnableINTEL(BF, Pipeline));
|
|
}
|
|
}
|
|
|
|
// In addition, process the decorations on the function
|
|
if (auto *FDecoMD = F->getMetadata(SPIRV_MD_DECORATIONS))
|
|
transMetadataDecorations(FDecoMD, BF);
|
|
}
|
|
|
|
void LLVMToSPIRVBase::transFunctionMetadataAsExecutionMode(SPIRVFunction *BF,
|
|
Function *F) {
|
|
SmallVector<MDNode *, 1> RegisterAllocModeMDs;
|
|
F->getMetadata("RegisterAllocMode", RegisterAllocModeMDs);
|
|
|
|
for (unsigned I = 0; I < RegisterAllocModeMDs.size(); I++) {
|
|
auto *RegisterAllocMode = RegisterAllocModeMDs[I]->getOperand(0).get();
|
|
if (isa<MDString>(RegisterAllocMode)) {
|
|
StringRef Str = getMDOperandAsString(RegisterAllocModeMDs[I], 0);
|
|
NamedMaximumNumberOfRegisters NamedValue =
|
|
SPIRVNamedMaximumNumberOfRegistersNameMap::rmap(Str.str());
|
|
BF->addExecutionMode(BM->add(new SPIRVExecutionMode(
|
|
OpExecutionMode, BF, ExecutionModeNamedMaximumRegistersINTEL,
|
|
NamedValue)));
|
|
} else if (isa<MDNode>(RegisterAllocMode)) {
|
|
auto *RegisterAllocNodeMDOp =
|
|
getMDOperandAsMDNode(RegisterAllocModeMDs[I], 0);
|
|
int Num = getMDOperandAsInt(RegisterAllocNodeMDOp, 0);
|
|
auto *Const =
|
|
BM->addConstant(transType(Type::getInt32Ty(F->getContext())), Num);
|
|
BF->addExecutionMode(BM->add(new SPIRVExecutionModeId(
|
|
BF, ExecutionModeMaximumRegistersIdINTEL, Const->getId())));
|
|
} else {
|
|
int64_t RegisterAllocVal =
|
|
mdconst::dyn_extract<ConstantInt>(RegisterAllocMode)->getZExtValue();
|
|
BF->addExecutionMode(BM->add(new SPIRVExecutionMode(
|
|
OpExecutionMode, BF, ExecutionModeMaximumRegistersINTEL,
|
|
RegisterAllocVal)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVMToSPIRVBase::transFunctionMetadataAsUserSemanticDecoration(
|
|
SPIRVFunction *BF, Function *F) {
|
|
if (auto *RegisterAllocModeMD = F->getMetadata("RegisterAllocMode")) {
|
|
// TODO: Once the design for per-kernel register size allocation is
|
|
// finalized, we will need to move away from UserSemantic and introduce an
|
|
// extension
|
|
int RegisterAllocNodeMDOp = getMDOperandAsInt(RegisterAllocModeMD, 0);
|
|
// The current RegisterAllocMode metadata format is as follows
|
|
// AUTO - 0
|
|
// SMALL - 1
|
|
// LARGE - 2
|
|
// DEFAULT - 3
|
|
// Currently we only support AUTO, SMALL and LARGE
|
|
if (RegisterAllocNodeMDOp == 0 || RegisterAllocNodeMDOp == 1 ||
|
|
RegisterAllocNodeMDOp == 2) {
|
|
// 4 threads per eu means large grf mode, and 8 threads per eu
|
|
// means small grf mode, 0 means use internal heuristics to choose
|
|
std::string NumThreads;
|
|
switch (RegisterAllocNodeMDOp) {
|
|
case 0:
|
|
NumThreads = "0";
|
|
break;
|
|
case 1:
|
|
NumThreads = "8";
|
|
break;
|
|
case 2:
|
|
NumThreads = "4";
|
|
break;
|
|
default:
|
|
llvm_unreachable("Not implemented");
|
|
}
|
|
BF->addDecorate(new SPIRVDecorateUserSemanticAttr(
|
|
BF, "num-thread-per-eu " + NumThreads));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVMToSPIRVBase::transAuxDataInst(SPIRVValue *BV, Value *V) {
|
|
auto *GO = cast<GlobalObject>(V);
|
|
auto *F = dyn_cast<Function>(GO);
|
|
auto *GV = dyn_cast<GlobalVariable>(GO);
|
|
assert((F || GV) && "Invalid value type");
|
|
auto *BM = BV->getModule();
|
|
if (!BM->preserveAuxData())
|
|
return;
|
|
if (!BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_6))
|
|
BM->addExtension(SPIRV::ExtensionID::SPV_KHR_non_semantic_info);
|
|
else
|
|
BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_6);
|
|
const auto &Attrs = F ? F->getAttributes().getFnAttrs() : GV->getAttributes();
|
|
for (const auto &Attr : Attrs) {
|
|
std::vector<SPIRVWord> Ops;
|
|
Ops.push_back(BV->getId());
|
|
if (Attr.isStringAttribute()) {
|
|
// Format for String attributes is:
|
|
// NonSemanticAuxData*Attribute ValueName AttrName AttrValue
|
|
// or, if no value:
|
|
// NonSemanticAuxData*Attribute ValueName AttrName
|
|
//
|
|
// AttrName and AttrValue are always Strings
|
|
StringRef AttrKind = Attr.getKindAsString();
|
|
StringRef AttrValue = Attr.getValueAsString();
|
|
auto *KindSpvString = BM->getString(AttrKind.str());
|
|
Ops.push_back(KindSpvString->getId());
|
|
if (!AttrValue.empty()) {
|
|
auto *ValueSpvString = BM->getString(AttrValue.str());
|
|
Ops.push_back(ValueSpvString->getId());
|
|
}
|
|
} else {
|
|
// Format for other types is:
|
|
// NonSemanticAuxData*Attribute ValueName AttrStr
|
|
// AttrStr is always a String.
|
|
std::string AttrStr = Attr.getAsString();
|
|
auto *AttrSpvString = BM->getString(AttrStr);
|
|
Ops.push_back(AttrSpvString->getId());
|
|
}
|
|
BM->addAuxData(F ? NonSemanticAuxData::FunctionAttribute
|
|
: NonSemanticAuxData::GlobalVariableAttribute,
|
|
transType(Type::getVoidTy(V->getContext())), Ops);
|
|
}
|
|
SmallVector<std::pair<unsigned, MDNode *>> AllMD;
|
|
SmallVector<StringRef> MDNames;
|
|
V->getContext().getMDKindNames(MDNames);
|
|
GO->getAllMetadata(AllMD);
|
|
for (const auto &MD : AllMD) {
|
|
std::string MDName = MDNames[MD.first].str();
|
|
|
|
// spirv.Decorations, spirv.ParameterDecorations and debug information are
|
|
// handled elsewhere for both forward and reverse translation and are
|
|
// complicated to support here, so just skip them.
|
|
if (MDName == SPIRV_MD_DECORATIONS ||
|
|
MDName == SPIRV_MD_PARAMETER_DECORATIONS ||
|
|
MD.first == LLVMContext::MD_dbg)
|
|
continue;
|
|
|
|
// Format for metadata is:
|
|
// NonSemanticAuxData*Metadata ValueName MDName MDVals...
|
|
// MDName is always a String, MDVals have different types as explained
|
|
// below. Also note this instruction has a variable number of operands
|
|
std::vector<SPIRVWord> Ops;
|
|
Ops.push_back(BV->getId());
|
|
Ops.push_back(BM->getString(MDName)->getId());
|
|
for (unsigned int OpIdx = 0; OpIdx < MD.second->getNumOperands(); OpIdx++) {
|
|
const auto &CurOp = MD.second->getOperand(OpIdx);
|
|
if (auto *MDStr = dyn_cast<MDString>(CurOp)) {
|
|
// For MDString, MDVal is String
|
|
auto *SPIRVStr = BM->getString(MDStr->getString().str());
|
|
Ops.push_back(SPIRVStr->getId());
|
|
} else if (auto *ValueAsMeta = dyn_cast<ValueAsMetadata>(CurOp)) {
|
|
// For Value metadata, MDVal is a SPIRVValue
|
|
auto *SPIRVVal = transValue(ValueAsMeta->getValue(), nullptr);
|
|
Ops.push_back(SPIRVVal->getId());
|
|
} else {
|
|
assert(false && "Unsupported metadata type");
|
|
}
|
|
}
|
|
BM->addAuxData(F ? NonSemanticAuxData::FunctionMetadata
|
|
: NonSemanticAuxData::GlobalVariableMetadata,
|
|
transType(Type::getVoidTy(V->getContext())), Ops);
|
|
}
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transConstantUse(Constant *C,
|
|
SPIRVType *ExpectedType) {
|
|
// Constant expressions expect their pointer types to be i8* in opaque pointer
|
|
// mode, but the value may have a different "natural" type. If that is the
|
|
// case, we need to adjust the type of the constant.
|
|
SPIRVValue *Trans = transValue(C, nullptr, true, FuncTransMode::Pointer);
|
|
std::vector<SPIRVWord> Ops = {Trans->getId()};
|
|
if (Trans->getType() == ExpectedType || Trans->getType()->isTypePipeStorage())
|
|
return Trans;
|
|
|
|
assert((C->getType()->isPointerTy() ||
|
|
ExpectedType->isTypeUntypedPointerKHR()) &&
|
|
"Only pointer type mismatches should be possible");
|
|
// In the common case of strings ([N x i8] GVs), see if we can emit a GEP
|
|
// instruction.
|
|
if (auto *GV = dyn_cast<GlobalVariable>(C)) {
|
|
if (GV->getValueType()->isArrayTy() &&
|
|
GV->getValueType()->getArrayElementType()->isIntegerTy(8)) {
|
|
SPIRVWord Offset = transValue(getUInt32(M, 0), nullptr)->getId();
|
|
Ops.push_back(Offset);
|
|
Ops.push_back(Offset);
|
|
if (ExpectedType->isTypeUntypedPointerKHR()) {
|
|
llvm::Type *Ty = Scavenger->getScavengedType(C);
|
|
SPIRVType *PtrTy = nullptr;
|
|
if (auto *TPT = dyn_cast<TypedPointerType>(Ty)) {
|
|
PtrTy = transType(TPT->getElementType());
|
|
Ops = getVec(PtrTy->getId(), Ops);
|
|
}
|
|
}
|
|
return BM->addPtrAccessChainInst(ExpectedType, std::move(Ops), nullptr,
|
|
true);
|
|
}
|
|
}
|
|
|
|
// Otherwise, just use a bitcast.
|
|
return BM->addUnaryInst(OpBitcast, ExpectedType, Trans, nullptr);
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transConstant(Value *V) {
|
|
SPIRVType *ExpectedType = transScavengedType(V);
|
|
if (isa<ConstantPointerNull>(V))
|
|
return BM->addNullConstant(bcast<SPIRVTypePointer>(ExpectedType));
|
|
|
|
if (isa<ConstantTargetNone>(V))
|
|
return BM->addNullConstant(ExpectedType);
|
|
|
|
if (auto *CAZero = dyn_cast<ConstantAggregateZero>(V)) {
|
|
Type *AggType = CAZero->getType();
|
|
if (const StructType *ST = dyn_cast<StructType>(AggType))
|
|
if (ST->hasName() &&
|
|
ST->getName() == getSPIRVTypeName(kSPIRVTypeName::ConstantSampler))
|
|
return BM->addSamplerConstant(transType(AggType), 0, 0, 0);
|
|
|
|
return BM->addNullConstant(transType(AggType));
|
|
}
|
|
|
|
if (auto *ConstI = dyn_cast<ConstantInt>(V)) {
|
|
unsigned BitWidth = ConstI->getIntegerType()->getBitWidth();
|
|
if (BitWidth > 64) {
|
|
BM->getErrorLog().checkError(
|
|
BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_arbitrary_precision_integers),
|
|
SPIRVEC_InvalidBitWidth, std::to_string(BitWidth));
|
|
return BM->addConstant(ExpectedType, ConstI->getValue());
|
|
}
|
|
return BM->addConstant(ExpectedType, ConstI->getZExtValue());
|
|
}
|
|
|
|
if (auto *ConstFP = dyn_cast<ConstantFP>(V)) {
|
|
auto *BT = static_cast<SPIRVType *>(ExpectedType);
|
|
return BM->addConstant(
|
|
BT, ConstFP->getValueAPF().bitcastToAPInt().getZExtValue());
|
|
}
|
|
|
|
if (auto *ConstDA = dyn_cast<ConstantDataArray>(V)) {
|
|
SPIRVType *InnerTy = ExpectedType->getArrayElementType();
|
|
std::vector<SPIRVValue *> BV;
|
|
for (unsigned I = 0, E = ConstDA->getNumElements(); I != E; ++I)
|
|
BV.push_back(transConstantUse(ConstDA->getElementAsConstant(I), InnerTy));
|
|
return BM->addCompositeConstant(ExpectedType, BV);
|
|
}
|
|
|
|
if (auto *ConstA = dyn_cast<ConstantArray>(V)) {
|
|
SPIRVType *InnerTy = ExpectedType->getArrayElementType();
|
|
std::vector<SPIRVValue *> BV;
|
|
for (auto I = ConstA->op_begin(), E = ConstA->op_end(); I != E; ++I)
|
|
BV.push_back(transConstantUse(cast<Constant>(*I), InnerTy));
|
|
return BM->addCompositeConstant(ExpectedType, BV);
|
|
}
|
|
|
|
if (auto *ConstDV = dyn_cast<ConstantDataVector>(V)) {
|
|
SPIRVType *InnerTy = ExpectedType->getScalarType();
|
|
std::vector<SPIRVValue *> BV;
|
|
for (unsigned I = 0, E = ConstDV->getNumElements(); I != E; ++I)
|
|
BV.push_back(transConstantUse(ConstDV->getElementAsConstant(I), InnerTy));
|
|
return BM->addCompositeConstant(ExpectedType, BV);
|
|
}
|
|
|
|
if (auto *ConstV = dyn_cast<ConstantVector>(V)) {
|
|
SPIRVType *InnerTy = ExpectedType->getScalarType();
|
|
std::vector<SPIRVValue *> BV;
|
|
for (auto I = ConstV->op_begin(), E = ConstV->op_end(); I != E; ++I)
|
|
BV.push_back(transConstantUse(cast<Constant>(*I), InnerTy));
|
|
return BM->addCompositeConstant(ExpectedType, BV);
|
|
}
|
|
|
|
if (const auto *ConstV = dyn_cast<ConstantStruct>(V)) {
|
|
StringRef StructName;
|
|
if (ConstV->getType()->hasName())
|
|
StructName = ConstV->getType()->getName();
|
|
if (StructName == getSPIRVTypeName(kSPIRVTypeName::ConstantSampler)) {
|
|
assert(ConstV->getNumOperands() == 3);
|
|
SPIRVWord AddrMode =
|
|
ConstV->getOperand(0)->getUniqueInteger().getZExtValue(),
|
|
Normalized =
|
|
ConstV->getOperand(1)->getUniqueInteger().getZExtValue(),
|
|
FilterMode =
|
|
ConstV->getOperand(2)->getUniqueInteger().getZExtValue();
|
|
assert(AddrMode < 5 && "Invalid addressing mode");
|
|
assert(Normalized < 2 && "Invalid value of normalized coords");
|
|
assert(FilterMode < 2 && "Invalid filter mode");
|
|
SPIRVType *SamplerTy = transType(ConstV->getType());
|
|
return BM->addSamplerConstant(SamplerTy, AddrMode, Normalized,
|
|
FilterMode);
|
|
}
|
|
if (StructName == getSPIRVTypeName(kSPIRVTypeName::ConstantPipeStorage)) {
|
|
assert(ConstV->getNumOperands() == 3);
|
|
SPIRVWord PacketSize =
|
|
ConstV->getOperand(0)->getUniqueInteger().getZExtValue(),
|
|
PacketAlign =
|
|
ConstV->getOperand(1)->getUniqueInteger().getZExtValue(),
|
|
Capacity =
|
|
ConstV->getOperand(2)->getUniqueInteger().getZExtValue();
|
|
assert(PacketAlign >= 1 && "Invalid packet alignment");
|
|
assert(PacketSize >= PacketAlign && PacketSize % PacketAlign == 0 &&
|
|
"Invalid packet size and/or alignment.");
|
|
SPIRVType *PipeStorageTy = transType(ConstV->getType());
|
|
return BM->addPipeStorageConstant(PipeStorageTy, PacketSize, PacketAlign,
|
|
Capacity);
|
|
}
|
|
std::vector<SPIRVValue *> BV;
|
|
for (auto I = ConstV->op_begin(), E = ConstV->op_end(); I != E; ++I) {
|
|
SPIRVType *InnerTy = ExpectedType->getStructMemberType(BV.size());
|
|
BV.push_back(transConstantUse(cast<Constant>(*I), InnerTy));
|
|
}
|
|
return BM->addCompositeConstant(ExpectedType, BV);
|
|
}
|
|
|
|
if (auto *ConstUE = dyn_cast<ConstantExpr>(V)) {
|
|
if (auto *GEP = dyn_cast<GEPOperator>(ConstUE)) {
|
|
auto *TransPointerOperand = transValue(GEP->getPointerOperand(), nullptr);
|
|
// Determine the expected pointer type from the GEP source element type.
|
|
Type *GepSourceElemTy = GEP->getSourceElementType();
|
|
SPIRVType *ExpectedPtrTy =
|
|
transPointerType(GepSourceElemTy, GEP->getPointerAddressSpace());
|
|
|
|
// Ensure the base pointer's type matches the GEP's effective source
|
|
// element type.
|
|
if (TransPointerOperand->getType() != ExpectedPtrTy) {
|
|
TransPointerOperand = BM->addUnaryInst(OpBitcast, ExpectedPtrTy,
|
|
TransPointerOperand, nullptr);
|
|
}
|
|
|
|
std::vector<SPIRVWord> Ops = {TransPointerOperand->getId()};
|
|
for (unsigned I = 0, E = GEP->getNumIndices(); I != E; ++I)
|
|
Ops.push_back(transValue(GEP->getOperand(I + 1), nullptr)->getId());
|
|
SPIRVType *TranslatedTy = transScavengedType(GEP);
|
|
if (TranslatedTy->isTypeUntypedPointerKHR()) {
|
|
llvm::Type *Ty = Scavenger->getScavengedType(GEP->getPointerOperand());
|
|
if (auto *TPT = dyn_cast<TypedPointerType>(Ty)) {
|
|
SPIRVType *PtrTy = transType(TPT->getElementType());
|
|
Ops = getVec(PtrTy->getId(), Ops);
|
|
}
|
|
}
|
|
return BM->addPtrAccessChainInst(TranslatedTy, std::move(Ops), nullptr,
|
|
GEP->isInBounds());
|
|
}
|
|
auto *Inst = ConstUE->getAsInstruction();
|
|
SPIRVDBG(dbgs() << "ConstantExpr: " << *ConstUE << '\n';
|
|
dbgs() << "Instruction: " << *Inst << '\n';)
|
|
auto *BI = transValue(Inst, nullptr, false);
|
|
Inst->dropAllReferences();
|
|
UnboundInst.push_back(Inst);
|
|
return BI;
|
|
}
|
|
|
|
// Translate aliases to their aliasee because they can't be represented
|
|
// directly in SPIR-V.
|
|
if (auto *const ConstAlias = dyn_cast<GlobalAlias>(V)) {
|
|
return transValue(ConstAlias->getAliasee(), nullptr, false,
|
|
FuncTransMode::Pointer);
|
|
}
|
|
|
|
if (isa<UndefValue>(V)) {
|
|
return BM->addUndef(ExpectedType);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transValue(Value *V, SPIRVBasicBlock *BB,
|
|
bool CreateForward,
|
|
FuncTransMode FuncTrans) {
|
|
LLVMToSPIRVValueMap::iterator Loc = ValueMap.find(V);
|
|
if (Loc != ValueMap.end() && (!Loc->second->isForward() || CreateForward) &&
|
|
// do not return forward-decl of a function if we
|
|
// actually want to create a function pointer
|
|
!(FuncTrans == FuncTransMode::Pointer && isa<Function>(V)))
|
|
return Loc->second;
|
|
|
|
SPIRVDBG(dbgs() << "[transValue] " << *V << '\n');
|
|
assert((!isa<Instruction>(V) || isa<GetElementPtrInst>(V) ||
|
|
isa<CastInst>(V) || isa<ExtractElementInst>(V) || isa<ICmpInst>(V) ||
|
|
isa<BinaryOperator>(V) || BB) &&
|
|
"Invalid SPIRV BB");
|
|
|
|
auto *BV = transValueWithoutDecoration(V, BB, CreateForward, FuncTrans);
|
|
if (!BV)
|
|
return nullptr;
|
|
// Only translate decorations for non-forward instructions. Forward
|
|
// instructions will have their decorations translated when the actual
|
|
// instruction is seen and rewritten to a real SPIR-V instruction.
|
|
if (!BV->isForward() && !transDecoration(V, BV))
|
|
return nullptr;
|
|
StringRef Name = V->getName();
|
|
if (!Name.empty()) // Don't erase the name, which BM might already have
|
|
BM->setName(BV, Name.str());
|
|
return BV;
|
|
}
|
|
|
|
SPIRVInstruction *LLVMToSPIRVBase::transBinaryInst(BinaryOperator *B,
|
|
SPIRVBasicBlock *BB) {
|
|
unsigned LLVMOC = B->getOpcode();
|
|
auto *Op0 = transValue(B->getOperand(0), BB);
|
|
SPIRVInstruction *BI = BM->addBinaryInst(
|
|
transBoolOpCode(Op0, OpCodeMap::map(LLVMOC)), transType(B->getType()),
|
|
Op0, transValue(B->getOperand(1), BB), BB);
|
|
|
|
// BinaryOperator can have no parent if it is handled as an expression inside
|
|
// another instruction.
|
|
if (B->getParent() && isUnfusedMulAdd(B)) {
|
|
Function *F = B->getFunction();
|
|
SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName()
|
|
<< ": possible fma candidate " << *B << '\n');
|
|
joinFPContract(F, FPContract::DISABLED);
|
|
}
|
|
|
|
return BI;
|
|
}
|
|
|
|
SPIRVInstruction *LLVMToSPIRVBase::transCmpInst(CmpInst *Cmp,
|
|
SPIRVBasicBlock *BB) {
|
|
auto *Op0 = Cmp->getOperand(0);
|
|
SPIRVValue *TOp0 = transValue(Op0, BB);
|
|
SPIRVValue *TOp1 = transValue(Cmp->getOperand(1), BB);
|
|
if (Op0->getType()->isPointerTy()) {
|
|
auto P = Cmp->getPredicate();
|
|
if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4) &&
|
|
(P == ICmpInst::ICMP_EQ || P == ICmpInst::ICMP_NE) &&
|
|
Cmp->getOperand(1)->getType()->isPointerTy()) {
|
|
Op OC = P == ICmpInst::ICMP_EQ ? OpPtrEqual : OpPtrNotEqual;
|
|
return BM->addBinaryInst(OC, transType(Cmp->getType()), TOp0, TOp1, BB);
|
|
}
|
|
unsigned AS = cast<PointerType>(Op0->getType())->getAddressSpace();
|
|
SPIRVType *Ty = transType(getSizetType(AS));
|
|
TOp0 = BM->addUnaryInst(OpConvertPtrToU, Ty, TOp0, BB);
|
|
TOp1 = BM->addUnaryInst(OpConvertPtrToU, Ty, TOp1, BB);
|
|
}
|
|
SPIRVInstruction *BI =
|
|
BM->addCmpInst(transBoolOpCode(TOp0, CmpMap::map(Cmp->getPredicate())),
|
|
transType(Cmp->getType()), TOp0, TOp1, BB);
|
|
return BI;
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transUnaryInst(UnaryInstruction *U,
|
|
SPIRVBasicBlock *BB) {
|
|
if (isa<BitCastInst>(U) && U->getType()->isPtrOrPtrVectorTy()) {
|
|
if (isa<ConstantPointerNull>(U->getOperand(0))) {
|
|
SPIRVType *ExpectedTy = transScavengedType(U);
|
|
return BM->addNullConstant(bcast<SPIRVTypePointer>(ExpectedTy));
|
|
}
|
|
if (isa<UndefValue>(U->getOperand(0))) {
|
|
SPIRVType *ExpectedTy = transScavengedType(U);
|
|
return BM->addUndef(ExpectedTy);
|
|
}
|
|
}
|
|
|
|
Op BOC = OpNop;
|
|
if (auto *Cast = dyn_cast<AddrSpaceCastInst>(U)) {
|
|
const auto SrcAddrSpace = Cast->getSrcTy()->getPointerAddressSpace();
|
|
const auto DestAddrSpace = Cast->getDestTy()->getPointerAddressSpace();
|
|
if (DestAddrSpace == SPIRAS_Generic) {
|
|
getErrorLog().checkError(
|
|
SrcAddrSpace != SPIRAS_Constant, SPIRVEC_InvalidModule, U,
|
|
"Casts from constant address space to generic are illegal\n");
|
|
BOC = OpPtrCastToGeneric;
|
|
// In SPIR-V only casts to/from generic are allowed. But with
|
|
// SPV_INTEL_usm_storage_classes we can also have casts from global_device
|
|
// and global_host to global addr space and vice versa.
|
|
} else if (SrcAddrSpace == SPIRAS_GlobalDevice ||
|
|
SrcAddrSpace == SPIRAS_GlobalHost) {
|
|
getErrorLog().checkError(DestAddrSpace == SPIRAS_Global ||
|
|
DestAddrSpace == SPIRAS_Generic,
|
|
SPIRVEC_InvalidModule, U,
|
|
"Casts from global_device/global_host only "
|
|
"allowed to global/generic\n");
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_usm_storage_classes)) {
|
|
if (DestAddrSpace == SPIRAS_Global)
|
|
return nullptr;
|
|
BOC = OpPtrCastToGeneric;
|
|
} else {
|
|
BOC = OpPtrCastToCrossWorkgroupINTEL;
|
|
}
|
|
} else if (DestAddrSpace == SPIRAS_GlobalDevice ||
|
|
DestAddrSpace == SPIRAS_GlobalHost) {
|
|
getErrorLog().checkError(SrcAddrSpace == SPIRAS_Global ||
|
|
SrcAddrSpace == SPIRAS_Generic,
|
|
SPIRVEC_InvalidModule, U,
|
|
"Casts to global_device/global_host only "
|
|
"allowed from global/generic\n");
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_usm_storage_classes)) {
|
|
if (SrcAddrSpace == SPIRAS_Global)
|
|
return nullptr;
|
|
BOC = OpGenericCastToPtr;
|
|
} else {
|
|
BOC = OpCrossWorkgroupCastToPtrINTEL;
|
|
}
|
|
} else {
|
|
getErrorLog().checkError(
|
|
SrcAddrSpace == SPIRAS_Generic, SPIRVEC_InvalidModule, U,
|
|
"Casts from private/local/global address space are allowed only to "
|
|
"generic\n");
|
|
getErrorLog().checkError(
|
|
DestAddrSpace != SPIRAS_Constant, SPIRVEC_InvalidModule, U,
|
|
"Casts from generic address space to constant are illegal\n");
|
|
BOC = OpGenericCastToPtr;
|
|
}
|
|
} else {
|
|
auto OpCode = U->getOpcode();
|
|
BOC = OpCodeMap::map(OpCode);
|
|
}
|
|
|
|
auto *Op = transValue(U->getOperand(0), BB, true, FuncTransMode::Pointer);
|
|
SPIRVType *TransTy = transScavengedType(U);
|
|
return BM->addUnaryInst(transBoolOpCode(Op, BOC), TransTy, Op, BB);
|
|
}
|
|
|
|
/// This helper class encapsulates information extraction from
|
|
/// "llvm.loop.parallel_access_indices" metadata hints. Initialize
|
|
/// with a pointer to an MDNode with the following structure:
|
|
/// !<Node> = !{!"llvm.loop.parallel_access_indices", !<Node>, !<Node>, ...}
|
|
/// OR:
|
|
/// !<Node> = !{!"llvm.loop.parallel_access_indices", !<Nodes...>, i32 <value>}
|
|
///
|
|
/// All of the MDNode-type operands mark the index groups for particular
|
|
/// array variables. An optional i32 value indicates the safelen (safe
|
|
/// number of iterations) for the optimization application to these
|
|
/// array variables. If the safelen value is absent, an infinite
|
|
/// number of iterations is implied.
|
|
class LLVMParallelAccessIndices {
|
|
public:
|
|
LLVMParallelAccessIndices(
|
|
MDNode *Node, LLVMToSPIRVBase::LLVMToSPIRVMetadataMap &IndexGroupArrayMap)
|
|
: Node(Node) {
|
|
|
|
assert(isValid() &&
|
|
"LLVMParallelAccessIndices initialized from an invalid MDNode");
|
|
|
|
unsigned NumOperands = Node->getNumOperands();
|
|
auto *SafeLenExpression = mdconst::dyn_extract_or_null<ConstantInt>(
|
|
Node->getOperand(NumOperands - 1));
|
|
// If no safelen value is specified and the last operand
|
|
// casts to an MDNode* rather than an int, 0 will be stored
|
|
SafeLen = SafeLenExpression ? SafeLenExpression->getZExtValue() : 0;
|
|
|
|
// Count MDNode operands that refer to index groups:
|
|
// - operand [0] is a string literal and should be ignored;
|
|
// - depending on whether a particular safelen is specified as the
|
|
// last operand, we may or may not want to extract the latter
|
|
// as an index group
|
|
unsigned NumIdxGroups = SafeLen ? NumOperands - 2 : NumOperands - 1;
|
|
for (unsigned I = 1; I <= NumIdxGroups; ++I) {
|
|
MDNode *IdxGroupNode = getMDOperandAsMDNode(Node, I);
|
|
assert(IdxGroupNode &&
|
|
"Invalid operand in the MDNode for LLVMParallelAccessIndices");
|
|
auto IdxGroupArrayPairIt = IndexGroupArrayMap.find(IdxGroupNode);
|
|
// TODO: Some LLVM IR optimizations (e.g. loop inlining as part of
|
|
// the function inlining) can result in invalid parallel_access_indices
|
|
// metadata. Only valid cases will pass the subsequent check and
|
|
// survive the translation. This check should be replaced with an
|
|
// assertion once all known cases are handled.
|
|
if (IdxGroupArrayPairIt != IndexGroupArrayMap.end())
|
|
for (SPIRVId ArrayAccessId : IdxGroupArrayPairIt->second)
|
|
ArrayVariablesVec.push_back(ArrayAccessId);
|
|
}
|
|
}
|
|
|
|
bool isValid() {
|
|
bool IsNamedCorrectly = getMDOperandAsString(Node, 0) == ExpectedName;
|
|
return Node && IsNamedCorrectly;
|
|
}
|
|
|
|
unsigned getSafeLen() { return SafeLen; }
|
|
const std::vector<SPIRVId> &getArrayVariables() { return ArrayVariablesVec; }
|
|
|
|
private:
|
|
MDNode *Node;
|
|
const std::string ExpectedName = "llvm.loop.parallel_access_indices";
|
|
std::vector<SPIRVId> ArrayVariablesVec;
|
|
unsigned SafeLen;
|
|
};
|
|
|
|
/// Go through the operands !llvm.loop metadata attached to the branch
|
|
/// instruction, fill the Loop Control mask and possible parameters for its
|
|
/// fields.
|
|
spv::LoopControlMask
|
|
LLVMToSPIRVBase::getLoopControl(const BranchInst *Branch,
|
|
std::vector<SPIRVWord> &Parameters) {
|
|
if (!Branch)
|
|
return spv::LoopControlMaskNone;
|
|
MDNode *LoopMD = Branch->getMetadata("llvm.loop");
|
|
if (!LoopMD)
|
|
return spv::LoopControlMaskNone;
|
|
|
|
size_t LoopControl = spv::LoopControlMaskNone;
|
|
std::vector<std::pair<SPIRVWord, SPIRVWord>> ParametersToSort;
|
|
// If only a subset of loop count parameters is defined in metadata
|
|
// then undefined ones should have a default value -1 in SPIR-V.
|
|
// Preset all loop count parameters with the default value.
|
|
struct LoopCountInfo {
|
|
int64_t Min = -1, Max = -1, Avg = -1;
|
|
} LoopCount;
|
|
|
|
// Unlike with most of the cases, some loop metadata specifications
|
|
// can occur multiple times - for these, all correspondent tokens
|
|
// need to be collected first, and only then added to SPIR-V loop
|
|
// parameters in a separate routine
|
|
std::vector<std::pair<SPIRVWord, SPIRVWord>> DependencyArrayParameters;
|
|
|
|
for (const MDOperand &MDOp : LoopMD->operands()) {
|
|
if (MDNode *Node = dyn_cast<MDNode>(MDOp)) {
|
|
StringRef S = getMDOperandAsString(Node, 0);
|
|
// Set the loop control bits. Parameters are set in the order described
|
|
// in 3.23 SPIR-V Spec. rev. 1.4:
|
|
// Bits that are set can indicate whether an additional operand follows,
|
|
// as described by the table. If there are multiple following operands
|
|
// indicated, they are ordered: Those indicated by smaller-numbered bits
|
|
// appear first.
|
|
if (S == "llvm.loop.unroll.disable")
|
|
LoopControl |= spv::LoopControlDontUnrollMask;
|
|
else if (S == "llvm.loop.unroll.enable")
|
|
LoopControl |= spv::LoopControlUnrollMask;
|
|
// Attempt to do full unroll of the loop and disable unrolling if the trip
|
|
// count is not known at compile time by setting PartialCount to 1
|
|
else if (S == "llvm.loop.unroll.full") {
|
|
LoopControl |= spv::LoopControlUnrollMask;
|
|
if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)) {
|
|
BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_4);
|
|
ParametersToSort.emplace_back(spv::LoopControlPartialCountMask, 1);
|
|
LoopControl |= spv::LoopControlPartialCountMask;
|
|
}
|
|
}
|
|
// PartialCount must not be used with the DontUnroll bit
|
|
else if (S == "llvm.loop.unroll.count" &&
|
|
!(LoopControl & LoopControlDontUnrollMask)) {
|
|
if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)) {
|
|
BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_4);
|
|
size_t I = getMDOperandAsInt(Node, 1);
|
|
ParametersToSort.emplace_back(spv::LoopControlPartialCountMask, I);
|
|
LoopControl |= spv::LoopControlPartialCountMask;
|
|
}
|
|
} else if (S == "llvm.loop.ivdep.enable")
|
|
LoopControl |= spv::LoopControlDependencyInfiniteMask;
|
|
else if (S == "llvm.loop.ivdep.safelen") {
|
|
size_t I = getMDOperandAsInt(Node, 1);
|
|
ParametersToSort.emplace_back(spv::LoopControlDependencyLengthMask, I);
|
|
LoopControl |= spv::LoopControlDependencyLengthMask;
|
|
} else if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_loop_controls)) {
|
|
// Add Intel specific Loop Control masks
|
|
if (S == "llvm.loop.ii.count") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
size_t I = getMDOperandAsInt(Node, 1);
|
|
ParametersToSort.emplace_back(
|
|
spv::LoopControlInitiationIntervalINTELMask, I);
|
|
LoopControl |= spv::LoopControlInitiationIntervalINTELMask;
|
|
} else if (S == "llvm.loop.max_concurrency.count") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
size_t I = getMDOperandAsInt(Node, 1);
|
|
ParametersToSort.emplace_back(spv::LoopControlMaxConcurrencyINTELMask,
|
|
I);
|
|
LoopControl |= spv::LoopControlMaxConcurrencyINTELMask;
|
|
} else if (S == "llvm.loop.parallel_access_indices") {
|
|
// Intel FPGA IVDep loop attribute
|
|
LLVMParallelAccessIndices IVDep(Node, IndexGroupArrayMap);
|
|
// Store IVDep-specific parameters into an intermediate
|
|
// container to address the case when there're multiple
|
|
// IVDep metadata nodes and this condition gets entered multiple
|
|
// times. The update of the main parameters vector & the loop control
|
|
// mask will be done later, in the main scope of the function
|
|
unsigned SafeLen = IVDep.getSafeLen();
|
|
for (auto &ArrayId : IVDep.getArrayVariables())
|
|
DependencyArrayParameters.emplace_back(ArrayId, SafeLen);
|
|
} else if (S == "llvm.loop.intel.pipelining.enable") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
size_t I = getMDOperandAsInt(Node, 1);
|
|
ParametersToSort.emplace_back(spv::LoopControlPipelineEnableINTELMask,
|
|
I);
|
|
LoopControl |= spv::LoopControlPipelineEnableINTELMask;
|
|
} else if (S == "llvm.loop.coalesce.enable") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
ParametersToSort.emplace_back(spv::LoopControlLoopCoalesceINTELMask,
|
|
0);
|
|
LoopControl |= spv::LoopControlLoopCoalesceINTELMask;
|
|
} else if (S == "llvm.loop.coalesce.count") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
size_t I = getMDOperandAsInt(Node, 1);
|
|
ParametersToSort.emplace_back(spv::LoopControlLoopCoalesceINTELMask,
|
|
I);
|
|
LoopControl |= spv::LoopControlLoopCoalesceINTELMask;
|
|
} else if (S == "llvm.loop.max_interleaving.count") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
size_t I = getMDOperandAsInt(Node, 1);
|
|
ParametersToSort.emplace_back(
|
|
spv::LoopControlMaxInterleavingINTELMask, I);
|
|
LoopControl |= spv::LoopControlMaxInterleavingINTELMask;
|
|
} else if (S == "llvm.loop.intel.speculated.iterations.count") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
size_t I = getMDOperandAsInt(Node, 1);
|
|
ParametersToSort.emplace_back(
|
|
spv::LoopControlSpeculatedIterationsINTELMask, I);
|
|
LoopControl |= spv::LoopControlSpeculatedIterationsINTELMask;
|
|
} else if (S == "llvm.loop.fusion.disable") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
LoopControl |= spv::LoopControlNoFusionINTELMask;
|
|
} else if (S == "llvm.loop.intel.loopcount_min") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
LoopCount.Min = getMDOperandAsInt(Node, 1);
|
|
LoopControl |= spv::LoopControlLoopCountINTELMask;
|
|
} else if (S == "llvm.loop.intel.loopcount_max") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
LoopCount.Max = getMDOperandAsInt(Node, 1);
|
|
LoopControl |= spv::LoopControlLoopCountINTELMask;
|
|
} else if (S == "llvm.loop.intel.loopcount_avg") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
LoopCount.Avg = getMDOperandAsInt(Node, 1);
|
|
LoopControl |= spv::LoopControlLoopCountINTELMask;
|
|
} else if (S == "llvm.loop.intel.max_reinvocation_delay.count") {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
size_t I = getMDOperandAsInt(Node, 1);
|
|
ParametersToSort.emplace_back(
|
|
spv::LoopControlMaxReinvocationDelayINTELMask, I);
|
|
LoopControl |= spv::LoopControlMaxReinvocationDelayINTELMask;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (LoopControl & spv::LoopControlLoopCountINTELMask) {
|
|
// LoopCountINTELMask have int64 literal parameters and we need to store
|
|
// int64 into 2 SPIRVWords
|
|
ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask,
|
|
static_cast<SPIRVWord>(LoopCount.Min));
|
|
ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask,
|
|
static_cast<SPIRVWord>(LoopCount.Min >> 32));
|
|
ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask,
|
|
static_cast<SPIRVWord>(LoopCount.Max));
|
|
ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask,
|
|
static_cast<SPIRVWord>(LoopCount.Max >> 32));
|
|
ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask,
|
|
static_cast<SPIRVWord>(LoopCount.Avg));
|
|
ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask,
|
|
static_cast<SPIRVWord>(LoopCount.Avg >> 32));
|
|
}
|
|
// If any loop control parameters were held back until fully collected,
|
|
// now is the time to move the information to the main parameters collection
|
|
if (!DependencyArrayParameters.empty()) {
|
|
// The first parameter states the number of <array, safelen> pairs to be
|
|
// listed
|
|
ParametersToSort.emplace_back(spv::LoopControlDependencyArrayINTELMask,
|
|
DependencyArrayParameters.size());
|
|
for (auto &ArraySflnPair : DependencyArrayParameters) {
|
|
ParametersToSort.emplace_back(spv::LoopControlDependencyArrayINTELMask,
|
|
ArraySflnPair.first);
|
|
ParametersToSort.emplace_back(spv::LoopControlDependencyArrayINTELMask,
|
|
ArraySflnPair.second);
|
|
}
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls);
|
|
BM->addCapability(CapabilityFPGALoopControlsINTEL);
|
|
LoopControl |= spv::LoopControlDependencyArrayINTELMask;
|
|
}
|
|
|
|
std::stable_sort(ParametersToSort.begin(), ParametersToSort.end(),
|
|
[](const std::pair<SPIRVWord, SPIRVWord> &CompareLeft,
|
|
const std::pair<SPIRVWord, SPIRVWord> &CompareRight) {
|
|
return CompareLeft.first < CompareRight.first;
|
|
});
|
|
for (const auto &Param : ParametersToSort)
|
|
Parameters.push_back(Param.second);
|
|
|
|
return static_cast<spv::LoopControlMask>(LoopControl);
|
|
}
|
|
|
|
static int transAtomicOrdering(llvm::AtomicOrdering Ordering) {
|
|
return OCLMemOrderMap::map(
|
|
static_cast<OCLMemOrderKind>(llvm::toCABI(Ordering)));
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transAtomicStore(StoreInst *ST,
|
|
SPIRVBasicBlock *BB) {
|
|
spv::Scope S = toSPIRVScope(ST->getContext(), ST->getSyncScopeID());
|
|
|
|
std::vector<Value *> Ops{ST->getPointerOperand(), getUInt32(M, S),
|
|
getUInt32(M, transAtomicOrdering(ST->getOrdering())),
|
|
ST->getValueOperand()};
|
|
std::vector<SPIRVValue *> SPIRVOps = transValue(Ops, BB);
|
|
|
|
return mapValue(ST, BM->addInstTemplate(OpAtomicStore, BM->getIds(SPIRVOps),
|
|
BB, nullptr));
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transAtomicLoad(LoadInst *LD,
|
|
SPIRVBasicBlock *BB) {
|
|
spv::Scope S = toSPIRVScope(LD->getContext(), LD->getSyncScopeID());
|
|
|
|
std::vector<Value *> Ops{
|
|
LD->getPointerOperand(), getUInt32(M, S),
|
|
getUInt32(M, transAtomicOrdering(LD->getOrdering()))};
|
|
std::vector<SPIRVValue *> SPIRVOps = transValue(Ops, BB);
|
|
|
|
return mapValue(LD, BM->addInstTemplate(OpAtomicLoad, BM->getIds(SPIRVOps),
|
|
BB, transScavengedType(LD)));
|
|
}
|
|
|
|
// Aliasing list MD contains several scope MD nodes whithin it. Each scope MD
|
|
// has a selfreference and an extra MD node for aliasing domain and also it
|
|
// can contain an optional string operand. Domain MD contains a self-reference
|
|
// with an optional string operand. Here we unfold the list, creating SPIR-V
|
|
// aliasing instructions.
|
|
// TODO: add support for an optional string operand.
|
|
SPIRVEntry *addMemAliasingINTELInstructions(SPIRVModule *M,
|
|
MDNode *AliasingListMD) {
|
|
if (AliasingListMD->getNumOperands() == 0)
|
|
return nullptr;
|
|
std::vector<SPIRVId> ListId;
|
|
for (const MDOperand &MDListOp : AliasingListMD->operands()) {
|
|
if (MDNode *ScopeMD = dyn_cast<MDNode>(MDListOp)) {
|
|
if (ScopeMD->getNumOperands() < 2)
|
|
return nullptr;
|
|
MDNode *DomainMD = dyn_cast<MDNode>(ScopeMD->getOperand(1));
|
|
if (!DomainMD)
|
|
return nullptr;
|
|
auto *Domain =
|
|
M->getOrAddAliasDomainDeclINTELInst(std::vector<SPIRVId>(), DomainMD);
|
|
auto *Scope =
|
|
M->getOrAddAliasScopeDeclINTELInst({Domain->getId()}, ScopeMD);
|
|
ListId.push_back(Scope->getId());
|
|
}
|
|
}
|
|
return M->getOrAddAliasScopeListDeclINTELInst(ListId, AliasingListMD);
|
|
}
|
|
|
|
// Translate alias.scope/noalias metadata attached to store and load
|
|
// instructions.
|
|
void transAliasingMemAccess(SPIRVModule *BM, MDNode *AliasingListMD,
|
|
std::vector<uint32_t> &MemoryAccess,
|
|
SPIRVWord MemAccessMask) {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_memory_access_aliasing))
|
|
return;
|
|
auto *MemAliasList = addMemAliasingINTELInstructions(BM, AliasingListMD);
|
|
if (!MemAliasList)
|
|
return;
|
|
MemoryAccess[0] |= MemAccessMask;
|
|
MemoryAccess.push_back(MemAliasList->getId());
|
|
}
|
|
|
|
/// An instruction may use an instruction from another BB which has not been
|
|
/// translated. SPIRVForward should be created as place holder for these
|
|
/// instructions and replaced later by the real instructions.
|
|
/// Use CreateForward = true to indicate such situation.
|
|
SPIRVValue *
|
|
LLVMToSPIRVBase::transValueWithoutDecoration(Value *V, SPIRVBasicBlock *BB,
|
|
bool CreateForward,
|
|
FuncTransMode FuncTrans) {
|
|
if (auto *LBB = dyn_cast<BasicBlock>(V)) {
|
|
auto *BF =
|
|
static_cast<SPIRVFunction *>(getTranslatedValue(LBB->getParent()));
|
|
assert(BF && "Function not translated");
|
|
BB = static_cast<SPIRVBasicBlock *>(mapValue(V, BM->addBasicBlock(BF)));
|
|
BM->setName(BB, LBB->getName().str());
|
|
return BB;
|
|
}
|
|
|
|
if (auto *F = dyn_cast<Function>(V)) {
|
|
if (FuncTrans == FuncTransMode::Decl)
|
|
return transFunctionDecl(F);
|
|
if (!BM->checkExtension(ExtensionID::SPV_INTEL_function_pointers,
|
|
SPIRVEC_FunctionPointers, toString(V)))
|
|
return nullptr;
|
|
return BM->addConstantFunctionPointerINTEL(
|
|
transPointerType(transScavengedType(F), F->getAddressSpace()),
|
|
static_cast<SPIRVFunction *>(transValue(F, nullptr)));
|
|
}
|
|
|
|
if (auto *GV = dyn_cast<GlobalVariable>(V)) {
|
|
llvm::Type *Ty = Scavenger->getScavengedType(GV);
|
|
// Though variables with common linkage type are initialized by 0,
|
|
// they can be represented in SPIR-V as uninitialized variables with
|
|
// 'Export' linkage type, just as tentative definitions look in C
|
|
llvm::Constant *Init = GV->hasInitializer() && !GV->hasCommonLinkage()
|
|
? GV->getInitializer()
|
|
: nullptr;
|
|
SPIRVValue *BVarInit = nullptr;
|
|
StructType *ST = Init ? dyn_cast<StructType>(Init->getType()) : nullptr;
|
|
if (ST && ST->hasName() && isSPIRVConstantName(ST->getName())) {
|
|
auto *BV = transConstant(Init);
|
|
assert(BV);
|
|
transAuxDataInst(BV, V);
|
|
return mapValue(V, BV);
|
|
}
|
|
if (isa_and_nonnull<ConstantExpr>(Init)) {
|
|
BVarInit = transValue(Init, nullptr);
|
|
} else if (ST && isa<UndefValue>(Init)) {
|
|
// Undef initializer for LLVM structure be can translated to
|
|
// OpConstantComposite with OpUndef constituents.
|
|
auto I = ValueMap.find(Init);
|
|
if (I == ValueMap.end()) {
|
|
std::vector<SPIRVValue *> Elements;
|
|
for (Type *E : ST->elements())
|
|
Elements.push_back(transValue(UndefValue::get(E), nullptr));
|
|
BVarInit = BM->addCompositeConstant(transType(ST), Elements);
|
|
ValueMap[Init] = BVarInit;
|
|
} else
|
|
BVarInit = I->second;
|
|
} else if (Init && !isa<UndefValue>(Init)) {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_long_composites)) {
|
|
if (auto *ArrTy = dyn_cast_or_null<ArrayType>(Init->getType())) {
|
|
// First 3 words of OpConstantComposite encode: 1) word count &
|
|
// opcode, 2) Result Type and 3) Result Id. Max length of SPIRV
|
|
// instruction = 65535 words.
|
|
constexpr int MaxNumElements =
|
|
MaxWordCount - SPIRVSpecConstantComposite::FixedWC;
|
|
if (ArrTy->getNumElements() > MaxNumElements &&
|
|
!isa<ConstantAggregateZero>(Init)) {
|
|
std::stringstream SS;
|
|
SS << "Global variable has a constant array initializer with a "
|
|
<< "number of elements greater than OpConstantComposite can "
|
|
<< "have (" << MaxNumElements << "). Should the array be "
|
|
<< "split?\n Original LLVM value:\n"
|
|
<< toString(GV);
|
|
getErrorLog().checkError(false, SPIRVEC_InvalidWordCount, SS.str());
|
|
}
|
|
}
|
|
}
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers)) {
|
|
BVarInit = transConstantUse(Init, transType(Init->getType()));
|
|
} else {
|
|
SPIRVType *TransTy = transType(Ty);
|
|
BVarInit = transConstantUse(Init, TransTy->getPointerElementType());
|
|
}
|
|
}
|
|
|
|
SPIRVStorageClassKind StorageClass;
|
|
auto AddressSpace = static_cast<SPIRAddressSpace>(GV->getAddressSpace());
|
|
bool IsVectorCompute =
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute) &&
|
|
GV->hasAttribute(kVCMetadata::VCGlobalVariable);
|
|
if (IsVectorCompute)
|
|
StorageClass =
|
|
VectorComputeUtil::getVCGlobalVarStorageClass(AddressSpace);
|
|
else {
|
|
// Lower global_device and global_host address spaces that were added in
|
|
// SYCL as part of SYCL_INTEL_usm_address_spaces extension to just global
|
|
// address space if device doesn't support SPV_INTEL_usm_storage_classes
|
|
// extension
|
|
if ((AddressSpace == SPIRAS_GlobalDevice ||
|
|
AddressSpace == SPIRAS_GlobalHost) &&
|
|
!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_usm_storage_classes))
|
|
AddressSpace = SPIRAS_Global;
|
|
StorageClass = SPIRSPIRVAddrSpaceMap::map(AddressSpace);
|
|
if (StorageClass == StorageClassFunction) {
|
|
std::stringstream SS;
|
|
SS << "Global variable cannot have Function storage class. "
|
|
<< "Consider setting a proper address space.\n "
|
|
<< "Original LLVM value:\n"
|
|
<< toString(GV);
|
|
getErrorLog().checkError(false, SPIRVEC_InvalidInstruction, SS.str());
|
|
}
|
|
}
|
|
|
|
SPIRVType *TranslatedTy = transType(Ty);
|
|
auto *BVar = static_cast<SPIRVVariableBase *>(BM->addVariable(
|
|
TranslatedTy,
|
|
TranslatedTy->isTypeUntypedPointerKHR() ? transType(GV->getValueType())
|
|
: nullptr,
|
|
GV->isConstant(), transLinkageType(GV), BVarInit, GV->getName().str(),
|
|
StorageClass, nullptr));
|
|
|
|
if (IsVectorCompute) {
|
|
BVar->addDecorate(DecorationVectorComputeVariableINTEL);
|
|
if (GV->hasAttribute(kVCMetadata::VCByteOffset)) {
|
|
SPIRVWord Offset = {};
|
|
GV->getAttribute(kVCMetadata::VCByteOffset)
|
|
.getValueAsString()
|
|
.getAsInteger(0, Offset);
|
|
BVar->addDecorate(DecorationGlobalVariableOffsetINTEL, Offset);
|
|
}
|
|
if (GV->hasAttribute(kVCMetadata::VCVolatile))
|
|
BVar->addDecorate(DecorationVolatile);
|
|
|
|
if (GV->hasAttribute(kVCMetadata::VCSingleElementVector))
|
|
translateSEVDecoration(
|
|
GV->getAttribute(kVCMetadata::VCSingleElementVector), BVar);
|
|
}
|
|
|
|
transAuxDataInst(BVar, V);
|
|
|
|
mapValue(V, BVar);
|
|
spv::BuiltIn Builtin = spv::BuiltInPosition;
|
|
if (!GV->hasName() || !getSPIRVBuiltin(GV->getName().str(), Builtin))
|
|
return BVar;
|
|
if (static_cast<uint32_t>(Builtin) >= internal::BuiltInSubDeviceIDINTEL &&
|
|
static_cast<uint32_t>(Builtin) <=
|
|
internal::BuiltInGlobalHWThreadIDINTEL) {
|
|
std::string ErrorStr = "SPV_INTEL_hw_thread_queries\n"
|
|
"Please report to "
|
|
"https://github.com/intel/llvm in case if you see "
|
|
"this error.\nRef LLVM Value:";
|
|
getErrorLog().checkError(false, SPIRVEC_DeprecatedExtension, V, ErrorStr);
|
|
}
|
|
|
|
BVar->setBuiltin(Builtin);
|
|
return BVar;
|
|
}
|
|
|
|
if (isa<Constant>(V)) {
|
|
auto *BV = transConstant(V);
|
|
assert(BV);
|
|
// Don't store pointer constants in the map -- they are opaque and thus we
|
|
// might reuse the wrong type (Example: a null value) if we do so.
|
|
if (V->getType()->isPointerTy())
|
|
return BV;
|
|
return mapValue(V, BV);
|
|
}
|
|
|
|
if (auto *Arg = dyn_cast<Argument>(V)) {
|
|
unsigned ArgNo = Arg->getArgNo();
|
|
SPIRVFunction *BF = BB->getParent();
|
|
// assert(BF->existArgument(ArgNo));
|
|
return mapValue(V, BF->getArgument(ArgNo));
|
|
}
|
|
|
|
if (CreateForward)
|
|
return mapValue(V, BM->addForward(transScavengedType(V)));
|
|
|
|
if (StoreInst *ST = dyn_cast<StoreInst>(V)) {
|
|
if (ST->isAtomic())
|
|
return transAtomicStore(ST, BB);
|
|
|
|
// Keep this vector to store MemoryAccess operands for both Alignment and
|
|
// Aliasing information.
|
|
std::vector<SPIRVWord> MemoryAccess(1, 0);
|
|
if (ST->isVolatile())
|
|
MemoryAccess[0] |= MemoryAccessVolatileMask;
|
|
MemoryAccess[0] |= MemoryAccessAlignedMask;
|
|
MemoryAccess.push_back(ST->getAlign().value());
|
|
if (ST->getMetadata(LLVMContext::MD_nontemporal))
|
|
MemoryAccess[0] |= MemoryAccessNontemporalMask;
|
|
if (MDNode *AliasingListMD = ST->getMetadata(LLVMContext::MD_alias_scope))
|
|
transAliasingMemAccess(BM, AliasingListMD, MemoryAccess,
|
|
MemoryAccessAliasScopeINTELMaskMask);
|
|
if (MDNode *AliasingListMD = ST->getMetadata(LLVMContext::MD_noalias))
|
|
transAliasingMemAccess(BM, AliasingListMD, MemoryAccess,
|
|
MemoryAccessNoAliasINTELMaskMask);
|
|
if (MemoryAccess.front() == 0)
|
|
MemoryAccess.clear();
|
|
|
|
return mapValue(V,
|
|
BM->addStoreInst(transValue(ST->getPointerOperand(), BB),
|
|
transValue(ST->getValueOperand(), BB, true,
|
|
FuncTransMode::Pointer),
|
|
MemoryAccess, BB));
|
|
}
|
|
|
|
if (LoadInst *LD = dyn_cast<LoadInst>(V)) {
|
|
if (LD->isAtomic())
|
|
return transAtomicLoad(LD, BB);
|
|
|
|
// Keep this vector to store MemoryAccess operands for both Alignment and
|
|
// Aliasing information.
|
|
std::vector<uint32_t> MemoryAccess(1, 0);
|
|
if (LD->isVolatile())
|
|
MemoryAccess[0] |= MemoryAccessVolatileMask;
|
|
MemoryAccess[0] |= MemoryAccessAlignedMask;
|
|
MemoryAccess.push_back(LD->getAlign().value());
|
|
if (LD->getMetadata(LLVMContext::MD_nontemporal))
|
|
MemoryAccess[0] |= MemoryAccessNontemporalMask;
|
|
if (MDNode *AliasingListMD = LD->getMetadata(LLVMContext::MD_alias_scope))
|
|
transAliasingMemAccess(BM, AliasingListMD, MemoryAccess,
|
|
MemoryAccessAliasScopeINTELMaskMask);
|
|
if (MDNode *AliasingListMD = LD->getMetadata(LLVMContext::MD_noalias))
|
|
transAliasingMemAccess(BM, AliasingListMD, MemoryAccess,
|
|
MemoryAccessNoAliasINTELMaskMask);
|
|
if (MemoryAccess.front() == 0)
|
|
MemoryAccess.clear();
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers)) {
|
|
SPIRVValue *Source = transValue(LD->getPointerOperand(), BB);
|
|
SPIRVType *PtrElTy = Source->getType()->getPointerElementType();
|
|
SPIRVType *LoadTy = transType(LD->getType());
|
|
// For special types (images, pipes, etc.) do not use explicit load type,
|
|
// but rather use the source type (calculated in SPIRVLoad constructor)
|
|
if (LoadTy->isTypeUntypedPointerKHR() && PtrElTy->isSPIRVOpaqueType()) {
|
|
LoadTy = nullptr;
|
|
}
|
|
return mapValue(V, BM->addLoadInst(Source, MemoryAccess, BB, LoadTy));
|
|
}
|
|
return mapValue(V, BM->addLoadInst(transValue(LD->getPointerOperand(), BB),
|
|
MemoryAccess, BB));
|
|
}
|
|
|
|
if (BinaryOperator *B = dyn_cast<BinaryOperator>(V)) {
|
|
SPIRVInstruction *BI = transBinaryInst(B, BB);
|
|
return mapValue(V, BI);
|
|
}
|
|
|
|
if (dyn_cast<UnreachableInst>(V))
|
|
return mapValue(V, BM->addUnreachableInst(BB));
|
|
|
|
if (auto *RI = dyn_cast<ReturnInst>(V)) {
|
|
if (auto *RV = RI->getReturnValue()) {
|
|
if (auto *II = dyn_cast<IntrinsicInst>(RV)) {
|
|
if (II->getIntrinsicID() == Intrinsic::frexp) {
|
|
// create composite type from the return value and second operand
|
|
auto *FrexpResult = transValue(RV, BB);
|
|
SPIRVValue *IntFromFrexpResult =
|
|
static_cast<SPIRVExtInst *>(FrexpResult)->getArgValues()[1];
|
|
SPIRVType *LoadTy = nullptr;
|
|
|
|
if (IntFromFrexpResult->isUntypedVariable())
|
|
LoadTy = static_cast<SPIRVUntypedVariableKHR *>(IntFromFrexpResult)
|
|
->getDataType();
|
|
|
|
IntFromFrexpResult =
|
|
BM->addLoadInst(IntFromFrexpResult, {}, BB, LoadTy);
|
|
|
|
std::vector<SPIRVId> Operands = {FrexpResult->getId(),
|
|
IntFromFrexpResult->getId()};
|
|
auto *Compos = BM->addCompositeConstructInst(transType(RV->getType()),
|
|
Operands, BB);
|
|
|
|
return mapValue(V, BM->addReturnValueInst(Compos, BB));
|
|
}
|
|
}
|
|
return mapValue(V, BM->addReturnValueInst(transValue(RV, BB), BB));
|
|
}
|
|
return mapValue(V, BM->addReturnInst(BB));
|
|
}
|
|
|
|
if (CmpInst *Cmp = dyn_cast<CmpInst>(V)) {
|
|
if (Cmp->getPredicate() == CmpInst::Predicate::FCMP_FALSE) {
|
|
auto *CmpTy = Cmp->getType();
|
|
SPIRVValue *FalseValue = CmpTy->isVectorTy()
|
|
? BM->addNullConstant(transType(CmpTy))
|
|
: BM->addConstant(BM->addBoolType(), 0);
|
|
return mapValue(V, FalseValue);
|
|
}
|
|
SPIRVInstruction *BI = transCmpInst(Cmp, BB);
|
|
return mapValue(V, BI);
|
|
}
|
|
|
|
if (SelectInst *Sel = dyn_cast<SelectInst>(V))
|
|
return mapValue(
|
|
V,
|
|
BM->addSelectInst(
|
|
transValue(Sel->getCondition(), BB),
|
|
transValue(Sel->getTrueValue(), BB, true, FuncTransMode::Pointer),
|
|
transValue(Sel->getFalseValue(), BB, true, FuncTransMode::Pointer),
|
|
BB));
|
|
|
|
if (AllocaInst *Alc = dyn_cast<AllocaInst>(V)) {
|
|
SPIRVType *TranslatedTy = transScavengedType(V);
|
|
if (Alc->isArrayAllocation()) {
|
|
SPIRVValue *Length = transValue(Alc->getArraySize(), BB);
|
|
assert(Length && "Couldn't translate array size!");
|
|
|
|
if (isConstantOpCode(Length->getOpCode())) {
|
|
// Length can be any constant instruction, either a specialization
|
|
// constant or a non-specialization constant.
|
|
//
|
|
// Array allocations need special treatment: as the allocation type will
|
|
// be 'OpTypePointer(Function, OpTypeArray(ElementType, Length))', we
|
|
// need to bitcast the obtained pointer to the expected type:
|
|
// 'OpTypePointer(Function, ElementType).
|
|
SPIRVType *AllocationType = BM->addPointerType(
|
|
StorageClassFunction,
|
|
BM->addArrayType(transType(Alc->getAllocatedType()), Length));
|
|
SPIRVValue *Arr = BM->addVariable(
|
|
AllocationType, nullptr, false, spv::internal::LinkageTypeInternal,
|
|
nullptr, Alc->getName().str() + "_alloca", StorageClassFunction,
|
|
BB);
|
|
// Manually set alignment. OpBitcast created below will be decorated as
|
|
// that's the SPIR-V value mapped to the original LLVM one.
|
|
transAlign(Alc, Arr);
|
|
return mapValue(V, BM->addUnaryInst(OpBitcast, TranslatedTy, Arr, BB));
|
|
}
|
|
|
|
if (!BM->checkExtension(ExtensionID::SPV_INTEL_variable_length_array,
|
|
SPIRVEC_InvalidInstruction,
|
|
toString(Alc) +
|
|
"\nTranslation of dynamic alloca requires "
|
|
"SPV_INTEL_variable_length_array extension."))
|
|
return nullptr;
|
|
|
|
return mapValue(V,
|
|
BM->addInstTemplate(OpVariableLengthArrayINTEL,
|
|
{Length->getId()}, BB, TranslatedTy));
|
|
}
|
|
SPIRVType *VarTy = TranslatedTy;
|
|
if (V->getType()->getPointerAddressSpace() == SPIRAS_Generic) {
|
|
if (TranslatedTy->isTypeUntypedPointerKHR())
|
|
VarTy = BM->addPointerType(StorageClassFunction, nullptr);
|
|
else
|
|
VarTy = BM->addPointerType(StorageClassFunction,
|
|
TranslatedTy->getPointerElementType());
|
|
}
|
|
|
|
SPIRVValue *Var = BM->addVariable(
|
|
VarTy,
|
|
VarTy->isTypeUntypedPointerKHR() ? transType(Alc->getAllocatedType())
|
|
: nullptr,
|
|
false, spv::internal::LinkageTypeInternal, nullptr,
|
|
Alc->getName().str(), StorageClassFunction, BB);
|
|
if (V->getType()->getPointerAddressSpace() == SPIRAS_Generic) {
|
|
SPIRVValue *Cast =
|
|
BM->addUnaryInst(OpPtrCastToGeneric, TranslatedTy, Var, BB);
|
|
return mapValue(V, Cast);
|
|
}
|
|
return mapValue(V, Var);
|
|
}
|
|
|
|
if (auto *Switch = dyn_cast<SwitchInst>(V)) {
|
|
std::vector<SPIRVSwitch::PairTy> Pairs;
|
|
auto *Select = transValue(Switch->getCondition(), BB);
|
|
|
|
for (auto I = Switch->case_begin(), E = Switch->case_end(); I != E; ++I) {
|
|
SPIRVSwitch::LiteralTy Lit;
|
|
uint64_t CaseValue = I->getCaseValue()->getZExtValue();
|
|
|
|
Lit.push_back(CaseValue);
|
|
assert(Select->getType()->getBitWidth() <= 64 &&
|
|
"unexpected selector bitwidth");
|
|
if (Select->getType()->getBitWidth() == 64)
|
|
Lit.push_back(CaseValue >> 32);
|
|
|
|
Pairs.push_back(
|
|
std::make_pair(Lit, static_cast<SPIRVBasicBlock *>(
|
|
transValue(I->getCaseSuccessor(), nullptr))));
|
|
}
|
|
|
|
return mapValue(
|
|
V, BM->addSwitchInst(Select,
|
|
static_cast<SPIRVBasicBlock *>(
|
|
transValue(Switch->getDefaultDest(), nullptr)),
|
|
Pairs, BB));
|
|
}
|
|
|
|
if (BranchInst *Branch = dyn_cast<BranchInst>(V)) {
|
|
SPIRVLabel *SuccessorTrue =
|
|
static_cast<SPIRVLabel *>(transValue(Branch->getSuccessor(0), BB));
|
|
|
|
/// Clang attaches !llvm.loop metadata to "latch" BB. This kind of blocks
|
|
/// has an edge directed to the loop header. Thus latch BB matching to
|
|
/// "Continue Target" per the SPIR-V spec. This statement is true only after
|
|
/// applying the loop-simplify pass to the LLVM module.
|
|
/// For "for" and "while" loops latch BB is terminated by an
|
|
/// unconditional branch. Also for this kind of loops "Merge Block" can
|
|
/// be found as block targeted by false edge of the "Header" BB.
|
|
/// For "do while" loop the latch is terminated by a conditional branch
|
|
/// with true edge going to the header and the false edge going out of
|
|
/// the loop, which corresponds to a "Merge Block" per the SPIR-V spec.
|
|
std::vector<SPIRVWord> Parameters;
|
|
spv::LoopControlMask LoopControl = getLoopControl(Branch, Parameters);
|
|
|
|
if (Branch->isUnconditional()) {
|
|
// Usually, "for" and "while" loops llvm.loop metadata is attached to an
|
|
// unconditional branch instruction.
|
|
if (LoopControl != spv::LoopControlMaskNone) {
|
|
// SuccessorTrue is the loop header BB.
|
|
const SPIRVInstruction *Term = SuccessorTrue->getTerminateInstr();
|
|
if (Term && Term->getOpCode() == OpBranchConditional) {
|
|
const auto *Br = static_cast<const SPIRVBranchConditional *>(Term);
|
|
BM->addLoopMergeInst(Br->getFalseLabel()->getId(), // Merge Block
|
|
BB->getId(), // Continue Target
|
|
LoopControl, Parameters, SuccessorTrue);
|
|
} else {
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_unstructured_loop_controls)) {
|
|
// For unstructured loop we add a special loop control instruction.
|
|
// Simple example of unstructured loop is an infinite loop, that has
|
|
// no terminate instruction.
|
|
BM->addLoopControlINTELInst(LoopControl, Parameters, SuccessorTrue);
|
|
}
|
|
}
|
|
}
|
|
return mapValue(V, BM->addBranchInst(SuccessorTrue, BB));
|
|
}
|
|
// For "do-while" (and in some cases, for "for" and "while") loops,
|
|
// llvm.loop metadata is attached to a conditional branch instructions
|
|
SPIRVLabel *SuccessorFalse =
|
|
static_cast<SPIRVLabel *>(transValue(Branch->getSuccessor(1), BB));
|
|
if (LoopControl != spv::LoopControlMaskNone) {
|
|
Function *Fun = Branch->getFunction();
|
|
DominatorTree DomTree(*Fun);
|
|
LoopInfo LI(DomTree);
|
|
for (const auto *LoopObj : LI.getLoopsInPreorder()) {
|
|
// Check whether SuccessorFalse or SuccessorTrue is the loop header BB.
|
|
// For example consider following LLVM IR:
|
|
// br i1 %compare, label %for.body, label %for.end
|
|
// <- SuccessorTrue is 'for.body' aka successor(0)
|
|
// br i1 %compare.not, label %for.end, label %for.body
|
|
// <- SuccessorTrue is 'for.end' aka successor(1)
|
|
// meanwhile the true successor (by definition) should be a loop header
|
|
// aka 'for.body'
|
|
if (LoopObj->getHeader() == Branch->getSuccessor(1))
|
|
// SuccessorFalse is the loop header BB.
|
|
BM->addLoopMergeInst(SuccessorTrue->getId(), // Merge Block
|
|
BB->getId(), // Continue Target
|
|
LoopControl, Parameters, SuccessorFalse);
|
|
else
|
|
// SuccessorTrue is the loop header BB.
|
|
BM->addLoopMergeInst(SuccessorFalse->getId(), // Merge Block
|
|
BB->getId(), // Continue Target
|
|
LoopControl, Parameters, SuccessorTrue);
|
|
}
|
|
}
|
|
return mapValue(
|
|
V, BM->addBranchConditionalInst(transValue(Branch->getCondition(), BB),
|
|
SuccessorTrue, SuccessorFalse, BB));
|
|
}
|
|
|
|
if (auto *Phi = dyn_cast<PHINode>(V)) {
|
|
std::vector<SPIRVValue *> IncomingPairs;
|
|
SPIRVType *Ty = transScavengedType(Phi);
|
|
|
|
for (size_t I = 0, E = Phi->getNumIncomingValues(); I != E; ++I) {
|
|
SPIRVValue *Val = transValue(Phi->getIncomingValue(I), BB, true,
|
|
FuncTransMode::Pointer);
|
|
if (Val->getType() != Ty)
|
|
Val = BM->addUnaryInst(OpBitcast, Ty, Val, BB);
|
|
IncomingPairs.push_back(Val);
|
|
IncomingPairs.push_back(transValue(Phi->getIncomingBlock(I), nullptr));
|
|
}
|
|
return mapValue(V, BM->addPhiInst(Ty, IncomingPairs, BB));
|
|
}
|
|
|
|
if (auto *Ext = dyn_cast<ExtractValueInst>(V)) {
|
|
if (auto *II = dyn_cast<IntrinsicInst>(Ext->getAggregateOperand())) {
|
|
if (II->getIntrinsicID() == Intrinsic::frexp) {
|
|
unsigned Idx = Ext->getIndices()[0];
|
|
auto *Val = transValue(II, BB);
|
|
if (Idx == 0)
|
|
return mapValue(V, Val);
|
|
|
|
// Idx = 1
|
|
SPIRVValue *IntFromFrexpResult =
|
|
static_cast<SPIRVExtInst *>(Val)->getArgValues()[1];
|
|
SPIRVType *LoadTy = nullptr;
|
|
if (IntFromFrexpResult->isUntypedVariable())
|
|
LoadTy = static_cast<SPIRVUntypedVariableKHR *>(IntFromFrexpResult)
|
|
->getDataType();
|
|
|
|
IntFromFrexpResult =
|
|
BM->addLoadInst(IntFromFrexpResult, {}, BB, LoadTy);
|
|
return mapValue(V, IntFromFrexpResult);
|
|
}
|
|
}
|
|
return mapValue(V, BM->addCompositeExtractInst(
|
|
transScavengedType(Ext),
|
|
transValue(Ext->getAggregateOperand(), BB),
|
|
Ext->getIndices(), BB));
|
|
}
|
|
|
|
if (auto *Ins = dyn_cast<InsertValueInst>(V)) {
|
|
return mapValue(V, BM->addCompositeInsertInst(
|
|
transValue(Ins->getInsertedValueOperand(), BB),
|
|
transValue(Ins->getAggregateOperand(), BB),
|
|
Ins->getIndices(), BB));
|
|
}
|
|
|
|
if (UnaryInstruction *U = dyn_cast<UnaryInstruction>(V)) {
|
|
auto *UI = transUnaryInst(U, BB);
|
|
return mapValue(V, UI ? UI : transValue(U->getOperand(0), BB));
|
|
}
|
|
|
|
if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(V)) {
|
|
auto *PointerOperand = GEP->getPointerOperand();
|
|
auto *SPVPointerOperand = transValue(PointerOperand, BB);
|
|
std::vector<SPIRVWord> Ops = {SPVPointerOperand->getId()};
|
|
for (unsigned I = 0, E = GEP->getNumIndices(); I != E; ++I)
|
|
Ops.push_back(transValue(GEP->getOperand(I + 1), BB)->getId());
|
|
|
|
// Certain array-related optimization hints can be expressed via
|
|
// LLVM metadata. For the purpose of linking this metadata with
|
|
// the accessed array variables, our GEP may have been marked into
|
|
// a so-called index group, an MDNode by itself.
|
|
if (MDNode *IndexGroup = GEP->getMetadata("llvm.index.group")) {
|
|
SPIRVValue *ActualMemoryPtr = BM->getValue(Ops[0]);
|
|
// If the source is a no-op bitcast (generated to fix up types), look
|
|
// through it to the underlying gep if possible.
|
|
if (auto *BC = dyn_cast<CastInst>(PointerOperand))
|
|
if (BC->getSrcTy() == BC->getDestTy()) {
|
|
PointerOperand = BC->getOperand(0);
|
|
}
|
|
if (auto *Load = dyn_cast<LoadInst>(PointerOperand)) {
|
|
ActualMemoryPtr = transValue(Load->getPointerOperand(), BB);
|
|
}
|
|
SPIRVId AccessedArrayId = ActualMemoryPtr->getId();
|
|
unsigned NumOperands = IndexGroup->getNumOperands();
|
|
// When we're working with embedded loops, it's natural that
|
|
// the outer loop's hints apply to all code contained within.
|
|
// The inner loop's specific hints, however, should stay private
|
|
// to the inner loop's scope.
|
|
// Consequently, the following division of the index group metadata
|
|
// nodes emerges:
|
|
|
|
// 1) The metadata node has no operands. It will be directly referenced
|
|
// from within the optimization hint metadata.
|
|
if (NumOperands == 0)
|
|
IndexGroupArrayMap[IndexGroup].insert(AccessedArrayId);
|
|
// 2) The metadata node has several operands. It serves to link an index
|
|
// group specific to some embedded loop with other index groups that
|
|
// mark the same array variable for the outer loop(s).
|
|
for (unsigned I = 0; I < NumOperands; ++I) {
|
|
auto *ContainedIndexGroup = getMDOperandAsMDNode(IndexGroup, I);
|
|
IndexGroupArrayMap[ContainedIndexGroup].insert(AccessedArrayId);
|
|
}
|
|
}
|
|
// GEP can return a vector of pointers, in this case GEP will calculate
|
|
// addresses for each pointer in the vector
|
|
SPIRVType *TranslatedTy = transScavengedType(GEP);
|
|
if (TranslatedTy->isTypeUntypedPointerKHR() ||
|
|
SPVPointerOperand->getType()->isTypeUntypedPointerKHR()) {
|
|
llvm::Type *Ty = Scavenger->getScavengedType(PointerOperand);
|
|
SPIRVType *PtrTy = nullptr;
|
|
if (auto *TPT = dyn_cast<TypedPointerType>(Ty)) {
|
|
PtrTy = transType(TPT->getElementType());
|
|
Ops = getVec(PtrTy->getId(), Ops);
|
|
}
|
|
}
|
|
if (SPVPointerOperand->getType()->isTypeUntypedPointerKHR())
|
|
// Untyped pointer as an input operand implies we should use untyped
|
|
// access chain instructions. Replace return type to do that.
|
|
TranslatedTy = SPVPointerOperand->getType();
|
|
|
|
return mapValue(V, BM->addPtrAccessChainInst(TranslatedTy, std::move(Ops),
|
|
BB, GEP->isInBounds()));
|
|
}
|
|
|
|
if (auto *Ext = dyn_cast<ExtractElementInst>(V)) {
|
|
auto *Index = Ext->getIndexOperand();
|
|
if (auto *Const = dyn_cast<ConstantInt>(Index))
|
|
return mapValue(V, BM->addCompositeExtractInst(
|
|
transScavengedType(Ext),
|
|
transValue(Ext->getVectorOperand(), BB),
|
|
std::vector<SPIRVWord>(1, Const->getZExtValue()),
|
|
BB));
|
|
else
|
|
return mapValue(V, BM->addVectorExtractDynamicInst(
|
|
transValue(Ext->getVectorOperand(), BB),
|
|
transValue(Index, BB), BB));
|
|
}
|
|
|
|
if (auto *Ins = dyn_cast<InsertElementInst>(V)) {
|
|
auto *Index = Ins->getOperand(2);
|
|
if (auto *Const = dyn_cast<ConstantInt>(Index)) {
|
|
return mapValue(
|
|
V,
|
|
BM->addCompositeInsertInst(
|
|
transValue(Ins->getOperand(1), BB, true, FuncTransMode::Pointer),
|
|
transValue(Ins->getOperand(0), BB),
|
|
std::vector<SPIRVWord>(1, Const->getZExtValue()), BB));
|
|
} else
|
|
return mapValue(
|
|
V, BM->addVectorInsertDynamicInst(transValue(Ins->getOperand(0), BB),
|
|
transValue(Ins->getOperand(1), BB),
|
|
transValue(Index, BB), BB));
|
|
}
|
|
|
|
if (auto *SF = dyn_cast<ShuffleVectorInst>(V)) {
|
|
std::vector<SPIRVWord> Comp;
|
|
for (auto &I : SF->getShuffleMask())
|
|
Comp.push_back(I);
|
|
return mapValue(V, BM->addVectorShuffleInst(
|
|
transScavengedType(SF),
|
|
transValue(SF->getOperand(0), BB),
|
|
transValue(SF->getOperand(1), BB), Comp, BB));
|
|
}
|
|
|
|
if (AtomicRMWInst *ARMW = dyn_cast<AtomicRMWInst>(V)) {
|
|
AtomicRMWInst::BinOp Op = ARMW->getOperation();
|
|
bool SupportedAtomicInst =
|
|
AtomicRMWInst::isFPOperation(Op)
|
|
? (Op == AtomicRMWInst::FAdd || Op == AtomicRMWInst::FSub ||
|
|
Op == AtomicRMWInst::FMin || Op == AtomicRMWInst::FMax)
|
|
: Op != AtomicRMWInst::Nand;
|
|
if (!BM->getErrorLog().checkError(
|
|
SupportedAtomicInst, SPIRVEC_InvalidInstruction, V,
|
|
"Atomic " + AtomicRMWInst::getOperationName(Op).str() +
|
|
" is not supported in SPIR-V!\n"))
|
|
return nullptr;
|
|
|
|
AtomicOrderingCABI Ordering = llvm::toCABI(ARMW->getOrdering());
|
|
auto MemSem = OCLMemOrderMap::map(static_cast<OCLMemOrderKind>(Ordering));
|
|
std::vector<Value *> Operands(4);
|
|
Operands[0] = ARMW->getPointerOperand();
|
|
spv::Scope S = toSPIRVScope(ARMW->getContext(), ARMW->getSyncScopeID());
|
|
Operands[1] = getUInt32(M, S);
|
|
Operands[2] = getUInt32(M, MemSem);
|
|
Operands[3] = ARMW->getValOperand();
|
|
std::vector<SPIRVValue *> OpVals = transValue(Operands, BB);
|
|
std::vector<SPIRVId> Ops = BM->getIds(OpVals);
|
|
SPIRVType *Ty = transScavengedType(ARMW);
|
|
|
|
spv::Op OC;
|
|
if (Op == AtomicRMWInst::FSub) {
|
|
// Implement FSub through FNegate and AtomicFAddExt
|
|
Ops[3] = BM->addUnaryInst(OpFNegate, Ty, OpVals[3], BB)->getId();
|
|
OC = OpAtomicFAddEXT;
|
|
} else
|
|
OC = LLVMSPIRVAtomicRmwOpCodeMap::map(Op);
|
|
|
|
return mapValue(V, BM->addInstTemplate(OC, Ops, BB, Ty));
|
|
}
|
|
|
|
if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(V)) {
|
|
SPIRVValue *BV = transIntrinsicInst(II, BB);
|
|
return BV ? mapValue(V, BV) : nullptr;
|
|
}
|
|
|
|
if (FenceInst *FI = dyn_cast<FenceInst>(V)) {
|
|
SPIRVValue *BV = transFenceInst(FI, BB);
|
|
return BV ? mapValue(V, BV) : nullptr;
|
|
}
|
|
|
|
if (InlineAsm *IA = dyn_cast<InlineAsm>(V))
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_inline_assembly))
|
|
return mapValue(V, transAsmINTEL(IA));
|
|
|
|
if (CallInst *CI = dyn_cast<CallInst>(V)) {
|
|
if (auto *Alias =
|
|
dyn_cast_or_null<llvm::GlobalAlias>(CI->getCalledOperand())) {
|
|
CI->setCalledFunction(cast<Function>(Alias->getAliasee()));
|
|
}
|
|
return mapValue(V, transCallInst(CI, BB));
|
|
}
|
|
|
|
if (Instruction *Inst = dyn_cast<Instruction>(V)) {
|
|
BM->SPIRVCK(false, InvalidInstruction, toString(Inst));
|
|
}
|
|
|
|
llvm_unreachable("Not implemented");
|
|
return nullptr;
|
|
}
|
|
|
|
SPIRVType *LLVMToSPIRVBase::mapType(Type *T, SPIRVType *BT) {
|
|
assert(!T->isPointerTy() && "Pointer types cannot be stored in the type map");
|
|
[[maybe_unused]] auto EmplaceStatus = TypeMap.try_emplace(T, BT);
|
|
assert(EmplaceStatus.second && "The type was already added to the map");
|
|
SPIRVDBG(dbgs() << "[mapType] " << *T << " => "; spvdbgs() << *BT << '\n');
|
|
return BT;
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::mapValue(Value *V, SPIRVValue *BV) {
|
|
auto Loc = ValueMap.find(V);
|
|
if (Loc != ValueMap.end()) {
|
|
if (Loc->second == BV)
|
|
return BV;
|
|
assert(Loc->second->isForward() &&
|
|
"LLVM Value is mapped to different SPIRV Values");
|
|
auto *Forward = static_cast<SPIRVForward *>(Loc->second);
|
|
BM->replaceForward(Forward, BV);
|
|
}
|
|
ValueMap[V] = BV;
|
|
SPIRVDBG(dbgs() << "[mapValue] " << *V << " => "; spvdbgs() << BV << "\n");
|
|
return BV;
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::shouldTryToAddMemAliasingDecoration(Instruction *Inst) {
|
|
// Limit translation of aliasing metadata with only this set of instructions
|
|
// gracefully considering others as compilation mistakes and ignoring them
|
|
if (!Inst->mayReadOrWriteMemory())
|
|
return false;
|
|
// Loads and Stores are handled during memory access mask addition
|
|
if (isa<StoreInst>(Inst) || isa<LoadInst>(Inst))
|
|
return false;
|
|
CallInst *CI = dyn_cast<CallInst>(Inst);
|
|
if (!CI)
|
|
return true;
|
|
if (Function *Fun = CI->getCalledFunction()) {
|
|
// Calls to intrinsics are skipped. At some point lifetime start/end will be
|
|
// handled separately, but specification isn't ready.
|
|
if (Fun->isIntrinsic())
|
|
return false;
|
|
// Also skip SPIR-V instructions that don't have result id to attach the
|
|
// decorations
|
|
if (isBuiltinTransToInst(Fun))
|
|
if (Fun->getReturnType()->isVoidTy())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void addFuncPointerCallArgumentAttributes(CallInst *CI,
|
|
SPIRVValue *FuncPtrCall) {
|
|
for (unsigned ArgNo = 0; ArgNo < CI->arg_size(); ++ArgNo) {
|
|
if (CI->doesNotCapture(ArgNo))
|
|
FuncPtrCall->addDecorate(new SPIRVDecorate(
|
|
spv::internal::DecorationArgumentAttributeINTEL, FuncPtrCall, ArgNo,
|
|
FunctionParameterAttributeNoCapture));
|
|
for (const auto &I : CI->getAttributes().getParamAttrs(ArgNo)) {
|
|
spv::FunctionParameterAttribute Attr = spv::FunctionParameterAttributeMax;
|
|
SPIRSPIRVFuncParamAttrMap::find(I.getKindAsEnum(), &Attr);
|
|
if (Attr != spv::FunctionParameterAttributeMax)
|
|
FuncPtrCall->addDecorate(
|
|
new SPIRVDecorate(spv::internal::DecorationArgumentAttributeINTEL,
|
|
FuncPtrCall, ArgNo, Attr));
|
|
}
|
|
}
|
|
}
|
|
|
|
#define ONE_STRING_DECORATION_CASE(NAME, NAMESPACE) \
|
|
case NAMESPACE::Decoration##NAME: { \
|
|
ErrLog.checkError(NumOperands == 2, SPIRVEC_InvalidLlvmModule, \
|
|
#NAME " requires exactly 1 extra operand"); \
|
|
auto *StrDecoEO = dyn_cast<MDString>(DecoMD->getOperand(1)); \
|
|
ErrLog.checkError(StrDecoEO, SPIRVEC_InvalidLlvmModule, \
|
|
#NAME " requires extra operand to be a string"); \
|
|
Target->addDecorate( \
|
|
new SPIRVDecorate##NAME##Attr(Target, StrDecoEO->getString().str())); \
|
|
break; \
|
|
}
|
|
|
|
#define ONE_INT_DECORATION_CASE(NAME, NAMESPACE, TYPE) \
|
|
case NAMESPACE::Decoration##NAME: { \
|
|
ErrLog.checkError(NumOperands == 2, SPIRVEC_InvalidLlvmModule, \
|
|
#NAME " requires exactly 1 extra operand"); \
|
|
auto *IntDecoEO = \
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1)); \
|
|
ErrLog.checkError(IntDecoEO, SPIRVEC_InvalidLlvmModule, \
|
|
#NAME " requires extra operand to be an integer"); \
|
|
Target->addDecorate(new SPIRVDecorate##NAME( \
|
|
Target, static_cast<TYPE>(IntDecoEO->getZExtValue()))); \
|
|
break; \
|
|
}
|
|
|
|
#define TWO_INT_DECORATION_CASE(NAME, NAMESPACE, TYPE1, TYPE2) \
|
|
case NAMESPACE::Decoration##NAME: { \
|
|
ErrLog.checkError(NumOperands == 3, SPIRVEC_InvalidLlvmModule, \
|
|
#NAME " requires exactly 2 extra operands"); \
|
|
auto *IntDecoEO1 = \
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1)); \
|
|
ErrLog.checkError(IntDecoEO1, SPIRVEC_InvalidLlvmModule, \
|
|
#NAME " requires first extra operand to be an integer"); \
|
|
auto *IntDecoEO2 = \
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(2)); \
|
|
ErrLog.checkError(IntDecoEO2, SPIRVEC_InvalidLlvmModule, \
|
|
#NAME \
|
|
" requires second extra operand to be an integer"); \
|
|
Target->addDecorate(new SPIRVDecorate##NAME( \
|
|
Target, static_cast<TYPE1>(IntDecoEO1->getZExtValue()), \
|
|
static_cast<TYPE2>(IntDecoEO2->getZExtValue()))); \
|
|
break; \
|
|
}
|
|
|
|
void checkIsGlobalVar(SPIRVEntry *E, Decoration Dec) {
|
|
std::string ErrStr =
|
|
SPIRVDecorationNameMap::map(Dec) + " can only be applied to a variable";
|
|
|
|
E->getErrorLog().checkError(E->isVariable(), SPIRVEC_InvalidModule, ErrStr);
|
|
|
|
auto AddrSpace = SPIRSPIRVAddrSpaceMap::rmap(
|
|
static_cast<SPIRVVariableBase *>(E)->getStorageClass());
|
|
ErrStr += " in a global (module) scope";
|
|
E->getErrorLog().checkError(AddrSpace == SPIRAS_Global, SPIRVEC_InvalidModule,
|
|
ErrStr);
|
|
}
|
|
|
|
static void transMetadataDecorations(Metadata *MD, SPIRVValue *Target) {
|
|
SPIRVErrorLog &ErrLog = Target->getErrorLog();
|
|
|
|
auto *ArgDecoMD = dyn_cast<MDNode>(MD);
|
|
assert(ArgDecoMD && "Decoration list must be a metadata node");
|
|
for (unsigned I = 0, E = ArgDecoMD->getNumOperands(); I != E; ++I) {
|
|
auto *DecoMD = dyn_cast<MDNode>(ArgDecoMD->getOperand(I));
|
|
ErrLog.checkError(DecoMD, SPIRVEC_InvalidLlvmModule,
|
|
"Decoration does not name metadata");
|
|
ErrLog.checkError(DecoMD->getNumOperands() > 0, SPIRVEC_InvalidLlvmModule,
|
|
"Decoration metadata must have at least one operand");
|
|
auto *DecoKindConst =
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(0));
|
|
ErrLog.checkError(DecoKindConst, SPIRVEC_InvalidLlvmModule,
|
|
"First operand of decoration must be the kind");
|
|
auto DecoKind = static_cast<Decoration>(DecoKindConst->getZExtValue());
|
|
|
|
const size_t NumOperands = DecoMD->getNumOperands();
|
|
switch (static_cast<size_t>(DecoKind)) {
|
|
case DecorationAlignment: {
|
|
// Handle Alignment via SPIRVValue::setAlignment() to avoid duplicate
|
|
// Alignment decorations.
|
|
auto *Alignment =
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1));
|
|
ErrLog.checkError(Alignment, SPIRVEC_InvalidLlvmModule,
|
|
"Alignment operand must be an integer.");
|
|
Target->setAlignment(Alignment->getZExtValue());
|
|
break;
|
|
}
|
|
|
|
ONE_STRING_DECORATION_CASE(MemoryINTEL, spv)
|
|
ONE_STRING_DECORATION_CASE(UserSemantic, spv)
|
|
ONE_INT_DECORATION_CASE(AliasScopeINTEL, spv, SPIRVId)
|
|
ONE_INT_DECORATION_CASE(NoAliasINTEL, spv, SPIRVId)
|
|
ONE_INT_DECORATION_CASE(InitiationIntervalINTEL, spv, SPIRVWord)
|
|
ONE_INT_DECORATION_CASE(MaxConcurrencyINTEL, spv, SPIRVWord)
|
|
ONE_INT_DECORATION_CASE(PipelineEnableINTEL, spv, SPIRVWord)
|
|
TWO_INT_DECORATION_CASE(FunctionRoundingModeINTEL, spv, SPIRVWord,
|
|
FPRoundingMode);
|
|
TWO_INT_DECORATION_CASE(FunctionDenormModeINTEL, spv, SPIRVWord,
|
|
FPDenormMode);
|
|
TWO_INT_DECORATION_CASE(FunctionFloatingPointModeINTEL, spv, SPIRVWord,
|
|
FPOperationMode);
|
|
TWO_INT_DECORATION_CASE(FuseLoopsInFunctionINTEL, spv, SPIRVWord,
|
|
SPIRVWord);
|
|
TWO_INT_DECORATION_CASE(MathOpDSPModeINTEL, spv, SPIRVWord, SPIRVWord);
|
|
|
|
case DecorationConduitKernelArgumentINTEL:
|
|
case DecorationRegisterMapKernelArgumentINTEL:
|
|
case DecorationStableKernelArgumentINTEL:
|
|
case DecorationRestrict: {
|
|
Target->addDecorate(new SPIRVDecorate(DecoKind, Target));
|
|
break;
|
|
}
|
|
case DecorationBufferLocationINTEL:
|
|
case DecorationMMHostInterfaceReadWriteModeINTEL:
|
|
case DecorationMMHostInterfaceAddressWidthINTEL:
|
|
case DecorationMMHostInterfaceDataWidthINTEL:
|
|
case DecorationMMHostInterfaceLatencyINTEL:
|
|
case DecorationMMHostInterfaceMaxBurstINTEL:
|
|
case DecorationMMHostInterfaceWaitRequestINTEL: {
|
|
ErrLog.checkError(NumOperands == 2, SPIRVEC_InvalidLlvmModule,
|
|
"MMHost Kernel Argument Annotation requires exactly 2 "
|
|
"extra operands");
|
|
auto *DecoValEO1 =
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1));
|
|
Target->addDecorate(
|
|
new SPIRVDecorate(DecoKind, Target, DecoValEO1->getZExtValue()));
|
|
break;
|
|
}
|
|
case DecorationStallEnableINTEL: {
|
|
Target->addDecorate(new SPIRVDecorateStallEnableINTEL(Target));
|
|
break;
|
|
}
|
|
case DecorationStallFreeINTEL: {
|
|
Target->addDecorate(new SPIRVDecorateStallFreeINTEL(Target));
|
|
break;
|
|
}
|
|
case DecorationMergeINTEL: {
|
|
ErrLog.checkError(NumOperands == 3, SPIRVEC_InvalidLlvmModule,
|
|
"MergeINTEL requires exactly 3 extra operands");
|
|
auto *Name = dyn_cast<MDString>(DecoMD->getOperand(1));
|
|
ErrLog.checkError(
|
|
Name, SPIRVEC_InvalidLlvmModule,
|
|
"MergeINTEL requires first extra operand to be a string");
|
|
auto *Direction = dyn_cast<MDString>(DecoMD->getOperand(2));
|
|
ErrLog.checkError(
|
|
Direction, SPIRVEC_InvalidLlvmModule,
|
|
"MergeINTEL requires second extra operand to be a string");
|
|
Target->addDecorate(new SPIRVDecorateMergeINTELAttr(
|
|
Target, Name->getString().str(), Direction->getString().str()));
|
|
break;
|
|
}
|
|
case DecorationLinkageAttributes: {
|
|
ErrLog.checkError(NumOperands == 3, SPIRVEC_InvalidLlvmModule,
|
|
"LinkageAttributes requires exactly 3 extra operands");
|
|
auto *Name = dyn_cast<MDString>(DecoMD->getOperand(1));
|
|
ErrLog.checkError(
|
|
Name, SPIRVEC_InvalidLlvmModule,
|
|
"LinkageAttributes requires first extra operand to be a string");
|
|
auto *Type = mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(2));
|
|
ErrLog.checkError(
|
|
Type, SPIRVEC_InvalidLlvmModule,
|
|
"LinkageAttributes requires second extra operand to be an int");
|
|
auto TypeKind = static_cast<SPIRVLinkageTypeKind>(Type->getZExtValue());
|
|
Target->addDecorate(new SPIRVDecorateLinkageAttr(
|
|
Target, Name->getString().str(), TypeKind));
|
|
break;
|
|
}
|
|
|
|
case spv::internal::DecorationHostAccessINTEL:
|
|
case DecorationHostAccessINTEL: {
|
|
checkIsGlobalVar(Target, DecoKind);
|
|
|
|
ErrLog.checkError(NumOperands == 3, SPIRVEC_InvalidLlvmModule,
|
|
"HostAccessINTEL requires exactly 2 extra operands "
|
|
"after the decoration kind number");
|
|
auto *AccessMode =
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1));
|
|
ErrLog.checkError(
|
|
AccessMode, SPIRVEC_InvalidLlvmModule,
|
|
"HostAccessINTEL requires first extra operand to be an int");
|
|
|
|
HostAccessQualifier Q =
|
|
static_cast<HostAccessQualifier>(AccessMode->getZExtValue());
|
|
auto *Name = dyn_cast<MDString>(DecoMD->getOperand(2));
|
|
ErrLog.checkError(
|
|
Name, SPIRVEC_InvalidLlvmModule,
|
|
"HostAccessINTEL requires second extra operand to be a string");
|
|
|
|
if (DecoKind == DecorationHostAccessINTEL)
|
|
Target->addDecorate(new SPIRVDecorateHostAccessINTEL(
|
|
Target, Q, Name->getString().str()));
|
|
else
|
|
Target->addDecorate(new SPIRVDecorateHostAccessINTELLegacy(
|
|
Target, Q, Name->getString().str()));
|
|
break;
|
|
}
|
|
|
|
case spv::internal::DecorationInitModeINTEL:
|
|
case DecorationInitModeINTEL: {
|
|
checkIsGlobalVar(Target, DecoKind);
|
|
ErrLog.checkError(
|
|
static_cast<SPIRVVariableBase *>(Target)->getInitializer(),
|
|
SPIRVEC_InvalidLlvmModule,
|
|
"InitModeINTEL only be applied to a global (module "
|
|
"scope) variable which has an Initializer operand");
|
|
|
|
ErrLog.checkError(NumOperands == 2, SPIRVEC_InvalidLlvmModule,
|
|
"InitModeINTEL requires exactly 1 extra operand");
|
|
auto *Trigger = mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1));
|
|
ErrLog.checkError(Trigger, SPIRVEC_InvalidLlvmModule,
|
|
"InitModeINTEL requires extra operand to be an int");
|
|
|
|
InitializationModeQualifier Q =
|
|
static_cast<InitializationModeQualifier>(Trigger->getZExtValue());
|
|
|
|
if (DecoKind == DecorationInitModeINTEL)
|
|
Target->addDecorate(new SPIRVDecorateInitModeINTEL(Target, Q));
|
|
else
|
|
Target->addDecorate(new SPIRVDecorateInitModeINTELLegacy(Target, Q));
|
|
|
|
break;
|
|
}
|
|
case spv::internal::DecorationImplementInCSRINTEL: {
|
|
checkIsGlobalVar(Target, DecoKind);
|
|
ErrLog.checkError(NumOperands == 2, SPIRVEC_InvalidLlvmModule,
|
|
"ImplementInCSRINTEL requires exactly 1 extra operand");
|
|
auto *Value = mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1));
|
|
ErrLog.checkError(
|
|
Value, SPIRVEC_InvalidLlvmModule,
|
|
"ImplementInCSRINTEL requires extra operand to be an integer");
|
|
|
|
Target->addDecorate(
|
|
new SPIRVDecorateImplementInCSRINTEL(Target, Value->getZExtValue()));
|
|
break;
|
|
}
|
|
case DecorationImplementInRegisterMapINTEL: {
|
|
checkIsGlobalVar(Target, DecoKind);
|
|
ErrLog.checkError(
|
|
NumOperands == 2, SPIRVEC_InvalidLlvmModule,
|
|
"ImplementInRegisterMapINTEL requires exactly 1 extra operand");
|
|
auto *Value = mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1));
|
|
ErrLog.checkError(Value, SPIRVEC_InvalidLlvmModule,
|
|
"ImplementInRegisterMapINTEL requires extra operand to "
|
|
"be an integer");
|
|
|
|
Target->addDecorate(new SPIRVDecorateImplementInRegisterMapINTEL(
|
|
Target, Value->getZExtValue()));
|
|
|
|
break;
|
|
}
|
|
|
|
case DecorationCacheControlLoadINTEL: {
|
|
ErrLog.checkError(
|
|
NumOperands == 3, SPIRVEC_InvalidLlvmModule,
|
|
"CacheControlLoadINTEL requires exactly 2 extra operands");
|
|
auto *CacheLevel =
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1));
|
|
auto *CacheControl =
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(2));
|
|
ErrLog.checkError(CacheLevel, SPIRVEC_InvalidLlvmModule,
|
|
"CacheControlLoadINTEL cache level operand is required "
|
|
"to be an integer");
|
|
ErrLog.checkError(CacheControl, SPIRVEC_InvalidLlvmModule,
|
|
"CacheControlLoadINTEL cache control operand is "
|
|
"required to be an integer");
|
|
|
|
Target->addDecorate(new SPIRVDecorateCacheControlLoadINTEL(
|
|
Target, CacheLevel->getZExtValue(),
|
|
static_cast<LoadCacheControl>(CacheControl->getZExtValue())));
|
|
break;
|
|
}
|
|
case DecorationCacheControlStoreINTEL: {
|
|
ErrLog.checkError(
|
|
NumOperands == 3, SPIRVEC_InvalidLlvmModule,
|
|
"CacheControlStoreINTEL requires exactly 2 extra operands");
|
|
auto *CacheLevel =
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1));
|
|
auto *CacheControl =
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(2));
|
|
ErrLog.checkError(CacheLevel, SPIRVEC_InvalidLlvmModule,
|
|
"CacheControlStoreINTEL cache level operand is "
|
|
"required to be an integer");
|
|
ErrLog.checkError(CacheControl, SPIRVEC_InvalidLlvmModule,
|
|
"CacheControlStoreINTEL cache control operand is "
|
|
"required to be an integer");
|
|
|
|
Target->addDecorate(new SPIRVDecorateCacheControlStoreINTEL(
|
|
Target, CacheLevel->getZExtValue(),
|
|
static_cast<StoreCacheControl>(CacheControl->getZExtValue())));
|
|
break;
|
|
}
|
|
default: {
|
|
if (NumOperands == 1) {
|
|
Target->addDecorate(new SPIRVDecorate(DecoKind, Target));
|
|
break;
|
|
}
|
|
|
|
auto *DecoValEO1 =
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(1));
|
|
ErrLog.checkError(
|
|
DecoValEO1, SPIRVEC_InvalidLlvmModule,
|
|
"First extra operand in default decoration case must be integer.");
|
|
if (NumOperands == 2) {
|
|
Target->addDecorate(
|
|
new SPIRVDecorate(DecoKind, Target, DecoValEO1->getZExtValue()));
|
|
break;
|
|
}
|
|
|
|
auto *DecoValEO2 =
|
|
mdconst::dyn_extract<ConstantInt>(DecoMD->getOperand(2));
|
|
ErrLog.checkError(
|
|
DecoValEO2, SPIRVEC_InvalidLlvmModule,
|
|
"Second extra operand in default decoration case must be integer.");
|
|
|
|
ErrLog.checkError(NumOperands == 3, SPIRVEC_InvalidLlvmModule,
|
|
"At most 2 extra operands expected.");
|
|
Target->addDecorate(new SPIRVDecorate(DecoKind, Target,
|
|
DecoValEO1->getZExtValue(),
|
|
DecoValEO2->getZExtValue()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef ONE_STRING_DECORATION_CASE
|
|
#undef ONE_INT_DECORATION_CASE
|
|
#undef TWO_INT_DECORATION_CASE
|
|
|
|
bool LLVMToSPIRVBase::transDecoration(Value *V, SPIRVValue *BV) {
|
|
if (!transAlign(V, BV))
|
|
return false;
|
|
if ((isa<AtomicCmpXchgInst>(V) && cast<AtomicCmpXchgInst>(V)->isVolatile()) ||
|
|
(isa<AtomicRMWInst>(V) && cast<AtomicRMWInst>(V)->isVolatile()))
|
|
BV->setVolatile(true);
|
|
|
|
if (auto *BVO = dyn_cast_or_null<OverflowingBinaryOperator>(V)) {
|
|
if (BVO->hasNoSignedWrap()) {
|
|
BV->setNoIntegerDecorationWrap<DecorationNoSignedWrap>(true);
|
|
}
|
|
if (BVO->hasNoUnsignedWrap()) {
|
|
BV->setNoIntegerDecorationWrap<DecorationNoUnsignedWrap>(true);
|
|
}
|
|
}
|
|
|
|
if (auto *BVF = dyn_cast_or_null<FPMathOperator>(V)) {
|
|
auto Opcode = BVF->getOpcode();
|
|
if (Opcode == Instruction::FAdd || Opcode == Instruction::FSub ||
|
|
Opcode == Instruction::FMul || Opcode == Instruction::FDiv ||
|
|
Opcode == Instruction::FRem ||
|
|
((Opcode == Instruction::FNeg || Opcode == Instruction::FCmp ||
|
|
BV->isExtInst()) &&
|
|
BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_6))) {
|
|
FastMathFlags FMF = BVF->getFastMathFlags();
|
|
SPIRVWord M{0};
|
|
if (FMF.isFast())
|
|
M |= FPFastMathModeFastMask;
|
|
else {
|
|
if (FMF.noNaNs())
|
|
M |= FPFastMathModeNotNaNMask;
|
|
if (FMF.noInfs())
|
|
M |= FPFastMathModeNotInfMask;
|
|
if (FMF.noSignedZeros())
|
|
M |= FPFastMathModeNSZMask;
|
|
if (FMF.allowReciprocal())
|
|
M |= FPFastMathModeAllowRecipMask;
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fp_fast_math_mode)) {
|
|
if (FMF.allowContract()) {
|
|
M |= FPFastMathModeAllowContractFastINTELMask;
|
|
BM->addCapability(CapabilityFPFastMathModeINTEL);
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fp_fast_math_mode);
|
|
}
|
|
if (FMF.allowReassoc()) {
|
|
M |= FPFastMathModeAllowReassocINTELMask;
|
|
BM->addCapability(CapabilityFPFastMathModeINTEL);
|
|
BM->addExtension(ExtensionID::SPV_INTEL_fp_fast_math_mode);
|
|
}
|
|
}
|
|
}
|
|
// Handle nofpclass attribute. Nothing to do if fast math flag is already
|
|
// set.
|
|
if ((BV->isExtInst() &&
|
|
static_cast<SPIRVExtInst *>(BV)->getExtSetKind() ==
|
|
SPIRVEIS_OpenCL) &&
|
|
BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_6) &&
|
|
!(M & FPFastMathModeFastMask)) {
|
|
auto *F = cast<CallInst>(V)->getCalledFunction();
|
|
auto FAttrs = F->getAttributes();
|
|
AttributeSet RetAttrs = FAttrs.getRetAttrs();
|
|
if (RetAttrs.hasAttribute(Attribute::NoFPClass)) {
|
|
FPClassTest RetTest =
|
|
RetAttrs.getAttribute(Attribute::NoFPClass).getNoFPClass();
|
|
// Only Nan and Inf tests are representable in SPIR-V now.
|
|
bool ToAddNoNan = RetTest & fcNan;
|
|
bool ToAddNoInf = RetTest & fcInf;
|
|
if (ToAddNoNan || ToAddNoInf) {
|
|
const auto *FT = F->getFunctionType();
|
|
const size_t NumParams = FT->getNumParams();
|
|
for (size_t I = 0; I != NumParams; ++I) {
|
|
if (!FT->getParamType(I)->isFloatTy())
|
|
continue;
|
|
if (!F->hasParamAttribute(I, Attribute::NoFPClass)) {
|
|
ToAddNoNan = false;
|
|
ToAddNoInf = false;
|
|
break;
|
|
}
|
|
FPClassTest ArgTest =
|
|
FAttrs.getParamAttr(I, Attribute::NoFPClass).getNoFPClass();
|
|
ToAddNoNan = ToAddNoNan && static_cast<bool>(ArgTest & fcNan);
|
|
ToAddNoInf = ToAddNoInf && static_cast<bool>(ArgTest & fcInf);
|
|
}
|
|
}
|
|
if (ToAddNoNan)
|
|
M |= FPFastMathModeNotNaNMask;
|
|
if (ToAddNoInf)
|
|
M |= FPFastMathModeNotInfMask;
|
|
}
|
|
}
|
|
if (M != 0) {
|
|
BV->setFPFastMathMode(M);
|
|
if (Opcode == Instruction::FNeg || Opcode == Instruction::FCmp ||
|
|
BV->isExtInst())
|
|
BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_6);
|
|
}
|
|
}
|
|
}
|
|
if (Instruction *Inst = dyn_cast<Instruction>(V)) {
|
|
if (shouldTryToAddMemAliasingDecoration(Inst))
|
|
transMemAliasingINTELDecorations(Inst, BV);
|
|
if (auto *IDecoMD = Inst->getMetadata(SPIRV_MD_DECORATIONS))
|
|
transMetadataDecorations(IDecoMD, BV);
|
|
if (BV->isInst())
|
|
addFPBuiltinDecoration(BM, Inst, static_cast<SPIRVInstruction *>(BV));
|
|
}
|
|
|
|
if (auto *CI = dyn_cast<CallInst>(V)) {
|
|
auto OC = BV->getOpCode();
|
|
if (OC == OpSpecConstantTrue || OC == OpSpecConstantFalse ||
|
|
OC == OpSpecConstant) {
|
|
auto SpecId = cast<ConstantInt>(CI->getArgOperand(0))->getZExtValue();
|
|
BV->addDecorate(DecorationSpecId, SpecId);
|
|
}
|
|
if (OC == OpFunctionPointerCallINTEL)
|
|
addFuncPointerCallArgumentAttributes(CI, BV);
|
|
}
|
|
|
|
if (auto *GV = dyn_cast<GlobalVariable>(V))
|
|
if (auto *GVDecoMD = GV->getMetadata(SPIRV_MD_DECORATIONS))
|
|
transMetadataDecorations(GVDecoMD, BV);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::transAlign(Value *V, SPIRVValue *BV) {
|
|
if (auto *AL = dyn_cast<AllocaInst>(V)) {
|
|
BM->setAlignment(BV, AL->getAlign().value());
|
|
return true;
|
|
}
|
|
if (auto *GV = dyn_cast<GlobalVariable>(V)) {
|
|
BM->setAlignment(BV, GV->getAlignment());
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Apply aliasing decorations to instructions annotated with aliasing metadata.
|
|
// Do it for any instruction but loads and stores.
|
|
void LLVMToSPIRVBase::transMemAliasingINTELDecorations(Instruction *Inst,
|
|
SPIRVValue *BV) {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_memory_access_aliasing))
|
|
return;
|
|
if (MDNode *AliasingListMD = Inst->getMetadata(LLVMContext::MD_alias_scope)) {
|
|
auto *MemAliasList = addMemAliasingINTELInstructions(BM, AliasingListMD);
|
|
if (!MemAliasList)
|
|
return;
|
|
BV->addDecorate(new SPIRVDecorateId(DecorationAliasScopeINTEL, BV,
|
|
MemAliasList->getId()));
|
|
}
|
|
if (MDNode *AliasingListMD = Inst->getMetadata(LLVMContext::MD_noalias)) {
|
|
auto *MemAliasList = addMemAliasingINTELInstructions(BM, AliasingListMD);
|
|
if (!MemAliasList)
|
|
return;
|
|
BV->addDecorate(
|
|
new SPIRVDecorateId(DecorationNoAliasINTEL, BV, MemAliasList->getId()));
|
|
}
|
|
}
|
|
|
|
/// Do this after source language is set.
|
|
bool LLVMToSPIRVBase::transBuiltinSet() {
|
|
SPIRVId EISId;
|
|
if (!BM->importBuiltinSet("OpenCL.std", &EISId))
|
|
return false;
|
|
if (SPIRVMDWalker(*M).getNamedMD("llvm.dbg.cu")) {
|
|
if (!BM->importBuiltinSet(
|
|
SPIRVBuiltinSetNameMap::map(BM->getDebugInfoEIS()), &EISId))
|
|
return false;
|
|
}
|
|
if (BM->preserveAuxData()) {
|
|
if (!BM->importBuiltinSet(
|
|
SPIRVBuiltinSetNameMap::map(SPIRVEIS_NonSemantic_AuxData), &EISId))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Translate sampler* spcv.cast(i32 arg) or
|
|
/// sampler* __translate_sampler_initializer(i32 arg)
|
|
/// Three cases are possible:
|
|
/// arg = ConstantInt x -> SPIRVConstantSampler
|
|
/// arg = i32 argument -> transValue(arg)
|
|
/// arg = load from sampler -> look through load
|
|
SPIRVValue *LLVMToSPIRVBase::oclTransSpvcCastSampler(CallInst *CI,
|
|
SPIRVBasicBlock *BB) {
|
|
assert(CI->getCalledFunction() && "Unexpected indirect call");
|
|
auto *Arg = CI->getArgOperand(0);
|
|
auto *TransRT = transType(getSPIRVType(OpTypeSampler));
|
|
|
|
auto GetSamplerConstant = [&](uint64_t SamplerValue) {
|
|
auto AddrMode = (SamplerValue & 0xE) >> 1;
|
|
auto Param = SamplerValue & 0x1;
|
|
auto Filter = SamplerValue ? ((SamplerValue & 0x30) >> 4) - 1 : 0;
|
|
auto *BV = BM->addSamplerConstant(TransRT, AddrMode, Param, Filter);
|
|
return BV;
|
|
};
|
|
|
|
if (auto *Const = dyn_cast<ConstantInt>(Arg)) {
|
|
// Sampler is declared as a kernel scope constant
|
|
return GetSamplerConstant(Const->getZExtValue());
|
|
} else if (auto *Load = dyn_cast<LoadInst>(Arg)) {
|
|
// If value of the sampler is loaded from a global constant, use its
|
|
// initializer for initialization of the sampler.
|
|
auto *Op = Load->getPointerOperand();
|
|
assert(isa<GlobalVariable>(Op) && "Unknown sampler pattern!");
|
|
auto *GV = cast<GlobalVariable>(Op);
|
|
assert(GV->isConstant() ||
|
|
GV->getType()->getPointerAddressSpace() == SPIRAS_Constant);
|
|
auto *Initializer = GV->getInitializer();
|
|
assert(isa<ConstantInt>(Initializer) && "sampler not constant int?");
|
|
return GetSamplerConstant(cast<ConstantInt>(Initializer)->getZExtValue());
|
|
}
|
|
// Sampler is a function argument
|
|
auto *BV = transValue(Arg, BB);
|
|
assert(BV && BV->getType() == TransRT);
|
|
return BV;
|
|
}
|
|
|
|
using DecorationsInfoVec =
|
|
std::vector<std::pair<Decoration, std::vector<std::string>>>;
|
|
|
|
struct AnnotationDecorations {
|
|
DecorationsInfoVec MemoryAttributesVec;
|
|
DecorationsInfoVec MemoryAccessesVec;
|
|
DecorationsInfoVec BufferLocationVec;
|
|
DecorationsInfoVec LatencyControlVec;
|
|
DecorationsInfoVec CacheControlVec;
|
|
|
|
bool empty() {
|
|
return (MemoryAttributesVec.empty() && MemoryAccessesVec.empty() &&
|
|
BufferLocationVec.empty() && LatencyControlVec.empty() &&
|
|
CacheControlVec.empty());
|
|
}
|
|
};
|
|
|
|
struct IntelLSUControlsInfo {
|
|
void setWithBitMask(unsigned ParamsBitMask) {
|
|
if (ParamsBitMask & IntelFPGAMemoryAccessesVal::BurstCoalesce)
|
|
BurstCoalesce = true;
|
|
if (ParamsBitMask & IntelFPGAMemoryAccessesVal::CacheSizeFlag)
|
|
CacheSizeInfo = 0;
|
|
if (ParamsBitMask & IntelFPGAMemoryAccessesVal::DontStaticallyCoalesce)
|
|
DontStaticallyCoalesce = true;
|
|
if (ParamsBitMask & IntelFPGAMemoryAccessesVal::PrefetchFlag)
|
|
PrefetchInfo = 0;
|
|
}
|
|
|
|
DecorationsInfoVec getDecorationsFromCurrentState() {
|
|
DecorationsInfoVec ResultVec;
|
|
// Simple flags
|
|
if (BurstCoalesce)
|
|
ResultVec.emplace_back(DecorationBurstCoalesceINTEL,
|
|
std::vector<std::string>());
|
|
if (DontStaticallyCoalesce)
|
|
ResultVec.emplace_back(DecorationDontStaticallyCoalesceINTEL,
|
|
std::vector<std::string>());
|
|
// Conditional values
|
|
if (CacheSizeInfo.has_value()) {
|
|
ResultVec.emplace_back(
|
|
DecorationCacheSizeINTEL,
|
|
std::vector<std::string>{std::to_string(CacheSizeInfo.value())});
|
|
}
|
|
if (PrefetchInfo.has_value()) {
|
|
ResultVec.emplace_back(
|
|
DecorationPrefetchINTEL,
|
|
std::vector<std::string>{std::to_string(PrefetchInfo.value())});
|
|
}
|
|
return ResultVec;
|
|
}
|
|
|
|
bool BurstCoalesce = false;
|
|
std::optional<unsigned> CacheSizeInfo;
|
|
bool DontStaticallyCoalesce = false;
|
|
std::optional<unsigned> PrefetchInfo;
|
|
};
|
|
|
|
// Handle optional var/ptr/global annotation parameter. It can be for example
|
|
// { %struct.S, i8*, void ()* } { %struct.S undef, i8* null,
|
|
// void ()* @_Z4blahv }
|
|
// Now we will just handle integer constants (wrapped in a constant
|
|
// struct, that is being bitcasted to i8*), converting them to string.
|
|
// TODO: remove this workaround when/if an extension spec that allows or adds
|
|
// variadic-arguments UserSemantic decoration
|
|
void processOptionalAnnotationInfo(Constant *Const,
|
|
std::string &AnnotationString) {
|
|
if (!Const->getNumOperands())
|
|
return;
|
|
if (auto *CStruct = dyn_cast<ConstantStruct>(Const->getOperand(0))) {
|
|
uint32_t NumOperands = CStruct->getNumOperands();
|
|
if (!NumOperands)
|
|
return;
|
|
if (auto *CInt = dyn_cast<ConstantInt>(CStruct->getOperand(0))) {
|
|
AnnotationString += ": ";
|
|
// For boolean, emit 0/1 for ease of readability.
|
|
if (CInt->getType()->getIntegerBitWidth() == 1)
|
|
AnnotationString += std::to_string(CInt->getZExtValue());
|
|
else
|
|
AnnotationString += std::to_string(CInt->getSExtValue());
|
|
}
|
|
for (uint32_t I = 1; I != NumOperands; ++I) {
|
|
if (auto *CInt = dyn_cast<ConstantInt>(CStruct->getOperand(I))) {
|
|
AnnotationString += ", ";
|
|
AnnotationString += std::to_string(CInt->getSExtValue());
|
|
}
|
|
}
|
|
} else if (auto *ZeroStruct =
|
|
dyn_cast<ConstantAggregateZero>(Const->getOperand(0))) {
|
|
// It covers case when all elements of struct are 0 and they become
|
|
// zeroinitializer. It represents like: { i32 i32 ... } zeroinitializer
|
|
uint32_t NumOperands = ZeroStruct->getType()->getStructNumElements();
|
|
AnnotationString += ": ";
|
|
AnnotationString += "0";
|
|
for (uint32_t I = 1; I != NumOperands; ++I) {
|
|
AnnotationString += ", ";
|
|
AnnotationString += "0";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process main var/ptr/global annotation string with the attached optional
|
|
// integer parameters
|
|
void processAnnotationString(IntrinsicInst *II, std::string &AnnotationString) {
|
|
auto *StrVal = II->getArgOperand(1);
|
|
auto *StrValTy = StrVal->getType();
|
|
if (StrValTy->isPointerTy()) {
|
|
StringRef StrRef;
|
|
if (getConstantStringInfo(dyn_cast<Constant>(StrVal), StrRef))
|
|
AnnotationString += StrRef.str();
|
|
if (auto *C = dyn_cast_or_null<Constant>(II->getArgOperand(4)))
|
|
processOptionalAnnotationInfo(C, AnnotationString);
|
|
return;
|
|
}
|
|
if (auto *GEP = dyn_cast<GetElementPtrInst>(StrVal)) {
|
|
if (auto *C = dyn_cast<Constant>(GEP->getOperand(0))) {
|
|
StringRef StrRef;
|
|
if (getConstantStringInfo(C, StrRef))
|
|
AnnotationString += StrRef.str();
|
|
}
|
|
}
|
|
if (auto *Cast = dyn_cast<BitCastInst>(II->getArgOperand(4)))
|
|
if (auto *C = dyn_cast_or_null<Constant>(Cast->getOperand(0)))
|
|
processOptionalAnnotationInfo(C, AnnotationString);
|
|
}
|
|
|
|
// Try to parse the annotation decoration values in a string. These values must
|
|
// be separated by a "," and must be either a word (including numbers) or a
|
|
// quotation mark enclosed string.
|
|
static bool tryParseAnnotationDecoValues(StringRef ValueStr,
|
|
std::vector<std::string> &ParsedArgs) {
|
|
unsigned ValueStart = 0;
|
|
bool IsParsingStringLiteral = false;
|
|
for (unsigned I = 0; I < ValueStr.size(); ++I) {
|
|
const char CurrentC = ValueStr[I];
|
|
if (IsParsingStringLiteral) {
|
|
if (CurrentC == '"') {
|
|
// We have reached the end of a string literal and have the arg string
|
|
// between this character and the start of the string literal.
|
|
IsParsingStringLiteral = false;
|
|
ParsedArgs.push_back(ValueStr.substr(ValueStart, I - ValueStart).str());
|
|
// End of a string literal must either be at the end of the values or
|
|
// right before a comma.
|
|
if (I + 1 != ValueStr.size() && ValueStr[I + 1] != ',')
|
|
return false;
|
|
// Skip the , delimiter and go directly to the start of next value.
|
|
ValueStart = (++I) + 1;
|
|
continue;
|
|
}
|
|
}
|
|
if (CurrentC == ',') {
|
|
// Since we are not currently in a string literal, comma denotes a
|
|
// separation of decoration arguments and we can copy the substring we are
|
|
// currently parsing.
|
|
ParsedArgs.push_back(ValueStr.substr(ValueStart, I - ValueStart).str());
|
|
ValueStart = I + 1;
|
|
continue;
|
|
}
|
|
if (CurrentC == '"') {
|
|
// We are entering a string literal. This must be either at the beginning
|
|
// of the values or right after a comma.
|
|
if (I != 0 && ValueStr[I - 1] != ',')
|
|
return false;
|
|
IsParsingStringLiteral = true;
|
|
ValueStart = I + 1;
|
|
continue;
|
|
}
|
|
// Any other character will be consumed as part of the argument.
|
|
}
|
|
// If we were still parsing a decoration argument when reaching the end of the
|
|
// parsed string, we must be at the end of the argument.
|
|
if (ValueStart < ValueStr.size())
|
|
ParsedArgs.push_back(
|
|
ValueStr.substr(ValueStart, ValueStr.size() - ValueStart).str());
|
|
|
|
// At the end, the arguments parsed are valid if we were not parsing a string
|
|
// literal with no end.
|
|
return !IsParsingStringLiteral;
|
|
}
|
|
|
|
AnnotationDecorations tryParseAnnotationString(SPIRVModule *BM,
|
|
StringRef AnnotatedCode) {
|
|
AnnotationDecorations Decorates;
|
|
// Annotation string decorations are separated into {word} OR
|
|
// {word:value,value,...} blocks, where value is either a word (including
|
|
// numbers) or a quotation mark enclosed string.
|
|
std::regex DecorationRegex("\\{\\w([\\w:,-]|\"[^\"]*\")*\\}");
|
|
using RegexIterT = std::regex_iterator<StringRef::const_iterator>;
|
|
RegexIterT DecorationsIt(AnnotatedCode.begin(), AnnotatedCode.end(),
|
|
DecorationRegex);
|
|
RegexIterT DecorationsEnd;
|
|
|
|
// If we didn't find any annotations that are separated as described above,
|
|
// then add a UserSemantic decoration
|
|
if (DecorationsIt == DecorationsEnd) {
|
|
Decorates.MemoryAttributesVec.emplace_back(
|
|
DecorationUserSemantic, std::vector<std::string>{AnnotatedCode.str()});
|
|
return Decorates;
|
|
}
|
|
|
|
const bool AllowFPGAMemAccesses =
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_memory_accesses);
|
|
const bool AllowFPGAMemAttr = BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_memory_attributes);
|
|
const bool AllowFPGABufLoc =
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_buffer_location);
|
|
const bool AllowFPGALatencyControl =
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_latency_control);
|
|
const bool AllowCacheControls =
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_cache_controls);
|
|
|
|
bool ValidDecorationFound = false;
|
|
DecorationsInfoVec DecorationsVec;
|
|
IntelLSUControlsInfo LSUControls;
|
|
for (; DecorationsIt != DecorationsEnd; ++DecorationsIt) {
|
|
// Drop the braces surrounding the actual decoration
|
|
const StringRef AnnotatedDecoration = AnnotatedCode.substr(
|
|
DecorationsIt->position() + 1, DecorationsIt->length() - 2);
|
|
|
|
std::pair<StringRef, StringRef> Split = AnnotatedDecoration.split(':');
|
|
StringRef Name = Split.first, ValueStr = Split.second;
|
|
SPIRVDBG(spvdbgs() << "[tryParseAnnotationString]: AnnotationString: "
|
|
<< Name.str() << "\n");
|
|
|
|
unsigned DecorationKind = 0;
|
|
if (!Name.getAsInteger(10, DecorationKind)) {
|
|
// If the name is a number it represents the decoration by its kind.
|
|
std::vector<std::string> DecValues;
|
|
if (tryParseAnnotationDecoValues(ValueStr, DecValues)) {
|
|
ValidDecorationFound = true;
|
|
|
|
if (AllowFPGABufLoc &&
|
|
DecorationKind == DecorationBufferLocationINTEL) {
|
|
Decorates.BufferLocationVec.emplace_back(
|
|
static_cast<Decoration>(DecorationKind), std::move(DecValues));
|
|
} else if (AllowFPGALatencyControl &&
|
|
(DecorationKind == DecorationLatencyControlLabelINTEL ||
|
|
DecorationKind ==
|
|
DecorationLatencyControlConstraintINTEL)) {
|
|
Decorates.LatencyControlVec.emplace_back(
|
|
static_cast<Decoration>(DecorationKind), std::move(DecValues));
|
|
} else if (AllowCacheControls &&
|
|
DecorationKind == DecorationCacheControlLoadINTEL) {
|
|
Decorates.CacheControlVec.emplace_back(
|
|
static_cast<Decoration>(DecorationKind), std::move(DecValues));
|
|
} else if (DecorationKind == DecorationMemoryINTEL) {
|
|
// SPIRV doesn't allow the same Decoration to be applied multiple
|
|
// times on a single SPIRVEntry, unless explicitly allowed by the
|
|
// language spec. Filter out the less specific MemoryINTEL
|
|
// decorations, if applied multiple times
|
|
auto CanFilterOut = [](auto &Val) {
|
|
if (!Val.second.empty())
|
|
return (Val.second[0] == "DEFAULT");
|
|
return false;
|
|
};
|
|
auto It = std::find_if(DecorationsVec.begin(), DecorationsVec.end(),
|
|
CanFilterOut);
|
|
|
|
if (It != DecorationsVec.end()) {
|
|
// replace the less specific decoration
|
|
*It = {static_cast<Decoration>(DecorationKind),
|
|
std::move(DecValues)};
|
|
} else {
|
|
// add new decoration
|
|
DecorationsVec.emplace_back(static_cast<Decoration>(DecorationKind),
|
|
std::move(DecValues));
|
|
}
|
|
} else {
|
|
DecorationsVec.emplace_back(static_cast<Decoration>(DecorationKind),
|
|
std::move(DecValues));
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (AllowFPGAMemAccesses) {
|
|
if (Name == "params") {
|
|
ValidDecorationFound = true;
|
|
unsigned ParamsBitMask = 0;
|
|
bool Failure = ValueStr.getAsInteger(10, ParamsBitMask);
|
|
assert(!Failure && "Non-integer LSU controls value");
|
|
(void)Failure;
|
|
LSUControls.setWithBitMask(ParamsBitMask);
|
|
} else if (Name == "cache-size") {
|
|
ValidDecorationFound = true;
|
|
if (!LSUControls.CacheSizeInfo.has_value())
|
|
continue;
|
|
unsigned CacheSizeValue = 0;
|
|
bool Failure = ValueStr.getAsInteger(10, CacheSizeValue);
|
|
assert(!Failure && "Non-integer cache size value");
|
|
(void)Failure;
|
|
LSUControls.CacheSizeInfo = CacheSizeValue;
|
|
} // TODO: Support LSU prefetch size, which currently defaults to 0
|
|
}
|
|
if (AllowFPGAMemAttr) {
|
|
std::vector<std::string> DecValues;
|
|
Decoration Dec;
|
|
if (Name == "pump") {
|
|
ValidDecorationFound = true;
|
|
Dec = llvm::StringSwitch<Decoration>(ValueStr)
|
|
.Case("1", DecorationSinglepumpINTEL)
|
|
.Case("2", DecorationDoublepumpINTEL);
|
|
} else if (Name == "register") {
|
|
ValidDecorationFound = true;
|
|
Dec = DecorationRegisterINTEL;
|
|
} else if (Name == "simple_dual_port") {
|
|
ValidDecorationFound = true;
|
|
Dec = DecorationSimpleDualPortINTEL;
|
|
} else {
|
|
Dec = llvm::StringSwitch<Decoration>(Name)
|
|
.Case("memory", DecorationMemoryINTEL)
|
|
.Case("numbanks", DecorationNumbanksINTEL)
|
|
.Case("bankwidth", DecorationBankwidthINTEL)
|
|
.Case("private_copies", DecorationMaxPrivateCopiesINTEL)
|
|
.Case("max_replicates", DecorationMaxReplicatesINTEL)
|
|
.Case("bank_bits", DecorationBankBitsINTEL)
|
|
.Case("merge", DecorationMergeINTEL)
|
|
.Case("force_pow2_depth", DecorationForcePow2DepthINTEL)
|
|
.Case("stride_size", DecorationStridesizeINTEL)
|
|
.Case("word_size", DecorationWordsizeINTEL)
|
|
.Case("true_dual_port", DecorationTrueDualPortINTEL)
|
|
.Default(DecorationUserSemantic);
|
|
if (Dec == DecorationUserSemantic)
|
|
// Restore the braces to translate the whole input string
|
|
DecValues =
|
|
std::vector<std::string>({'{' + AnnotatedDecoration.str() + '}'});
|
|
else if (Dec == DecorationMergeINTEL) {
|
|
ValidDecorationFound = true;
|
|
std::pair<StringRef, StringRef> MergeValues = ValueStr.split(':');
|
|
DecValues = std::vector<std::string>(
|
|
{MergeValues.first.str(), MergeValues.second.str()});
|
|
} else if (Dec == DecorationBankBitsINTEL) {
|
|
ValidDecorationFound = true;
|
|
SmallVector<StringRef, 4> BitsStrs;
|
|
ValueStr.split(BitsStrs, ',');
|
|
DecValues.reserve(BitsStrs.size());
|
|
for (const StringRef &BitsStr : BitsStrs)
|
|
DecValues.push_back(BitsStr.str());
|
|
} else {
|
|
ValidDecorationFound = true;
|
|
DecValues = std::vector<std::string>({ValueStr.str()});
|
|
}
|
|
}
|
|
DecorationsVec.emplace_back(Dec, std::move(DecValues));
|
|
}
|
|
}
|
|
|
|
// Even if there is an annotation string that is split in blocks like Intel
|
|
// FPGA annotation, it's not necessarily an FPGA annotation. Translate the
|
|
// whole string as UserSemantic decoration in this case.
|
|
if (ValidDecorationFound)
|
|
Decorates.MemoryAttributesVec = DecorationsVec;
|
|
else
|
|
Decorates.MemoryAttributesVec.emplace_back(
|
|
DecorationUserSemantic,
|
|
std::vector<std::string>({AnnotatedCode.str()}));
|
|
Decorates.MemoryAccessesVec = LSUControls.getDecorationsFromCurrentState();
|
|
|
|
return Decorates;
|
|
}
|
|
|
|
std::vector<SPIRVWord>
|
|
getLiteralsFromStrings(const std::vector<std::string> &Strings) {
|
|
std::vector<SPIRVWord> Literals(Strings.size());
|
|
for (size_t J = 0; J < Strings.size(); ++J)
|
|
if (StringRef(Strings[J]).getAsInteger(10, Literals[J]))
|
|
return {};
|
|
return Literals;
|
|
}
|
|
|
|
void addAnnotationDecorations(SPIRVEntry *E, DecorationsInfoVec &Decorations) {
|
|
SPIRVModule *M = E->getModule();
|
|
for (const auto &I : Decorations) {
|
|
// Such decoration already exists on a type, try to skip it
|
|
if (E->hasDecorate(I.first, /*Index=*/0, /*Result=*/nullptr))
|
|
// Allow multiple UserSemantic Decorations
|
|
if (I.first != DecorationUserSemantic)
|
|
continue;
|
|
|
|
switch (static_cast<size_t>(I.first)) {
|
|
case DecorationUserSemantic:
|
|
M->getErrorLog().checkError(I.second.size() == 1,
|
|
SPIRVEC_InvalidLlvmModule,
|
|
"UserSemantic requires a single argument.");
|
|
E->addDecorate(new SPIRVDecorateUserSemanticAttr(E, I.second[0]));
|
|
break;
|
|
case DecorationMemoryINTEL: {
|
|
if (M->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_memory_attributes)) {
|
|
M->getErrorLog().checkError(I.second.size() == 1,
|
|
SPIRVEC_InvalidLlvmModule,
|
|
"MemoryINTEL requires a single argument.");
|
|
E->addDecorate(new SPIRVDecorateMemoryINTELAttr(E, I.second[0]));
|
|
}
|
|
} break;
|
|
case DecorationMergeINTEL: {
|
|
if (M->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_memory_attributes)) {
|
|
M->getErrorLog().checkError(I.second.size() == 2,
|
|
SPIRVEC_InvalidLlvmModule,
|
|
"MergeINTEL requires two arguments.");
|
|
// First argument is the name and the second argument is the direction.
|
|
E->addDecorate(
|
|
new SPIRVDecorateMergeINTELAttr(E, I.second[0], I.second[1]));
|
|
}
|
|
} break;
|
|
case DecorationBankBitsINTEL: {
|
|
if (M->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_memory_attributes)) {
|
|
M->getErrorLog().checkError(
|
|
I.second.size() > 0, SPIRVEC_InvalidLlvmModule,
|
|
"BankBitsINTEL requires at least one argument.");
|
|
E->addDecorate(new SPIRVDecorateBankBitsINTELAttr(
|
|
E, getLiteralsFromStrings(I.second)));
|
|
}
|
|
} break;
|
|
case DecorationRegisterINTEL:
|
|
case DecorationSinglepumpINTEL:
|
|
case DecorationDoublepumpINTEL:
|
|
case DecorationSimpleDualPortINTEL:
|
|
case DecorationTrueDualPortINTEL: {
|
|
if (M->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_memory_attributes)) {
|
|
M->getErrorLog().checkError(I.second.empty(), SPIRVEC_InvalidLlvmModule,
|
|
"Decoration takes no arguments.");
|
|
E->addDecorate(I.first);
|
|
}
|
|
} break;
|
|
case DecorationBurstCoalesceINTEL:
|
|
case DecorationDontStaticallyCoalesceINTEL: {
|
|
if (M->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_memory_accesses)) {
|
|
M->getErrorLog().checkError(I.second.empty(), SPIRVEC_InvalidLlvmModule,
|
|
"Decoration takes no arguments.");
|
|
E->addDecorate(I.first);
|
|
}
|
|
} break;
|
|
case DecorationNumbanksINTEL:
|
|
case DecorationBankwidthINTEL:
|
|
case DecorationMaxPrivateCopiesINTEL:
|
|
case DecorationMaxReplicatesINTEL:
|
|
case DecorationForcePow2DepthINTEL:
|
|
case DecorationStridesizeINTEL:
|
|
case DecorationWordsizeINTEL: {
|
|
if (M->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_memory_attributes)) {
|
|
M->getErrorLog().checkError(I.second.size() == 1,
|
|
SPIRVEC_InvalidLlvmModule,
|
|
"Decoration requires a single argument.");
|
|
SPIRVWord Result = 0;
|
|
StringRef(I.second[0]).getAsInteger(10, Result);
|
|
E->addDecorate(I.first, Result);
|
|
}
|
|
} break;
|
|
case DecorationCacheSizeINTEL:
|
|
case DecorationPrefetchINTEL: {
|
|
if (M->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_memory_accesses)) {
|
|
M->getErrorLog().checkError(I.second.size() == 1,
|
|
SPIRVEC_InvalidLlvmModule,
|
|
"Decoration requires a single argument.");
|
|
SPIRVWord Result = 0;
|
|
StringRef(I.second[0]).getAsInteger(10, Result);
|
|
E->addDecorate(I.first, Result);
|
|
}
|
|
} break;
|
|
case DecorationBufferLocationINTEL: {
|
|
if (M->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_buffer_location)) {
|
|
M->getErrorLog().checkError(I.second.size() == 1,
|
|
SPIRVEC_InvalidLlvmModule,
|
|
"Decoration requires a single argument.");
|
|
SPIRVWord Result = 0;
|
|
StringRef(I.second[0]).getAsInteger(10, Result);
|
|
E->addDecorate(I.first, Result);
|
|
}
|
|
} break;
|
|
case DecorationLatencyControlLabelINTEL: {
|
|
if (M->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_latency_control)) {
|
|
M->getErrorLog().checkError(
|
|
I.second.size() == 1, SPIRVEC_InvalidLlvmModule,
|
|
"LatencyControlLabelINTEL requires exactly 1 extra operand");
|
|
SPIRVWord Label = 0;
|
|
StringRef(I.second[0]).getAsInteger(10, Label);
|
|
E->addDecorate(
|
|
new SPIRVDecorate(DecorationLatencyControlLabelINTEL, E, Label));
|
|
}
|
|
break;
|
|
}
|
|
case DecorationLatencyControlConstraintINTEL: {
|
|
if (M->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_fpga_latency_control)) {
|
|
M->getErrorLog().checkError(
|
|
I.second.size() == 3, SPIRVEC_InvalidLlvmModule,
|
|
"LatencyControlConstraintINTEL requires exactly 3 extra operands");
|
|
auto Literals = getLiteralsFromStrings(I.second);
|
|
E->addDecorate(
|
|
new SPIRVDecorate(DecorationLatencyControlConstraintINTEL, E,
|
|
Literals[0], Literals[1], Literals[2]));
|
|
}
|
|
break;
|
|
}
|
|
case DecorationCacheControlLoadINTEL: {
|
|
if (M->isAllowedToUseExtension(ExtensionID::SPV_INTEL_cache_controls)) {
|
|
M->getErrorLog().checkError(
|
|
I.second.size() == 2, SPIRVEC_InvalidLlvmModule,
|
|
"CacheControlLoadINTEL requires exactly 2 extra operands");
|
|
SPIRVWord CacheLevel = 0;
|
|
SPIRVWord CacheControl = 0;
|
|
StringRef(I.second[0]).getAsInteger(10, CacheLevel);
|
|
StringRef(I.second[1]).getAsInteger(10, CacheControl);
|
|
E->addDecorate(new SPIRVDecorateCacheControlLoadINTEL(
|
|
E, CacheLevel, static_cast<LoadCacheControl>(CacheControl)));
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// Other decorations are either not supported by the translator or
|
|
// handled in other places.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void addAnnotationDecorationsForStructMember(SPIRVEntry *E,
|
|
SPIRVWord MemberNumber,
|
|
DecorationsInfoVec &Decorations) {
|
|
SPIRVModule *M = E->getModule();
|
|
for (const auto &I : Decorations) {
|
|
// Such decoration already exists on a type, skip it
|
|
if (E->hasMemberDecorate(I.first, /*Index=*/0, MemberNumber,
|
|
/*Result=*/nullptr))
|
|
// Allow multiple UserSemantic Decorations
|
|
if (I.first != DecorationUserSemantic)
|
|
continue;
|
|
|
|
switch (I.first) {
|
|
case DecorationUserSemantic:
|
|
M->getErrorLog().checkError(I.second.size() == 1,
|
|
SPIRVEC_InvalidLlvmModule,
|
|
"UserSemantic requires a single argument.");
|
|
E->addMemberDecorate(new SPIRVMemberDecorateUserSemanticAttr(
|
|
E, MemberNumber, I.second[0]));
|
|
break;
|
|
case DecorationMemoryINTEL:
|
|
M->getErrorLog().checkError(I.second.size() == 1,
|
|
SPIRVEC_InvalidLlvmModule,
|
|
"MemoryINTEL requires a single argument.");
|
|
E->addMemberDecorate(
|
|
new SPIRVMemberDecorateMemoryINTELAttr(E, MemberNumber, I.second[0]));
|
|
break;
|
|
case DecorationMergeINTEL: {
|
|
M->getErrorLog().checkError(I.second.size() == 2,
|
|
SPIRVEC_InvalidLlvmModule,
|
|
"MergeINTEL requires two arguments.");
|
|
// First argument is the name, the other is the direction.
|
|
E->addMemberDecorate(new SPIRVMemberDecorateMergeINTELAttr(
|
|
E, MemberNumber, I.second[0], I.second[1]));
|
|
} break;
|
|
case DecorationBankBitsINTEL:
|
|
M->getErrorLog().checkError(
|
|
I.second.size() > 0, SPIRVEC_InvalidLlvmModule,
|
|
"BankBitsINTEL requires at least one argument.");
|
|
E->addMemberDecorate(new SPIRVMemberDecorateBankBitsINTELAttr(
|
|
E, MemberNumber, getLiteralsFromStrings(I.second)));
|
|
break;
|
|
case DecorationRegisterINTEL:
|
|
case DecorationSinglepumpINTEL:
|
|
case DecorationDoublepumpINTEL:
|
|
case DecorationSimpleDualPortINTEL:
|
|
case DecorationTrueDualPortINTEL:
|
|
M->getErrorLog().checkError(I.second.empty(), SPIRVEC_InvalidLlvmModule,
|
|
"Member decoration takes no arguments.");
|
|
E->addMemberDecorate(MemberNumber, I.first);
|
|
break;
|
|
// The rest of IntelFPGA decorations:
|
|
// DecorationNumbanksINTEL
|
|
// DecorationBankwidthINTEL
|
|
// DecorationMaxPrivateCopiesINTEL
|
|
// DecorationMaxReplicatesINTEL
|
|
// DecorationForcePow2DepthINTEL
|
|
// DecorarionStridesizeINTEL
|
|
// DecorationWordsizeINTEL
|
|
default:
|
|
M->getErrorLog().checkError(
|
|
I.second.size() == 1, SPIRVEC_InvalidLlvmModule,
|
|
"Member decoration requires a single argument.");
|
|
SPIRVWord Result = 0;
|
|
StringRef(I.second[0]).getAsInteger(10, Result);
|
|
E->addMemberDecorate(MemberNumber, I.first, Result);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::isKnownIntrinsic(Intrinsic::ID Id) {
|
|
// Known intrinsics usually do not need translation of their declaration
|
|
switch (Id) {
|
|
case Intrinsic::abs:
|
|
case Intrinsic::assume:
|
|
case Intrinsic::bitreverse:
|
|
case Intrinsic::ceil:
|
|
case Intrinsic::copysign:
|
|
case Intrinsic::cos:
|
|
case Intrinsic::exp:
|
|
case Intrinsic::exp2:
|
|
case Intrinsic::fabs:
|
|
case Intrinsic::floor:
|
|
case Intrinsic::fma:
|
|
case Intrinsic::frexp:
|
|
case Intrinsic::log:
|
|
case Intrinsic::log10:
|
|
case Intrinsic::log2:
|
|
case Intrinsic::maximum:
|
|
case Intrinsic::maxnum:
|
|
case Intrinsic::smax:
|
|
case Intrinsic::umax:
|
|
case Intrinsic::minimum:
|
|
case Intrinsic::minnum:
|
|
case Intrinsic::smin:
|
|
case Intrinsic::umin:
|
|
case Intrinsic::scmp:
|
|
case Intrinsic::ucmp:
|
|
case Intrinsic::nearbyint:
|
|
case Intrinsic::pow:
|
|
case Intrinsic::powi:
|
|
case Intrinsic::rint:
|
|
case Intrinsic::round:
|
|
case Intrinsic::roundeven:
|
|
case Intrinsic::sin:
|
|
case Intrinsic::sqrt:
|
|
case Intrinsic::tan:
|
|
case Intrinsic::trunc:
|
|
case Intrinsic::ctpop:
|
|
case Intrinsic::ctlz:
|
|
case Intrinsic::cttz:
|
|
case Intrinsic::expect:
|
|
case Intrinsic::experimental_noalias_scope_decl:
|
|
case Intrinsic::experimental_constrained_fadd:
|
|
case Intrinsic::experimental_constrained_fsub:
|
|
case Intrinsic::experimental_constrained_fmul:
|
|
case Intrinsic::experimental_constrained_fdiv:
|
|
case Intrinsic::experimental_constrained_sqrt:
|
|
case Intrinsic::experimental_constrained_frem:
|
|
case Intrinsic::experimental_constrained_fma:
|
|
case Intrinsic::experimental_constrained_fptoui:
|
|
case Intrinsic::experimental_constrained_fptosi:
|
|
case Intrinsic::experimental_constrained_uitofp:
|
|
case Intrinsic::experimental_constrained_sitofp:
|
|
case Intrinsic::experimental_constrained_fptrunc:
|
|
case Intrinsic::experimental_constrained_fpext:
|
|
case Intrinsic::experimental_constrained_fcmp:
|
|
case Intrinsic::experimental_constrained_fcmps:
|
|
case Intrinsic::experimental_constrained_fmuladd:
|
|
case Intrinsic::fmuladd:
|
|
case Intrinsic::memset:
|
|
case Intrinsic::memcpy:
|
|
case Intrinsic::lifetime_start:
|
|
case Intrinsic::lifetime_end:
|
|
case Intrinsic::dbg_declare:
|
|
case Intrinsic::dbg_value:
|
|
case Intrinsic::annotation:
|
|
case Intrinsic::var_annotation:
|
|
case Intrinsic::ptr_annotation:
|
|
case Intrinsic::invariant_start:
|
|
case Intrinsic::invariant_end:
|
|
case Intrinsic::dbg_label:
|
|
case Intrinsic::trap:
|
|
case Intrinsic::ubsantrap:
|
|
case Intrinsic::debugtrap:
|
|
case Intrinsic::uadd_with_overflow:
|
|
case Intrinsic::usub_with_overflow:
|
|
case Intrinsic::arithmetic_fence:
|
|
case Intrinsic::masked_gather:
|
|
case Intrinsic::masked_scatter:
|
|
case Intrinsic::modf:
|
|
case Intrinsic::fake_use:
|
|
return true;
|
|
default:
|
|
// Unknown intrinsics' declarations should always be translated
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Performs mapping of LLVM IR rounding mode to SPIR-V rounding mode
|
|
// Value *V is metadata <rounding mode> argument of
|
|
// llvm.experimental.constrained.* intrinsics
|
|
SPIRVInstruction *
|
|
LLVMToSPIRVBase::applyRoundingModeConstraint(Value *V, SPIRVInstruction *I) {
|
|
StringRef RMode =
|
|
cast<MDString>(cast<MetadataAsValue>(V)->getMetadata())->getString();
|
|
if (RMode.ends_with("tonearest"))
|
|
I->addFPRoundingMode(FPRoundingModeRTE);
|
|
else if (RMode.ends_with("towardzero"))
|
|
I->addFPRoundingMode(FPRoundingModeRTZ);
|
|
else if (RMode.ends_with("upward"))
|
|
I->addFPRoundingMode(FPRoundingModeRTP);
|
|
else if (RMode.ends_with("downward"))
|
|
I->addFPRoundingMode(FPRoundingModeRTN);
|
|
return I;
|
|
}
|
|
|
|
static SPIRVWord getBuiltinIdForIntrinsic(Intrinsic::ID IID) {
|
|
switch (IID) {
|
|
// Note: In some cases the semantics of the OpenCL builtin are not identical
|
|
// to the semantics of the corresponding LLVM IR intrinsic. The LLVM
|
|
// intrinsics handled here assume the default floating point environment
|
|
// (no unmasked exceptions, round-to-nearest-ties-even rounding mode)
|
|
// and assume that the operations have no side effects (FP status flags
|
|
// aren't maintained), so the OpenCL builtin behavior should be
|
|
// acceptable.
|
|
case Intrinsic::acos:
|
|
return OpenCLLIB::Acos;
|
|
case Intrinsic::asin:
|
|
return OpenCLLIB::Asin;
|
|
case Intrinsic::atan:
|
|
return OpenCLLIB::Atan;
|
|
case Intrinsic::atan2:
|
|
return OpenCLLIB::Atan2;
|
|
case Intrinsic::ceil:
|
|
return OpenCLLIB::Ceil;
|
|
case Intrinsic::copysign:
|
|
return OpenCLLIB::Copysign;
|
|
case Intrinsic::cos:
|
|
return OpenCLLIB::Cos;
|
|
case Intrinsic::cosh:
|
|
return OpenCLLIB::Cosh;
|
|
case Intrinsic::exp:
|
|
return OpenCLLIB::Exp;
|
|
case Intrinsic::exp2:
|
|
return OpenCLLIB::Exp2;
|
|
case Intrinsic::fabs:
|
|
return OpenCLLIB::Fabs;
|
|
case Intrinsic::floor:
|
|
return OpenCLLIB::Floor;
|
|
case Intrinsic::fma:
|
|
return OpenCLLIB::Fma;
|
|
case Intrinsic::frexp:
|
|
return OpenCLLIB::Frexp;
|
|
case Intrinsic::log:
|
|
return OpenCLLIB::Log;
|
|
case Intrinsic::log10:
|
|
return OpenCLLIB::Log10;
|
|
case Intrinsic::log2:
|
|
return OpenCLLIB::Log2;
|
|
case Intrinsic::maximum:
|
|
return OpenCLLIB::Fmax;
|
|
case Intrinsic::maxnum:
|
|
return OpenCLLIB::Fmax;
|
|
case Intrinsic::minimum:
|
|
return OpenCLLIB::Fmin;
|
|
case Intrinsic::minnum:
|
|
return OpenCLLIB::Fmin;
|
|
case Intrinsic::nearbyint:
|
|
return OpenCLLIB::Rint;
|
|
case Intrinsic::pow:
|
|
return OpenCLLIB::Pow;
|
|
case Intrinsic::powi:
|
|
return OpenCLLIB::Pown;
|
|
case Intrinsic::rint:
|
|
return OpenCLLIB::Rint;
|
|
case Intrinsic::round:
|
|
return OpenCLLIB::Round;
|
|
case Intrinsic::roundeven:
|
|
return OpenCLLIB::Rint;
|
|
case Intrinsic::sin:
|
|
return OpenCLLIB::Sin;
|
|
case Intrinsic::sinh:
|
|
return OpenCLLIB::Sinh;
|
|
case Intrinsic::sqrt:
|
|
return OpenCLLIB::Sqrt;
|
|
case Intrinsic::tan:
|
|
return OpenCLLIB::Tan;
|
|
case Intrinsic::tanh:
|
|
return OpenCLLIB::Tanh;
|
|
case Intrinsic::trunc:
|
|
return OpenCLLIB::Trunc;
|
|
case Intrinsic::modf:
|
|
return OpenCLLIB::Modf;
|
|
default:
|
|
assert(false && "Builtin ID requested for Unhandled intrinsic!");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static SPIRVWord getNativeBuiltinIdForIntrinsic(Intrinsic::ID IID) {
|
|
switch (IID) {
|
|
case Intrinsic::cos:
|
|
return OpenCLLIB::Native_cos;
|
|
case Intrinsic::exp:
|
|
return OpenCLLIB::Native_exp;
|
|
case Intrinsic::exp2:
|
|
return OpenCLLIB::Native_exp2;
|
|
case Intrinsic::log:
|
|
return OpenCLLIB::Native_log;
|
|
case Intrinsic::log10:
|
|
return OpenCLLIB::Native_log10;
|
|
case Intrinsic::log2:
|
|
return OpenCLLIB::Native_log2;
|
|
case Intrinsic::sin:
|
|
return OpenCLLIB::Native_sin;
|
|
case Intrinsic::sqrt:
|
|
return OpenCLLIB::Native_sqrt;
|
|
case Intrinsic::tan:
|
|
return OpenCLLIB::Native_tan;
|
|
default:
|
|
return getBuiltinIdForIntrinsic(IID);
|
|
}
|
|
}
|
|
|
|
static bool allowsApproxFunction(IntrinsicInst *II) {
|
|
auto *Ty = II->getType();
|
|
// OpenCL native_* built-ins only support single precision data type
|
|
return II->hasApproxFunc() &&
|
|
(Ty->isFloatTy() ||
|
|
(Ty->isVectorTy() &&
|
|
cast<VectorType>(Ty)->getElementType()->isFloatTy()));
|
|
}
|
|
|
|
namespace {
|
|
bool checkMemUser(User *User) {
|
|
if (isa<LoadInst>(User) || isa<StoreInst>(User))
|
|
return true;
|
|
if (auto *III = dyn_cast<IntrinsicInst>(User)) {
|
|
if (III->getIntrinsicID() == Intrinsic::memcpy)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
} // namespace
|
|
|
|
bool allowDecorateWithLatencyControlINTEL(IntrinsicInst *II) {
|
|
for (auto *Inst : II->users()) {
|
|
// if castInst, check Successors
|
|
if (auto *Cast = dyn_cast<CastInst>(Inst)) {
|
|
for (auto *Successor : Cast->users())
|
|
if (checkMemUser(Successor))
|
|
return true;
|
|
} else {
|
|
if (checkMemUser(Inst))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transIntrinsicInst(IntrinsicInst *II,
|
|
SPIRVBasicBlock *BB) {
|
|
auto GetMemoryAccess =
|
|
[](MemIntrinsic *MI,
|
|
bool AllowTwoMemAccessMasks) -> std::vector<SPIRVWord> {
|
|
std::vector<SPIRVWord> MemoryAccess(1, MemoryAccessMaskNone);
|
|
MaybeAlign DestAlignVal = MI->getDestAlign();
|
|
if (DestAlignVal) {
|
|
Align AlignVal = *DestAlignVal;
|
|
MemoryAccess[0] |= MemoryAccessAlignedMask;
|
|
if (auto *MTI = dyn_cast<MemCpyInst>(MI)) {
|
|
MaybeAlign SourceAlignVal = MTI->getSourceAlign();
|
|
assert(SourceAlignVal && "Missed Source alignment!");
|
|
|
|
// In a case when alignment of source differs from dest one
|
|
// we either preserve both (allowed since SPIR-V 1.4), or the least
|
|
// value is guaranteed anyway.
|
|
if (AllowTwoMemAccessMasks) {
|
|
if (*DestAlignVal != *SourceAlignVal) {
|
|
MemoryAccess.push_back(DestAlignVal.valueOrOne().value());
|
|
MemoryAccess.push_back(MemoryAccessAlignedMask);
|
|
AlignVal = *SourceAlignVal;
|
|
}
|
|
} else {
|
|
AlignVal = std::min(*DestAlignVal, *SourceAlignVal);
|
|
}
|
|
}
|
|
MemoryAccess.push_back(AlignVal.value());
|
|
}
|
|
if (MI->isVolatile())
|
|
MemoryAccess[0] |= MemoryAccessVolatileMask;
|
|
return MemoryAccess;
|
|
};
|
|
|
|
// LLVM intrinsics with known translation to SPIR-V are handled here. They
|
|
// also must be registered at isKnownIntrinsic function in order to make
|
|
// -spirv-allow-unknown-intrinsics work correctly.
|
|
auto IID = II->getIntrinsicID();
|
|
switch (IID) {
|
|
case Intrinsic::fabs:
|
|
case Intrinsic::fma:
|
|
case Intrinsic::maxnum:
|
|
case Intrinsic::minnum:
|
|
case Intrinsic::fmuladd: {
|
|
Type *Ty = II->getType();
|
|
if (Ty->isBFloatTy())
|
|
BM->addCapability(internal::CapabilityBFloat16ArithmeticINTEL);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
switch (IID) {
|
|
case Intrinsic::assume: {
|
|
// llvm.assume translation is currently supported only within
|
|
// SPV_KHR_expect_assume extension, ignore it otherwise, since it's
|
|
// an optimization hint
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_expect_assume)) {
|
|
SPIRVValue *Condition = transValue(II->getArgOperand(0), BB);
|
|
return BM->addAssumeTrueKHRInst(Condition, BB);
|
|
}
|
|
return nullptr;
|
|
}
|
|
case Intrinsic::bitreverse: {
|
|
if (!BM->getErrorLog().checkError(
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_bit_instructions),
|
|
SPIRVEC_InvalidFunctionCall, II,
|
|
"Translation of llvm.bitreverse intrinsic requires "
|
|
"SPV_KHR_bit_instructions extension.")) {
|
|
return nullptr;
|
|
}
|
|
SPIRVType *Ty = transType(II->getType());
|
|
SPIRVValue *Op = transValue(II->getArgOperand(0), BB);
|
|
return BM->addUnaryInst(OpBitReverse, Ty, Op, BB);
|
|
}
|
|
|
|
// Unary FP intrinsic
|
|
case Intrinsic::acos:
|
|
case Intrinsic::asin:
|
|
case Intrinsic::atan:
|
|
case Intrinsic::ceil:
|
|
case Intrinsic::cos:
|
|
case Intrinsic::cosh:
|
|
case Intrinsic::exp:
|
|
case Intrinsic::exp2:
|
|
case Intrinsic::fabs:
|
|
case Intrinsic::floor:
|
|
case Intrinsic::log:
|
|
case Intrinsic::log10:
|
|
case Intrinsic::log2:
|
|
case Intrinsic::nearbyint:
|
|
case Intrinsic::rint:
|
|
case Intrinsic::round:
|
|
case Intrinsic::roundeven:
|
|
case Intrinsic::sin:
|
|
case Intrinsic::sinh:
|
|
case Intrinsic::sqrt:
|
|
case Intrinsic::tan:
|
|
case Intrinsic::tanh:
|
|
case Intrinsic::trunc: {
|
|
if (!checkTypeForSPIRVExtendedInstLowering(II, BM))
|
|
break;
|
|
SPIRVWord ExtOp = allowsApproxFunction(II)
|
|
? getNativeBuiltinIdForIntrinsic(IID)
|
|
: getBuiltinIdForIntrinsic(IID);
|
|
SPIRVType *STy = transType(II->getType());
|
|
std::vector<SPIRVValue *> Ops(1, transValue(II->getArgOperand(0), BB));
|
|
return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops,
|
|
BB);
|
|
}
|
|
case Intrinsic::lround: {
|
|
SPIRVType *SourceTy = transType(II->getArgOperand(0)->getType());
|
|
SPIRVType *DestTy = transType(II->getType());
|
|
SPIRVValue *Rounded = BM->addExtInst(
|
|
SourceTy, BM->getExtInstSetId(SPIRVEIS_OpenCL), OpenCLLIB::Round,
|
|
{transValue(II->getArgOperand(0), BB)}, BB);
|
|
return BM->addUnaryInst(OpConvertFToS, DestTy, Rounded, BB);
|
|
}
|
|
case Intrinsic::frexp: {
|
|
if (!checkTypeForSPIRVExtendedInstLowering(II, BM))
|
|
break;
|
|
SPIRVWord ExtOp = getBuiltinIdForIntrinsic(IID);
|
|
|
|
SPIRVType *FTy = transType(II->getType()->getStructElementType(0));
|
|
SPIRVTypePointer *ITy = static_cast<SPIRVTypePointer *>(transPointerType(
|
|
II->getType()->getStructElementType(1), SPIRAS_Private));
|
|
if (!ITy->isTypeUntypedPointerKHR()) {
|
|
unsigned BitWidth = ITy->getElementType()->getBitWidth();
|
|
BM->getErrorLog().checkError(BitWidth == 32, SPIRVEC_InvalidBitWidth,
|
|
std::to_string(BitWidth));
|
|
}
|
|
SPIRVValue *IntVal =
|
|
BM->addVariable(ITy,
|
|
ITy->isTypeUntypedPointerKHR()
|
|
? transType(II->getType()->getStructElementType(1))
|
|
: nullptr,
|
|
false, spv::internal::LinkageTypeInternal, nullptr, "",
|
|
ITy->getStorageClass(), BB);
|
|
|
|
std::vector<SPIRVValue *> Ops{transValue(II->getArgOperand(0), BB), IntVal};
|
|
|
|
return BM->addExtInst(FTy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops,
|
|
BB);
|
|
}
|
|
case Intrinsic::modf: {
|
|
// llvm.modf has a single arg --the number to be decomposed-- and returns a
|
|
// struct { double, double }, while OpenCLLIB::modf has two args --the
|
|
// number to be decomposed and a pointer--, returns the fractional part and
|
|
// the integral part is stored in the pointer argument. Therefore, we can't
|
|
// use directly the OpenCLLIB::modf intrinsic. However, we can do some
|
|
// scaffolding to make it work. The idea is to create an alloca instruction
|
|
// to get a ptr, pass this ptr to OpenCLLIB::modf, and then load the value
|
|
// from this ptr to place it in the struct.
|
|
|
|
// llvm.modf returns the fractional part as the first element of the result,
|
|
// and the integral part as the second element of the result. Therefore, the
|
|
// first element is the return value of OpenCLLIB::modf, and the second
|
|
// element is the value loaded from the ptr of the alloca we created.
|
|
|
|
// Create the alloca instruction.
|
|
SPIRVType *IntegralTy = transType(II->getType()->getStructElementType(1));
|
|
// IntegralTy is the type of the result. We want to create a pointer to this
|
|
// that we can pass to OpenCLLIB::modf to store the integral part.
|
|
SPIRVType *GenericPtrTy = BM->addPointerType(StorageClassFunction, IntegralTy);
|
|
auto *IntegralPtrTy = dyn_cast<SPIRVTypePointer>(GenericPtrTy);
|
|
// We need to use the entry BB of the function calling llvm.modf.*, instead
|
|
// of the current BB. For that, we'll find current BB's parent and get its
|
|
// first BB, which is the entry BB of the function.
|
|
SPIRVBasicBlock *EntryBB = BB->getParent()->getBasicBlock(0);
|
|
SPIRVValue *Ptr = BM->addVariable(
|
|
IntegralPtrTy, nullptr, false, spv::internal::LinkageTypeInternal,
|
|
nullptr, "", StorageClassFunction, EntryBB);
|
|
|
|
// Create the OpenCLLIB::modf instruction.
|
|
SPIRVType *FTy = transType(II->getType()->getStructElementType(0));
|
|
SPIRVValue *Arg = transValue(II->getArgOperand(0), BB);
|
|
std::vector<SPIRVValue *> Ops{Arg, Ptr};
|
|
SPIRVValue *Modf = BM->addExtInst(FTy, BM->getExtInstSetId(SPIRVEIS_OpenCL),
|
|
OpenCLLIB::Modf, Ops, BB);
|
|
|
|
// Load the value from the ptr.
|
|
SPIRVValue *IntegralVal = BM->addLoadInst(Ptr, {}, BB, IntegralTy);
|
|
|
|
// Create the struct.
|
|
SPIRVType *STy = transType(II->getType());
|
|
std::vector<SPIRVId> StructVals{Modf->getId(), IntegralVal->getId()};
|
|
return BM->addCompositeConstructInst(STy, StructVals, BB);
|
|
}
|
|
// Binary FP intrinsics
|
|
case Intrinsic::atan2:
|
|
case Intrinsic::copysign:
|
|
case Intrinsic::pow:
|
|
case Intrinsic::powi:
|
|
case Intrinsic::maximum:
|
|
case Intrinsic::maxnum:
|
|
case Intrinsic::minimum:
|
|
case Intrinsic::minnum: {
|
|
if (!checkTypeForSPIRVExtendedInstLowering(II, BM))
|
|
break;
|
|
SPIRVWord ExtOp = allowsApproxFunction(II)
|
|
? getNativeBuiltinIdForIntrinsic(IID)
|
|
: getBuiltinIdForIntrinsic(IID);
|
|
SPIRVType *STy = transType(II->getType());
|
|
std::vector<SPIRVValue *> Ops{transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB)};
|
|
return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops,
|
|
BB);
|
|
}
|
|
case Intrinsic::umin:
|
|
case Intrinsic::umax:
|
|
case Intrinsic::smin:
|
|
case Intrinsic::smax: {
|
|
Type *BoolTy = IntegerType::getInt1Ty(M->getContext());
|
|
SPIRVValue *FirstArgVal = transValue(II->getArgOperand(0), BB);
|
|
SPIRVValue *SecondArgVal = transValue(II->getArgOperand(1), BB);
|
|
|
|
Op OC =
|
|
(IID == Intrinsic::smin)
|
|
? OpSLessThan
|
|
: ((IID == Intrinsic::smax)
|
|
? OpSGreaterThan
|
|
: ((IID == Intrinsic::umin) ? OpULessThan : OpUGreaterThan));
|
|
if (auto *VecTy = dyn_cast<VectorType>(II->getArgOperand(0)->getType()))
|
|
BoolTy = VectorType::get(BoolTy, VecTy->getElementCount());
|
|
SPIRVValue *Cmp =
|
|
BM->addCmpInst(OC, transType(BoolTy), FirstArgVal, SecondArgVal, BB);
|
|
return BM->addSelectInst(Cmp, FirstArgVal, SecondArgVal, BB);
|
|
}
|
|
case Intrinsic::ucmp:
|
|
case Intrinsic::scmp: {
|
|
Type *BoolTy = IntegerType::getInt1Ty(M->getContext());
|
|
SPIRVValue *FirstArgVal = transValue(II->getArgOperand(0), BB);
|
|
SPIRVValue *SecondArgVal = transValue(II->getArgOperand(1), BB);
|
|
SPIRVType *ResTy = transType(II->getType());
|
|
SPIRVValue *One = nullptr;
|
|
SPIRVValue *Zero = nullptr;
|
|
SPIRVValue *MinusOne = nullptr;
|
|
if (auto *VecTy = dyn_cast<VectorType>(II->getType())) {
|
|
auto *ElemTy = transType(VecTy->getElementType());
|
|
APInt MinusOneValue(ElemTy->getIntegerBitWidth(), 0, 1);
|
|
MinusOneValue.setAllBits();
|
|
unsigned VecSize = VecTy->getElementCount().getFixedValue();
|
|
auto ElemCount =
|
|
static_cast<std::vector<SPIRVValue *>::size_type>(VecSize);
|
|
auto *ElemOne = BM->addConstant(ElemTy, 1);
|
|
auto *ElemZero = BM->addConstant(ElemTy, 0);
|
|
auto *ElemMinusOne = BM->addConstant(ElemTy, std::move(MinusOneValue));
|
|
std::vector<SPIRVValue *> ElemsOne(ElemCount, ElemOne);
|
|
std::vector<SPIRVValue *> ElemsZero(ElemCount, ElemZero);
|
|
std::vector<SPIRVValue *> ElemsMinusOne(ElemCount, ElemMinusOne);
|
|
One = BM->addCompositeConstant(ResTy, ElemsOne);
|
|
Zero = BM->addCompositeConstant(ResTy, ElemsZero);
|
|
MinusOne = BM->addCompositeConstant(ResTy, ElemsMinusOne);
|
|
} else {
|
|
One = BM->addConstant(ResTy, 1);
|
|
Zero = BM->addConstant(ResTy, 0);
|
|
APInt MinusOneValue(ResTy->getIntegerBitWidth(), 0, 1);
|
|
MinusOneValue.setAllBits();
|
|
MinusOne = BM->addConstant(ResTy, std::move(MinusOneValue));
|
|
}
|
|
|
|
Op OC1 = (IID == Intrinsic::scmp) ? OpSLessThanEqual : OpULessThanEqual;
|
|
Op OC2 = (IID == Intrinsic::scmp) ? OpSLessThan : OpULessThan;
|
|
if (auto *VecTy = dyn_cast<VectorType>(II->getArgOperand(0)->getType()))
|
|
BoolTy = VectorType::get(BoolTy, VecTy->getElementCount());
|
|
SPIRVValue *Cmp1 =
|
|
BM->addCmpInst(OC1, transType(BoolTy), FirstArgVal, SecondArgVal, BB);
|
|
SPIRVValue *Cmp2 =
|
|
BM->addCmpInst(OC2, transType(BoolTy), FirstArgVal, SecondArgVal, BB);
|
|
auto *ResCmp2 = BM->addSelectInst(Cmp2, MinusOne, Zero, BB);
|
|
auto *ResCmp1 = BM->addSelectInst(Cmp1, ResCmp2, One, BB);
|
|
return ResCmp1;
|
|
}
|
|
case Intrinsic::fma: {
|
|
if (!checkTypeForSPIRVExtendedInstLowering(II, BM))
|
|
break;
|
|
SPIRVType *STy = transType(II->getType());
|
|
std::vector<SPIRVValue *> Ops{transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB),
|
|
transValue(II->getArgOperand(2), BB)};
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_fma))
|
|
return BM->addInstTemplate(OpFmaKHR, BM->getIds(Ops), BB, STy);
|
|
return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL),
|
|
OpenCLLIB::Fma, Ops, BB);
|
|
}
|
|
case Intrinsic::abs: {
|
|
if (!checkTypeForSPIRVExtendedInstLowering(II, BM))
|
|
break;
|
|
// LLVM has only one version of abs and it is only for signed integers. We
|
|
// unconditionally choose SAbs here
|
|
SPIRVWord ExtOp = OpenCLLIB::SAbs;
|
|
SPIRVType *STy = transType(II->getType());
|
|
std::vector<SPIRVValue *> Ops(1, transValue(II->getArgOperand(0), BB));
|
|
return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops,
|
|
BB);
|
|
}
|
|
case Intrinsic::ctpop: {
|
|
return BM->addUnaryInst(OpBitCount, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB), BB);
|
|
}
|
|
case Intrinsic::ctlz:
|
|
case Intrinsic::cttz: {
|
|
SPIRVWord ExtOp = IID == Intrinsic::ctlz ? OpenCLLIB::Clz : OpenCLLIB::Ctz;
|
|
SPIRVType *Ty = transType(II->getType());
|
|
std::vector<SPIRVValue *> Ops(1, transValue(II->getArgOperand(0), BB));
|
|
return BM->addExtInst(Ty, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops,
|
|
BB);
|
|
}
|
|
case Intrinsic::expect: {
|
|
// llvm.expect translation is currently supported only within
|
|
// SPV_KHR_expect_assume extension, replace it with a translated value of #0
|
|
// operand otherwise, since it's an optimization hint
|
|
SPIRVValue *Value = transValue(II->getArgOperand(0), BB);
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_expect_assume)) {
|
|
SPIRVType *Ty = transType(II->getType());
|
|
SPIRVValue *ExpectedValue = transValue(II->getArgOperand(1), BB);
|
|
return BM->addExpectKHRInst(Ty, Value, ExpectedValue, BB);
|
|
}
|
|
return Value;
|
|
}
|
|
case Intrinsic::experimental_constrained_fadd: {
|
|
SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic "
|
|
"\"fadd\" not supported by SPIR-V, dropping it.\n");
|
|
return BM->addBinaryInst(OpFAdd, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB), BB);
|
|
}
|
|
case Intrinsic::experimental_constrained_fsub: {
|
|
SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic "
|
|
"\"fsub\" not supported by SPIR-V, dropping it.\n");
|
|
return BM->addBinaryInst(OpFSub, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB), BB);
|
|
}
|
|
case Intrinsic::experimental_constrained_fmul: {
|
|
SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic "
|
|
"\"fmul\" not supported by SPIR-V, dropping it.\n");
|
|
return BM->addBinaryInst(OpFMul, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB), BB);
|
|
}
|
|
case Intrinsic::experimental_constrained_fdiv: {
|
|
auto *BI = BM->addBinaryInst(OpFDiv, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB), BB);
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_rounded_divide_sqrt) &&
|
|
BI->getType()->isTypeIEEE754Float()) {
|
|
BM->addCapability(CapabilityRoundedDivideSqrtINTEL);
|
|
return applyRoundingModeConstraint(II->getOperand(2), BI);
|
|
}
|
|
SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic "
|
|
"\"fdiv\" not supported by SPIR-V without "
|
|
"SPV_INTEL_rounded_divide_sqrt, dropping it.\n");
|
|
return BI;
|
|
}
|
|
case Intrinsic::experimental_constrained_sqrt: {
|
|
SPIRVType *STy = transType(II->getType());
|
|
std::vector<SPIRVValue *> Ops{transValue(II->getArgOperand(0), BB)};
|
|
auto *BI = BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL),
|
|
OpenCLLIB::Sqrt, Ops, BB);
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_rounded_divide_sqrt) &&
|
|
BI->getType()->isTypeIEEE754Float()) {
|
|
BM->addCapability(CapabilityRoundedDivideSqrtINTEL);
|
|
return applyRoundingModeConstraint(II->getOperand(1), BI);
|
|
}
|
|
SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic "
|
|
"\"sqrt\" not supported by SPIR-V without "
|
|
"SPV_INTEL_rounded_divide_sqrt, dropping it.\n");
|
|
return BI;
|
|
}
|
|
case Intrinsic::experimental_constrained_frem: {
|
|
SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic "
|
|
"\"frem\" not supported by SPIR-V, dropping it.\n");
|
|
return BM->addBinaryInst(OpFRem, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB), BB);
|
|
}
|
|
case Intrinsic::experimental_constrained_fma: {
|
|
SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic "
|
|
"\"fma\" not supported by SPIR-V, dropping it.\n");
|
|
std::vector<SPIRVValue *> Args{transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB),
|
|
transValue(II->getArgOperand(2), BB)};
|
|
SPIRVInstruction *BI;
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_fma))
|
|
BI = BM->addInstTemplate(OpFmaKHR, BM->getIds(Args), BB,
|
|
transType(II->getType()));
|
|
else
|
|
BI = BM->addExtInst(transType(II->getType()),
|
|
BM->getExtInstSetId(SPIRVEIS_OpenCL), OpenCLLIB::Fma,
|
|
Args, BB);
|
|
return BI;
|
|
}
|
|
case Intrinsic::experimental_constrained_fptoui: {
|
|
return BM->addUnaryInst(OpConvertFToU, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB), BB);
|
|
}
|
|
case Intrinsic::experimental_constrained_fptosi: {
|
|
return BM->addUnaryInst(OpConvertFToS, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB), BB);
|
|
}
|
|
case Intrinsic::experimental_constrained_uitofp: {
|
|
auto *BI = BM->addUnaryInst(OpConvertUToF, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB), BB);
|
|
return applyRoundingModeConstraint(II->getOperand(1), BI);
|
|
}
|
|
case Intrinsic::experimental_constrained_sitofp: {
|
|
auto *BI = BM->addUnaryInst(OpConvertSToF, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB), BB);
|
|
return applyRoundingModeConstraint(II->getOperand(1), BI);
|
|
}
|
|
case Intrinsic::experimental_constrained_fpext: {
|
|
return BM->addUnaryInst(OpFConvert, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB), BB);
|
|
}
|
|
case Intrinsic::experimental_constrained_fptrunc: {
|
|
auto *BI = BM->addUnaryInst(OpFConvert, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB), BB);
|
|
return applyRoundingModeConstraint(II->getOperand(1), BI);
|
|
}
|
|
case Intrinsic::experimental_constrained_fcmp:
|
|
case Intrinsic::experimental_constrained_fcmps: {
|
|
auto *MetaMod = cast<MetadataAsValue>(II->getOperand(2))->getMetadata();
|
|
Op CmpTypeOp = StringSwitch<Op>(cast<MDString>(MetaMod)->getString())
|
|
.Case("oeq", OpFOrdEqual)
|
|
.Case("ogt", OpFOrdGreaterThan)
|
|
.Case("oge", OpFOrdGreaterThanEqual)
|
|
.Case("olt", OpFOrdLessThan)
|
|
.Case("ole", OpFOrdLessThanEqual)
|
|
.Case("one", OpFOrdNotEqual)
|
|
.Case("ord", OpOrdered)
|
|
.Case("ueq", OpFUnordEqual)
|
|
.Case("ugt", OpFUnordGreaterThan)
|
|
.Case("uge", OpFUnordGreaterThanEqual)
|
|
.Case("ult", OpFUnordLessThan)
|
|
.Case("ule", OpFUnordLessThanEqual)
|
|
.Case("une", OpFUnordNotEqual)
|
|
.Case("uno", OpUnordered)
|
|
.Default(OpNop);
|
|
assert(CmpTypeOp != OpNop && "Invalid condition code!");
|
|
return BM->addCmpInst(CmpTypeOp, transType(II->getType()),
|
|
transValue(II->getOperand(0), BB),
|
|
transValue(II->getOperand(1), BB), BB);
|
|
}
|
|
case Intrinsic::experimental_constrained_fmuladd: {
|
|
SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic "
|
|
"\"fmuladd\" not supported by SPIR-V, dropping it.\n");
|
|
SPIRVType *Ty = transType(II->getType());
|
|
SPIRVValue *Mul =
|
|
BM->addBinaryInst(OpFMul, Ty, transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB), BB);
|
|
return BM->addBinaryInst(OpFAdd, Ty, Mul,
|
|
transValue(II->getArgOperand(2), BB), BB);
|
|
}
|
|
case Intrinsic::fmuladd: {
|
|
// For llvm.fmuladd.* fusion is not guaranteed. If a fused multiply-add
|
|
// is required the corresponding llvm.fma.* intrinsic function should be
|
|
// used instead.
|
|
// If allowed, let's replace llvm.fmuladd.* with mad from OpenCL extended
|
|
// instruction set, as it has the same semantic for FULL_PROFILE OpenCL
|
|
// devices (implementation-defined for EMBEDDED_PROFILE).
|
|
if (BM->shouldReplaceLLVMFmulAddWithOpenCLMad() ||
|
|
BM->getExtInst() == SPIRV::ExtInst::OpenCL) {
|
|
std::vector<SPIRVValue *> Ops{transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB),
|
|
transValue(II->getArgOperand(2), BB)};
|
|
return BM->addExtInst(transType(II->getType()),
|
|
BM->getExtInstSetId(SPIRVEIS_OpenCL),
|
|
OpenCLLIB::Mad, Ops, BB);
|
|
}
|
|
|
|
// Otherwise, just break llvm.fmuladd.* into a pair of fmul + fadd
|
|
SPIRVType *Ty = transType(II->getType());
|
|
SPIRVValue *Mul =
|
|
BM->addBinaryInst(OpFMul, Ty, transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB), BB);
|
|
return BM->addBinaryInst(OpFAdd, Ty, Mul,
|
|
transValue(II->getArgOperand(2), BB), BB);
|
|
}
|
|
case Intrinsic::fptoui_sat: {
|
|
auto *UI = BM->addUnaryInst(OpConvertFToU, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB), BB);
|
|
UI->setSaturatedConversion(true);
|
|
return UI;
|
|
}
|
|
case Intrinsic::fptosi_sat: {
|
|
auto *UI = BM->addUnaryInst(OpConvertFToS, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB), BB);
|
|
UI->setSaturatedConversion(true);
|
|
return UI;
|
|
}
|
|
case Intrinsic::uadd_sat:
|
|
case Intrinsic::usub_sat:
|
|
case Intrinsic::sadd_sat:
|
|
case Intrinsic::ssub_sat: {
|
|
SPIRVWord ExtOp;
|
|
if (IID == Intrinsic::uadd_sat)
|
|
ExtOp = OpenCLLIB::UAdd_sat;
|
|
else if (IID == Intrinsic::usub_sat)
|
|
ExtOp = OpenCLLIB::USub_sat;
|
|
else if (IID == Intrinsic::sadd_sat)
|
|
ExtOp = OpenCLLIB::SAdd_sat;
|
|
else
|
|
ExtOp = OpenCLLIB::SSub_sat;
|
|
|
|
SPIRVType *Ty = transType(II->getType());
|
|
std::vector<SPIRVValue *> Operands = {transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB)};
|
|
return BM->addExtInst(Ty, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp,
|
|
std::move(Operands), BB);
|
|
}
|
|
case Intrinsic::uadd_with_overflow: {
|
|
return BM->addBinaryInst(OpIAddCarry, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB), BB);
|
|
}
|
|
case Intrinsic::usub_with_overflow: {
|
|
return BM->addBinaryInst(OpISubBorrow, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB), BB);
|
|
}
|
|
case Intrinsic::vector_reduce_add:
|
|
case Intrinsic::vector_reduce_mul:
|
|
case Intrinsic::vector_reduce_and:
|
|
case Intrinsic::vector_reduce_or:
|
|
case Intrinsic::vector_reduce_xor: {
|
|
Op Op;
|
|
if (IID == Intrinsic::vector_reduce_add) {
|
|
Op = OpIAdd;
|
|
} else if (IID == Intrinsic::vector_reduce_mul) {
|
|
Op = OpIMul;
|
|
} else if (IID == Intrinsic::vector_reduce_and) {
|
|
Op = OpBitwiseAnd;
|
|
} else if (IID == Intrinsic::vector_reduce_or) {
|
|
Op = OpBitwiseOr;
|
|
} else {
|
|
Op = OpBitwiseXor;
|
|
}
|
|
VectorType *VecTy = cast<VectorType>(II->getArgOperand(0)->getType());
|
|
SPIRVValue *VecSVal = transValue(II->getArgOperand(0), BB);
|
|
SPIRVTypeInt *ResultSType =
|
|
BM->addIntegerType(VecTy->getElementType()->getIntegerBitWidth());
|
|
SPIRVTypeInt *I32STy = BM->addIntegerType(32);
|
|
unsigned VecSize = VecTy->getElementCount().getFixedValue();
|
|
if (VecSize > 0) {
|
|
SmallVector<SPIRVValue *, 16> Extracts(VecSize);
|
|
for (unsigned Idx = 0; Idx < VecSize; ++Idx) {
|
|
Extracts[Idx] = BM->addVectorExtractDynamicInst(
|
|
VecSVal, BM->addIntegerConstant(I32STy, Idx), BB);
|
|
}
|
|
unsigned Counter = VecSize >> 1;
|
|
while (Counter != 0) {
|
|
for (unsigned Idx = 0; Idx < Counter; ++Idx) {
|
|
Extracts[Idx] = BM->addBinaryInst(Op, ResultSType, Extracts[Idx << 1],
|
|
Extracts[(Idx << 1) + 1], BB);
|
|
}
|
|
Counter >>= 1;
|
|
}
|
|
if ((VecSize & 1) != 0) {
|
|
Extracts[0] = BM->addBinaryInst(Op, ResultSType, Extracts[0],
|
|
Extracts[VecSize - 1], BB);
|
|
}
|
|
return Extracts[0];
|
|
}
|
|
assert(VecSize && "Zero Extracts size for vector reduce lowering");
|
|
return nullptr;
|
|
}
|
|
case Intrinsic::vector_reduce_fadd:
|
|
case Intrinsic::vector_reduce_fmul: {
|
|
Op Op = IID == Intrinsic::vector_reduce_fadd ? OpFAdd : OpFMul;
|
|
VectorType *VecTy = cast<VectorType>(II->getArgOperand(1)->getType());
|
|
SPIRVValue *VecSVal = transValue(II->getArgOperand(1), BB);
|
|
SPIRVValue *StartingSVal = transValue(II->getArgOperand(0), BB);
|
|
SPIRVTypeInt *I32STy = BM->addIntegerType(32);
|
|
unsigned VecSize = VecTy->getElementCount().getFixedValue();
|
|
if (VecSize > 0) {
|
|
SmallVector<SPIRVValue *, 16> Extracts(VecSize);
|
|
for (unsigned Idx = 0; Idx < VecSize; ++Idx) {
|
|
Extracts[Idx] = BM->addVectorExtractDynamicInst(
|
|
VecSVal, BM->addIntegerConstant(I32STy, Idx), BB);
|
|
}
|
|
SPIRVValue *V = BM->addBinaryInst(Op, StartingSVal->getType(),
|
|
StartingSVal, Extracts[0], BB);
|
|
for (unsigned Idx = 1; Idx < VecSize; ++Idx) {
|
|
V = BM->addBinaryInst(Op, StartingSVal->getType(), V, Extracts[Idx],
|
|
BB);
|
|
}
|
|
return V;
|
|
}
|
|
assert(VecSize && "Zero Extracts size for vector reduce lowering");
|
|
return nullptr;
|
|
}
|
|
case Intrinsic::vector_reduce_smax:
|
|
case Intrinsic::vector_reduce_smin:
|
|
case Intrinsic::vector_reduce_umax:
|
|
case Intrinsic::vector_reduce_umin:
|
|
case Intrinsic::vector_reduce_fmax:
|
|
case Intrinsic::vector_reduce_fmin:
|
|
case Intrinsic::vector_reduce_fmaximum:
|
|
case Intrinsic::vector_reduce_fminimum: {
|
|
Op Op;
|
|
if (IID == Intrinsic::vector_reduce_smax) {
|
|
Op = OpSGreaterThan;
|
|
} else if (IID == Intrinsic::vector_reduce_smin) {
|
|
Op = OpSLessThan;
|
|
} else if (IID == Intrinsic::vector_reduce_umax) {
|
|
Op = OpUGreaterThan;
|
|
} else if (IID == Intrinsic::vector_reduce_umin) {
|
|
Op = OpULessThan;
|
|
} else if (IID == Intrinsic::vector_reduce_fmax) {
|
|
Op = OpFOrdGreaterThan;
|
|
} else if (IID == Intrinsic::vector_reduce_fmin) {
|
|
Op = OpFOrdLessThan;
|
|
} else if (IID == Intrinsic::vector_reduce_fmaximum) {
|
|
Op = OpFUnordGreaterThan;
|
|
} else {
|
|
Op = OpFUnordLessThan;
|
|
}
|
|
VectorType *VecTy = cast<VectorType>(II->getArgOperand(0)->getType());
|
|
SPIRVValue *VecSVal = transValue(II->getArgOperand(0), BB);
|
|
SPIRVType *BoolSTy = transType(Type::getInt1Ty(II->getContext()));
|
|
SPIRVTypeInt *I32STy = BM->addIntegerType(32);
|
|
unsigned VecSize = VecTy->getElementCount().getFixedValue();
|
|
SmallVector<SPIRVValue *, 16> Extracts(VecSize);
|
|
if (VecSize > 0) {
|
|
for (unsigned Idx = 0; Idx < VecSize; ++Idx) {
|
|
Extracts[Idx] = BM->addVectorExtractDynamicInst(
|
|
VecSVal, BM->addIntegerConstant(I32STy, Idx), BB);
|
|
}
|
|
unsigned Counter = VecSize >> 1;
|
|
while (Counter != 0) {
|
|
for (unsigned Idx = 0; Idx < Counter; ++Idx) {
|
|
SPIRVValue *Cond = BM->addBinaryInst(Op, BoolSTy, Extracts[Idx << 1],
|
|
Extracts[(Idx << 1) + 1], BB);
|
|
Extracts[Idx] = BM->addSelectInst(Cond, Extracts[Idx << 1],
|
|
Extracts[(Idx << 1) + 1], BB);
|
|
}
|
|
Counter >>= 1;
|
|
}
|
|
if ((VecSize & 1) != 0) {
|
|
SPIRVValue *Cond = BM->addBinaryInst(Op, BoolSTy, Extracts[0],
|
|
Extracts[VecSize - 1], BB);
|
|
Extracts[0] =
|
|
BM->addSelectInst(Cond, Extracts[0], Extracts[VecSize - 1], BB);
|
|
}
|
|
return Extracts[0];
|
|
}
|
|
assert(VecSize && "Zero Extracts size for vector reduce lowering");
|
|
return nullptr;
|
|
}
|
|
case Intrinsic::memset: {
|
|
// Generally there is no direct mapping of memset to SPIR-V. But it turns
|
|
// out that memset is emitted by Clang for initialization in default
|
|
// constructors so we need some basic support. The code below only handles
|
|
// cases with constant value and constant length.
|
|
MemSetInst *MSI = cast<MemSetInst>(II);
|
|
Value *Val = MSI->getValue();
|
|
if (!isa<Constant>(Val)) {
|
|
assert(false &&
|
|
"Can't translate llvm.memset with non-const `value` argument");
|
|
return nullptr;
|
|
}
|
|
Value *Len = MSI->getLength();
|
|
if (!isa<ConstantInt>(Len)) {
|
|
assert(false &&
|
|
"Can't translate llvm.memset with non-const `length` argument");
|
|
return nullptr;
|
|
}
|
|
uint64_t NumElements = static_cast<ConstantInt *>(Len)->getZExtValue();
|
|
auto *AT = ArrayType::get(Val->getType(), NumElements);
|
|
SPIRVTypeArray *CompositeTy = static_cast<SPIRVTypeArray *>(transType(AT));
|
|
SPIRVValue *Init;
|
|
if (cast<Constant>(Val)->isZeroValue()) {
|
|
Init = BM->addNullConstant(CompositeTy);
|
|
} else {
|
|
// On 32-bit systems, size_type of std::vector is not a 64-bit type. Let's
|
|
// assume that we won't encounter memset for more than 2^32 elements and
|
|
// insert explicit cast to avoid possible warning/error about narrowing
|
|
// conversion
|
|
auto TNumElts =
|
|
static_cast<std::vector<SPIRVValue *>::size_type>(NumElements);
|
|
std::vector<SPIRVValue *> Elts(TNumElts, transValue(Val, BB));
|
|
Init = BM->addCompositeConstant(CompositeTy, Elts);
|
|
}
|
|
SPIRVType *VarTy = transPointerType(AT, SPIRV::SPIRAS_Constant);
|
|
SPIRVValue *Var = BM->addVariable(VarTy, nullptr, /*isConstant*/ true,
|
|
spv::internal::LinkageTypeInternal, Init,
|
|
"", StorageClassUniformConstant, nullptr);
|
|
std::vector<SPIRVWord> MemAccess = GetMemoryAccess(
|
|
MSI, BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4));
|
|
if (!MemAccess.empty() && MemAccess[0] == MemoryAccessAlignedMask)
|
|
Var->setAlignment(MemAccess[1]);
|
|
SPIRVType *SourceTy =
|
|
transPointerType(Val->getType(), SPIRV::SPIRAS_Constant);
|
|
SPIRVValue *Source = BM->addUnaryInst(OpBitcast, SourceTy, Var, BB);
|
|
SPIRVValue *Target = transValue(MSI->getRawDest(), BB);
|
|
return BM->addCopyMemorySizedInst(Target, Source, CompositeTy->getLength(),
|
|
MemAccess, BB);
|
|
} break;
|
|
case Intrinsic::memcpy:
|
|
return BM->addCopyMemorySizedInst(
|
|
transValue(II->getOperand(0), BB), transValue(II->getOperand(1), BB),
|
|
transValue(II->getOperand(2), BB),
|
|
GetMemoryAccess(cast<MemIntrinsic>(II),
|
|
BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)),
|
|
BB);
|
|
case Intrinsic::lifetime_start:
|
|
case Intrinsic::lifetime_end: {
|
|
Op OC = (II->getIntrinsicID() == Intrinsic::lifetime_start)
|
|
? OpLifetimeStart
|
|
: OpLifetimeStop;
|
|
int64_t Size = dyn_cast<ConstantInt>(II->getOperand(0))->getSExtValue();
|
|
if (Size == -1)
|
|
Size = 0;
|
|
Value *LLVMPtrOp = II->getOperand(1);
|
|
unsigned PtrAS = cast<PointerType>(LLVMPtrOp->getType())->getAddressSpace();
|
|
auto *PtrOp = transValue(LLVMPtrOp, BB);
|
|
if (PtrAS == SPIRAS_Private)
|
|
return BM->addLifetimeInst(OC, PtrOp, Size, BB);
|
|
// If pointer address space is Generic - cast to private first
|
|
BM->getErrorLog().checkError(
|
|
PtrAS == SPIRAS_Generic, SPIRVEC_InvalidInstruction, II,
|
|
"lifetime intrinsic pointer operand must be in private or generic AS");
|
|
auto *SrcTy = PtrOp->getType();
|
|
SPIRVType *DstTy = nullptr;
|
|
if (SrcTy->isTypeUntypedPointerKHR())
|
|
DstTy = BM->addPointerType(StorageClassFunction, nullptr);
|
|
else
|
|
DstTy = BM->addPointerType(StorageClassFunction,
|
|
SrcTy->getPointerElementType());
|
|
PtrOp = BM->addUnaryInst(OpGenericCastToPtr, DstTy, PtrOp, BB);
|
|
ValueMap[LLVMPtrOp] = PtrOp;
|
|
return BM->addLifetimeInst(OC, PtrOp, Size, BB);
|
|
}
|
|
// We don't want to mix translation of regular code and debug info, because
|
|
// it creates a mess, therefore translation of debug intrinsics is
|
|
// postponed until LLVMToSPIRVDbgTran::finalizeDebug...() methods.
|
|
case Intrinsic::dbg_declare:
|
|
return DbgTran->createDebugDeclarePlaceholder(cast<DbgDeclareInst>(II), BB);
|
|
case Intrinsic::dbg_value:
|
|
return DbgTran->createDebugValuePlaceholder(cast<DbgValueInst>(II), BB);
|
|
case Intrinsic::annotation: {
|
|
SPIRVType *Ty = transScavengedType(II);
|
|
Constant *C = cast<Constant>(II->getArgOperand(1)->stripPointerCasts());
|
|
StringRef AnnotationString;
|
|
if (!getConstantStringInfo(C, AnnotationString))
|
|
return nullptr;
|
|
|
|
if (AnnotationString == kOCLBuiltinName::FPGARegIntel) {
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_reg))
|
|
return BM->addFPGARegINTELInst(Ty, transValue(II->getOperand(0), BB),
|
|
BB);
|
|
else
|
|
return transValue(II->getOperand(0), BB);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
case Intrinsic::var_annotation: {
|
|
SPIRVValue *SV;
|
|
if (auto *BI = dyn_cast<BitCastInst>(II->getArgOperand(0))) {
|
|
SV = transValue(BI->getOperand(0), BB);
|
|
} else {
|
|
SV = transValue(II->getOperand(0), BB);
|
|
}
|
|
|
|
std::string AnnotationString;
|
|
processAnnotationString(II, AnnotationString);
|
|
DecorationsInfoVec Decorations =
|
|
tryParseAnnotationString(BM, AnnotationString).MemoryAttributesVec;
|
|
|
|
// If we didn't find any IntelFPGA-specific decorations, let's add the whole
|
|
// annotation string as UserSemantic Decoration
|
|
if (Decorations.empty()) {
|
|
SV->addDecorate(
|
|
new SPIRVDecorateUserSemanticAttr(SV, AnnotationString.c_str()));
|
|
} else {
|
|
addAnnotationDecorations(SV, Decorations);
|
|
}
|
|
return SV;
|
|
}
|
|
// The layout of llvm.ptr.annotation is:
|
|
// declare iN* @llvm.ptr.annotation.p<address space>iN(
|
|
// iN* <val>, i8* <str>, i8* <str>, i32 <int>, i8* <ptr>)
|
|
// where N is a power of two number,
|
|
// first i8* <str> stands for the annotation itself,
|
|
// second i8* <str> is for the location (file name),
|
|
// i8* <ptr> is a pointer on a GV, which can carry optinal variadic
|
|
// clang::annotation attribute expression arguments.
|
|
case Intrinsic::ptr_annotation: {
|
|
Value *AnnotSubj = nullptr;
|
|
if (auto *BI = dyn_cast<BitCastInst>(II->getArgOperand(0))) {
|
|
AnnotSubj = BI->getOperand(0);
|
|
} else {
|
|
AnnotSubj = II->getOperand(0);
|
|
}
|
|
|
|
std::string AnnotationString;
|
|
processAnnotationString(II, AnnotationString);
|
|
AnnotationDecorations Decorations =
|
|
tryParseAnnotationString(BM, AnnotationString);
|
|
// Translate FPGARegIntel annotations to OpFPGARegINTEL.
|
|
if (AnnotationString == kOCLBuiltinName::FPGARegIntel) {
|
|
auto *Ty = transScavengedType(II);
|
|
auto *BI = dyn_cast<BitCastInst>(II->getOperand(0));
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_reg))
|
|
return BM->addFPGARegINTELInst(Ty, transValue(BI, BB), BB);
|
|
return transValue(BI, BB);
|
|
}
|
|
|
|
SPIRVValue *DecSubj = transValue(AnnotSubj, BB);
|
|
if (Decorations.empty()) {
|
|
DecSubj->addDecorate(
|
|
new SPIRVDecorateUserSemanticAttr(DecSubj, AnnotationString.c_str()));
|
|
} else {
|
|
addAnnotationDecorations(DecSubj, Decorations.MemoryAttributesVec);
|
|
// Apply the LSU parameter decoration to the pointer result of a GEP
|
|
// to the given struct member (InBoundsPtrAccessChain in SPIR-V).
|
|
// Decorating the member itself with a MemberDecoration is not feasible,
|
|
// because multiple accesses to the struct-held memory can require
|
|
// different LSU parameters.
|
|
addAnnotationDecorations(DecSubj, Decorations.MemoryAccessesVec);
|
|
addAnnotationDecorations(DecSubj, Decorations.CacheControlVec);
|
|
addAnnotationDecorations(DecSubj, Decorations.BufferLocationVec);
|
|
if (allowDecorateWithLatencyControlINTEL(II)) {
|
|
addAnnotationDecorations(DecSubj, Decorations.LatencyControlVec);
|
|
}
|
|
}
|
|
II->replaceAllUsesWith(II->getOperand(0));
|
|
return DecSubj;
|
|
}
|
|
case Intrinsic::stacksave: {
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_variable_length_array)) {
|
|
auto *Ty = transPointerType(Type::getInt8Ty(M->getContext()), 0);
|
|
return BM->addInstTemplate(OpSaveMemoryINTEL, BB, Ty);
|
|
}
|
|
BM->getErrorLog().checkError(
|
|
BM->isUnknownIntrinsicAllowed(II), SPIRVEC_InvalidFunctionCall, II,
|
|
"Translation of llvm.stacksave intrinsic requires "
|
|
"SPV_INTEL_variable_length_array extension or "
|
|
"-spirv-allow-unknown-intrinsics option.");
|
|
break;
|
|
}
|
|
case Intrinsic::stackrestore: {
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_variable_length_array)) {
|
|
auto *Ptr = transValue(II->getArgOperand(0), BB);
|
|
return BM->addInstTemplate(OpRestoreMemoryINTEL, {Ptr->getId()}, BB,
|
|
nullptr);
|
|
}
|
|
BM->getErrorLog().checkError(
|
|
BM->isUnknownIntrinsicAllowed(II), SPIRVEC_InvalidFunctionCall, II,
|
|
"Translation of llvm.restore intrinsic requires "
|
|
"SPV_INTEL_variable_length_array extension or "
|
|
"-spirv-allow-unknown-intrinsics option.");
|
|
break;
|
|
}
|
|
// We can just ignore/drop some intrinsics, like optimizations hint.
|
|
case Intrinsic::experimental_noalias_scope_decl:
|
|
case Intrinsic::invariant_start:
|
|
case Intrinsic::invariant_end:
|
|
case Intrinsic::dbg_label:
|
|
// llvm.trap and llvm.debugtrap intrinsics are not implemented. But for now
|
|
// don't crash. This change is pending the trap/abort intrinsic
|
|
// implementation.
|
|
case Intrinsic::trap:
|
|
case Intrinsic::ubsantrap:
|
|
case Intrinsic::debugtrap:
|
|
// Just ignore llvm.fake.use intrinsic, as it has no translation in SPIRV.
|
|
case Intrinsic::fake_use:
|
|
// llvm.instrprof.* intrinsics are not supported
|
|
case Intrinsic::instrprof_increment:
|
|
case Intrinsic::instrprof_increment_step:
|
|
case Intrinsic::instrprof_value_profile:
|
|
return nullptr;
|
|
case Intrinsic::is_constant: {
|
|
auto *CO = dyn_cast<Constant>(II->getOperand(0));
|
|
if (CO && isManifestConstant(CO))
|
|
return transValue(ConstantInt::getTrue(II->getType()), BB, false);
|
|
else
|
|
return transValue(ConstantInt::getFalse(II->getType()), BB, false);
|
|
}
|
|
case Intrinsic::arithmetic_fence: {
|
|
SPIRVType *Ty = transType(II->getType());
|
|
SPIRVValue *Op = transValue(II->getArgOperand(0), BB);
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_EXT_arithmetic_fence)) {
|
|
BM->addCapability(CapabilityArithmeticFenceEXT);
|
|
BM->addExtension(ExtensionID::SPV_EXT_arithmetic_fence);
|
|
return BM->addUnaryInst(OpArithmeticFenceEXT, Ty, Op, BB);
|
|
}
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_arithmetic_fence)) {
|
|
// Note: SPV_INTEL_arithmetic_fence was unpublished and superseded by
|
|
// SPV_EXT_arithmetic_fence.
|
|
BM->addCapability(CapabilityArithmeticFenceEXT);
|
|
BM->addExtension(ExtensionID::SPV_INTEL_arithmetic_fence);
|
|
return BM->addUnaryInst(OpArithmeticFenceEXT, Ty, Op, BB);
|
|
}
|
|
return Op;
|
|
}
|
|
case Intrinsic::masked_gather: {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_masked_gather_scatter)) {
|
|
BM->getErrorLog().checkError(
|
|
BM->isUnknownIntrinsicAllowed(II), SPIRVEC_InvalidFunctionCall, II,
|
|
"Translation of llvm.masked.gather intrinsic requires "
|
|
"SPV_INTEL_masked_gather_scatter extension or "
|
|
"-spirv-allow-unknown-intrinsics option.");
|
|
return nullptr;
|
|
}
|
|
SPIRVType *Ty = transScavengedType(II);
|
|
auto *PtrVector = transValue(II->getArgOperand(0), BB);
|
|
uint32_t Alignment =
|
|
cast<ConstantInt>(II->getArgOperand(1))->getZExtValue();
|
|
auto *Mask = transValue(II->getArgOperand(2), BB);
|
|
auto *FillEmpty = transValue(II->getArgOperand(3), BB);
|
|
std::vector<SPIRVWord> Ops = {PtrVector->getId(), Alignment, Mask->getId(),
|
|
FillEmpty->getId()};
|
|
return BM->addInstTemplate(internal::OpMaskedGatherINTEL, Ops, BB, Ty);
|
|
}
|
|
case Intrinsic::masked_scatter: {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_masked_gather_scatter)) {
|
|
BM->getErrorLog().checkError(
|
|
BM->isUnknownIntrinsicAllowed(II), SPIRVEC_InvalidFunctionCall, II,
|
|
"Translation of llvm.masked.scatter intrinsic requires "
|
|
"SPV_INTEL_masked_gather_scatter extension or "
|
|
"-spirv-allow-unknown-intrinsics option.");
|
|
return nullptr;
|
|
}
|
|
auto *InputVector = transValue(II->getArgOperand(0), BB);
|
|
auto *PtrVector = transValue(II->getArgOperand(1), BB);
|
|
uint32_t Alignment =
|
|
cast<ConstantInt>(II->getArgOperand(2))->getZExtValue();
|
|
auto *Mask = transValue(II->getArgOperand(3), BB);
|
|
std::vector<SPIRVWord> Ops = {InputVector->getId(), PtrVector->getId(),
|
|
Alignment, Mask->getId()};
|
|
return BM->addInstTemplate(internal::OpMaskedScatterINTEL, Ops, BB,
|
|
nullptr);
|
|
}
|
|
case Intrinsic::is_fpclass: {
|
|
// There is no direct counterpart for the intrinsic in SPIR-V, hence
|
|
// we need to emulate its work by sequence of other instructions
|
|
SPIRVType *ResTy = transType(II->getType());
|
|
llvm::FPClassTest FPClass = static_cast<llvm::FPClassTest>(
|
|
cast<ConstantInt>(II->getArgOperand(1))->getZExtValue());
|
|
// if no tests are provided - return false
|
|
if (FPClass == 0)
|
|
return BM->addConstant(ResTy, false);
|
|
// if all tests are provided - return true
|
|
if (FPClass == fcAllFlags)
|
|
return BM->addConstant(ResTy, true);
|
|
|
|
Type *OpLLVMTy = II->getArgOperand(0)->getType();
|
|
SPIRVValue *InputFloat = transValue(II->getArgOperand(0), BB);
|
|
std::vector<SPIRVValue *> ResultVec;
|
|
|
|
// Adds test for Negative/Positive values
|
|
SPIRVValue *SignBitTest = nullptr;
|
|
SPIRVValue *NoSignTest = nullptr;
|
|
auto GetNegPosInstTest = [&](SPIRVValue *TestInst,
|
|
bool IsNegative) -> SPIRVValue * {
|
|
SignBitTest = (SignBitTest)
|
|
? SignBitTest
|
|
: BM->addInstTemplate(OpSignBitSet,
|
|
{InputFloat->getId()}, BB, ResTy);
|
|
if (IsNegative) {
|
|
return BM->addInstTemplate(
|
|
OpLogicalAnd, {SignBitTest->getId(), TestInst->getId()}, BB, ResTy);
|
|
}
|
|
NoSignTest = (NoSignTest)
|
|
? NoSignTest
|
|
: BM->addInstTemplate(OpLogicalNot,
|
|
{SignBitTest->getId()}, BB, ResTy);
|
|
return BM->addInstTemplate(
|
|
OpLogicalAnd, {NoSignTest->getId(), TestInst->getId()}, BB, ResTy);
|
|
};
|
|
|
|
// Get LLVM Op type converted to integer. It can be either scalar or vector.
|
|
const uint32_t BitSize = OpLLVMTy->getScalarSizeInBits();
|
|
Type *IntOpLLVMTy = IntegerType::getIntNTy(M->getContext(), BitSize);
|
|
if (OpLLVMTy->isVectorTy())
|
|
IntOpLLVMTy = FixedVectorType::get(
|
|
IntOpLLVMTy, cast<FixedVectorType>(OpLLVMTy)->getNumElements());
|
|
SPIRVType *OpSPIRVTy = transType(IntOpLLVMTy);
|
|
const llvm::fltSemantics &Semantics =
|
|
OpLLVMTy->getScalarType()->getFltSemantics();
|
|
const APInt Inf = APFloat::getInf(Semantics).bitcastToAPInt();
|
|
const APInt AllOneMantissa =
|
|
APFloat::getLargest(Semantics).bitcastToAPInt() & ~Inf;
|
|
const APInt OneValue = APInt(BitSize, 1);
|
|
// Some checks can be inverted tests for simple cases, for example
|
|
// simultaneous check for inf, normal, subnormal and zero is a check for
|
|
// non nan.
|
|
auto GetInvertedFPClassTest =
|
|
[](const llvm::FPClassTest Test) -> llvm::FPClassTest {
|
|
llvm::FPClassTest InvertedTest = ~Test & fcAllFlags;
|
|
switch (InvertedTest) {
|
|
default:
|
|
break;
|
|
case fcNan:
|
|
case fcSNan:
|
|
case fcQNan:
|
|
case fcInf:
|
|
case fcPosInf:
|
|
case fcNegInf:
|
|
case fcNormal:
|
|
case fcPosNormal:
|
|
case fcNegNormal:
|
|
case fcSubnormal:
|
|
case fcPosSubnormal:
|
|
case fcNegSubnormal:
|
|
case fcZero:
|
|
case fcPosZero:
|
|
case fcNegZero:
|
|
case fcFinite:
|
|
case fcPosFinite:
|
|
case fcNegFinite:
|
|
return InvertedTest;
|
|
}
|
|
return fcNone;
|
|
};
|
|
bool IsInverted = false;
|
|
if (llvm::FPClassTest InvertedCheck = GetInvertedFPClassTest(FPClass)) {
|
|
IsInverted = true;
|
|
FPClass = InvertedCheck;
|
|
}
|
|
auto GetInvertedTestIfNeeded = [&](SPIRVValue *TestInst) -> SPIRVValue * {
|
|
if (!IsInverted)
|
|
return TestInst;
|
|
return BM->addInstTemplate(OpLogicalNot, {TestInst->getId()}, BB, ResTy);
|
|
};
|
|
|
|
// TODO: we can add some optimization for fcFinite check by replacing it
|
|
// with fabs + cmp to 0x7FF0000000000000
|
|
|
|
// Integer parameter of the intrinsic is combined from several bit masks
|
|
// referenced in FPClassTest enum from FloatingPointMode.h in LLVM.
|
|
// Since a single intrinsic can provide multiple tests - here we might end
|
|
// up adding several sequences of SPIR-V instructions
|
|
if (FPClass & fcNan) {
|
|
// Map on OpIsNan if we have both QNan and SNan test bits set
|
|
if (FPClass & fcSNan && FPClass & fcQNan) {
|
|
auto *TestIsNan =
|
|
BM->addInstTemplate(OpIsNan, {InputFloat->getId()}, BB, ResTy);
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsNan));
|
|
} else {
|
|
// isquiet(V) ==> abs(V) >= (unsigned(Inf) | quiet_bit)
|
|
APInt QNaNBitMask =
|
|
APInt::getOneBitSet(BitSize, AllOneMantissa.getActiveBits() - 1);
|
|
APInt InfWithQnanBit = Inf | QNaNBitMask;
|
|
auto *QNanBitConst = transValue(
|
|
Constant::getIntegerValue(IntOpLLVMTy, InfWithQnanBit), BB);
|
|
auto *BitCastToInt =
|
|
BM->addUnaryInst(OpBitcast, OpSPIRVTy, InputFloat, BB);
|
|
auto *TestIsQNan = BM->addCmpInst(OpUGreaterThanEqual, ResTy,
|
|
BitCastToInt, QNanBitConst, BB);
|
|
if (FPClass & fcQNan) {
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsQNan));
|
|
} else {
|
|
// issignaling(V) ==> isnan(V) && !isquiet(V)
|
|
auto *TestIsNan =
|
|
BM->addInstTemplate(OpIsNan, {InputFloat->getId()}, BB, ResTy);
|
|
auto *NotQNan = BM->addInstTemplate(OpLogicalNot,
|
|
{TestIsQNan->getId()}, BB, ResTy);
|
|
auto *TestIsSNan = BM->addInstTemplate(
|
|
OpLogicalAnd, {TestIsNan->getId(), NotQNan->getId()}, BB, ResTy);
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsSNan));
|
|
}
|
|
}
|
|
}
|
|
if (FPClass & fcInf) {
|
|
auto *TestIsInf =
|
|
BM->addInstTemplate(OpIsInf, {InputFloat->getId()}, BB, ResTy);
|
|
if (FPClass & fcNegInf && FPClass & fcPosInf)
|
|
// Map on OpIsInf if we have both Inf test bits set
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsInf));
|
|
else
|
|
// Map on OpIsInf with following check for sign bit
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(
|
|
GetNegPosInstTest(TestIsInf, FPClass & fcNegInf)));
|
|
}
|
|
if (FPClass & fcNormal) {
|
|
auto *TestIsNormal =
|
|
BM->addInstTemplate(OpIsNormal, {InputFloat->getId()}, BB, ResTy);
|
|
if (FPClass & fcNegNormal && FPClass & fcPosNormal)
|
|
// Map on OpIsNormal if we have both Normal test bits set
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsNormal));
|
|
else
|
|
// Map on OpIsNormal with following check for sign bit
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(
|
|
GetNegPosInstTest(TestIsNormal, FPClass & fcNegNormal)));
|
|
}
|
|
if (FPClass & fcSubnormal) {
|
|
// issubnormal(V) ==> unsigned(abs(V) - 1) < (all mantissa bits set)
|
|
auto *BitCastToInt =
|
|
BM->addUnaryInst(OpBitcast, OpSPIRVTy, InputFloat, BB);
|
|
auto *MantissaConst = transValue(
|
|
Constant::getIntegerValue(IntOpLLVMTy, AllOneMantissa), BB);
|
|
auto *ConstOne =
|
|
transValue(Constant::getIntegerValue(IntOpLLVMTy, OneValue), BB);
|
|
auto *MinusOne =
|
|
BM->addBinaryInst(OpISub, OpSPIRVTy, BitCastToInt, ConstOne, BB);
|
|
auto *TestIsSubnormal =
|
|
BM->addCmpInst(OpULessThan, ResTy, MinusOne, MantissaConst, BB);
|
|
if (FPClass & fcPosSubnormal && FPClass & fcNegSubnormal)
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsSubnormal));
|
|
else
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(
|
|
GetNegPosInstTest(TestIsSubnormal, FPClass & fcNegSubnormal)));
|
|
}
|
|
if (FPClass & fcZero) {
|
|
// Create zero integer constant and check for equality with bitcasted to
|
|
// int float value
|
|
auto SetUpCMPToZero = [&](SPIRVValue *BitCastToInt,
|
|
bool IsPositive) -> SPIRVValue * {
|
|
APInt ZeroInt = APInt::getZero(BitSize);
|
|
if (IsPositive) {
|
|
auto *ZeroConst =
|
|
transValue(Constant::getIntegerValue(IntOpLLVMTy, ZeroInt), BB);
|
|
return BM->addCmpInst(OpIEqual, ResTy, BitCastToInt, ZeroConst, BB);
|
|
}
|
|
// Created 'negated' zero
|
|
ZeroInt.setSignBit();
|
|
auto *NegZeroConst =
|
|
transValue(Constant::getIntegerValue(IntOpLLVMTy, ZeroInt), BB);
|
|
return BM->addCmpInst(OpIEqual, ResTy, BitCastToInt, NegZeroConst, BB);
|
|
};
|
|
auto *BitCastToInt =
|
|
BM->addUnaryInst(OpBitcast, OpSPIRVTy, InputFloat, BB);
|
|
if (FPClass & fcPosZero && FPClass & fcNegZero) {
|
|
APInt ZeroInt = APInt::getZero(BitSize);
|
|
auto *ZeroConst =
|
|
transValue(Constant::getIntegerValue(IntOpLLVMTy, ZeroInt), BB);
|
|
APInt MaskToClearSignBit = APInt::getSignedMaxValue(BitSize);
|
|
auto *MaskToClearSignBitConst = transValue(
|
|
Constant::getIntegerValue(IntOpLLVMTy, MaskToClearSignBit), BB);
|
|
auto *BitwiseAndRes = BM->addBinaryInst(
|
|
OpBitwiseAnd, OpSPIRVTy, BitCastToInt, MaskToClearSignBitConst, BB);
|
|
auto *TestIsZero =
|
|
BM->addCmpInst(OpIEqual, ResTy, BitwiseAndRes, ZeroConst, BB);
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsZero));
|
|
} else if (FPClass & fcPosZero) {
|
|
auto *TestIsPosZero =
|
|
SetUpCMPToZero(BitCastToInt, true /*'positive' zero*/);
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsPosZero));
|
|
} else {
|
|
auto *TestIsNegZero =
|
|
SetUpCMPToZero(BitCastToInt, false /*'negated' zero*/);
|
|
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsNegZero));
|
|
}
|
|
}
|
|
if (ResultVec.size() == 1)
|
|
return ResultVec.back();
|
|
SPIRVValue *Result = ResultVec.front();
|
|
for (size_t I = 1; I != ResultVec.size(); ++I) {
|
|
// Create a sequence of LogicalOr instructions from ResultVec to get
|
|
// the overall test result
|
|
std::vector<SPIRVId> LogicOps = {Result->getId(), ResultVec[I]->getId()};
|
|
Result = BM->addInstTemplate(OpLogicalOr, LogicOps, BB, ResTy);
|
|
}
|
|
return Result;
|
|
}
|
|
default:
|
|
if (auto *BVar = transFPBuiltinIntrinsicInst(II, BB))
|
|
return BVar;
|
|
if (BM->isUnknownIntrinsicAllowed(II))
|
|
return BM->addCallInst(
|
|
transFunctionDecl(II->getCalledFunction()),
|
|
transArguments(II, BB,
|
|
SPIRVEntry::createUnique(OpFunctionCall).get()),
|
|
BB);
|
|
else
|
|
// Other LLVM intrinsics shouldn't get to SPIRV, because they
|
|
// can't be represented in SPIRV or aren't implemented yet.
|
|
BM->SPIRVCK(false, InvalidFunctionCall,
|
|
II->getCalledOperand()->getName().str());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
LLVMToSPIRVBase::FPBuiltinType
|
|
LLVMToSPIRVBase::getFPBuiltinType(IntrinsicInst *II, StringRef &OpName) {
|
|
StringRef Name = II->getCalledFunction()->getName();
|
|
if (!Name.consume_front("llvm.fpbuiltin."))
|
|
return FPBuiltinType::UNKNOWN;
|
|
OpName = Name.split('.').first;
|
|
FPBuiltinType Type =
|
|
StringSwitch<FPBuiltinType>(OpName)
|
|
.Cases("fadd", "fsub", "fmul", "fdiv", "frem",
|
|
FPBuiltinType::REGULAR_MATH)
|
|
.Cases("sin", "cos", "tan", FPBuiltinType::EXT_1OPS)
|
|
.Cases("sinh", "cosh", "tanh", FPBuiltinType::EXT_1OPS)
|
|
.Cases("asin", "acos", "atan", FPBuiltinType::EXT_1OPS)
|
|
.Cases("asinh", "acosh", "atanh", FPBuiltinType::EXT_1OPS)
|
|
.Cases("exp", "exp2", "exp10", "expm1", FPBuiltinType::EXT_1OPS)
|
|
.Cases("log", "log2", "log10", "log1p", FPBuiltinType::EXT_1OPS)
|
|
.Cases("sqrt", "rsqrt", "erf", "erfc", FPBuiltinType::EXT_1OPS)
|
|
.Cases("atan2", "pow", "hypot", "ldexp", FPBuiltinType::EXT_2OPS)
|
|
.Case("sincos", FPBuiltinType::EXT_3OPS)
|
|
.Default(FPBuiltinType::UNKNOWN);
|
|
return Type;
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transFPBuiltinIntrinsicInst(IntrinsicInst *II,
|
|
SPIRVBasicBlock *BB) {
|
|
StringRef OpName;
|
|
auto FPBuiltinTypeVal = getFPBuiltinType(II, OpName);
|
|
if (FPBuiltinTypeVal == FPBuiltinType::UNKNOWN)
|
|
return nullptr;
|
|
switch (FPBuiltinTypeVal) {
|
|
case FPBuiltinType::REGULAR_MATH: {
|
|
auto BinOp = StringSwitch<Op>(OpName)
|
|
.Case("fadd", OpFAdd)
|
|
.Case("fsub", OpFSub)
|
|
.Case("fmul", OpFMul)
|
|
.Case("fdiv", OpFDiv)
|
|
.Case("frem", OpFRem)
|
|
.Default(OpUndef);
|
|
return BM->addBinaryInst(BinOp, transType(II->getType()),
|
|
transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB), BB);
|
|
}
|
|
case FPBuiltinType::EXT_1OPS: {
|
|
if (!checkTypeForSPIRVExtendedInstLowering(II, BM))
|
|
break;
|
|
SPIRVType *STy = transType(II->getType());
|
|
std::vector<SPIRVValue *> Ops(1, transValue(II->getArgOperand(0), BB));
|
|
auto ExtOp = StringSwitch<SPIRVWord>(OpName)
|
|
.Case("sin", OpenCLLIB::Sin)
|
|
.Case("cos", OpenCLLIB::Cos)
|
|
.Case("tan", OpenCLLIB::Tan)
|
|
.Case("sinh", OpenCLLIB::Sinh)
|
|
.Case("cosh", OpenCLLIB::Cosh)
|
|
.Case("tanh", OpenCLLIB::Tanh)
|
|
.Case("asin", OpenCLLIB::Asin)
|
|
.Case("acos", OpenCLLIB::Acos)
|
|
.Case("atan", OpenCLLIB::Atan)
|
|
.Case("asinh", OpenCLLIB::Asinh)
|
|
.Case("acosh", OpenCLLIB::Acosh)
|
|
.Case("atanh", OpenCLLIB::Atanh)
|
|
.Case("exp", OpenCLLIB::Exp)
|
|
.Case("exp2", OpenCLLIB::Exp2)
|
|
.Case("exp10", OpenCLLIB::Exp10)
|
|
.Case("expm1", OpenCLLIB::Expm1)
|
|
.Case("log", OpenCLLIB::Log)
|
|
.Case("log2", OpenCLLIB::Log2)
|
|
.Case("log10", OpenCLLIB::Log10)
|
|
.Case("log1p", OpenCLLIB::Log1p)
|
|
.Case("sqrt", OpenCLLIB::Sqrt)
|
|
.Case("rsqrt", OpenCLLIB::Rsqrt)
|
|
.Case("erf", OpenCLLIB::Erf)
|
|
.Case("erfc", OpenCLLIB::Erfc)
|
|
.Default(SPIRVWORD_MAX);
|
|
assert(ExtOp != SPIRVWORD_MAX);
|
|
return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops,
|
|
BB);
|
|
}
|
|
case FPBuiltinType::EXT_2OPS: {
|
|
if (!checkTypeForSPIRVExtendedInstLowering(II, BM))
|
|
break;
|
|
SPIRVType *STy = transType(II->getType());
|
|
std::vector<SPIRVValue *> Ops{transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB)};
|
|
auto ExtOp = StringSwitch<SPIRVWord>(OpName)
|
|
.Case("atan2", OpenCLLIB::Atan2)
|
|
.Case("hypot", OpenCLLIB::Hypot)
|
|
.Case("pow", OpenCLLIB::Pow)
|
|
.Case("ldexp", OpenCLLIB::Ldexp)
|
|
.Default(SPIRVWORD_MAX);
|
|
assert(ExtOp != SPIRVWORD_MAX);
|
|
return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops,
|
|
BB);
|
|
}
|
|
case FPBuiltinType::EXT_3OPS: {
|
|
if (!checkTypeForSPIRVExtendedInstLowering(II, BM))
|
|
break;
|
|
SPIRVType *STy = transType(II->getType());
|
|
std::vector<SPIRVValue *> Ops{transValue(II->getArgOperand(0), BB),
|
|
transValue(II->getArgOperand(1), BB),
|
|
transValue(II->getArgOperand(2), BB)};
|
|
auto ExtOp = StringSwitch<SPIRVWord>(OpName)
|
|
.Case("sincos", OpenCLLIB::Sincos)
|
|
.Default(SPIRVWORD_MAX);
|
|
assert(ExtOp != SPIRVWORD_MAX);
|
|
return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops,
|
|
BB);
|
|
}
|
|
default:
|
|
return nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transFenceInst(FenceInst *FI,
|
|
SPIRVBasicBlock *BB) {
|
|
SPIRVWord MemorySemantics;
|
|
// Fence ordering may only be Acquire, Release, AcquireRelease, or
|
|
// SequentiallyConsistent
|
|
switch (FI->getOrdering()) {
|
|
case llvm::AtomicOrdering::Acquire:
|
|
MemorySemantics = MemorySemanticsAcquireMask;
|
|
break;
|
|
case llvm::AtomicOrdering::Release:
|
|
MemorySemantics = MemorySemanticsReleaseMask;
|
|
break;
|
|
case llvm::AtomicOrdering::AcquireRelease:
|
|
MemorySemantics = MemorySemanticsAcquireReleaseMask;
|
|
break;
|
|
case llvm::AtomicOrdering::SequentiallyConsistent:
|
|
MemorySemantics = MemorySemanticsSequentiallyConsistentMask;
|
|
break;
|
|
default:
|
|
assert(false && "Unexpected fence ordering");
|
|
MemorySemantics = SPIRVWORD_MAX;
|
|
break;
|
|
}
|
|
|
|
Module *M = FI->getParent()->getModule();
|
|
spv::Scope S = toSPIRVScope(FI->getContext(), FI->getSyncScopeID());
|
|
|
|
SPIRVValue *RetScope = transConstant(getUInt32(M, S));
|
|
SPIRVValue *Val = transConstant(getUInt32(M, MemorySemantics));
|
|
assert(RetScope && Val && "RetScope and Value are not constants");
|
|
return BM->addMemoryBarrierInst(static_cast<Scope>(RetScope->getId()),
|
|
Val->getId(), BB);
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transCallInst(CallInst *CI, SPIRVBasicBlock *BB) {
|
|
assert(CI);
|
|
Function *F = CI->getFunction();
|
|
if (isa<InlineAsm>(CI->getCalledOperand()) &&
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_inline_assembly)) {
|
|
// Inline asm is opaque, so we cannot reason about its FP contraction
|
|
// requirements.
|
|
SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName()
|
|
<< ": inline asm " << *CI << '\n');
|
|
joinFPContract(F, FPContract::DISABLED);
|
|
return transAsmCallINTEL(CI, BB);
|
|
}
|
|
|
|
if (CI->isIndirectCall()) {
|
|
// The function is not known in advance
|
|
SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName()
|
|
<< ": indirect call " << *CI << '\n');
|
|
joinFPContract(F, FPContract::DISABLED);
|
|
return transIndirectCallInst(CI, BB);
|
|
}
|
|
return transDirectCallInst(CI, BB);
|
|
}
|
|
|
|
// Helper function to process mini-float or int4 types for FP conversions.
|
|
// Processes type width, packing, and creates the appropriate SPIRV type.
|
|
// Returns the SPIRV type and outputs vector size information.
|
|
static SPIRVType *
|
|
processMiniFPOrInt4Type(Type *LLVMTy, FPEncodingWrap Encoding,
|
|
std::function<Type *(Type *)> GetScalarTy,
|
|
SPIRVModule *BM, unsigned &OutVecSize) {
|
|
Type *ScalarTy = GetScalarTy(LLVMTy);
|
|
unsigned TyWidth = cast<IntegerType>(ScalarTy)->getBitWidth();
|
|
unsigned VecSize = 0;
|
|
|
|
bool IsPacked = Encoding == FPEncodingWrap::E2M1 ||
|
|
Encoding == FPEncodingWrap::E2M1INTEL ||
|
|
Encoding == FPEncodingWrap::Integer;
|
|
if (IsPacked &&
|
|
(TyWidth == 8 || TyWidth == 16 || TyWidth == 32 || TyWidth == 64)) {
|
|
// Int4 or FP4 packed in an integer: each N-bit integer holds N/4 values.
|
|
assert(!isLLVMCooperativeMatrixType(LLVMTy) &&
|
|
"FP4 and Int4 matrices must not be packed");
|
|
unsigned OuterVecLen =
|
|
LLVMTy->isVectorTy()
|
|
? cast<VectorType>(LLVMTy)->getElementCount().getFixedValue()
|
|
: 1;
|
|
VecSize = (TyWidth / 4) * OuterVecLen;
|
|
TyWidth = 4;
|
|
} else {
|
|
if (LLVMTy->isVectorTy())
|
|
VecSize = cast<VectorType>(LLVMTy)->getElementCount().getFixedValue();
|
|
}
|
|
|
|
SPIRVType *Ty;
|
|
if (Encoding == FPEncodingWrap::Integer) {
|
|
Ty = BM->addIntegerType(TyWidth);
|
|
} else {
|
|
Ty = BM->addFloatType(TyWidth, Encoding);
|
|
}
|
|
|
|
OutVecSize = VecSize;
|
|
return Ty;
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transDirectCallInst(CallInst *CI,
|
|
SPIRVBasicBlock *BB) {
|
|
SPIRVExtInstSetKind ExtSetKind = SPIRVEIS_Count;
|
|
SPIRVWord ExtOp = SPIRVWORD_MAX;
|
|
llvm::Function *F = CI->getCalledFunction();
|
|
auto MangledName = F->getName();
|
|
StringRef DemangledName;
|
|
|
|
if (MangledName.starts_with(SPCV_CAST) || MangledName == SAMPLER_INIT)
|
|
return oclTransSpvcCastSampler(CI, BB);
|
|
|
|
if (oclIsBuiltin(MangledName, DemangledName) ||
|
|
isDecoratedSPIRVFunc(F, DemangledName)) {
|
|
if (auto *BV = transBuiltinToConstant(DemangledName, CI))
|
|
return BV;
|
|
if (auto *BV = transBuiltinToInst(DemangledName, CI, BB))
|
|
return BV;
|
|
}
|
|
|
|
if (isInternalSPIRVBuiltin(MangledName, DemangledName)) {
|
|
// Logic of the code below is described in
|
|
// docs/SPIRVMiniFloatsRepresentationInLLVM.rst
|
|
// A quick recap of the document:
|
|
// For FP4 and FP8 types (which don't have appropriate counterparts in LLVM)
|
|
// the translator expect to see external function calls with __builtin_spirv
|
|
// prefix, names of the functions encode the used in the conversion
|
|
// FP types and will be used by the translator for proper TypeFloat values
|
|
// generation. Since in LLVM IR FP8 types are represented by
|
|
// integer type, we have to insert bitcasts for dependent values that expect
|
|
// integer inputs or produces integer output that is used in the conversion.
|
|
if (SPIRV::FPConvertToEncodingMap::find(DemangledName)) {
|
|
FPConversionDesc FPDesc =
|
|
SPIRV::FPConvertToEncodingMap::map(DemangledName);
|
|
Value *Src = CI->getOperand(0);
|
|
Type *LLVMSrcTy = Src->getType();
|
|
Type *LLVMDstTy = CI->getType();
|
|
SPIRVType *SrcTy = nullptr;
|
|
SPIRVType *DstTy = nullptr;
|
|
|
|
auto GetScalarTy = [&](Type *Ty) -> Type * {
|
|
if (isLLVMCooperativeMatrixType(Ty))
|
|
return cast<TargetExtType>(Ty)->getTypeParameter(0);
|
|
return Ty->getScalarType();
|
|
};
|
|
|
|
// FP types representable in LLVM IR, no need for special handling
|
|
// Also, int4 matrices remain the same.
|
|
if (FPDesc.SrcEncoding == FPEncodingWrap::IEEE754 ||
|
|
FPDesc.SrcEncoding == FPEncodingWrap::BF16 ||
|
|
(FPDesc.SrcEncoding == FPEncodingWrap::Integer &&
|
|
isLLVMCooperativeMatrixType(LLVMDstTy)))
|
|
SrcTy = transType(LLVMSrcTy);
|
|
if (FPDesc.DstEncoding == FPEncodingWrap::IEEE754 ||
|
|
FPDesc.DstEncoding == FPEncodingWrap::BF16 ||
|
|
(FPDesc.DstEncoding == FPEncodingWrap::Integer &&
|
|
(isLLVMCooperativeMatrixType(LLVMDstTy) ||
|
|
FPDesc.SrcEncoding == FPEncodingWrap::Integer)))
|
|
DstTy = transType(LLVMDstTy);
|
|
|
|
SPIRVValue *SrcOp = transValue(Src, BB);
|
|
|
|
if (!SrcTy) {
|
|
// Src type is 'mini' float or int4.
|
|
unsigned SrcVecSize = 0;
|
|
SrcTy = processMiniFPOrInt4Type(LLVMSrcTy, FPDesc.SrcEncoding,
|
|
GetScalarTy, BM, SrcVecSize);
|
|
|
|
if (SrcVecSize > 0)
|
|
SrcTy = BM->addVectorType(SrcTy, SrcVecSize);
|
|
|
|
if (isLLVMCooperativeMatrixType(LLVMSrcTy)) {
|
|
// Create FP4/FP8 matrix with a new type and insert a bitcast.
|
|
SrcTy = BM->addCooperativeMatrixKHRType(
|
|
SrcTy,
|
|
static_cast<SPIRVTypeCooperativeMatrixKHR *>(transType(LLVMSrcTy))
|
|
->getArgs());
|
|
SrcOp = BM->addUnaryInst(OpBitcast, SrcTy, SrcOp, BB);
|
|
} else if (FPDesc.SrcEncoding != FPEncodingWrap::Integer ||
|
|
SrcVecSize > 0) {
|
|
// Create bitcast for FP4, FP8 and packed Int4 (including cases where
|
|
// both the LLVM and SPIR-V types are vectors but with different
|
|
// sizes, e.g. <2 x i8> repacked as <4 x Int4>).
|
|
SrcOp = BM->addUnaryInst(OpBitcast, SrcTy, SrcOp, BB);
|
|
}
|
|
}
|
|
unsigned DstVecSize = 0;
|
|
if (!DstTy) {
|
|
// Dst type is 'mini' float or int4.
|
|
DstTy = processMiniFPOrInt4Type(LLVMDstTy, FPDesc.DstEncoding,
|
|
GetScalarTy, BM, DstVecSize);
|
|
|
|
if (isLLVMCooperativeMatrixType(LLVMDstTy))
|
|
// Create FP8 matrix with a new type.
|
|
DstTy = BM->addCooperativeMatrixKHRType(
|
|
DstTy,
|
|
static_cast<SPIRVTypeCooperativeMatrixKHR *>(transType(LLVMDstTy))
|
|
->getArgs());
|
|
|
|
if (DstVecSize > 0)
|
|
DstTy = BM->addVectorType(DstTy, DstVecSize);
|
|
}
|
|
|
|
std::vector<SPIRVValue *> Ops = {SrcOp};
|
|
const auto OC = static_cast<Op>(FPDesc.ConvOpCode);
|
|
|
|
// Translate operands for stochastic roundings.
|
|
for (size_t I = 1; I != CI->arg_size(); ++I)
|
|
Ops.push_back(transValue(CI->getOperand(I), BB));
|
|
|
|
SPIRVValue *Conv = BM->addInstTemplate(OC, BM->getIds(Ops), BB, DstTy);
|
|
|
|
if (FPDesc.Saturate)
|
|
Conv->addDecorate(new SPIRVDecorate(
|
|
DecorationSaturatedToLargestFloat8NormalConversionEXT, Conv));
|
|
|
|
// Representable in LLVM FP types: bitcast is not needed.
|
|
if (FPDesc.DstEncoding == FPEncodingWrap::IEEE754 ||
|
|
FPDesc.DstEncoding == FPEncodingWrap::BF16)
|
|
return Conv;
|
|
// Originally not-packed integer (no repacking, or cooperative matrix).
|
|
if (FPDesc.DstEncoding == FPEncodingWrap::Integer &&
|
|
(DstVecSize == 0 || isLLVMCooperativeMatrixType(LLVMDstTy)))
|
|
return Conv;
|
|
// Need to adjust types: create bitcast for FP8 and packed Int4.
|
|
SPIRVValue *BitCast =
|
|
BM->addUnaryInst(OpBitcast, transType(CI->getType()), Conv, BB);
|
|
return BitCast;
|
|
}
|
|
}
|
|
|
|
SmallVector<std::string, 2> Dec;
|
|
if (isBuiltinTransToExtInst(CI->getCalledFunction(), &ExtSetKind, &ExtOp,
|
|
&Dec)) {
|
|
if (const auto *FirstArg = F->getArg(0)) {
|
|
const auto *Type = FirstArg->getType();
|
|
if (Type->isBFloatTy())
|
|
BM->addCapability(internal::CapabilityBFloat16ArithmeticINTEL);
|
|
}
|
|
if (DemangledName.find("__spirv_ocl_printf") != StringRef::npos) {
|
|
auto *FormatStrPtr = cast<PointerType>(CI->getArgOperand(0)->getType());
|
|
if (FormatStrPtr->getAddressSpace() !=
|
|
SPIR::TypeAttributeEnum::ATTR_CONST) {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_EXT_relaxed_printf_string_address_space)) {
|
|
std::string ErrorStr =
|
|
"Either SPV_EXT_relaxed_printf_string_address_space extension "
|
|
"should be allowed to translate this module, because this LLVM "
|
|
"module contains the printf function with format string, whose "
|
|
"address space is not equal to 2 (constant).";
|
|
getErrorLog().checkError(false, SPIRVEC_RequiresExtension, CI,
|
|
ErrorStr);
|
|
}
|
|
BM->addExtension(
|
|
ExtensionID::SPV_EXT_relaxed_printf_string_address_space);
|
|
}
|
|
} else if (DemangledName.find("__spirv_ocl_prefetch") != StringRef::npos) {
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers)) {
|
|
return BM->addUntypedPrefetchKHRInst(
|
|
transScavengedType(CI),
|
|
BM->getIds(transValue(getArguments(CI), BB)), BB);
|
|
}
|
|
} else if (DemangledName == "__spirv_ocl_fma") {
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_fma))
|
|
return BM->addInstTemplate(OpFmaKHR,
|
|
BM->getIds(transValue(getArguments(CI), BB)),
|
|
BB, transScavengedType(CI));
|
|
}
|
|
|
|
return addDecorations(
|
|
BM->addExtInst(
|
|
transScavengedType(CI), BM->getExtInstSetId(ExtSetKind), ExtOp,
|
|
transArguments(CI, BB,
|
|
SPIRVEntry::createUnique(ExtSetKind, ExtOp).get()),
|
|
BB),
|
|
Dec);
|
|
}
|
|
|
|
Function *Callee = CI->getCalledFunction();
|
|
if (Callee->isDeclaration()) {
|
|
SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName().str()
|
|
<< ": call to an undefined function " << *CI << '\n');
|
|
joinFPContract(CI->getFunction(), FPContract::DISABLED);
|
|
} else {
|
|
FPContract CalleeFPC = getFPContract(Callee);
|
|
joinFPContract(CI->getFunction(), CalleeFPC);
|
|
if (CalleeFPC == FPContract::DISABLED) {
|
|
SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName().str()
|
|
<< ": call to a function with disabled contraction: "
|
|
<< *CI << '\n');
|
|
}
|
|
}
|
|
|
|
return BM->addCallInst(
|
|
transFunctionDecl(Callee),
|
|
transArguments(CI, BB, SPIRVEntry::createUnique(OpFunctionCall).get()),
|
|
BB);
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transIndirectCallInst(CallInst *CI,
|
|
SPIRVBasicBlock *BB) {
|
|
if (BM->getErrorLog().checkError(
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_function_pointers),
|
|
SPIRVEC_FunctionPointers, CI)) {
|
|
return BM->addIndirectCallInst(
|
|
transValue(CI->getCalledOperand(), BB), transScavengedType(CI),
|
|
transArguments(CI, BB, SPIRVEntry::createUnique(OpFunctionCall).get()),
|
|
BB);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transAsmINTEL(InlineAsm *IA) {
|
|
assert(IA);
|
|
|
|
// TODO: intention here is to provide information about actual target
|
|
// but in fact spir-64 is substituted as triple when translator works
|
|
// eventually we need to fix it (not urgent)
|
|
StringRef TripleStr(M->getTargetTriple().str());
|
|
auto *AsmTarget = static_cast<SPIRVAsmTargetINTEL *>(
|
|
BM->getOrAddAsmTargetINTEL(TripleStr.str()));
|
|
auto *SIA = BM->addAsmINTEL(
|
|
static_cast<SPIRVTypeFunction *>(transType(IA->getFunctionType())),
|
|
AsmTarget, IA->getAsmString().str(), IA->getConstraintString().str());
|
|
if (IA->hasSideEffects())
|
|
SIA->addDecorate(DecorationSideEffectsINTEL);
|
|
return SIA;
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transAsmCallINTEL(CallInst *CI,
|
|
SPIRVBasicBlock *BB) {
|
|
assert(CI);
|
|
auto *IA = cast<InlineAsm>(CI->getCalledOperand());
|
|
return BM->addAsmCallINTELInst(
|
|
static_cast<SPIRVAsmINTEL *>(transValue(IA, BB, false)),
|
|
transArguments(CI, BB, SPIRVEntry::createUnique(OpAsmCallINTEL).get()),
|
|
BB);
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::transAddressingMode() {
|
|
Triple TargetTriple(M->getTargetTriple());
|
|
|
|
if (TargetTriple.isArch32Bit())
|
|
BM->setAddressingModel(AddressingModelPhysical32);
|
|
else
|
|
BM->setAddressingModel(AddressingModelPhysical64);
|
|
// Physical addressing model requires Addresses capability
|
|
BM->addCapability(CapabilityAddresses);
|
|
return true;
|
|
}
|
|
std::vector<SPIRVValue *>
|
|
LLVMToSPIRVBase::transValue(const std::vector<Value *> &Args,
|
|
SPIRVBasicBlock *BB) {
|
|
std::vector<SPIRVValue *> BArgs;
|
|
for (auto &I : Args)
|
|
BArgs.push_back(transValue(I, BB));
|
|
return BArgs;
|
|
}
|
|
|
|
std::vector<SPIRVWord>
|
|
LLVMToSPIRVBase::transValue(const std::vector<Value *> &Args,
|
|
SPIRVBasicBlock *BB, SPIRVEntry *Entry) {
|
|
std::vector<SPIRVWord> Operands;
|
|
for (size_t I = 0, E = Args.size(); I != E; ++I) {
|
|
Operands.push_back(Entry->isOperandLiteral(I)
|
|
? cast<ConstantInt>(Args[I])->getZExtValue()
|
|
: transValue(Args[I], BB)->getId());
|
|
}
|
|
return Operands;
|
|
}
|
|
|
|
std::vector<SPIRVWord> LLVMToSPIRVBase::transArguments(CallInst *CI,
|
|
SPIRVBasicBlock *BB,
|
|
SPIRVEntry *Entry) {
|
|
return transValue(getArguments(CI), BB, Entry);
|
|
}
|
|
|
|
SPIRVWord LLVMToSPIRVBase::transFunctionControlMask(Function *F) {
|
|
SPIRVWord FCM = 0;
|
|
SPIRSPIRVFuncCtlMaskMap::foreach (
|
|
[&](Attribute::AttrKind Attr, SPIRVFunctionControlMaskKind Mask) {
|
|
if (F->hasFnAttribute(Attr)) {
|
|
if (Attr == Attribute::OptimizeNone) {
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_EXT_optnone)) {
|
|
BM->addExtension(ExtensionID::SPV_EXT_optnone);
|
|
BM->addCapability(CapabilityOptNoneEXT);
|
|
} else if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_optnone)) {
|
|
BM->addExtension(ExtensionID::SPV_INTEL_optnone);
|
|
BM->addCapability(CapabilityOptNoneINTEL);
|
|
} else
|
|
return;
|
|
}
|
|
FCM |= Mask;
|
|
}
|
|
});
|
|
return FCM;
|
|
}
|
|
|
|
void LLVMToSPIRVBase::transGlobalAnnotation(GlobalVariable *V) {
|
|
SPIRVDBG(dbgs() << "[transGlobalAnnotation] " << *V << '\n');
|
|
|
|
// @llvm.global.annotations is an array that contains structs with 4 fields.
|
|
// Get the array of structs with metadata
|
|
// TODO: actually, now it contains 5 fields, the fifth by default is nullptr
|
|
// or undef, but it can be defined to include variadic arguments of
|
|
// clang::annotation attribute. Need to refactor this function to turn on this
|
|
// translation
|
|
ConstantArray *CA = cast<ConstantArray>(V->getOperand(0));
|
|
for (Value *Op : CA->operands()) {
|
|
ConstantStruct *CS = cast<ConstantStruct>(Op);
|
|
// The first field of the struct contains a pointer to annotated variable
|
|
Value *AnnotatedVar = CS->getOperand(0)->stripPointerCasts();
|
|
SPIRVValue *SV = transValue(AnnotatedVar, nullptr);
|
|
|
|
// The second field contains a pointer to a global annotation string
|
|
GlobalVariable *GV =
|
|
cast<GlobalVariable>(CS->getOperand(1)->stripPointerCasts());
|
|
|
|
StringRef AnnotationString;
|
|
if (!getConstantStringInfo(GV, AnnotationString)) {
|
|
assert(false && "Annotation string missing");
|
|
return;
|
|
}
|
|
DecorationsInfoVec Decorations =
|
|
tryParseAnnotationString(BM, AnnotationString).MemoryAttributesVec;
|
|
|
|
// If we didn't find any annotation decorations, let's add the whole
|
|
// annotation string as UserSemantic Decoration
|
|
if (Decorations.empty()) {
|
|
SV->addDecorate(
|
|
new SPIRVDecorateUserSemanticAttr(SV, AnnotationString.str()));
|
|
} else {
|
|
addAnnotationDecorations(SV, Decorations);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLVMToSPIRVBase::transGlobalIOPipeStorage(GlobalVariable *V, MDNode *IO) {
|
|
SPIRVDBG(dbgs() << "[transGlobalIOPipeStorage] " << *V << '\n');
|
|
SPIRVValue *SV = transValue(V, nullptr);
|
|
assert(SV && "Failed to process OCL PipeStorage object");
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_io_pipes)) {
|
|
unsigned ID = getMDOperandAsInt(IO, 0);
|
|
SV->addDecorate(DecorationIOPipeStorageINTEL, ID);
|
|
}
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::transGlobalVariables() {
|
|
for (auto I = M->global_begin(), E = M->global_end(); I != E; ++I) {
|
|
if ((*I).getName() == "llvm.global.annotations")
|
|
transGlobalAnnotation(&(*I));
|
|
else if ([I]() -> bool {
|
|
// Check if the GV is used only in var/ptr instructions. If yes -
|
|
// skip processing of this since it's only an annotation GV.
|
|
if (I->user_empty())
|
|
return false;
|
|
for (auto *U : I->users()) {
|
|
Value *V = U;
|
|
while (isa<BitCastInst>(V) || isa<AddrSpaceCastInst>(V))
|
|
V = cast<CastInst>(V)->getOperand(0);
|
|
auto *GEP = dyn_cast_or_null<GetElementPtrInst>(V);
|
|
if (!GEP)
|
|
return false;
|
|
for (auto *GEPU : GEP->users()) {
|
|
auto *II = dyn_cast<IntrinsicInst>(GEPU);
|
|
if (!II)
|
|
return false;
|
|
switch (II->getIntrinsicID()) {
|
|
case Intrinsic::var_annotation:
|
|
case Intrinsic::ptr_annotation:
|
|
continue;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}())
|
|
continue;
|
|
else if ((I->getName() == "llvm.global_ctors" ||
|
|
I->getName() == "llvm.global_dtors") &&
|
|
!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_function_pointers)) {
|
|
// Function pointers are required to represent structor lists; do not
|
|
// translate the variable if function pointers are not available.
|
|
continue;
|
|
} else if (MDNode *IO = ((*I).getMetadata("io_pipe_id")))
|
|
transGlobalIOPipeStorage(&(*I), IO);
|
|
else if (!transValue(&(*I), nullptr))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::isAnyFunctionReachableFromFunction(
|
|
const Function *FS,
|
|
const std::unordered_set<const Function *> Funcs) const {
|
|
std::unordered_set<const Function *> Done;
|
|
std::unordered_set<const Function *> ToDo;
|
|
ToDo.insert(FS);
|
|
|
|
while (!ToDo.empty()) {
|
|
auto It = ToDo.begin();
|
|
const Function *F = *It;
|
|
|
|
if (Funcs.find(F) != Funcs.end())
|
|
return true;
|
|
|
|
ToDo.erase(It);
|
|
Done.insert(F);
|
|
|
|
const CallGraphNode *FN = (*CG)[F];
|
|
for (unsigned I = 0; I < FN->size(); ++I) {
|
|
const CallGraphNode *NN = (*FN)[I];
|
|
const Function *NNF = NN->getFunction();
|
|
if (!NNF)
|
|
continue;
|
|
if (Done.find(NNF) == Done.end()) {
|
|
ToDo.insert(NNF);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::vector<SPIRVId>
|
|
LLVMToSPIRVBase::collectEntryPointInterfaces(SPIRVFunction *SF, Function *F) {
|
|
std::vector<SPIRVId> Interface;
|
|
for (auto &GV : M->globals()) {
|
|
const auto AS = GV.getAddressSpace();
|
|
SPIRVModule *BM = SF->getModule();
|
|
if (!BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4))
|
|
if (AS != SPIRAS_Input && AS != SPIRAS_Output)
|
|
continue;
|
|
|
|
std::unordered_set<const Function *> Funcs;
|
|
|
|
for (const auto &U : GV.uses()) {
|
|
const Instruction *Inst = dyn_cast<Instruction>(U.getUser());
|
|
if (!Inst)
|
|
continue;
|
|
Funcs.insert(Inst->getFunction());
|
|
}
|
|
|
|
if (isAnyFunctionReachableFromFunction(F, Funcs)) {
|
|
SPIRVWord ModuleVersion = static_cast<SPIRVWord>(BM->getSPIRVVersion());
|
|
if (AS != SPIRAS_Input && AS != SPIRAS_Output &&
|
|
ModuleVersion < static_cast<SPIRVWord>(VersionNumber::SPIRV_1_4))
|
|
BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_4);
|
|
Interface.push_back(ValueMap[&GV]->getId());
|
|
}
|
|
}
|
|
return Interface;
|
|
}
|
|
|
|
void LLVMToSPIRVBase::mutateFuncArgType(
|
|
const std::unordered_map<unsigned, Type *> &ChangedType, Function *F) {
|
|
for (auto &I : ChangedType) {
|
|
for (auto UI = F->user_begin(), UE = F->user_end(); UI != UE; ++UI) {
|
|
auto *Call = dyn_cast<CallInst>(*UI);
|
|
if (!Call)
|
|
continue;
|
|
auto *Arg = Call->getArgOperand(I.first);
|
|
auto *OrigTy = Arg->getType();
|
|
if (OrigTy == I.second)
|
|
continue;
|
|
SPIRVDBG(dbgs() << "[mutate arg type] " << *Call << ", " << *Arg << '\n');
|
|
auto CastF = M->getOrInsertFunction(SPCV_CAST, I.second, OrigTy);
|
|
std::vector<Value *> Args;
|
|
Args.push_back(Arg);
|
|
auto *Cast = CallInst::Create(CastF, Args, "", Call->getIterator());
|
|
Call->replaceUsesOfWith(Arg, Cast);
|
|
SPIRVDBG(dbgs() << "[mutate arg type] -> " << *Cast << '\n');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Propagate contraction requirement of F up the call graph.
|
|
void LLVMToSPIRVBase::fpContractUpdateRecursive(Function *F, FPContract FPC) {
|
|
std::queue<User *> Users;
|
|
for (User *FU : F->users()) {
|
|
Users.push(FU);
|
|
}
|
|
|
|
bool EnableLogger = FPC == FPContract::DISABLED && !Users.empty();
|
|
if (EnableLogger) {
|
|
SPIRVDBG(dbgs() << "[fp-contract] disabled for users of " << F->getName()
|
|
<< '\n');
|
|
}
|
|
|
|
while (!Users.empty()) {
|
|
User *U = Users.front();
|
|
Users.pop();
|
|
|
|
if (EnableLogger) {
|
|
SPIRVDBG(dbgs() << "[fp-contract] user: " << *U << '\n');
|
|
}
|
|
|
|
// Move from an Instruction to its Function
|
|
if (Instruction *I = dyn_cast<Instruction>(U)) {
|
|
Users.push(I->getFunction());
|
|
continue;
|
|
}
|
|
|
|
if (Function *F = dyn_cast<Function>(U)) {
|
|
if (!joinFPContract(F, FPC)) {
|
|
// FP contract was not updated - no need to propagate
|
|
// This also terminates a recursion (if any).
|
|
if (EnableLogger) {
|
|
SPIRVDBG(dbgs() << "[fp-contract] already disabled " << F->getName()
|
|
<< '\n');
|
|
}
|
|
continue;
|
|
}
|
|
if (EnableLogger) {
|
|
SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName()
|
|
<< '\n');
|
|
}
|
|
for (User *FU : F->users()) {
|
|
Users.push(FU);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Unwrap a constant until we reach an Instruction.
|
|
// This is checked after the Function, because a Function is also a
|
|
// Constant.
|
|
if (Constant *C = dyn_cast<Constant>(U)) {
|
|
for (User *CU : C->users()) {
|
|
Users.push(CU);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
llvm_unreachable("Unexpected use.");
|
|
}
|
|
}
|
|
|
|
/// Returns a range that traverses \p F ensuring that dominator blocks are
|
|
/// visited before the blocks they dominate.
|
|
///
|
|
/// Compared to llvm::ReversePostOrderTraversal which also visits dominators
|
|
/// before dominated blocks, this traversal aims to be more stable and will keep
|
|
/// basic blocks in their original order as much as possible, only reordering
|
|
/// them to visit dominators ahead of their dominated blocks when needed. \p DT
|
|
/// is not copied by this function and needs to outlive any iterators created
|
|
/// from this range.
|
|
static auto stablePreDominatorTraversal(Function &F, const DominatorTree &DT) {
|
|
|
|
// A local iterator type for traversing a function in the desired order.
|
|
class StablePreDominatorIterator
|
|
: public iterator_facade_base<StablePreDominatorIterator,
|
|
std::forward_iterator_tag, BasicBlock> {
|
|
|
|
// The passed DominatorTree; may be unset for end iterators.
|
|
const DominatorTree *DT = nullptr;
|
|
|
|
// The set of basic blocks already visited in this traversal.
|
|
SmallPtrSet<const BasicBlock *, 4> VisitedBBs;
|
|
|
|
// The next basic block in original function order, or nullptr if the
|
|
// traversal is over.
|
|
BasicBlock *NextBB = nullptr;
|
|
|
|
// The current basic block in the traversal, or nullptr for end iterators.
|
|
BasicBlock *CurBB = nullptr;
|
|
|
|
// Returns the most immediate dominator of \p BB which does not have an
|
|
// unvisited dominator and so can be visited in this traversal.
|
|
BasicBlock *visitableDominator(BasicBlock *BB) const {
|
|
|
|
// Find BB's dominator; if there is none, BB can be visited immediately.
|
|
const auto *const BBNode = DT->getNode(BB);
|
|
if (!BBNode)
|
|
return BB;
|
|
const auto *const DomNode = BBNode->getIDom();
|
|
if (!DomNode)
|
|
return BB;
|
|
BasicBlock *const Dominator = DomNode->getBlock();
|
|
|
|
// If the dominator's been visited, BB can now be visited.
|
|
if (VisitedBBs.contains(Dominator))
|
|
return BB;
|
|
|
|
// Otherwise, find the dominator's visitable dominator instead.
|
|
return visitableDominator(Dominator);
|
|
}
|
|
|
|
// Advances the iterator and returns the next basic block to be visited in
|
|
// the traversal.
|
|
BasicBlock *next() {
|
|
|
|
// If NextBB is nullptr, the end of the traversal has been reached.
|
|
if (!NextBB)
|
|
return nullptr;
|
|
|
|
// Check if NextBB has already been visited; if so, advance past it.
|
|
if (VisitedBBs.contains(NextBB)) {
|
|
NextBB = NextBB->getNextNode();
|
|
return next();
|
|
}
|
|
|
|
// If NextBB is unvisited, visit its next visitable dominator.
|
|
BasicBlock *const ToVisit = visitableDominator(NextBB);
|
|
VisitedBBs.insert(ToVisit);
|
|
return ToVisit;
|
|
}
|
|
|
|
public:
|
|
// Constructs an end iterator.
|
|
StablePreDominatorIterator() {}
|
|
|
|
// Constructs a begin iterator at the start of \p F.
|
|
StablePreDominatorIterator(Function &F, const DominatorTree &DT)
|
|
: DT(&DT), NextBB(&F.getEntryBlock()) {
|
|
++*this;
|
|
}
|
|
|
|
// Methods required by iterator_facade_base.
|
|
bool operator==(const StablePreDominatorIterator &Other) const {
|
|
return CurBB == Other.CurBB;
|
|
}
|
|
BasicBlock &operator*() const { return *CurBB; }
|
|
StablePreDominatorIterator &operator++() {
|
|
CurBB = next();
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
return make_range(StablePreDominatorIterator(F, DT),
|
|
StablePreDominatorIterator());
|
|
}
|
|
|
|
void LLVMToSPIRVBase::transFunction(Function *I) {
|
|
SPIRVFunction *BF = transFunctionDecl(I);
|
|
// Creating all basic blocks before creating any instruction. SPIR-V requires
|
|
// that blocks appear after their dominators, so stablePreDominatorTraversal
|
|
// is used to ensure blocks are written in the right order.
|
|
const DominatorTree DT(*I);
|
|
for (BasicBlock &FI : stablePreDominatorTraversal(*I, DT)) {
|
|
FI.convertFromNewDbgValues();
|
|
transValue(&FI, nullptr);
|
|
}
|
|
for (auto &FI : *I) {
|
|
SPIRVBasicBlock *BB =
|
|
static_cast<SPIRVBasicBlock *>(transValue(&FI, nullptr));
|
|
for (auto &BI : FI) {
|
|
transValue(&BI, BB, false);
|
|
}
|
|
}
|
|
// Enable FP contraction unless proven otherwise
|
|
joinFPContract(I, FPContract::ENABLED);
|
|
fpContractUpdateRecursive(I, getFPContract(I));
|
|
|
|
if (isKernel(I)) {
|
|
auto Interface = collectEntryPointInterfaces(BF, I);
|
|
BM->addEntryPoint(ExecutionModelKernel, BF->getId(), BF->getName(),
|
|
Interface);
|
|
}
|
|
}
|
|
|
|
bool isEmptyLLVMModule(Module *M) {
|
|
return M->empty() && // No functions
|
|
M->global_empty(); // No global variables
|
|
}
|
|
|
|
// A module is VectorCompute when any function or global variable carries
|
|
// VectorCompute metadata.
|
|
static bool hasVectorComputeMetadata(Module *M) {
|
|
return any_of(*M,
|
|
[](const Function &F) {
|
|
return F.hasFnAttribute(kVCMetadata::VCFunction);
|
|
}) ||
|
|
any_of(M->globals(), [](const GlobalVariable &GV) {
|
|
return GV.hasAttribute(kVCMetadata::VCGlobalVariable);
|
|
});
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::translate() {
|
|
BM->setGeneratorVer(KTranslatorVer);
|
|
|
|
if (isEmptyLLVMModule(M))
|
|
BM->addCapability(CapabilityLinkage);
|
|
|
|
// Check before type translation so that SPIRVTypeVector can choose the
|
|
// matching capability.
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute))
|
|
BM->setVectorCompute(hasVectorComputeMetadata(M));
|
|
|
|
if (!lowerBuiltinCallsToVariables(M))
|
|
return false;
|
|
|
|
// Use the type scavenger to recover pointer element types.
|
|
Scavenger = std::make_unique<SPIRVTypeScavenger>(*M);
|
|
|
|
if (!transSourceLanguage())
|
|
return false;
|
|
if (!transExtension())
|
|
return false;
|
|
if (!transBuiltinSet())
|
|
return false;
|
|
if (!transAddressingMode())
|
|
return false;
|
|
if (!transGlobalVariables())
|
|
return false;
|
|
|
|
for (auto &F : *M) {
|
|
auto *FT = F.getFunctionType();
|
|
std::unordered_map<unsigned, Type *> ChangedType;
|
|
oclGetMutatedArgumentTypesByBuiltin(FT, ChangedType, &F);
|
|
mutateFuncArgType(ChangedType, &F);
|
|
}
|
|
|
|
// SPIR-V logical layout requires all function declarations go before
|
|
// function definitions.
|
|
std::vector<Function *> Decls, Defs;
|
|
for (auto &F : *M) {
|
|
if (isBuiltinTransToInst(&F) || isBuiltinTransToExtInst(&F) ||
|
|
F.getName().starts_with(SPCV_CAST) ||
|
|
F.getName().starts_with(LLVM_MEMCPY) ||
|
|
F.getName().starts_with(SAMPLER_INIT))
|
|
continue;
|
|
if (F.isDeclaration())
|
|
Decls.push_back(&F);
|
|
else
|
|
Defs.push_back(&F);
|
|
}
|
|
for (auto *I : Decls)
|
|
transFunctionDecl(I);
|
|
for (auto *I : Defs)
|
|
transFunction(I);
|
|
|
|
if (!transMetadata())
|
|
return false;
|
|
if (!transExecutionMode())
|
|
return false;
|
|
|
|
BM->resolveUnknownStructFields();
|
|
DbgTran->transDebugMetadata();
|
|
return true;
|
|
}
|
|
|
|
llvm::IntegerType *LLVMToSPIRVBase::getSizetType(unsigned AS) {
|
|
return IntegerType::getIntNTy(M->getContext(),
|
|
M->getDataLayout().getPointerSizeInBits(AS));
|
|
}
|
|
|
|
void LLVMToSPIRVBase::oclGetMutatedArgumentTypesByBuiltin(
|
|
llvm::FunctionType *FT, std::unordered_map<unsigned, Type *> &ChangedType,
|
|
Function *F) {
|
|
StringRef Demangled;
|
|
if (!oclIsBuiltin(F->getName(), Demangled))
|
|
return;
|
|
// Note: kSPIRVName::ConvertHandleToSampledImageINTEL contains
|
|
// kSPIRVName::SampledImage as a substring, but we still want to return in
|
|
// this case.
|
|
if (Demangled.find(kSPIRVName::SampledImage) == std::string::npos ||
|
|
Demangled.find(kSPIRVName::ConvertHandleToSampledImageINTEL) !=
|
|
std::string::npos)
|
|
return;
|
|
if (FT->getParamType(1)->isIntegerTy())
|
|
ChangedType[1] = getSPIRVType(OpTypeSampler, true);
|
|
}
|
|
|
|
SPIRVValue *LLVMToSPIRVBase::transBuiltinToConstant(StringRef DemangledName,
|
|
CallInst *CI) {
|
|
Op OC = getSPIRVFuncOC(DemangledName);
|
|
if (!isSpecConstantOpCode(OC))
|
|
return nullptr;
|
|
if (OC == spv::OpSpecConstantComposite) {
|
|
return BM->addSpecConstantComposite(transType(CI->getType()),
|
|
transValue(getArguments(CI), nullptr));
|
|
}
|
|
Value *V = CI->getArgOperand(1);
|
|
Type *Ty = CI->getType();
|
|
assert(((Ty == V->getType()) ||
|
|
// If bool is stored into memory, then clang will emit it as i8,
|
|
// however for other usages of bool (like return type of a function),
|
|
// it is emitted as i1.
|
|
// Therefore, situation when we encounter
|
|
// i1 _Z20__spirv_SpecConstant(i32, i8) is valid
|
|
(Ty->isIntegerTy(1) && V->getType()->isIntegerTy(8))) &&
|
|
"Type mismatch!");
|
|
uint64_t Val = 0;
|
|
if (Ty->isIntegerTy())
|
|
Val = cast<ConstantInt>(V)->getZExtValue();
|
|
else if (Ty->isFloatingPointTy())
|
|
Val = cast<ConstantFP>(V)->getValueAPF().bitcastToAPInt().getZExtValue();
|
|
else
|
|
return nullptr;
|
|
SPIRVValue *SC = BM->addSpecConstant(transType(Ty), Val);
|
|
return SC;
|
|
}
|
|
|
|
SPIRVInstruction *LLVMToSPIRVBase::transBuiltinToInst(StringRef DemangledName,
|
|
CallInst *CI,
|
|
SPIRVBasicBlock *BB) {
|
|
SmallVector<std::string, 2> Dec;
|
|
auto OC = getSPIRVFuncOC(DemangledName, &Dec);
|
|
|
|
if (OC == OpNop)
|
|
return nullptr;
|
|
|
|
if (OpReadPipeBlockingINTEL <= OC && OC <= OpWritePipeBlockingINTEL &&
|
|
!BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_blocking_pipes))
|
|
return nullptr;
|
|
|
|
if (OpFixedSqrtINTEL <= OC && OC <= OpFixedExpINTEL)
|
|
BM->getErrorLog().checkError(
|
|
BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_arbitrary_precision_fixed_point),
|
|
SPIRVEC_InvalidInstruction,
|
|
CI->getCalledOperand()->getName().str() +
|
|
"\nFixed point instructions can't be translated correctly without "
|
|
"enabled SPV_INTEL_arbitrary_precision_fixed_point extension!\n");
|
|
|
|
if ((OpArbitraryFloatSinCosPiINTEL <= OC &&
|
|
OC <= OpArbitraryFloatCastToIntINTEL) ||
|
|
(OpArbitraryFloatAddINTEL <= OC && OC <= OpArbitraryFloatPowNINTEL))
|
|
BM->getErrorLog().checkError(
|
|
BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_arbitrary_precision_floating_point),
|
|
SPIRVEC_InvalidInstruction,
|
|
CI->getCalledOperand()->getName().str() +
|
|
"\nFloating point instructions can't be translated correctly "
|
|
"without enabled SPV_INTEL_arbitrary_precision_floating_point "
|
|
"extension!\n");
|
|
|
|
auto *Inst = transBuiltinToInstWithoutDecoration(OC, CI, BB);
|
|
addDecorations(Inst, Dec);
|
|
return Inst;
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::transExecutionMode() {
|
|
if (auto NMD = SPIRVMDWalker(*M).getNamedMD(kSPIRVMD::ExecutionMode)) {
|
|
while (!NMD.atEnd()) {
|
|
unsigned EMode = ~0U;
|
|
Function *F = nullptr;
|
|
auto N = NMD.nextOp(); /* execution mode MDNode */
|
|
N.get(F).get(EMode);
|
|
|
|
SPIRVFunction *BF = static_cast<SPIRVFunction *>(getTranslatedValue(F));
|
|
assert(BF && "Invalid kernel function");
|
|
if (!BF)
|
|
return false;
|
|
|
|
auto AddSingleArgExecutionMode = [&](ExecutionMode EMode) {
|
|
uint32_t Arg = ~0u;
|
|
N.get(Arg);
|
|
BF->addExecutionMode(
|
|
BM->add(new SPIRVExecutionMode(OpExecutionMode, BF, EMode, Arg)));
|
|
};
|
|
|
|
switch (EMode) {
|
|
case spv::ExecutionModeContractionOff:
|
|
BF->addExecutionMode(BM->add(new SPIRVExecutionMode(
|
|
OpExecutionMode, BF, static_cast<ExecutionMode>(EMode))));
|
|
break;
|
|
case spv::ExecutionModeInitializer:
|
|
case spv::ExecutionModeFinalizer:
|
|
if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_1)) {
|
|
BF->addExecutionMode(BM->add(new SPIRVExecutionMode(
|
|
OpExecutionMode, BF, static_cast<ExecutionMode>(EMode))));
|
|
} else {
|
|
getErrorLog().checkError(false, SPIRVEC_Requires1_1,
|
|
"Initializer/Finalizer Execution Mode");
|
|
return false;
|
|
}
|
|
break;
|
|
case spv::ExecutionModeLocalSize:
|
|
case spv::ExecutionModeLocalSizeHint: {
|
|
unsigned X = 0, Y = 0, Z = 0;
|
|
N.get(X).get(Y).get(Z);
|
|
BF->addExecutionMode(BM->add(new SPIRVExecutionMode(
|
|
OpExecutionMode, BF, static_cast<ExecutionMode>(EMode), X, Y, Z)));
|
|
} break;
|
|
case spv::ExecutionModeMaxWorkgroupSizeINTEL: {
|
|
if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_kernel_attributes)) {
|
|
unsigned X = 0, Y = 0, Z = 0;
|
|
N.get(X).get(Y).get(Z);
|
|
BF->addExecutionMode(BM->add(new SPIRVExecutionMode(
|
|
OpExecutionMode, BF, static_cast<ExecutionMode>(EMode), X, Y,
|
|
Z)));
|
|
BM->addExtension(ExtensionID::SPV_INTEL_kernel_attributes);
|
|
BM->addCapability(CapabilityKernelAttributesINTEL);
|
|
}
|
|
} break;
|
|
case spv::ExecutionModeNoGlobalOffsetINTEL: {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_kernel_attributes))
|
|
break;
|
|
BF->addExecutionMode(BM->add(new SPIRVExecutionMode(
|
|
OpExecutionMode, BF, static_cast<ExecutionMode>(EMode))));
|
|
BM->addExtension(ExtensionID::SPV_INTEL_kernel_attributes);
|
|
BM->addCapability(CapabilityKernelAttributesINTEL);
|
|
} break;
|
|
case spv::ExecutionModeVecTypeHint:
|
|
case spv::ExecutionModeSubgroupSize:
|
|
case spv::ExecutionModeSubgroupsPerWorkgroup:
|
|
AddSingleArgExecutionMode(static_cast<ExecutionMode>(EMode));
|
|
break;
|
|
case spv::ExecutionModeNumSIMDWorkitemsINTEL:
|
|
case spv::ExecutionModeSchedulerTargetFmaxMhzINTEL:
|
|
case spv::ExecutionModeMaxWorkDimINTEL:
|
|
case spv::ExecutionModeRegisterMapInterfaceINTEL: {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_kernel_attributes))
|
|
break;
|
|
AddSingleArgExecutionMode(static_cast<ExecutionMode>(EMode));
|
|
BM->addExtension(ExtensionID::SPV_INTEL_kernel_attributes);
|
|
BM->addCapability(CapabilityFPGAKernelAttributesINTEL);
|
|
// RegisterMapInterfaceINTEL mode is defined by the
|
|
// CapabilityFPGAKernelAttributesv2INTEL capability and that
|
|
// capability implicitly defines CapabilityFPGAKernelAttributesINTEL
|
|
if (EMode == spv::ExecutionModeRegisterMapInterfaceINTEL)
|
|
BM->addCapability(CapabilityFPGAKernelAttributesv2INTEL);
|
|
} break;
|
|
case spv::ExecutionModeStreamingInterfaceINTEL: {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_kernel_attributes))
|
|
break;
|
|
AddSingleArgExecutionMode(static_cast<ExecutionMode>(EMode));
|
|
BM->addExtension(ExtensionID::SPV_INTEL_kernel_attributes);
|
|
BM->addCapability(CapabilityFPGAKernelAttributesINTEL);
|
|
} break;
|
|
case spv::ExecutionModeSharedLocalMemorySizeINTEL: {
|
|
if (!BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute))
|
|
break;
|
|
AddSingleArgExecutionMode(static_cast<ExecutionMode>(EMode));
|
|
} break;
|
|
case spv::ExecutionModeNamedBarrierCountINTEL: {
|
|
if (!BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute))
|
|
break;
|
|
unsigned NBarrierCnt = 0;
|
|
N.get(NBarrierCnt);
|
|
BF->addExecutionMode(BM->add(new SPIRVExecutionMode(
|
|
OpExecutionMode, BF, static_cast<ExecutionMode>(EMode),
|
|
NBarrierCnt)));
|
|
BM->addExtension(ExtensionID::SPV_INTEL_vector_compute);
|
|
BM->addCapability(CapabilityVectorComputeINTEL);
|
|
} break;
|
|
|
|
case spv::ExecutionModeDenormPreserve:
|
|
case spv::ExecutionModeDenormFlushToZero:
|
|
case spv::ExecutionModeSignedZeroInfNanPreserve:
|
|
case spv::ExecutionModeRoundingModeRTE:
|
|
case spv::ExecutionModeRoundingModeRTZ: {
|
|
if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)) {
|
|
BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_4);
|
|
AddSingleArgExecutionMode(static_cast<ExecutionMode>(EMode));
|
|
} else if (BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_KHR_float_controls)) {
|
|
BM->addExtension(ExtensionID::SPV_KHR_float_controls);
|
|
AddSingleArgExecutionMode(static_cast<ExecutionMode>(EMode));
|
|
}
|
|
} break;
|
|
case spv::ExecutionModeRoundingModeRTPINTEL:
|
|
case spv::ExecutionModeRoundingModeRTNINTEL:
|
|
case spv::ExecutionModeFloatingPointModeALTINTEL:
|
|
case spv::ExecutionModeFloatingPointModeIEEEINTEL: {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_float_controls2))
|
|
break;
|
|
AddSingleArgExecutionMode(static_cast<ExecutionMode>(EMode));
|
|
} break;
|
|
case spv::internal::ExecutionModeNamedSubgroupSizeINTEL: {
|
|
if (!BM->isAllowedToUseExtension(
|
|
ExtensionID::SPV_INTEL_subgroup_requirements))
|
|
break;
|
|
AddSingleArgExecutionMode(static_cast<ExecutionMode>(EMode));
|
|
} break;
|
|
default:
|
|
llvm_unreachable("invalid execution mode");
|
|
}
|
|
}
|
|
}
|
|
|
|
transFPContract();
|
|
|
|
return true;
|
|
}
|
|
|
|
void LLVMToSPIRVBase::transFPContract() {
|
|
FPContractMode Mode = BM->getFPContractMode();
|
|
|
|
for (Function &F : *M) {
|
|
SPIRVValue *TranslatedF = getTranslatedValue(&F);
|
|
if (!TranslatedF) {
|
|
continue;
|
|
}
|
|
SPIRVFunction *BF = static_cast<SPIRVFunction *>(TranslatedF);
|
|
|
|
bool IsKernelEntryPoint =
|
|
BF->getModule()->isEntryPoint(spv::ExecutionModelKernel, BF->getId());
|
|
if (!IsKernelEntryPoint)
|
|
continue;
|
|
|
|
FPContract FPC = getFPContract(&F);
|
|
assert(FPC != FPContract::UNDEF);
|
|
|
|
bool DisableContraction = false;
|
|
switch (Mode) {
|
|
case FPContractMode::Fast:
|
|
DisableContraction = false;
|
|
break;
|
|
case FPContractMode::On:
|
|
DisableContraction = FPC == FPContract::DISABLED;
|
|
break;
|
|
case FPContractMode::Off:
|
|
DisableContraction = true;
|
|
break;
|
|
}
|
|
|
|
if (DisableContraction) {
|
|
BF->addExecutionMode(BF->getModule()->add(new SPIRVExecutionMode(
|
|
OpExecutionMode, BF, spv::ExecutionModeContractionOff)));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::transMetadata() {
|
|
if (!transOCLMetadata())
|
|
return false;
|
|
|
|
auto Model = getMemoryModel(*M);
|
|
if (Model != SPIRVMemoryModelKind::MemoryModelMax)
|
|
BM->setMemoryModel(static_cast<SPIRVMemoryModelKind>(Model));
|
|
|
|
return true;
|
|
}
|
|
|
|
// Work around to translate kernel_arg_type and kernel_arg_type_qual metadata
|
|
static void transKernelArgTypeMD(SPIRVModule *BM, Function *F, MDNode *MD,
|
|
std::string MDName) {
|
|
std::string Prefix = kSPIRVName::EntrypointPrefix;
|
|
std::string Name = F->getName().str().substr(Prefix.size());
|
|
std::string KernelArgTypesMDStr = std::string(MDName) + "." + Name + ".";
|
|
for (const auto &TyOp : MD->operands())
|
|
KernelArgTypesMDStr += cast<MDString>(TyOp)->getString().str() + ",";
|
|
BM->getString(KernelArgTypesMDStr);
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::transOCLMetadata() {
|
|
for (auto &F : *M) {
|
|
if (F.getCallingConv() != CallingConv::SPIR_KERNEL)
|
|
continue;
|
|
|
|
SPIRVFunction *BF = static_cast<SPIRVFunction *>(getTranslatedValue(&F));
|
|
assert(BF && "Kernel function should be translated first");
|
|
|
|
// Create 'OpString' as a workaround to store information about
|
|
// *orignal* (typedef'ed, unsigned integers) type names of kernel arguments.
|
|
// OpString "kernel_arg_type.%kernel_name%.typename0,typename1,..."
|
|
if (auto *KernelArgType = F.getMetadata(SPIR_MD_KERNEL_ARG_TYPE))
|
|
if (BM->shouldPreserveOCLKernelArgTypeMetadataThroughString())
|
|
transKernelArgTypeMD(BM, &F, KernelArgType, SPIR_MD_KERNEL_ARG_TYPE);
|
|
|
|
if (auto *KernelArgTypeQual = F.getMetadata(SPIR_MD_KERNEL_ARG_TYPE_QUAL)) {
|
|
foreachKernelArgMD(
|
|
KernelArgTypeQual, BF,
|
|
[](const std::string &Str, SPIRVFunctionParameter *BA) {
|
|
if (Str.find("volatile") != std::string::npos)
|
|
BA->addDecorate(new SPIRVDecorate(DecorationVolatile, BA));
|
|
if (Str.find("restrict") != std::string::npos)
|
|
BA->addDecorate(
|
|
new SPIRVDecorate(DecorationFuncParamAttr, BA,
|
|
FunctionParameterAttributeNoAlias));
|
|
});
|
|
// Create 'OpString' as a workaround to store information about
|
|
// constant qualifiers of pointer kernel arguments. Store empty string
|
|
// for a non constant parameter.
|
|
// OpString "kernel_arg_type_qual.%kernel_name%.qual0,qual1,..."
|
|
if (BM->shouldPreserveOCLKernelArgTypeMetadataThroughString())
|
|
transKernelArgTypeMD(BM, &F, KernelArgTypeQual,
|
|
SPIR_MD_KERNEL_ARG_TYPE_QUAL);
|
|
}
|
|
if (auto *KernelArgName = F.getMetadata(SPIR_MD_KERNEL_ARG_NAME)) {
|
|
foreachKernelArgMD(
|
|
KernelArgName, BF,
|
|
[=](const std::string &Str, SPIRVFunctionParameter *BA) {
|
|
BM->setName(BA, Str);
|
|
});
|
|
}
|
|
if (auto *KernArgDecoMD = F.getMetadata(SPIRV_MD_PARAMETER_DECORATIONS))
|
|
foreachKernelArgMD(KernArgDecoMD, BF, transMetadataDecorations);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::transSourceLanguage() {
|
|
auto Src = getSPIRVSource(M);
|
|
SrcLang = std::get<0>(Src);
|
|
SrcLangVer = std::get<1>(Src);
|
|
BM->setSourceLanguage(static_cast<SourceLanguage>(SrcLang), SrcLangVer);
|
|
return true;
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::transExtension() {
|
|
if (auto N = SPIRVMDWalker(*M).getNamedMD(kSPIRVMD::Extension)) {
|
|
while (!N.atEnd()) {
|
|
std::string S;
|
|
N.nextOp().get(S);
|
|
assert(!S.empty() && "Invalid extension");
|
|
ExtensionID ExtID = SPIRVMap<ExtensionID, std::string>::rmap(S);
|
|
if (!BM->getErrorLog().checkError(BM->isAllowedToUseExtension(ExtID),
|
|
SPIRVEC_RequiresExtension, S))
|
|
return false;
|
|
BM->getExtension().insert(S);
|
|
}
|
|
}
|
|
if (auto N = SPIRVMDWalker(*M).getNamedMD(kSPIRVMD::SourceExtension)) {
|
|
while (!N.atEnd()) {
|
|
std::string S;
|
|
N.nextOp().get(S);
|
|
assert(!S.empty() && "Invalid extension");
|
|
BM->getSourceExtension().insert(S);
|
|
}
|
|
}
|
|
for (auto &I :
|
|
map<SPIRVCapabilityKind>(rmap<OclExt::Kind>(BM->getExtension())))
|
|
BM->addCapability(I);
|
|
|
|
return true;
|
|
}
|
|
|
|
void LLVMToSPIRVBase::dumpUsers(Value *V) {
|
|
SPIRVDBG(dbgs() << "Users of " << *V << " :\n");
|
|
for (auto UI = V->user_begin(), UE = V->user_end(); UI != UE; ++UI)
|
|
SPIRVDBG(dbgs() << " " << **UI << '\n');
|
|
}
|
|
|
|
Op LLVMToSPIRVBase::transBoolOpCode(SPIRVValue *Opn, Op OC) {
|
|
if (!Opn->getType()->isTypeVectorOrScalarBool())
|
|
return OC;
|
|
IntBoolOpMap::find(OC, &OC);
|
|
return OC;
|
|
}
|
|
|
|
SPIRVInstruction *
|
|
LLVMToSPIRVBase::transBuiltinToInstWithoutDecoration(Op OC, CallInst *CI,
|
|
SPIRVBasicBlock *BB) {
|
|
// OpAtomicCompareExchangeWeak is not "weak" at all,
|
|
// but instead has the same semantics as OpAtomicCompareExchange.
|
|
// Moreover, OpAtomicCompareExchangeWeak has been deprecated.
|
|
if (OC == OpAtomicCompareExchangeWeak)
|
|
OC = OpAtomicCompareExchange;
|
|
|
|
// We should do this replacement only for SPIR-V 1.5, as OpLessOrGreater is
|
|
// deprecated there. However we do such replacement for the usual pipeline
|
|
// (not via SPIR-V friendly calls) without minding the version, so we can do
|
|
// such thing here as well.
|
|
if (OC == OpLessOrGreater &&
|
|
BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_5))
|
|
OC = OpFOrdNotEqual;
|
|
|
|
if (isGroupOpCode(OC))
|
|
BM->addCapability(CapabilityGroups);
|
|
switch (static_cast<size_t>(OC)) {
|
|
case OpControlBarrier: {
|
|
auto BArgs = transValue(getArguments(CI), BB);
|
|
return BM->addControlBarrierInst(BArgs[0], BArgs[1], BArgs[2], BB);
|
|
} break;
|
|
case OpGroupAsyncCopy: {
|
|
auto BArgs = transValue(getArguments(CI), BB);
|
|
return BM->addAsyncGroupCopy(BArgs[0], BArgs[1], BArgs[2], BArgs[3],
|
|
BArgs[4], BArgs[5], BB);
|
|
} break;
|
|
case OpVectorExtractDynamic: {
|
|
auto BArgs = transValue(getArguments(CI), BB);
|
|
return BM->addVectorExtractDynamicInst(BArgs[0], BArgs[1], BB);
|
|
} break;
|
|
case OpVectorInsertDynamic: {
|
|
auto BArgs = transValue(getArguments(CI), BB);
|
|
return BM->addVectorInsertDynamicInst(BArgs[0], BArgs[1], BArgs[2], BB);
|
|
} break;
|
|
case OpSampledImage: {
|
|
// Clang can generate SPIRV-friendly call for OpSampledImage instruction,
|
|
// i.e. __spirv_SampledImage... But it can't generate correct return type
|
|
// for this call, because there is no support for type corresponding to
|
|
// OpTypeSampledImage. So, in this case, we create the required type here.
|
|
Value *Image = CI->getArgOperand(0);
|
|
Type *SampledImgTy =
|
|
adjustImageType(getCallValueType(CI, 0), kSPIRVTypeName::Image,
|
|
kSPIRVTypeName::SampledImg);
|
|
Value *Sampler = CI->getArgOperand(1);
|
|
return BM->addSampledImageInst(transType(SampledImgTy),
|
|
transValue(Image, BB),
|
|
transValue(Sampler, BB), BB);
|
|
}
|
|
case OpImageRead:
|
|
case OpImageSampleExplicitLod:
|
|
case OpImageWrite: {
|
|
// Image Op needs handling of SignExtend or ZeroExtend image operands.
|
|
auto Args = getArguments(CI);
|
|
SPIRVType *SPRetTy =
|
|
CI->getType()->isVoidTy() ? nullptr : transScavengedType(CI);
|
|
auto *SPI = SPIRVInstTemplateBase::create(OC);
|
|
std::vector<SPIRVWord> SPArgs;
|
|
for (size_t I = 0, E = Args.size(); I != E; ++I) {
|
|
if (Args[I]->getType()->isPointerTy()) {
|
|
[[maybe_unused]] Value *Pointee = Args[I]->stripPointerCasts();
|
|
assert((Pointee == Args[I] || !isa<Function>(Pointee)) &&
|
|
"Illegal use of a function pointer type");
|
|
}
|
|
SPArgs.push_back(SPI->isOperandLiteral(I)
|
|
? cast<ConstantInt>(Args[I])->getZExtValue()
|
|
: transValue(Args[I], BB)->getId());
|
|
}
|
|
if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)) {
|
|
size_t ImOpIdx = getImageOperandsIndex(OC);
|
|
if (Args.size() > ImOpIdx) {
|
|
// Update existing ImageOperands with SignExtendMask/ZeroExtendMask.
|
|
if (auto *ImOp = dyn_cast<ConstantInt>(Args[ImOpIdx])) {
|
|
uint64_t ImOpVal = ImOp->getZExtValue();
|
|
unsigned SignZeroExtMasks =
|
|
ImageOperandsMask::ImageOperandsSignExtendMask |
|
|
ImageOperandsMask::ImageOperandsZeroExtendMask;
|
|
if (!(ImOpVal & SignZeroExtMasks))
|
|
if (int SignZeroExt = getImageSignZeroExt(CI->getCalledFunction()))
|
|
SPArgs[ImOpIdx] = ImOpVal | SignZeroExt;
|
|
}
|
|
} else {
|
|
// Add new ImageOperands argument.
|
|
if (int SignZeroExt = getImageSignZeroExt(CI->getCalledFunction()))
|
|
SPArgs.push_back(SignZeroExt);
|
|
}
|
|
}
|
|
BM->addInstTemplate(SPI, SPArgs, BB, SPRetTy);
|
|
return SPI;
|
|
}
|
|
case OpFixedSqrtINTEL:
|
|
case OpFixedRecipINTEL:
|
|
case OpFixedRsqrtINTEL:
|
|
case OpFixedSinINTEL:
|
|
case OpFixedCosINTEL:
|
|
case OpFixedSinCosINTEL:
|
|
case OpFixedSinPiINTEL:
|
|
case OpFixedCosPiINTEL:
|
|
case OpFixedSinCosPiINTEL:
|
|
case OpFixedLogINTEL:
|
|
case OpFixedExpINTEL: {
|
|
// LLVM fixed point functions return value:
|
|
// iN (arbitrary precision integer of N bits length)
|
|
// Arguments:
|
|
// A(iN), S(i1), I(i32), rI(i32), Quantization(i32), Overflow(i32)
|
|
// where A - integer input of any width.
|
|
|
|
// SPIR-V fixed point instruction contains:
|
|
// <id>ResTy Res<id> In<id> \
|
|
// Literal S Literal I Literal rI Literal Q Literal O
|
|
|
|
Type *ResTy = CI->getType();
|
|
|
|
auto OpItr = CI->value_op_begin();
|
|
auto OpEnd = OpItr + CI->arg_size();
|
|
|
|
// If the return type of an instruction is wider than 64-bit, then this
|
|
// instruction will return via 'sret' argument added into the arguments
|
|
// list. Here we reverse this, removing 'sret' argument and restoring
|
|
// the original return type.
|
|
if (CI->hasStructRetAttr()) {
|
|
assert(ResTy->isVoidTy() && "Return type is not void");
|
|
ResTy = CI->getParamStructRetType(0);
|
|
OpItr++;
|
|
}
|
|
|
|
// Input - integer input of any width or 'byval' pointer to this integer
|
|
SPIRVValue *Input = transValue(*OpItr, BB);
|
|
SPIRVType *LoadTy = nullptr;
|
|
if (Input->isUntypedVariable())
|
|
LoadTy = static_cast<SPIRVUntypedVariableKHR *>(Input)->getDataType();
|
|
if (OpItr->getType()->isPointerTy())
|
|
Input = BM->addLoadInst(Input, {}, BB, LoadTy);
|
|
OpItr++;
|
|
|
|
std::vector<SPIRVWord> Literals;
|
|
std::transform(OpItr, OpEnd, std::back_inserter(Literals), [](auto *O) {
|
|
return cast<llvm::ConstantInt>(O)->getZExtValue();
|
|
});
|
|
|
|
auto *APIntInst =
|
|
BM->addFixedPointIntelInst(OC, transType(ResTy), Input, Literals, BB);
|
|
if (!CI->hasStructRetAttr())
|
|
return APIntInst;
|
|
return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), APIntInst, {},
|
|
BB);
|
|
}
|
|
case OpArbitraryFloatCastINTEL:
|
|
case OpArbitraryFloatCastFromIntINTEL:
|
|
case OpArbitraryFloatCastToIntINTEL:
|
|
case OpArbitraryFloatRecipINTEL:
|
|
case OpArbitraryFloatRSqrtINTEL:
|
|
case OpArbitraryFloatCbrtINTEL:
|
|
case OpArbitraryFloatSqrtINTEL:
|
|
case OpArbitraryFloatLogINTEL:
|
|
case OpArbitraryFloatLog2INTEL:
|
|
case OpArbitraryFloatLog10INTEL:
|
|
case OpArbitraryFloatLog1pINTEL:
|
|
case OpArbitraryFloatExpINTEL:
|
|
case OpArbitraryFloatExp2INTEL:
|
|
case OpArbitraryFloatExp10INTEL:
|
|
case OpArbitraryFloatExpm1INTEL:
|
|
case OpArbitraryFloatSinINTEL:
|
|
case OpArbitraryFloatCosINTEL:
|
|
case OpArbitraryFloatSinCosINTEL:
|
|
case OpArbitraryFloatSinPiINTEL:
|
|
case OpArbitraryFloatCosPiINTEL:
|
|
case OpArbitraryFloatSinCosPiINTEL:
|
|
case OpArbitraryFloatASinINTEL:
|
|
case OpArbitraryFloatASinPiINTEL:
|
|
case OpArbitraryFloatACosINTEL:
|
|
case OpArbitraryFloatACosPiINTEL:
|
|
case OpArbitraryFloatATanINTEL:
|
|
case OpArbitraryFloatATanPiINTEL: {
|
|
// Format of instruction CastFromInt:
|
|
// LLVM arbitrary floating point functions return value type:
|
|
// iN (arbitrary precision integer of N bits length)
|
|
// Arguments: A(iN), Mout(i32), FromSign(bool), EnableSubnormals(i32),
|
|
// RoundingMode(i32), RoundingAccuracy(i32)
|
|
// where A and return values are of arbitrary precision integer type.
|
|
// SPIR-V arbitrary floating point instruction layout:
|
|
// <id>ResTy Res<id> A<id> Literal Mout Literal FromSign
|
|
// Literal EnableSubnormals Literal RoundingMode
|
|
// Literal RoundingAccuracy
|
|
|
|
// Format of instruction CastToInt:
|
|
// LLVM arbitrary floating point functions return value: iN
|
|
// Arguments: A(iN), MA(i32), ToSign(bool), EnableSubnormals(i32),
|
|
// RoundingMode(i32), RoundingAccuracy(i32)
|
|
// where A and return values are of arbitrary precision integer type.
|
|
// SPIR-V arbitrary floating point instruction layout:
|
|
// <id>ResTy Res<id> A<id> Literal MA Literal ToSign
|
|
// Literal EnableSubnormals Literal RoundingMode
|
|
// Literal RoundingAccuracy
|
|
|
|
// Format of other instructions:
|
|
// LLVM arbitrary floating point functions return value: iN
|
|
// Arguments: A(iN), MA(i32), Mout(i32), EnableSubnormals(i32),
|
|
// RoundingMode(i32), RoundingAccuracy(i32)
|
|
// where A and return values are of arbitrary precision integer type.
|
|
// SPIR-V arbitrary floating point instruction layout:
|
|
// <id>ResTy Res<id> A<id> Literal MA Literal Mout
|
|
// Literal EnableSubnormals Literal RoundingMode
|
|
// Literal RoundingAccuracy
|
|
|
|
Type *ResTy = CI->getType();
|
|
|
|
auto OpItr = CI->value_op_begin();
|
|
auto OpEnd = OpItr + CI->arg_size();
|
|
|
|
// If the return type of an instruction is wider than 64-bit, then this
|
|
// instruction will return via 'sret' argument added into the arguments
|
|
// list. Here we reverse this, removing 'sret' argument and restoring
|
|
// the original return type.
|
|
if (CI->hasStructRetAttr()) {
|
|
assert(ResTy->isVoidTy() && "Return type is not void");
|
|
ResTy = CI->getParamStructRetType(0);
|
|
OpItr++;
|
|
}
|
|
|
|
// Input - integer input of any width or 'byval' pointer to this integer
|
|
SPIRVValue *Input = transValue(*OpItr, BB);
|
|
SPIRVType *LoadTy = nullptr;
|
|
if (Input->isUntypedVariable())
|
|
LoadTy = static_cast<SPIRVUntypedVariableKHR *>(Input)->getDataType();
|
|
if (OpItr->getType()->isPointerTy())
|
|
Input = BM->addLoadInst(Input, {}, BB, LoadTy);
|
|
OpItr++;
|
|
|
|
std::vector<SPIRVWord> Literals;
|
|
std::transform(OpItr, OpEnd, std::back_inserter(Literals), [](auto *O) {
|
|
return cast<llvm::ConstantInt>(O)->getZExtValue();
|
|
});
|
|
|
|
auto *APIntInst = BM->addArbFloatPointIntelInst(OC, transType(ResTy), Input,
|
|
nullptr, Literals, BB);
|
|
if (!CI->hasStructRetAttr())
|
|
return APIntInst;
|
|
return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), APIntInst, {},
|
|
BB);
|
|
}
|
|
case OpArbitraryFloatAddINTEL:
|
|
case OpArbitraryFloatSubINTEL:
|
|
case OpArbitraryFloatMulINTEL:
|
|
case OpArbitraryFloatDivINTEL:
|
|
case OpArbitraryFloatGTINTEL:
|
|
case OpArbitraryFloatGEINTEL:
|
|
case OpArbitraryFloatLTINTEL:
|
|
case OpArbitraryFloatLEINTEL:
|
|
case OpArbitraryFloatEQINTEL:
|
|
case OpArbitraryFloatHypotINTEL:
|
|
case OpArbitraryFloatATan2INTEL:
|
|
case OpArbitraryFloatPowINTEL:
|
|
case OpArbitraryFloatPowRINTEL:
|
|
case OpArbitraryFloatPowNINTEL: {
|
|
// Format of instructions Add, Sub, Mul, Div, Hypot, ATan2, Pow, PowR:
|
|
// LLVM arbitrary floating point functions return value:
|
|
// iN (arbitrary precision integer of N bits length)
|
|
// Arguments: A(iN), MA(i32), B(iN), MB(i32), Mout(i32),
|
|
// EnableSubnormals(i32), RoundingMode(i32),
|
|
// RoundingAccuracy(i32)
|
|
// where A, B and return values are of arbitrary precision integer type.
|
|
// SPIR-V arbitrary floating point instruction layout:
|
|
// <id>ResTy Res<id> A<id> Literal MA B<id> Literal MB Literal Mout
|
|
// Literal EnableSubnormals Literal RoundingMode
|
|
// Literal RoundingAccuracy
|
|
|
|
// Format of instruction PowN:
|
|
// LLVM arbitrary floating point functions return value: iN
|
|
// Arguments: A(iN), MA(i32), B(iN), SignOfB(i1), Mout(i32),
|
|
// EnableSubnormals(i32), RoundingMode(i32),
|
|
// RoundingAccuracy(i32)
|
|
// where A, B and return values are of arbitrary precision integer type.
|
|
// SPIR-V arbitrary floating point instruction layout:
|
|
// <id>ResTy Res<id> A<id> Literal MA B<id> Literal SignOfB Literal Mout
|
|
// Literal EnableSubnormals Literal RoundingMode
|
|
// Literal RoundingAccuracy
|
|
|
|
// Format of instructions GT, GE, LT, LE, EQ:
|
|
// LLVM arbitrary floating point functions return value: Bool
|
|
// Arguments: A(iN), MA(i32), B(iN), MB(i32)
|
|
// where A and B are of arbitrary precision integer type.
|
|
// SPIR-V arbitrary floating point instruction layout:
|
|
// <id>ResTy Res<id> A<id> Literal MA B<id> Literal MB
|
|
|
|
Type *ResTy = CI->getType();
|
|
|
|
auto OpItr = CI->value_op_begin();
|
|
auto OpEnd = OpItr + CI->arg_size();
|
|
|
|
// If the return type of an instruction is wider than 64-bit, then this
|
|
// instruction will return via 'sret' argument added into the arguments
|
|
// list. Here we reverse this, removing 'sret' argument and restoring
|
|
// the original return type.
|
|
if (CI->hasStructRetAttr()) {
|
|
assert(ResTy->isVoidTy() && "Return type is not void");
|
|
ResTy = CI->getParamStructRetType(0);
|
|
OpItr++;
|
|
}
|
|
|
|
// InA - integer input of any width or 'byval' pointer to this integer
|
|
SPIRVValue *InA = transValue(*OpItr, BB);
|
|
SPIRVType *LoadTy = nullptr;
|
|
if (InA->isUntypedVariable())
|
|
LoadTy = static_cast<SPIRVUntypedVariableKHR *>(InA)->getDataType();
|
|
if (OpItr->getType()->isPointerTy())
|
|
InA = BM->addLoadInst(InA, {}, BB, LoadTy);
|
|
OpItr++;
|
|
|
|
std::vector<SPIRVWord> Literals;
|
|
Literals.push_back(cast<llvm::ConstantInt>(*OpItr++)->getZExtValue());
|
|
|
|
// InB - integer input of any width or 'byval' pointer to this integer
|
|
SPIRVValue *InB = transValue(*OpItr, BB);
|
|
LoadTy = nullptr;
|
|
if (InB->isUntypedVariable())
|
|
LoadTy = static_cast<SPIRVUntypedVariableKHR *>(InB)->getDataType();
|
|
if (OpItr->getType()->isPointerTy()) {
|
|
std::vector<SPIRVWord> Mem;
|
|
InB = BM->addLoadInst(InB, Mem, BB, LoadTy);
|
|
}
|
|
OpItr++;
|
|
|
|
std::transform(OpItr, OpEnd, std::back_inserter(Literals), [](auto *O) {
|
|
return cast<llvm::ConstantInt>(O)->getZExtValue();
|
|
});
|
|
|
|
auto *APIntInst = BM->addArbFloatPointIntelInst(OC, transType(ResTy), InA,
|
|
InB, Literals, BB);
|
|
if (!CI->hasStructRetAttr())
|
|
return APIntInst;
|
|
return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), APIntInst, {},
|
|
BB);
|
|
}
|
|
case internal::OpTaskSequenceGetINTEL: {
|
|
Type *ResTy = nullptr;
|
|
auto OpItr = CI->value_op_begin();
|
|
|
|
if (CI->hasStructRetAttr()) {
|
|
assert(CI->getType()->isVoidTy() && "Return type is not void");
|
|
ResTy = CI->getParamStructRetType(0);
|
|
OpItr++;
|
|
}
|
|
|
|
SPIRVType *RetTy = ResTy ? transType(ResTy) : transScavengedType(CI);
|
|
auto *TaskSeqGet =
|
|
BM->addTaskSequenceGetINTELInst(RetTy, transValue(*OpItr++, BB), BB);
|
|
|
|
if (!CI->hasStructRetAttr())
|
|
return TaskSeqGet;
|
|
return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), TaskSeqGet,
|
|
{}, BB);
|
|
}
|
|
case OpLoad: {
|
|
std::vector<SPIRVWord> MemoryAccess;
|
|
assert(CI->arg_size() > 0 && "Expected at least 1 operand for OpLoad call");
|
|
for (size_t I = 1; I < CI->arg_size(); ++I)
|
|
MemoryAccess.push_back(
|
|
cast<ConstantInt>(CI->getArgOperand(I))->getZExtValue());
|
|
return BM->addLoadInst(transValue(CI->getArgOperand(0), BB), MemoryAccess,
|
|
BB);
|
|
}
|
|
case OpStore: {
|
|
std::vector<SPIRVWord> MemoryAccess;
|
|
assert(CI->arg_size() > 1 &&
|
|
"Expected at least 2 operands for OpStore call");
|
|
for (size_t I = 2; I < CI->arg_size(); ++I)
|
|
MemoryAccess.push_back(
|
|
cast<ConstantInt>(CI->getArgOperand(I))->getZExtValue());
|
|
return BM->addStoreInst(transValue(CI->getArgOperand(0), BB),
|
|
transValue(CI->getArgOperand(1), BB), MemoryAccess,
|
|
BB);
|
|
}
|
|
case OpCompositeConstruct: {
|
|
std::vector<SPIRVId> Operands = {
|
|
transValue(CI->getArgOperand(0), BB)->getId()};
|
|
return BM->addCompositeConstructInst(transType(CI->getType()), Operands,
|
|
BB);
|
|
}
|
|
case OpIAddCarry:
|
|
case OpISubBorrow:
|
|
case OpUMulExtended:
|
|
case OpSMulExtended: {
|
|
Function *F = CI->getCalledFunction();
|
|
StructType *St = cast<StructType>(F->getParamStructRetType(0));
|
|
SPIRVValue *V = BM->addBinaryInst(OC, transType(St),
|
|
transValue(CI->getArgOperand(1), BB),
|
|
transValue(CI->getArgOperand(2), BB), BB);
|
|
return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), V, {}, BB);
|
|
}
|
|
case OpCooperativeMatrixLengthKHR: {
|
|
return BM->addCooperativeMatrixLengthKHRInst(
|
|
transScavengedType(CI), transType(CI->getArgOperand(0)->getType()), BB);
|
|
}
|
|
case OpGroupNonUniformShuffle:
|
|
case OpGroupNonUniformShuffleDown:
|
|
case OpGroupNonUniformShuffleUp:
|
|
case OpGroupNonUniformShuffleXor: {
|
|
Function *F = CI->getCalledFunction();
|
|
if (F->arg_size() && F->getArg(0)->hasStructRetAttr()) {
|
|
StructType *St = cast<StructType>(F->getParamStructRetType(0));
|
|
assert(isSYCLHalfType(St) || isSYCLBfloat16Type(St));
|
|
SPIRVValue *InValue =
|
|
transValue(CI->getArgOperand(0)->stripPointerCasts(), BB);
|
|
SPIRVId ScopeId = transValue(CI->getArgOperand(1), BB)->getId();
|
|
SPIRVValue *Delta = transValue(CI->getArgOperand(3), BB);
|
|
SPIRVType *LoadTy = nullptr;
|
|
if (InValue->isUntypedVariable())
|
|
LoadTy = static_cast<SPIRVUntypedVariableKHR *>(InValue)->getDataType();
|
|
SPIRVValue *Composite0 = BM->addLoadInst(InValue, {}, BB, LoadTy);
|
|
Type *MemberTy = St->getElementType(0);
|
|
SPIRVType *ElementTy = transType(MemberTy);
|
|
SPIRVValue *Element0 =
|
|
BM->addCompositeExtractInst(ElementTy, Composite0, {0}, BB);
|
|
SPIRVValue *Src = BM->addGroupInst(
|
|
OC, ElementTy, static_cast<Scope>(ScopeId), {Element0, Delta}, BB);
|
|
SPIRVValue *Composite1 =
|
|
BM->addCompositeInsertInst(Src, Composite0, {0}, BB);
|
|
return BM->addStoreInst(InValue, Composite1, {}, BB);
|
|
}
|
|
[[fallthrough]];
|
|
}
|
|
default: {
|
|
if (isCvtOpCode(OC) && OC != OpGenericCastToPtrExplicit) {
|
|
return BM->addUnaryInst(OC, transScavengedType(CI),
|
|
transValue(CI->getArgOperand(0), BB), BB);
|
|
} else if (isCmpOpCode(OC) || isUnaryPredicateOpCode(OC)) {
|
|
auto *ResultTy = CI->getType();
|
|
Type *BoolTy = IntegerType::getInt1Ty(M->getContext());
|
|
auto IsVector = ResultTy->isVectorTy();
|
|
if (IsVector)
|
|
BoolTy = FixedVectorType::get(
|
|
BoolTy, cast<FixedVectorType>(ResultTy)->getNumElements());
|
|
auto *BBT = transType(BoolTy);
|
|
SPIRVInstruction *Res;
|
|
if (isCmpOpCode(OC)) {
|
|
assert(CI && CI->arg_size() == 2 && "Invalid call inst");
|
|
Res = BM->addCmpInst(OC, BBT, transValue(CI->getArgOperand(0), BB),
|
|
transValue(CI->getArgOperand(1), BB), BB);
|
|
} else {
|
|
assert(CI && CI->arg_size() == 1 && "Invalid call inst");
|
|
Res =
|
|
BM->addUnaryInst(OC, BBT, transValue(CI->getArgOperand(0), BB), BB);
|
|
}
|
|
// OpenCL C and OpenCL C++ built-ins may have different return type
|
|
if (ResultTy == BoolTy)
|
|
return Res;
|
|
assert(IsVector || (!IsVector && ResultTy->isIntegerTy(32)));
|
|
auto *Zero = transValue(Constant::getNullValue(ResultTy), BB);
|
|
auto *One = transValue(
|
|
IsVector ? Constant::getAllOnesValue(ResultTy) : getInt32(M, 1), BB);
|
|
return BM->addSelectInst(Res, One, Zero, BB);
|
|
} else if (isBinaryOpCode(OC)) {
|
|
assert(CI && CI->arg_size() == 2 && "Invalid call inst");
|
|
return BM->addBinaryInst(OC, transScavengedType(CI),
|
|
transValue(CI->getArgOperand(0), BB),
|
|
transValue(CI->getArgOperand(1), BB), BB);
|
|
} else if (CI->arg_size() == 1 && !CI->getType()->isVoidTy() &&
|
|
!hasExecScope(OC) && !isAtomicOpCode(OC)) {
|
|
return BM->addUnaryInst(OC, transScavengedType(CI),
|
|
transValue(CI->getArgOperand(0), BB), BB);
|
|
} else {
|
|
auto Args = getArguments(CI);
|
|
SPIRVType *SPRetTy = nullptr;
|
|
Type *RetTy = CI->getType();
|
|
auto *F = CI->getCalledFunction();
|
|
if (!RetTy->isVoidTy()) {
|
|
SPRetTy = transScavengedType(CI);
|
|
} else if (Args.size() > 0 && F->arg_begin()->hasStructRetAttr()) {
|
|
SPRetTy = transType(F->getParamStructRetType(0));
|
|
Args.erase(Args.begin());
|
|
}
|
|
if (RetTy->isPointerTy() &&
|
|
BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers)) {
|
|
if (OC == OpAccessChain)
|
|
OC = OpUntypedAccessChainKHR;
|
|
else if (OC == OpInBoundsAccessChain)
|
|
OC = OpUntypedInBoundsAccessChainKHR;
|
|
else if (OC == OpPtrAccessChain)
|
|
OC = OpUntypedPtrAccessChainKHR;
|
|
else if (OC == OpInBoundsPtrAccessChain)
|
|
OC = OpUntypedInBoundsPtrAccessChainKHR;
|
|
}
|
|
auto *SPI = SPIRVInstTemplateBase::create(OC);
|
|
std::vector<SPIRVWord> SPArgs;
|
|
if (isUntypedAccessChainOpCode(OC)) {
|
|
// Untyped access chain instructions have an additional argument BaseTy.
|
|
Type *Ty = Scavenger->getScavengedType(Args[0]);
|
|
SPIRVType *PtrTy = nullptr;
|
|
if (auto *TPT = dyn_cast<TypedPointerType>(Ty)) {
|
|
PtrTy = transType(TPT->getElementType());
|
|
SPArgs.push_back(PtrTy->getId());
|
|
}
|
|
}
|
|
for (size_t I = 0, E = Args.size(); I != E; ++I) {
|
|
if (Args[I]->getType()->isPointerTy()) {
|
|
Value *Pointee = Args[I]->stripPointerCasts();
|
|
(void)Pointee;
|
|
assert((Pointee == Args[I] || !isa<Function>(Pointee)) &&
|
|
"Illegal use of a function pointer type");
|
|
}
|
|
if (!SPI->isOperandLiteral(I)) {
|
|
SPIRVValue *Val = transValue(Args[I], BB);
|
|
SPArgs.push_back(Val->getId());
|
|
} else {
|
|
SPArgs.push_back(cast<ConstantInt>(Args[I])->getZExtValue());
|
|
}
|
|
}
|
|
BM->addInstTemplate(SPI, SPArgs, BB, SPRetTy);
|
|
if (!SPRetTy || !SPRetTy->isTypeStruct())
|
|
return SPI;
|
|
std::vector<SPIRVWord> Mem;
|
|
SPIRVDBG(spvdbgs() << *SPI << '\n');
|
|
return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), SPI, Mem,
|
|
BB);
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SPIRV::SPIRVLinkageTypeKind
|
|
LLVMToSPIRVBase::transLinkageType(const GlobalValue *GV) {
|
|
if (GV->isDeclarationForLinker())
|
|
return SPIRVLinkageTypeKind::LinkageTypeImport;
|
|
if (GV->hasInternalLinkage() || GV->hasPrivateLinkage())
|
|
return spv::internal::LinkageTypeInternal;
|
|
if (GV->hasLinkOnceODRLinkage())
|
|
if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_linkonce_odr))
|
|
return SPIRVLinkageTypeKind::LinkageTypeLinkOnceODR;
|
|
return SPIRVLinkageTypeKind::LinkageTypeExport;
|
|
}
|
|
|
|
LLVMToSPIRVBase::FPContract LLVMToSPIRVBase::getFPContract(Function *F) {
|
|
auto It = FPContractMap.find(F);
|
|
if (It == FPContractMap.end()) {
|
|
return FPContract::UNDEF;
|
|
}
|
|
return It->second;
|
|
}
|
|
|
|
bool LLVMToSPIRVBase::joinFPContract(Function *F, FPContract C) {
|
|
FPContract &Existing = FPContractMap[F];
|
|
switch (Existing) {
|
|
case FPContract::UNDEF:
|
|
if (C != FPContract::UNDEF) {
|
|
Existing = C;
|
|
return true;
|
|
}
|
|
return false;
|
|
case FPContract::ENABLED:
|
|
if (C == FPContract::DISABLED) {
|
|
Existing = C;
|
|
return true;
|
|
}
|
|
return false;
|
|
case FPContract::DISABLED:
|
|
return false;
|
|
}
|
|
llvm_unreachable("Unhandled FPContract value.");
|
|
}
|
|
} // namespace SPIRV
|
|
|
|
char LLVMToSPIRVLegacy::ID = 0;
|
|
|
|
INITIALIZE_PASS_BEGIN(LLVMToSPIRVLegacy, "llvmtospv",
|
|
"Translate LLVM to SPIR-V", false, false)
|
|
INITIALIZE_PASS_DEPENDENCY(OCLTypeToSPIRVLegacy)
|
|
INITIALIZE_PASS_END(LLVMToSPIRVLegacy, "llvmtospv", "Translate LLVM to SPIR-V",
|
|
false, false)
|
|
|
|
ModulePass *llvm::createLLVMToSPIRVLegacy(SPIRVModule *SMod) {
|
|
return new LLVMToSPIRVLegacy(SMod);
|
|
}
|
|
|
|
void addPassesForSPIRV(ModulePassManager &PassMgr,
|
|
const SPIRV::TranslatorOpts &Opts) {
|
|
if (Opts.isSPIRVMemToRegEnabled())
|
|
PassMgr.addPass(createModuleToFunctionPassAdaptor(PromotePass()));
|
|
PassMgr.addPass(PreprocessMetadataPass());
|
|
PassMgr.addPass(SPIRVLowerOCLBlocksPass());
|
|
PassMgr.addPass(OCLToSPIRVPass());
|
|
PassMgr.addPass(SPIRVRegularizeLLVMPass());
|
|
PassMgr.addPass(SPIRVLowerConstExprPass());
|
|
PassMgr.addPass(SPIRVLowerBoolPass());
|
|
PassMgr.addPass(SPIRVLowerMemmovePass());
|
|
PassMgr.addPass(SPIRVLowerLLVMIntrinsicPass(Opts));
|
|
PassMgr.addPass(createModuleToFunctionPassAdaptor(
|
|
SPIRVLowerBitCastToNonStandardTypePass(Opts)));
|
|
}
|
|
|
|
bool isValidLLVMModule(Module *M, SPIRVErrorLog &ErrorLog) {
|
|
if (!M)
|
|
return false;
|
|
|
|
if (isEmptyLLVMModule(M))
|
|
return true;
|
|
|
|
Triple TT(M->getTargetTriple());
|
|
if (!ErrorLog.checkError(isSupportedTriple(TT), SPIRVEC_InvalidTargetTriple,
|
|
"Actual target triple is " +
|
|
M->getTargetTriple().str()))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
#if defined(LLVM_SPIRV_BACKEND_TARGET_PRESENT)
|
|
namespace llvm {
|
|
extern "C" bool
|
|
SPIRVTranslateModule(Module *M, std::string &SpirvObj, std::string &ErrMsg,
|
|
const std::vector<std::string> &AllowExtNames,
|
|
const std::vector<std::string> &Opts);
|
|
} // namespace llvm
|
|
#if defined(_SPIRV_SUPPORT_TEXT_FMT)
|
|
namespace SPIRV {
|
|
bool convertSpirv(std::istream &IS, std::ostream &OS, std::string &ErrMsg,
|
|
bool FromText, bool ToText);
|
|
} // namespace SPIRV
|
|
#endif // _SPIRV_SUPPORT_TEXT_FMT
|
|
#endif // LLVM_SPIRV_BACKEND_TARGET_PRESENT
|
|
|
|
namespace {
|
|
|
|
#if defined(LLVM_SPIRV_BACKEND_TARGET_PRESENT)
|
|
static inline Triple::SubArchType spirvVersionToSubArch(VersionNumber VN) {
|
|
switch (VN) {
|
|
case VersionNumber::SPIRV_1_0:
|
|
return Triple::SPIRVSubArch_v10;
|
|
case VersionNumber::SPIRV_1_1:
|
|
return Triple::SPIRVSubArch_v11;
|
|
case VersionNumber::SPIRV_1_2:
|
|
return Triple::SPIRVSubArch_v12;
|
|
case VersionNumber::SPIRV_1_3:
|
|
return Triple::SPIRVSubArch_v13;
|
|
case VersionNumber::SPIRV_1_4:
|
|
return Triple::SPIRVSubArch_v14;
|
|
case VersionNumber::SPIRV_1_5:
|
|
return Triple::SPIRVSubArch_v15;
|
|
case VersionNumber::SPIRV_1_6:
|
|
return Triple::SPIRVSubArch_v16;
|
|
}
|
|
return Triple::NoSubArch;
|
|
}
|
|
|
|
// TODO:
|
|
// - consider exposing user facing options that are to set explicitly a Triple
|
|
// value and an optimization level for the LLVM SPIR-V Backend
|
|
// - switch to the new API call: see
|
|
// https://github.com/llvm/llvm-project/pull/124745
|
|
bool runSpirvBackend(Module *M, std::string &Result, std::string &ErrMsg,
|
|
const SPIRV::TranslatorOpts &TranslatorOpts) {
|
|
// A list of known extensions supported by SPIR-V Backend: it's to be updated
|
|
// each time when SPIR-V Backend introduces support for a new extension.
|
|
static const std::set<ExtensionID> ImplementedBySpirvBE{
|
|
SPIRV::ExtensionID::SPV_EXT_shader_atomic_float_add,
|
|
SPIRV::ExtensionID::SPV_EXT_shader_atomic_float16_add,
|
|
SPIRV::ExtensionID::SPV_EXT_shader_atomic_float_min_max,
|
|
SPIRV::ExtensionID::SPV_EXT_arithmetic_fence,
|
|
SPIRV::ExtensionID::SPV_INTEL_arbitrary_precision_integers,
|
|
SPIRV::ExtensionID::SPV_INTEL_cache_controls,
|
|
SPIRV::ExtensionID::SPV_INTEL_float_controls2,
|
|
SPIRV::ExtensionID::SPV_INTEL_global_variable_fpga_decorations,
|
|
SPIRV::ExtensionID::SPV_INTEL_global_variable_host_access,
|
|
SPIRV::ExtensionID::SPV_EXT_optnone,
|
|
SPIRV::ExtensionID::SPV_INTEL_optnone,
|
|
SPIRV::ExtensionID::SPV_INTEL_usm_storage_classes,
|
|
SPIRV::ExtensionID::SPV_INTEL_split_barrier,
|
|
SPIRV::ExtensionID::SPV_INTEL_subgroups,
|
|
SPIRV::ExtensionID::SPV_INTEL_media_block_io,
|
|
SPIRV::ExtensionID::SPV_INTEL_joint_matrix,
|
|
SPIRV::ExtensionID::SPV_KHR_uniform_group_instructions,
|
|
SPIRV::ExtensionID::SPV_KHR_no_integer_wrap_decoration,
|
|
SPIRV::ExtensionID::SPV_KHR_float_controls,
|
|
SPIRV::ExtensionID::SPV_KHR_expect_assume,
|
|
SPIRV::ExtensionID::SPV_KHR_bit_instructions,
|
|
SPIRV::ExtensionID::SPV_KHR_linkonce_odr,
|
|
SPIRV::ExtensionID::SPV_INTEL_inline_assembly,
|
|
SPIRV::ExtensionID::SPV_INTEL_bfloat16_conversion,
|
|
SPIRV::ExtensionID::SPV_KHR_subgroup_rotate,
|
|
SPIRV::ExtensionID::SPV_INTEL_variable_length_array,
|
|
SPIRV::ExtensionID::SPV_INTEL_function_pointers,
|
|
SPIRV::ExtensionID::SPV_KHR_shader_clock,
|
|
SPIRV::ExtensionID::SPV_KHR_cooperative_matrix,
|
|
SPIRV::ExtensionID::SPV_KHR_non_semantic_info};
|
|
// The fallback for the Triple value.
|
|
static const std::string DefaultTriple = "spirv64-unknown-unknown";
|
|
|
|
std::function<bool(SPIRV::ExtensionID)> Filter =
|
|
[](SPIRV::ExtensionID Ext) -> bool {
|
|
return ImplementedBySpirvBE.find(Ext) != ImplementedBySpirvBE.end();
|
|
};
|
|
const std::vector<std::string> AllowExtNames =
|
|
TranslatorOpts.getAllowedSPIRVExtensionNames(Filter);
|
|
|
|
// Correct the Triple value if needed
|
|
Triple TargetTriple(M->getTargetTriple());
|
|
if (TargetTriple.isSPIR()) {
|
|
TargetTriple.setArch(TargetTriple.getArch() == Triple::spir64
|
|
? Triple::spirv64
|
|
: Triple::spirv32,
|
|
TargetTriple.getSubArch());
|
|
M->setTargetTriple(TargetTriple);
|
|
// We need to reset Data Layout to conform with the TargetMachine
|
|
M->setDataLayout("");
|
|
}
|
|
if (TranslatorOpts.getMaxVersion() != VersionNumber::MaximumVersion) {
|
|
if (TargetTriple.getTriple().empty())
|
|
TargetTriple.setTriple(DefaultTriple);
|
|
TargetTriple.setArch(TargetTriple.getArch(),
|
|
spirvVersionToSubArch(TranslatorOpts.getMaxVersion()));
|
|
M->setTargetTriple(TargetTriple);
|
|
}
|
|
|
|
// Translate the Module into SPIR-V
|
|
return llvm::SPIRVTranslateModule(M, Result, ErrMsg, AllowExtNames, {});
|
|
}
|
|
|
|
bool runSpirvBackend(Module *M, std::ostream *OS, std::string &ErrMsg,
|
|
const SPIRV::TranslatorOpts &TranslatorOpts) {
|
|
std::string Result;
|
|
bool Status = runSpirvBackend(M, Result, ErrMsg, TranslatorOpts);
|
|
if (Status && OS)
|
|
*OS << Result;
|
|
return Status;
|
|
}
|
|
#endif // LLVM_SPIRV_BACKEND_TARGET_PRESENT
|
|
|
|
VersionNumber getVersionFromTriple(const Triple &TT, SPIRVErrorLog &ErrorLog) {
|
|
switch (TT.getSubArch()) {
|
|
case Triple::SPIRVSubArch_v10:
|
|
return VersionNumber::SPIRV_1_0;
|
|
case Triple::SPIRVSubArch_v11:
|
|
return VersionNumber::SPIRV_1_1;
|
|
case Triple::SPIRVSubArch_v12:
|
|
return VersionNumber::SPIRV_1_2;
|
|
case Triple::SPIRVSubArch_v13:
|
|
return VersionNumber::SPIRV_1_3;
|
|
case Triple::SPIRVSubArch_v14:
|
|
return VersionNumber::SPIRV_1_4;
|
|
case Triple::SPIRVSubArch_v15:
|
|
return VersionNumber::SPIRV_1_5;
|
|
case Triple::SPIRVSubArch_v16:
|
|
return VersionNumber::SPIRV_1_6;
|
|
default:
|
|
ErrorLog.checkError(false, SPIRVEC_InvalidSubArch, TT.getArchName().str());
|
|
return VersionNumber::MaximumVersion;
|
|
}
|
|
}
|
|
|
|
bool runSpirvWriterPasses(Module *M, std::ostream *OS, std::string &ErrMsg,
|
|
const SPIRV::TranslatorOpts &Opts) {
|
|
// Perform the conversion and write the resulting SPIR-V if an ostream has
|
|
// been given; otherwise only perform regularization.
|
|
bool WriteSpirv = OS != nullptr;
|
|
|
|
std::unique_ptr<SPIRVModule> BM(SPIRVModule::createSPIRVModule(Opts));
|
|
if (!isValidLLVMModule(M, BM->getErrorLog()))
|
|
return false;
|
|
|
|
// If the module carries a SPIR-V triple with a version subarch, target
|
|
// that SPIR-V version.
|
|
Triple TargetTriple(M->getTargetTriple());
|
|
if ((TargetTriple.getArch() == Triple::spirv32 ||
|
|
TargetTriple.getArch() == Triple::spirv64) &&
|
|
TargetTriple.getSubArch() != Triple::NoSubArch) {
|
|
VersionNumber ModuleVer =
|
|
getVersionFromTriple(TargetTriple, BM->getErrorLog());
|
|
if (!BM->getErrorLog().checkError(ModuleVer <= Opts.getMaxVersion(),
|
|
SPIRVEC_TripleMaxVersionIncompatible))
|
|
return false;
|
|
BM->setMinSPIRVVersion(ModuleVer);
|
|
BM->setMaxSPIRVVersion(ModuleVer);
|
|
}
|
|
|
|
ModulePassManager PassMgr;
|
|
addPassesForSPIRV(PassMgr, Opts);
|
|
if (WriteSpirv) {
|
|
// Run loop simplify pass in order to avoid duplicate OpLoopMerge
|
|
// instruction. It can happen in case of continue operand in the loop.
|
|
if (hasLoopMetadata(M))
|
|
PassMgr.addPass(createModuleToFunctionPassAdaptor(LoopSimplifyPass()));
|
|
PassMgr.addPass(LLVMToSPIRVPass(BM.get()));
|
|
}
|
|
|
|
LoopAnalysisManager LAM;
|
|
CGSCCAnalysisManager CGAM;
|
|
FunctionAnalysisManager FAM;
|
|
ModuleAnalysisManager MAM;
|
|
|
|
PassBuilder PB;
|
|
PB.registerModuleAnalyses(MAM);
|
|
PB.registerCGSCCAnalyses(CGAM);
|
|
PB.registerFunctionAnalyses(FAM);
|
|
PB.registerLoopAnalyses(LAM);
|
|
MAM.registerPass([&] { return OCLTypeToSPIRVPass(); });
|
|
PB.crossRegisterProxies(LAM, FAM, CGAM, MAM);
|
|
|
|
PassMgr.run(*M, MAM);
|
|
|
|
if (BM->getError(ErrMsg) != SPIRVEC_Success)
|
|
return false;
|
|
|
|
if (WriteSpirv)
|
|
*OS << *BM;
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool llvm::writeSpirv(Module *M, std::ostream &OS, std::string &ErrMsg) {
|
|
SPIRV::TranslatorOpts DefaultOpts;
|
|
// To preserve old behavior of the translator, let's enable all extensions
|
|
// by default in this API
|
|
DefaultOpts.enableAllExtensions();
|
|
return llvm::writeSpirv(M, DefaultOpts, OS, ErrMsg);
|
|
}
|
|
|
|
bool llvm::writeSpirv(Module *M, const SPIRV::TranslatorOpts &Opts,
|
|
std::ostream &OS, std::string &ErrMsg) {
|
|
#if defined(LLVM_SPIRV_BACKEND_TARGET_PRESENT)
|
|
// Check if a user asks to convert LLVM to SPIR-V using the LLVM SPIR-V
|
|
// Backend target
|
|
if (Opts.getUseLLVMTarget()) {
|
|
#if !defined(_SPIRV_SUPPORT_TEXT_FMT)
|
|
return runSpirvBackend(M, &OS, ErrMsg, Opts);
|
|
#else
|
|
if (!SPIRVUseTextFormat)
|
|
return runSpirvBackend(M, &OS, ErrMsg, Opts);
|
|
|
|
// SPIR-V Backend always returns a binary: let's use temporary buffers to
|
|
// store the binary representation and convert it later into Translator's
|
|
// textual representation
|
|
std::string BinResult;
|
|
if (!runSpirvBackend(M, BinResult, ErrMsg, Opts))
|
|
return false;
|
|
std::istringstream IS(BinResult);
|
|
return SPIRV::convertSpirv(IS, OS, ErrMsg, false /*FromText*/,
|
|
true /*ToText*/);
|
|
#endif // _SPIRV_SUPPORT_TEXT_FMT
|
|
}
|
|
#endif // LLVM_SPIRV_BACKEND_TARGET_PRESENT
|
|
return runSpirvWriterPasses(M, &OS, ErrMsg, Opts);
|
|
}
|
|
|
|
bool llvm::regularizeLlvmForSpirv(Module *M, std::string &ErrMsg) {
|
|
SPIRV::TranslatorOpts DefaultOpts;
|
|
// To preserve old behavior of the translator, let's enable all extensions
|
|
// by default in this API
|
|
DefaultOpts.enableAllExtensions();
|
|
return llvm::regularizeLlvmForSpirv(M, ErrMsg, DefaultOpts);
|
|
}
|
|
|
|
bool llvm::regularizeLlvmForSpirv(Module *M, std::string &ErrMsg,
|
|
const SPIRV::TranslatorOpts &Opts) {
|
|
return runSpirvWriterPasses(M, nullptr, ErrMsg, Opts);
|
|
}
|