// Copyright (c) 2022 Google LLC // // 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_INTERFACE_VAR_SROA_H_ #define SOURCE_OPT_INTERFACE_VAR_SROA_H_ #include #include "source/opt/pass.h" namespace spvtools { namespace opt { // See optimizer.hpp for documentation. // // Note that the current implementation of this pass covers only store, load, // access chain instructions for the interface variables. Supporting other types // of instructions is a future work. class InterfaceVariableScalarReplacement : public Pass { public: InterfaceVariableScalarReplacement() {} const char* name() const override { return "interface-variable-scalar-replacement"; } Status Process() override; IRContext::Analysis GetPreservedAnalyses() override { return IRContext::kAnalysisDecorations | IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants | IRContext::kAnalysisTypes; } private: // A struct containing components of a composite variable. If the composite // consists of multiple or recursive components, |component_variable| is // nullptr and |nested_composite_components| keeps the components. If it has a // single component, |nested_composite_components| is empty and // |component_variable| is the component. Note that each element of // |nested_composite_components| has the NestedCompositeComponents struct as // its type that can recursively keep the components. struct NestedCompositeComponents { NestedCompositeComponents() : component_variable(nullptr) {} bool HasMultipleComponents() const { return !nested_composite_components.empty(); } const std::vector& GetComponents() const { return nested_composite_components; } void AddComponent(const NestedCompositeComponents& component) { nested_composite_components.push_back(component); } Instruction* GetComponentVariable() const { return component_variable; } void SetSingleComponentVariable(Instruction* var) { component_variable = var; } private: std::vector nested_composite_components; Instruction* component_variable; }; // Collects all interface variables used by the |entry_point|. std::vector CollectInterfaceVariables(Instruction& entry_point); // Returns whether |var| has the extra arrayness for the entry point // |entry_point| or not. bool HasExtraArrayness(Instruction& entry_point, Instruction* var); // Finds a Location BuiltIn decoration of |var| and returns it via // |location|. Returns true whether the location exists or not. bool GetVariableLocation(Instruction* var, uint32_t* location); // Finds a Component BuiltIn decoration of |var| and returns it via // |component|. Returns true whether the component exists or not. bool GetVariableComponent(Instruction* var, uint32_t* component); // Returns the interface variable instruction whose result id is // |interface_var_id|. Instruction* GetInterfaceVariable(uint32_t interface_var_id); // Returns the type of |var| as an instruction. Instruction* GetTypeOfVariable(Instruction* var); // Replaces an interface variable |interface_var| whose type is // |interface_var_type| with scalars and returns whether it succeeds or not. // |location| is the value of Location Decoration for |interface_var|. // |component| is the value of Component Decoration for |interface_var|. // If |extra_array_length| is 0, it means |interface_var| has a Patch // decoration. Otherwise, |extra_array_length| denotes the length of the extra // array of |interface_var|. bool ReplaceInterfaceVariableWithScalars(Instruction* interface_var, Instruction* interface_var_type, uint32_t location, uint32_t component, uint32_t extra_array_length); // Creates scalar variables with the storage classe |storage_class| to replace // an interface variable whose type is |interface_var_type|. If // |extra_array_length| is not zero, adds the extra arrayness to the created // scalar variables. NestedCompositeComponents CreateScalarInterfaceVarsForReplacement( Instruction* interface_var_type, spv::StorageClass storage_class, uint32_t extra_array_length); // Creates scalar variables with the storage classe |storage_class| to replace // the interface variable whose type is OpTypeArray |interface_var_type| with. // If |extra_array_length| is not zero, adds the extra arrayness to all the // scalar variables. NestedCompositeComponents CreateScalarInterfaceVarsForArray( Instruction* interface_var_type, spv::StorageClass storage_class, uint32_t extra_array_length); // Creates scalar variables with the storage classe |storage_class| to replace // the interface variable whose type is OpTypeMatrix |interface_var_type| // with. If |extra_array_length| is not zero, adds the extra arrayness to all // the scalar variables. NestedCompositeComponents CreateScalarInterfaceVarsForMatrix( Instruction* interface_var_type, spv::StorageClass storage_class, uint32_t extra_array_length); // Recursively adds Location and Component decorations to variables in // |vars| with |location| and |component|. Increases |location| by one after // it actually adds Location and Component decorations for a variable. void AddLocationAndComponentDecorations(const NestedCompositeComponents& vars, uint32_t* location, uint32_t component); // Replaces the interface variable |interface_var| with // |scalar_interface_vars| and returns whether it succeeds or not. // |extra_arrayness| is the extra arrayness of the interface variable. // |scalar_interface_vars| contains the nested variables to replace the // interface variable with. bool ReplaceInterfaceVarWith( Instruction* interface_var, uint32_t extra_arrayness, const NestedCompositeComponents& scalar_interface_vars); // Replaces |interface_var| in the operands of instructions // |interface_var_users| with |scalar_interface_vars|. This is a recursive // method and |interface_var_component_indices| is used to specify which // recursive component of |interface_var| is replaced. Returns composite // construct instructions to be replaced with load instructions of // |interface_var_users| via |loads_to_composites|. Returns composite // construct instructions to be replaced with load instructions of access // chain instructions in |interface_var_users| via // |loads_for_access_chain_to_composites|. bool ReplaceComponentsOfInterfaceVarWith( Instruction* interface_var, const std::vector& interface_var_users, const NestedCompositeComponents& scalar_interface_vars, std::vector& interface_var_component_indices, const uint32_t* extra_array_index, std::unordered_map* loads_to_composites, std::unordered_map* loads_for_access_chain_to_composites); // Replaces |interface_var| in the operands of instructions // |interface_var_users| with |components| that is a vector of components for // the interface variable |interface_var|. This is a recursive method and // |interface_var_component_indices| is used to specify which recursive // component of |interface_var| is replaced. Returns composite construct // instructions to be replaced with load instructions of |interface_var_users| // via |loads_to_composites|. Returns composite construct instructions to be // replaced with load instructions of access chain instructions in // |interface_var_users| via |loads_for_access_chain_to_composites|. bool ReplaceMultipleComponentsOfInterfaceVarWith( Instruction* interface_var, const std::vector& interface_var_users, const std::vector& components, std::vector& interface_var_component_indices, const uint32_t* extra_array_index, std::unordered_map* loads_to_composites, std::unordered_map* loads_for_access_chain_to_composites); // Replaces a component of |interface_var| that is used as an operand of // instruction |interface_var_user| with |scalar_var|. // |interface_var_component_indices| is a vector of recursive indices for // which recursive component of |interface_var| is replaced. If // |interface_var_user| is a load, returns the component value via // |loads_to_component_values|. If |interface_var_user| is an access chain, // returns the component value for loads of |interface_var_user| via // |loads_for_access_chain_to_component_values|. bool ReplaceComponentOfInterfaceVarWith( Instruction* interface_var, Instruction* interface_var_user, Instruction* scalar_var, const std::vector& interface_var_component_indices, const uint32_t* extra_array_index, std::unordered_map* loads_to_component_values, std::unordered_map* loads_for_access_chain_to_component_values); // Creates instructions to load |scalar_var| and inserts them before // |insert_before|. If |extra_array_index| is not null, they load // |extra_array_index| th component of |scalar_var| instead of |scalar_var| // itself. Instruction* LoadScalarVar(Instruction* scalar_var, const uint32_t* extra_array_index, Instruction* insert_before); // Creates instructions to load an access chain to |var| and inserts them // before |insert_before|. |Indexes| will be Indexes operand of the access // chain. Instruction* LoadAccessChainToVar(Instruction* var, const std::vector& indexes, Instruction* insert_before); // Creates instructions to store a component of an aggregate whose id is // |value_id| to an access chain to |scalar_var| and inserts the created // instructions before |insert_before|. To get the component, recursively // traverses the aggregate with |component_indices| as indexes. // Numbers in |access_chain_indices| are the Indexes operand of the access // chain to |scalar_var| void StoreComponentOfValueToAccessChainToScalarVar( uint32_t value_id, const std::vector& component_indices, Instruction* scalar_var, const std::vector& access_chain_indices, Instruction* insert_before); // Creates instructions to store a component of an aggregate whose id is // |value_id| to |scalar_var| and inserts the created instructions before // |insert_before|. To get the component, recursively traverses the aggregate // using |extra_array_index| and |component_indices| as indexes. void StoreComponentOfValueToScalarVar( uint32_t value_id, const std::vector& component_indices, Instruction* scalar_var, const uint32_t* extra_array_index, Instruction* insert_before); // Creates instructions to store a component of an aggregate whose id is // |value_id| to |ptr| and inserts the created instructions before // |insert_before|. To get the component, recursively traverses the aggregate // using |extra_array_index| and |component_indices| as indexes. // |component_type_id| is the id of the type instruction of the component. void StoreComponentOfValueTo(uint32_t component_type_id, uint32_t value_id, const std::vector& component_indices, Instruction* ptr, const uint32_t* extra_array_index, Instruction* insert_before); // Creates new OpCompositeExtract with |type_id| for Result Type, // |composite_id| for Composite operand, and |indexes| for Indexes operands. // If |extra_first_index| is not nullptr, uses it as the first Indexes // operand. Instruction* CreateCompositeExtract(uint32_t type_id, uint32_t composite_id, const std::vector& indexes, const uint32_t* extra_first_index); // Creates a new OpLoad whose Result Type is |type_id| and Pointer operand is // |ptr|. Inserts the new instruction before |insert_before|. Instruction* CreateLoad(uint32_t type_id, Instruction* ptr, Instruction* insert_before); // Clones an annotation instruction |annotation_inst| and sets the target // operand of the new annotation instruction as |var_id|. void CloneAnnotationForVariable(Instruction* annotation_inst, uint32_t var_id); // Replaces the interface variable |interface_var| in the operands of the // entry point |entry_point| with |scalar_var_id|. If it cannot find // |interface_var| from the operands of the entry point |entry_point|, adds // |scalar_var_id| as an operand of the entry point |entry_point|. bool ReplaceInterfaceVarInEntryPoint(Instruction* interface_var, Instruction* entry_point, uint32_t scalar_var_id); // Creates an access chain instruction whose Base operand is |var| and Indexes // operand is |index|. |component_type_id| is the id of the type instruction // that is the type of component. Inserts the new access chain before // |insert_before|. Instruction* CreateAccessChainWithIndex(uint32_t component_type_id, Instruction* var, uint32_t index, Instruction* insert_before); // Returns the pointee type of the type of variable |var|. uint32_t GetPointeeTypeIdOfVar(Instruction* var); // Replaces the access chain |access_chain| and its users with a new access // chain that points |scalar_var| as the Base operand having // |interface_var_component_indices| as Indexes operands and users of the new // access chain. When some of the users are load instructions, returns the // original load instruction to the new instruction that loads a component of // the original load value via |loads_to_component_values|. void ReplaceAccessChainWith( Instruction* access_chain, const std::vector& interface_var_component_indices, Instruction* scalar_var, std::unordered_map* loads_to_component_values); // Assuming that |access_chain| is an access chain instruction whose Base // operand is |base_access_chain|, replaces the operands of |access_chain| // with operands of |base_access_chain| and Indexes operands of // |access_chain|. void UseBaseAccessChainForAccessChain(Instruction* access_chain, Instruction* base_access_chain); // Creates composite construct instructions for load instructions that are the // keys of |loads_to_component_values| if no such composite construct // instructions exist. Adds a component of the composite as an operand of the // created composite construct instruction. Each value of // |loads_to_component_values| is the component. Returns the created composite // construct instructions using |loads_to_composites|. |depth_to_component| is // the number of recursive access steps to get the component from the // composite. void AddComponentsToCompositesForLoads( const std::unordered_map& loads_to_component_values, std::unordered_map* loads_to_composites, uint32_t depth_to_component); // Creates a composite construct instruction for a component of the value of // instruction |load| in |depth_to_component| th recursive depth and inserts // it after |load|. Instruction* CreateCompositeConstructForComponentOfLoad( Instruction* load, uint32_t depth_to_component); // Creates a new access chain instruction that points to variable |var| whose // type is the instruction with |var_type_id| and inserts it before // |insert_before|. The new access chain will have |index_ids| for Indexes // operands. Returns the type id of the component that is pointed by the new // access chain via |component_type_id|. Instruction* CreateAccessChainToVar(uint32_t var_type_id, Instruction* var, const std::vector& index_ids, Instruction* insert_before, uint32_t* component_type_id); // Returns the result id of OpTypeArray instrunction whose Element Type // operand is |elem_type_id| and Length operand is |array_length|. uint32_t GetArrayType(uint32_t elem_type_id, uint32_t array_length); // Returns the result id of OpTypePointer instrunction whose Type // operand is |type_id| and Storage Class operand is |storage_class|. uint32_t GetPointerType(uint32_t type_id, spv::StorageClass storage_class); // Kills an instrunction |inst| and its users. void KillInstructionAndUsers(Instruction* inst); // Kills a vector of instrunctions |insts| and their users. void KillInstructionsAndUsers(const std::vector& insts); // Kills all OpDecorate instructions for Location and Component of the // variable whose id is |var_id|. void KillLocationAndComponentDecorations(uint32_t var_id); // If |var| has the extra arrayness for an entry point, reports an error and // returns true. Otherwise, returns false. bool ReportErrorIfHasExtraArraynessForOtherEntry(Instruction* var); // If |var| does not have the extra arrayness for an entry point, reports an // error and returns true. Otherwise, returns false. bool ReportErrorIfHasNoExtraArraynessForOtherEntry(Instruction* var); // If |interface_var| has the extra arrayness for an entry point but it does // not have one for another entry point, reports an error and returns false. // Otherwise, returns true. |has_extra_arrayness| denotes whether it has an // extra arrayness for an entry point or not. bool CheckExtraArraynessConflictBetweenEntries(Instruction* interface_var, bool has_extra_arrayness); // Conducts the scalar replacement for the interface variables used by the // |entry_point|. Pass::Status ReplaceInterfaceVarsWithScalars(Instruction& entry_point); // A set of interface variable ids that were already removed from operands of // the entry point. std::unordered_set interface_vars_removed_from_entry_point_operands_; // A mapping from ids of new composite construct instructions that load // instructions are replaced with to the recursive depth of the component of // load that the new component construct instruction is used for. std::unordered_map composite_ids_to_component_depths; // A set of interface variables with the extra arrayness for any of the entry // points. std::unordered_set vars_with_extra_arrayness; // A set of interface variables without the extra arrayness for any of the // entry points. std::unordered_set vars_without_extra_arrayness; }; } // namespace opt } // namespace spvtools #endif // SOURCE_OPT_INTERFACE_VAR_SROA_H_