/* * Copyright 2015-2017 ARM Limited * * 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 SPIRV_CROSS_HPP #define SPIRV_CROSS_HPP #include "spirv.hpp" #include "spirv_common.hpp" namespace spirv_cross { class CFG; struct Resource { // Resources are identified with their SPIR-V ID. // This is the ID of the OpVariable. uint32_t id; // The type ID of the variable which includes arrays and all type modifications. // This type ID is not suitable for parsing OpMemberDecoration of a struct and other decorations in general // since these modifications typically happen on the base_type_id. uint32_t type_id; // The base type of the declared resource. // This type is the base type which ignores pointers and arrays of the type_id. // This is mostly useful to parse decorations of the underlying type. // base_type_id can also be obtained with get_type(get_type(type_id).self). uint32_t base_type_id; // The declared name (OpName) of the resource. // For Buffer blocks, the name actually reflects the externally // visible Block name. // // This name can be retrieved again by using either // get_name(id) or get_name(base_type_id) depending if it's a buffer block or not. // // This name can be an empty string in which case get_fallback_name(id) can be // used which obtains a suitable fallback identifier for an ID. std::string name; }; struct ShaderResources { std::vector uniform_buffers; std::vector storage_buffers; std::vector stage_inputs; std::vector stage_outputs; std::vector subpass_inputs; std::vector storage_images; std::vector sampled_images; std::vector atomic_counters; // There can only be one push constant block, // but keep the vector in case this restriction is lifted in the future. std::vector push_constant_buffers; // For Vulkan GLSL and HLSL source, // these correspond to separate texture2D and samplers respectively. std::vector separate_images; std::vector separate_samplers; }; struct CombinedImageSampler { // The ID of the sampler2D variable. uint32_t combined_id; // The ID of the texture2D variable. uint32_t image_id; // The ID of the sampler variable. uint32_t sampler_id; }; struct SpecializationConstant { // The ID of the specialization constant. uint32_t id; // The constant ID of the constant, used in Vulkan during pipeline creation. uint32_t constant_id; }; struct BufferRange { unsigned index; size_t offset; size_t range; }; class Compiler { public: friend class CFG; friend class DominatorBuilder; // The constructor takes a buffer of SPIR-V words and parses it. Compiler(std::vector ir); Compiler(const uint32_t *ir, size_t word_count); virtual ~Compiler() = default; // After parsing, API users can modify the SPIR-V via reflection and call this // to disassemble the SPIR-V into the desired langauage. // Sub-classes actually implement this. virtual std::string compile(); // Gets the identifier (OpName) of an ID. If not defined, an empty string will be returned. const std::string &get_name(uint32_t id) const; // Applies a decoration to an ID. Effectively injects OpDecorate. void set_decoration(uint32_t id, spv::Decoration decoration, uint32_t argument = 0); // Overrides the identifier OpName of an ID. // Identifiers beginning with underscores or identifiers which contain double underscores // are reserved by the implementation. void set_name(uint32_t id, const std::string &name); // Gets a bitmask for the decorations which are applied to ID. // I.e. (1ull << spv::DecorationFoo) | (1ull << spv::DecorationBar) uint64_t get_decoration_mask(uint32_t id) const; // Returns whether the decoration has been applied to the ID. bool has_decoration(uint32_t id, spv::Decoration decoration) const; // Gets the value for decorations which take arguments. // If the decoration is a boolean (i.e. spv::DecorationNonWritable), // 1 will be returned. // If decoration doesn't exist or decoration is not recognized, // 0 will be returned. uint32_t get_decoration(uint32_t id, spv::Decoration decoration) const; // Removes the decoration for a an ID. void unset_decoration(uint32_t id, spv::Decoration decoration); // Gets the SPIR-V type associated with ID. // Mostly used with Resource::type_id and Resource::base_type_id to parse the underlying type of a resource. const SPIRType &get_type(uint32_t id) const; // Gets the SPIR-V type of a variable. const SPIRType &get_type_from_variable(uint32_t id) const; // Gets the underlying storage class for an OpVariable. spv::StorageClass get_storage_class(uint32_t id) const; // If get_name() is an empty string, get the fallback name which will be used // instead in the disassembled source. virtual const std::string get_fallback_name(uint32_t id) const { return join("_", id); } // Given an OpTypeStruct in ID, obtain the identifier for member number "index". // This may be an empty string. const std::string &get_member_name(uint32_t id, uint32_t index) const; // Given an OpTypeStruct in ID, obtain the OpMemberDecoration for member number "index". uint32_t get_member_decoration(uint32_t id, uint32_t index, spv::Decoration decoration) const; // Sets the member identifier for OpTypeStruct ID, member number "index". void set_member_name(uint32_t id, uint32_t index, const std::string &name); // Returns the qualified member identifier for OpTypeStruct ID, member number "index", // or an empty string if no qualified alias exists const std::string &get_member_qualified_name(uint32_t type_id, uint32_t index) const; // Sets the qualified member identifier for OpTypeStruct ID, member number "index". void set_member_qualified_name(uint32_t type_id, uint32_t index, const std::string &name); // Gets the decoration mask for a member of a struct, similar to get_decoration_mask. uint64_t get_member_decoration_mask(uint32_t id, uint32_t index) const; // Returns whether the decoration has been applied to a member of a struct. bool has_member_decoration(uint32_t id, uint32_t index, spv::Decoration decoration) const; // Similar to set_decoration, but for struct members. void set_member_decoration(uint32_t id, uint32_t index, spv::Decoration decoration, uint32_t argument = 0); // Unsets a member decoration, similar to unset_decoration. void unset_member_decoration(uint32_t id, uint32_t index, spv::Decoration decoration); // Gets the fallback name for a member, similar to get_fallback_name. virtual const std::string get_fallback_member_name(uint32_t index) const { return join("_", index); } // Returns a vector of which members of a struct are potentially in use by a // SPIR-V shader. The granularity of this analysis is per-member of a struct. // This can be used for Buffer (UBO), BufferBlock (SSBO) and PushConstant blocks. // ID is the Resource::id obtained from get_shader_resources(). std::vector get_active_buffer_ranges(uint32_t id) const; // Returns the effective size of a buffer block. size_t get_declared_struct_size(const SPIRType &struct_type) const; // Returns the effective size of a buffer block struct member. virtual size_t get_declared_struct_member_size(const SPIRType &struct_type, uint32_t index) const; // Legacy GLSL compatibility method. Deprecated in favor of CompilerGLSL::flatten_buffer_block SPIRV_CROSS_DEPRECATED("Please use flatten_buffer_block instead.") void flatten_interface_block(uint32_t id); // Returns a set of all global variables which are statically accessed // by the control flow graph from the current entry point. // Only variables which change the interface for a shader are returned, that is, // variables with storage class of Input, Output, Uniform, UniformConstant, PushConstant and AtomicCounter // storage classes are returned. // // To use the returned set as the filter for which variables are used during compilation, // this set can be moved to set_enabled_interface_variables(). std::unordered_set get_active_interface_variables() const; // Sets the interface variables which are used during compilation. // By default, all variables are used. // Once set, compile() will only consider the set in active_variables. void set_enabled_interface_variables(std::unordered_set active_variables); // Query shader resources, use ids with reflection interface to modify or query binding points, etc. ShaderResources get_shader_resources() const; // Query shader resources, but only return the variables which are part of active_variables. // E.g.: get_shader_resources(get_active_variables()) to only return the variables which are statically // accessed. ShaderResources get_shader_resources(const std::unordered_set &active_variables) const; // Remapped variables are considered built-in variables and a backend will // not emit a declaration for this variable. // This is mostly useful for making use of builtins which are dependent on extensions. void set_remapped_variable_state(uint32_t id, bool remap_enable); bool get_remapped_variable_state(uint32_t id) const; // For subpassInput variables which are remapped to plain variables, // the number of components in the remapped // variable must be specified as the backing type of subpass inputs are opaque. void set_subpass_input_remapped_components(uint32_t id, uint32_t components); uint32_t get_subpass_input_remapped_components(uint32_t id) const; // All operations work on the current entry point. // Entry points can be swapped out with set_entry_point(). // Entry points should be set right after the constructor completes as some reflection functions traverse the graph from the entry point. // Resource reflection also depends on the entry point. // By default, the current entry point is set to the first OpEntryPoint which appears in the SPIR-V module. std::vector get_entry_points() const; void set_entry_point(const std::string &name); // Returns the internal data structure for entry points to allow poking around. const SPIREntryPoint &get_entry_point(const std::string &name) const; SPIREntryPoint &get_entry_point(const std::string &name); // Query and modify OpExecutionMode. uint64_t get_execution_mode_mask() const; void unset_execution_mode(spv::ExecutionMode mode); void set_execution_mode(spv::ExecutionMode mode, uint32_t arg0 = 0, uint32_t arg1 = 0, uint32_t arg2 = 0); // Gets argument for an execution mode (LocalSize, Invocations, OutputVertices). // For LocalSize, the index argument is used to select the dimension (X = 0, Y = 1, Z = 2). // For execution modes which do not have arguments, 0 is returned. uint32_t get_execution_mode_argument(spv::ExecutionMode mode, uint32_t index = 0) const; spv::ExecutionModel get_execution_model() const; // Analyzes all separate image and samplers used from the currently selected entry point, // and re-routes them all to a combined image sampler instead. // This is required to "support" separate image samplers in targets which do not natively support // this feature, like GLSL/ESSL. // // This must be called before compile() if such remapping is desired. // This call will add new sampled images to the SPIR-V, // so it will appear in reflection if get_shader_resources() is called after build_combined_image_samplers. // // If any image/sampler remapping was found, no separate image/samplers will appear in the decompiled output, // but will still appear in reflection. // // The resulting samplers will be void of any decorations like name, descriptor sets and binding points, // so this can be added before compile() if desired. // // Combined image samplers originating from this set are always considered active variables. void build_combined_image_samplers(); // Gets a remapping for the combined image samplers. const std::vector &get_combined_image_samplers() const { return combined_image_samplers; } // Set a new variable type remap callback. // The type remapping is designed to allow global interface variable to assume more special types. // A typical example here is to remap sampler2D into samplerExternalOES, which currently isn't supported // directly by SPIR-V. // // In compile() while emitting code, // for every variable that is declared, including function parameters, the callback will be called // and the API user has a chance to change the textual representation of the type used to declare the variable. // The API user can detect special patterns in names to guide the remapping. void set_variable_type_remap_callback(VariableTypeRemapCallback cb) { variable_remap_callback = std::move(cb); } // API for querying which specialization constants exist. // To modify a specialization constant before compile(), use get_constant(constant.id), // then update constants directly in the SPIRConstant data structure. // For composite types, the subconstants can be iterated over and modified. // constant_type is the SPIRType for the specialization constant, // which can be queried to determine which fields in the unions should be poked at. std::vector get_specialization_constants() const; SPIRConstant &get_constant(uint32_t id); const SPIRConstant &get_constant(uint32_t id) const; uint32_t get_current_id_bound() const { return uint32_t(ids.size()); } // API for querying buffer objects. // The type passed in here should be the base type of a resource, i.e. // get_type(resource.base_type_id) // as decorations are set in the basic Block type. // The type passed in here must have these decorations set, or an exception is raised. // Only UBOs and SSBOs or sub-structs which are part of these buffer types will have these decorations set. uint32_t type_struct_member_offset(const SPIRType &type, uint32_t index) const; uint32_t type_struct_member_array_stride(const SPIRType &type, uint32_t index) const; uint32_t type_struct_member_matrix_stride(const SPIRType &type, uint32_t index) const; // Gets the offset in SPIR-V words (uint32_t) for a decoration which was originally declared in the SPIR-V binary. // The offset will point to one or more uint32_t literals which can be modified in-place before using the SPIR-V binary. // Note that adding or removing decorations using the reflection API will not change the behavior of this function. // If the decoration was declared, sets the word_offset to an offset into the provided SPIR-V binary buffer and returns true, // otherwise, returns false. // If the decoration does not have any value attached to it (e.g. DecorationRelaxedPrecision), this function will also return false. bool get_binary_offset_for_decoration(uint32_t id, spv::Decoration decoration, uint32_t &word_offset) const; // HLSL counter buffer reflection interface. // Append/Consume/Increment/Decrement in HLSL is implemented as two "neighbor" buffer objects where // one buffer implements the storage, and a single buffer containing just a lone "int" implements the counter. // To SPIR-V these will be exposed as two separate buffers, but glslang HLSL frontend emits a special indentifier // which lets us link the two buffers together. // Queries if a variable ID is a counter buffer which "belongs" to a regular buffer object. // NOTE: This query is purely based on OpName identifiers as found in the SPIR-V module, and will // only return true if OpSource was reported HLSL. // To rely on this functionality, ensure that the SPIR-V module is not stripped. bool buffer_is_hlsl_counter_buffer(uint32_t id) const; // Queries if a buffer object has a neighbor "counter" buffer. // If so, the ID of that counter buffer will be returned in counter_id. // NOTE: This query is purely based on OpName identifiers as found in the SPIR-V module, and will // only return true if OpSource was reported HLSL. // To rely on this functionality, ensure that the SPIR-V module is not stripped. bool buffer_get_hlsl_counter_buffer(uint32_t id, uint32_t &counter_id) const; // Gets the list of all SPIR-V Capabilities which were declared in the SPIR-V module. const std::vector &get_declared_capabilities() const; // Gets the list of all SPIR-V extensions which were declared in the SPIR-V module. const std::vector &get_declared_extensions() const; protected: const uint32_t *stream(const Instruction &instr) const { // If we're not going to use any arguments, just return nullptr. // We want to avoid case where we return an out of range pointer // that trips debug assertions on some platforms. if (!instr.length) return nullptr; if (instr.offset + instr.length > spirv.size()) SPIRV_CROSS_THROW("Compiler::stream() out of range."); return &spirv[instr.offset]; } std::vector spirv; std::vector inst; std::vector ids; std::vector meta; SPIRFunction *current_function = nullptr; SPIRBlock *current_block = nullptr; std::vector global_variables; std::vector aliased_variables; std::unordered_set active_interface_variables; bool check_active_interface_variables = false; // If our IDs are out of range here as part of opcodes, throw instead of // undefined behavior. template T &set(uint32_t id, P &&... args) { auto &var = variant_set(ids.at(id), std::forward

(args)...); var.self = id; return var; } template T &get(uint32_t id) { return variant_get(ids.at(id)); } template T *maybe_get(uint32_t id) { if (ids.at(id).get_type() == T::type) return &get(id); else return nullptr; } template const T &get(uint32_t id) const { return variant_get(ids.at(id)); } template const T *maybe_get(uint32_t id) const { if (ids.at(id).get_type() == T::type) return &get(id); else return nullptr; } uint32_t entry_point = 0; // Normally, we'd stick SPIREntryPoint in ids array, but it conflicts with SPIRFunction. // Entry points can therefore be seen as some sort of meta structure. std::unordered_map entry_points; const SPIREntryPoint &get_entry_point() const; SPIREntryPoint &get_entry_point(); struct Source { uint32_t version = 0; bool es = false; bool known = false; bool hlsl = false; Source() = default; } source; std::unordered_set loop_blocks; std::unordered_set continue_blocks; std::unordered_set loop_merge_targets; std::unordered_set selection_merge_targets; std::unordered_set multiselect_merge_targets; virtual std::string to_name(uint32_t id, bool allow_alias = true) const; bool is_builtin_variable(const SPIRVariable &var) const; bool is_hidden_variable(const SPIRVariable &var, bool include_builtins = false) const; bool is_immutable(uint32_t id) const; bool is_member_builtin(const SPIRType &type, uint32_t index, spv::BuiltIn *builtin) const; bool is_scalar(const SPIRType &type) const; bool is_vector(const SPIRType &type) const; bool is_matrix(const SPIRType &type) const; bool is_array(const SPIRType &type) const; uint32_t expression_type_id(uint32_t id) const; const SPIRType &expression_type(uint32_t id) const; bool expression_is_lvalue(uint32_t id) const; bool variable_storage_is_aliased(const SPIRVariable &var); SPIRVariable *maybe_get_backing_variable(uint32_t chain); void register_read(uint32_t expr, uint32_t chain, bool forwarded); void register_write(uint32_t chain); inline bool is_continue(uint32_t next) const { return continue_blocks.find(next) != end(continue_blocks); } inline bool is_break(uint32_t next) const { return loop_merge_targets.find(next) != end(loop_merge_targets) || multiselect_merge_targets.find(next) != end(multiselect_merge_targets); } inline bool is_conditional(uint32_t next) const { return selection_merge_targets.find(next) != end(selection_merge_targets) && multiselect_merge_targets.find(next) == end(multiselect_merge_targets); } // Dependency tracking for temporaries read from variables. void flush_dependees(SPIRVariable &var); void flush_all_active_variables(); void flush_all_atomic_capable_variables(); void flush_all_aliased_variables(); void register_global_read_dependencies(const SPIRBlock &func, uint32_t id); void register_global_read_dependencies(const SPIRFunction &func, uint32_t id); std::unordered_set invalid_expressions; void update_name_cache(std::unordered_set &cache, std::string &name); bool function_is_pure(const SPIRFunction &func); bool block_is_pure(const SPIRBlock &block); bool block_is_outside_flow_control_from_block(const SPIRBlock &from, const SPIRBlock &to); bool execution_is_branchless(const SPIRBlock &from, const SPIRBlock &to) const; bool execution_is_noop(const SPIRBlock &from, const SPIRBlock &to) const; SPIRBlock::ContinueBlockType continue_block_type(const SPIRBlock &continue_block) const; bool force_recompile = false; bool block_is_loop_candidate(const SPIRBlock &block, SPIRBlock::Method method) const; uint32_t increase_bound_by(uint32_t incr_amount); bool types_are_logically_equivalent(const SPIRType &a, const SPIRType &b) const; void inherit_expression_dependencies(uint32_t dst, uint32_t source); // For proper multiple entry point support, allow querying if an Input or Output // variable is part of that entry points interface. bool interface_variable_exists_in_entry_point(uint32_t id) const; std::vector combined_image_samplers; void remap_variable_type_name(const SPIRType &type, const std::string &var_name, std::string &type_name) const { if (variable_remap_callback) variable_remap_callback(type, var_name, type_name); } void analyze_variable_scope(SPIRFunction &function); void parse(); void parse(const Instruction &i); // Used internally to implement various traversals for queries. struct OpcodeHandler { virtual ~OpcodeHandler() = default; // Return true if traversal should continue. // If false, traversal will end immediately. virtual bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) = 0; virtual bool follow_function_call(const SPIRFunction &) { return true; } virtual void set_current_block(const SPIRBlock &) { } virtual bool begin_function_scope(const uint32_t *, uint32_t) { return true; } virtual bool end_function_scope(const uint32_t *, uint32_t) { return true; } }; struct BufferAccessHandler : OpcodeHandler { BufferAccessHandler(const Compiler &compiler_, std::vector &ranges_, uint32_t id_) : compiler(compiler_) , ranges(ranges_) , id(id_) { } bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override; const Compiler &compiler; std::vector &ranges; uint32_t id; std::unordered_set seen; }; struct InterfaceVariableAccessHandler : OpcodeHandler { InterfaceVariableAccessHandler(const Compiler &compiler_, std::unordered_set &variables_) : compiler(compiler_) , variables(variables_) { } bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override; const Compiler &compiler; std::unordered_set &variables; }; struct CombinedImageSamplerHandler : OpcodeHandler { CombinedImageSamplerHandler(Compiler &compiler_) : compiler(compiler_) { } bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override; bool begin_function_scope(const uint32_t *args, uint32_t length) override; bool end_function_scope(const uint32_t *args, uint32_t length) override; Compiler &compiler; // Each function in the call stack needs its own remapping for parameters so we can deduce which global variable each texture/sampler the parameter is statically bound to. std::stack> parameter_remapping; std::stack functions; uint32_t remap_parameter(uint32_t id); void push_remap_parameters(const SPIRFunction &func, const uint32_t *args, uint32_t length); void pop_remap_parameters(); void register_combined_image_sampler(SPIRFunction &caller, uint32_t texture_id, uint32_t sampler_id, bool depth); }; struct ActiveBuiltinHandler : OpcodeHandler { ActiveBuiltinHandler(Compiler &compiler_) : compiler(compiler_) { } bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override; Compiler &compiler; }; bool traverse_all_reachable_opcodes(const SPIRBlock &block, OpcodeHandler &handler) const; bool traverse_all_reachable_opcodes(const SPIRFunction &block, OpcodeHandler &handler) const; // This must be an ordered data structure so we always pick the same type aliases. std::vector global_struct_cache; ShaderResources get_shader_resources(const std::unordered_set *active_variables) const; VariableTypeRemapCallback variable_remap_callback; uint64_t get_buffer_block_flags(const SPIRVariable &var); bool get_common_basic_type(const SPIRType &type, SPIRType::BaseType &base_type); std::unordered_set forced_temporaries; std::unordered_set forwarded_temporaries; uint64_t active_input_builtins = 0; uint64_t active_output_builtins = 0; // Traverses all reachable opcodes and sets active_builtins to a bitmask of all builtin variables which are accessed in the shader. void update_active_builtins(); bool has_active_builtin(spv::BuiltIn builtin, spv::StorageClass storage); void analyze_parameter_preservation( SPIRFunction &entry, const CFG &cfg, const std::unordered_map> &variable_to_blocks, const std::unordered_map> &complete_write_blocks); // If a variable ID or parameter ID is found in this set, a sampler is actually a shadow/comparison sampler. // SPIR-V does not support this distinction, so we must keep track of this information outside the type system. // There might be unrelated IDs found in this set which do not correspond to actual variables. // This set should only be queried for the existence of samplers which are already known to be variables or parameter IDs. std::unordered_set comparison_samplers; void analyze_sampler_comparison_states(); struct CombinedImageSamplerUsageHandler : OpcodeHandler { CombinedImageSamplerUsageHandler(Compiler &compiler_) : compiler(compiler_) { } bool begin_function_scope(const uint32_t *args, uint32_t length) override; bool handle(spv::Op opcode, const uint32_t *args, uint32_t length) override; Compiler &compiler; std::unordered_map> dependency_hierarchy; std::unordered_set comparison_samplers; void add_hierarchy_to_comparison_samplers(uint32_t sampler); }; void make_constant_null(uint32_t id, uint32_t type); std::vector declared_capabilities; std::vector declared_extensions; }; } #endif