Generic value propagation engine.
authorDiego Novillo <dnovillo@google.com>
Fri, 17 Nov 2017 13:59:25 +0000 (08:59 -0500)
committerDiego Novillo <dnovillo@google.com>
Tue, 28 Nov 2017 04:32:06 +0000 (23:32 -0500)
This class implements a generic value propagation algorithm based on the
conditional constant propagation algorithm proposed in

     Constant propagation with conditional branches,
     Wegman and Zadeck, ACM TOPLAS 13(2):181-210.

The implementation is based on

     A Propagation Engine for GCC
     Diego Novillo, GCC Summit 2005
     http://ols.fedoraproject.org/GCC/Reprints-2005/novillo-Reprint.pdf

The purpose of this implementation is to act as a common framework for any
transformation that needs to propagate values from statements producing new
values to statements using those values.

14 files changed:
Android.mk
source/opcode.cpp
source/opcode.h
source/opt/CMakeLists.txt
source/opt/aggressive_dead_code_elim_pass.cpp
source/opt/aggressive_dead_code_elim_pass.h
source/opt/basic_block.h
source/opt/cfg.h
source/opt/instruction.h
source/opt/propagator.cpp [new file with mode: 0644]
source/opt/propagator.h [new file with mode: 0644]
test/opt/CMakeLists.txt
test/opt/aggressive_dead_code_elim_test.cpp
test/opt/propagator_test.cpp [new file with mode: 0644]

index 03c24c0..920b1ce 100644 (file)
@@ -87,6 +87,7 @@ SPVTOOLS_OPT_SRC_FILES := \
                source/opt/optimizer.cpp \
                source/opt/pass.cpp \
                source/opt/pass_manager.cpp \
+               source/opt/propagator.cpp \
                source/opt/remove_duplicates_pass.cpp \
                source/opt/set_spec_constant_default_value_pass.cpp \
                source/opt/strength_reduction_pass.cpp \
index 50f5e52..a9ab1e6 100644 (file)
@@ -360,6 +360,17 @@ bool spvOpcodeIsLoad(const SpvOp opcode) {
   }
 }
 
+bool spvOpcodeIsBranch(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpBranch:
+    case SpvOpBranchConditional:
+    case SpvOpSwitch:
+      return true;
+    default:
+      return false;
+  }
+}
+
 bool spvOpcodeIsAtomicOp(const SpvOp opcode) {
   switch (opcode) {
     case SpvOpAtomicLoad:
@@ -385,3 +396,18 @@ bool spvOpcodeIsAtomicOp(const SpvOp opcode) {
       return false;
   }
 }
+
+bool spvOpcodeIsReturn(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpReturn:
+    case SpvOpReturnValue:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool spvOpcodeIsBlockTerminator(SpvOp opcode) {
+  return spvOpcodeIsBranch(opcode) || spvOpcodeIsReturn(opcode) ||
+         opcode == SpvOpKill || opcode == SpvOpUnreachable;
+}
index ba693f0..3ba99df 100644 (file)
@@ -97,4 +97,13 @@ bool spvOpcodeIsLoad(const SpvOp opcode);
 // Returns true if the opcode is an atomic operation.
 bool spvOpcodeIsAtomicOp(const SpvOp opcode);
 
+// Returns true if the given opcode is a branch instruction.
+bool spvOpcodeIsBranch(SpvOp opcode);
+
+// Returns true if the given opcode is a return instruction.
+bool spvOpcodeIsReturn(SpvOp opcode);
+
+// Returns true if the given opcode is a basic block terminator.
+bool spvOpcodeIsBlockTerminator(SpvOp opcode);
+
 #endif  // LIBSPIRV_OPCODE_H_
index 2964ebd..2294e81 100644 (file)
@@ -26,39 +26,40 @@ add_library(SPIRV-Tools-opt
   decoration_manager.h
   def_use_manager.h
   eliminate_dead_constant_pass.h
+  eliminate_dead_functions_pass.h
   flatten_decoration_pass.h
-  function.h
   fold.h
   fold_spec_constant_op_and_composite_pass.h
   freeze_spec_constant_value_pass.h
-  inline_pass.h
+  function.h
   inline_exhaustive_pass.h
   inline_opaque_pass.h
+  inline_pass.h
   insert_extract_elim.h
   instruction.h
-  ir_loader.h
   ir_context.h
+  ir_loader.h
   local_access_chain_convert_pass.h
   local_redundancy_elimination.h
   local_single_block_elim_pass.h
   local_single_store_elim_pass.h
   local_ssa_elim_pass.h
   log.h
+  mem_pass.h
   merge_return_pass.h
   module.h
   null_pass.h
-  reflect.h
-  mem_pass.h
-  pass.h
   passes.h
+  pass.h
   pass_manager.h
-  eliminate_dead_functions_pass.h
+  propagator.h
+  reflect.h
   remove_duplicates_pass.h
   set_spec_constant_default_value_pass.h
   strength_reduction_pass.h
   strip_debug_info_pass.h
-  types.h
   type_manager.h
+  types.h
   unify_const_pass.h
   value_number_table.h
 
@@ -70,42 +71,43 @@ add_library(SPIRV-Tools-opt
   cfg.cpp
   common_uniform_elim_pass.cpp
   compact_ids_pass.cpp
-  decoration_manager.cpp
-  def_use_manager.cpp
   dead_branch_elim_pass.cpp
   dead_variable_elimination.cpp
+  decoration_manager.cpp
+  def_use_manager.cpp
   eliminate_dead_constant_pass.cpp
+  eliminate_dead_functions_pass.cpp
   flatten_decoration_pass.cpp
   fold.cpp
   fold_spec_constant_op_and_composite_pass.cpp
   freeze_spec_constant_value_pass.cpp
   function.cpp
-  inline_pass.cpp
   inline_exhaustive_pass.cpp
   inline_opaque_pass.cpp
+  inline_pass.cpp
   insert_extract_elim.cpp
   instruction.cpp
   instruction_list.cpp
-  ir_loader.cpp
   ir_context.cpp
+  ir_loader.cpp
   local_access_chain_convert_pass.cpp
   local_redundancy_elimination.cpp
   local_single_block_elim_pass.cpp
   local_single_store_elim_pass.cpp
   local_ssa_elim_pass.cpp
+  mem_pass.cpp
   merge_return_pass.cpp
   module.cpp
-  eliminate_dead_functions_pass.cpp
-  remove_duplicates_pass.cpp
-  set_spec_constant_default_value_pass.cpp
   optimizer.cpp
-  mem_pass.cpp
   pass.cpp
   pass_manager.cpp
+  propagator.cpp
+  remove_duplicates_pass.cpp
+  set_spec_constant_default_value_pass.cpp
   strength_reduction_pass.cpp
   strip_debug_info_pass.cpp
-  types.cpp
   type_manager.cpp
+  types.cpp
   unify_const_pass.cpp
   value_number_table.cpp
 )
index b914702..d8c5164 100644 (file)
@@ -300,7 +300,7 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
   for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
     for (auto ii = (*bi)->begin(); ii != (*bi)->end(); ++ii) {
       if (IsLive(&*ii)) continue;
-      if (IsBranch(ii->opcode()) &&
+      if (ii->IsBranch() &&
           !IsStructuredIfHeader(*bi, nullptr, nullptr, nullptr))
         continue;
       dead_insts_.insert(&*ii);
index 54d05d2..6540f3e 100644 (file)
@@ -57,11 +57,6 @@ class AggressiveDCEPass : public MemPass {
   // privates_like_local_)
   bool IsLocalVar(uint32_t varId);
 
-  // Return true if |op| is branch instruction
-  bool IsBranch(SpvOp op) {
-    return op == SpvOpBranch || op == SpvOpBranchConditional;
-  }
-
   // Return true if |inst| is marked live
   bool IsLive(ir::Instruction* inst) {
     return live_insts_.find(inst) != live_insts_.end();
index f4405f2..c84206c 100644 (file)
@@ -138,6 +138,10 @@ class BasicBlock {
   // this block, if any.  If none, returns zero.
   uint32_t ContinueBlockIdIfAny() const;
 
+  // Returns true if this basic block exits this function and returns to its
+  // caller.
+  bool IsReturn() const { return ctail()->IsReturn(); }
+
  private:
   // The enclosing function.
   Function* function_;
index f8f3c6c..0a2054b 100644 (file)
@@ -63,10 +63,10 @@ class CFG {
     return block_ptr == &pseudo_exit_block_;
   }
 
-  // Compute structured block order into |structuredOrder| for |func| starting
-  // at |root|. This order has the property that dominators come before all
-  // blocks they dominate and merge blocks come after all blocks that are in
-  // the control constructs of their header.
+  // Compute structured block order into |order| for |func| starting at |root|.
+  // This order has the property that dominators come before all blocks they
+  // dominate and merge blocks come after all blocks that are in the control
+  // constructs of their header.
   void ComputeStructuredOrder(ir::Function* func, ir::BasicBlock* root,
                               std::list<ir::BasicBlock*>* order);
 
index a046e8d..08c5b77 100644 (file)
@@ -295,6 +295,19 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> {
   // Returns true if the instruction is an atom operation.
   inline bool IsAtomicOp() const;
 
+  // Returns true if this instruction is a branch or switch instruction (either
+  // conditional or not).
+  bool IsBranch() const { return spvOpcodeIsBranch(opcode()); }
+
+  // Returns true if this instruction causes the function to finish execution
+  // and return to its caller
+  bool IsReturn() const { return spvOpcodeIsReturn(opcode()); }
+
+  // Returns true if this instruction is a basic block terminator.
+  bool IsBlockTerminator() const {
+    return spvOpcodeIsBlockTerminator(opcode());
+  }
+
   inline bool operator==(const Instruction&) const;
   inline bool operator!=(const Instruction&) const;
   inline bool operator<(const Instruction&) const;
@@ -344,8 +357,7 @@ inline const Operand& Instruction::GetOperand(uint32_t index) const {
 };
 
 inline void Instruction::AddOperand(Operand&& operand) {
-  operands_.push_back(operand);
-  return;
+  operands_.push_back(std::move(operand));
 }
 
 inline void Instruction::SetInOperand(uint32_t index,
diff --git a/source/opt/propagator.cpp b/source/opt/propagator.cpp
new file mode 100644 (file)
index 0000000..907f41f
--- /dev/null
@@ -0,0 +1,232 @@
+// 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 "propagator.h"
+
+namespace spvtools {
+namespace opt {
+
+void SSAPropagator::AddControlEdge(const Edge& edge) {
+  ir::BasicBlock* dest_bb = edge.dest;
+
+  // Refuse to add the exit block to the work list.
+  if (dest_bb == 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(uint32_t id) {
+  get_def_use_mgr()->ForEachUser(id, [this](ir::Instruction *instr) {
+    // If the basic block for |instr| has not been simulated yet, do nothing.
+    if (!BlockHasBeenSimulated(ctx_->get_instr_block(instr))) {
+      return;
+    }
+
+    if (ShouldSimulateAgain(instr)) {
+      ssa_edge_uses_.push(instr);
+    }
+  });
+}
+
+bool SSAPropagator::Simulate(ir::Instruction* instr) {
+  bool changed = false;
+
+  // Don't bother visiting instructions that should not be simulated again.
+  if (!ShouldSimulateAgain(instr)) {
+    return changed;
+  }
+
+  ir::BasicBlock* dest_bb = nullptr;
+  PropStatus status = visit_fn_(instr, &dest_bb);
+
+  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 (instr->result_id() > 0) {
+      AddSSAEdges(instr->result_id());
+    }
+
+    // If |instr| is a block terminator, add all the control edges out of its
+    // block.
+    if (instr->IsBlockTerminator()) {
+      ir::BasicBlock* block = ctx_->get_instr_block(instr);
+      for (const auto& e : bb_succs_.at(block)) {
+        AddControlEdge(e);
+      }
+    }
+    return false;
+  } else if (status == kInteresting) {
+    // If the instruction produced a new interesting value, add the SSA edge
+    // for its result ID.
+    if (instr->result_id() > 0) {
+      AddSSAEdges(instr->result_id());
+    }
+
+    // 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) {
+      blocks_.push(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;
+  ir::BasicBlock* instr_bb = ctx_->get_instr_block(instr);
+  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);
+      ir::Instruction* arg_def_instr = get_def_use_mgr()->GetDef(arg_id);
+      uint32_t in_label_id = instr->GetSingleWordOperand(i + 1);
+      ir::Instruction* in_label_instr = get_def_use_mgr()->GetDef(in_label_id);
+      ir::BasicBlock* in_bb = ctx_->get_instr_block(in_label_instr);
+      Edge edge(in_bb, instr_bb);
+
+      if (!IsEdgeExecutable(edge) || 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.
+    instr->ForEachInId([&has_operands_to_simulate, this](const uint32_t* use) {
+      ir::Instruction* def_instr = get_def_use_mgr()->GetDef(*use);
+      if (ShouldSimulateAgain(def_instr)) {
+        has_operands_to_simulate = true;
+        return;
+      }
+    });
+  }
+
+  if (!has_operands_to_simulate) {
+    DontSimulateAgain(instr);
+  }
+
+  return changed;
+}
+
+bool SSAPropagator::Simulate(ir::BasicBlock* block) {
+  if (block == 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](ir::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](ir::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(ir::Function* fn) {
+  // Compute predecessor and successor blocks for every block in |fn|'s CFG.
+  // TODO(dnovillo): Move this to ir::CFG and always build them. Alternately,
+  // move it to IRContext and build CFG preds/succs on-demand.
+  bb_succs_[cfg_->pseudo_entry_block()].push_back(
+      Edge(cfg_->pseudo_entry_block(), fn->entry().get()));
+
+  for (auto& block : *fn) {
+    block.ForEachSuccessorLabel([this, &block](uint32_t label_id) {
+      ir::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.IsReturn()) {
+      bb_succs_[&block].push_back(Edge(&block, cfg_->pseudo_exit_block()));
+      bb_preds_[cfg_->pseudo_exit_block()].push_back(
+          Edge(cfg_->pseudo_exit_block(), &block));
+    }
+  }
+
+  // Add the edges out of the entry block to seed the propagator.
+  const auto& entry_succs = bb_succs_[cfg_->pseudo_entry_block()];
+  for (const auto& e : entry_succs) {
+    AddControlEdge(e);
+  }
+}
+
+bool SSAPropagator::Run(ir::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()) {
+      ir::Instruction* instr = ssa_edge_uses_.front();
+      changed |= Simulate(instr);
+      ssa_edge_uses_.pop();
+    }
+  }
+
+  return changed;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/propagator.h b/source/opt/propagator.h
new file mode 100644 (file)
index 0000000..454bcdf
--- /dev/null
@@ -0,0 +1,296 @@
+// 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 LIBSPIRV_OPT_PROPAGATOR_H_
+#define LIBSPIRV_OPT_PROPAGATOR_H_
+
+#include <functional>
+#include <queue>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "cfg.h"
+#include "ir_context.h"
+#include "module.h"
+
+namespace spvtools {
+namespace opt {
+
+// Represents a CFG control edge.
+struct Edge {
+  explicit Edge(ir::BasicBlock* b1, ir::BasicBlock* b2)
+      : source(b1), dest(b2) {}
+  ir::BasicBlock* source;
+  ir::BasicBlock* dest;
+  bool operator<(const Edge& o) const {
+    return source < o.source || dest < o.dest;
+  }
+};
+
+// This class implements a generic value propagation algorithm based on the
+// conditional constant propagation algorithm proposed in
+//
+//      Constant propagation with conditional branches,
+//      Wegman and Zadeck, ACM TOPLAS 13(2):181-210.
+//
+//      A Propagation Engine for GCC
+//      Diego Novillo, GCC Summit 2005
+//      http://ols.fedoraproject.org/GCC/Reprints-2005/novillo-Reprint.pdf
+//
+// The purpose of this implementation is to act as a common framework for any
+// transformation that needs to propagate values from statements producing new
+// values to statements using those values.  Simulation proceeds as follows:
+//
+// 1- Initially, all edges of the CFG are marked not executable and the CFG
+//    worklist is seeded with all the statements in the entry basic block.
+//
+// 2- Every instruction I is simulated by calling a pass-provided function
+//    |visit_fn|. This function is responsible for three things:
+//
+//    (a) Keep a value table of interesting values.  This table maps SSA IDs to
+//        their values.  For instance, when implementing constant propagation,
+//        given a store operation 'OpStore %f %int_3', |visit_fn| should assign
+//        the value 3 to the table slot for %f.
+//
+//        In general, |visit_fn| will need to use the value table to replace its
+//        operands, fold the result and decide whether a new value needs to be
+//        stored in the table. |visit_fn| should only create a new mapping in
+//        the value table if all the operands in the instruction are known and
+//        present in the value table.
+//
+//    (b) Return a status indicator to direct the propagator logic.  Once the
+//        instruction is simulated, the propagator needs to know whether this
+//        instruction produced something interesting.  This is indicated via
+//        |visit_fn|'s return value:
+//
+//         SSAPropagator::kNotInteresting: Instruction I produces nothing of
+//             interest and does not affect any of the work lists.  The
+//             propagator will visit the statement again if any of its operands
+//             produce an interesting value in the future.
+//
+//             |visit_fn| should always return this value when it is not sure
+//             whether the instruction will produce an interesting value in the
+//             future or not.  For instance, for constant propagation, an OpIAdd
+//             instruction may produce a constant if its two operands are
+//             constant, but the first time we visit the instruction, we still
+//             may not have its operands in the value table.
+//
+//         SSAPropagator::kVarying: The value produced by I cannot be determined
+//             at compile time.  Further simulation of I is not required.  The
+//             propagator will not visit this instruction again.  Additionally,
+//             the propagator will add all the instructions at the end of SSA
+//             def-use edges to be simulated again.
+//
+//             If I is a basic block terminator, it will mark all outgoing edges
+//             as executable so they are traversed one more time.  Eventually
+//             the kVarying attribute will be spread out to all the data and
+//             control dependents for I.
+//
+//             It is important for propagation to use kVarying as a bottom value
+//             for the propagation lattice.  It should never be possible for an
+//             instruction to return kVarying once and kInteresting on a second
+//             visit.  Otherwise, propagation would not stabilize.
+//
+//         SSAPropagator::kInteresting: Instruction I produces a value that can
+//             be computed at compile time.  In this case, |visit_fn| should
+//             create a new mapping between I's result ID and the produced
+//             value.  Much like the kNotInteresting case, the propagator will
+//             visit this instruction again if any of its operands changes.
+//             This is useful when the statement changes from one interesting
+//             state to another.
+//
+//    (c) For conditional branches, |visit_fn| may decide which edge to take out
+//        of I's basic block.  For example, if the operand for an OpSwitch is
+//        known to take a specific constant value, |visit_fn| should figure out
+//        the destination basic block and pass it back by setting the second
+//        argument to |visit_fn|.
+//
+//    At the end of propagation, values in the value table are guaranteed to be
+//    stable and can be replaced in the IR.
+//
+// 3- The propagator keeps two work queues.  Instructions are only added to
+//    these queues if they produce an interesting or varying value. None of this
+//    should be handled by |visit_fn|. The propagator keeps track of this
+//    automatically (see SSAPropagator::Simulate for implementation).
+//
+//      CFG blocks: contains the queue of blocks to be simulated.
+//             Blocks are added to this queue if their incoming edges are
+//             executable.
+//
+//      SSA Edges: An SSA edge is a def-use edge between a value-producing
+//              instruction and its use instruction.  The SSA edges list
+//              contains the statements at the end of a def-use edge that need
+//              to be re-visited when an instruction produces a kVarying or
+//              kInteresting result.
+//
+// 4- Simulation terminates when all work queues are drained.
+//
+//
+// EXAMPLE: Basic constant store propagator.
+//
+// Suppose we want to propagate all constant assignments of the form "OpStore
+// %id %cst" where "%id" is some variable and "%cst" an OpConstant.  The
+// following code builds a table |values| where every id that was assigned a
+// constant value is mapped to the constant value it was assigned.
+//
+//   auto ctx = spvtools::BuildModule(...);
+//   std::map<uint32_t, uint32_t> values;
+//   const auto visit_fn = [&ctx, &values](ir::Instruction* instr,
+//                                         ir::BasicBlock** dest_bb) {
+//     if (instr->opcode() == SpvOpStore) {
+//       uint32_t rhs_id = instr->GetSingleWordOperand(1);
+//       ir::Instruction* rhs_def = ctx->get_def_use_mgr()->GetDef(rhs_id);
+//       if (rhs_def->opcode() == SpvOpConstant) {
+//         uint32_t val = rhs_def->GetSingleWordOperand(2);
+//         values[rhs_id] = val;
+//         return opt::SSAPropagator::kInteresting;
+//       }
+//     }
+//     return opt::SSAPropagator::kVarying;
+//   };
+//   opt::SSAPropagator propagator(ctx.get(), &cfg, visit_fn);
+//   propagator.Run(&fn);
+//
+// Given the code:
+//
+//       %int_4 = OpConstant %int 4
+//       %int_3 = OpConstant %int 3
+//       %int_1 = OpConstant %int 1
+//                OpStore %x %int_4
+//                OpStore %y %int_3
+//                OpStore %z %int_1
+//
+// After SSAPropagator::Run returns, the |values| map will contain the entries:
+// values[%x] = 4, values[%y] = 3, and, values[%z] = 1.
+class SSAPropagator {
+ public:
+  // Lattice values used for propagation. See class documentation for
+  // a description.
+  enum PropStatus { kNotInteresting, kInteresting, kVarying };
+
+  using VisitFunction =
+      std::function<PropStatus(ir::Instruction*, ir::BasicBlock**)>;
+
+  SSAPropagator(ir::IRContext* context, ir::CFG* cfg,
+                const VisitFunction& visit_fn)
+      : ctx_(context), cfg_(cfg), visit_fn_(visit_fn) {}
+
+  // Run the propagator on function |fn|. Returns true if changes were made to
+  // the function. Otherwise, it returns false.
+  bool Run(ir::Function* fn);
+
+ private:
+  // Initialize processing.
+  void Initialize(ir::Function* fn);
+
+  // Simulate the execution |block| by calling |visit_fn_| on every instruction
+  // in it.
+  bool Simulate(ir::BasicBlock* block);
+
+  // Simulate the execution of |instr| by replacing all the known values in
+  // every operand and determining whether the result is interesting for
+  // propagation. This invokes the callback function |visit_fn_| to determine
+  // the value computed by |instr|.
+  bool Simulate(ir::Instruction* instr);
+
+  // Returns true if |instr| should be simulated again.
+  bool ShouldSimulateAgain(ir::Instruction* instr) const {
+    return do_not_simulate_.find(instr) == do_not_simulate_.end();
+  }
+
+  // Add |instr| to the set of instructions not to simulate again.
+  void DontSimulateAgain(ir::Instruction* instr) {
+    do_not_simulate_.insert(instr);
+  }
+
+  // Returns true if |block| has been simulated already.
+  bool BlockHasBeenSimulated(ir::BasicBlock* block) {
+    return simulated_blocks_.find(block) != simulated_blocks_.end();
+  }
+
+  // Marks block |block| as simulated.
+  void MarkBlockSimulated(ir::BasicBlock* block) {
+    simulated_blocks_.insert(block);
+  }
+
+  // Marks |edge| as executable.  Returns false if the edge was already marked
+  // as executable.
+  bool MarkEdgeExecutable(const Edge& edge) {
+    return executable_edges_.insert(edge).second;
+  }
+
+  // Returns true if |edge| has been marked as executable.
+  bool IsEdgeExecutable(const Edge& edge) {
+    return executable_edges_.find(edge) != executable_edges_.end();
+  }
+
+  // Returns a pointer to the def-use manager for |ctx_|.
+  analysis::DefUseManager* get_def_use_mgr() const {
+    return ctx_->get_def_use_mgr();
+  }
+
+  // If the CFG edge |e| has not been executed, add its destination block to the
+  // work list.
+  void AddControlEdge(const Edge& e);
+
+  // Add all the instructions that use |id| to the SSA edges work list.
+  void AddSSAEdges(uint32_t id);
+
+  // IR context to use.
+  ir::IRContext* ctx_;
+
+  // CFG to propagate values on. TODO(dnovillo): The CFG should be part of
+  // IRContext.
+  ir::CFG* cfg_;
+
+  // Function that visits instructions during simulation. The output of this
+  // function is used to determine if the simulated instruction produced a value
+  // interesting for propagation. The function is responsible for keeping
+  // track of interesting values by storing them in some user-provided map.
+  VisitFunction visit_fn_;
+
+  // SSA def-use edges to traverse. Each entry is a destination statement for an
+  // SSA def-use edge as returned by |def_use_manager_|.
+  std::queue<ir::Instruction*> ssa_edge_uses_;
+
+  // Blocks to simulate.
+  std::queue<ir::BasicBlock*> blocks_;
+
+  // Blocks simulated during propagation.
+  std::unordered_set<ir::BasicBlock*> simulated_blocks_;
+
+  // Set of instructions that should not be simulated again because they have
+  // been found to be in the kVarying state.
+  std::unordered_set<ir::Instruction*> do_not_simulate_;
+
+  // Map between a basic block and its predecessor edges.
+  // TODO(dnovillo): Move this to ir::CFG and always build them. Alternately,
+  // move it to IRContext and build CFG preds/succs on-demand.
+  std::unordered_map<ir::BasicBlock*, std::vector<Edge>> bb_preds_;
+
+  // Map between a basic block and its successor edges.
+  // TODO(dnovillo): Move this to ir::CFG and always build them. Alternately,
+  // move it to IRContext and build CFG preds/succs on-demand.
+  std::unordered_map<ir::BasicBlock*, std::vector<Edge>> bb_succs_;
+
+  // Set of executable CFG edges.
+  std::set<Edge> executable_edges_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_PROPAGATOR_H_
index 891eba3..b6e75fc 100644 (file)
@@ -218,3 +218,8 @@ add_spvtools_unittest(TARGET local_redundancy_elimination
   SRCS local_redundancy_elimination_test.cpp pass_utils.cpp
   LIBS SPIRV-Tools-opt
 )
+
+add_spvtools_unittest(TARGET propagator
+  SRCS propagator_test.cpp
+  LIBS SPIRV-Tools-opt
+)
index 6982e51..0fc7b20 100644 (file)
@@ -1608,6 +1608,117 @@ OpFunctionEnd
       predefs_before + func_before, predefs_after + func_after, true, true);
 }
 
+// This test fails. OpSwitch is not handled by ADCE.
+// (https://github.com/KhronosGroup/SPIRV-Tools/issues/1021).
+TEST_F(AggressiveDCETest, DISABLED_EliminateDeadSwitch) {
+  // #version 450
+  //
+  // layout(location = 0) in vec4 BaseColor;
+  // layout(location = 1) in flat int x;
+  // layout(location = 0) out vec4 OutColor;
+  //
+  // void main()
+  // {
+  //     float d;
+  //     switch (x) {
+  //       case 0:
+  //         d = BaseColor.y;
+  //     }
+  //     OutColor = vec4(1.0,1.0,1.0,1.0);
+  // }
+  const std::string before =
+      R"(OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %x %BaseColor %OutColor
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %x "x"
+               OpName %d "d"
+               OpName %BaseColor "BaseColor"
+               OpName %OutColor "OutColor"
+               OpDecorate %x Flat
+               OpDecorate %x Location 1
+               OpDecorate %BaseColor Location 0
+               OpDecorate %OutColor Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+          %x = OpVariable %_ptr_Input_int Input
+      %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+  %BaseColor = OpVariable %_ptr_Input_v4float Input
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+   %OutColor = OpVariable %_ptr_Output_v4float Output
+    %float_1 = OpConstant %float 1
+         %27 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %d = OpVariable %_ptr_Function_float Function
+          %9 = OpLoad %int %x
+               OpSelectionMerge %11 None
+               OpSwitch %9 %11 0 %10
+         %10 = OpLabel
+         %21 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1
+         %22 = OpLoad %float %21
+               OpStore %d %22
+               OpBranch %11
+         %11 = OpLabel
+               OpStore %OutColor %27
+               OpReturn
+               OpFunctionEnd)";
+
+  const std::string after =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %x %BaseColor %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %x "x"
+OpName %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %x Flat
+OpDecorate %x Location 1
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%x = OpVariable %_ptr_Input_int Input
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_1 = OpConstant %float 1
+%20 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%main = OpFunction %void None %8
+%21 = OpLabel
+OpBranch %11
+%11 = OpLabel
+OpStore %OutColor %27
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(before, after, true, true);
+}
+
 TEST_F(AggressiveDCETest, EliminateDeadIfThenElseNested) {
   // #version 450
   //
diff --git a/test/opt/propagator_test.cpp b/test/opt/propagator_test.cpp
new file mode 100644 (file)
index 0000000..cd3f0d4
--- /dev/null
@@ -0,0 +1,217 @@
+// 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "opt/build_module.h"
+#include "opt/cfg.h"
+#include "opt/ir_context.h"
+#include "opt/pass.h"
+#include "opt/propagator.h"
+
+namespace {
+
+using namespace spvtools;
+
+using ::testing::UnorderedElementsAre;
+
+class PropagatorTest : public testing::Test {
+ protected:
+  virtual void TearDown() {
+    ctx_.reset(nullptr);
+    cfg_.reset(nullptr);
+    values_.clear();
+    values_vec_.clear();
+  }
+
+  void Assemble(const std::string& input) {
+    ctx_ = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input);
+    ASSERT_NE(nullptr, ctx_) << "Assembling failed for shader:\n"
+                             << input << "\n";
+    cfg_.reset(new ir::CFG(ctx_->module()));
+  }
+
+  bool Propagate(const opt::SSAPropagator::VisitFunction& visit_fn) {
+    opt::SSAPropagator propagator(ctx_.get(), cfg_.get(), visit_fn);
+    bool retval = false;
+    for (auto& fn : *ctx_->module()) {
+      retval |= propagator.Run(&fn);
+    }
+    return retval;
+  }
+
+  const std::vector<uint32_t>& GetValues() {
+    values_vec_.clear();
+    for (const auto& it : values_) {
+      values_vec_.push_back(it.second);
+    }
+    return values_vec_;
+  }
+
+  std::unique_ptr<ir::IRContext> ctx_;
+  std::unique_ptr<ir::CFG> cfg_;
+  std::map<uint32_t, uint32_t> values_;
+  std::vector<uint32_t> values_vec_;
+};
+
+TEST_F(PropagatorTest, LocalPropagate) {
+  const std::string spv_asm = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %outparm
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %x "x"
+               OpName %y "y"
+               OpName %z "z"
+               OpName %outparm "outparm"
+               OpDecorate %outparm Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+      %int_4 = OpConstant %int 4
+      %int_3 = OpConstant %int 3
+      %int_1 = OpConstant %int 1
+%_ptr_Output_int = OpTypePointer Output %int
+    %outparm = OpVariable %_ptr_Output_int Output
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %x = OpVariable %_ptr_Function_int Function
+          %y = OpVariable %_ptr_Function_int Function
+          %z = OpVariable %_ptr_Function_int Function
+               OpStore %x %int_4
+               OpStore %y %int_3
+               OpStore %z %int_1
+         %20 = OpLoad %int %z
+               OpStore %outparm %20
+               OpReturn
+               OpFunctionEnd
+               )";
+  Assemble(spv_asm);
+
+  const auto visit_fn = [this](ir::Instruction* instr,
+                               ir::BasicBlock** dest_bb) {
+    *dest_bb = nullptr;
+    if (instr->opcode() == SpvOpStore) {
+      uint32_t lhs_id = instr->GetSingleWordOperand(0);
+      uint32_t rhs_id = instr->GetSingleWordOperand(1);
+      ir::Instruction* rhs_def = ctx_->get_def_use_mgr()->GetDef(rhs_id);
+      if (rhs_def->opcode() == SpvOpConstant) {
+        uint32_t val = rhs_def->GetSingleWordOperand(2);
+        values_[lhs_id] = val;
+        return opt::SSAPropagator::kInteresting;
+      }
+    }
+    return opt::SSAPropagator::kVarying;
+  };
+
+  EXPECT_TRUE(Propagate(visit_fn));
+  EXPECT_THAT(GetValues(), UnorderedElementsAre(4, 3, 1));
+}
+
+TEST_F(PropagatorTest, PropagateThroughPhis) {
+  const std::string spv_asm = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %x %outparm
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %x "x"
+               OpName %outparm "outparm"
+               OpDecorate %x Flat
+               OpDecorate %x Location 0
+               OpDecorate %outparm Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %bool = OpTypeBool
+%_ptr_Function_int = OpTypePointer Function %int
+      %int_4 = OpConstant %int 4
+      %int_3 = OpConstant %int 3
+      %int_1 = OpConstant %int 1
+%_ptr_Input_int = OpTypePointer Input %int
+          %x = OpVariable %_ptr_Input_int Input
+%_ptr_Output_int = OpTypePointer Output %int
+    %outparm = OpVariable %_ptr_Output_int Output
+       %main = OpFunction %void None %3
+          %4 = OpLabel
+          %5 = OpLoad %int %x
+          %6 = OpSGreaterThan %bool %5 %int_3
+               OpSelectionMerge %25 None
+               OpBranchConditional %6 %22 %23
+         %22 = OpLabel
+          %7 = OpLoad %int %int_4
+               OpBranch %25
+         %23 = OpLabel
+          %8 = OpLoad %int %int_4
+               OpBranch %25
+         %25 = OpLabel
+         %35 = OpPhi %int %7 %22 %8 %23
+               OpStore %outparm %35
+               OpReturn
+               OpFunctionEnd
+               )";
+
+  Assemble(spv_asm);
+
+  ir::Instruction *phi_instr = nullptr;
+  const auto visit_fn = [this, &phi_instr](ir::Instruction* instr,
+                                           ir::BasicBlock** dest_bb) {
+    *dest_bb = nullptr;
+    if (instr->opcode() == SpvOpLoad) {
+      uint32_t rhs_id = instr->GetSingleWordOperand(2);
+      ir::Instruction* rhs_def = ctx_->get_def_use_mgr()->GetDef(rhs_id);
+      if (rhs_def->opcode() == SpvOpConstant) {
+        uint32_t val = rhs_def->GetSingleWordOperand(2);
+        values_[instr->result_id()] = val;
+        return opt::SSAPropagator::kInteresting;
+      }
+    } else if (instr->opcode() == SpvOpPhi) {
+      phi_instr = instr;
+      opt::SSAPropagator::PropStatus retval;
+      for (uint32_t i = 2; i < instr->NumOperands(); i += 2) {
+        uint32_t phi_arg_id = instr->GetSingleWordOperand(i);
+        auto it = values_.find(phi_arg_id);
+        if (it != values_.end()) {
+          EXPECT_EQ(it->second, 4);
+          retval = opt::SSAPropagator::kInteresting;
+          values_[instr->result_id()] = it->second;
+        } else {
+          retval = opt::SSAPropagator::kNotInteresting;
+          break;
+        }
+      }
+      return retval;
+    }
+
+    return opt::SSAPropagator::kVarying;
+  };
+
+  EXPECT_TRUE(Propagate(visit_fn));
+
+  // The propagator should've concluded that the Phi instruction has a constant
+  // value of 4.
+  EXPECT_NE(phi_instr, nullptr);
+  EXPECT_EQ(values_[phi_instr->result_id()], 4);
+
+  EXPECT_THAT(GetValues(), UnorderedElementsAre(4, 4, 4));
+}
+
+}  // namespace