| // Copyright (c) 2017 The Khronos Group Inc. | |
| // Copyright (c) 2017 Valve Corporation | |
| // Copyright (c) 2017 LunarG 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_AGGRESSIVE_DEAD_CODE_ELIM_PASS_H_ | |
| #define SOURCE_OPT_AGGRESSIVE_DEAD_CODE_ELIM_PASS_H_ | |
| #include <algorithm> | |
| #include <list> | |
| #include <map> | |
| #include <queue> | |
| #include <string> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "source/opt/basic_block.h" | |
| #include "source/opt/def_use_manager.h" | |
| #include "source/opt/mem_pass.h" | |
| #include "source/opt/module.h" | |
| #include "source/util/bit_vector.h" | |
| namespace spvtools { | |
| namespace opt { | |
| // See optimizer.hpp for documentation. | |
| class AggressiveDCEPass : public MemPass { | |
| using cbb_ptr = const BasicBlock*; | |
| public: | |
| using GetBlocksFunction = | |
| std::function<std::vector<BasicBlock*>*(const BasicBlock*)>; | |
| AggressiveDCEPass(bool preserve_interface = false, | |
| bool remove_outputs = false) | |
| : preserve_interface_(preserve_interface), | |
| remove_outputs_(remove_outputs) {} | |
| const char* name() const override { return "eliminate-dead-code-aggressive"; } | |
| Status Process() override; | |
| IRContext::Analysis GetPreservedAnalyses() override { | |
| return IRContext::kAnalysisDefUse | | |
| IRContext::kAnalysisInstrToBlockMapping | | |
| IRContext::kAnalysisConstants | IRContext::kAnalysisTypes; | |
| } | |
| private: | |
| // Preserve entry point interface if true. All variables in interface | |
| // will be marked live and will not be eliminated. This mode is needed by | |
| // GPU-Assisted Validation instrumentation where a change in the interface | |
| // is not allowed. | |
| bool preserve_interface_; | |
| // Output variables can be removed from the interface if this is true. | |
| // This is safe if the caller knows that the corresponding input variable | |
| // in the following shader has been removed. It is false by default. | |
| bool remove_outputs_; | |
| // Return true if |varId| is a variable of |storageClass|. |varId| must either | |
| // be 0 or the result of an instruction. | |
| bool IsVarOfStorage(uint32_t varId, spv::StorageClass storageClass); | |
| // Return true if the instance of the variable |varId| can only be access in | |
| // |func|. For example, a function scope variable, or a private variable | |
| // where |func| is an entry point with no function calls. | |
| bool IsLocalVar(uint32_t varId, Function* func); | |
| // Return true if |inst| is marked live. | |
| bool IsLive(const Instruction* inst) const { | |
| return live_insts_.Get(inst->unique_id()); | |
| } | |
| // Adds entry points, execution modes and workgroup size decorations to the | |
| // worklist for processing with the first function. | |
| void InitializeModuleScopeLiveInstructions(); | |
| // Add |inst| to worklist_ and live_insts_. | |
| void AddToWorklist(Instruction* inst) { | |
| if (!live_insts_.Set(inst->unique_id())) { | |
| worklist_.push(inst); | |
| } | |
| } | |
| // Add all store instruction which use |ptrId|, directly or indirectly, | |
| // to the live instruction worklist. | |
| void AddStores(Function* func, uint32_t ptrId); | |
| // Initialize extensions allowlist | |
| void InitExtensions(); | |
| // Return true if all extensions in this module are supported by this pass. | |
| bool AllExtensionsSupported() const; | |
| // Returns true if the target of |inst| is dead. An instruction is dead if | |
| // its result id is used in decoration or debug instructions only. |inst| is | |
| // assumed to be OpName, OpMemberName or an annotation instruction. | |
| bool IsTargetDead(Instruction* inst); | |
| // If |varId| is local, mark all stores of varId as live. | |
| void ProcessLoad(Function* func, uint32_t varId); | |
| // Add branch to |labelId| to end of block |bp|. | |
| void AddBranch(uint32_t labelId, BasicBlock* bp); | |
| // Add all break and continue branches in the construct associated with | |
| // |mergeInst| to worklist if not already live | |
| void AddBreaksAndContinuesToWorklist(Instruction* mergeInst); | |
| // Eliminates dead debug2 and annotation instructions. Marks dead globals for | |
| // removal (e.g. types, constants and variables). | |
| bool ProcessGlobalValues(); | |
| // Erases functions that are unreachable from the entry points of the module. | |
| bool EliminateDeadFunctions(); | |
| // For function |func|, mark all Stores to non-function-scope variables | |
| // and block terminating instructions as live. Recursively mark the values | |
| // they use. When complete, mark any non-live instructions to be deleted. | |
| // Returns true if the function has been modified. | |
| // | |
| // Note: This function does not delete useless control structures. All | |
| // existing control structures will remain. This can leave not-insignificant | |
| // sequences of ultimately useless code. | |
| // TODO(): Remove useless control constructs. | |
| bool AggressiveDCE(Function* func); | |
| Pass::Status ProcessImpl(); | |
| // Adds instructions which must be kept because of they have side-effects | |
| // that ADCE cannot model to the work list. | |
| void InitializeWorkList(Function* func, | |
| std::list<BasicBlock*>& structured_order); | |
| // Process each instruction in the work list by marking any instruction that | |
| // that it depends on as live, and adding it to the work list. The work list | |
| // will be empty at the end. | |
| void ProcessWorkList(Function* func); | |
| // Kills any instructions in |func| that have not been marked as live. | |
| bool KillDeadInstructions(const Function* func, | |
| std::list<BasicBlock*>& structured_order); | |
| // Adds the instructions that define the operands of |inst| to the work list. | |
| void AddOperandsToWorkList(const Instruction* inst); | |
| // Marks all of the labels and branch that inst requires as live. | |
| void MarkBlockAsLive(Instruction* inst); | |
| // Marks any variables from which |inst| may require data as live. | |
| void MarkLoadedVariablesAsLive(Function* func, Instruction* inst); | |
| // Returns the id of the variable that |ptr_id| point to. |ptr_id| must be a | |
| // value whose type is a pointer. | |
| uint32_t GetVariableId(uint32_t ptr_id); | |
| // Returns all of the ids for the variables from which |inst| will load data. | |
| std::vector<uint32_t> GetLoadedVariables(Instruction* inst); | |
| // Returns all of the ids for the variables from which |inst| will load data. | |
| // The opcode of |inst| must be OpFunctionCall. | |
| std::vector<uint32_t> GetLoadedVariablesFromFunctionCall( | |
| const Instruction* inst); | |
| // Returns the id of the variable from which |inst| will load data. |inst| | |
| // must not be an OpFunctionCall. Returns 0 if no data is read or the | |
| // variable cannot be determined. Note that in logical addressing mode the | |
| // latter is not possible for function and private storage class because there | |
| // cannot be variable pointers pointing to those storage classes. | |
| uint32_t GetLoadedVariableFromNonFunctionCalls(Instruction* inst); | |
| // Adds all decorations of |inst| to the work list. | |
| void AddDecorationsToWorkList(const Instruction* inst); | |
| // Adds DebugScope instruction associated with |inst| to the work list. | |
| void AddDebugScopeToWorkList(const Instruction* inst); | |
| // Adds all debug instruction associated with |inst| to the work list. | |
| void AddDebugInstructionsToWorkList(const Instruction* inst); | |
| // Marks all of the OpFunctionParameter instructions in |func| as live. | |
| void MarkFunctionParameterAsLive(const Function* func); | |
| // Returns the terminator instruction in the header for the innermost | |
| // construct that contains |blk|. Returns nullptr if no such header exists. | |
| Instruction* GetHeaderBranch(BasicBlock* blk); | |
| // Returns the header for the innermost construct that contains |blk|. A loop | |
| // header will be its own header. Returns nullptr if no such header exists. | |
| BasicBlock* GetHeaderBlock(BasicBlock* blk) const; | |
| // Returns the same as |GetHeaderBlock| except if |blk| is a loop header it | |
| // will return the header of the next enclosing construct. Returns nullptr if | |
| // no such header exists. | |
| Instruction* GetBranchForNextHeader(BasicBlock* blk); | |
| // Returns the merge instruction in the same basic block as |inst|. Returns | |
| // nullptr if one does not exist. | |
| Instruction* GetMergeInstruction(Instruction* inst); | |
| // Returns true if |bb| is in the construct with header |header_block|. | |
| bool BlockIsInConstruct(BasicBlock* header_block, BasicBlock* bb); | |
| // Returns true if |func| is an entry point that does not have any function | |
| // calls. | |
| bool IsEntryPointWithNoCalls(Function* func); | |
| // Returns true if |func| is an entry point. | |
| bool IsEntryPoint(Function* func); | |
| // Returns true if |func| contains a function call. | |
| bool HasCall(Function* func); | |
| // Marks the first block, which is the entry block, in |func| as live. | |
| void MarkFirstBlockAsLive(Function* func); | |
| // Adds an OpUnreachable instruction at the end of |block|. | |
| void AddUnreachable(BasicBlock*& block); | |
| // Marks the OpLoopMerge and the terminator in |basic_block| as live if | |
| // |basic_block| is a loop header. | |
| void MarkLoopConstructAsLiveIfLoopHeader(BasicBlock* basic_block); | |
| // The cached results for |IsEntryPointWithNoCalls|. It maps the function's | |
| // result id to the return value. | |
| std::unordered_map<uint32_t, bool> entry_point_with_no_calls_cache_; | |
| // Live Instruction Worklist. An instruction is added to this list | |
| // if it might have a side effect, either directly or indirectly. | |
| // If we don't know, then add it to this list. Instructions are | |
| // removed from this list as the algorithm traces side effects, | |
| // building up the live instructions set |live_insts_|. | |
| std::queue<Instruction*> worklist_; | |
| // Live Instructions | |
| utils::BitVector live_insts_; | |
| // Live Local Variables | |
| std::unordered_set<uint32_t> live_local_vars_; | |
| // List of instructions to delete. Deletion is delayed until debug and | |
| // annotation instructions are processed. | |
| std::vector<Instruction*> to_kill_; | |
| // Extensions supported by this pass. | |
| std::unordered_set<std::string> extensions_allowlist_; | |
| }; | |
| } // namespace opt | |
| } // namespace spvtools | |
| #endif // SOURCE_OPT_AGGRESSIVE_DEAD_CODE_ELIM_PASS_H_ |