|  | // Copyright (c) 2017 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. | 
|  |  | 
|  | #ifndef SOURCE_OPT_SCALAR_REPLACEMENT_PASS_H_ | 
|  | #define SOURCE_OPT_SCALAR_REPLACEMENT_PASS_H_ | 
|  |  | 
|  | #include <cstdio> | 
|  | #include <memory> | 
|  | #include <queue> | 
|  | #include <unordered_map> | 
|  | #include <unordered_set> | 
|  | #include <vector> | 
|  |  | 
|  | #include "source/opt/function.h" | 
|  | #include "source/opt/pass.h" | 
|  | #include "source/opt/type_manager.h" | 
|  |  | 
|  | namespace spvtools { | 
|  | namespace opt { | 
|  |  | 
|  | // Documented in optimizer.hpp | 
|  | class ScalarReplacementPass : public Pass { | 
|  | private: | 
|  | static const uint32_t kDefaultLimit = 100; | 
|  |  | 
|  | public: | 
|  | ScalarReplacementPass(uint32_t limit = kDefaultLimit) | 
|  | : max_num_elements_(limit) { | 
|  | name_[0] = '\0'; | 
|  | strcat(name_, "scalar-replacement="); | 
|  | sprintf(&name_[strlen(name_)], "%d", max_num_elements_); | 
|  | } | 
|  |  | 
|  | const char* name() const override { return name_; } | 
|  |  | 
|  | // Attempts to scalarize all appropriate function scope variables. Returns | 
|  | // SuccessWithChange if any change is made. | 
|  | Status Process() override; | 
|  |  | 
|  | IRContext::Analysis GetPreservedAnalyses() override { | 
|  | return IRContext::kAnalysisDefUse | | 
|  | IRContext::kAnalysisInstrToBlockMapping | | 
|  | IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators | | 
|  | IRContext::kAnalysisCFG | IRContext::kAnalysisNameMap | | 
|  | IRContext::kAnalysisConstants | IRContext::kAnalysisTypes; | 
|  | } | 
|  |  | 
|  | private: | 
|  | // Small container for tracking statistics about variables. | 
|  | // | 
|  | // TODO(alanbaker): Develop some useful heuristics to tune this pass. | 
|  | struct VariableStats { | 
|  | uint32_t num_partial_accesses; | 
|  | uint32_t num_full_accesses; | 
|  | }; | 
|  |  | 
|  | // Attempts to scalarize all appropriate function scope variables in | 
|  | // |function|. Returns SuccessWithChange if any changes are mode. | 
|  | Status ProcessFunction(Function* function); | 
|  |  | 
|  | // Returns true if |varInst| can be scalarized. | 
|  | // | 
|  | // Examines the use chain of |varInst| to verify all uses are valid for | 
|  | // scalarization. | 
|  | bool CanReplaceVariable(const Instruction* varInst) const; | 
|  |  | 
|  | // Returns true if |typeInst| is an acceptable type to scalarize. | 
|  | // | 
|  | // Allows all aggregate types except runtime arrays. Additionally, checks the | 
|  | // that the number of elements that would be scalarized is within bounds. | 
|  | bool CheckType(const Instruction* typeInst) const; | 
|  |  | 
|  | // Returns true if all the decorations for |varInst| are acceptable for | 
|  | // scalarization. | 
|  | bool CheckAnnotations(const Instruction* varInst) const; | 
|  |  | 
|  | // Returns true if all the decorations for |typeInst| are acceptable for | 
|  | // scalarization. | 
|  | bool CheckTypeAnnotations(const Instruction* typeInst) const; | 
|  |  | 
|  | // Returns true if the uses of |inst| are acceptable for scalarization. | 
|  | // | 
|  | // Recursively checks all the uses of |inst|. For |inst| specifically, only | 
|  | // allows SpvOpAccessChain, SpvOpInBoundsAccessChain, SpvOpLoad and | 
|  | // SpvOpStore. Access chains must have the first index be a compile-time | 
|  | // constant. Subsequent uses of access chains (including other access chains) | 
|  | // are checked in a more relaxed manner. | 
|  | bool CheckUses(const Instruction* inst) const; | 
|  |  | 
|  | // Helper function for the above |CheckUses|. | 
|  | // | 
|  | // This version tracks some stats about the current OpVariable. These stats | 
|  | // are used to drive heuristics about when to scalarize. | 
|  | bool CheckUses(const Instruction* inst, VariableStats* stats) const; | 
|  |  | 
|  | // Relaxed helper function for |CheckUses|. | 
|  | bool CheckUsesRelaxed(const Instruction* inst) const; | 
|  |  | 
|  | // Transfers appropriate decorations from |source| to |replacements|. | 
|  | void TransferAnnotations(const Instruction* source, | 
|  | std::vector<Instruction*>* replacements); | 
|  |  | 
|  | // Scalarizes |inst| and updates its uses. | 
|  | // | 
|  | // |inst| must be an OpVariable. It is replaced with an OpVariable for each | 
|  | // for element of the composite type. Uses of |inst| are updated as | 
|  | // appropriate. If the replacement variables are themselves scalarizable, they | 
|  | // get added to |worklist| for further processing. If any replacement | 
|  | // variable ends up with no uses it is erased. Returns | 
|  | //  - Status::SuccessWithoutChange if the variable could not be replaced. | 
|  | //  - Status::SuccessWithChange if it made replacements. | 
|  | //  - Status::Failure if it couldn't create replacement variables. | 
|  | Pass::Status ReplaceVariable(Instruction* inst, | 
|  | std::queue<Instruction*>* worklist); | 
|  |  | 
|  | // Returns the underlying storage type for |inst|. | 
|  | // | 
|  | // |inst| must be an OpVariable. Returns the type that is pointed to by | 
|  | // |inst|. | 
|  | Instruction* GetStorageType(const Instruction* inst) const; | 
|  |  | 
|  | // Returns true if the load can be scalarized. | 
|  | // | 
|  | // |inst| must be an OpLoad. Returns true if |index| is the pointer operand of | 
|  | // |inst| and the load is not from volatile memory. | 
|  | bool CheckLoad(const Instruction* inst, uint32_t index) const; | 
|  |  | 
|  | // Returns true if the store can be scalarized. | 
|  | // | 
|  | // |inst| must be an OpStore. Returns true if |index| is the pointer operand | 
|  | // of |inst| and the store is not to volatile memory. | 
|  | bool CheckStore(const Instruction* inst, uint32_t index) const; | 
|  |  | 
|  | // Creates a variable of type |typeId| from the |index|'th element of | 
|  | // |varInst|. The new variable is added to |replacements|.  If the variable | 
|  | // could not be created, then |nullptr| is appended to |replacements|. | 
|  | void CreateVariable(uint32_t typeId, Instruction* varInst, uint32_t index, | 
|  | std::vector<Instruction*>* replacements); | 
|  |  | 
|  | // Populates |replacements| with a new OpVariable for each element of |inst|. | 
|  | // Returns true if the replacement variables were successfully created. | 
|  | // | 
|  | // |inst| must be an OpVariable of a composite type. New variables are | 
|  | // initialized the same as the corresponding index in |inst|. |replacements| | 
|  | // will contain a variable for each element of the composite with matching | 
|  | // indexes (i.e. the 0'th element of |inst| is the 0'th entry of | 
|  | // |replacements|). | 
|  | bool CreateReplacementVariables(Instruction* inst, | 
|  | std::vector<Instruction*>* replacements); | 
|  |  | 
|  | // Returns the array length for |arrayInst|. | 
|  | uint64_t GetArrayLength(const Instruction* arrayInst) const; | 
|  |  | 
|  | // Returns the number of elements in |type|. | 
|  | // | 
|  | // |type| must be a vector or matrix type. | 
|  | uint64_t GetNumElements(const Instruction* type) const; | 
|  |  | 
|  | // Returns true if |id| is a specialization constant. | 
|  | // | 
|  | // |id| must be registered definition. | 
|  | bool IsSpecConstant(uint32_t id) const; | 
|  |  | 
|  | // Returns an id for a pointer to |id|. | 
|  | uint32_t GetOrCreatePointerType(uint32_t id); | 
|  |  | 
|  | // Creates the initial value for the |index| element of |source| in |newVar|. | 
|  | // | 
|  | // If there is an initial value for |source| for element |index|, it is | 
|  | // appended as an operand on |newVar|. If the initial value is OpUndef, no | 
|  | // initial value is added to |newVar|. | 
|  | void GetOrCreateInitialValue(Instruction* source, uint32_t index, | 
|  | Instruction* newVar); | 
|  |  | 
|  | // Replaces the load to the entire composite. | 
|  | // | 
|  | // Generates a load for each replacement variable and then creates a new | 
|  | // composite by combining all of the loads. | 
|  | // | 
|  | // |load| must be a load.  Returns true if successful. | 
|  | bool ReplaceWholeLoad(Instruction* load, | 
|  | const std::vector<Instruction*>& replacements); | 
|  |  | 
|  | // Replaces the store to the entire composite. | 
|  | // | 
|  | // Generates a composite extract and store for each element in the scalarized | 
|  | // variable from the original store data input.  Returns true if successful. | 
|  | bool ReplaceWholeStore(Instruction* store, | 
|  | const std::vector<Instruction*>& replacements); | 
|  |  | 
|  | // Replaces an access chain to the composite variable with either a direct use | 
|  | // of the appropriate replacement variable or another access chain with the | 
|  | // replacement variable as the base and one fewer indexes. Returns true if | 
|  | // successful. | 
|  | bool ReplaceAccessChain(Instruction* chain, | 
|  | const std::vector<Instruction*>& replacements); | 
|  |  | 
|  | // Returns a set containing the which components of the result of |inst| are | 
|  | // potentially used.  If the return value is |nullptr|, then every components | 
|  | // is possibly used. | 
|  | std::unique_ptr<std::unordered_set<int64_t>> GetUsedComponents( | 
|  | Instruction* inst); | 
|  |  | 
|  | // Returns an instruction defining a null constant with type |type_id|.  If | 
|  | // one already exists, it is returned.  Otherwise a new one is created. | 
|  | // Returns |nullptr| if the new constant could not be created. | 
|  | Instruction* CreateNullConstant(uint32_t type_id); | 
|  |  | 
|  | // Maps storage type to a pointer type enclosing that type. | 
|  | std::unordered_map<uint32_t, uint32_t> pointee_to_pointer_; | 
|  |  | 
|  | // Maps type id to OpConstantNull for that type. | 
|  | std::unordered_map<uint32_t, uint32_t> type_to_null_; | 
|  |  | 
|  | // Returns the number of elements in the variable |var_inst|. | 
|  | uint64_t GetMaxLegalIndex(const Instruction* var_inst) const; | 
|  |  | 
|  | // Returns true if |length| is larger than limit on the size of the variable | 
|  | // that we will be willing to split. | 
|  | bool IsLargerThanSizeLimit(uint64_t length) const; | 
|  |  | 
|  | // Limit on the number of members in an object that will be replaced. | 
|  | // 0 means there is no limit. | 
|  | uint32_t max_num_elements_; | 
|  | char name_[55]; | 
|  | }; | 
|  |  | 
|  | }  // namespace opt | 
|  | }  // namespace spvtools | 
|  |  | 
|  | #endif  // SOURCE_OPT_SCALAR_REPLACEMENT_PASS_H_ |