| // 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. |
| |
| #include "source/opt/propagator.h" |
| |
| namespace spvtools { |
| namespace opt { |
| |
| void SSAPropagator::AddControlEdge(const Edge& edge) { |
| BasicBlock* dest_bb = edge.dest; |
| |
| // Refuse to add the exit block to the work list. |
| if (dest_bb == ctx_->cfg()->pseudo_exit_block()) { |
| return; |
| } |
| |
| // Try to mark the edge executable. If it was already in the set of |
| // executable edges, do nothing. |
| if (!MarkEdgeExecutable(edge)) { |
| return; |
| } |
| |
| // If the edge had not already been marked executable, add the destination |
| // basic block to the work list. |
| blocks_.push(dest_bb); |
| } |
| |
| void SSAPropagator::AddSSAEdges(Instruction* instr) { |
| // Ignore instructions that produce no result. |
| if (instr->result_id() == 0) { |
| return; |
| } |
| |
| get_def_use_mgr()->ForEachUser( |
| instr->result_id(), [this](Instruction* use_instr) { |
| // If the basic block for |use_instr| has not been simulated yet, do |
| // nothing. The instruction |use_instr| will be simulated next time the |
| // block is scheduled. |
| if (!BlockHasBeenSimulated(ctx_->get_instr_block(use_instr))) { |
| return; |
| } |
| |
| if (ShouldSimulateAgain(use_instr)) { |
| ssa_edge_uses_.push(use_instr); |
| } |
| }); |
| } |
| |
| bool SSAPropagator::IsPhiArgExecutable(Instruction* phi, uint32_t i) const { |
| BasicBlock* phi_bb = ctx_->get_instr_block(phi); |
| |
| uint32_t in_label_id = phi->GetSingleWordOperand(i + 1); |
| Instruction* in_label_instr = get_def_use_mgr()->GetDef(in_label_id); |
| BasicBlock* in_bb = ctx_->get_instr_block(in_label_instr); |
| |
| return IsEdgeExecutable(Edge(in_bb, phi_bb)); |
| } |
| |
| bool SSAPropagator::SetStatus(Instruction* inst, PropStatus status) { |
| bool has_old_status = false; |
| PropStatus old_status = kVarying; |
| if (HasStatus(inst)) { |
| has_old_status = true; |
| old_status = Status(inst); |
| } |
| |
| assert((!has_old_status || old_status <= status) && |
| "Invalid lattice transition"); |
| |
| bool status_changed = !has_old_status || (old_status != status); |
| if (status_changed) statuses_[inst] = status; |
| |
| return status_changed; |
| } |
| |
| bool SSAPropagator::Simulate(Instruction* instr) { |
| bool changed = false; |
| |
| // Don't bother visiting instructions that should not be simulated again. |
| if (!ShouldSimulateAgain(instr)) { |
| return changed; |
| } |
| |
| BasicBlock* dest_bb = nullptr; |
| PropStatus status = visit_fn_(instr, &dest_bb); |
| bool status_changed = SetStatus(instr, status); |
| |
| if (status == kVarying) { |
| // The statement produces a varying result, add it to the list of statements |
| // not to simulate anymore and add its SSA def-use edges for simulation. |
| DontSimulateAgain(instr); |
| if (status_changed) { |
| AddSSAEdges(instr); |
| } |
| |
| // If |instr| is a block terminator, add all the control edges out of its |
| // block. |
| if (instr->IsBlockTerminator()) { |
| BasicBlock* block = ctx_->get_instr_block(instr); |
| for (const auto& e : bb_succs_.at(block)) { |
| AddControlEdge(e); |
| } |
| } |
| return false; |
| } else if (status == kInteresting) { |
| // Add the SSA edges coming out of this instruction if the propagation |
| // status has changed. |
| if (status_changed) { |
| AddSSAEdges(instr); |
| } |
| |
| // If there are multiple outgoing control flow edges and we know which one |
| // will be taken, add the destination block to the CFG work list. |
| if (dest_bb) { |
| AddControlEdge(Edge(ctx_->get_instr_block(instr), dest_bb)); |
| } |
| changed = true; |
| } |
| |
| // At this point, we are dealing with instructions that are in status |
| // kInteresting or kNotInteresting. To decide whether this instruction should |
| // be simulated again, we examine its operands. If at least one operand O is |
| // defined at an instruction D that should be simulated again, then the output |
| // of D might affect |instr|, so we should simulate |instr| again. |
| bool has_operands_to_simulate = false; |
| if (instr->opcode() == SpvOpPhi) { |
| // For Phi instructions, an operand causes the Phi to be simulated again if |
| // the operand comes from an edge that has not yet been traversed or if its |
| // definition should be simulated again. |
| for (uint32_t i = 2; i < instr->NumOperands(); i += 2) { |
| // Phi arguments come in pairs. Index 'i' contains the |
| // variable id, index 'i + 1' is the originating block id. |
| assert(i % 2 == 0 && i < instr->NumOperands() - 1 && |
| "malformed Phi arguments"); |
| |
| uint32_t arg_id = instr->GetSingleWordOperand(i); |
| Instruction* arg_def_instr = get_def_use_mgr()->GetDef(arg_id); |
| if (!IsPhiArgExecutable(instr, i) || ShouldSimulateAgain(arg_def_instr)) { |
| has_operands_to_simulate = true; |
| break; |
| } |
| } |
| } else { |
| // For regular instructions, check if the defining instruction of each |
| // operand needs to be simulated again. If so, then this instruction should |
| // also be simulated again. |
| has_operands_to_simulate = |
| !instr->WhileEachInId([this](const uint32_t* use) { |
| Instruction* def_instr = get_def_use_mgr()->GetDef(*use); |
| if (ShouldSimulateAgain(def_instr)) { |
| return false; |
| } |
| return true; |
| }); |
| } |
| |
| if (!has_operands_to_simulate) { |
| DontSimulateAgain(instr); |
| } |
| |
| return changed; |
| } |
| |
| bool SSAPropagator::Simulate(BasicBlock* block) { |
| if (block == ctx_->cfg()->pseudo_exit_block()) { |
| return false; |
| } |
| |
| // Always simulate Phi instructions, even if we have simulated this block |
| // before. We do this because Phi instructions receive their inputs from |
| // incoming edges. When those edges are marked executable, the corresponding |
| // operand can be simulated. |
| bool changed = false; |
| block->ForEachPhiInst( |
| [&changed, this](Instruction* instr) { changed |= Simulate(instr); }); |
| |
| // If this is the first time this block is being simulated, simulate every |
| // statement in it. |
| if (!BlockHasBeenSimulated(block)) { |
| block->ForEachInst([this, &changed](Instruction* instr) { |
| if (instr->opcode() != SpvOpPhi) { |
| changed |= Simulate(instr); |
| } |
| }); |
| |
| MarkBlockSimulated(block); |
| |
| // If this block has exactly one successor, mark the edge to its successor |
| // as executable. |
| if (bb_succs_.at(block).size() == 1) { |
| AddControlEdge(bb_succs_.at(block).at(0)); |
| } |
| } |
| |
| return changed; |
| } |
| |
| void SSAPropagator::Initialize(Function* fn) { |
| // Compute predecessor and successor blocks for every block in |fn|'s CFG. |
| // TODO(dnovillo): Move this to CFG and always build them. Alternately, |
| // move it to IRContext and build CFG preds/succs on-demand. |
| bb_succs_[ctx_->cfg()->pseudo_entry_block()].push_back( |
| Edge(ctx_->cfg()->pseudo_entry_block(), fn->entry().get())); |
| |
| for (auto& block : *fn) { |
| const auto& const_block = block; |
| const_block.ForEachSuccessorLabel([this, &block](const uint32_t label_id) { |
| BasicBlock* succ_bb = |
| ctx_->get_instr_block(get_def_use_mgr()->GetDef(label_id)); |
| bb_succs_[&block].push_back(Edge(&block, succ_bb)); |
| bb_preds_[succ_bb].push_back(Edge(succ_bb, &block)); |
| }); |
| if (block.IsReturnOrAbort()) { |
| bb_succs_[&block].push_back( |
| Edge(&block, ctx_->cfg()->pseudo_exit_block())); |
| bb_preds_[ctx_->cfg()->pseudo_exit_block()].push_back( |
| Edge(ctx_->cfg()->pseudo_exit_block(), &block)); |
| } |
| } |
| |
| // Add the edges out of the entry block to seed the propagator. |
| const auto& entry_succs = bb_succs_[ctx_->cfg()->pseudo_entry_block()]; |
| for (const auto& e : entry_succs) { |
| AddControlEdge(e); |
| } |
| } |
| |
| bool SSAPropagator::Run(Function* fn) { |
| Initialize(fn); |
| |
| bool changed = false; |
| while (!blocks_.empty() || !ssa_edge_uses_.empty()) { |
| // Simulate all blocks first. Simulating blocks will add SSA edges to |
| // follow after all the blocks have been simulated. |
| if (!blocks_.empty()) { |
| auto block = blocks_.front(); |
| changed |= Simulate(block); |
| blocks_.pop(); |
| continue; |
| } |
| |
| // Simulate edges from the SSA queue. |
| if (!ssa_edge_uses_.empty()) { |
| Instruction* instr = ssa_edge_uses_.front(); |
| changed |= Simulate(instr); |
| ssa_edge_uses_.pop(); |
| } |
| } |
| |
| #ifndef NDEBUG |
| // Verify all visited values have settled. No value that has been simulated |
| // should end on not interesting. |
| fn->ForEachInst([this](Instruction* inst) { |
| assert( |
| (!HasStatus(inst) || Status(inst) != SSAPropagator::kNotInteresting) && |
| "Unsettled value"); |
| }); |
| #endif |
| |
| return changed; |
| } |
| |
| std::ostream& operator<<(std::ostream& str, |
| const SSAPropagator::PropStatus& status) { |
| switch (status) { |
| case SSAPropagator::kVarying: |
| str << "Varying"; |
| break; |
| case SSAPropagator::kInteresting: |
| str << "Interesting"; |
| break; |
| default: |
| str << "Not interesting"; |
| break; |
| } |
| return str; |
| } |
| |
| } // namespace opt |
| } // namespace spvtools |