This PR is to address legacy issues with module analysis that currently uses a complicated and not so efficient approach to trace dependencies between SPIR-V id's via a duplicate tracker data structures and an explicitly built dependency graph. Even a quick performance check without any specialized benchmarks points to this part of the implementation as a biggest bottleneck. This PR specifically: * eliminates a need to build a dependency graph as a data structure, * updates the test suite (mainly, by fixing incorrect CHECK's referring to a hardcoded order of definitions, contradicting the spec requirement to allow certain definitions to go "in any order", see https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_logical_layout_of_a_module), * improves function pointers implementation so that it now passes EXPENSIVE_CHECKS (thus removing 3 XFAIL's in the test suite). As a quick sanity check of whether goals of the PR are achieved, we can measure time of translation for any big LLVM IR. While testing the PR in the local development environment, improvements of the x5 order have been observed. For example, the SYCL test case "group barrier" that is a ~1Mb binary IR input shows the following values of the naive performance metric that we can nevertheless apply here to roughly estimate effects of the PR. before the PR: ``` $ time llc -O0 -mtriple=spirv64v1.6-unknown-unknown _group_barrier_phi.bc -o 1 --filetype=obj real 3m33.241s user 3m14.688s sys 0m18.530s ``` after the PR ``` $ time llc -O0 -mtriple=spirv64v1.6-unknown-unknown _group_barrier_phi.bc -o 1 --filetype=obj real 0m42.031s user 0m38.834s sys 0m3.193s ``` Next work should probably address Duplicate Tracker further, as it needs analysis now from the perspective of what parts of it are not necessary now, after changing the approach to implementation of the module analysis step.
252 lines
10 KiB
C++
252 lines
10 KiB
C++
//===- SPIRVModuleAnalysis.h - analysis of global instrs & regs -*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// The analysis collects instructions that should be output at the module level
|
|
// and performs the global register numbering.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVMODULEANALYSIS_H
|
|
#define LLVM_LIB_TARGET_SPIRV_SPIRVMODULEANALYSIS_H
|
|
|
|
#include "MCTargetDesc/SPIRVBaseInfo.h"
|
|
#include "SPIRVGlobalRegistry.h"
|
|
#include "SPIRVUtils.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
|
|
namespace llvm {
|
|
class SPIRVSubtarget;
|
|
class MachineFunction;
|
|
class MachineModuleInfo;
|
|
|
|
namespace SPIRV {
|
|
// The enum contains logical module sections for the instruction collection.
|
|
enum ModuleSectionType {
|
|
// MB_Capabilities, MB_Extensions, MB_ExtInstImports, MB_MemoryModel,
|
|
MB_EntryPoints, // All OpEntryPoint instructions (if any).
|
|
// MB_ExecutionModes, MB_DebugSourceAndStrings,
|
|
MB_DebugNames, // All OpName and OpMemberName intrs.
|
|
MB_DebugStrings, // All OpString intrs.
|
|
MB_DebugModuleProcessed, // All OpModuleProcessed instructions.
|
|
MB_Annotations, // OpDecorate, OpMemberDecorate etc.
|
|
MB_TypeConstVars, // OpTypeXXX, OpConstantXXX, and global OpVariables.
|
|
MB_NonSemanticGlobalDI, // OpExtInst with e.g. DebugSource, DebugTypeBasic.
|
|
MB_ExtFuncDecls, // OpFunction etc. to declare for external funcs.
|
|
NUM_MODULE_SECTIONS // Total number of sections requiring basic blocks.
|
|
};
|
|
|
|
struct Requirements {
|
|
const bool IsSatisfiable;
|
|
const std::optional<Capability::Capability> Cap;
|
|
const ExtensionList Exts;
|
|
const VersionTuple MinVer; // 0 if no min version is required.
|
|
const VersionTuple MaxVer; // 0 if no max version is required.
|
|
|
|
Requirements(bool IsSatisfiable = false,
|
|
std::optional<Capability::Capability> Cap = {},
|
|
ExtensionList Exts = {}, VersionTuple MinVer = VersionTuple(),
|
|
VersionTuple MaxVer = VersionTuple())
|
|
: IsSatisfiable(IsSatisfiable), Cap(Cap), Exts(Exts), MinVer(MinVer),
|
|
MaxVer(MaxVer) {}
|
|
Requirements(Capability::Capability Cap) : Requirements(true, {Cap}) {}
|
|
};
|
|
|
|
struct RequirementHandler {
|
|
private:
|
|
CapabilityList MinimalCaps;
|
|
|
|
// AllCaps and AvailableCaps are related but different. AllCaps is a subset of
|
|
// AvailableCaps. AvailableCaps is the complete set of capabilities that are
|
|
// available to the current target. AllCaps is the set of capabilities that
|
|
// are required by the current module.
|
|
SmallSet<Capability::Capability, 8> AllCaps;
|
|
DenseSet<unsigned> AvailableCaps;
|
|
|
|
SmallSet<Extension::Extension, 4> AllExtensions;
|
|
VersionTuple MinVersion; // 0 if no min version is defined.
|
|
VersionTuple MaxVersion; // 0 if no max version is defined.
|
|
// Add capabilities to AllCaps, recursing through their implicitly declared
|
|
// capabilities too.
|
|
void recursiveAddCapabilities(const CapabilityList &ToPrune);
|
|
|
|
void initAvailableCapabilitiesForOpenCL(const SPIRVSubtarget &ST);
|
|
void initAvailableCapabilitiesForVulkan(const SPIRVSubtarget &ST);
|
|
|
|
public:
|
|
RequirementHandler() {}
|
|
void clear() {
|
|
MinimalCaps.clear();
|
|
AllCaps.clear();
|
|
AvailableCaps.clear();
|
|
AllExtensions.clear();
|
|
MinVersion = VersionTuple();
|
|
MaxVersion = VersionTuple();
|
|
}
|
|
const CapabilityList &getMinimalCapabilities() const { return MinimalCaps; }
|
|
const SmallSet<Extension::Extension, 4> &getExtensions() const {
|
|
return AllExtensions;
|
|
}
|
|
// Add a list of capabilities, ensuring AllCaps captures all the implicitly
|
|
// declared capabilities, and MinimalCaps has the minimal set of required
|
|
// capabilities (so all implicitly declared ones are removed).
|
|
void addCapabilities(const CapabilityList &ToAdd);
|
|
void addCapability(Capability::Capability ToAdd) { addCapabilities({ToAdd}); }
|
|
void addExtensions(const ExtensionList &ToAdd) {
|
|
AllExtensions.insert(ToAdd.begin(), ToAdd.end());
|
|
}
|
|
void addExtension(Extension::Extension ToAdd) { AllExtensions.insert(ToAdd); }
|
|
// Add the given requirements to the lists. If constraints conflict, or these
|
|
// requirements cannot be satisfied, then abort the compilation.
|
|
void addRequirements(const Requirements &Req);
|
|
// Get requirement and add it to the list.
|
|
void getAndAddRequirements(SPIRV::OperandCategory::OperandCategory Category,
|
|
uint32_t i, const SPIRVSubtarget &ST);
|
|
// Check if all the requirements can be satisfied for the given subtarget, and
|
|
// if not abort compilation.
|
|
void checkSatisfiable(const SPIRVSubtarget &ST) const;
|
|
void initAvailableCapabilities(const SPIRVSubtarget &ST);
|
|
// Add the given capabilities to available and all their implicitly defined
|
|
// capabilities too.
|
|
void addAvailableCaps(const CapabilityList &ToAdd);
|
|
bool isCapabilityAvailable(Capability::Capability Cap) const {
|
|
return AvailableCaps.contains(Cap);
|
|
}
|
|
|
|
// Remove capability ToRemove, but only if IfPresent is present.
|
|
void removeCapabilityIf(const Capability::Capability ToRemove,
|
|
const Capability::Capability IfPresent);
|
|
};
|
|
|
|
using InstrList = SmallVector<const MachineInstr *>;
|
|
// Maps a local register to the corresponding global alias.
|
|
using LocalToGlobalRegTable = std::map<Register, Register>;
|
|
using RegisterAliasMapTy =
|
|
std::map<const MachineFunction *, LocalToGlobalRegTable>;
|
|
|
|
// The struct contains results of the module analysis and methods
|
|
// to access them.
|
|
struct ModuleAnalysisInfo {
|
|
RequirementHandler Reqs;
|
|
MemoryModel::MemoryModel Mem;
|
|
AddressingModel::AddressingModel Addr;
|
|
SourceLanguage::SourceLanguage SrcLang;
|
|
unsigned SrcLangVersion;
|
|
StringSet<> SrcExt;
|
|
// Maps ExtInstSet to corresponding ID register.
|
|
DenseMap<unsigned, Register> ExtInstSetMap;
|
|
// Contains the list of all global OpVariables in the module.
|
|
SmallVector<const MachineInstr *, 4> GlobalVarList;
|
|
// Maps functions to corresponding function ID registers.
|
|
DenseMap<const Function *, Register> FuncMap;
|
|
// The set contains machine instructions which are necessary
|
|
// for correct MIR but will not be emitted in function bodies.
|
|
DenseSet<const MachineInstr *> InstrsToDelete;
|
|
// The table contains global aliases of local registers for each machine
|
|
// function. The aliases are used to substitute local registers during
|
|
// code emission.
|
|
RegisterAliasMapTy RegisterAliasTable;
|
|
// The counter holds the maximum ID we have in the module.
|
|
unsigned MaxID;
|
|
// The array contains lists of MIs for each module section.
|
|
InstrList MS[NUM_MODULE_SECTIONS];
|
|
// The table maps MBB number to SPIR-V unique ID register.
|
|
DenseMap<std::pair<const MachineFunction *, int>, Register> BBNumToRegMap;
|
|
|
|
Register getFuncReg(const Function *F) {
|
|
assert(F && "Function is null");
|
|
auto FuncPtrRegPair = FuncMap.find(F);
|
|
return FuncPtrRegPair == FuncMap.end() ? Register(0)
|
|
: FuncPtrRegPair->second;
|
|
}
|
|
Register getExtInstSetReg(unsigned SetNum) { return ExtInstSetMap[SetNum]; }
|
|
InstrList &getMSInstrs(unsigned MSType) { return MS[MSType]; }
|
|
void setSkipEmission(const MachineInstr *MI) { InstrsToDelete.insert(MI); }
|
|
bool getSkipEmission(const MachineInstr *MI) {
|
|
return InstrsToDelete.contains(MI);
|
|
}
|
|
void setRegisterAlias(const MachineFunction *MF, Register Reg,
|
|
Register AliasReg) {
|
|
RegisterAliasTable[MF][Reg] = AliasReg;
|
|
}
|
|
Register getRegisterAlias(const MachineFunction *MF, Register Reg) {
|
|
auto RI = RegisterAliasTable[MF].find(Reg);
|
|
if (RI == RegisterAliasTable[MF].end()) {
|
|
return Register(0);
|
|
}
|
|
return RegisterAliasTable[MF][Reg];
|
|
}
|
|
bool hasRegisterAlias(const MachineFunction *MF, Register Reg) {
|
|
return RegisterAliasTable.find(MF) != RegisterAliasTable.end() &&
|
|
RegisterAliasTable[MF].find(Reg) != RegisterAliasTable[MF].end();
|
|
}
|
|
unsigned getNextID() { return MaxID++; }
|
|
bool hasMBBRegister(const MachineBasicBlock &MBB) {
|
|
auto Key = std::make_pair(MBB.getParent(), MBB.getNumber());
|
|
return BBNumToRegMap.contains(Key);
|
|
}
|
|
// Convert MBB's number to corresponding ID register.
|
|
Register getOrCreateMBBRegister(const MachineBasicBlock &MBB) {
|
|
auto Key = std::make_pair(MBB.getParent(), MBB.getNumber());
|
|
auto It = BBNumToRegMap.find(Key);
|
|
if (It != BBNumToRegMap.end())
|
|
return It->second;
|
|
Register NewReg = Register::index2VirtReg(getNextID());
|
|
BBNumToRegMap[Key] = NewReg;
|
|
return NewReg;
|
|
}
|
|
};
|
|
} // namespace SPIRV
|
|
|
|
using InstrSignature = SmallVector<size_t>;
|
|
using InstrTraces = std::set<InstrSignature>;
|
|
using InstrGRegsMap = std::map<SmallVector<size_t>, unsigned>;
|
|
|
|
struct SPIRVModuleAnalysis : public ModulePass {
|
|
static char ID;
|
|
|
|
public:
|
|
SPIRVModuleAnalysis() : ModulePass(ID) {}
|
|
|
|
bool runOnModule(Module &M) override;
|
|
void getAnalysisUsage(AnalysisUsage &AU) const override;
|
|
static struct SPIRV::ModuleAnalysisInfo MAI;
|
|
|
|
private:
|
|
void setBaseInfo(const Module &M);
|
|
void collectFuncNames(MachineInstr &MI, const Function *F);
|
|
void processOtherInstrs(const Module &M);
|
|
void numberRegistersGlobally(const Module &M);
|
|
|
|
// analyze dependencies to collect module scope definitions
|
|
void collectDeclarations(const Module &M);
|
|
void visitDecl(const MachineRegisterInfo &MRI, InstrGRegsMap &SignatureToGReg,
|
|
std::map<const Value *, unsigned> &GlobalToGReg,
|
|
const MachineFunction *MF, const MachineInstr &MI);
|
|
Register handleVariable(const MachineFunction *MF, const MachineInstr &MI,
|
|
std::map<const Value *, unsigned> &GlobalToGReg);
|
|
Register handleTypeDeclOrConstant(const MachineInstr &MI,
|
|
InstrGRegsMap &SignatureToGReg);
|
|
Register
|
|
handleFunctionOrParameter(const MachineFunction *MF, const MachineInstr &MI,
|
|
std::map<const Value *, unsigned> &GlobalToGReg,
|
|
bool &IsFunDef);
|
|
void visitFunPtrUse(Register OpReg, InstrGRegsMap &SignatureToGReg,
|
|
std::map<const Value *, unsigned> &GlobalToGReg,
|
|
const MachineFunction *MF, const MachineInstr &MI);
|
|
bool isDeclSection(const MachineRegisterInfo &MRI, const MachineInstr &MI);
|
|
|
|
const SPIRVSubtarget *ST;
|
|
SPIRVGlobalRegistry *GR;
|
|
const SPIRVInstrInfo *TII;
|
|
MachineModuleInfo *MMI;
|
|
};
|
|
} // namespace llvm
|
|
#endif // LLVM_LIB_TARGET_SPIRV_SPIRVMODULEANALYSIS_H
|