1 // Copyright (c) 2023 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef SOURCE_OPT_TRIM_CAPABILITIES_PASS_H_ 16 #define SOURCE_OPT_TRIM_CAPABILITIES_PASS_H_ 17 18 #include <algorithm> 19 #include <array> 20 #include <functional> 21 #include <optional> 22 #include <unordered_map> 23 #include <unordered_set> 24 25 #include "source/enum_set.h" 26 #include "source/extensions.h" 27 #include "source/opt/ir_context.h" 28 #include "source/opt/module.h" 29 #include "source/opt/pass.h" 30 31 namespace spvtools { 32 namespace opt { 33 34 // This is required for NDK build. The unordered_set/unordered_map 35 // implementation don't work with class enums. 36 struct ClassEnumHash { operatorClassEnumHash37 std::size_t operator()(spv::Capability value) const { 38 using StoringType = typename std::underlying_type_t<spv::Capability>; 39 return std::hash<StoringType>{}(static_cast<StoringType>(value)); 40 } 41 operatorClassEnumHash42 std::size_t operator()(spv::Op value) const { 43 using StoringType = typename std::underlying_type_t<spv::Op>; 44 return std::hash<StoringType>{}(static_cast<StoringType>(value)); 45 } 46 }; 47 48 // An opcode handler is a function which, given an instruction, returns either 49 // the required capability, or nothing. 50 // Each handler checks one case for a capability requirement. 51 // 52 // Example: 53 // - `OpTypeImage` can have operand `A` operand which requires capability 1 54 // - `OpTypeImage` can also have operand `B` which requires capability 2. 55 // -> We have 2 handlers: `Handler_OpTypeImage_1` and 56 // `Handler_OpTypeImage_2`. 57 using OpcodeHandler = 58 std::optional<spv::Capability> (*)(const Instruction* instruction); 59 60 // This pass tried to remove superfluous capabilities declared in the module. 61 // - If all the capabilities listed by an extension are removed, the extension 62 // is also trimmed. 63 // - If the module countains any capability listed in `kForbiddenCapabilities`, 64 // the module is left untouched. 65 // - No capabilities listed in `kUntouchableCapabilities` are trimmed, even when 66 // not used. 67 // - Only capabilitied listed in `kSupportedCapabilities` are supported. 68 // - If the module contains unsupported capabilities, results might be 69 // incorrect. 70 class TrimCapabilitiesPass : public Pass { 71 private: 72 // All the capabilities supported by this optimization pass. If your module 73 // contains unsupported instruction, the pass could yield bad results. 74 static constexpr std::array kSupportedCapabilities{ 75 // clang-format off 76 spv::Capability::Groups, 77 spv::Capability::Linkage, 78 spv::Capability::MinLod, 79 spv::Capability::Shader, 80 spv::Capability::ShaderClockKHR, 81 spv::Capability::StorageInputOutput16 82 // clang-format on 83 }; 84 85 // Those capabilities disable all transformation of the module. 86 static constexpr std::array kForbiddenCapabilities{ 87 spv::Capability::Linkage, 88 }; 89 90 // Those capabilities are never removed from a module because we cannot 91 // guess from the SPIR-V only if they are required or not. 92 static constexpr std::array kUntouchableCapabilities{ 93 spv::Capability::Shader, 94 }; 95 96 public: 97 TrimCapabilitiesPass(); 98 TrimCapabilitiesPass(const TrimCapabilitiesPass&) = delete; 99 TrimCapabilitiesPass(TrimCapabilitiesPass&&) = delete; 100 101 private: 102 // Inserts every capability in `capabilities[capabilityCount]` supported by 103 // this pass into `output`. addSupportedCapabilitiesToSet(uint32_t capabilityCount,const spv::Capability * const capabilities,CapabilitySet * output)104 inline void addSupportedCapabilitiesToSet( 105 uint32_t capabilityCount, const spv::Capability* const capabilities, 106 CapabilitySet* output) const { 107 for (uint32_t i = 0; i < capabilityCount; ++i) { 108 if (supportedCapabilities_.contains(capabilities[i])) { 109 output->insert(capabilities[i]); 110 } 111 } 112 } 113 114 // Given an `instruction`, determines the capabilities and extension it 115 // requires, and output them in `capabilities` and `extensions`. The returned 116 // capabilities form a subset of kSupportedCapabilities. 117 void addInstructionRequirements(Instruction* instruction, 118 CapabilitySet* capabilities, 119 ExtensionSet* extensions) const; 120 121 // Returns the list of required capabilities and extensions for the module. 122 // The returned capabilities form a subset of kSupportedCapabilities. 123 std::pair<CapabilitySet, ExtensionSet> 124 DetermineRequiredCapabilitiesAndExtensions() const; 125 126 // Trims capabilities not listed in `required_capabilities` if possible. 127 // Returns whether or not the module was modified. 128 Pass::Status TrimUnrequiredCapabilities( 129 const CapabilitySet& required_capabilities) const; 130 131 // Trims extensions not listed in `required_extensions` if supported by this 132 // pass. An extensions is considered supported as soon as one capability this 133 // pass support requires it. 134 Pass::Status TrimUnrequiredExtensions( 135 const ExtensionSet& required_extensions) const; 136 137 public: name()138 const char* name() const override { return "trim-capabilities"; } 139 Status Process() override; 140 141 private: 142 const CapabilitySet supportedCapabilities_; 143 const CapabilitySet forbiddenCapabilities_; 144 const CapabilitySet untouchableCapabilities_; 145 const std::unordered_multimap<spv::Op, OpcodeHandler, ClassEnumHash> 146 opcodeHandlers_; 147 }; 148 149 } // namespace opt 150 } // namespace spvtools 151 #endif // SOURCE_OPT_TRIM_CAPABILITIES_H_ 152