| // 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 <stack> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <vector> | |
| #include "source/enum_set.h" | |
| #include "source/ext_inst.h" | |
| #include "source/opt/ir_context.h" | |
| #include "source/opt/reflect.h" | |
| #include "source/spirv_target_env.h" | |
| #include "source/table2.h" | |
| #include "source/util/string_utils.h" | |
| namespace spvtools { | |
| namespace opt { | |
| namespace { | |
| constexpr uint32_t kOpTypeFloatSizeIndex = 0; | |
| constexpr uint32_t kOpTypePointerStorageClassIndex = 0; | |
| constexpr uint32_t kTypeArrayTypeIndex = 0; | |
| constexpr uint32_t kOpTypeScalarBitWidthIndex = 0; | |
| constexpr uint32_t kTypePointerTypeIdInIndex = 1; | |
| constexpr uint32_t kOpTypeIntSizeIndex = 0; | |
| constexpr uint32_t kOpTypeImageDimIndex = 1; | |
| constexpr uint32_t kOpTypeImageArrayedIndex = kOpTypeImageDimIndex + 2; | |
| constexpr uint32_t kOpTypeImageMSIndex = kOpTypeImageArrayedIndex + 1; | |
| constexpr uint32_t kOpTypeImageSampledIndex = kOpTypeImageMSIndex + 1; | |
| constexpr uint32_t kOpTypeImageFormatIndex = kOpTypeImageSampledIndex + 1; | |
| constexpr uint32_t kOpImageReadImageIndex = 0; | |
| constexpr uint32_t kOpImageWriteImageIndex = 0; | |
| constexpr uint32_t kOpImageSparseReadImageIndex = 0; | |
| constexpr uint32_t kOpExtInstSetInIndex = 0; | |
| constexpr uint32_t kOpExtInstInstructionInIndex = 1; | |
| constexpr uint32_t kOpExtInstImportNameInIndex = 0; | |
| // DFS visit of the type defined by `instruction`. | |
| // If `condition` is true, children of the current node are visited. | |
| // If `condition` is false, the children of the current node are ignored. | |
| template <class UnaryPredicate> | |
| static void DFSWhile(const Instruction* instruction, UnaryPredicate condition) { | |
| std::stack<uint32_t> instructions_to_visit; | |
| std::unordered_set<uint32_t> visited_instructions; | |
| instructions_to_visit.push(instruction->result_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.top()); | |
| instructions_to_visit.pop(); | |
| // Forward references can be allowed, meaning we can have cycles | |
| // between ID uses. Need to keep track of this. | |
| if (visited_instructions.count(item->result_id())) continue; | |
| visited_instructions.insert(item->result_id()); | |
| if (!condition(item)) { | |
| continue; | |
| } | |
| if (item->opcode() == spv::Op::OpTypePointer) { | |
| instructions_to_visit.push( | |
| item->GetSingleWordInOperand(kTypePointerTypeIdInIndex)); | |
| 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; | |
| } | |
| } | |
| } | |
| // Walks the type defined by `instruction` (OpType* only). | |
| // Returns `true` if any call to `predicate` with the type/subtype returns true. | |
| template <class UnaryPredicate> | |
| static bool AnyTypeOf(const Instruction* instruction, | |
| UnaryPredicate predicate) { | |
| assert(IsTypeInst(instruction->opcode()) && | |
| "AnyTypeOf called with a non-type instruction."); | |
| bool found_one = false; | |
| DFSWhile(instruction, [&found_one, predicate](const Instruction* node) { | |
| if (found_one || predicate(node)) { | |
| found_one = true; | |
| return false; | |
| } | |
| return true; | |
| }); | |
| return found_one; | |
| } | |
| static bool is16bitType(const Instruction* instruction) { | |
| if (instruction->opcode() != spv::Op::OpTypeInt && | |
| instruction->opcode() != spv::Op::OpTypeFloat) { | |
| return false; | |
| } | |
| return instruction->GetSingleWordInOperand(kOpTypeScalarBitWidthIndex) == 16; | |
| } | |
| static bool Has16BitCapability(const FeatureManager* feature_manager) { | |
| const CapabilitySet& capabilities = feature_manager->GetCapabilities(); | |
| return capabilities.contains(spv::Capability::Float16) || | |
| capabilities.contains(spv::Capability::Int16); | |
| } | |
| } // 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_OpTypeFloat_Float16( | |
| const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpTypeFloat && | |
| "This handler only support OpTypeFloat opcodes."); | |
| const uint32_t size = | |
| instruction->GetSingleWordInOperand(kOpTypeFloatSizeIndex); | |
| return size == 16 ? std::optional(spv::Capability::Float16) : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> Handler_OpTypeFloat_Float64( | |
| const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpTypeFloat && | |
| "This handler only support OpTypeFloat opcodes."); | |
| const uint32_t size = | |
| instruction->GetSingleWordInOperand(kOpTypeFloatSizeIndex); | |
| return size == 64 ? std::optional(spv::Capability::Float64) : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> | |
| Handler_OpTypePointer_StorageInputOutput16(const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpTypePointer && | |
| "This handler only support OpTypePointer opcodes."); | |
| // This capability is only required if the variable has an Input/Output | |
| // storage class. | |
| spv::StorageClass storage_class = spv::StorageClass( | |
| instruction->GetSingleWordInOperand(kOpTypePointerStorageClassIndex)); | |
| if (storage_class != spv::StorageClass::Input && | |
| storage_class != spv::StorageClass::Output) { | |
| return std::nullopt; | |
| } | |
| if (!Has16BitCapability(instruction->context()->get_feature_mgr())) { | |
| return std::nullopt; | |
| } | |
| return AnyTypeOf(instruction, is16bitType) | |
| ? std::optional(spv::Capability::StorageInputOutput16) | |
| : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> | |
| Handler_OpTypePointer_StoragePushConstant16(const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpTypePointer && | |
| "This handler only support OpTypePointer opcodes."); | |
| // This capability is only required if the variable has a PushConstant storage | |
| // class. | |
| spv::StorageClass storage_class = spv::StorageClass( | |
| instruction->GetSingleWordInOperand(kOpTypePointerStorageClassIndex)); | |
| if (storage_class != spv::StorageClass::PushConstant) { | |
| return std::nullopt; | |
| } | |
| if (!Has16BitCapability(instruction->context()->get_feature_mgr())) { | |
| return std::nullopt; | |
| } | |
| return AnyTypeOf(instruction, is16bitType) | |
| ? std::optional(spv::Capability::StoragePushConstant16) | |
| : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> | |
| Handler_OpTypePointer_StorageUniformBufferBlock16( | |
| const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpTypePointer && | |
| "This handler only support OpTypePointer opcodes."); | |
| // This capability is only required if the variable has a Uniform storage | |
| // class. | |
| spv::StorageClass storage_class = spv::StorageClass( | |
| instruction->GetSingleWordInOperand(kOpTypePointerStorageClassIndex)); | |
| if (storage_class != spv::StorageClass::Uniform) { | |
| return std::nullopt; | |
| } | |
| if (!Has16BitCapability(instruction->context()->get_feature_mgr())) { | |
| return std::nullopt; | |
| } | |
| const auto* decoration_mgr = instruction->context()->get_decoration_mgr(); | |
| const bool matchesCondition = | |
| AnyTypeOf(instruction, [decoration_mgr](const Instruction* item) { | |
| if (!decoration_mgr->HasDecoration(item->result_id(), | |
| spv::Decoration::BufferBlock)) { | |
| return false; | |
| } | |
| return AnyTypeOf(item, is16bitType); | |
| }); | |
| return matchesCondition | |
| ? std::optional(spv::Capability::StorageUniformBufferBlock16) | |
| : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> | |
| Handler_OpTypePointer_StorageBuffer16BitAccess(const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpTypePointer && | |
| "This handler only support OpTypePointer opcodes."); | |
| // Requires StorageBuffer, ShaderRecordBufferKHR or PhysicalStorageBuffer | |
| // storage classes. | |
| spv::StorageClass storage_class = spv::StorageClass( | |
| instruction->GetSingleWordInOperand(kOpTypePointerStorageClassIndex)); | |
| if (storage_class != spv::StorageClass::StorageBuffer && | |
| storage_class != spv::StorageClass::ShaderRecordBufferKHR && | |
| storage_class != spv::StorageClass::PhysicalStorageBuffer) { | |
| return std::nullopt; | |
| } | |
| const auto* decoration_mgr = instruction->context()->get_decoration_mgr(); | |
| const bool matchesCondition = | |
| AnyTypeOf(instruction, [decoration_mgr](const Instruction* item) { | |
| if (!decoration_mgr->HasDecoration(item->result_id(), | |
| spv::Decoration::Block)) { | |
| return false; | |
| } | |
| return AnyTypeOf(item, is16bitType); | |
| }); | |
| return matchesCondition | |
| ? std::optional(spv::Capability::StorageBuffer16BitAccess) | |
| : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> Handler_OpTypePointer_StorageUniform16( | |
| const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpTypePointer && | |
| "This handler only support OpTypePointer opcodes."); | |
| // This capability is only required if the variable has a Uniform storage | |
| // class. | |
| spv::StorageClass storage_class = spv::StorageClass( | |
| instruction->GetSingleWordInOperand(kOpTypePointerStorageClassIndex)); | |
| if (storage_class != spv::StorageClass::Uniform) { | |
| return std::nullopt; | |
| } | |
| const auto* feature_manager = instruction->context()->get_feature_mgr(); | |
| if (!Has16BitCapability(feature_manager)) { | |
| return std::nullopt; | |
| } | |
| const bool hasBufferBlockCapability = | |
| feature_manager->GetCapabilities().contains( | |
| spv::Capability::StorageUniformBufferBlock16); | |
| const auto* decoration_mgr = instruction->context()->get_decoration_mgr(); | |
| bool found16bitType = false; | |
| DFSWhile(instruction, [decoration_mgr, hasBufferBlockCapability, | |
| &found16bitType](const Instruction* item) { | |
| if (found16bitType) { | |
| return false; | |
| } | |
| if (hasBufferBlockCapability && | |
| decoration_mgr->HasDecoration(item->result_id(), | |
| spv::Decoration::BufferBlock)) { | |
| return false; | |
| } | |
| if (is16bitType(item)) { | |
| found16bitType = true; | |
| return false; | |
| } | |
| return true; | |
| }); | |
| return found16bitType ? std::optional(spv::Capability::StorageUniform16) | |
| : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> Handler_OpTypeInt_Int16( | |
| const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpTypeInt && | |
| "This handler only support OpTypeInt opcodes."); | |
| const uint32_t size = | |
| instruction->GetSingleWordInOperand(kOpTypeIntSizeIndex); | |
| return size == 16 ? std::optional(spv::Capability::Int16) : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> Handler_OpTypeInt_Int64( | |
| const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpTypeInt && | |
| "This handler only support OpTypeInt opcodes."); | |
| const uint32_t size = | |
| instruction->GetSingleWordInOperand(kOpTypeIntSizeIndex); | |
| return size == 64 ? std::optional(spv::Capability::Int64) : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> Handler_OpTypeImage_ImageMSArray( | |
| const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpTypeImage && | |
| "This handler only support OpTypeImage opcodes."); | |
| const uint32_t arrayed = | |
| instruction->GetSingleWordInOperand(kOpTypeImageArrayedIndex); | |
| const uint32_t ms = instruction->GetSingleWordInOperand(kOpTypeImageMSIndex); | |
| const uint32_t sampled = | |
| instruction->GetSingleWordInOperand(kOpTypeImageSampledIndex); | |
| return arrayed == 1 && sampled == 2 && ms == 1 | |
| ? std::optional(spv::Capability::ImageMSArray) | |
| : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> | |
| Handler_OpImageRead_StorageImageReadWithoutFormat( | |
| const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpImageRead && | |
| "This handler only support OpImageRead opcodes."); | |
| const auto* def_use_mgr = instruction->context()->get_def_use_mgr(); | |
| const uint32_t image_index = | |
| instruction->GetSingleWordInOperand(kOpImageReadImageIndex); | |
| const uint32_t type_index = def_use_mgr->GetDef(image_index)->type_id(); | |
| const Instruction* type = def_use_mgr->GetDef(type_index); | |
| const uint32_t dim = type->GetSingleWordInOperand(kOpTypeImageDimIndex); | |
| const uint32_t format = type->GetSingleWordInOperand(kOpTypeImageFormatIndex); | |
| // If the Image Format is Unknown and Dim is SubpassData, | |
| // StorageImageReadWithoutFormat is required. | |
| const bool is_unknown = spv::ImageFormat(format) == spv::ImageFormat::Unknown; | |
| const bool requires_capability_for_unknown = | |
| spv::Dim(dim) != spv::Dim::SubpassData; | |
| return is_unknown && requires_capability_for_unknown | |
| ? std::optional(spv::Capability::StorageImageReadWithoutFormat) | |
| : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> | |
| Handler_OpImageWrite_StorageImageWriteWithoutFormat( | |
| const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpImageWrite && | |
| "This handler only support OpImageWrite opcodes."); | |
| const auto* def_use_mgr = instruction->context()->get_def_use_mgr(); | |
| const uint32_t image_index = | |
| instruction->GetSingleWordInOperand(kOpImageWriteImageIndex); | |
| const uint32_t type_index = def_use_mgr->GetDef(image_index)->type_id(); | |
| // If the Image Format is Unknown, StorageImageWriteWithoutFormat is required. | |
| const Instruction* type = def_use_mgr->GetDef(type_index); | |
| const uint32_t format = type->GetSingleWordInOperand(kOpTypeImageFormatIndex); | |
| const bool is_unknown = spv::ImageFormat(format) == spv::ImageFormat::Unknown; | |
| return is_unknown | |
| ? std::optional(spv::Capability::StorageImageWriteWithoutFormat) | |
| : std::nullopt; | |
| } | |
| static std::optional<spv::Capability> | |
| Handler_OpImageSparseRead_StorageImageReadWithoutFormat( | |
| const Instruction* instruction) { | |
| assert(instruction->opcode() == spv::Op::OpImageSparseRead && | |
| "This handler only support OpImageSparseRead opcodes."); | |
| const auto* def_use_mgr = instruction->context()->get_def_use_mgr(); | |
| const uint32_t image_index = | |
| instruction->GetSingleWordInOperand(kOpImageSparseReadImageIndex); | |
| const uint32_t type_index = def_use_mgr->GetDef(image_index)->type_id(); | |
| const Instruction* type = def_use_mgr->GetDef(type_index); | |
| const uint32_t format = type->GetSingleWordInOperand(kOpTypeImageFormatIndex); | |
| return spv::ImageFormat(format) == spv::ImageFormat::Unknown | |
| ? std::optional(spv::Capability::StorageImageReadWithoutFormat) | |
| : std::nullopt; | |
| } | |
| // Opcode of interest to determine capabilities requirements. | |
| constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 14> kOpcodeHandlers{{ | |
| // clang-format off | |
| {spv::Op::OpImageRead, Handler_OpImageRead_StorageImageReadWithoutFormat}, | |
| {spv::Op::OpImageWrite, Handler_OpImageWrite_StorageImageWriteWithoutFormat}, | |
| {spv::Op::OpImageSparseRead, Handler_OpImageSparseRead_StorageImageReadWithoutFormat}, | |
| {spv::Op::OpTypeFloat, Handler_OpTypeFloat_Float16 }, | |
| {spv::Op::OpTypeFloat, Handler_OpTypeFloat_Float64 }, | |
| {spv::Op::OpTypeImage, Handler_OpTypeImage_ImageMSArray}, | |
| {spv::Op::OpTypeInt, Handler_OpTypeInt_Int16 }, | |
| {spv::Op::OpTypeInt, Handler_OpTypeInt_Int64 }, | |
| {spv::Op::OpTypePointer, Handler_OpTypePointer_StorageInputOutput16}, | |
| {spv::Op::OpTypePointer, Handler_OpTypePointer_StoragePushConstant16}, | |
| {spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniform16}, | |
| {spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniform16}, | |
| {spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniformBufferBlock16}, | |
| {spv::Op::OpTypePointer, Handler_OpTypePointer_StorageBuffer16BitAccess}, | |
| // clang-format on | |
| }}; | |
| // ============== End opcode handler implementations. ======================= | |
| namespace { | |
| ExtensionSet getExtensionsRelatedTo(const CapabilitySet& capabilities) { | |
| ExtensionSet output; | |
| const spvtools::OperandDesc* desc = nullptr; | |
| for (auto capability : capabilities) { | |
| if (SPV_SUCCESS != | |
| spvtools::LookupOperand(SPV_OPERAND_TYPE_CAPABILITY, | |
| static_cast<uint32_t>(capability), &desc)) { | |
| continue; | |
| } | |
| for (auto extension : desc->extensions()) { | |
| output.insert(extension); | |
| } | |
| } | |
| return output; | |
| } | |
| bool hasOpcodeConflictingCapabilities(spv::Op opcode) { | |
| switch (opcode) { | |
| case spv::Op::OpBeginInvocationInterlockEXT: | |
| case spv::Op::OpEndInvocationInterlockEXT: | |
| case spv::Op::OpGroupNonUniformIAdd: | |
| case spv::Op::OpGroupNonUniformFAdd: | |
| case spv::Op::OpGroupNonUniformIMul: | |
| case spv::Op::OpGroupNonUniformFMul: | |
| case spv::Op::OpGroupNonUniformSMin: | |
| case spv::Op::OpGroupNonUniformUMin: | |
| case spv::Op::OpGroupNonUniformFMin: | |
| case spv::Op::OpGroupNonUniformSMax: | |
| case spv::Op::OpGroupNonUniformUMax: | |
| case spv::Op::OpGroupNonUniformFMax: | |
| case spv::Op::OpGroupNonUniformBitwiseAnd: | |
| case spv::Op::OpGroupNonUniformBitwiseOr: | |
| case spv::Op::OpGroupNonUniformBitwiseXor: | |
| case spv::Op::OpGroupNonUniformLogicalAnd: | |
| case spv::Op::OpGroupNonUniformLogicalOr: | |
| case spv::Op::OpGroupNonUniformLogicalXor: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| } // 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::addInstructionRequirementsForOpcode( | |
| spv::Op opcode, CapabilitySet* capabilities, | |
| ExtensionSet* extensions) const { | |
| if (hasOpcodeConflictingCapabilities(opcode)) { | |
| return; | |
| } | |
| const spvtools::InstructionDesc* desc; | |
| auto result = spvtools::LookupOpcode(opcode, &desc); | |
| if (result != SPV_SUCCESS) { | |
| return; | |
| } | |
| addSupportedCapabilitiesToSet(desc, capabilities); | |
| addSupportedExtensionsToSet(desc, extensions); | |
| } | |
| void TrimCapabilitiesPass::addInstructionRequirementsForOperand( | |
| const Operand& operand, CapabilitySet* capabilities, | |
| ExtensionSet* extensions) const { | |
| // No supported capability relies on a 2+-word operand. | |
| if (operand.words.size() != 1) { | |
| return; | |
| } | |
| // No supported capability relies on a literal string operand or an ID. | |
| if (operand.type == SPV_OPERAND_TYPE_LITERAL_STRING || | |
| operand.type == SPV_OPERAND_TYPE_ID || | |
| operand.type == SPV_OPERAND_TYPE_RESULT_ID) { | |
| return; | |
| } | |
| // If the Vulkan memory model is declared and any instruction uses Device | |
| // scope, the VulkanMemoryModelDeviceScope capability must be declared. This | |
| // rule cannot be covered by the grammar, so must be checked explicitly. | |
| if (operand.type == SPV_OPERAND_TYPE_SCOPE_ID) { | |
| const Instruction* memory_model = context()->GetMemoryModel(); | |
| if (memory_model && memory_model->GetSingleWordInOperand(1u) == | |
| uint32_t(spv::MemoryModel::Vulkan)) { | |
| capabilities->insert(spv::Capability::VulkanMemoryModelDeviceScope); | |
| } | |
| } | |
| // case 1: Operand is a single value, can directly lookup. | |
| if (!spvOperandIsConcreteMask(operand.type)) { | |
| const spvtools::OperandDesc* desc = nullptr; | |
| auto result = | |
| spvtools::LookupOperand(operand.type, operand.words[0], &desc); | |
| if (result != SPV_SUCCESS) { | |
| return; | |
| } | |
| addSupportedCapabilitiesToSet(desc, capabilities); | |
| addSupportedExtensionsToSet(desc, extensions); | |
| return; | |
| } | |
| // case 2: operand can be a bitmask, we need to decompose the lookup. | |
| for (uint32_t i = 0; i < 32; i++) { | |
| const uint32_t mask = (1 << i) & operand.words[0]; | |
| if (!mask) { | |
| continue; | |
| } | |
| const spvtools::OperandDesc* desc = nullptr; | |
| auto result = spvtools::LookupOperand(operand.type, mask, &desc); | |
| if (result != SPV_SUCCESS) { | |
| continue; | |
| } | |
| addSupportedCapabilitiesToSet(desc, capabilities); | |
| addSupportedExtensionsToSet(desc, extensions); | |
| } | |
| } | |
| void TrimCapabilitiesPass::addInstructionRequirementsForExtInst( | |
| Instruction* instruction, CapabilitySet* capabilities) const { | |
| assert(instruction->opcode() == spv::Op::OpExtInst && | |
| "addInstructionRequirementsForExtInst must be passed an OpExtInst " | |
| "instruction"); | |
| const auto* def_use_mgr = context()->get_def_use_mgr(); | |
| const Instruction* extInstImport = def_use_mgr->GetDef( | |
| instruction->GetSingleWordInOperand(kOpExtInstSetInIndex)); | |
| uint32_t extInstruction = | |
| instruction->GetSingleWordInOperand(kOpExtInstInstructionInIndex); | |
| const Operand& extInstSet = | |
| extInstImport->GetInOperand(kOpExtInstImportNameInIndex); | |
| spv_ext_inst_type_t instructionSet = | |
| spvExtInstImportTypeGet(extInstSet.AsString().c_str()); | |
| const ExtInstDesc* desc = nullptr; | |
| auto result = LookupExtInst(instructionSet, extInstruction, &desc); | |
| if (result != SPV_SUCCESS) { | |
| return; | |
| } | |
| addSupportedCapabilitiesToSet(desc, capabilities); | |
| } | |
| void TrimCapabilitiesPass::addInstructionRequirements( | |
| Instruction* instruction, CapabilitySet* capabilities, | |
| ExtensionSet* extensions) const { | |
| // Ignoring OpCapability and OpExtension instructions. | |
| if (instruction->opcode() == spv::Op::OpCapability || | |
| instruction->opcode() == spv::Op::OpExtension) { | |
| return; | |
| } | |
| if (instruction->opcode() == spv::Op::OpExtInst) { | |
| addInstructionRequirementsForExtInst(instruction, capabilities); | |
| } else { | |
| addInstructionRequirementsForOpcode(instruction->opcode(), capabilities, | |
| extensions); | |
| } | |
| // 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++) { | |
| addInstructionRequirementsForOperand(instruction->GetOperand(i), | |
| capabilities, extensions); | |
| } | |
| // 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()) { | |
| continue; | |
| } | |
| capabilities->insert(*result); | |
| } | |
| } | |
| void TrimCapabilitiesPass::AddExtensionsForOperand( | |
| const spv_operand_type_t type, const uint32_t value, | |
| ExtensionSet* extensions) const { | |
| const spvtools::OperandDesc* desc = nullptr; | |
| spv_result_t result = spvtools::LookupOperand(type, value, &desc); | |
| if (result != SPV_SUCCESS) { | |
| return; | |
| } | |
| addSupportedExtensionsToSet(desc, extensions); | |
| } | |
| std::pair<CapabilitySet, ExtensionSet> | |
| TrimCapabilitiesPass::DetermineRequiredCapabilitiesAndExtensions() const { | |
| CapabilitySet required_capabilities; | |
| ExtensionSet required_extensions; | |
| get_module()->ForEachInst([&](Instruction* instruction) { | |
| addInstructionRequirements(instruction, &required_capabilities, | |
| &required_extensions); | |
| }); | |
| for (auto capability : required_capabilities) { | |
| AddExtensionsForOperand(SPV_OPERAND_TYPE_CAPABILITY, | |
| static_cast<uint32_t>(capability), | |
| &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()) { | |
| // 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_); | |
| bool modified_module = false; | |
| for (auto extension : supported_extensions) { | |
| if (required_extensions.contains(extension)) { | |
| continue; | |
| } | |
| if (context()->RemoveExtension(extension)) { | |
| modified_module = true; | |
| } | |
| } | |
| return modified_module ? Pass::Status::SuccessWithChange | |
| : Pass::Status::SuccessWithoutChange; | |
| } | |
| bool TrimCapabilitiesPass::HasForbiddenCapabilities() const { | |
| // EnumSet.HasAnyOf returns `true` if the given set is empty. | |
| if (forbiddenCapabilities_.size() == 0) { | |
| return false; | |
| } | |
| const auto& capabilities = context()->get_feature_mgr()->GetCapabilities(); | |
| return capabilities.HasAnyOf(forbiddenCapabilities_); | |
| } | |
| Pass::Status TrimCapabilitiesPass::Process() { | |
| if (HasForbiddenCapabilities()) { | |
| return Status::SuccessWithoutChange; | |
| } | |
| auto[required_capabilities, required_extensions] = | |
| DetermineRequiredCapabilitiesAndExtensions(); | |
| Pass::Status capStatus = TrimUnrequiredCapabilities(required_capabilities); | |
| Pass::Status extStatus = TrimUnrequiredExtensions(required_extensions); | |
| return capStatus == Pass::Status::SuccessWithChange || | |
| extStatus == Pass::Status::SuccessWithChange | |
| ? Pass::Status::SuccessWithChange | |
| : Pass::Status::SuccessWithoutChange; | |
| } | |
| } // namespace opt | |
| } // namespace spvtools |