mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-02-17 20:52:36 +01:00
245 lines
8.5 KiB
C++
245 lines
8.5 KiB
C++
// Copyright 2025 The Khronos Group Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Implementation of generating multitarget modules according to the
|
|
// *SPV_INTEL_function_variants* extension
|
|
//
|
|
// Multitarget module is generated by linking separate modules: a base module
|
|
// and variant modules containing device-specific variants of the functions in
|
|
// the base module. The behavior is controlled by Comma-Separated Values (CSV)
|
|
// files passed to the following flags:
|
|
// --fnvar-targets: Required columns:
|
|
// module - module file name
|
|
// target - device target ISA value
|
|
// features - feature values for the target separated by '/' (FEAT_SEP)
|
|
// --fnvar-architectures: Required columns:
|
|
// module - module file name
|
|
// category - device category value
|
|
// family - device family value
|
|
// op - opcode of the comparison instruction
|
|
// architecture - device architecture
|
|
// The values (except module) are decimal strings with their meaning defined in
|
|
// the 'targets registry' as described in the extension spec. The decimal
|
|
// strings may only encode unsigned 32-bit integers (characters 0-9), possibly
|
|
// with leading zeros.
|
|
//
|
|
// In addition, --fnvar-capabilities generates OpSpecConstantCapabilitiesINTEL
|
|
// for each module with operands corresponding to the module's capabilities.
|
|
//
|
|
// Each line in the targets/architectures CSV file defines one
|
|
// OpSpecConstant<Target/Architecture>INTEL instruction, the columns correspond
|
|
// to the operands of these instructions. One module can have multiple lines, in
|
|
// which case they are combined into a single boolean spec constant using
|
|
// OpSpecConstantOp and OpLogicalOr (except when category and family in the
|
|
// architectures CSV are the same, then the lines are combined with
|
|
// OpLogicalAnd). For example, the following architectures CSV
|
|
//
|
|
// module,category,family,op,architecture
|
|
// foo.spv,1,7,174,1
|
|
// foo.spv,1,7,178,3
|
|
// foo.spv,1,8,170,1
|
|
//
|
|
// is combined as follows:
|
|
//
|
|
// %53 = OpSpecConstantArchitectureINTEL %bool 1 7 174 1
|
|
// %54 = OpSpecConstantArchitectureINTEL %bool 1 7 178 3
|
|
// %55 = OpSpecConstantArchitectureINTEL %bool 1 8 170 1
|
|
// %56 = OpSpecConstantOp %bool LogicalAnd %53 %54
|
|
// %foo_spv = OpSpecConstantOp %bool LogicalOr %55 %56
|
|
//
|
|
// The %foo_spv is annotated with OpName "foo.spv" (the module's name) which
|
|
// serves as an identifier to find the constant later. We cannot use IDs for it
|
|
// because the IDs get shifted during linking.
|
|
//
|
|
// The first module passed to `spirv-link` is considered the 'base' module. For
|
|
// example, if base module defines functions 'foo' and 'bar' and the other
|
|
// modules define only 'foo', only the 'foo' is treated as a function variant
|
|
// guarded by spec constants. The 'bar' function will be untouched and therefore
|
|
// present for all variants. The function variants are matched by name, and
|
|
// therefore they must either have an entry point, or an Export linkage
|
|
// attribute.
|
|
|
|
#ifndef FNVAR_H
|
|
#define FNVAR_H
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "source/opt/ir_context.h"
|
|
#include "source/opt/module.h"
|
|
#include "spirv-tools/linker.hpp"
|
|
|
|
namespace spvtools {
|
|
|
|
using opt::IRContext;
|
|
using opt::Module;
|
|
|
|
// Map of instruction hash -> which variants are using the instruction (denoted
|
|
// by the index to the variants vector)
|
|
using FnVarUsage = std::unordered_map<size_t, std::vector<size_t>>;
|
|
|
|
// Map of base function call ID -> variant functions corresponding to the
|
|
// called function (along with the variant name)
|
|
using BaseFnCalls =
|
|
std::map<uint32_t,
|
|
std::vector<std::pair<std::string, const opt::Function*>>>;
|
|
|
|
constexpr char FNVAR_EXT_NAME[] = "SPV_INTEL_function_variants";
|
|
constexpr uint32_t FNVAR_REGISTRY_VERSION = 0;
|
|
constexpr char FEAT_SEP = '/';
|
|
|
|
struct FnVarArchDef {
|
|
uint32_t category;
|
|
uint32_t family;
|
|
uint32_t op;
|
|
uint32_t architecture;
|
|
};
|
|
|
|
struct FnVarTargetDef {
|
|
uint32_t target;
|
|
std::vector<uint32_t> features;
|
|
};
|
|
|
|
// Definition of a variant
|
|
//
|
|
// Stores architecture and target definitions inferred from lines in the CSV
|
|
// files for a single module (as well as a pointer to the Module).
|
|
class VariantDef {
|
|
public:
|
|
VariantDef(bool isbase, std::string nm, Module* mod)
|
|
: is_base(isbase), name(nm), module(mod) {}
|
|
|
|
bool IsBase() const { return this->is_base; }
|
|
std::string GetName() const { return this->name; }
|
|
Module* GetModule() const { return this->module; }
|
|
|
|
void AddArchDef(uint32_t category, uint32_t family, uint32_t op,
|
|
uint32_t architecture) {
|
|
FnVarArchDef arch_def;
|
|
arch_def.category = category;
|
|
arch_def.family = family;
|
|
arch_def.op = op;
|
|
arch_def.architecture = architecture;
|
|
this->arch_defs.push_back(arch_def);
|
|
}
|
|
const std::vector<FnVarArchDef>& GetArchDefs() const {
|
|
return this->arch_defs;
|
|
}
|
|
|
|
void AddTgtDef(uint32_t target, std::vector<uint32_t> features) {
|
|
FnVarTargetDef tgt_def;
|
|
tgt_def.target = target;
|
|
tgt_def.features = features;
|
|
this->tgt_defs.push_back(tgt_def);
|
|
}
|
|
const std::vector<FnVarTargetDef>& GetTgtDefs() const {
|
|
return this->tgt_defs;
|
|
}
|
|
|
|
void InferCapabilities() {
|
|
for (const auto& cap_inst : module->capabilities()) {
|
|
capabilities.insert(spv::Capability(cap_inst.GetOperand(0).words[0]));
|
|
}
|
|
}
|
|
const std::set<spv::Capability>& GetCapabilities() const {
|
|
return this->capabilities;
|
|
}
|
|
|
|
private:
|
|
bool is_base;
|
|
std::string name;
|
|
Module* module;
|
|
std::vector<FnVarTargetDef> tgt_defs;
|
|
std::vector<FnVarArchDef> arch_defs;
|
|
std::set<spv::Capability> capabilities;
|
|
};
|
|
|
|
// Collection of VariantDef instances
|
|
//
|
|
// Apart from being a wrapper around a vector of VariantDef instances, it
|
|
// defines the main API for generating SPV_INTEL_function_variants instructions
|
|
// based on the CSV files.
|
|
class VariantDefs {
|
|
public:
|
|
// Returns last error message.
|
|
std::string GetErr() { return err_.str(); }
|
|
|
|
// Processes CSV files passed to the CLI and populate _variants.
|
|
//
|
|
// Returns true on success, false on error.
|
|
bool ProcessFnVar(const LinkerOptions& options,
|
|
const std::vector<Module*>& modules);
|
|
|
|
// Analyses each variant def module and generates those instructions that are
|
|
// module-specific, ie., not requiring knowledge from other modules.
|
|
//
|
|
// Returns true on success, false on error.
|
|
bool ProcessVariantDefs();
|
|
|
|
// Generates basic instructions required for this extension to work.
|
|
void GenerateHeader(IRContext* linked_context);
|
|
|
|
// Generates instructions from this extension that result from combining
|
|
// several variant def modules.
|
|
void CombineVariantInstructions(IRContext* linked_context);
|
|
|
|
private:
|
|
// Adds a boolean type to every module if there is none.
|
|
//
|
|
// These are necessary for spec constants.
|
|
void EnsureBoolType();
|
|
|
|
// Collects which combinable instructions are defined in which modules
|
|
void CollectVarInsts();
|
|
|
|
// Generates OpSpecConstant<Target/Architecture/Capabilities>INTEL and
|
|
// combines them as necessary. Also converts entry points to conditional ones
|
|
// and decorates module-specific instructions with ConditionalINTEL.
|
|
//
|
|
// Returns true on success, false on error.
|
|
bool GenerateFnVarConstants();
|
|
|
|
// Determines which functions in the base module are called by which function
|
|
// variants.
|
|
void CollectBaseFnCalls();
|
|
|
|
// Combines OpFunctionCall instructions collected with CollectBaseFnCalls()
|
|
// using conditional copy.
|
|
void CombineBaseFnCalls(IRContext* linked_context);
|
|
|
|
// Decorates instructions shared between modules with ConditionalINTEL or
|
|
// generates conditional capabilities and extensions, depending on which
|
|
// variants are used by each.
|
|
void CombineInstructions(IRContext* linked_context);
|
|
|
|
// Accumulates all errors encountered during processing.
|
|
std::stringstream err_;
|
|
|
|
// Collection of VariantDef instances
|
|
std::vector<VariantDef> variant_defs_;
|
|
|
|
// Used for combining OpFunctionCall instructions
|
|
BaseFnCalls base_fn_calls_;
|
|
|
|
// Used for determining which function variant uses which (applicable)
|
|
// instruction
|
|
FnVarUsage fnvar_usage_;
|
|
};
|
|
|
|
} // namespace spvtools
|
|
|
|
#endif // FNVAR_H
|