| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- // 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() == spv::Op::OpPhi) {
- // 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() != spv::Op::OpPhi) {
- 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
|