386 lines
14 KiB
C++
386 lines
14 KiB
C++
//===- PreprocessMetadata.cpp - - C++ -*-===//
|
|
//
|
|
// The LLVM/SPIRV Translator
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
// Copyright (c) 2014 Advanced Micro Devices, Inc. All rights reserved.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the "Software"),
|
|
// to deal with the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimers.
|
|
// Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimers in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
// Neither the names of Advanced Micro Devices, Inc., nor the names of its
|
|
// contributors may be used to endorse or promote products derived from this
|
|
// Software without specific prior written permission.
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH
|
|
// THE SOFTWARE.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements preprocessing of LLVM IR metadata in order to perform
|
|
// further translation to SPIR-V.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "PreprocessMetadata.h"
|
|
#include "OCLUtil.h"
|
|
#include "SPIRVInternal.h"
|
|
#include "SPIRVMDBuilder.h"
|
|
#include "SPIRVMDWalker.h"
|
|
#include "VectorComputeUtil.h"
|
|
#include "libSPIRV/SPIRVDebug.h"
|
|
|
|
#include "llvm/IR/IRBuilder.h"
|
|
#include "llvm/IR/InstVisitor.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/TargetParser/Triple.h"
|
|
|
|
#define DEBUG_TYPE "clmdtospv"
|
|
|
|
using namespace llvm;
|
|
using namespace SPIRV;
|
|
using namespace OCLUtil;
|
|
|
|
namespace SPIRV {
|
|
|
|
cl::opt<bool> EraseOCLMD("spirv-erase-cl-md", cl::init(true),
|
|
cl::desc("Erase OpenCL metadata"));
|
|
|
|
char PreprocessMetadataLegacy::ID = 0;
|
|
|
|
bool PreprocessMetadataLegacy::runOnModule(Module &Module) {
|
|
return runPreprocessMetadata(Module);
|
|
}
|
|
|
|
llvm::PreservedAnalyses
|
|
PreprocessMetadataPass::run(llvm::Module &M, llvm::ModuleAnalysisManager &MAM) {
|
|
return runPreprocessMetadata(M) ? llvm::PreservedAnalyses::none()
|
|
: llvm::PreservedAnalyses::all();
|
|
}
|
|
|
|
bool PreprocessMetadataBase::runPreprocessMetadata(Module &Module) {
|
|
M = &Module;
|
|
Ctx = &M->getContext();
|
|
|
|
LLVM_DEBUG(dbgs() << "Enter PreprocessMetadata:\n");
|
|
visit(M);
|
|
LLVM_DEBUG(dbgs() << "After PreprocessMetadata:\n" << *M);
|
|
|
|
verifyRegularizationPass(*M, "PreprocessMetadata");
|
|
|
|
return true;
|
|
}
|
|
|
|
void PreprocessMetadataBase::preprocessCXXStructorList(
|
|
SPIRVMDBuilder::NamedMDWrapper &EM, GlobalVariable *V,
|
|
ExecutionMode EMode) {
|
|
auto *List = dyn_cast_or_null<ConstantArray>(V->getInitializer());
|
|
if (!List)
|
|
return;
|
|
|
|
for (Value *V : List->operands()) {
|
|
auto *Structor = cast<ConstantStruct>(V);
|
|
|
|
// Each entry in the list is a struct containing 3 members:
|
|
// (priority, function, data), with function being the entry point.
|
|
auto *Kernel = cast<Function>(Structor->getOperand(1));
|
|
|
|
EM.addOp().add(Kernel).add(EMode).done();
|
|
}
|
|
}
|
|
|
|
void PreprocessMetadataBase::visit(Module *M) {
|
|
SPIRVMDBuilder B(*M);
|
|
SPIRVMDWalker W(*M);
|
|
|
|
preprocessOCLMetadata(M, &B, &W);
|
|
preprocessVectorComputeMetadata(M, &B, &W);
|
|
|
|
// Create metadata representing (empty so far) list
|
|
// of OpExecutionMode instructions
|
|
auto EM = B.addNamedMD(kSPIRVMD::ExecutionMode); // !spirv.ExecutionMode = {}
|
|
|
|
// Process special variables in LLVM IR module.
|
|
if (auto *GV = M->getGlobalVariable("llvm.global_ctors"))
|
|
preprocessCXXStructorList(EM, GV, spv::ExecutionModeInitializer);
|
|
|
|
// Add execution modes for kernels. We take it from metadata attached to
|
|
// the kernel functions.
|
|
for (Function &Kernel : *M) {
|
|
if (Kernel.getCallingConv() != CallingConv::SPIR_KERNEL)
|
|
continue;
|
|
|
|
// Specifing execution modes for the Kernel and adding it to the list
|
|
// of ExecutionMode instructions.
|
|
|
|
// !{void (i32 addrspace(1)*)* @kernel, i32 17, i32 X, i32 Y, i32 Z}
|
|
// !{void (i32 addrspace(1)*)* @kernel, i32 18, i32 X, i32 Y, i32 Z}
|
|
// !{void (i32 addrspace(1)*)* @kernel, i32 max_work_group_size, i32 X,
|
|
// i32 Y, i32 Z}
|
|
std::pair<unsigned, const char *> WGSizeMDs[3] = {
|
|
{spv::ExecutionModeLocalSize, kSPIR2MD::WGSize},
|
|
{spv::ExecutionModeLocalSizeHint, kSPIR2MD::WGSizeHint},
|
|
{spv::ExecutionModeMaxWorkgroupSizeINTEL, kSPIR2MD::MaxWGSize},
|
|
};
|
|
|
|
for (auto &[ExMode, MDName] : WGSizeMDs) {
|
|
if (MDNode *WGMD = Kernel.getMetadata(MDName)) {
|
|
assert(WGMD->getNumOperands() >= 1 && WGMD->getNumOperands() <= 3 &&
|
|
"work-group metadata does not have between 1 and 3 operands.");
|
|
SmallVector<unsigned, 3> DecodedVals = decodeMDNode(WGMD);
|
|
EM.addOp()
|
|
.add(&Kernel)
|
|
.add(ExMode)
|
|
.add(DecodedVals[0])
|
|
.add(DecodedVals.size() >= 2 ? DecodedVals[1] : 1)
|
|
.add(DecodedVals.size() == 3 ? DecodedVals[2] : 1)
|
|
.done();
|
|
}
|
|
}
|
|
|
|
// !{void (i32 addrspace(1)*)* @kernel, i32 30, i32 hint}
|
|
if (MDNode *VecTypeHint = Kernel.getMetadata(kSPIR2MD::VecTyHint)) {
|
|
EM.addOp()
|
|
.add(&Kernel)
|
|
.add(spv::ExecutionModeVecTypeHint)
|
|
.add(transVecTypeHint(VecTypeHint))
|
|
.done();
|
|
}
|
|
|
|
// !{void (i32 addrspace(1)*)* @kernel, i32 35, i32 size}
|
|
if (MDNode *ReqdSubgroupSize = Kernel.getMetadata(kSPIR2MD::SubgroupSize)) {
|
|
// A primary named subgroup size is encoded as
|
|
// the metadata intel_reqd_sub_group_size with value -1.
|
|
auto Val = getMDOperandAsInt(ReqdSubgroupSize, 0);
|
|
if (Val == -1U)
|
|
EM.addOp()
|
|
.add(&Kernel)
|
|
.add(spv::internal::ExecutionModeNamedSubgroupSizeINTEL)
|
|
.add(/* PrimarySubgroupSizeINTEL = */ 0U)
|
|
.done();
|
|
EM.addOp()
|
|
.add(&Kernel)
|
|
.add(spv::ExecutionModeSubgroupSize)
|
|
.add(Val)
|
|
.done();
|
|
}
|
|
|
|
// !{void (i32 addrspace(1)*)* @kernel, i32 no_global_work_offset}
|
|
if (Kernel.getMetadata(kSPIR2MD::NoGlobalOffset)) {
|
|
EM.addOp().add(&Kernel).add(spv::ExecutionModeNoGlobalOffsetINTEL).done();
|
|
}
|
|
|
|
// !{void (i32 addrspace(1)*)* @kernel, i32 max_global_work_dim, i32 dim}
|
|
if (MDNode *MaxWorkDimINTEL = Kernel.getMetadata(kSPIR2MD::MaxWGDim)) {
|
|
EM.addOp()
|
|
.add(&Kernel)
|
|
.add(spv::ExecutionModeMaxWorkDimINTEL)
|
|
.add(getMDOperandAsInt(MaxWorkDimINTEL, 0))
|
|
.done();
|
|
}
|
|
|
|
// !{void (i32 addrspace(1)*)* @kernel, i32 num_simd_work_items, i32 num}
|
|
if (MDNode *NumSIMDWorkitemsINTEL = Kernel.getMetadata(kSPIR2MD::NumSIMD)) {
|
|
EM.addOp()
|
|
.add(&Kernel)
|
|
.add(spv::ExecutionModeNumSIMDWorkitemsINTEL)
|
|
.add(getMDOperandAsInt(NumSIMDWorkitemsINTEL, 0))
|
|
.done();
|
|
}
|
|
|
|
// !{void (i32 addrspace(1)*)* @kernel, i32 scheduler_target_fmax_mhz,
|
|
// i32 num}
|
|
if (MDNode *SchedulerTargetFmaxMhzINTEL =
|
|
Kernel.getMetadata(kSPIR2MD::FmaxMhz)) {
|
|
EM.addOp()
|
|
.add(&Kernel)
|
|
.add(spv::ExecutionModeSchedulerTargetFmaxMhzINTEL)
|
|
.add(getMDOperandAsInt(SchedulerTargetFmaxMhzINTEL, 0))
|
|
.done();
|
|
}
|
|
|
|
// !{void (i32 addrspace(1)*)* @kernel, i32 ip_interface, i32 interface}
|
|
if (MDNode *Interface =
|
|
Kernel.getMetadata(kSPIR2MD::IntelFPGAIPInterface)) {
|
|
std::set<std::string> InterfaceStrSet;
|
|
for (size_t I = 0; I != Interface->getNumOperands(); ++I)
|
|
InterfaceStrSet.insert(getMDOperandAsString(Interface, I).str());
|
|
|
|
// ip_interface metadata will either have Register Map metadata or
|
|
// Streaming metadata.
|
|
//
|
|
// Register Map mode metadata:
|
|
// Not 'WaitForDoneWrite' mode (to be mapped on '0' literal)
|
|
// !ip_interface !N
|
|
// !N = !{!"csr"}
|
|
// 'WaitForDoneWrite' mode (to be mapped on '1' literal)
|
|
// !ip_interface !N
|
|
// !N = !{!"csr", !"wait_for_done_write"}
|
|
if (InterfaceStrSet.find("csr") != InterfaceStrSet.end()) {
|
|
int32_t InterfaceMode = 0;
|
|
if (InterfaceStrSet.find("wait_for_done_write") !=
|
|
InterfaceStrSet.end())
|
|
InterfaceMode = 1;
|
|
EM.addOp()
|
|
.add(&Kernel)
|
|
.add(spv::ExecutionModeRegisterMapInterfaceINTEL)
|
|
.add(InterfaceMode)
|
|
.done();
|
|
}
|
|
|
|
// Streaming mode metadata be like:
|
|
// Not 'stall free' mode (to be mapped on '0' literal)
|
|
// !ip_interface !N
|
|
// !N = !{!"streaming"}
|
|
// 'stall free' mode (to be mapped on '1' literal)
|
|
// !ip_interface !N
|
|
// !N = !{!"streaming", !"stall_free_return"}
|
|
if (InterfaceStrSet.find("streaming") != InterfaceStrSet.end()) {
|
|
int32_t InterfaceMode = 0;
|
|
if (InterfaceStrSet.find("stall_free_return") != InterfaceStrSet.end())
|
|
InterfaceMode = 1;
|
|
EM.addOp()
|
|
.add(&Kernel)
|
|
.add(spv::ExecutionModeStreamingInterfaceINTEL)
|
|
.add(InterfaceMode)
|
|
.done();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PreprocessMetadataBase::preprocessOCLMetadata(Module *M, SPIRVMDBuilder *B,
|
|
SPIRVMDWalker *W) {
|
|
unsigned CLVer = getOCLVersion(M, true);
|
|
if (CLVer == 0)
|
|
return;
|
|
// Preprocess OpenCL-specific metadata
|
|
// !spirv.Source = !{!x}
|
|
// !{x} = !{i32 3, i32 102000}
|
|
B->addNamedMD(kSPIRVMD::Source)
|
|
.addOp()
|
|
.add(M->getNamedMetadata(kSPIR2MD::OCLCXXVer) &&
|
|
(CLVer == kOCLVer::CLCXX10 || CLVer == kOCLVer::CLCXX2021)
|
|
? spv::SourceLanguageCPP_for_OpenCL
|
|
: spv::SourceLanguageOpenCL_C)
|
|
.add(CLVer)
|
|
.done();
|
|
if (EraseOCLMD)
|
|
B->eraseNamedMD(kSPIR2MD::OCLVer)
|
|
.eraseNamedMD(kSPIR2MD::SPIRVer)
|
|
.eraseNamedMD(kSPIR2MD::OCLCXXVer);
|
|
|
|
// !spirv.MemoryModel = !{!x}
|
|
// !{x} = !{i32 1, i32 2}
|
|
Triple TT(M->getTargetTriple());
|
|
assert(isSupportedTriple(TT) && "Invalid triple");
|
|
B->addNamedMD(kSPIRVMD::MemoryModel)
|
|
.addOp()
|
|
.add(TT.isArch32Bit() ? spv::AddressingModelPhysical32
|
|
: spv::AddressingModelPhysical64)
|
|
.add(spv::MemoryModelOpenCL)
|
|
.done();
|
|
|
|
// Add source extensions
|
|
// !spirv.SourceExtension = !{!x, !y, ...}
|
|
// !x = {!"cl_khr_..."}
|
|
// !y = {!"cl_khr_..."}
|
|
auto Exts = getNamedMDAsStringSet(M, kSPIR2MD::Extensions);
|
|
if (!Exts.empty()) {
|
|
auto N = B->addNamedMD(kSPIRVMD::SourceExtension);
|
|
for (auto &I : Exts)
|
|
N.addOp().add(I).done();
|
|
}
|
|
if (EraseOCLMD)
|
|
B->eraseNamedMD(kSPIR2MD::Extensions).eraseNamedMD(kSPIR2MD::OptFeatures);
|
|
|
|
if (EraseOCLMD)
|
|
B->eraseNamedMD(kSPIR2MD::FPContract);
|
|
}
|
|
|
|
void PreprocessMetadataBase::preprocessVectorComputeMetadata(Module *M,
|
|
SPIRVMDBuilder *B,
|
|
SPIRVMDWalker *W) {
|
|
using namespace VectorComputeUtil;
|
|
|
|
auto EM = B->addNamedMD(kSPIRVMD::ExecutionMode);
|
|
|
|
for (auto &F : *M) {
|
|
if (F.getCallingConv() != CallingConv::SPIR_KERNEL)
|
|
continue;
|
|
|
|
// Add VC float control execution modes
|
|
// RoundMode and FloatMode are always same for all types in VC
|
|
// While Denorm could be different for double, float and half
|
|
auto Attrs = F.getAttributes();
|
|
if (Attrs.hasFnAttr(kVCMetadata::VCFloatControl)) {
|
|
SPIRVWord Mode = 0;
|
|
Attrs.getFnAttr(kVCMetadata::VCFloatControl)
|
|
.getValueAsString()
|
|
.getAsInteger(0, Mode);
|
|
spv::ExecutionMode ExecRoundMode =
|
|
FPRoundingModeExecModeMap::map(getFPRoundingMode(Mode));
|
|
spv::ExecutionMode ExecFloatMode =
|
|
FPOperationModeExecModeMap::map(getFPOperationMode(Mode));
|
|
VCFloatTypeSizeMap::foreach ([&](VCFloatType FloatType,
|
|
unsigned TargetWidth) {
|
|
EM.addOp().add(&F).add(ExecRoundMode).add(TargetWidth).done();
|
|
EM.addOp().add(&F).add(ExecFloatMode).add(TargetWidth).done();
|
|
EM.addOp()
|
|
.add(&F)
|
|
.add(FPDenormModeExecModeMap::map(getFPDenormMode(Mode, FloatType)))
|
|
.add(TargetWidth)
|
|
.done();
|
|
});
|
|
}
|
|
if (Attrs.hasFnAttr(kVCMetadata::VCSLMSize)) {
|
|
SPIRVWord SLMSize = 0;
|
|
Attrs.getFnAttr(kVCMetadata::VCSLMSize)
|
|
.getValueAsString()
|
|
.getAsInteger(0, SLMSize);
|
|
EM.addOp()
|
|
.add(&F)
|
|
.add(spv::ExecutionModeSharedLocalMemorySizeINTEL)
|
|
.add(SLMSize)
|
|
.done();
|
|
}
|
|
|
|
if (Attrs.hasFnAttr(kVCMetadata::VCNamedBarrierCount)) {
|
|
SPIRVWord NBarrierCnt = 0;
|
|
Attrs.getFnAttr(kVCMetadata::VCNamedBarrierCount)
|
|
.getValueAsString()
|
|
.getAsInteger(0, NBarrierCnt);
|
|
EM.addOp()
|
|
.add(&F)
|
|
.add(spv::ExecutionModeNamedBarrierCountINTEL)
|
|
.add(NBarrierCnt)
|
|
.done();
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace SPIRV
|
|
|
|
INITIALIZE_PASS(PreprocessMetadataLegacy, "preprocess-metadata",
|
|
"Transform LLVM IR metadata to SPIR-V metadata format", false,
|
|
false)
|
|
|
|
ModulePass *llvm::createPreprocessMetadataLegacy() {
|
|
return new PreprocessMetadataLegacy();
|
|
}
|