Generic value propagation engine.

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.
This commit is contained in:
Diego Novillo 2017-11-17 08:59:25 -05:00
parent 491b112fd2
commit 74327845aa
14 changed files with 939 additions and 29 deletions

View 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 \

View 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;
}

View 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_

View 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
)

View 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);

View 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();

View 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_;

View 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);

View 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,

232
source/opt/propagator.cpp Normal file
View File

@ -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

296
source/opt/propagator.h Normal file
View File

@ -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_

View 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
)

View 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
//

View File

@ -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