mirror of
https://gitee.com/openharmony/third_party_spirv-tools
synced 2024-11-27 01:21:25 +00:00
SSA rewrite pass.
This pass replaces the load/store elimination passes. It implements the SSA re-writing algorithm proposed in Simple and Efficient Construction of Static Single Assignment Form. Braun M., Buchwald S., Hack S., Leißa R., Mallon C., Zwinkau A. (2013) In: Jhala R., De Bosschere K. (eds) Compiler Construction. CC 2013. Lecture Notes in Computer Science, vol 7791. Springer, Berlin, Heidelberg https://link.springer.com/chapter/10.1007/978-3-642-37051-9_6 In contrast to common eager algorithms based on dominance and dominance frontier information, this algorithm works backwards from load operations. When a target variable is loaded, it queries the variable's reaching definition. If the reaching definition is unknown at the current location, it searches backwards in the CFG, inserting Phi instructions at join points in the CFG along the way until it finds the desired store instruction. The algorithm avoids repeated lookups using memoization. For reducible CFGs, which are a superset of the structured CFGs in SPIRV, this algorithm is proven to produce minimal SSA. That is, it inserts the minimal number of Phi instructions required to ensure the SSA property, but some Phi instructions may be dead (https://en.wikipedia.org/wiki/Static_single_assignment_form).
This commit is contained in:
parent
bdf421cf40
commit
735d8a579e
@ -118,6 +118,7 @@ SPVTOOLS_OPT_SRC_FILES := \
|
||||
source/opt/scalar_replacement_pass.cpp \
|
||||
source/opt/set_spec_constant_default_value_pass.cpp \
|
||||
source/opt/simplification_pass.cpp \
|
||||
source/opt/ssa_rewrite_pass.cpp \
|
||||
source/opt/strength_reduction_pass.cpp \
|
||||
source/opt/strip_debug_info_pass.cpp \
|
||||
source/opt/strip_reflect_info_pass.cpp \
|
||||
|
@ -536,6 +536,13 @@ Optimizer::PassToken CreateSimplificationPass();
|
||||
// won't be unrolled. See CanPerformUnroll LoopUtils.h for more information.
|
||||
Optimizer::PassToken CreateLoopUnrollPass(bool fully_unroll, int factor = 0);
|
||||
|
||||
// Create the SSA rewrite pass.
|
||||
// This pass converts load/store operations on function local variables into
|
||||
// operations on SSA IDs. This allows SSA optimizers to act on these variables.
|
||||
// Only variables that are local to the function and of supported types are
|
||||
// processed (see IsSSATargetVar for details).
|
||||
Optimizer::PassToken CreateSSARewritePass();
|
||||
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SPIRV_TOOLS_OPTIMIZER_HPP_
|
||||
|
@ -79,6 +79,7 @@ add_library(SPIRV-Tools-opt
|
||||
scalar_replacement_pass.h
|
||||
set_spec_constant_default_value_pass.h
|
||||
simplification_pass.h
|
||||
ssa_rewrite_pass.h
|
||||
strength_reduction_pass.h
|
||||
strip_debug_info_pass.h
|
||||
strip_reflect_info_pass.h
|
||||
@ -151,6 +152,7 @@ add_library(SPIRV-Tools-opt
|
||||
scalar_replacement_pass.cpp
|
||||
set_spec_constant_default_value_pass.cpp
|
||||
simplification_pass.cpp
|
||||
ssa_rewrite_pass.cpp
|
||||
strength_reduction_pass.cpp
|
||||
strip_debug_info_pass.cpp
|
||||
strip_reflect_info_pass.cpp
|
||||
|
@ -190,13 +190,19 @@ uint32_t BasicBlock::ContinueBlockIdIfAny() const {
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& str, const BasicBlock& block) {
|
||||
block.ForEachInst([&str](const ir::Instruction* inst) {
|
||||
str << *inst;
|
||||
str << block.PrettyPrint();
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string BasicBlock::PrettyPrint(uint32_t options) const {
|
||||
std::ostringstream str;
|
||||
ForEachInst([&str, options](const ir::Instruction* inst) {
|
||||
str << inst->PrettyPrint(options);
|
||||
if (!IsTerminatorInst(inst->opcode())) {
|
||||
str << std::endl;
|
||||
}
|
||||
});
|
||||
return str;
|
||||
return str.str();
|
||||
}
|
||||
|
||||
BasicBlock* BasicBlock::SplitBasicBlock(IRContext* context, uint32_t label_id,
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@ -181,6 +182,13 @@ class BasicBlock {
|
||||
BasicBlock* SplitBasicBlock(IRContext* context, uint32_t label_id,
|
||||
iterator iter);
|
||||
|
||||
// Pretty-prints this basic block into a std::string by printing every
|
||||
// instruction in it.
|
||||
//
|
||||
// |options| are the disassembly options. SPV_BINARY_TO_TEXT_OPTION_NO_HEADER
|
||||
// is always added to |options|.
|
||||
std::string PrettyPrint(uint32_t options = 0u) const;
|
||||
|
||||
private:
|
||||
// The enclosing function.
|
||||
Function* function_;
|
||||
|
@ -14,7 +14,8 @@
|
||||
|
||||
#include "function.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace spvtools {
|
||||
namespace ir {
|
||||
@ -89,13 +90,19 @@ BasicBlock* Function::InsertBasicBlockAfter(
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& str, const Function& func) {
|
||||
func.ForEachInst([&str](const ir::Instruction* inst) {
|
||||
str << *inst;
|
||||
str << func.PrettyPrint();
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string Function::PrettyPrint(uint32_t options) const {
|
||||
std::ostringstream str;
|
||||
ForEachInst([&str, options](const ir::Instruction* inst) {
|
||||
str << inst->PrettyPrint(options);
|
||||
if (inst->opcode() != SpvOpFunctionEnd) {
|
||||
str << std::endl;
|
||||
}
|
||||
});
|
||||
return str;
|
||||
return str.str();
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
|
@ -118,6 +118,12 @@ class Function {
|
||||
BasicBlock* InsertBasicBlockAfter(std::unique_ptr<ir::BasicBlock>&& new_block,
|
||||
BasicBlock* position);
|
||||
|
||||
// Pretty-prints all the basic blocks in this function into a std::string.
|
||||
//
|
||||
// |options| are the disassembly options. SPV_BINARY_TO_TEXT_OPTION_NO_HEADER
|
||||
// is always added to |options|.
|
||||
std::string PrettyPrint(uint32_t options = 0u) const;
|
||||
|
||||
private:
|
||||
// The enclosing module.
|
||||
Module* module_;
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include "cfa.h"
|
||||
#include "iterator.h"
|
||||
#include "ssa_rewrite_pass.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
@ -41,10 +42,6 @@ bool LocalMultiStoreElimPass::AllExtensionsSupported() const {
|
||||
}
|
||||
|
||||
Pass::Status LocalMultiStoreElimPass::ProcessImpl() {
|
||||
// Assumes all control flow structured.
|
||||
// TODO(greg-lunarg): Do SSA rewrite for non-structured control flow
|
||||
if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
|
||||
return Status::SuccessWithoutChange;
|
||||
// Assumes relaxed logical addressing only (see instruction.h)
|
||||
// TODO(greg-lunarg): Add support for physical addressing
|
||||
if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
|
||||
@ -58,7 +55,7 @@ Pass::Status LocalMultiStoreElimPass::ProcessImpl() {
|
||||
if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
|
||||
// Process functions
|
||||
ProcessFunction pfn = [this](ir::Function* fp) {
|
||||
return InsertPhiInstructions(fp);
|
||||
return SSARewriter(this).RewriteFunctionIntoSSA(fp);
|
||||
};
|
||||
bool modified = ProcessEntryPointCallTree(pfn, get_module());
|
||||
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
|
||||
|
@ -245,34 +245,11 @@ bool MemPass::HasOnlySupportedRefs(uint32_t varId) {
|
||||
|
||||
void MemPass::InitSSARewrite(ir::Function* func) {
|
||||
// Clear collections.
|
||||
seen_target_vars_.clear();
|
||||
seen_non_target_vars_.clear();
|
||||
visitedBlocks_.clear();
|
||||
type2undefs_.clear();
|
||||
supported_ref_vars_.clear();
|
||||
block_defs_map_.clear();
|
||||
phis_to_patch_.clear();
|
||||
dominator_ = context()->GetDominatorAnalysis(func, *cfg());
|
||||
|
||||
// Collect target (and non-) variable sets. Remove variables with
|
||||
// non-load/store refs from target variable set
|
||||
for (auto& blk : *func) {
|
||||
for (auto& inst : blk) {
|
||||
switch (inst.opcode()) {
|
||||
case SpvOpStore:
|
||||
case SpvOpLoad: {
|
||||
uint32_t varId;
|
||||
(void)GetPtr(&inst, &varId);
|
||||
if (!IsTargetVar(varId)) break;
|
||||
if (HasOnlySupportedRefs(varId)) break;
|
||||
seen_non_target_vars_.insert(varId);
|
||||
seen_target_vars_.erase(varId);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
CollectTargetVars(func);
|
||||
}
|
||||
|
||||
bool MemPass::IsLiveAfter(uint32_t var_id, uint32_t label) const {
|
||||
@ -873,5 +850,32 @@ bool MemPass::CFGCleanup(ir::Function* func) {
|
||||
return modified;
|
||||
}
|
||||
|
||||
void MemPass::CollectTargetVars(ir::Function* func) {
|
||||
seen_target_vars_.clear();
|
||||
seen_non_target_vars_.clear();
|
||||
supported_ref_vars_.clear();
|
||||
type2undefs_.clear();
|
||||
|
||||
// Collect target (and non-) variable sets. Remove variables with
|
||||
// non-load/store refs from target variable set
|
||||
for (auto& blk : *func) {
|
||||
for (auto& inst : blk) {
|
||||
switch (inst.opcode()) {
|
||||
case SpvOpStore:
|
||||
case SpvOpLoad: {
|
||||
uint32_t varId;
|
||||
(void)GetPtr(&inst, &varId);
|
||||
if (!IsTargetVar(varId)) break;
|
||||
if (HasOnlySupportedRefs(varId)) break;
|
||||
seen_non_target_vars_.insert(varId);
|
||||
seen_target_vars_.erase(varId);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
@ -41,6 +41,34 @@ class MemPass : public Pass {
|
||||
MemPass();
|
||||
virtual ~MemPass() = default;
|
||||
|
||||
// Returns an undef value for the given |var_id|'s type.
|
||||
uint32_t GetUndefVal(uint32_t var_id) {
|
||||
return Type2Undef(GetPointeeTypeId(get_def_use_mgr()->GetDef(var_id)));
|
||||
}
|
||||
|
||||
// Given a load or store |ip|, return the pointer instruction.
|
||||
// Also return the base variable's id in |varId|. If no base variable is
|
||||
// found, |varId| will be 0.
|
||||
ir::Instruction* GetPtr(ir::Instruction* ip, uint32_t* varId);
|
||||
|
||||
// Return true if |varId| is a previously identified target variable.
|
||||
// Return false if |varId| is a previously identified non-target variable.
|
||||
//
|
||||
// Non-target variables are variable of function scope of a target type that
|
||||
// are accessed with constant-index access chains. not accessed with
|
||||
// non-constant-index access chains. Also cache non-target variables.
|
||||
//
|
||||
// If variable is not cached, return true if variable is a function scope
|
||||
// variable of target type, false otherwise. Updates caches of target and
|
||||
// non-target variables.
|
||||
bool IsTargetVar(uint32_t varId);
|
||||
|
||||
// Collect target SSA variables. This traverses all the loads and stores in
|
||||
// function |func| looking for variables that can be replaced with SSA IDs. It
|
||||
// populates the sets |seen_target_vars_|, |seen_non_target_vars_| and
|
||||
// |supported_ref_vars_|.
|
||||
void CollectTargetVars(ir::Function* func);
|
||||
|
||||
protected:
|
||||
// Returns true if |typeInst| is a scalar type
|
||||
// or a vector or matrix
|
||||
@ -63,11 +91,6 @@ class MemPass : public Pass {
|
||||
// found, |varId| will be 0.
|
||||
ir::Instruction* GetPtr(uint32_t ptrId, uint32_t* varId);
|
||||
|
||||
// Given a load or store |ip|, return the pointer instruction.
|
||||
// Also return the base variable's id in |varId|. If no base variable is
|
||||
// found, |varId| will be 0.
|
||||
ir::Instruction* GetPtr(ir::Instruction* ip, uint32_t* varId);
|
||||
|
||||
// Return true if all uses of |id| are only name or decorate ops.
|
||||
bool HasOnlyNamesAndDecorates(uint32_t id) const;
|
||||
|
||||
@ -103,18 +126,6 @@ class MemPass : public Pass {
|
||||
return (op == SpvOpDecorate || op == SpvOpDecorateId);
|
||||
}
|
||||
|
||||
// Return true if |varId| is a previously identified target variable.
|
||||
// Return false if |varId| is a previously identified non-target variable.
|
||||
//
|
||||
// Non-target variables are variable of function scope of a target type that
|
||||
// are accessed with constant-index access chains. not accessed with
|
||||
// non-constant-index access chains. Also cache non-target variables.
|
||||
//
|
||||
// If variable is not cached, return true if variable is a function scope
|
||||
// variable of target type, false otherwise. Updates caches of target and
|
||||
// non-target variables.
|
||||
bool IsTargetVar(uint32_t varId);
|
||||
|
||||
// Return undef in function for type. Create and insert an undef after the
|
||||
// first non-variable in the function if it doesn't already exist. Add
|
||||
// undef to function undef map.
|
||||
|
@ -426,4 +426,10 @@ Optimizer::PassToken CreateLoopUnrollPass(bool fully_unroll, int factor) {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::LoopUnroller>(fully_unroll, factor));
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateSSARewritePass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::SSARewritePass>());
|
||||
}
|
||||
|
||||
} // namespace spvtools
|
||||
|
@ -130,6 +130,9 @@ class Pass {
|
||||
return ir::IRContext::kAnalysisNone;
|
||||
}
|
||||
|
||||
// Return type id for |ptrInst|'s pointee
|
||||
uint32_t GetPointeeTypeId(const ir::Instruction* ptrInst) const;
|
||||
|
||||
protected:
|
||||
// Initialize basic data structures for the pass. This sets up the def-use
|
||||
// manager, module and other attributes.
|
||||
@ -140,9 +143,6 @@ class Pass {
|
||||
// succesful to indicate whether changes are made to the module.
|
||||
virtual Status Process(ir::IRContext* context) = 0;
|
||||
|
||||
// Return type id for |ptrInst|'s pointee
|
||||
uint32_t GetPointeeTypeId(const ir::Instruction* ptrInst) const;
|
||||
|
||||
// Return the next available SSA id and increment it.
|
||||
uint32_t TakeNextId() { return context_->TakeNextId(); }
|
||||
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include "replace_invalid_opc.h"
|
||||
#include "scalar_replacement_pass.h"
|
||||
#include "set_spec_constant_default_value_pass.h"
|
||||
#include "ssa_rewrite_pass.h"
|
||||
#include "strength_reduction_pass.h"
|
||||
#include "strip_debug_info_pass.h"
|
||||
#include "strip_reflect_info_pass.h"
|
||||
|
590
source/opt/ssa_rewrite_pass.cpp
Normal file
590
source/opt/ssa_rewrite_pass.cpp
Normal file
@ -0,0 +1,590 @@
|
||||
// Copyright (c) 2018 Google LLC.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// This file implements the SSA rewriting algorithm proposed in
|
||||
//
|
||||
// Simple and Efficient Construction of Static Single Assignment Form.
|
||||
// Braun M., Buchwald S., Hack S., Leißa R., Mallon C., Zwinkau A. (2013)
|
||||
// In: Jhala R., De Bosschere K. (eds)
|
||||
// Compiler Construction. CC 2013.
|
||||
// Lecture Notes in Computer Science, vol 7791.
|
||||
// Springer, Berlin, Heidelberg
|
||||
//
|
||||
// https://link.springer.com/chapter/10.1007/978-3-642-37051-9_6
|
||||
//
|
||||
// In contrast to common eager algorithms based on dominance and dominance
|
||||
// frontier information, this algorithm works backwards from load operations.
|
||||
//
|
||||
// When a target variable is loaded, it queries the variable's reaching
|
||||
// definition. If the reaching definition is unknown at the current location,
|
||||
// it searches backwards in the CFG, inserting Phi instructions at join points
|
||||
// in the CFG along the way until it finds the desired store instruction.
|
||||
//
|
||||
// The algorithm avoids repeated lookups using memoization.
|
||||
//
|
||||
// For reducible CFGs, which are a superset of the structured CFGs in SPIRV,
|
||||
// this algorithm is proven to produce minimal SSA. That is, it inserts the
|
||||
// minimal number of Phi instructions required to ensure the SSA property, but
|
||||
// some Phi instructions may be dead
|
||||
// (https://en.wikipedia.org/wiki/Static_single_assignment_form).
|
||||
|
||||
#include "ssa_rewrite_pass.h"
|
||||
#include "cfg.h"
|
||||
#include "make_unique.h"
|
||||
#include "mem_pass.h"
|
||||
#include "opcode.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
// Debug logging (0: Off, 1-N: Verbosity level). Replace this with the
|
||||
// implementation done for
|
||||
// https://github.com/KhronosGroup/SPIRV-Tools/issues/1351
|
||||
// #define SSA_REWRITE_DEBUGGING_LEVEL 3
|
||||
|
||||
#ifdef SSA_REWRITE_DEBUGGING_LEVEL
|
||||
#include <ostream>
|
||||
#else
|
||||
#define SSA_REWRITE_DEBUGGING_LEVEL 0
|
||||
#endif
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
namespace {
|
||||
const uint32_t kStoreValIdInIdx = 1;
|
||||
const uint32_t kVariableInitIdInIdx = 1;
|
||||
} // namespace
|
||||
|
||||
std::string SSARewriter::PhiCandidate::PrettyPrint(const ir::CFG* cfg) const {
|
||||
std::ostringstream str;
|
||||
str << "%" << result_id_ << " = Phi[%" << var_id_ << ", BB %" << bb_->id()
|
||||
<< "](";
|
||||
if (phi_args_.size() > 0) {
|
||||
uint32_t arg_ix = 0;
|
||||
for (uint32_t pred_label : cfg->preds(bb_->id())) {
|
||||
uint32_t arg_id = phi_args_[arg_ix++];
|
||||
str << "[%" << arg_id << ", bb(%" << pred_label << ")] ";
|
||||
}
|
||||
}
|
||||
str << ")";
|
||||
if (copy_of_ != 0) {
|
||||
str << " [COPY OF " << copy_of_ << "]";
|
||||
}
|
||||
str << ((is_complete_) ? " [COMPLETE]" : " [INCOMPLETE]");
|
||||
|
||||
return str.str();
|
||||
}
|
||||
|
||||
SSARewriter::PhiCandidate& SSARewriter::CreatePhiCandidate(uint32_t var_id,
|
||||
ir::BasicBlock* bb) {
|
||||
uint32_t phi_result_id = pass_->context()->TakeNextId();
|
||||
auto result = phi_candidates_.emplace(
|
||||
phi_result_id, PhiCandidate(var_id, phi_result_id, bb));
|
||||
PhiCandidate& phi_candidate = result.first->second;
|
||||
return phi_candidate;
|
||||
}
|
||||
|
||||
void SSARewriter::ReplacePhiUsersWith(const PhiCandidate& phi_to_remove,
|
||||
uint32_t repl_id) {
|
||||
for (uint32_t user_id : phi_to_remove.users()) {
|
||||
PhiCandidate* user_phi = GetPhiCandidate(user_id);
|
||||
if (user_phi) {
|
||||
// If the user is a Phi candidate, replace all arguments that refer to
|
||||
// |phi_to_remove.result_id()| with |repl_id|.
|
||||
for (uint32_t& arg : user_phi->phi_args()) {
|
||||
if (arg == phi_to_remove.result_id()) {
|
||||
arg = repl_id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For regular loads, traverse the |load_replacement_| table looking for
|
||||
// instances of |phi_to_remove|.
|
||||
for (auto& it : load_replacement_) {
|
||||
if (it.second == phi_to_remove.result_id()) {
|
||||
it.second = repl_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SSARewriter::TryRemoveTrivialPhi(PhiCandidate* phi_candidate) {
|
||||
uint32_t same_id = 0;
|
||||
for (uint32_t arg_id : phi_candidate->phi_args()) {
|
||||
if (arg_id == same_id || arg_id == phi_candidate->result_id()) {
|
||||
// This is a self-reference operand or a reference to the same value ID.
|
||||
continue;
|
||||
}
|
||||
if (same_id != 0) {
|
||||
// This Phi candidate merges at least two values. Therefore, it is not
|
||||
// trivial.
|
||||
assert(phi_candidate->copy_of() == 0 &&
|
||||
"Phi candidate transitioning from copy to non-copy.");
|
||||
return phi_candidate->result_id();
|
||||
}
|
||||
same_id = arg_id;
|
||||
}
|
||||
|
||||
// The previous logic has determined that this Phi candidate |phi_candidate|
|
||||
// is trivial. It is essentially the copy operation phi_candidate->phi_result
|
||||
// = Phi(same, same, same, ...). Since it is not necessary, we can re-route
|
||||
// all the users of |phi_candidate->phi_result| to all its users, and remove
|
||||
// |phi_candidate|.
|
||||
|
||||
// Mark the Phi candidate as a trivial copy of |same_id|, so it won't be
|
||||
// generated.
|
||||
phi_candidate->MarkCopyOf(same_id);
|
||||
|
||||
assert(same_id != 0 && "Completed Phis cannot have %0 in their arguments");
|
||||
|
||||
// Since |phi_candidate| always produces |same_id|, replace all the users of
|
||||
// |phi_candidate| with |same_id|.
|
||||
ReplacePhiUsersWith(*phi_candidate, same_id);
|
||||
|
||||
return same_id;
|
||||
}
|
||||
|
||||
uint32_t SSARewriter::AddPhiOperands(PhiCandidate* phi_candidate) {
|
||||
assert(phi_candidate->phi_args().size() == 0 &&
|
||||
"Phi candidate already has arguments");
|
||||
|
||||
bool found_0_arg = false;
|
||||
for (uint32_t pred : pass_->cfg()->preds(phi_candidate->bb()->id())) {
|
||||
ir::BasicBlock* pred_bb = pass_->cfg()->block(pred);
|
||||
|
||||
// If |pred_bb| is not sealed, use %0 to indicate that
|
||||
// |phi_candidate| needs to be completed after the whole CFG has
|
||||
// been processed.
|
||||
//
|
||||
// Note that we cannot call GetReachingDef() in these cases
|
||||
// because this would generate an empty Phi candidate in
|
||||
// |pred_bb|. When |pred_bb| is later processed, a new definition
|
||||
// for |phi_candidate->var_id_| will be lost because
|
||||
// |phi_candidate| will still be reached by the empty Phi.
|
||||
//
|
||||
// Consider:
|
||||
//
|
||||
// BB %23:
|
||||
// %38 = Phi[%i](%int_0[%1], %39[%25])
|
||||
//
|
||||
// ...
|
||||
//
|
||||
// BB %25: [Starts unsealed]
|
||||
// %39 = Phi[%i]()
|
||||
// %34 = ...
|
||||
// OpStore %i %34 -> Currdef(%i) at %25 is %34
|
||||
// OpBranch %23
|
||||
//
|
||||
// When we first create the Phi in %38, we add an operandless Phi in
|
||||
// %39 to hold the unknown reaching def for %i.
|
||||
//
|
||||
// But then, when we go to complete %39 at the end. The reaching def
|
||||
// for %i in %25's predecessor is %38 itself. So we miss the fact
|
||||
// that %25 has a def for %i that should be used.
|
||||
//
|
||||
// By making the argument %0, we make |phi_candidate| incomplete,
|
||||
// which will cause it to be completed after the whole CFG has
|
||||
// been scanned.
|
||||
uint32_t arg_id = IsBlockSealed(pred_bb)
|
||||
? GetReachingDef(phi_candidate->var_id(), pred_bb)
|
||||
: 0;
|
||||
phi_candidate->phi_args().push_back(arg_id);
|
||||
|
||||
if (arg_id == 0) {
|
||||
found_0_arg = true;
|
||||
} else {
|
||||
// If this argument is another Phi candidate, add |phi_candidate| to the
|
||||
// list of users for the defining Phi.
|
||||
PhiCandidate* defining_phi = GetPhiCandidate(arg_id);
|
||||
if (defining_phi && defining_phi != phi_candidate) {
|
||||
defining_phi->AddUser(phi_candidate->result_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we could not fill-in all the arguments of this Phi, mark it incomplete
|
||||
// so it gets completed after the whole CFG has been processed.
|
||||
if (found_0_arg) {
|
||||
phi_candidate->MarkIncomplete();
|
||||
incomplete_phis_.push(phi_candidate);
|
||||
return phi_candidate->result_id();
|
||||
}
|
||||
|
||||
// Try to remove |phi_candidate|, if it's trivial.
|
||||
uint32_t repl_id = TryRemoveTrivialPhi(phi_candidate);
|
||||
if (repl_id == phi_candidate->result_id()) {
|
||||
// |phi_candidate| is complete and not trivial. Add it to the
|
||||
// list of Phi candidates to generate.
|
||||
phi_candidate->MarkComplete();
|
||||
phis_to_generate_.push_back(phi_candidate);
|
||||
}
|
||||
|
||||
return repl_id;
|
||||
}
|
||||
|
||||
uint32_t SSARewriter::GetReachingDef(uint32_t var_id, ir::BasicBlock* bb) {
|
||||
// If |var_id| has a definition in |bb|, return it.
|
||||
const auto& bb_it = defs_at_block_.find(bb);
|
||||
if (bb_it != defs_at_block_.end()) {
|
||||
const auto& current_defs = bb_it->second;
|
||||
const auto& var_it = current_defs.find(var_id);
|
||||
if (var_it != current_defs.end()) {
|
||||
return var_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, look up the value for |var_id| in |bb|'s predecessors.
|
||||
uint32_t val_id = 0;
|
||||
auto& predecessors = pass_->cfg()->preds(bb->id());
|
||||
if (!IsBlockSealed(bb)) {
|
||||
// We should only be reaching unsealed blocks trying to populate Phi
|
||||
// arguments. This is already handled by AddPhiOperands.
|
||||
assert(false &&
|
||||
"Trying to get a reaching definition in an unsealed block.");
|
||||
return 0;
|
||||
} else if (predecessors.size() == 1) {
|
||||
// If |bb| has exactly one predecessor, we look for |var_id|'s definition
|
||||
// there.
|
||||
val_id = GetReachingDef(var_id, pass_->cfg()->block(predecessors[0]));
|
||||
} else if (predecessors.size() > 1) {
|
||||
// If there is more than one predecessor, this is a join block which may
|
||||
// require a Phi instruction. This will act as |var_id|'s current
|
||||
// definition to break potential cycles.
|
||||
PhiCandidate& phi_candidate = CreatePhiCandidate(var_id, bb);
|
||||
WriteVariable(var_id, bb, phi_candidate.result_id());
|
||||
val_id = AddPhiOperands(&phi_candidate);
|
||||
}
|
||||
|
||||
// If we could not find a store for this variable in the path from the root
|
||||
// of the CFG, the variable is not defined, so we use undef.
|
||||
if (val_id == 0) {
|
||||
val_id = pass_->GetUndefVal(var_id);
|
||||
}
|
||||
|
||||
WriteVariable(var_id, bb, val_id);
|
||||
|
||||
return val_id;
|
||||
}
|
||||
|
||||
void SSARewriter::SealBlock(ir::BasicBlock* bb) {
|
||||
auto result = sealed_blocks_.insert(bb);
|
||||
(void)result;
|
||||
assert(result.second == true &&
|
||||
"Tried to seal the same basic block more than once.");
|
||||
}
|
||||
|
||||
void SSARewriter::ProcessStore(ir::Instruction* inst, ir::BasicBlock* bb) {
|
||||
auto opcode = inst->opcode();
|
||||
assert((opcode == SpvOpStore || opcode == SpvOpVariable) &&
|
||||
"Expecting a store or a variable definition instruction.");
|
||||
|
||||
uint32_t var_id = 0;
|
||||
uint32_t val_id = 0;
|
||||
if (opcode == SpvOpStore) {
|
||||
(void)pass_->GetPtr(inst, &var_id);
|
||||
val_id = inst->GetSingleWordInOperand(kStoreValIdInIdx);
|
||||
} else if (inst->NumInOperands() >= 2) {
|
||||
var_id = inst->result_id();
|
||||
val_id = inst->GetSingleWordInOperand(kVariableInitIdInIdx);
|
||||
}
|
||||
if (pass_->IsTargetVar(var_id)) {
|
||||
WriteVariable(var_id, bb, val_id);
|
||||
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
|
||||
std::cerr << "\tFound store '%" << var_id << " = %" << val_id << "': "
|
||||
<< inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
|
||||
<< "\n";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void SSARewriter::ProcessLoad(ir::Instruction* inst, ir::BasicBlock* bb) {
|
||||
uint32_t var_id = 0;
|
||||
(void)pass_->GetPtr(inst, &var_id);
|
||||
if (pass_->IsTargetVar(var_id)) {
|
||||
// Get the immediate reaching definition for |var_id|.
|
||||
uint32_t val_id = GetReachingDef(var_id, bb);
|
||||
|
||||
// Schedule a replacement for the result of this load instruction with
|
||||
// |val_id|. After all the rewriting decisions are made, every use of
|
||||
// this load will be replaced with |val_id|.
|
||||
const uint32_t load_id = inst->result_id();
|
||||
assert(load_replacement_.count(load_id) == 0);
|
||||
load_replacement_[load_id] = val_id;
|
||||
PhiCandidate* defining_phi = GetPhiCandidate(val_id);
|
||||
if (defining_phi) {
|
||||
defining_phi->AddUser(load_id);
|
||||
}
|
||||
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
|
||||
std::cerr << "\tFound load: "
|
||||
<< inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
|
||||
<< " (replacement for %" << load_id << " is %" << val_id << ")\n";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void SSARewriter::PrintPhiCandidates() const {
|
||||
std::cerr << "\nPhi candidates:\n";
|
||||
for (const auto& phi_it : phi_candidates_) {
|
||||
std::cerr << "\tBB %" << phi_it.second.bb()->id() << ": "
|
||||
<< phi_it.second.PrettyPrint(pass_->cfg()) << "\n";
|
||||
}
|
||||
std::cerr << "\n";
|
||||
}
|
||||
|
||||
void SSARewriter::PrintReplacementTable() const {
|
||||
std::cerr << "\nLoad replacement table\n";
|
||||
for (const auto& it : load_replacement_) {
|
||||
std::cerr << "\t%" << it.first << " -> %" << it.second << "\n";
|
||||
}
|
||||
std::cerr << "\n";
|
||||
}
|
||||
|
||||
void SSARewriter::GenerateSSAReplacements(ir::BasicBlock* bb) {
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
|
||||
std::cerr << "Generating SSA replacements for block: " << bb->id() << "\n";
|
||||
std::cerr << bb->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
|
||||
<< "\n";
|
||||
#endif
|
||||
|
||||
// Seal |bb|. This means that all the stores in it have been scanned and it's
|
||||
// ready to feed them into its successors. Note that we seal the block before
|
||||
// we scan its instructions, this way loads reading from stores within |bb| do
|
||||
// not create unnecessary trivial Phi candidates.
|
||||
SealBlock(bb);
|
||||
|
||||
for (auto& inst : *bb) {
|
||||
auto opcode = inst.opcode();
|
||||
if (opcode == SpvOpStore || opcode == SpvOpVariable) {
|
||||
ProcessStore(&inst, bb);
|
||||
} else if (inst.opcode() == SpvOpLoad) {
|
||||
ProcessLoad(&inst, bb);
|
||||
}
|
||||
}
|
||||
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
|
||||
PrintPhiCandidates();
|
||||
PrintReplacementTable();
|
||||
std::cerr << "\n\n";
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t SSARewriter::GetReplacement(std::pair<uint32_t, uint32_t> repl) {
|
||||
uint32_t val_id = repl.second;
|
||||
auto it = load_replacement_.find(val_id);
|
||||
while (it != load_replacement_.end()) {
|
||||
val_id = it->second;
|
||||
it = load_replacement_.find(val_id);
|
||||
}
|
||||
return val_id;
|
||||
}
|
||||
|
||||
uint32_t SSARewriter::GetPhiArgument(const PhiCandidate* phi_candidate,
|
||||
uint32_t ix) {
|
||||
assert(phi_candidate->IsReady() &&
|
||||
"Tried to get the final argument from an incomplete/trivial Phi");
|
||||
|
||||
uint32_t arg_id = phi_candidate->phi_args()[ix];
|
||||
while (arg_id != 0) {
|
||||
PhiCandidate* phi_user = GetPhiCandidate(arg_id);
|
||||
if (phi_user == nullptr || phi_user->IsReady()) {
|
||||
// If the argument is not a Phi or it's a Phi candidate ready to be
|
||||
// emitted, return it.
|
||||
return arg_id;
|
||||
}
|
||||
arg_id = phi_user->copy_of();
|
||||
}
|
||||
|
||||
assert(false &&
|
||||
"No Phi candidates in the copy-of chain are ready to be generated");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SSARewriter::ApplyReplacements() {
|
||||
bool modified = false;
|
||||
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 2
|
||||
std::cerr << "\n\nApplying replacement decisions to IR\n\n";
|
||||
PrintPhiCandidates();
|
||||
PrintReplacementTable();
|
||||
std::cerr << "\n\n";
|
||||
#endif
|
||||
|
||||
// Add Phi instructions from completed Phi candidates.
|
||||
std::vector<ir::Instruction*> generated_phis;
|
||||
for (const PhiCandidate* phi_candidate : phis_to_generate_) {
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 2
|
||||
std::cerr << "Phi candidate: " << phi_candidate->PrettyPrint(pass_->cfg())
|
||||
<< "\n";
|
||||
#endif
|
||||
|
||||
assert(phi_candidate->is_complete() &&
|
||||
"Tried to instantiate a Phi instruction from an incomplete Phi "
|
||||
"candidate");
|
||||
|
||||
// Build the vector of operands for the new OpPhi instruction.
|
||||
uint32_t type_id = pass_->GetPointeeTypeId(
|
||||
pass_->get_def_use_mgr()->GetDef(phi_candidate->var_id()));
|
||||
std::vector<ir::Operand> phi_operands;
|
||||
uint32_t arg_ix = 0;
|
||||
for (uint32_t pred_label : pass_->cfg()->preds(phi_candidate->bb()->id())) {
|
||||
uint32_t op_val_id = GetPhiArgument(phi_candidate, arg_ix++);
|
||||
phi_operands.push_back(
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {op_val_id}});
|
||||
phi_operands.push_back(
|
||||
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {pred_label}});
|
||||
}
|
||||
|
||||
// Generate a new OpPhi instruction and insert it in its basic
|
||||
// block.
|
||||
std::unique_ptr<ir::Instruction> phi_inst(
|
||||
new ir::Instruction(pass_->context(), SpvOpPhi, type_id,
|
||||
phi_candidate->result_id(), phi_operands));
|
||||
generated_phis.push_back(phi_inst.get());
|
||||
pass_->get_def_use_mgr()->AnalyzeInstDef(&*phi_inst);
|
||||
pass_->context()->set_instr_block(&*phi_inst, phi_candidate->bb());
|
||||
auto insert_it = phi_candidate->bb()->begin();
|
||||
insert_it.InsertBefore(std::move(phi_inst));
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Scan uses for all inserted Phi instructions. Do this separately from the
|
||||
// registration of the Phi instruction itself to avoid trying to analyze uses
|
||||
// of Phi instructions that have not been registered yet.
|
||||
for (ir::Instruction* phi_inst : generated_phis) {
|
||||
pass_->get_def_use_mgr()->AnalyzeInstUse(&*phi_inst);
|
||||
}
|
||||
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
|
||||
std::cerr << "\n\nReplacing the result of load instructions with the "
|
||||
"corresponding SSA id\n\n";
|
||||
#endif
|
||||
|
||||
// Apply replacements from the load replacement table.
|
||||
for (auto& repl : load_replacement_) {
|
||||
uint32_t load_id = repl.first;
|
||||
uint32_t val_id = GetReplacement(repl);
|
||||
ir::Instruction* load_inst =
|
||||
pass_->context()->get_def_use_mgr()->GetDef(load_id);
|
||||
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 2
|
||||
std::cerr << "\t"
|
||||
<< load_inst->PrettyPrint(
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
|
||||
<< " (%" << load_id << " -> %" << val_id << ")\n";
|
||||
#endif
|
||||
|
||||
// Remove the load instruction and replace all the uses of this load's
|
||||
// result with |val_id|. Kill any names or decorates using the load's
|
||||
// result before replacing to prevent incorrect replacement in those
|
||||
// instructions.
|
||||
pass_->context()->KillNamesAndDecorates(load_id);
|
||||
pass_->context()->ReplaceAllUsesWith(load_id, val_id);
|
||||
pass_->context()->KillInst(load_inst);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
void SSARewriter::FinalizePhiCandidate(PhiCandidate* phi_candidate) {
|
||||
assert(phi_candidate->phi_args().size() > 0 &&
|
||||
"Phi candidate should have arguments");
|
||||
|
||||
uint32_t ix = 0;
|
||||
for (uint32_t pred : pass_->cfg()->preds(phi_candidate->bb()->id())) {
|
||||
ir::BasicBlock* pred_bb = pass_->cfg()->block(pred);
|
||||
uint32_t& arg_id = phi_candidate->phi_args()[ix++];
|
||||
if (arg_id == 0) {
|
||||
// If |pred_bb| is still not sealed, it means it's unreachable. In this
|
||||
// case, we just use Undef as an argument.
|
||||
arg_id = IsBlockSealed(pred_bb)
|
||||
? GetReachingDef(phi_candidate->var_id(), pred_bb)
|
||||
: pass_->GetUndefVal(phi_candidate->var_id());
|
||||
}
|
||||
}
|
||||
|
||||
// This candidate is now completed.
|
||||
phi_candidate->MarkComplete();
|
||||
|
||||
// If |phi_candidate| is not trivial, add it to the list of Phis to generate.
|
||||
if (TryRemoveTrivialPhi(phi_candidate) == phi_candidate->result_id()) {
|
||||
// If we could not remove |phi_candidate|, it means that it is complete
|
||||
// and not trivial. Add it to the list of Phis to generate.
|
||||
assert(!phi_candidate->copy_of() && "A completed Phi cannot be trivial.");
|
||||
phis_to_generate_.push_back(phi_candidate);
|
||||
}
|
||||
}
|
||||
|
||||
void SSARewriter::FinalizePhiCandidates() {
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 1
|
||||
std::cerr << "Finalizing Phi candidates:\n\n";
|
||||
PrintPhiCandidates();
|
||||
std::cerr << "\n";
|
||||
#endif
|
||||
|
||||
// Now, complete the collected candidates.
|
||||
while (incomplete_phis_.size() > 0) {
|
||||
PhiCandidate* phi_candidate = incomplete_phis_.front();
|
||||
incomplete_phis_.pop();
|
||||
FinalizePhiCandidate(phi_candidate);
|
||||
}
|
||||
}
|
||||
|
||||
bool SSARewriter::RewriteFunctionIntoSSA(ir::Function* fp) {
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 0
|
||||
std::cerr << "Function before SSA rewrite:\n"
|
||||
<< fp->PrettyPrint(0) << "\n\n\n";
|
||||
#endif
|
||||
|
||||
// Collect variables that can be converted into SSA IDs.
|
||||
pass_->CollectTargetVars(fp);
|
||||
|
||||
// Generate all the SSA replacements and Phi candidates. This will
|
||||
// generate incomplete and trivial Phis.
|
||||
pass_->cfg()->ForEachBlockInReversePostOrder(
|
||||
fp->entry().get(),
|
||||
[this](ir::BasicBlock* bb) { GenerateSSAReplacements(bb); });
|
||||
|
||||
// Remove trivial Phis and add arguments to incomplete Phis.
|
||||
FinalizePhiCandidates();
|
||||
|
||||
// Finally, apply all the replacements in the IR.
|
||||
bool modified = ApplyReplacements();
|
||||
|
||||
#if SSA_REWRITE_DEBUGGING_LEVEL > 0
|
||||
std::cerr << "\n\n\nFunction after SSA rewrite:\n"
|
||||
<< fp->PrettyPrint(0) << "\n";
|
||||
#endif
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
void SSARewritePass::Initialize(ir::IRContext* c) { InitializeProcessing(c); }
|
||||
|
||||
Pass::Status SSARewritePass::Process(ir::IRContext* c) {
|
||||
Initialize(c);
|
||||
|
||||
bool modified = false;
|
||||
for (auto& fn : *get_module()) {
|
||||
modified |= SSARewriter(this).RewriteFunctionIntoSSA(&fn);
|
||||
}
|
||||
return modified ? Pass::Status::SuccessWithChange
|
||||
: Pass::Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
308
source/opt/ssa_rewrite_pass.h
Normal file
308
source/opt/ssa_rewrite_pass.h
Normal file
@ -0,0 +1,308 @@
|
||||
// Copyright (c) 2018 Google LLC.
|
||||
//
|
||||
// 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_SSA_REWRITE_PASS_H_
|
||||
#define LIBSPIRV_OPT_SSA_REWRITE_PASS_H_
|
||||
|
||||
#include "basic_block.h"
|
||||
#include "ir_context.h"
|
||||
#include "mem_pass.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
// Utility class for passes that need to rewrite a function into SSA. This
|
||||
// converts load/store operations on function-local variables into SSA IDs,
|
||||
// which allows them to be the target of optimizing transformations.
|
||||
//
|
||||
// Store and load operations to these variables are converted into
|
||||
// operations on SSA IDs. Phi instructions are added when needed. See the
|
||||
// SSA construction paper for algorithmic details
|
||||
// (https://link.springer.com/chapter/10.1007/978-3-642-37051-9_6)
|
||||
class SSARewriter {
|
||||
public:
|
||||
SSARewriter(MemPass* pass)
|
||||
: pass_(pass), first_phi_id_(pass_->get_module()->IdBound()) {}
|
||||
|
||||
// Rewrites SSA-target variables in function |fp| into SSA. This is the
|
||||
// entry point for the SSA rewrite algorithm. SSA-target variables are
|
||||
// locally defined variables that meet the criteria set by IsSSATargetVar.
|
||||
//
|
||||
// It returns true if function |fp| was modified. Otherwise, it returns
|
||||
// false.
|
||||
bool RewriteFunctionIntoSSA(ir::Function* fp);
|
||||
|
||||
private:
|
||||
class PhiCandidate {
|
||||
public:
|
||||
explicit PhiCandidate(uint32_t var, uint32_t result, ir::BasicBlock* block)
|
||||
: var_id_(var),
|
||||
result_id_(result),
|
||||
bb_(block),
|
||||
phi_args_(),
|
||||
copy_of_(0),
|
||||
is_complete_(false),
|
||||
users_() {}
|
||||
|
||||
PhiCandidate(const PhiCandidate&) = delete;
|
||||
PhiCandidate(PhiCandidate&&) = default;
|
||||
PhiCandidate& operator=(const PhiCandidate&) = delete;
|
||||
|
||||
uint32_t var_id() const { return var_id_; }
|
||||
uint32_t result_id() const { return result_id_; }
|
||||
ir::BasicBlock* bb() const { return bb_; }
|
||||
std::vector<uint32_t>& phi_args() { return phi_args_; }
|
||||
const std::vector<uint32_t>& phi_args() const { return phi_args_; }
|
||||
uint32_t copy_of() const { return copy_of_; }
|
||||
bool is_complete() const { return is_complete_; }
|
||||
std::vector<uint32_t>& users() { return users_; }
|
||||
const std::vector<uint32_t>& users() const { return users_; }
|
||||
|
||||
// Marks this phi candidate as a trivial copy of |orig_id|.
|
||||
void MarkCopyOf(uint32_t orig_id) { copy_of_ = orig_id; }
|
||||
|
||||
// Marks this phi candidate as incomplete.
|
||||
void MarkIncomplete() { is_complete_ = false; }
|
||||
|
||||
// Marks this phi candidate as complete.
|
||||
void MarkComplete() { is_complete_ = true; }
|
||||
|
||||
// Returns true if this Phi candidate is ready to be emitted.
|
||||
bool IsReady() const { return is_complete() && copy_of() == 0; }
|
||||
|
||||
// Pretty prints this Phi candidate into a string and returns it. |cfg| is
|
||||
// needed to lookup basic block predecessors.
|
||||
std::string PrettyPrint(const ir::CFG* cfg) const;
|
||||
|
||||
// Registers |operand_id| as a user of this Phi candidate.
|
||||
void AddUser(uint32_t operand_id) { users_.push_back(operand_id); }
|
||||
|
||||
private:
|
||||
// Variable ID that this Phi is merging.
|
||||
uint32_t var_id_;
|
||||
|
||||
// SSA ID generated by this Phi (i.e., this is the result ID of the eventual
|
||||
// Phi instruction).
|
||||
uint32_t result_id_;
|
||||
|
||||
// Basic block to hold this Phi.
|
||||
ir::BasicBlock* bb_;
|
||||
|
||||
// Vector of operands for every predecessor block of |bb|. This vector is
|
||||
// organized so that the Ith slot contains the argument coming from the Ith
|
||||
// predecessor of |bb|.
|
||||
std::vector<uint32_t> phi_args_;
|
||||
|
||||
// If this Phi is a trivial copy of another Phi, this is the ID of the
|
||||
// original. If this is 0, it means that this is not a trivial Phi.
|
||||
uint32_t copy_of_;
|
||||
|
||||
// False, if this Phi candidate has no arguments or at least one argument is
|
||||
// %0.
|
||||
bool is_complete_;
|
||||
|
||||
// List of all users for this Phi instruction. Each element is the result ID
|
||||
// of the load instruction replaced by this Phi, or the result ID of a Phi
|
||||
// candidate that has this Phi in its list of operands.
|
||||
std::vector<uint32_t> users_;
|
||||
};
|
||||
|
||||
// Type used to keep track of store operations in each basic block.
|
||||
typedef std::unordered_map<ir::BasicBlock*,
|
||||
std::unordered_map<uint32_t, uint32_t>>
|
||||
BlockDefsMap;
|
||||
|
||||
// Generates all the SSA rewriting decisions for basic block |bb|. This
|
||||
// populates the Phi candidate table (|phi_candidate_|) and the load
|
||||
// replacement table (|load_replacement_).
|
||||
void GenerateSSAReplacements(ir::BasicBlock* bb);
|
||||
|
||||
// Seals block |bb|. Sealing a basic block means |bb| and all its
|
||||
// predecessors of |bb| have been scanned for loads/stores.
|
||||
void SealBlock(ir::BasicBlock* bb);
|
||||
|
||||
// Returns true if |bb| has been sealed.
|
||||
bool IsBlockSealed(ir::BasicBlock* bb) {
|
||||
return sealed_blocks_.count(bb) != 0;
|
||||
}
|
||||
|
||||
// Returns the Phi candidate with result ID |id| if it exists in the table
|
||||
// |phi_candidates_|. If no such Phi candidate exists, it returns nullptr.
|
||||
PhiCandidate* GetPhiCandidate(uint32_t id) {
|
||||
auto it = phi_candidates_.find(id);
|
||||
return (it != phi_candidates_.end()) ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
// Replaces all the users of Phi candidate |phi_cand| to be users of
|
||||
// |repl_id|.
|
||||
void ReplacePhiUsersWith(const PhiCandidate& phi_cand, uint32_t repl_id);
|
||||
|
||||
// Returns the value ID that should replace the load ID in the given
|
||||
// replacement pair |repl|. The replacement is a pair (|load_id|, |val_id|).
|
||||
// If |val_id| is itself replaced by another value in the table, this function
|
||||
// will look the replacement for |val_id| until it finds one that is not
|
||||
// itself replaced. For instance, given:
|
||||
//
|
||||
// %34 = OpLoad %float %f1
|
||||
// OpStore %t %34
|
||||
// %36 = OpLoad %float %t
|
||||
//
|
||||
// Assume that %f1 is reached by a Phi candidate %42, the load
|
||||
// replacement table will have the following entries:
|
||||
//
|
||||
// %34 -> %42
|
||||
// %36 -> %34
|
||||
//
|
||||
// So, when looking for the replacement for %36, we should not use
|
||||
// %34. Rather, we should use %42. To do this, the chain of
|
||||
// replacements must be followed until we reach an element that has
|
||||
// no replacement.
|
||||
uint32_t GetReplacement(std::pair<uint32_t, uint32_t> repl);
|
||||
|
||||
// Returns the argument at index |ix| from |phi_candidate|. If argument |ix|
|
||||
// comes from a trivial Phi, it follows the copy-of chain from that trivial
|
||||
// Phi until it finds the original Phi candidate.
|
||||
//
|
||||
// This is only valid after all Phi candidates have been completed. It can
|
||||
// only be called when generating the IR for these Phis.
|
||||
uint32_t GetPhiArgument(const PhiCandidate* phi_candidate, uint32_t ix);
|
||||
|
||||
// Applies all the SSA replacement decisions. This replaces loads/stores to
|
||||
// SSA target variables with their corresponding SSA IDs, and inserts Phi
|
||||
// instructions for them.
|
||||
bool ApplyReplacements();
|
||||
|
||||
// Registers a definition for variable |var_id| in basic block |bb| with
|
||||
// value |val_id|.
|
||||
void WriteVariable(uint32_t var_id, ir::BasicBlock* bb, uint32_t val_id) {
|
||||
defs_at_block_[bb][var_id] = val_id;
|
||||
}
|
||||
|
||||
// Processes the store operation |inst| in basic block |bb|. This extracts
|
||||
// the variable ID being stored into, determines whether the variable is an
|
||||
// SSA-target variable, and, if it is, it stores its value in the
|
||||
// |defs_at_block_| map.
|
||||
void ProcessStore(ir::Instruction* inst, ir::BasicBlock* bb);
|
||||
|
||||
// Processes the load operation |inst| in basic block |bb|. This extracts
|
||||
// the variable ID being stored into, determines whether the variable is an
|
||||
// SSA-target variable, and, if it is, it reads its reaching definition by
|
||||
// calling |GetReachingDef|.
|
||||
void ProcessLoad(ir::Instruction* inst, ir::BasicBlock* bb);
|
||||
|
||||
// Reads the current definition for variable |var_id| in basic block |bb|.
|
||||
// If |var_id| is not defined in block |bb| it walks up the predecessors of
|
||||
// |bb|, creating new Phi candidates along the way, if needed.
|
||||
//
|
||||
// It returns the value for |var_id| from the RHS of the current reaching
|
||||
// definition for |var_id|.
|
||||
uint32_t GetReachingDef(uint32_t var_id, ir::BasicBlock* bb);
|
||||
|
||||
// Adds arguments to |phi_candidate| by getting the reaching definition of
|
||||
// |phi_candidate|'s variable on each of the predecessors of its basic
|
||||
// block. After populating the argument list, it determines whether all its
|
||||
// arguments are the same. If so, it returns the ID of the argument that
|
||||
// this Phi copies.
|
||||
uint32_t AddPhiOperands(PhiCandidate* phi_candidate);
|
||||
|
||||
// Creates a Phi candidate instruction for variable |var_id| in basic block
|
||||
// |bb|.
|
||||
//
|
||||
// Since the rewriting algorithm may remove Phi candidates when it finds
|
||||
// them to be trivial, we avoid the expense of creating actual Phi
|
||||
// instructions by keeping a pool of Phi candidates (|phi_candidates_|)
|
||||
// during rewriting.
|
||||
//
|
||||
// Once the candidate Phi is created, it returns its ID.
|
||||
PhiCandidate& CreatePhiCandidate(uint32_t var_id, ir::BasicBlock* bb);
|
||||
|
||||
// Attempts to remove a trivial Phi candidate |phi_cand|. Trivial Phis are
|
||||
// those that only reference themselves and one other value |val| any number
|
||||
// of times. This will try to remove any other Phis that become trivial
|
||||
// after |phi_cand| is removed.
|
||||
//
|
||||
// If |phi_cand| is trivial, it returns the SSA ID for the value that should
|
||||
// replace it. Otherwise, it returns the SSA ID for |phi_cand|.
|
||||
uint32_t TryRemoveTrivialPhi(PhiCandidate* phi_cand);
|
||||
|
||||
// Finalizes |phi_candidate| by replacing every argument that is still %0
|
||||
// with its reaching definition.
|
||||
void FinalizePhiCandidate(PhiCandidate* phi_candidate);
|
||||
|
||||
// Finalizes processing of Phi candidates. Once the whole function has been
|
||||
// scanned for loads and stores, the CFG will still have some incomplete and
|
||||
// trivial Phis. This will add missing arguments and remove trivial Phi
|
||||
// candidates.
|
||||
void FinalizePhiCandidates();
|
||||
|
||||
// Prints the table of Phi candidates to std::cerr.
|
||||
void PrintPhiCandidates() const;
|
||||
|
||||
// Prints the load replacement table to std::cerr.
|
||||
void PrintReplacementTable() const;
|
||||
|
||||
// Map holding the value of every SSA-target variable at every basic block
|
||||
// where the variable is stored. defs_at_block_[block][var_id] = val_id
|
||||
// means that there is a store or Phi instruction for variable |var_id| at
|
||||
// basic block |block| with value |val_id|.
|
||||
BlockDefsMap defs_at_block_;
|
||||
|
||||
// Map, indexed by Phi ID, holding all the Phi candidates created during SSA
|
||||
// rewriting. |phi_candidates_[id]| returns the Phi candidate whose result
|
||||
// is |id|.
|
||||
std::unordered_map<uint32_t, PhiCandidate> phi_candidates_;
|
||||
|
||||
// Queue of incomplete Phi candidates. These are Phi candidates created at
|
||||
// unsealed blocks. They need to be completed before they are instantiated
|
||||
// in ApplyReplacements.
|
||||
std::queue<PhiCandidate*> incomplete_phis_;
|
||||
|
||||
// List of completed Phi candidates. These are the only candidates that
|
||||
// will become real Phi instructions.
|
||||
std::vector<PhiCandidate*> phis_to_generate_;
|
||||
|
||||
// SSA replacement table. This maps variable IDs, resulting from a load
|
||||
// operation, to the value IDs that will replace them after SSA rewriting.
|
||||
// After all the rewriting decisions are made, a final scan through the IR
|
||||
// is done to replace all uses of the original load ID with the value ID.
|
||||
std::unordered_map<uint32_t, uint32_t> load_replacement_;
|
||||
|
||||
// Set of blocks that have been sealed already.
|
||||
std::unordered_set<ir::BasicBlock*> sealed_blocks_;
|
||||
|
||||
// Memory pass requesting the SSA rewriter.
|
||||
MemPass* pass_;
|
||||
|
||||
// ID of the first Phi created by the SSA rewriter. During rewriting, any
|
||||
// ID bigger than this corresponds to a Phi candidate.
|
||||
uint32_t first_phi_id_;
|
||||
};
|
||||
|
||||
class SSARewritePass : public MemPass {
|
||||
public:
|
||||
SSARewritePass() = default;
|
||||
const char* name() const override { return "ssa-rewrite"; }
|
||||
Status Process(ir::IRContext* c) override;
|
||||
|
||||
private:
|
||||
// Initializes the pass.
|
||||
void Initialize(ir::IRContext* c);
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // LIBSPIRV_OPT_SSA_REWRITE_PASS_H_
|
@ -112,25 +112,25 @@ OpStore %f %float_0
|
||||
OpStore %i %int_0
|
||||
OpBranch %23
|
||||
%23 = OpLabel
|
||||
%38 = OpPhi %float %float_0 %22 %34 %25
|
||||
%39 = OpPhi %int %int_0 %22 %36 %25
|
||||
%39 = OpPhi %float %float_0 %22 %34 %25
|
||||
%38 = OpPhi %int %int_0 %22 %36 %25
|
||||
OpLoopMerge %24 %25 None
|
||||
OpBranch %26
|
||||
%26 = OpLabel
|
||||
%28 = OpSLessThan %bool %39 %int_4
|
||||
%28 = OpSLessThan %bool %38 %int_4
|
||||
OpBranchConditional %28 %29 %24
|
||||
%29 = OpLabel
|
||||
%32 = OpAccessChain %_ptr_Input_float %BC %39
|
||||
%32 = OpAccessChain %_ptr_Input_float %BC %38
|
||||
%33 = OpLoad %float %32
|
||||
%34 = OpFAdd %float %38 %33
|
||||
%34 = OpFAdd %float %39 %33
|
||||
OpStore %f %34
|
||||
OpBranch %25
|
||||
%25 = OpLabel
|
||||
%36 = OpIAdd %int %39 %int_1
|
||||
%36 = OpIAdd %int %38 %int_1
|
||||
OpStore %i %36
|
||||
OpBranch %23
|
||||
%24 = OpLabel
|
||||
OpStore %fo %38
|
||||
OpStore %fo %39
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
@ -241,8 +241,7 @@ OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string after =
|
||||
R"(%46 = OpUndef %float
|
||||
%main = OpFunction %void None %9
|
||||
R"(%main = OpFunction %void None %9
|
||||
%23 = OpLabel
|
||||
%f = OpVariable %_ptr_Function_float Function
|
||||
%i = OpVariable %_ptr_Function_int Function
|
||||
@ -251,16 +250,15 @@ OpStore %f %float_0
|
||||
OpStore %i %int_0
|
||||
OpBranch %24
|
||||
%24 = OpLabel
|
||||
%44 = OpPhi %float %float_0 %23 %48 %26
|
||||
%45 = OpPhi %int %int_0 %23 %42 %26
|
||||
%47 = OpPhi %float %46 %23 %33 %26
|
||||
%45 = OpPhi %float %float_0 %23 %47 %26
|
||||
%44 = OpPhi %int %int_0 %23 %42 %26
|
||||
OpLoopMerge %25 %26 None
|
||||
OpBranch %27
|
||||
%27 = OpLabel
|
||||
%29 = OpSLessThan %bool %45 %int_4
|
||||
%29 = OpSLessThan %bool %44 %int_4
|
||||
OpBranchConditional %29 %30 %25
|
||||
%30 = OpLabel
|
||||
%32 = OpAccessChain %_ptr_Input_float %BC %45
|
||||
%32 = OpAccessChain %_ptr_Input_float %BC %44
|
||||
%33 = OpLoad %float %32
|
||||
OpStore %t %33
|
||||
%35 = OpFOrdLessThan %bool %33 %float_0
|
||||
@ -269,16 +267,16 @@ OpBranchConditional %35 %37 %36
|
||||
%37 = OpLabel
|
||||
OpBranch %26
|
||||
%36 = OpLabel
|
||||
%40 = OpFAdd %float %44 %33
|
||||
%40 = OpFAdd %float %45 %33
|
||||
OpStore %f %40
|
||||
OpBranch %26
|
||||
%26 = OpLabel
|
||||
%48 = OpPhi %float %44 %37 %40 %36
|
||||
%42 = OpIAdd %int %45 %int_1
|
||||
%47 = OpPhi %float %45 %37 %40 %36
|
||||
%42 = OpIAdd %int %44 %int_1
|
||||
OpStore %i %42
|
||||
OpBranch %24
|
||||
%25 = OpLabel
|
||||
OpStore %fo %44
|
||||
OpStore %fo %45
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
@ -385,8 +383,7 @@ OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string after =
|
||||
R"(%47 = OpUndef %float
|
||||
%main = OpFunction %void None %9
|
||||
R"(%main = OpFunction %void None %9
|
||||
%24 = OpLabel
|
||||
%f = OpVariable %_ptr_Function_float Function
|
||||
%i = OpVariable %_ptr_Function_int Function
|
||||
@ -395,18 +392,17 @@ OpStore %f %float_0
|
||||
OpStore %i %int_0
|
||||
OpBranch %25
|
||||
%25 = OpLabel
|
||||
%45 = OpPhi %float %float_0 %24 %36 %27
|
||||
%46 = OpPhi %int %int_0 %24 %43 %27
|
||||
%48 = OpPhi %float %47 %24 %36 %27
|
||||
%46 = OpPhi %float %float_0 %24 %36 %27
|
||||
%45 = OpPhi %int %int_0 %24 %43 %27
|
||||
OpLoopMerge %26 %27 None
|
||||
OpBranch %28
|
||||
%28 = OpLabel
|
||||
%30 = OpSLessThan %bool %46 %int_4
|
||||
%30 = OpSLessThan %bool %45 %int_4
|
||||
OpBranchConditional %30 %31 %26
|
||||
%31 = OpLabel
|
||||
%34 = OpAccessChain %_ptr_Input_float %BC %46
|
||||
%34 = OpAccessChain %_ptr_Input_float %BC %45
|
||||
%35 = OpLoad %float %34
|
||||
%36 = OpFAdd %float %45 %35
|
||||
%36 = OpFAdd %float %46 %35
|
||||
OpStore %t %36
|
||||
%38 = OpFOrdGreaterThan %bool %36 %float_1
|
||||
OpSelectionMerge %39 None
|
||||
@ -417,12 +413,11 @@ OpBranch %26
|
||||
OpStore %f %36
|
||||
OpBranch %27
|
||||
%27 = OpLabel
|
||||
%43 = OpIAdd %int %46 %int_1
|
||||
%43 = OpIAdd %int %45 %int_1
|
||||
OpStore %i %43
|
||||
OpBranch %25
|
||||
%26 = OpLabel
|
||||
%49 = OpPhi %float %48 %28 %36 %40
|
||||
OpStore %fo %45
|
||||
OpStore %fo %46
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
@ -526,8 +521,7 @@ OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string after =
|
||||
R"(%43 = OpUndef %float
|
||||
%main = OpFunction %void None %11
|
||||
R"(%main = OpFunction %void None %11
|
||||
%23 = OpLabel
|
||||
%f1 = OpVariable %_ptr_Function_float Function
|
||||
%f2 = OpVariable %_ptr_Function_float Function
|
||||
@ -542,26 +536,25 @@ OpStore %ie %25
|
||||
OpStore %i %int_0
|
||||
OpBranch %26
|
||||
%26 = OpLabel
|
||||
%40 = OpPhi %float %float_0 %23 %41 %28
|
||||
%41 = OpPhi %float %float_1 %23 %40 %28
|
||||
%42 = OpPhi %int %int_0 %23 %38 %28
|
||||
%44 = OpPhi %float %43 %23 %40 %28
|
||||
%43 = OpPhi %float %float_1 %23 %42 %28
|
||||
%42 = OpPhi %float %float_0 %23 %43 %28
|
||||
%40 = OpPhi %int %int_0 %23 %38 %28
|
||||
OpLoopMerge %27 %28 None
|
||||
OpBranch %29
|
||||
%29 = OpLabel
|
||||
%32 = OpSLessThan %bool %42 %25
|
||||
%32 = OpSLessThan %bool %40 %25
|
||||
OpBranchConditional %32 %33 %27
|
||||
%33 = OpLabel
|
||||
OpStore %t %40
|
||||
OpStore %f1 %41
|
||||
OpStore %f2 %40
|
||||
OpStore %t %42
|
||||
OpStore %f1 %43
|
||||
OpStore %f2 %42
|
||||
OpBranch %28
|
||||
%28 = OpLabel
|
||||
%38 = OpIAdd %int %42 %int_1
|
||||
%38 = OpIAdd %int %40 %int_1
|
||||
OpStore %i %38
|
||||
OpBranch %26
|
||||
%27 = OpLabel
|
||||
OpStore %fo %40
|
||||
OpStore %fo %42
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
@ -668,7 +661,7 @@ OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string after =
|
||||
R"(%47 = OpUndef %float
|
||||
R"(%49 = OpUndef %float
|
||||
%main = OpFunction %void None %9
|
||||
%24 = OpLabel
|
||||
%f = OpVariable %_ptr_Function_float Function
|
||||
@ -678,19 +671,19 @@ OpStore %f %float_0
|
||||
OpStore %i %int_0
|
||||
OpBranch %25
|
||||
%25 = OpLabel
|
||||
%45 = OpPhi %float %float_0 %24 %37 %27
|
||||
%46 = OpPhi %int %int_0 %24 %43 %27
|
||||
%48 = OpPhi %float %47 %24 %45 %27
|
||||
%46 = OpPhi %float %float_0 %24 %37 %27
|
||||
%45 = OpPhi %int %int_0 %24 %43 %27
|
||||
%48 = OpPhi %float %49 %24 %46 %27
|
||||
OpLoopMerge %26 %27 None
|
||||
OpBranch %28
|
||||
%28 = OpLabel
|
||||
%30 = OpSLessThan %bool %46 %int_4
|
||||
%30 = OpSLessThan %bool %45 %int_4
|
||||
OpBranchConditional %30 %31 %26
|
||||
%31 = OpLabel
|
||||
OpStore %t %45
|
||||
%35 = OpAccessChain %_ptr_Input_float %BC %46
|
||||
OpStore %t %46
|
||||
%35 = OpAccessChain %_ptr_Input_float %BC %45
|
||||
%36 = OpLoad %float %35
|
||||
%37 = OpFAdd %float %45 %36
|
||||
%37 = OpFAdd %float %46 %36
|
||||
OpStore %f %37
|
||||
%39 = OpFOrdGreaterThan %bool %37 %float_1
|
||||
OpSelectionMerge %40 None
|
||||
@ -700,13 +693,12 @@ OpBranch %26
|
||||
%40 = OpLabel
|
||||
OpBranch %27
|
||||
%27 = OpLabel
|
||||
%43 = OpIAdd %int %46 %int_1
|
||||
%43 = OpIAdd %int %45 %int_1
|
||||
OpStore %i %43
|
||||
OpBranch %25
|
||||
%26 = OpLabel
|
||||
%49 = OpPhi %float %45 %28 %37 %41
|
||||
%50 = OpPhi %float %48 %28 %45 %41
|
||||
OpStore %fo %50
|
||||
%47 = OpPhi %float %48 %28 %46 %41
|
||||
OpStore %fo %47
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
@ -1489,6 +1481,103 @@ OpFunctionEnd
|
||||
EXPECT_TRUE(status == opt::Pass::Status::SuccessWithChange);
|
||||
}
|
||||
|
||||
// TODO(dneto): Add Effcee as required dependency, and make this unconditional.
|
||||
#ifdef SPIRV_EFFCEE
|
||||
TEST_F(LocalSSAElimTest, CompositeExtractProblem) {
|
||||
const std::string spv_asm = R"(
|
||||
OpCapability Tessellation
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint TessellationControl %2 "main"
|
||||
%void = OpTypeVoid
|
||||
%4 = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_3 = OpConstant %uint 3
|
||||
%v3float = OpTypeVector %float 3
|
||||
%v2float = OpTypeVector %float 2
|
||||
%_struct_11 = OpTypeStruct %v4float %v4float %v4float %v3float %v3float %v2float %v2float
|
||||
%_arr__struct_11_uint_3 = OpTypeArray %_struct_11 %uint_3
|
||||
%_ptr_Function__arr__struct_11_uint_3 = OpTypePointer Function %_arr__struct_11_uint_3
|
||||
%_arr_v4float_uint_3 = OpTypeArray %v4float %uint_3
|
||||
%_ptr_Input__arr_v4float_uint_3 = OpTypePointer Input %_arr_v4float_uint_3
|
||||
%16 = OpVariable %_ptr_Input__arr_v4float_uint_3 Input
|
||||
%17 = OpVariable %_ptr_Input__arr_v4float_uint_3 Input
|
||||
%18 = OpVariable %_ptr_Input__arr_v4float_uint_3 Input
|
||||
%_ptr_Input_uint = OpTypePointer Input %uint
|
||||
%20 = OpVariable %_ptr_Input_uint Input
|
||||
%_ptr_Output__arr_v4float_uint_3 = OpTypePointer Output %_arr_v4float_uint_3
|
||||
%22 = OpVariable %_ptr_Output__arr_v4float_uint_3 Output
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
%_arr_v3float_uint_3 = OpTypeArray %v3float %uint_3
|
||||
%_ptr_Input__arr_v3float_uint_3 = OpTypePointer Input %_arr_v3float_uint_3
|
||||
%26 = OpVariable %_ptr_Input__arr_v3float_uint_3 Input
|
||||
%27 = OpVariable %_ptr_Input__arr_v3float_uint_3 Input
|
||||
%_arr_v2float_uint_3 = OpTypeArray %v2float %uint_3
|
||||
%_ptr_Input__arr_v2float_uint_3 = OpTypePointer Input %_arr_v2float_uint_3
|
||||
%30 = OpVariable %_ptr_Input__arr_v2float_uint_3 Input
|
||||
%31 = OpVariable %_ptr_Input__arr_v2float_uint_3 Input
|
||||
%_ptr_Function__struct_11 = OpTypePointer Function %_struct_11
|
||||
%2 = OpFunction %void None %4
|
||||
%33 = OpLabel
|
||||
%34 = OpLoad %_arr_v4float_uint_3 %16
|
||||
%35 = OpLoad %_arr_v4float_uint_3 %17
|
||||
%36 = OpLoad %_arr_v4float_uint_3 %18
|
||||
%37 = OpLoad %_arr_v3float_uint_3 %26
|
||||
%38 = OpLoad %_arr_v3float_uint_3 %27
|
||||
%39 = OpLoad %_arr_v2float_uint_3 %30
|
||||
%40 = OpLoad %_arr_v2float_uint_3 %31
|
||||
%41 = OpCompositeExtract %v4float %34 0
|
||||
%42 = OpCompositeExtract %v4float %35 0
|
||||
%43 = OpCompositeExtract %v4float %36 0
|
||||
%44 = OpCompositeExtract %v3float %37 0
|
||||
%45 = OpCompositeExtract %v3float %38 0
|
||||
%46 = OpCompositeExtract %v2float %39 0
|
||||
%47 = OpCompositeExtract %v2float %40 0
|
||||
%48 = OpCompositeConstruct %_struct_11 %41 %42 %43 %44 %45 %46 %47
|
||||
%49 = OpCompositeExtract %v4float %34 1
|
||||
%50 = OpCompositeExtract %v4float %35 1
|
||||
%51 = OpCompositeExtract %v4float %36 1
|
||||
%52 = OpCompositeExtract %v3float %37 1
|
||||
%53 = OpCompositeExtract %v3float %38 1
|
||||
%54 = OpCompositeExtract %v2float %39 1
|
||||
%55 = OpCompositeExtract %v2float %40 1
|
||||
%56 = OpCompositeConstruct %_struct_11 %49 %50 %51 %52 %53 %54 %55
|
||||
%57 = OpCompositeExtract %v4float %34 2
|
||||
%58 = OpCompositeExtract %v4float %35 2
|
||||
%59 = OpCompositeExtract %v4float %36 2
|
||||
%60 = OpCompositeExtract %v3float %37 2
|
||||
%61 = OpCompositeExtract %v3float %38 2
|
||||
%62 = OpCompositeExtract %v2float %39 2
|
||||
%63 = OpCompositeExtract %v2float %40 2
|
||||
%64 = OpCompositeConstruct %_struct_11 %57 %58 %59 %60 %61 %62 %63
|
||||
%65 = OpCompositeConstruct %_arr__struct_11_uint_3 %48 %56 %64
|
||||
%66 = OpVariable %_ptr_Function__arr__struct_11_uint_3 Function
|
||||
%67 = OpLoad %uint %20
|
||||
|
||||
; CHECK OpStore {{%\d+}} [[store_source:%\d+]]
|
||||
OpStore %66 %65
|
||||
%68 = OpAccessChain %_ptr_Function__struct_11 %66 %67
|
||||
|
||||
; This load was being removed, because %_ptr_Function__struct_11 was being
|
||||
; wrongfully considered an SSA target.
|
||||
; CHECK OpLoad %_struct_11 %68
|
||||
%69 = OpLoad %_struct_11 %68
|
||||
|
||||
; Similarly, %69 cannot be replaced with %65.
|
||||
; CHECK-NOT: OpCompositeExtract %v4float [[store_source]] 0
|
||||
%70 = OpCompositeExtract %v4float %69 0
|
||||
|
||||
%71 = OpAccessChain %_ptr_Output_v4float %22 %67
|
||||
OpStore %71 %70
|
||||
OpReturn
|
||||
OpFunctionEnd)";
|
||||
|
||||
SinglePassRunAndMatch<opt::SSARewritePass>(spv_asm, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO(greg-lunarg): Add tests to verify handling of these cases:
|
||||
//
|
||||
// No optimization in the presence of
|
||||
|
@ -195,9 +195,6 @@ Options (in lexicographical order):
|
||||
|
||||
These conditions are guaranteed to be met after running
|
||||
dead-branch elimination.
|
||||
--local-redundancy-elimination
|
||||
Looks for instructions in the same basic block that compute the
|
||||
same value, and deletes the redundant ones.
|
||||
--loop-unswitch
|
||||
Hoists loop-invariant conditionals out of loops by duplicating
|
||||
the loop on each branch of the conditional and adjusting each
|
||||
@ -265,6 +262,9 @@ Options (in lexicographical order):
|
||||
Replaces instructions whose opcode is valid for shader modules,
|
||||
but not for the current shader stage. To have an effect, all
|
||||
entry points must have the same execution model.
|
||||
--ssa-rewrite
|
||||
Replace loads and stores to function local variables with
|
||||
operations on SSA IDs.
|
||||
--scalar-replacement
|
||||
Replace aggregate function scope variables that are only accessed
|
||||
via their elements with new function variables representing each
|
||||
@ -277,7 +277,7 @@ Options (in lexicographical order):
|
||||
be separated with colon ':' without any blank spaces in between.
|
||||
e.g.: --set-spec-const-default-value "1:100 2:400"
|
||||
--simplify-instructions
|
||||
Will simplfy all instructions in the function as much as
|
||||
Will simplify all instructions in the function as much as
|
||||
possible.
|
||||
--skip-validation
|
||||
Will not validate the SPIR-V before optimizing. If the SPIR-V
|
||||
@ -515,6 +515,8 @@ OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
|
||||
optimizer->RegisterPass(CreateReplaceInvalidOpcodePass());
|
||||
} else if (0 == strcmp(cur_arg, "--simplify-instructions")) {
|
||||
optimizer->RegisterPass(CreateSimplificationPass());
|
||||
} else if (0 == strcmp(cur_arg, "--ssa-rewrite")) {
|
||||
optimizer->RegisterPass(CreateSSARewritePass());
|
||||
} else if (0 == strcmp(cur_arg, "--loop-unroll")) {
|
||||
optimizer->RegisterPass(CreateLoopUnrollPass(true));
|
||||
} else if (0 == strcmp(cur_arg, "--loop-unroll-partial")) {
|
||||
|
Loading…
Reference in New Issue
Block a user