// 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 #include #include #include #include #include #include #include #include #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*(const BasicBlock*)>; AggressiveDCEPass(bool preserve_interface = false) : preserve_interface_(preserve_interface) {} 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_; // 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, uint32_t 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& 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& 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 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 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 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 worklist_; // Live Instructions utils::BitVector live_insts_; // Live Local Variables std::unordered_set live_local_vars_; // List of instructions to delete. Deletion is delayed until debug and // annotation instructions are processed. std::vector to_kill_; // Extensions supported by this pass. std::unordered_set extensions_allowlist_; }; } // namespace opt } // namespace spvtools #endif // SOURCE_OPT_AGGRESSIVE_DEAD_CODE_ELIM_PASS_H_