blob: 45ed3717ad8d50571b9d5b46b9633f2efaeaf721 [file] [log] [blame]
// 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 <unordered_set>
#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<NestedCompositeComponents>& 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<NestedCompositeComponents> nested_composite_components;
Instruction* component_variable;
};
// Collects all interface variables used by the |entry_point|.
std::vector<Instruction*> 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 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<Instruction*>& interface_var_users,
const NestedCompositeComponents& scalar_interface_vars,
std::vector<uint32_t>& interface_var_component_indices,
const uint32_t* extra_array_index,
std::unordered_map<Instruction*, Instruction*>* loads_to_composites,
std::unordered_map<Instruction*, Instruction*>*
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<Instruction*>& interface_var_users,
const std::vector<NestedCompositeComponents>& components,
std::vector<uint32_t>& interface_var_component_indices,
const uint32_t* extra_array_index,
std::unordered_map<Instruction*, Instruction*>* loads_to_composites,
std::unordered_map<Instruction*, Instruction*>*
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<uint32_t>& interface_var_component_indices,
const uint32_t* extra_array_index,
std::unordered_map<Instruction*, Instruction*>* loads_to_component_values,
std::unordered_map<Instruction*, Instruction*>*
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<uint32_t>& 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<uint32_t>& component_indices,
Instruction* scalar_var,
const std::vector<uint32_t>& 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<uint32_t>& 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<uint32_t>& 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<uint32_t>& 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<uint32_t>& interface_var_component_indices,
Instruction* scalar_var,
std::unordered_map<Instruction*, Instruction*>*
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<Instruction*, Instruction*>&
loads_to_component_values,
std::unordered_map<Instruction*, Instruction*>* 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<uint32_t>& 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<Instruction*>& 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<uint32_t>
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<uint32_t, uint32_t> composite_ids_to_component_depths;
// A set of interface variables with the extra arrayness for any of the entry
// points.
std::unordered_set<Instruction*> vars_with_extra_arrayness;
// A set of interface variables without the extra arrayness for any of the
// entry points.
std::unordered_set<Instruction*> vars_without_extra_arrayness;
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_INTERFACE_VAR_SROA_H_