summaryrefslogtreecommitdiff
path: root/source/opt
diff options
context:
space:
mode:
authorGregF <greg@LunarG.com>2017-06-16 15:37:31 -0600
committerDavid Neto <dneto@google.com>2017-07-07 17:54:21 -0400
commitcc8bad3a5b5d75921602720cd6a8957e2b716986 (patch)
treedf7cb96b0cde22321af1c14cd8d8f76e51bf0eb1 /source/opt
parent52e247f2216911507d2b9ab9b53c591e4b8c9d6f (diff)
downloadSPIRV-Tools-cc8bad3a5b5d75921602720cd6a8957e2b716986.tar.gz
SPIRV-Tools-cc8bad3a5b5d75921602720cd6a8957e2b716986.tar.bz2
SPIRV-Tools-cc8bad3a5b5d75921602720cd6a8957e2b716986.zip
Add LocalMultiStoreElim pass
A SSA local variable load/store elimination pass. For every entry point function, eliminate all loads and stores of function scope variables only referenced with non-access-chain loads and stores. Eliminate the variables as well. The presence of access chain references and function calls can inhibit the above optimization. Only shader modules with logical addressing are currently processed. Currently modules with any extensions enabled are not processed. This is left for future work. This pass is most effective if preceeded by Inlining and LocalAccessChainConvert. LocalSingleStoreElim and LocalSingleBlockElim will reduce the work that this pass has to do.
Diffstat (limited to 'source/opt')
-rw-r--r--source/opt/CMakeLists.txt2
-rw-r--r--source/opt/def_use_manager.cpp34
-rw-r--r--source/opt/def_use_manager.h6
-rw-r--r--source/opt/local_ssa_elim_pass.cpp781
-rw-r--r--source/opt/local_ssa_elim_pass.h259
-rw-r--r--source/opt/module.h12
-rw-r--r--source/opt/optimizer.cpp5
-rw-r--r--source/opt/passes.h1
8 files changed, 1087 insertions, 13 deletions
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 54223255..3752728d 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -31,6 +31,7 @@ add_library(SPIRV-Tools-opt
local_access_chain_convert_pass.h
local_single_block_elim_pass.h
local_single_store_elim_pass.h
+ local_ssa_elim_pass.h
log.h
module.h
null_pass.h
@@ -62,6 +63,7 @@ add_library(SPIRV-Tools-opt
local_access_chain_convert_pass.cpp
local_single_block_elim_pass.cpp
local_single_store_elim_pass.cpp
+ local_ssa_elim_pass.cpp
module.cpp
set_spec_constant_default_value_pass.cpp
optimizer.cpp
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp
index 3d8fd50e..a144acd4 100644
--- a/source/opt/def_use_manager.cpp
+++ b/source/opt/def_use_manager.cpp
@@ -21,7 +21,7 @@ namespace spvtools {
namespace opt {
namespace analysis {
-void DefUseManager::AnalyzeInstDefUse(ir::Instruction* inst) {
+void DefUseManager::AnalyzeInstDef(ir::Instruction* inst) {
const uint32_t def_id = inst->result_id();
if (def_id != 0) {
auto iter = id_to_def_.find(def_id);
@@ -31,10 +31,13 @@ void DefUseManager::AnalyzeInstDefUse(ir::Instruction* inst) {
ClearInst(iter->second);
}
id_to_def_[def_id] = inst;
- } else {
+ }
+ else {
ClearInst(inst);
}
+}
+void DefUseManager::AnalyzeInstUse(ir::Instruction* inst) {
// Create entry for the given instruction. Note that the instruction may
// not have any in-operands. In such cases, we still need a entry for those
// instructions so this manager knows it has seen the instruction later.
@@ -43,21 +46,26 @@ void DefUseManager::AnalyzeInstDefUse(ir::Instruction* inst) {
for (uint32_t i = 0; i < inst->NumOperands(); ++i) {
switch (inst->GetOperand(i).type) {
// For any id type but result id type
- case SPV_OPERAND_TYPE_ID:
- case SPV_OPERAND_TYPE_TYPE_ID:
- case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
- case SPV_OPERAND_TYPE_SCOPE_ID: {
- uint32_t use_id = inst->GetSingleWordOperand(i);
- // use_id is used by the instruction generating def_id.
- id_to_uses_[use_id].push_back({inst, i});
- inst_to_used_ids_[inst].push_back(use_id);
- } break;
- default:
- break;
+ case SPV_OPERAND_TYPE_ID:
+ case SPV_OPERAND_TYPE_TYPE_ID:
+ case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
+ case SPV_OPERAND_TYPE_SCOPE_ID: {
+ uint32_t use_id = inst->GetSingleWordOperand(i);
+ // use_id is used by the instruction generating def_id.
+ id_to_uses_[use_id].push_back({ inst, i });
+ inst_to_used_ids_[inst].push_back(use_id);
+ } break;
+ default:
+ break;
}
}
}
+void DefUseManager::AnalyzeInstDefUse(ir::Instruction* inst) {
+ AnalyzeInstDef(inst);
+ AnalyzeInstUse(inst);
+}
+
ir::Instruction* DefUseManager::GetDef(uint32_t id) {
auto iter = id_to_def_.find(id);
if (iter == id_to_def_.end()) return nullptr;
diff --git a/source/opt/def_use_manager.h b/source/opt/def_use_manager.h
index cd779d53..a639caba 100644
--- a/source/opt/def_use_manager.h
+++ b/source/opt/def_use_manager.h
@@ -59,6 +59,12 @@ class DefUseManager {
DefUseManager& operator=(const DefUseManager&) = delete;
DefUseManager& operator=(DefUseManager&&) = delete;
+ // Analyzes the defs in the given |inst|.
+ void AnalyzeInstDef(ir::Instruction* inst);
+
+ // Analyzes the uses in the given |inst|.
+ void AnalyzeInstUse(ir::Instruction* inst);
+
// Analyzes the defs and uses in the given |inst|.
void AnalyzeInstDefUse(ir::Instruction* inst);
diff --git a/source/opt/local_ssa_elim_pass.cpp b/source/opt/local_ssa_elim_pass.cpp
new file mode 100644
index 00000000..8ddb6e61
--- /dev/null
+++ b/source/opt/local_ssa_elim_pass.cpp
@@ -0,0 +1,781 @@
+// Copyright (c) 2017 The Khronos Group Inc.
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG 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 "local_ssa_elim_pass.h"
+
+#include "iterator.h"
+#include "cfa.h"
+
+namespace spvtools {
+namespace opt {
+
+namespace {
+
+const uint32_t kEntryPointFunctionIdInIdx = 1;
+const uint32_t kStorePtrIdInIdx = 0;
+const uint32_t kStoreValIdInIdx = 1;
+const uint32_t kLoadPtrIdInIdx = 0;
+const uint32_t kAccessChainPtrIdInIdx = 0;
+const uint32_t kTypePointerStorageClassInIdx = 0;
+const uint32_t kTypePointerTypeIdInIdx = 1;
+const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
+const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
+const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
+const uint32_t kCopyObjectOperandInIdx = 0;
+
+} // anonymous namespace
+
+bool LocalMultiStoreElimPass::IsNonPtrAccessChain(
+ const SpvOp opcode) const {
+ return opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain;
+}
+
+bool LocalMultiStoreElimPass::IsMathType(
+ const ir::Instruction* typeInst) const {
+ switch (typeInst->opcode()) {
+ case SpvOpTypeInt:
+ case SpvOpTypeFloat:
+ case SpvOpTypeBool:
+ case SpvOpTypeVector:
+ case SpvOpTypeMatrix:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool LocalMultiStoreElimPass::IsTargetType(
+ const ir::Instruction* typeInst) const {
+ if (IsMathType(typeInst))
+ return true;
+ if (typeInst->opcode() == SpvOpTypeArray)
+ return IsMathType(def_use_mgr_->GetDef(typeInst->GetSingleWordOperand(1)));
+ if (typeInst->opcode() != SpvOpTypeStruct)
+ return false;
+ // All struct members must be math type
+ int nonMathComp = 0;
+ typeInst->ForEachInId([&nonMathComp,this](const uint32_t* tid) {
+ const ir::Instruction* compTypeInst = def_use_mgr_->GetDef(*tid);
+ if (!IsMathType(compTypeInst)) ++nonMathComp;
+ });
+ return nonMathComp == 0;
+}
+
+ir::Instruction* LocalMultiStoreElimPass::GetPtr(
+ ir::Instruction* ip, uint32_t* varId) {
+ const SpvOp op = ip->opcode();
+ assert(op == SpvOpStore || op == SpvOpLoad);
+ *varId = ip->GetSingleWordInOperand(
+ op == SpvOpStore ? kStorePtrIdInIdx : kLoadPtrIdInIdx);
+ ir::Instruction* ptrInst = def_use_mgr_->GetDef(*varId);
+ ir::Instruction* varInst = ptrInst;
+ while (varInst->opcode() != SpvOpVariable) {
+ if (IsNonPtrAccessChain(varInst->opcode())) {
+ *varId = varInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
+ }
+ else {
+ assert(varInst->opcode() == SpvOpCopyObject);
+ *varId = varInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
+ }
+ varInst = def_use_mgr_->GetDef(*varId);
+ }
+ return ptrInst;
+}
+
+bool LocalMultiStoreElimPass::IsTargetVar(uint32_t varId) {
+ if (seen_non_target_vars_.find(varId) != seen_non_target_vars_.end())
+ return false;
+ if (seen_target_vars_.find(varId) != seen_target_vars_.end())
+ return true;
+ const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
+ assert(varInst->opcode() == SpvOpVariable);
+ const uint32_t varTypeId = varInst->type_id();
+ const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
+ if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) !=
+ SpvStorageClassFunction) {
+ seen_non_target_vars_.insert(varId);
+ return false;
+ }
+ const uint32_t varPteTypeId =
+ varTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
+ ir::Instruction* varPteTypeInst = def_use_mgr_->GetDef(varPteTypeId);
+ if (!IsTargetType(varPteTypeInst)) {
+ seen_non_target_vars_.insert(varId);
+ return false;
+ }
+ seen_target_vars_.insert(varId);
+ return true;
+}
+
+bool LocalMultiStoreElimPass::HasLoads(uint32_t ptrId) const {
+ analysis::UseList* uses = def_use_mgr_->GetUses(ptrId);
+ if (uses == nullptr)
+ return false;
+ for (auto u : *uses) {
+ const SpvOp op = u.inst->opcode();
+ if (IsNonPtrAccessChain(op) || op == SpvOpCopyObject) {
+ if (HasLoads(u.inst->result_id()))
+ return true;
+ }
+ else {
+ // Conservatively assume that any non-store use is a load
+ // TODO(greg-lunarg): Improve analysis around function calls, etc
+ if (op != SpvOpStore && op != SpvOpName && !IsDecorate(op))
+ return true;
+ }
+ }
+ return false;
+}
+
+bool LocalMultiStoreElimPass::IsLiveVar(uint32_t varId) const {
+ // non-function scope vars are live
+ const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
+ assert(varInst->opcode() == SpvOpVariable);
+ const uint32_t varTypeId = varInst->type_id();
+ const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
+ if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) !=
+ SpvStorageClassFunction)
+ return true;
+ // test if variable is loaded from
+ return HasLoads(varId);
+}
+
+void LocalMultiStoreElimPass::AddStores(
+ uint32_t ptr_id, std::queue<ir::Instruction*>* insts) {
+ analysis::UseList* uses = def_use_mgr_->GetUses(ptr_id);
+ if (uses != nullptr) {
+ for (auto u : *uses) {
+ if (IsNonPtrAccessChain(u.inst->opcode()))
+ AddStores(u.inst->result_id(), insts);
+ else if (u.inst->opcode() == SpvOpStore)
+ insts->push(u.inst);
+ }
+ }
+}
+
+bool LocalMultiStoreElimPass::HasOnlyNamesAndDecorates(uint32_t id) const {
+ analysis::UseList* uses = def_use_mgr_->GetUses(id);
+ if (uses == nullptr)
+ return true;
+ for (auto u : *uses) {
+ const SpvOp op = u.inst->opcode();
+ if (op != SpvOpName && !IsDecorate(op))
+ return false;
+ }
+ return true;
+}
+
+void LocalMultiStoreElimPass::KillNamesAndDecorates(uint32_t id) {
+ // TODO(greg-lunarg): Remove id from any OpGroupDecorate and
+ // kill if no other operands.
+ analysis::UseList* uses = def_use_mgr_->GetUses(id);
+ if (uses == nullptr)
+ return;
+ std::list<ir::Instruction*> killList;
+ for (auto u : *uses) {
+ const SpvOp op = u.inst->opcode();
+ if (op != SpvOpName && !IsDecorate(op))
+ continue;
+ killList.push_back(u.inst);
+ }
+ for (auto kip : killList)
+ def_use_mgr_->KillInst(kip);
+}
+
+void LocalMultiStoreElimPass::KillNamesAndDecorates(ir::Instruction* inst) {
+ // TODO(greg-lunarg): Remove inst from any OpGroupDecorate and
+ // kill if not other operands.
+ const uint32_t rId = inst->result_id();
+ if (rId == 0)
+ return;
+ KillNamesAndDecorates(rId);
+}
+
+void LocalMultiStoreElimPass::DCEInst(ir::Instruction* inst) {
+ std::queue<ir::Instruction*> deadInsts;
+ deadInsts.push(inst);
+ while (!deadInsts.empty()) {
+ ir::Instruction* di = deadInsts.front();
+ // Don't delete labels
+ if (di->opcode() == SpvOpLabel) {
+ deadInsts.pop();
+ continue;
+ }
+ // Remember operands
+ std::vector<uint32_t> ids;
+ di->ForEachInId([&ids](uint32_t* iid) {
+ ids.push_back(*iid);
+ });
+ uint32_t varId = 0;
+ // Remember variable if dead load
+ if (di->opcode() == SpvOpLoad)
+ (void) GetPtr(di, &varId);
+ KillNamesAndDecorates(di);
+ def_use_mgr_->KillInst(di);
+ // For all operands with no remaining uses, add their instruction
+ // to the dead instruction queue.
+ for (auto id : ids)
+ if (HasOnlyNamesAndDecorates(id))
+ deadInsts.push(def_use_mgr_->GetDef(id));
+ // if a load was deleted and it was the variable's
+ // last load, add all its stores to dead queue
+ if (varId != 0 && !IsLiveVar(varId))
+ AddStores(varId, &deadInsts);
+ deadInsts.pop();
+ }
+}
+
+bool LocalMultiStoreElimPass::HasOnlySupportedRefs(uint32_t varId) {
+ if (supported_ref_vars_.find(varId) != supported_ref_vars_.end())
+ return true;
+ analysis::UseList* uses = def_use_mgr_->GetUses(varId);
+ if (uses == nullptr)
+ return true;
+ for (auto u : *uses) {
+ const SpvOp op = u.inst->opcode();
+ if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName &&
+ !IsDecorate(op))
+ return false;
+ }
+ supported_ref_vars_.insert(varId);
+ return true;
+}
+
+void LocalMultiStoreElimPass::InitSSARewrite(ir::Function& func) {
+ // Init predecessors
+ label2preds_.clear();
+ for (auto& blk : func) {
+ uint32_t blkId = blk.id();
+ blk.ForEachSuccessorLabel([&blkId, this](uint32_t sbid) {
+ label2preds_[sbid].push_back(blkId);
+ });
+ }
+ // 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;
+ }
+ }
+ }
+}
+
+uint32_t LocalMultiStoreElimPass::MergeBlockIdIfAny(const ir::BasicBlock& blk,
+ uint32_t* cbid) {
+ auto merge_ii = blk.cend();
+ --merge_ii;
+ *cbid = 0;
+ uint32_t mbid = 0;
+ if (merge_ii != blk.cbegin()) {
+ --merge_ii;
+ if (merge_ii->opcode() == SpvOpLoopMerge) {
+ mbid = merge_ii->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx);
+ *cbid = merge_ii->GetSingleWordInOperand(kLoopMergeContinueBlockIdInIdx);
+ }
+ else if (merge_ii->opcode() == SpvOpSelectionMerge) {
+ mbid = merge_ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
+ }
+ }
+ return mbid;
+}
+
+void LocalMultiStoreElimPass::ComputeStructuredSuccessors(ir::Function* func) {
+ for (auto& blk : *func) {
+ // If no predecessors in function, make successor to pseudo entry
+ if (label2preds_[blk.id()].size() == 0)
+ block2structured_succs_[&pseudo_entry_block_].push_back(&blk);
+ // If header, make merge block first successor.
+ uint32_t cbid;
+ const uint32_t mbid = MergeBlockIdIfAny(blk, &cbid);
+ if (mbid != 0) {
+ block2structured_succs_[&blk].push_back(id2block_[mbid]);
+ if (cbid != 0)
+ block2structured_succs_[&blk].push_back(id2block_[cbid]);
+ }
+ // add true successors
+ blk.ForEachSuccessorLabel([&blk, this](uint32_t sbid) {
+ block2structured_succs_[&blk].push_back(id2block_[sbid]);
+ });
+ }
+}
+
+void LocalMultiStoreElimPass::ComputeStructuredOrder(
+ ir::Function* func, std::list<ir::BasicBlock*>* order) {
+ // Compute structured successors and do DFS
+ ComputeStructuredSuccessors(func);
+ auto ignore_block = [](cbb_ptr) {};
+ auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
+ auto get_structured_successors = [this](const ir::BasicBlock* block) {
+ return &(block2structured_succs_[block]); };
+ // TODO(greg-lunarg): Get rid of const_cast by making moving const
+ // out of the cfa.h prototypes and into the invoking code.
+ auto post_order = [&](cbb_ptr b) {
+ order->push_front(const_cast<ir::BasicBlock*>(b)); };
+
+ spvtools::CFA<ir::BasicBlock>::DepthFirstTraversal(
+ &pseudo_entry_block_, get_structured_successors, ignore_block,
+ post_order, ignore_edge);
+}
+
+void LocalMultiStoreElimPass::SSABlockInitSinglePred(ir::BasicBlock* block_ptr) {
+ // Copy map entry from single predecessor
+ const uint32_t label = block_ptr->id();
+ const uint32_t predLabel = label2preds_[label].front();
+ assert(visitedBlocks_.find(predLabel) != visitedBlocks_.end());
+ label2ssa_map_[label] = label2ssa_map_[predLabel];
+}
+
+bool LocalMultiStoreElimPass::IsLiveAfter(uint32_t var_id, uint32_t label) const {
+ // For now, return very conservative result: true. This will result in
+ // correct, but possibly usused, phi code to be generated. A subsequent
+ // DCE pass should eliminate this code.
+ // TODO(greg-lunarg): Return more accurate information
+ (void) var_id;
+ (void) label;
+ return true;
+}
+
+uint32_t LocalMultiStoreElimPass::Type2Undef(uint32_t type_id) {
+ const auto uitr = type2undefs_.find(type_id);
+ if (uitr != type2undefs_.end())
+ return uitr->second;
+ const uint32_t undefId = TakeNextId();
+ std::unique_ptr<ir::Instruction> undef_inst(
+ new ir::Instruction(SpvOpUndef, type_id, undefId, {}));
+ def_use_mgr_->AnalyzeInstDefUse(&*undef_inst);
+ module_->AddGlobalValue(std::move(undef_inst));
+ type2undefs_[type_id] = undefId;
+ return undefId;
+}
+
+uint32_t LocalMultiStoreElimPass::GetPointeeTypeId(
+ const ir::Instruction* ptrInst) const {
+ const uint32_t ptrTypeId = ptrInst->type_id();
+ const ir::Instruction* ptrTypeInst = def_use_mgr_->GetDef(ptrTypeId);
+ return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
+}
+
+void LocalMultiStoreElimPass::SSABlockInitLoopHeader(
+ std::list<ir::BasicBlock*>::iterator block_itr) {
+ const uint32_t label = (*block_itr)->id();
+ // Determine backedge label.
+ uint32_t backLabel = 0;
+ for (uint32_t predLabel : label2preds_[label])
+ if (visitedBlocks_.find(predLabel) == visitedBlocks_.end()) {
+ assert(backLabel == 0);
+ backLabel = predLabel;
+ break;
+ }
+ assert(backLabel != 0);
+ // Determine merge block.
+ auto mergeInst = (*block_itr)->end();
+ --mergeInst;
+ --mergeInst;
+ uint32_t mergeLabel = mergeInst->GetSingleWordInOperand(
+ kLoopMergeMergeBlockIdInIdx);
+ // Collect all live variables and a default value for each across all
+ // non-backedge predecesors. Must be ordered map because phis are
+ // generated based on order and test results will otherwise vary across
+ // platforms.
+ std::map<uint32_t, uint32_t> liveVars;
+ for (uint32_t predLabel : label2preds_[label]) {
+ for (auto var_val : label2ssa_map_[predLabel]) {
+ uint32_t varId = var_val.first;
+ liveVars[varId] = var_val.second;
+ }
+ }
+ // Add all stored variables in loop. Set their default value id to zero.
+ for (auto bi = block_itr; (*bi)->id() != mergeLabel; ++bi) {
+ ir::BasicBlock* bp = *bi;
+ for (auto ii = bp->begin(); ii != bp->end(); ++ii) {
+ if (ii->opcode() != SpvOpStore)
+ continue;
+ uint32_t varId;
+ (void) GetPtr(&*ii, &varId);
+ if (!IsTargetVar(varId))
+ continue;
+ liveVars[varId] = 0;
+ }
+ }
+ // Insert phi for all live variables that require them. All variables
+ // defined in loop require a phi. Otherwise all variables
+ // with differing predecessor values require a phi.
+ auto insertItr = (*block_itr)->begin();
+ for (auto var_val : liveVars) {
+ const uint32_t varId = var_val.first;
+ if (!IsLiveAfter(varId, label))
+ continue;
+ const uint32_t val0Id = var_val.second;
+ bool needsPhi = false;
+ if (val0Id != 0) {
+ for (uint32_t predLabel : label2preds_[label]) {
+ // Skip back edge predecessor.
+ if (predLabel == backLabel)
+ continue;
+ const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
+ // Missing (undef) values always cause difference with (defined) value
+ if (var_val_itr == label2ssa_map_[predLabel].end()) {
+ needsPhi = true;
+ break;
+ }
+ if (var_val_itr->second != val0Id) {
+ needsPhi = true;
+ break;
+ }
+ }
+ }
+ else {
+ needsPhi = true;
+ }
+ // If val is the same for all predecessors, enter it in map
+ if (!needsPhi) {
+ label2ssa_map_[label].insert(var_val);
+ continue;
+ }
+ // Val differs across predecessors. Add phi op to block and
+ // add its result id to the map. For live back edge predecessor,
+ // use the variable id. We will patch this after visiting back
+ // edge predecessor. For predecessors that do not define a value,
+ // use undef.
+ std::vector<ir::Operand> phi_in_operands;
+ uint32_t typeId = GetPointeeTypeId(def_use_mgr_->GetDef(varId));
+ for (uint32_t predLabel : label2preds_[label]) {
+ uint32_t valId;
+ if (predLabel == backLabel) {
+ if (val0Id == 0)
+ valId = varId;
+ else
+ valId = Type2Undef(typeId);
+ }
+ else {
+ const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
+ if (var_val_itr == label2ssa_map_[predLabel].end())
+ valId = Type2Undef(typeId);
+ else
+ valId = var_val_itr->second;
+ }
+ phi_in_operands.push_back(
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}});
+ phi_in_operands.push_back(
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {predLabel}});
+ }
+ const uint32_t phiId = TakeNextId();
+ std::unique_ptr<ir::Instruction> newPhi(
+ new ir::Instruction(SpvOpPhi, typeId, phiId, phi_in_operands));
+ // Only analyze the phi define now; analyze the phi uses after the
+ // phi backedge predecessor value is patched.
+ def_use_mgr_->AnalyzeInstDef(&*newPhi);
+ insertItr = insertItr.InsertBefore(std::move(newPhi));
+ ++insertItr;
+ label2ssa_map_[label].insert({ varId, phiId });
+ }
+}
+
+void LocalMultiStoreElimPass::SSABlockInitMultiPred(ir::BasicBlock* block_ptr) {
+ const uint32_t label = block_ptr->id();
+ // Collect all live variables and a default value for each across all
+ // predecesors. Must be ordered map because phis are generated based on
+ // order and test results will otherwise vary across platforms.
+ std::map<uint32_t, uint32_t> liveVars;
+ for (uint32_t predLabel : label2preds_[label]) {
+ assert(visitedBlocks_.find(predLabel) != visitedBlocks_.end());
+ for (auto var_val : label2ssa_map_[predLabel]) {
+ const uint32_t varId = var_val.first;
+ liveVars[varId] = var_val.second;
+ }
+ }
+ // For each live variable, look for a difference in values across
+ // predecessors that would require a phi and insert one.
+ auto insertItr = block_ptr->begin();
+ for (auto var_val : liveVars) {
+ const uint32_t varId = var_val.first;
+ if (!IsLiveAfter(varId, label))
+ continue;
+ const uint32_t val0Id = var_val.second;
+ bool differs = false;
+ for (uint32_t predLabel : label2preds_[label]) {
+ const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
+ // Missing values cause a difference because we'll need to create an
+ // undef for that predecessor.
+ if (var_val_itr == label2ssa_map_[predLabel].end()) {
+ differs = true;
+ break;
+ }
+ if (var_val_itr->second != val0Id) {
+ differs = true;
+ break;
+ }
+ }
+ // If val is the same for all predecessors, enter it in map
+ if (!differs) {
+ label2ssa_map_[label].insert(var_val);
+ continue;
+ }
+ // Val differs across predecessors. Add phi op to block and
+ // add its result id to the map
+ std::vector<ir::Operand> phi_in_operands;
+ const uint32_t typeId = GetPointeeTypeId(def_use_mgr_->GetDef(varId));
+ for (uint32_t predLabel : label2preds_[label]) {
+ const auto var_val_itr = label2ssa_map_[predLabel].find(varId);
+ // If variable not defined on this path, use undef
+ const uint32_t valId = (var_val_itr != label2ssa_map_[predLabel].end()) ?
+ var_val_itr->second : Type2Undef(typeId);
+ phi_in_operands.push_back(
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}});
+ phi_in_operands.push_back(
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {predLabel}});
+ }
+ const uint32_t phiId = TakeNextId();
+ std::unique_ptr<ir::Instruction> newPhi(
+ new ir::Instruction(SpvOpPhi, typeId, phiId, phi_in_operands));
+ def_use_mgr_->AnalyzeInstDefUse(&*newPhi);
+ insertItr = insertItr.InsertBefore(std::move(newPhi));
+ ++insertItr;
+ label2ssa_map_[label].insert({varId, phiId});
+ }
+}
+
+bool LocalMultiStoreElimPass::IsLoopHeader(ir::BasicBlock* block_ptr) const {
+ auto iItr = block_ptr->end();
+ --iItr;
+ if (iItr == block_ptr->begin())
+ return false;
+ --iItr;
+ return iItr->opcode() == SpvOpLoopMerge;
+}
+
+void LocalMultiStoreElimPass::SSABlockInit(
+ std::list<ir::BasicBlock*>::iterator block_itr) {
+ const size_t numPreds = label2preds_[(*block_itr)->id()].size();
+ if (numPreds == 0)
+ return;
+ if (numPreds == 1)
+ SSABlockInitSinglePred(*block_itr);
+ else if (IsLoopHeader(*block_itr))
+ SSABlockInitLoopHeader(block_itr);
+ else
+ SSABlockInitMultiPred(*block_itr);
+}
+
+void LocalMultiStoreElimPass::PatchPhis(uint32_t header_id, uint32_t back_id) {
+ ir::BasicBlock* header = id2block_[header_id];
+ auto phiItr = header->begin();
+ for (; phiItr->opcode() == SpvOpPhi; ++phiItr) {
+ uint32_t cnt = 0;
+ uint32_t idx;
+ phiItr->ForEachInId([&cnt,&back_id,&idx](uint32_t* iid) {
+ if (cnt % 2 == 1 && *iid == back_id) idx = cnt - 1;
+ ++cnt;
+ });
+ // Only patch operands that are in the backedge predecessor map
+ const uint32_t varId = phiItr->GetSingleWordInOperand(idx);
+ const auto valItr = label2ssa_map_[back_id].find(varId);
+ if (valItr != label2ssa_map_[back_id].end()) {
+ phiItr->SetInOperand(idx, { valItr->second });
+ // Analyze uses now that they are complete
+ def_use_mgr_->AnalyzeInstUse(&*phiItr);
+ }
+ }
+}
+
+bool LocalMultiStoreElimPass::EliminateMultiStoreLocal(ir::Function* func) {
+ InitSSARewrite(*func);
+ // Process all blocks in structured order. This is just one way (the
+ // simplest?) to make sure all predecessors blocks are processed before
+ // a block itself.
+ std::list<ir::BasicBlock*> structuredOrder;
+ ComputeStructuredOrder(func, &structuredOrder);
+ bool modified = false;
+ for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
+ // Skip pseudo entry block
+ if (*bi == &pseudo_entry_block_)
+ continue;
+ // Initialize this block's label2ssa_map_ entry using predecessor maps.
+ // Then process all stores and loads of targeted variables.
+ SSABlockInit(bi);
+ ir::BasicBlock* bp = *bi;
+ const uint32_t label = bp->id();
+ for (auto ii = bp->begin(); ii != bp->end(); ++ii) {
+ switch (ii->opcode()) {
+ case SpvOpStore: {
+ uint32_t varId;
+ (void) GetPtr(&*ii, &varId);
+ if (!IsTargetVar(varId))
+ break;
+ // Register new stored value for the variable
+ label2ssa_map_[label][varId] =
+ ii->GetSingleWordInOperand(kStoreValIdInIdx);
+ } break;
+ case SpvOpLoad: {
+ uint32_t varId;
+ (void) GetPtr(&*ii, &varId);
+ if (!IsTargetVar(varId))
+ break;
+ uint32_t replId = 0;
+ const auto ssaItr = label2ssa_map_.find(label);
+ if (ssaItr != label2ssa_map_.end()) {
+ const auto valItr = ssaItr->second.find(varId);
+ if (valItr != ssaItr->second.end())
+ replId = valItr->second;
+ }
+ // If variable is not defined, use undef
+ if (replId == 0) {
+ replId = Type2Undef(GetPointeeTypeId(def_use_mgr_->GetDef(varId)));
+ }
+ // Replace load's id with the last stored value id for variable
+ // and delete load. Kill any names or decorates using id before
+ // replacing to prevent incorrect replacement in those instructions.
+ const uint32_t loadId = ii->result_id();
+ KillNamesAndDecorates(loadId);
+ (void)def_use_mgr_->ReplaceAllUsesWith(loadId, replId);
+ def_use_mgr_->KillInst(&*ii);
+ modified = true;
+ } break;
+ default: {
+ } break;
+ }
+ }
+ visitedBlocks_.insert(label);
+ // Look for successor backedge and patch phis in loop header
+ // if found.
+ uint32_t header = 0;
+ bp->ForEachSuccessorLabel([&header,this](uint32_t succ) {
+ if (visitedBlocks_.find(succ) == visitedBlocks_.end()) return;
+ assert(header == 0);
+ header = succ;
+ });
+ if (header != 0)
+ PatchPhis(header, label);
+ }
+ // Remove all target variable stores.
+ for (auto bi = func->begin(); bi != func->end(); ++bi) {
+ for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
+ if (ii->opcode() != SpvOpStore)
+ continue;
+ uint32_t varId;
+ (void) GetPtr(&*ii, &varId);
+ if (!IsTargetVar(varId))
+ continue;
+ assert(!HasLoads(varId));
+ DCEInst(&*ii);
+ modified = true;
+ }
+ }
+ return modified;
+}
+
+void LocalMultiStoreElimPass::Initialize(ir::Module* module) {
+
+ module_ = module;
+
+ // TODO(greg-lunarg): Reuse def/use from previous passes
+ def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
+
+ // Initialize function and block maps
+ id2function_.clear();
+ id2block_.clear();
+ block2structured_succs_.clear();
+ for (auto& fn : *module_) {
+ id2function_[fn.result_id()] = &fn;
+ for (auto& blk : fn)
+ id2block_[blk.id()] = &blk;
+ }
+
+ // Clear collections
+ seen_target_vars_.clear();
+ seen_non_target_vars_.clear();
+ visitedBlocks_.clear();
+ type2undefs_.clear();
+ supported_ref_vars_.clear();
+ block2structured_succs_.clear();
+ label2preds_.clear();
+ label2ssa_map_.clear();
+
+ // Start new ids with next availablein module
+ next_id_ = module_->id_bound();
+};
+
+bool LocalMultiStoreElimPass::AllExtensionsSupported() const {
+ // Currently disallows all extensions. This is just super conservative
+ // to allow this to go public and many can likely be allowed with little
+ // to no additional coding. One exception is SPV_KHR_variable_pointers
+ // which will require some additional work around HasLoads, AddStores
+ // and generally DCEInst.
+ // TODO(greg-lunarg): Enable more extensions.
+ for (auto& ei : module_->extensions()) {
+ (void) ei;
+ return false;
+ }
+ return true;
+}
+
+Pass::Status LocalMultiStoreElimPass::ProcessImpl() {
+ // Assumes all control flow structured.
+ // TODO(greg-lunarg): Do SSA rewrite for non-structured control flow
+ if (!module_->HasCapability(SpvCapabilityShader))
+ return Status::SuccessWithoutChange;
+ // Assumes logical addressing only
+ // TODO(greg-lunarg): Add support for physical addressing
+ if (module_->HasCapability(SpvCapabilityAddresses))
+ return Status::SuccessWithoutChange;
+ // Do not process if module contains OpGroupDecorate. Additional
+ // support required in KillNamesAndDecorates().
+ // TODO(greg-lunarg): Add support for OpGroupDecorate
+ for (auto& ai : module_->annotations())
+ if (ai.opcode() == SpvOpGroupDecorate)
+ return Status::SuccessWithoutChange;
+ // Do not process if any disallowed extensions are enabled
+ if (!AllExtensionsSupported())
+ return Status::SuccessWithoutChange;
+ // Process functions
+ bool modified = false;
+ for (auto& e : module_->entry_points()) {
+ ir::Function* fn =
+ id2function_[e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)];
+ modified = EliminateMultiStoreLocal(fn) || modified;
+ }
+ FinalizeNextId(module_);
+ return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+LocalMultiStoreElimPass::LocalMultiStoreElimPass()
+ : module_(nullptr), def_use_mgr_(nullptr),
+ pseudo_entry_block_(std::unique_ptr<ir::Instruction>(
+ new ir::Instruction(SpvOpLabel, 0, 0, {}))),
+ next_id_(0) {}
+
+Pass::Status LocalMultiStoreElimPass::Process(ir::Module* module) {
+ Initialize(module);
+ return ProcessImpl();
+}
+
+} // namespace opt
+} // namespace spvtools
+
diff --git a/source/opt/local_ssa_elim_pass.h b/source/opt/local_ssa_elim_pass.h
new file mode 100644
index 00000000..7cce51a9
--- /dev/null
+++ b/source/opt/local_ssa_elim_pass.h
@@ -0,0 +1,259 @@
+// Copyright (c) 2017 The Khronos Group Inc.
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG 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_LOCAL_SSA_ELIM_PASS_H_
+#define LIBSPIRV_OPT_LOCAL_SSA_ELIM_PASS_H_
+
+
+#include <algorithm>
+#include <map>
+#include <queue>
+#include <utility>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "basic_block.h"
+#include "def_use_manager.h"
+#include "module.h"
+#include "pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class LocalMultiStoreElimPass : public Pass {
+ using cbb_ptr = const ir::BasicBlock*;
+
+ public:
+ using GetBlocksFunction =
+ std::function<std::vector<ir::BasicBlock*>*(const ir::BasicBlock*)>;
+
+ LocalMultiStoreElimPass();
+ const char* name() const override { return "eliminate-local-multi-store"; }
+ Status Process(ir::Module*) override;
+
+ private:
+ // Returns true if |opcode| is a non-ptr access chain op
+ bool IsNonPtrAccessChain(const SpvOp opcode) const;
+
+ // Returns true if |typeInst| is a scalar type
+ // or a vector or matrix
+ bool IsMathType(const ir::Instruction* typeInst) const;
+
+ // Returns true if |typeInst| is a math type or a struct or array
+ // of a math type.
+ bool IsTargetType(const ir::Instruction* typeInst) const;
+
+ // Given a load or store |ip|, return the pointer instruction.
+ // Also return the base variable's id in |varId|.
+ 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.
+ // 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 type id for |ptrInst|'s pointee
+ uint32_t GetPointeeTypeId(const ir::Instruction* ptrInst) const;
+
+ // Replace all instances of |loadInst|'s id with |replId| and delete
+ // |loadInst|.
+ void ReplaceAndDeleteLoad(ir::Instruction* loadInst, uint32_t replId);
+
+ // Return true if any instruction loads from |ptrId|
+ bool HasLoads(uint32_t ptrId) const;
+
+ // Return true if |varId| is not a function variable or if it has
+ // a load
+ bool IsLiveVar(uint32_t varId) const;
+
+ // Add stores using |ptr_id| to |insts|
+ void AddStores(uint32_t ptr_id, std::queue<ir::Instruction*>* insts);
+
+ // Delete |inst| and iterate DCE on all its operands. Won't delete
+ // labels.
+ void DCEInst(ir::Instruction* inst);
+
+ // Return true if all uses of |varId| are only through supported reference
+ // operations ie. loads and store. Also cache in supported_ref_vars_;
+ bool HasOnlySupportedRefs(uint32_t varId);
+
+ // Return true if all uses of |id| are only name or decorate ops.
+ bool HasOnlyNamesAndDecorates(uint32_t id) const;
+
+ // Kill all name and decorate ops using |inst|
+ void KillNamesAndDecorates(ir::Instruction* inst);
+
+ // Kill all name and decorate ops using |id|
+ void KillNamesAndDecorates(uint32_t id);
+
+ // Initialize data structures used by EliminateLocalMultiStore for
+ // function |func|, specifically block predecessors and target variables.
+ void InitSSARewrite(ir::Function& func);
+
+ // Returns the id of the merge block declared by a merge instruction in
+ // this block, if any. If none, returns zero.
+ uint32_t MergeBlockIdIfAny(const ir::BasicBlock& blk, uint32_t* cbid);
+
+ // Compute structured successors for function |func|.
+ // A block's structured successors are the blocks it branches to
+ // together with its declared merge block if it has one.
+ // When order matters, the merge block always appears first.
+ // This assures correct depth first search in the presence of early
+ // returns and kills. If the successor vector contain duplicates
+ // if the merge block, they are safely ignored by DFS.
+ void ComputeStructuredSuccessors(ir::Function* func);
+
+ // Compute structured block order for |func| into |structuredOrder|. 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,
+ std::list<ir::BasicBlock*>* order);
+
+ // Return true if loop header block
+ bool IsLoopHeader(ir::BasicBlock* block_ptr) const;
+
+ // Initialize label2ssa_map_ entry for block |block_ptr| with single
+ // predecessor.
+ void SSABlockInitSinglePred(ir::BasicBlock* block_ptr);
+
+ // Return true if variable is loaded in block with |label| or in
+ // any succeeding block in structured order.
+ bool IsLiveAfter(uint32_t var_id, uint32_t label) const;
+
+ // Initialize label2ssa_map_ entry for loop header block pointed to
+ // |block_itr| by merging entries from all predecessors. If any value
+ // ids differ for any variable across predecessors, create a phi function
+ // in the block and use that value id for the variable in the new map.
+ // Assumes all predecessors have been visited by EliminateLocalMultiStore
+ // except the back edge. Use a dummy value in the phi for the back edge
+ // until the back edge block is visited and patch the phi value then.
+ void SSABlockInitLoopHeader(std::list<ir::BasicBlock*>::iterator block_itr);
+
+ // Initialize label2ssa_map_ entry for multiple predecessor block
+ // |block_ptr| by merging label2ssa_map_ entries for all predecessors.
+ // If any value ids differ for any variable across predecessors, create
+ // a phi function in the block and use that value id for the variable in
+ // the new map. Assumes all predecessors have been visited by
+ // EliminateLocalMultiStore.
+ void SSABlockInitMultiPred(ir::BasicBlock* block_ptr);
+
+ // Initialize the label2ssa_map entry for a block pointed to by |block_itr|.
+ // Insert phi instructions into block when necessary. All predecessor
+ // blocks must have been visited by EliminateLocalMultiStore except for
+ // backedges.
+ void SSABlockInit(std::list<ir::BasicBlock*>::iterator block_itr);
+
+ // 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.
+ uint32_t Type2Undef(uint32_t type_id);
+
+ // Patch phis in loop header block now that the map is complete for the
+ // backedge predecessor. Specifically, for each phi, find the value
+ // corresponding to the backedge predecessor. That contains the variable id
+ // that this phi corresponds to. Change this phi operand to the the value
+ // which corresponds to that variable in the predecessor map.
+ void PatchPhis(uint32_t header_id, uint32_t back_id);
+
+ // Return true if all extensions in this module are allowed by this pass.
+ // Currently, no extensions are supported.
+ // TODO(greg-lunarg): Add extensions to supported list.
+ bool AllExtensionsSupported() const;
+
+ // Remove remaining loads and stores of function scope variables only
+ // referenced with non-access-chain loads and stores from function |func|.
+ // Insert Phi functions where necessary. Running LocalAccessChainRemoval,
+ // SingleBlockLocalElim and SingleStoreLocalElim beforehand will improve
+ // the runtime and effectiveness of this function.
+ bool EliminateMultiStoreLocal(ir::Function* func);
+
+ // Return true if all uses of varId are only through supported reference
+ // operations ie. loads and store. Also cache in supported_ref_vars_;
+ inline bool IsDecorate(uint32_t op) const {
+ return (op == SpvOpDecorate || op == SpvOpDecorateId);
+ }
+
+ // Save next available id into |module|.
+ inline void FinalizeNextId(ir::Module* module) {
+ module->SetIdBound(next_id_);
+ }
+
+ // Return next available id and calculate next.
+ inline uint32_t TakeNextId() {
+ return next_id_++;
+ }
+
+ void Initialize(ir::Module* module);
+ Pass::Status ProcessImpl();
+
+ // Module this pass is processing
+ ir::Module* module_;
+
+ // Def-Uses for the module we are processing
+ std::unique_ptr<analysis::DefUseManager> def_use_mgr_;
+
+ // Map from function's result id to function
+ std::unordered_map<uint32_t, ir::Function*> id2function_;
+
+ // Map from block's label id to block.
+ std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
+
+ // Cache of previously seen target types
+ std::unordered_set<uint32_t> seen_target_vars_;
+
+ // Cache of previously seen non-target types
+ std::unordered_set<uint32_t> seen_non_target_vars_;
+
+ // Set of label ids of visited blocks
+ std::unordered_set<uint32_t> visitedBlocks_;
+
+ // Map from type to undef
+ std::unordered_map<uint32_t, uint32_t> type2undefs_;
+
+ // Variables that are only referenced by supported operations for this
+ // pass ie. loads and stores.
+ std::unordered_set<uint32_t> supported_ref_vars_;
+
+ // Map from block to its structured successor blocks. See
+ // ComputeStructuredSuccessors() for definition.
+ std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
+ block2structured_succs_;
+
+ // Map from block's label id to its predecessor blocks ids
+ std::unordered_map<uint32_t, std::vector<uint32_t>> label2preds_;
+
+ // Map from block's label id to a map of a variable to its value at the
+ // end of the block.
+ std::unordered_map<uint32_t, std::unordered_map<uint32_t, uint32_t>>
+ label2ssa_map_;
+
+ // Extra block whose successors are all blocks with no predecessors
+ // in function.
+ ir::BasicBlock pseudo_entry_block_;
+
+ // Next unused ID
+ uint32_t next_id_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // LIBSPIRV_OPT_LOCAL_SSA_ELIM_PASS_H_
+
diff --git a/source/opt/module.h b/source/opt/module.h
index 37d49025..91454a5e 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -112,6 +112,10 @@ class Module {
IteratorRange<inst_iterator> annotations();
IteratorRange<const_inst_iterator> annotations() const;
+ // Iterators for extension instructions contained in this module.
+ IteratorRange<inst_iterator> extensions();
+ IteratorRange<const_inst_iterator> extensions() const;
+
// Iterators for types, constants and global variables instructions.
inline inst_iterator types_values_begin();
inline inst_iterator types_values_end();
@@ -235,6 +239,14 @@ inline IteratorRange<Module::const_inst_iterator> Module::annotations() const {
return make_const_range(annotations_);
}
+inline IteratorRange<Module::inst_iterator> Module::extensions() {
+ return make_range(extensions_);
+}
+
+inline IteratorRange<Module::const_inst_iterator> Module::extensions() const {
+ return make_const_range(extensions_);
+}
+
inline Module::inst_iterator Module::types_values_begin() {
return inst_iterator(&types_values_, types_values_.begin());
}
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 80b26397..887dff34 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -166,6 +166,11 @@ Optimizer::PassToken CreateDeadBranchElimPass() {
MakeUnique<opt::DeadBranchElimPass>());
}
+Optimizer::PassToken CreateLocalMultiStoreElimPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::LocalMultiStoreElimPass>());
+}
+
Optimizer::PassToken CreateCompactIdsPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::CompactIdsPass>());
diff --git a/source/opt/passes.h b/source/opt/passes.h
index e2244a97..4d0ac793 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -27,6 +27,7 @@
#include "insert_extract_elim.h"
#include "local_single_block_elim_pass.h"
#include "local_single_store_elim_pass.h"
+#include "local_ssa_elim_pass.h"
#include "freeze_spec_constant_value_pass.h"
#include "local_access_chain_convert_pass.h"
#include "null_pass.h"