summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--source/opcode.cpp26
-rw-r--r--source/opcode.h9
-rw-r--r--source/opt/CMakeLists.txt36
-rw-r--r--source/opt/aggressive_dead_code_elim_pass.cpp2
-rw-r--r--source/opt/aggressive_dead_code_elim_pass.h5
-rw-r--r--source/opt/basic_block.h4
-rw-r--r--source/opt/cfg.h8
-rw-r--r--source/opt/instruction.h16
-rw-r--r--source/opt/propagator.cpp232
-rw-r--r--source/opt/propagator.h296
-rw-r--r--test/opt/CMakeLists.txt5
-rw-r--r--test/opt/aggressive_dead_code_elim_test.cpp111
-rw-r--r--test/opt/propagator_test.cpp217
14 files changed, 939 insertions, 29 deletions
diff --git a/Android.mk b/Android.mk
index 03c24c03..920b1ced 100644
--- a/Android.mk
+++ b/Android.mk
@@ -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 \
diff --git a/source/opcode.cpp b/source/opcode.cpp
index 50f5e52c..a9ab1e60 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -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;
+}
diff --git a/source/opcode.h b/source/opcode.h
index ba693f05..3ba99df6 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -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_
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 2964ebdb..2294e81f 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -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
)
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index b9147024..d8c51645 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -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);
diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h
index 54d05d23..6540f3ed 100644
--- a/source/opt/aggressive_dead_code_elim_pass.h
+++ b/source/opt/aggressive_dead_code_elim_pass.h
@@ -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();
diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h
index f4405f2a..c84206cd 100644
--- a/source/opt/basic_block.h
+++ b/source/opt/basic_block.h
@@ -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_;
diff --git a/source/opt/cfg.h b/source/opt/cfg.h
index f8f3c6c0..0a2054b7 100644
--- a/source/opt/cfg.h
+++ b/source/opt/cfg.h
@@ -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);
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index a046e8dc..08c5b77e 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -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
index 00000000..907f41f5
--- /dev/null
+++ b/source/opt/propagator.cpp
@@ -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
index 00000000..454bcdfc
--- /dev/null
+++ b/source/opt/propagator.h
@@ -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_
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 891eba34..b6e75fcb 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -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
+)
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index 6982e517..0fc7b20f 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -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
index 00000000..cd3f0d41
--- /dev/null
+++ b/test/opt/propagator_test.cpp
@@ -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