Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2023-07-30 16:28:44 -07:00
parent d15c26db2a
commit efb7f66a0e
30 changed files with 806 additions and 159 deletions

View File

@@ -1 +1 @@
"v2023.3", "SPIRV-Tools v2023.3 v2022.4-269-g34399abb"
"v2023.4", "SPIRV-Tools v2023.4 v2022.4-291-ga913df4a"

File diff suppressed because one or more lines are too long

View File

@@ -37,6 +37,7 @@ kSPV_INTEL_debug_module,
kSPV_INTEL_device_side_avc_motion_estimation,
kSPV_INTEL_float_controls2,
kSPV_INTEL_fp_fast_math_mode,
kSPV_INTEL_fp_max_error,
kSPV_INTEL_fpga_argument_interfaces,
kSPV_INTEL_fpga_buffer_location,
kSPV_INTEL_fpga_cluster_attributes,

View File

@@ -27,6 +27,7 @@ static const spv::Capability pygen_variable_caps_FPGALatencyControlINTEL[] = {sp
static const spv::Capability pygen_variable_caps_FPGALoopControlsINTEL[] = {spv::Capability::FPGALoopControlsINTEL};
static const spv::Capability pygen_variable_caps_FPGAMemoryAccessesINTEL[] = {spv::Capability::FPGAMemoryAccessesINTEL};
static const spv::Capability pygen_variable_caps_FPGAMemoryAttributesINTEL[] = {spv::Capability::FPGAMemoryAttributesINTEL};
static const spv::Capability pygen_variable_caps_FPMaxErrorINTEL[] = {spv::Capability::FPMaxErrorINTEL};
static const spv::Capability pygen_variable_caps_FragmentBarycentricNVFragmentBarycentricKHR[] = {spv::Capability::FragmentBarycentricNV, spv::Capability::FragmentBarycentricKHR};
static const spv::Capability pygen_variable_caps_FragmentDensityEXTShadingRateNV[] = {spv::Capability::FragmentDensityEXT, spv::Capability::ShadingRateNV};
static const spv::Capability pygen_variable_caps_FragmentFullyCoveredEXT[] = {spv::Capability::FragmentFullyCoveredEXT};
@@ -169,6 +170,7 @@ static const spvtools::Extension pygen_variable_exts_SPV_INTEL_debug_module[] =
static const spvtools::Extension pygen_variable_exts_SPV_INTEL_device_side_avc_motion_estimation[] = {spvtools::Extension::kSPV_INTEL_device_side_avc_motion_estimation};
static const spvtools::Extension pygen_variable_exts_SPV_INTEL_float_controls2[] = {spvtools::Extension::kSPV_INTEL_float_controls2};
static const spvtools::Extension pygen_variable_exts_SPV_INTEL_fp_fast_math_mode[] = {spvtools::Extension::kSPV_INTEL_fp_fast_math_mode};
static const spvtools::Extension pygen_variable_exts_SPV_INTEL_fp_max_error[] = {spvtools::Extension::kSPV_INTEL_fp_max_error};
static const spvtools::Extension pygen_variable_exts_SPV_INTEL_fpga_argument_interfaces[] = {spvtools::Extension::kSPV_INTEL_fpga_argument_interfaces};
static const spvtools::Extension pygen_variable_exts_SPV_INTEL_fpga_buffer_location[] = {spvtools::Extension::kSPV_INTEL_fpga_buffer_location};
static const spvtools::Extension pygen_variable_exts_SPV_INTEL_fpga_cluster_attributes[] = {spvtools::Extension::kSPV_INTEL_fpga_cluster_attributes};
@@ -855,6 +857,7 @@ static const spv_operand_desc_t pygen_variable_DecorationEntries[] = {
{"SingleElementVectorINTEL", 6085, 1, pygen_variable_caps_VectorComputeINTEL, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"VectorComputeCallableFunctionINTEL", 6087, 1, pygen_variable_caps_VectorComputeINTEL, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"MediaBlockIOINTEL", 6140, 1, pygen_variable_caps_VectorComputeINTEL, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"FPMaxErrorDecorationINTEL", 6170, 1, pygen_variable_caps_FPMaxErrorINTEL, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_FLOAT}, 0xffffffffu, 0xffffffffu},
{"LatencyControlLabelINTEL", 6172, 1, pygen_variable_caps_FPGALatencyControlINTEL, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, 0xffffffffu, 0xffffffffu},
{"LatencyControlConstraintINTEL", 6173, 1, pygen_variable_caps_FPGALatencyControlINTEL, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER}, 0xffffffffu, 0xffffffffu},
{"ConduitKernelArgumentINTEL", 6175, 1, pygen_variable_caps_FPGAArgumentInterfacesINTEL, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
@@ -1272,6 +1275,7 @@ static const spv_operand_desc_t pygen_variable_CapabilityEntries[] = {
{"BFloat16ConversionINTEL", 6115, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_bfloat16_conversion, {}, 0xffffffffu, 0xffffffffu},
{"SplitBarrierINTEL", 6141, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_split_barrier, {}, 0xffffffffu, 0xffffffffu},
{"FPGAKernelAttributesv2INTEL", 6161, 1, pygen_variable_caps_FPGAKernelAttributesINTEL, 1, pygen_variable_exts_SPV_INTEL_kernel_attributes, {}, 0xffffffffu, 0xffffffffu},
{"FPMaxErrorINTEL", 6169, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_fp_max_error, {}, 0xffffffffu, 0xffffffffu},
{"FPGALatencyControlINTEL", 6171, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_fpga_latency_control, {}, 0xffffffffu, 0xffffffffu},
{"FPGAArgumentInterfacesINTEL", 6174, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_fpga_argument_interfaces, {}, 0xffffffffu, 0xffffffffu},
{"GroupUniformArithmeticKHR", 6400, 0, nullptr, 1, pygen_variable_exts_SPV_KHR_uniform_group_instructions, {}, 0xffffffffu, 0xffffffffu}
@@ -1299,12 +1303,12 @@ static const spv_operand_desc_t pygen_variable_PackedVectorFormatEntries[] = {
};
static const spv_operand_desc_t pygen_variable_CooperativeMatrixOperandsEntries[] = {
{"None", 0x0000, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"MatrixASignedComponents", 0x0001, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"MatrixBSignedComponents", 0x0002, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"MatrixCSignedComponents", 0x0004, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"MatrixResultSignedComponents", 0x0008, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"SaturatingAccumulation", 0x0010, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu}
{"NoneKHR", 0x0000, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"MatrixASignedComponentsKHR", 0x0001, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"MatrixBSignedComponentsKHR", 0x0002, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"MatrixCSignedComponentsKHR", 0x0004, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"MatrixResultSignedComponentsKHR", 0x0008, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"SaturatingAccumulationKHR", 0x0010, 1, pygen_variable_caps_CooperativeMatrixKHR, 0, nullptr, {}, 0xffffffffu, 0xffffffffu}
};
static const spv_operand_desc_t pygen_variable_CooperativeMatrixLayoutEntries[] = {

View File

@@ -143,6 +143,7 @@ typedef enum spv_operand_type_t {
// may be larger than 32, which would require such a typed literal value to
// occupy multiple SPIR-V words.
SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER,
SPV_OPERAND_TYPE_LITERAL_FLOAT, // Always 32-bit float.
// Set 3: The literal string operand type.
SPV_OPERAND_TYPE_LITERAL_STRING,

View File

@@ -981,6 +981,17 @@ Optimizer::PassToken CreateRemoveDontInlinePass();
// object, currently the pass would remove accesschain pointer argument passed
// to the function
Optimizer::PassToken CreateFixFuncCallArgumentsPass();
// Creates a trim-capabilities pass.
// This pass removes unused capabilities for a given module, and if possible,
// associated extensions.
// See `trim_capabilities.h` for the list of supported capabilities.
//
// If the module contains unsupported capabilities, this pass will ignore them.
// This should be fine in most cases, but could yield to incorrect results if
// the unknown capability interacts with one of the trimmed capabilities.
Optimizer::PassToken CreateTrimCapabilitiesPass();
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_

View File

@@ -546,6 +546,13 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
parsed_operand.number_bit_width = 32;
break;
case SPV_OPERAND_TYPE_LITERAL_FLOAT:
// These are regular single-word literal float operands.
parsed_operand.type = SPV_OPERAND_TYPE_LITERAL_FLOAT;
parsed_operand.number_kind = SPV_NUMBER_FLOATING;
parsed_operand.number_bit_width = 32;
break;
case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:
case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER:
parsed_operand.type = SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER;

View File

@@ -2038,6 +2038,10 @@ spv_number_kind_t Differ::GetNumberKind(const IdInstructions& id_to,
// Always unsigned integers.
*number_bit_width = 32;
return SPV_NUMBER_UNSIGNED_INT;
case SPV_OPERAND_TYPE_LITERAL_FLOAT:
// Always float.
*number_bit_width = 32;
return SPV_NUMBER_FLOATING;
case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:
case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER:
switch (inst.opcode()) {

View File

@@ -357,7 +357,8 @@ void InstructionDisassembler::EmitOperand(const spv_parsed_instruction_t& inst,
stream_ << opcode_desc->name;
} break;
case SPV_OPERAND_TYPE_LITERAL_INTEGER:
case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:
case SPV_OPERAND_TYPE_LITERAL_FLOAT: {
SetRed();
EmitNumericLiteral(&stream_, inst, operand);
ResetColor();

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <functional>
@@ -203,6 +204,14 @@ class EnumSet {
}
}
// Creates a set initialized with the content of the range [begin; end[.
template <class InputIt>
EnumSet(InputIt begin, InputIt end) : EnumSet() {
for (; begin != end; ++begin) {
insert(*begin);
}
}
// Copies the EnumSet `other` into a new EnumSet.
EnumSet(const EnumSet& other)
: buckets_(other.buckets_), size_(other.size_) {}
@@ -255,6 +264,15 @@ class EnumSet {
// insertion.
iterator insert(const_iterator, T&& value) { return insert(value).first; }
// Inserts all the values in the range [`first`; `last[.
// Similar to `std::unordered_set::insert`.
template <class InputIt>
void insert(InputIt first, InputIt last) {
for (auto it = first; it != last; ++it) {
insert(*it);
}
}
// Removes the value `value` into the set.
// Similar to `std::unordered_set::erase`.
// Returns the number of erased elements.

View File

@@ -155,6 +155,7 @@ const char* spvOperandTypeStr(spv_operand_type_t type) {
case SPV_OPERAND_TYPE_LITERAL_INTEGER:
case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER:
case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER:
case SPV_OPERAND_TYPE_LITERAL_FLOAT:
return "literal number";
case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER:
return "possibly multi-word literal integer";
@@ -332,6 +333,7 @@ bool spvOperandIsConcrete(spv_operand_type_t type) {
}
switch (type) {
case SPV_OPERAND_TYPE_LITERAL_INTEGER:
case SPV_OPERAND_TYPE_LITERAL_FLOAT:
case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER:
case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER:
case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:

View File

@@ -438,6 +438,9 @@ std::vector<uint32_t> AggressiveDCEPass::GetLoadedVariablesFromFunctionCall(
const Instruction* inst) {
assert(inst->opcode() == spv::Op::OpFunctionCall);
std::vector<uint32_t> live_variables;
// NOTE: we should only be checking function call parameters here, not the
// function itself, however, `IsPtr` will trivially return false for
// OpFunction
inst->ForEachInId([this, &live_variables](const uint32_t* operand_id) {
if (!IsPtr(*operand_id)) return;
uint32_t var_id = GetVariableId(*operand_id);
@@ -995,6 +998,7 @@ void AggressiveDCEPass::InitExtensions() {
"SPV_KHR_uniform_group_instructions",
"SPV_KHR_fragment_shader_barycentric",
"SPV_NV_bindless_texture",
"SPV_EXT_shader_atomic_float_add",
});
}

View File

@@ -967,6 +967,11 @@ const analysis::Constant* FoldScalarFPDivide(
return FoldFPScalarDivideByZero(result_type, numerator, const_mgr);
}
uint32_t width = denominator->type()->AsFloat()->width();
if (width != 32 && width != 64) {
return nullptr;
}
const analysis::FloatConstant* denominator_float =
denominator->AsFloatConstant();
if (denominator_float && denominator->GetValueAsDouble() == -0.0) {

View File

@@ -63,6 +63,10 @@ bool ConvertToHalfPass::IsRelaxed(uint32_t id) {
void ConvertToHalfPass::AddRelaxed(uint32_t id) { relaxed_ids_set_.insert(id); }
bool ConvertToHalfPass::CanRelaxOpOperands(Instruction* inst) {
return image_ops_.count(inst->opcode()) == 0;
}
analysis::Type* ConvertToHalfPass::FloatScalarType(uint32_t width) {
analysis::Float float_ty(width);
return context()->get_type_mgr()->GetRegisteredType(&float_ty);
@@ -313,7 +317,8 @@ bool ConvertToHalfPass::CloseRelaxInst(Instruction* inst) {
relax = true;
get_def_use_mgr()->ForEachUser(inst, [&relax, this](Instruction* uinst) {
if (uinst->result_id() == 0 || !IsFloat(uinst, 32) ||
(!IsDecoratedRelaxed(uinst) && !IsRelaxed(uinst->result_id()))) {
(!IsDecoratedRelaxed(uinst) && !IsRelaxed(uinst->result_id())) ||
!CanRelaxOpOperands(uinst)) {
relax = false;
return;
}

View File

@@ -56,6 +56,9 @@ class ConvertToHalfPass : public Pass {
// Add |id| to the relaxed id set
void AddRelaxed(uint32_t id);
// Return true if the instruction's operands can be relaxed
bool CanRelaxOpOperands(Instruction* inst);
// Return type id for float with |width|
analysis::Type* FloatScalarType(uint32_t width);
@@ -133,13 +136,13 @@ class ConvertToHalfPass : public Pass {
// Set of 450 extension operations to be processed
std::unordered_set<uint32_t> target_ops_450_;
// Set of sample operations
// Set of all sample operations, including dref and non-dref operations
std::unordered_set<spv::Op, hasher> image_ops_;
// Set of dref sample operations
// Set of only dref sample operations
std::unordered_set<spv::Op, hasher> dref_image_ops_;
// Set of dref sample operations
// Set of operations that can be marked as relaxed
std::unordered_set<spv::Op, hasher> closure_ops_;
// Set of ids of all relaxed instructions

View File

@@ -25,27 +25,19 @@ namespace opt {
// Tracks features enabled by a module. The IRContext has a FeatureManager.
class FeatureManager {
public:
explicit FeatureManager(const AssemblyGrammar& grammar) : grammar_(grammar) {}
// Returns true if |ext| is an enabled extension in the module.
bool HasExtension(Extension ext) const { return extensions_.contains(ext); }
// Removes the given |extension| from the current FeatureManager.
void RemoveExtension(Extension extension);
// Returns true if |cap| is an enabled capability in the module.
bool HasCapability(spv::Capability cap) const {
return capabilities_.contains(cap);
}
// Removes the given |capability| from the current FeatureManager.
void RemoveCapability(spv::Capability capability);
// Returns the capabilities the module declares.
inline const CapabilitySet& GetCapabilities() const { return capabilities_; }
// Analyzes |module| and records enabled extensions and capabilities.
void Analyze(Module* module);
CapabilitySet* GetCapabilities() { return &capabilities_; }
const CapabilitySet* GetCapabilities() const { return &capabilities_; }
// Returns the extensions the module imports.
inline const ExtensionSet& GetExtensions() const { return extensions_; }
uint32_t GetExtInstImportId_GLSLstd450() const {
return extinst_importid_GLSLstd450_;
@@ -64,23 +56,34 @@ class FeatureManager {
return !(a == b);
}
// Adds the given |capability| and all implied capabilities into the current
// FeatureManager.
void AddCapability(spv::Capability capability);
private:
explicit FeatureManager(const AssemblyGrammar& grammar) : grammar_(grammar) {}
// Analyzes |module| and records enabled extensions and capabilities.
void Analyze(Module* module);
// Add the extension |ext| to the feature manager.
void AddExtension(Instruction* ext);
// Analyzes |module| and records imported external instruction sets.
void AddExtInstImportIds(Module* module);
private:
// Analyzes |module| and records enabled extensions.
void AddExtensions(Module* module);
// Removes the given |extension| from the current FeatureManager.
void RemoveExtension(Extension extension);
// Adds the given |capability| and all implied capabilities into the current
// FeatureManager.
void AddCapability(spv::Capability capability);
// Analyzes |module| and records enabled capabilities.
void AddCapabilities(Module* module);
// Removes the given |capability| from the current FeatureManager.
void RemoveCapability(spv::Capability capability);
// Analyzes |module| and records imported external instruction sets.
void AddExtInstImportIds(Module* module);
// Auxiliary object for querying SPIR-V grammar facts.
const AssemblyGrammar& grammar_;
@@ -100,6 +103,8 @@ class FeatureManager {
// Common NonSemanticShader100DebugInfo external instruction import ids,
// cached for performance.
uint32_t extinst_importid_Shader100DebugInfo_ = 0;
friend class IRContext;
};
} // namespace opt

View File

@@ -573,9 +573,9 @@ uint32_t GraphicsRobustAccessPass::GetGlslInsts() {
context()->module()->AddExtInstImport(std::move(import_inst));
module_status_.modified = true;
context()->AnalyzeDefUse(inst);
// Reanalyze the feature list, since we added an extended instruction
// set improt.
context()->get_feature_mgr()->Analyze(context()->module());
// Invalidates the feature manager, since we added an extended instruction
// set import.
context()->ResetFeatureManager();
}
}
return module_status_.glsl_insts_id;

View File

@@ -241,15 +241,7 @@ Pass::Status InstDebugPrintfPass::ProcessImpl() {
}
}
if (!non_sem_set_seen) {
for (auto c_itr = context()->module()->extension_begin();
c_itr != context()->module()->extension_end(); ++c_itr) {
const std::string ext_name = c_itr->GetInOperand(0).AsString();
if (ext_name == "SPV_KHR_non_semantic_info") {
context()->KillInst(&*c_itr);
break;
}
}
context()->get_feature_mgr()->RemoveExtension(kSPV_KHR_non_semantic_info);
context()->RemoveExtension(kSPV_KHR_non_semantic_info);
}
return Status::SuccessWithChange;
}

View File

@@ -220,6 +220,28 @@ Instruction* IRContext::KillInst(Instruction* inst) {
return next_instruction;
}
bool IRContext::KillInstructionIf(Module::inst_iterator begin,
Module::inst_iterator end,
std::function<bool(Instruction*)> condition) {
bool removed = false;
for (auto it = begin; it != end;) {
if (!condition(&*it)) {
++it;
continue;
}
removed = true;
// `it` is an iterator on an intrusive list. Next is invalidated on the
// current node when an instruction is killed. The iterator must be moved
// forward before deleting the node.
auto instruction = &*it;
++it;
KillInst(instruction);
}
return removed;
}
void IRContext::CollectNonSemanticTree(
Instruction* inst, std::unordered_set<Instruction*>* to_kill) {
if (!inst->HasResultId()) return;
@@ -251,6 +273,36 @@ bool IRContext::KillDef(uint32_t id) {
return false;
}
bool IRContext::RemoveCapability(spv::Capability capability) {
const bool removed = KillInstructionIf(
module()->capability_begin(), module()->capability_end(),
[capability](Instruction* inst) {
return static_cast<spv::Capability>(inst->GetSingleWordOperand(0)) ==
capability;
});
if (removed && feature_mgr_ != nullptr) {
feature_mgr_->RemoveCapability(capability);
}
return removed;
}
bool IRContext::RemoveExtension(Extension extension) {
const std::string_view extensionName = ExtensionToString(extension);
const bool removed = KillInstructionIf(
module()->extension_begin(), module()->extension_end(),
[&extensionName](Instruction* inst) {
return inst->GetOperand(0).AsString() == extensionName;
});
if (removed && feature_mgr_ != nullptr) {
feature_mgr_->RemoveExtension(extension);
}
return removed;
}
bool IRContext::ReplaceAllUsesWith(uint32_t before, uint32_t after) {
return ReplaceAllUsesWithPredicate(before, after,
[](Instruction*) { return true; });
@@ -718,7 +770,7 @@ void IRContext::AddCombinatorsForExtension(Instruction* extension) {
}
void IRContext::InitializeCombinators() {
for (auto capability : *get_feature_mgr()->GetCapabilities()) {
for (auto capability : get_feature_mgr()->GetCapabilities()) {
AddCombinatorsForCapability(uint32_t(capability));
}

View File

@@ -27,6 +27,7 @@
#include <vector>
#include "source/assembly_grammar.h"
#include "source/enum_string_mapping.h"
#include "source/opt/cfg.h"
#include "source/opt/constants.h"
#include "source/opt/debug_info_manager.h"
@@ -153,13 +154,19 @@ class IRContext {
inline IteratorRange<Module::inst_iterator> capabilities();
inline IteratorRange<Module::const_inst_iterator> capabilities() const;
// Iterators for extensions instructions contained in this module.
inline Module::inst_iterator extension_begin();
inline Module::inst_iterator extension_end();
inline IteratorRange<Module::inst_iterator> extensions();
inline IteratorRange<Module::const_inst_iterator> extensions() const;
// Iterators for types, constants and global variables instructions.
inline Module::inst_iterator types_values_begin();
inline Module::inst_iterator types_values_end();
inline IteratorRange<Module::inst_iterator> types_values();
inline IteratorRange<Module::const_inst_iterator> types_values() const;
// Iterators for extension instructions contained in this module.
// Iterators for ext_inst import instructions contained in this module.
inline Module::inst_iterator ext_inst_import_begin();
inline Module::inst_iterator ext_inst_import_end();
inline IteratorRange<Module::inst_iterator> ext_inst_imports();
@@ -204,12 +211,19 @@ class IRContext {
// Add |capability| to the module, if it is not already enabled.
inline void AddCapability(spv::Capability capability);
// Appends a capability instruction to this module.
inline void AddCapability(std::unique_ptr<Instruction>&& c);
// Removes instruction declaring `capability` from this module.
// Returns true if the capability was removed, false otherwise.
bool RemoveCapability(spv::Capability capability);
// Appends an extension instruction to this module.
inline void AddExtension(const std::string& ext_name);
inline void AddExtension(std::unique_ptr<Instruction>&& e);
// Removes instruction declaring `extension` from this module.
// Returns true if the extension was removed, false otherwise.
bool RemoveExtension(Extension extension);
// Appends an extended instruction set instruction to this module.
inline void AddExtInstImport(const std::string& name);
inline void AddExtInstImport(std::unique_ptr<Instruction>&& e);
@@ -422,6 +436,15 @@ class IRContext {
// instruction exists.
Instruction* KillInst(Instruction* inst);
// Deletes all the instruction in the range [`begin`; `end`[, for which the
// unary predicate `condition` returned true.
// Returns true if at least one instruction was removed, false otherwise.
//
// Pointer and iterator pointing to the deleted instructions become invalid.
// However other pointers and iterators are still valid.
bool KillInstructionIf(Module::inst_iterator begin, Module::inst_iterator end,
std::function<bool(Instruction*)> condition);
// Collects the non-semantic instruction tree that uses |inst|'s result id
// to be killed later.
void CollectNonSemanticTree(Instruction* inst,
@@ -772,7 +795,8 @@ class IRContext {
// Analyzes the features in the owned module. Builds the manager if required.
void AnalyzeFeatures() {
feature_mgr_ = MakeUnique<FeatureManager>(grammar_);
feature_mgr_ =
std::unique_ptr<FeatureManager>(new FeatureManager(grammar_));
feature_mgr_->Analyze(module());
}
@@ -964,6 +988,22 @@ IteratorRange<Module::const_inst_iterator> IRContext::capabilities() const {
return ((const Module*)module())->capabilities();
}
Module::inst_iterator IRContext::extension_begin() {
return module()->extension_begin();
}
Module::inst_iterator IRContext::extension_end() {
return module()->extension_end();
}
IteratorRange<Module::inst_iterator> IRContext::extensions() {
return module()->extensions();
}
IteratorRange<Module::const_inst_iterator> IRContext::extensions() const {
return ((const Module*)module())->extensions();
}
Module::inst_iterator IRContext::types_values_begin() {
return module()->types_values_begin();
}

View File

@@ -427,7 +427,7 @@ void LocalAccessChainConvertPass::InitExtensions() {
"SPV_EXT_shader_image_int64", "SPV_KHR_non_semantic_info",
"SPV_KHR_uniform_group_instructions",
"SPV_KHR_fragment_shader_barycentric", "SPV_KHR_vulkan_memory_model",
"SPV_NV_bindless_texture"});
"SPV_NV_bindless_texture", "SPV_EXT_shader_atomic_float_add"});
}
bool LocalAccessChainConvertPass::AnyIndexIsOutOfBounds(

View File

@@ -233,62 +233,61 @@ Pass::Status LocalSingleBlockLoadStoreElimPass::Process() {
void LocalSingleBlockLoadStoreElimPass::InitExtensions() {
extensions_allowlist_.clear();
extensions_allowlist_.insert({
"SPV_AMD_shader_explicit_vertex_parameter",
"SPV_AMD_shader_trinary_minmax",
"SPV_AMD_gcn_shader",
"SPV_KHR_shader_ballot",
"SPV_AMD_shader_ballot",
"SPV_AMD_gpu_shader_half_float",
"SPV_KHR_shader_draw_parameters",
"SPV_KHR_subgroup_vote",
"SPV_KHR_8bit_storage",
"SPV_KHR_16bit_storage",
"SPV_KHR_device_group",
"SPV_KHR_multiview",
"SPV_NVX_multiview_per_view_attributes",
"SPV_NV_viewport_array2",
"SPV_NV_stereo_view_rendering",
"SPV_NV_sample_mask_override_coverage",
"SPV_NV_geometry_shader_passthrough",
"SPV_AMD_texture_gather_bias_lod",
"SPV_KHR_storage_buffer_storage_class",
"SPV_KHR_variable_pointers",
"SPV_AMD_gpu_shader_int16",
"SPV_KHR_post_depth_coverage",
"SPV_KHR_shader_atomic_counter_ops",
"SPV_EXT_shader_stencil_export",
"SPV_EXT_shader_viewport_index_layer",
"SPV_AMD_shader_image_load_store_lod",
"SPV_AMD_shader_fragment_mask",
"SPV_EXT_fragment_fully_covered",
"SPV_AMD_gpu_shader_half_float_fetch",
"SPV_GOOGLE_decorate_string",
"SPV_GOOGLE_hlsl_functionality1",
"SPV_GOOGLE_user_type",
"SPV_NV_shader_subgroup_partitioned",
"SPV_EXT_demote_to_helper_invocation",
"SPV_EXT_descriptor_indexing",
"SPV_NV_fragment_shader_barycentric",
"SPV_NV_compute_shader_derivatives",
"SPV_NV_shader_image_footprint",
"SPV_NV_shading_rate",
"SPV_NV_mesh_shader",
"SPV_NV_ray_tracing",
"SPV_KHR_ray_tracing",
"SPV_KHR_ray_query",
"SPV_EXT_fragment_invocation_density",
"SPV_EXT_physical_storage_buffer",
"SPV_KHR_terminate_invocation",
"SPV_KHR_subgroup_uniform_control_flow",
"SPV_KHR_integer_dot_product",
"SPV_EXT_shader_image_int64",
"SPV_KHR_non_semantic_info",
"SPV_KHR_uniform_group_instructions",
"SPV_KHR_fragment_shader_barycentric",
"SPV_KHR_vulkan_memory_model",
"SPV_NV_bindless_texture",
});
extensions_allowlist_.insert({"SPV_AMD_shader_explicit_vertex_parameter",
"SPV_AMD_shader_trinary_minmax",
"SPV_AMD_gcn_shader",
"SPV_KHR_shader_ballot",
"SPV_AMD_shader_ballot",
"SPV_AMD_gpu_shader_half_float",
"SPV_KHR_shader_draw_parameters",
"SPV_KHR_subgroup_vote",
"SPV_KHR_8bit_storage",
"SPV_KHR_16bit_storage",
"SPV_KHR_device_group",
"SPV_KHR_multiview",
"SPV_NVX_multiview_per_view_attributes",
"SPV_NV_viewport_array2",
"SPV_NV_stereo_view_rendering",
"SPV_NV_sample_mask_override_coverage",
"SPV_NV_geometry_shader_passthrough",
"SPV_AMD_texture_gather_bias_lod",
"SPV_KHR_storage_buffer_storage_class",
"SPV_KHR_variable_pointers",
"SPV_AMD_gpu_shader_int16",
"SPV_KHR_post_depth_coverage",
"SPV_KHR_shader_atomic_counter_ops",
"SPV_EXT_shader_stencil_export",
"SPV_EXT_shader_viewport_index_layer",
"SPV_AMD_shader_image_load_store_lod",
"SPV_AMD_shader_fragment_mask",
"SPV_EXT_fragment_fully_covered",
"SPV_AMD_gpu_shader_half_float_fetch",
"SPV_GOOGLE_decorate_string",
"SPV_GOOGLE_hlsl_functionality1",
"SPV_GOOGLE_user_type",
"SPV_NV_shader_subgroup_partitioned",
"SPV_EXT_demote_to_helper_invocation",
"SPV_EXT_descriptor_indexing",
"SPV_NV_fragment_shader_barycentric",
"SPV_NV_compute_shader_derivatives",
"SPV_NV_shader_image_footprint",
"SPV_NV_shading_rate",
"SPV_NV_mesh_shader",
"SPV_NV_ray_tracing",
"SPV_KHR_ray_tracing",
"SPV_KHR_ray_query",
"SPV_EXT_fragment_invocation_density",
"SPV_EXT_physical_storage_buffer",
"SPV_KHR_terminate_invocation",
"SPV_KHR_subgroup_uniform_control_flow",
"SPV_KHR_integer_dot_product",
"SPV_EXT_shader_image_int64",
"SPV_KHR_non_semantic_info",
"SPV_KHR_uniform_group_instructions",
"SPV_KHR_fragment_shader_barycentric",
"SPV_KHR_vulkan_memory_model",
"SPV_NV_bindless_texture",
"SPV_EXT_shader_atomic_float_add"});
}
} // namespace opt

View File

@@ -86,59 +86,58 @@ Pass::Status LocalSingleStoreElimPass::Process() {
}
void LocalSingleStoreElimPass::InitExtensionAllowList() {
extensions_allowlist_.insert({
"SPV_AMD_shader_explicit_vertex_parameter",
"SPV_AMD_shader_trinary_minmax",
"SPV_AMD_gcn_shader",
"SPV_KHR_shader_ballot",
"SPV_AMD_shader_ballot",
"SPV_AMD_gpu_shader_half_float",
"SPV_KHR_shader_draw_parameters",
"SPV_KHR_subgroup_vote",
"SPV_KHR_8bit_storage",
"SPV_KHR_16bit_storage",
"SPV_KHR_device_group",
"SPV_KHR_multiview",
"SPV_NVX_multiview_per_view_attributes",
"SPV_NV_viewport_array2",
"SPV_NV_stereo_view_rendering",
"SPV_NV_sample_mask_override_coverage",
"SPV_NV_geometry_shader_passthrough",
"SPV_AMD_texture_gather_bias_lod",
"SPV_KHR_storage_buffer_storage_class",
"SPV_KHR_variable_pointers",
"SPV_AMD_gpu_shader_int16",
"SPV_KHR_post_depth_coverage",
"SPV_KHR_shader_atomic_counter_ops",
"SPV_EXT_shader_stencil_export",
"SPV_EXT_shader_viewport_index_layer",
"SPV_AMD_shader_image_load_store_lod",
"SPV_AMD_shader_fragment_mask",
"SPV_EXT_fragment_fully_covered",
"SPV_AMD_gpu_shader_half_float_fetch",
"SPV_GOOGLE_decorate_string",
"SPV_GOOGLE_hlsl_functionality1",
"SPV_NV_shader_subgroup_partitioned",
"SPV_EXT_descriptor_indexing",
"SPV_NV_fragment_shader_barycentric",
"SPV_NV_compute_shader_derivatives",
"SPV_NV_shader_image_footprint",
"SPV_NV_shading_rate",
"SPV_NV_mesh_shader",
"SPV_NV_ray_tracing",
"SPV_KHR_ray_query",
"SPV_EXT_fragment_invocation_density",
"SPV_EXT_physical_storage_buffer",
"SPV_KHR_terminate_invocation",
"SPV_KHR_subgroup_uniform_control_flow",
"SPV_KHR_integer_dot_product",
"SPV_EXT_shader_image_int64",
"SPV_KHR_non_semantic_info",
"SPV_KHR_uniform_group_instructions",
"SPV_KHR_fragment_shader_barycentric",
"SPV_KHR_vulkan_memory_model",
"SPV_NV_bindless_texture",
});
extensions_allowlist_.insert({"SPV_AMD_shader_explicit_vertex_parameter",
"SPV_AMD_shader_trinary_minmax",
"SPV_AMD_gcn_shader",
"SPV_KHR_shader_ballot",
"SPV_AMD_shader_ballot",
"SPV_AMD_gpu_shader_half_float",
"SPV_KHR_shader_draw_parameters",
"SPV_KHR_subgroup_vote",
"SPV_KHR_8bit_storage",
"SPV_KHR_16bit_storage",
"SPV_KHR_device_group",
"SPV_KHR_multiview",
"SPV_NVX_multiview_per_view_attributes",
"SPV_NV_viewport_array2",
"SPV_NV_stereo_view_rendering",
"SPV_NV_sample_mask_override_coverage",
"SPV_NV_geometry_shader_passthrough",
"SPV_AMD_texture_gather_bias_lod",
"SPV_KHR_storage_buffer_storage_class",
"SPV_KHR_variable_pointers",
"SPV_AMD_gpu_shader_int16",
"SPV_KHR_post_depth_coverage",
"SPV_KHR_shader_atomic_counter_ops",
"SPV_EXT_shader_stencil_export",
"SPV_EXT_shader_viewport_index_layer",
"SPV_AMD_shader_image_load_store_lod",
"SPV_AMD_shader_fragment_mask",
"SPV_EXT_fragment_fully_covered",
"SPV_AMD_gpu_shader_half_float_fetch",
"SPV_GOOGLE_decorate_string",
"SPV_GOOGLE_hlsl_functionality1",
"SPV_NV_shader_subgroup_partitioned",
"SPV_EXT_descriptor_indexing",
"SPV_NV_fragment_shader_barycentric",
"SPV_NV_compute_shader_derivatives",
"SPV_NV_shader_image_footprint",
"SPV_NV_shading_rate",
"SPV_NV_mesh_shader",
"SPV_NV_ray_tracing",
"SPV_KHR_ray_query",
"SPV_EXT_fragment_invocation_density",
"SPV_EXT_physical_storage_buffer",
"SPV_KHR_terminate_invocation",
"SPV_KHR_subgroup_uniform_control_flow",
"SPV_KHR_integer_dot_product",
"SPV_EXT_shader_image_int64",
"SPV_KHR_non_semantic_info",
"SPV_KHR_uniform_group_instructions",
"SPV_KHR_fragment_shader_barycentric",
"SPV_KHR_vulkan_memory_model",
"SPV_NV_bindless_texture",
"SPV_EXT_shader_atomic_float_add"});
}
bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) {
std::vector<Instruction*> users;

View File

@@ -76,6 +76,11 @@ bool MemPass::IsNonPtrAccessChain(const spv::Op opcode) const {
bool MemPass::IsPtr(uint32_t ptrId) {
uint32_t varId = ptrId;
Instruction* ptrInst = get_def_use_mgr()->GetDef(varId);
if (ptrInst->opcode() == spv::Op::OpFunction) {
// A function is not a pointer, but it's return type could be, which will
// erroneously lead to this function returning true later on
return false;
}
while (ptrInst->opcode() == spv::Op::OpCopyObject) {
varId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
ptrInst = get_def_use_mgr()->GetDef(varId);

View File

@@ -17,6 +17,7 @@
#include <functional>
#include <memory>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>

View File

@@ -82,6 +82,7 @@
#include "source/opt/strength_reduction_pass.h"
#include "source/opt/strip_debug_info_pass.h"
#include "source/opt/strip_nonsemantic_info_pass.h"
#include "source/opt/trim_capabilities_pass.h"
#include "source/opt/unify_const_pass.h"
#include "source/opt/upgrade_memory_model.h"
#include "source/opt/vector_dce.h"

View File

@@ -0,0 +1,320 @@
// Copyright (c) 2023 Google 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.
#include "source/opt/trim_capabilities_pass.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <functional>
#include <optional>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "source/enum_set.h"
#include "source/enum_string_mapping.h"
#include "source/opt/ir_context.h"
#include "source/spirv_target_env.h"
#include "source/util/string_utils.h"
namespace spvtools {
namespace opt {
namespace {
constexpr uint32_t kVariableStorageClassIndex = 0;
constexpr uint32_t kTypeArrayTypeIndex = 0;
constexpr uint32_t kOpTypeScalarBitWidthIndex = 0;
constexpr uint32_t kTypePointerTypeIdInIdx = 1;
} // namespace
// ============== Begin opcode handler implementations. =======================
//
// Adding support for a new capability should only require adding a new handler,
// and updating the
// kSupportedCapabilities/kUntouchableCapabilities/kFordiddenCapabilities lists.
//
// Handler names follow the following convention:
// Handler_<Opcode>_<Capability>()
static std::optional<spv::Capability> Handler_OpVariable_StorageInputOutput16(
const Instruction* instruction) {
assert(instruction->opcode() == spv::Op::OpVariable &&
"This handler only support OpVariable opcodes.");
// This capability is only required if the variable as an Input/Output storage
// class.
spv::StorageClass storage_class = spv::StorageClass(
instruction->GetSingleWordInOperand(kVariableStorageClassIndex));
if (storage_class != spv::StorageClass::Input &&
storage_class != spv::StorageClass::Output) {
return std::nullopt;
}
// This capability is only required if the type involves a 16-bit component.
// Quick check: are 16-bit types allowed?
const CapabilitySet& capabilities =
instruction->context()->get_feature_mgr()->GetCapabilities();
if (!capabilities.contains(spv::Capability::Float16) &&
!capabilities.contains(spv::Capability::Int16)) {
return std::nullopt;
}
// We need to walk the type definition.
std::queue<uint32_t> instructions_to_visit;
instructions_to_visit.push(instruction->type_id());
const auto* def_use_mgr = instruction->context()->get_def_use_mgr();
while (!instructions_to_visit.empty()) {
const Instruction* item =
def_use_mgr->GetDef(instructions_to_visit.front());
instructions_to_visit.pop();
if (item->opcode() == spv::Op::OpTypePointer) {
instructions_to_visit.push(
item->GetSingleWordInOperand(kTypePointerTypeIdInIdx));
continue;
}
if (item->opcode() == spv::Op::OpTypeMatrix ||
item->opcode() == spv::Op::OpTypeVector ||
item->opcode() == spv::Op::OpTypeArray ||
item->opcode() == spv::Op::OpTypeRuntimeArray) {
instructions_to_visit.push(
item->GetSingleWordInOperand(kTypeArrayTypeIndex));
continue;
}
if (item->opcode() == spv::Op::OpTypeStruct) {
item->ForEachInOperand([&instructions_to_visit](const uint32_t* op_id) {
instructions_to_visit.push(*op_id);
});
continue;
}
if (item->opcode() != spv::Op::OpTypeInt &&
item->opcode() != spv::Op::OpTypeFloat) {
continue;
}
if (item->GetSingleWordInOperand(kOpTypeScalarBitWidthIndex) == 16) {
return spv::Capability::StorageInputOutput16;
}
}
return std::nullopt;
}
// Opcode of interest to determine capabilities requirements.
constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 1> kOpcodeHandlers{{
{spv::Op::OpVariable, Handler_OpVariable_StorageInputOutput16},
}};
// ============== End opcode handler implementations. =======================
namespace {
ExtensionSet getExtensionsRelatedTo(const CapabilitySet& capabilities,
const AssemblyGrammar& grammar) {
ExtensionSet output;
const spv_operand_desc_t* desc = nullptr;
for (auto capability : capabilities) {
if (SPV_SUCCESS != grammar.lookupOperand(SPV_OPERAND_TYPE_CAPABILITY,
static_cast<uint32_t>(capability),
&desc)) {
continue;
}
for (uint32_t i = 0; i < desc->numExtensions; ++i) {
output.insert(desc->extensions[i]);
}
}
return output;
}
} // namespace
TrimCapabilitiesPass::TrimCapabilitiesPass()
: supportedCapabilities_(
TrimCapabilitiesPass::kSupportedCapabilities.cbegin(),
TrimCapabilitiesPass::kSupportedCapabilities.cend()),
forbiddenCapabilities_(
TrimCapabilitiesPass::kForbiddenCapabilities.cbegin(),
TrimCapabilitiesPass::kForbiddenCapabilities.cend()),
untouchableCapabilities_(
TrimCapabilitiesPass::kUntouchableCapabilities.cbegin(),
TrimCapabilitiesPass::kUntouchableCapabilities.cend()),
opcodeHandlers_(kOpcodeHandlers.cbegin(), kOpcodeHandlers.cend()) {}
void TrimCapabilitiesPass::addInstructionRequirements(
Instruction* instruction, CapabilitySet* capabilities,
ExtensionSet* extensions) const {
// Ignoring OpCapability instructions.
if (instruction->opcode() == spv::Op::OpCapability) {
return;
}
// First case: the opcode is itself gated by a capability.
{
const spv_opcode_desc_t* desc = {};
auto result =
context()->grammar().lookupOpcode(instruction->opcode(), &desc);
if (result == SPV_SUCCESS) {
addSupportedCapabilitiesToSet(desc->numCapabilities, desc->capabilities,
capabilities);
if (desc->minVersion <=
spvVersionForTargetEnv(context()->GetTargetEnv())) {
extensions->insert(desc->extensions,
desc->extensions + desc->numExtensions);
}
}
}
// Second case: one of the opcode operand is gated by a capability.
const uint32_t operandCount = instruction->NumOperands();
for (uint32_t i = 0; i < operandCount; i++) {
const auto& operand = instruction->GetOperand(i);
// No supported capability relies on a 2+-word operand.
if (operand.words.size() != 1) {
continue;
}
// No supported capability relies on a literal string operand.
if (operand.type == SPV_OPERAND_TYPE_LITERAL_STRING) {
continue;
}
const spv_operand_desc_t* desc = {};
auto result = context()->grammar().lookupOperand(operand.type,
operand.words[0], &desc);
if (result != SPV_SUCCESS) {
continue;
}
addSupportedCapabilitiesToSet(desc->numCapabilities, desc->capabilities,
capabilities);
if (desc->minVersion <= spvVersionForTargetEnv(context()->GetTargetEnv())) {
extensions->insert(desc->extensions,
desc->extensions + desc->numExtensions);
}
}
// Last case: some complex logic needs to be run to determine capabilities.
auto[begin, end] = opcodeHandlers_.equal_range(instruction->opcode());
for (auto it = begin; it != end; it++) {
const OpcodeHandler handler = it->second;
auto result = handler(instruction);
if (result.has_value()) {
capabilities->insert(*result);
}
}
}
std::pair<CapabilitySet, ExtensionSet>
TrimCapabilitiesPass::DetermineRequiredCapabilitiesAndExtensions() const {
CapabilitySet required_capabilities;
ExtensionSet required_extensions;
get_module()->ForEachInst([&](Instruction* instruction) {
addInstructionRequirements(instruction, &required_capabilities,
&required_extensions);
});
#if !defined(NDEBUG)
// Debug only. We check the outputted required capabilities against the
// supported capabilities list. The supported capabilities list is useful for
// API users to quickly determine if they can use the pass or not. But this
// list has to remain up-to-date with the pass code. If we can detect a
// capability as required, but it's not listed, it means the list is
// out-of-sync. This method is not ideal, but should cover most cases.
{
for (auto capability : required_capabilities) {
assert(supportedCapabilities_.contains(capability) &&
"Module is using a capability that is not listed as supported.");
}
}
#endif
return std::make_pair(std::move(required_capabilities),
std::move(required_extensions));
}
Pass::Status TrimCapabilitiesPass::TrimUnrequiredCapabilities(
const CapabilitySet& required_capabilities) const {
const FeatureManager* feature_manager = context()->get_feature_mgr();
CapabilitySet capabilities_to_trim;
for (auto capability : feature_manager->GetCapabilities()) {
// Forbidden capability completely prevents trimming. Early exit.
if (forbiddenCapabilities_.contains(capability)) {
return Pass::Status::SuccessWithoutChange;
}
// Some capabilities cannot be safely removed. Leaving them untouched.
if (untouchableCapabilities_.contains(capability)) {
continue;
}
// If the capability is unsupported, don't trim it.
if (!supportedCapabilities_.contains(capability)) {
continue;
}
if (required_capabilities.contains(capability)) {
continue;
}
capabilities_to_trim.insert(capability);
}
for (auto capability : capabilities_to_trim) {
context()->RemoveCapability(capability);
}
return capabilities_to_trim.size() == 0 ? Pass::Status::SuccessWithoutChange
: Pass::Status::SuccessWithChange;
}
Pass::Status TrimCapabilitiesPass::TrimUnrequiredExtensions(
const ExtensionSet& required_extensions) const {
const auto supported_extensions =
getExtensionsRelatedTo(supportedCapabilities_, context()->grammar());
bool modified_module = false;
for (auto extension : supported_extensions) {
if (!required_extensions.contains(extension)) {
modified_module = true;
context()->RemoveExtension(extension);
}
}
return modified_module ? Pass::Status::SuccessWithChange
: Pass::Status::SuccessWithoutChange;
}
Pass::Status TrimCapabilitiesPass::Process() {
auto[required_capabilities, required_extensions] =
DetermineRequiredCapabilitiesAndExtensions();
Pass::Status status = TrimUnrequiredCapabilities(required_capabilities);
// If no capabilities were removed, we have no extension to trim.
// Note: this is true because this pass only removes unused extensions caused
// by unused capabilities.
// This is not an extension trimming pass.
if (status == Pass::Status::SuccessWithoutChange) {
return status;
}
return TrimUnrequiredExtensions(required_extensions);
}
} // namespace opt
} // namespace spvtools

View File

@@ -0,0 +1,151 @@
// Copyright (c) 2023 Google 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.
#ifndef SOURCE_OPT_TRIM_CAPABILITIES_PASS_H_
#define SOURCE_OPT_TRIM_CAPABILITIES_PASS_H_
#include <algorithm>
#include <array>
#include <functional>
#include <optional>
#include <unordered_map>
#include <unordered_set>
#include "source/enum_set.h"
#include "source/extensions.h"
#include "source/opt/ir_context.h"
#include "source/opt/module.h"
#include "source/opt/pass.h"
namespace spvtools {
namespace opt {
// This is required for NDK build. The unordered_set/unordered_map
// implementation don't work with class enums.
struct ClassEnumHash {
std::size_t operator()(spv::Capability value) const {
using StoringType = typename std::underlying_type_t<spv::Capability>;
return std::hash<StoringType>{}(static_cast<StoringType>(value));
}
std::size_t operator()(spv::Op value) const {
using StoringType = typename std::underlying_type_t<spv::Op>;
return std::hash<StoringType>{}(static_cast<StoringType>(value));
}
};
// An opcode handler is a function which, given an instruction, returns either
// the required capability, or nothing.
// Each handler checks one case for a capability requirement.
//
// Example:
// - `OpTypeImage` can have operand `A` operand which requires capability 1
// - `OpTypeImage` can also have operand `B` which requires capability 2.
// -> We have 2 handlers: `Handler_OpTypeImage_1` and
// `Handler_OpTypeImage_2`.
using OpcodeHandler =
std::optional<spv::Capability> (*)(const Instruction* instruction);
// This pass tried to remove superfluous capabilities declared in the module.
// - If all the capabilities listed by an extension are removed, the extension
// is also trimmed.
// - If the module countains any capability listed in `kForbiddenCapabilities`,
// the module is left untouched.
// - No capabilities listed in `kUntouchableCapabilities` are trimmed, even when
// not used.
// - Only capabilitied listed in `kSupportedCapabilities` are supported.
// - If the module contains unsupported capabilities, results might be
// incorrect.
class TrimCapabilitiesPass : public Pass {
private:
// All the capabilities supported by this optimization pass. If your module
// contains unsupported instruction, the pass could yield bad results.
static constexpr std::array kSupportedCapabilities{
// clang-format off
spv::Capability::Groups,
spv::Capability::Linkage,
spv::Capability::MinLod,
spv::Capability::Shader,
spv::Capability::ShaderClockKHR,
spv::Capability::StorageInputOutput16
// clang-format on
};
// Those capabilities disable all transformation of the module.
static constexpr std::array kForbiddenCapabilities{
spv::Capability::Linkage,
};
// Those capabilities are never removed from a module because we cannot
// guess from the SPIR-V only if they are required or not.
static constexpr std::array kUntouchableCapabilities{
spv::Capability::Shader,
};
public:
TrimCapabilitiesPass();
TrimCapabilitiesPass(const TrimCapabilitiesPass&) = delete;
TrimCapabilitiesPass(TrimCapabilitiesPass&&) = delete;
private:
// Inserts every capability in `capabilities[capabilityCount]` supported by
// this pass into `output`.
inline void addSupportedCapabilitiesToSet(
uint32_t capabilityCount, const spv::Capability* const capabilities,
CapabilitySet* output) const {
for (uint32_t i = 0; i < capabilityCount; ++i) {
if (supportedCapabilities_.contains(capabilities[i])) {
output->insert(capabilities[i]);
}
}
}
// Given an `instruction`, determines the capabilities and extension it
// requires, and output them in `capabilities` and `extensions`. The returned
// capabilities form a subset of kSupportedCapabilities.
void addInstructionRequirements(Instruction* instruction,
CapabilitySet* capabilities,
ExtensionSet* extensions) const;
// Returns the list of required capabilities and extensions for the module.
// The returned capabilities form a subset of kSupportedCapabilities.
std::pair<CapabilitySet, ExtensionSet>
DetermineRequiredCapabilitiesAndExtensions() const;
// Trims capabilities not listed in `required_capabilities` if possible.
// Returns whether or not the module was modified.
Pass::Status TrimUnrequiredCapabilities(
const CapabilitySet& required_capabilities) const;
// Trims extensions not listed in `required_extensions` if supported by this
// pass. An extensions is considered supported as soon as one capability this
// pass support requires it.
Pass::Status TrimUnrequiredExtensions(
const ExtensionSet& required_extensions) const;
public:
const char* name() const override { return "trim-capabilities"; }
Status Process() override;
private:
const CapabilitySet supportedCapabilities_;
const CapabilitySet forbiddenCapabilities_;
const CapabilitySet untouchableCapabilities_;
const std::unordered_multimap<spv::Op, OpcodeHandler, ClassEnumHash>
opcodeHandlers_;
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_TRIM_CAPABILITIES_H_

View File

@@ -24,6 +24,7 @@ namespace spvtools {
void EmitNumericLiteral(std::ostream* out, const spv_parsed_instruction_t& inst,
const spv_parsed_operand_t& operand) {
if (operand.type != SPV_OPERAND_TYPE_LITERAL_INTEGER &&
operand.type != SPV_OPERAND_TYPE_LITERAL_FLOAT &&
operand.type != SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER &&
operand.type != SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER &&
operand.type != SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER)

View File

@@ -312,6 +312,17 @@ spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar,
}
} break;
case SPV_OPERAND_TYPE_LITERAL_FLOAT: {
// The current operand is a 32-bit float.
// That's just how the grammar works.
spvtools::IdType expected_type = {
32, false, spvtools::IdTypeClass::kScalarFloatType};
if (auto error = context->binaryEncodeNumericLiteral(
textValue, error_code_for_literals, expected_type, pInst)) {
return error;
}
} break;
case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER:
// This is a context-independent literal number which can be a 32-bit
// number of floating point value.