Opt: Have "size" passes process full entry point call tree.
authorGregF <greg@LunarG.com>
Thu, 10 Aug 2017 22:42:16 +0000 (16:42 -0600)
committerDavid Neto <dneto@google.com>
Fri, 18 Aug 2017 14:16:01 +0000 (10:16 -0400)
Includes code to deal correctly with OpFunctionParameter. This
is needed by opaque propagation which may not exhaustively inline
entry point functions.

Adds ProcessEntryPointCallTree: a method to do work on the
functions in the entry point call trees in a deterministic order.

26 files changed:
source/opt/CMakeLists.txt
source/opt/aggressive_dead_code_elim_pass.cpp
source/opt/aggressive_dead_code_elim_pass.h
source/opt/block_merge_pass.cpp
source/opt/block_merge_pass.h
source/opt/common_uniform_elim_pass.cpp
source/opt/common_uniform_elim_pass.h
source/opt/dead_branch_elim_pass.cpp
source/opt/dead_branch_elim_pass.h
source/opt/inline_exhaustive_pass.cpp
source/opt/insert_extract_elim.cpp
source/opt/insert_extract_elim.h
source/opt/local_access_chain_convert_pass.cpp
source/opt/local_access_chain_convert_pass.h
source/opt/local_single_block_elim_pass.cpp
source/opt/local_single_block_elim_pass.h
source/opt/local_single_store_elim_pass.cpp
source/opt/local_single_store_elim_pass.h
source/opt/local_ssa_elim_pass.cpp
source/opt/local_ssa_elim_pass.h
source/opt/mem_pass.cpp
source/opt/mem_pass.h
source/opt/pass.cpp [new file with mode: 0644]
source/opt/pass.h
test/opt/aggressive_dead_code_elim_test.cpp
test/opt/local_single_block_elim.cpp

index ad6076b..756fb17 100644 (file)
@@ -75,6 +75,7 @@ add_library(SPIRV-Tools-opt
   set_spec_constant_default_value_pass.cpp
   optimizer.cpp
   mem_pass.cpp
+  pass.cpp
   pass_manager.cpp
   strip_debug_info_pass.cpp
   types.cpp
index ae3eba5..dcb5a26 100644 (file)
@@ -24,7 +24,6 @@ namespace opt {
 
 namespace {
 
-const uint32_t kEntryPointFunctionIdInIdx = 1;
 const uint32_t kTypePointerStorageClassInIdx = 0;
 const uint32_t kExtInstSetIdInIndx = 0;
 const uint32_t kExtInstInstructionInIndx = 1;
@@ -33,9 +32,13 @@ const uint32_t kExtInstInstructionInIndx = 1;
 
 bool AggressiveDCEPass::IsLocalVar(uint32_t varId) {
   const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
-  assert(varInst->opcode() == SpvOpVariable);
+  const SpvOp op = varInst->opcode();
+  if (op != SpvOpVariable && op != SpvOpFunctionParameter) 
+    return false;
   const uint32_t varTypeId = varInst->type_id();
   const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
+  if (varTypeInst->opcode() != SpvOpTypePointer)
+    return false;
   return varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) ==
       SpvStorageClassFunction;
 }
@@ -54,7 +57,7 @@ void AggressiveDCEPass::AddStores(uint32_t ptrId) {
       } break;
       case SpvOpLoad:
         break;
-      // Assume it stores eg frexp, modf, function call
+      // If default, assume it stores eg frexp, modf, function call
       case SpvOpStore:
       default: {
         if (live_insts_.find(u.inst) == live_insts_.end())
@@ -136,7 +139,8 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
             worklist_.push(&inst);
         } break;
         default: {
-          // eg. control flow, function call, atomics
+          // eg. control flow, function call, atomics, function param,
+          // function return
           // TODO(greg-lunarg): function calls live only if write to non-local
           if (!IsCombinator(op))
             worklist_.push(&inst);
@@ -177,6 +181,10 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
         ProcessLoad(varId);
       });
     }
+    // If function parameter, treat as if it's result id is loaded from
+    else if (liveInst->opcode() == SpvOpFunctionParameter) {
+      ProcessLoad(liveInst->result_id());
+    }
     worklist_.pop();
   }
   // Mark all non-live instructions dead
@@ -217,11 +225,6 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
 void AggressiveDCEPass::Initialize(ir::Module* module) {
   module_ = module;
 
-  // Initialize id-to-function map
-  id2function_.clear();
-  for (auto& fn : *module_)
-    id2function_[fn.result_id()] = &fn;
-
   // Clear collections
   worklist_ = std::queue<ir::Instruction*>{};
   live_insts_.clear();
@@ -253,12 +256,10 @@ Pass::Status AggressiveDCEPass::ProcessImpl() {
   // Initialize combinator whitelists
   InitCombinatorSets();
   // Process all entry point functions
-  bool modified = false;
-  for (auto& e : module_->entry_points()) {
-    ir::Function* fn =
-        id2function_[e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)];
-    modified = AggressiveDCE(fn) || modified;
-  }
+  ProcessFunction pfn = [this](ir::Function* fp) {
+    return AggressiveDCE(fp);
+  };
+  bool modified = ProcessEntryPointCallTree(pfn, module_);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
index 28b7e9b..94eb049 100644 (file)
@@ -50,7 +50,8 @@ class AggressiveDCEPass : public MemPass {
   // to the live instruction worklist.
   void AddStores(uint32_t ptrId);
 
-  // Return true if variable with |varId| is function scope
+  // Return true if object with |varId| is function scope variable or
+  // function parameter with pointer type.
   bool IsLocalVar(uint32_t varId);
 
   // Initialize combinator data structures
@@ -93,9 +94,6 @@ class AggressiveDCEPass : public MemPass {
   void Initialize(ir::Module* module);
   Pass::Status ProcessImpl();
 
-  // Map from function's result id to function
-  std::unordered_map<uint32_t, ir::Function*> id2function_;
-
   // Live Instruction Worklist.  An instruction is added to this list
   // if it might have a side effect, either directly or indirectly.
   // If we don't know, then add it to this list.  Instructions are
index ad9de47..12c205f 100644 (file)
 namespace spvtools {
 namespace opt {
 
-namespace {
-
-const int kEntryPointFunctionIdInIdx = 1;
-
-} // anonymous namespace
-
 bool BlockMergePass::IsLoopHeader(ir::BasicBlock* block_ptr) {
   auto iItr = block_ptr->tail();
   if (iItr == block_ptr->begin())
@@ -112,11 +106,6 @@ void BlockMergePass::Initialize(ir::Module* module) {
 
   module_ = module;
 
-  // Initialize function and block maps
-  id2function_.clear();
-  for (auto& fn : *module_) 
-    id2function_[fn.result_id()] = &fn;
-
   // TODO(greg-lunarg): Reuse def/use from previous passes
   def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
 
@@ -139,12 +128,11 @@ Pass::Status BlockMergePass::ProcessImpl() {
   // Do not process if any disallowed extensions are enabled
   if (!AllExtensionsSupported())
     return Status::SuccessWithoutChange;
-  bool modified = false;
-  for (auto& e : module_->entry_points()) {
-    ir::Function* fn =
-        id2function_[e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)];
-    modified = MergeBlocks(fn) || modified;
-  }
+  // Process all entry point functions.
+  ProcessFunction pfn = [this](ir::Function* fp) {
+    return MergeBlocks(fp);
+  };
+  bool modified = ProcessEntryPointCallTree(pfn, module_);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
index 73adf0e..ee68617 100644 (file)
@@ -68,9 +68,6 @@ class BlockMergePass : public Pass {
   // 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_;
-
   // Extensions supported by this pass.
   std::unordered_set<std::string> extensions_whitelist_;
 };
index 6872fff..d8d6519 100644 (file)
@@ -24,7 +24,6 @@ namespace opt {
 
 namespace {
 
-const uint32_t kEntryPointFunctionIdInIdx = 1;
 const uint32_t kAccessChainPtrIdInIdx = 0;
 const uint32_t kTypePointerStorageClassInIdx = 0;
 const uint32_t kTypePointerTypeIdInIdx = 1;
@@ -106,26 +105,27 @@ uint32_t CommonUniformElimPass::MergeBlockIdIfAny(const ir::BasicBlock& blk,
 }
 
 ir::Instruction* CommonUniformElimPass::GetPtr(
-      ir::Instruction* ip, uint32_t* varId) {
+      ir::Instruction* ip, uint32_t* objId) {
   const SpvOp op = ip->opcode();
   assert(op == SpvOpStore || op == SpvOpLoad);
-  *varId = ip->GetSingleWordInOperand(
+  *objId = ip->GetSingleWordInOperand(
       op == SpvOpStore ? kStorePtrIdInIdx : kLoadPtrIdInIdx);
-  ir::Instruction* ptrInst = def_use_mgr_->GetDef(*varId);
+  ir::Instruction* ptrInst = def_use_mgr_->GetDef(*objId);
   while (ptrInst->opcode() == SpvOpCopyObject) {
-    *varId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
-    ptrInst = def_use_mgr_->GetDef(*varId);
+    *objId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
+    ptrInst = def_use_mgr_->GetDef(*objId);
   }
-  ir::Instruction* varInst = ptrInst;
-  while (varInst->opcode() != SpvOpVariable) {
-    if (IsNonPtrAccessChain(varInst->opcode())) {
-      *varId = varInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
+  ir::Instruction* objInst = ptrInst;
+  while (objInst->opcode() != SpvOpVariable &&
+      objInst->opcode() != SpvOpFunctionParameter) {
+    if (IsNonPtrAccessChain(objInst->opcode())) {
+      *objId = objInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
     }
     else {
-      assert(varInst->opcode() == SpvOpCopyObject);
-      *varId = varInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
+      assert(objInst->opcode() == SpvOpCopyObject);
+      *objId = objInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
     }
-    varInst = def_use_mgr_->GetDef(*varId);
+    objInst = def_use_mgr_->GetDef(*objId);
   }
   return ptrInst;
 }
@@ -133,7 +133,8 @@ ir::Instruction* CommonUniformElimPass::GetPtr(
 bool CommonUniformElimPass::IsUniformVar(uint32_t varId) {
   const ir::Instruction* varInst =
     def_use_mgr_->id_to_defs().find(varId)->second;
-  assert(varInst->opcode() == SpvOpVariable);
+  if (varInst->opcode() != SpvOpVariable)
+    return false;
   const uint32_t varTypeId = varInst->type_id();
   const ir::Instruction* varTypeInst =
     def_use_mgr_->id_to_defs().find(varTypeId)->second;
@@ -514,13 +515,10 @@ void CommonUniformElimPass::Initialize(ir::Module* module) {
   module_ = module;
 
   // Initialize function and block maps
-  id2function_.clear();
   id2block_.clear();
-  for (auto& fn : *module_) {
-    id2function_[fn.result_id()] = &fn;
+  for (auto& fn : *module_)
     for (auto& blk : fn)
       id2block_[blk.id()] = &blk;
-  }
 
   // Clear collections
   block2structured_succs_.clear();
@@ -574,12 +572,10 @@ Pass::Status CommonUniformElimPass::ProcessImpl() {
         inst.GetSingleWordInOperand(kTypeIntWidthInIdx) != 32)
       return Status::SuccessWithoutChange;
   // Process entry point functions
-  bool modified = false;
-  for (auto& e : module_->entry_points()) {
-    ir::Function* fn =
-        id2function_[e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)];
-    modified = EliminateCommonUniform(fn) || modified;
-  }
+  ProcessFunction pfn = [this](ir::Function* fp) {
+    return EliminateCommonUniform(fp);
+  };
+  bool modified = ProcessEntryPointCallTree(pfn, module_);
   FinalizeNextId(module_);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
index a02ed28..1e332eb 100644 (file)
@@ -58,9 +58,10 @@ class CommonUniformElimPass : public Pass {
   // Return true if |block_ptr| is loop header block
   bool IsLoopHeader(ir::BasicBlock* block_ptr);
 
-  // Given a load or store pointed at by |ip|, return the pointer
-  // instruction. Also return the variable's id in |varId|.
-  ir::Instruction* GetPtr(ir::Instruction* ip, uint32_t* varId);
+  // Given a load or store pointed at by |ip|, return the top-most
+  // non-CopyObj in its pointer operand. Also return the base pointer
+  // in |objId|.
+  ir::Instruction* GetPtr(ir::Instruction* ip, uint32_t* objId);
 
   // Return true if variable is uniform
   bool IsUniformVar(uint32_t varId);
@@ -178,9 +179,6 @@ class CommonUniformElimPass : public Pass {
   // 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_;
 
index 3be40f9..8e225fe 100644 (file)
@@ -24,7 +24,6 @@ namespace opt {
 
 namespace {
 
-const uint32_t kEntryPointFunctionIdInIdx = 1;
 const uint32_t kBranchCondConditionalIdInIdx = 0;
 const uint32_t kBranchCondTrueLabIdInIdx = 1;
 const uint32_t kBranchCondFalseLabIdInIdx = 2;
@@ -288,16 +287,13 @@ void DeadBranchElimPass::Initialize(ir::Module* module) {
   module_ = module;
 
   // Initialize function and block maps
-  id2function_.clear();
   id2block_.clear();
   block2structured_succs_.clear();
-  for (auto& fn : *module_) {
-    // Initialize function and block maps.
-    id2function_[fn.result_id()] = &fn;
-    for (auto& blk : fn) {
+
+  // Initialize block map
+  for (auto& fn : *module_)
+    for (auto& blk : fn)
       id2block_[blk.id()] = &blk;
-    }
-  }
 
   // TODO(greg-lunarg): Reuse def/use from previous passes
   def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
@@ -334,12 +330,10 @@ Pass::Status DeadBranchElimPass::ProcessImpl() {
   // Collect all named and decorated ids
   FindNamedOrDecoratedIds();
   // Process all entry point functions
-  bool modified = false;
-  for (const auto& e : module_->entry_points()) {
-    ir::Function* fn =
-        id2function_[e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)];
-    modified = EliminateDeadBranches(fn) || modified;
-  }
+  ProcessFunction pfn = [this](ir::Function* fp) {
+    return EliminateDeadBranches(fp);
+  };
+  bool modified = ProcessEntryPointCallTree(pfn, module_);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
index 07709dd..01e84b2 100644 (file)
@@ -114,9 +114,6 @@ class DeadBranchElimPass : public MemPass {
   void Initialize(ir::Module* module);
   Pass::Status ProcessImpl();
 
-  // 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_;
 
index fe65798..3c894bc 100644 (file)
 namespace spvtools {
 namespace opt {
 
-namespace {
-
-const int kEntryPointFunctionIdInIdx = 1;
-
-} // anonymous namespace
-
 bool InlineExhaustivePass::InlineExhaustive(ir::Function* func) {
   bool modified = false;
   // Using block iterators here because of block erasures and insertions.
@@ -60,16 +54,12 @@ void InlineExhaustivePass::Initialize(ir::Module* module) {
 };
 
 Pass::Status InlineExhaustivePass::ProcessImpl() {
-  // Do exhaustive inlining on each entry point function in module
-  bool modified = false;
-  for (auto& e : module_->entry_points()) {
-    ir::Function* fn =
-        id2function_[e.GetSingleWordOperand(kEntryPointFunctionIdInIdx)];
-    modified = InlineExhaustive(fn) || modified;
-  }
-
+  // Attempt exhaustive inlining on each entry point function in module
+  ProcessFunction pfn = [this](ir::Function* fp) {
+    return InlineExhaustive(fp);
+  };
+  bool modified = ProcessEntryPointCallTree(pfn, module_);
   FinalizeNextId(module_);
-
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
index cae3bab..c543644 100644 (file)
 
 #include "iterator.h"
 
-static const int kSpvEntryPointFunctionId = 1;
-static const int kSpvExtractCompositeId = 0;
-static const int kSpvInsertObjectId = 0;
-static const int kSpvInsertCompositeId = 1;
-
 namespace spvtools {
 namespace opt {
 
+namespace {
+
+const uint32_t kExtractCompositeIdInIdx = 0;
+const uint32_t kInsertObjectIdInIdx = 0;
+const uint32_t kInsertCompositeIdInIdx = 1;
+
+} // anonymous namespace
+
 bool InsertExtractElimPass::ExtInsMatch(const ir::Instruction* extInst,
     const ir::Instruction* insInst) const {
   if (extInst->NumInOperands() != insInst->NumInOperands() - 1)
@@ -58,17 +61,17 @@ bool InsertExtractElimPass::EliminateInsertExtract(ir::Function* func) {
     for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
       switch (ii->opcode()) {
         case SpvOpCompositeExtract: {
-          uint32_t cid = ii->GetSingleWordInOperand(kSpvExtractCompositeId);
+          uint32_t cid = ii->GetSingleWordInOperand(kExtractCompositeIdInIdx);
           ir::Instruction* cinst = def_use_mgr_->GetDef(cid);
           uint32_t replId = 0;
           while (cinst->opcode() == SpvOpCompositeInsert) {
             if (ExtInsConflict(&*ii, cinst))
               break;
             if (ExtInsMatch(&*ii, cinst)) {
-              replId = cinst->GetSingleWordInOperand(kSpvInsertObjectId);
+              replId = cinst->GetSingleWordInOperand(kInsertObjectIdInIdx);
               break;
             }
-            cid = cinst->GetSingleWordInOperand(kSpvInsertCompositeId);
+            cid = cinst->GetSingleWordInOperand(kInsertCompositeIdInIdx);
             cinst = def_use_mgr_->GetDef(cid);
           }
           if (replId != 0) {
@@ -90,11 +93,6 @@ void InsertExtractElimPass::Initialize(ir::Module* module) {
 
   module_ = module;
 
-  // Initialize function and block maps
-  id2function_.clear();
-  for (auto& fn : *module_)
-    id2function_[fn.result_id()] = &fn;
-
   // Do def/use on whole module
   def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module_));
 
@@ -118,13 +116,10 @@ Pass::Status InsertExtractElimPass::ProcessImpl() {
   if (!AllExtensionsSupported())
     return Status::SuccessWithoutChange;
   // Process all entry point functions.
-  bool modified = false;
-  for (auto& e : module_->entry_points()) {
-    ir::Function* fn =
-        id2function_[e.GetSingleWordOperand(kSpvEntryPointFunctionId)];
-    modified = EliminateInsertExtract(fn) || modified;
-  }
-
+  ProcessFunction pfn = [this](ir::Function* fp) {
+    return EliminateInsertExtract(fp);
+  };
+  bool modified = ProcessEntryPointCallTree(pfn, module_);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
index 440dcd6..52213db 100644 (file)
@@ -73,9 +73,6 @@ class InsertExtractElimPass : public Pass {
   // 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_;
-
   // Extensions supported by this pass.
   std::unordered_set<std::string> extensions_whitelist_;
 };
index 76ac956..1e5863d 100644 (file)
@@ -23,7 +23,6 @@ namespace opt {
 
 namespace {
 
-const uint32_t kEntryPointFunctionIdInIdx = 1;
 const uint32_t kStoreValIdInIdx = 1;
 const uint32_t kAccessChainPtrIdInIdx = 0;
 const uint32_t kTypePointerTypeIdInIdx = 1;
@@ -262,11 +261,6 @@ void LocalAccessChainConvertPass::Initialize(ir::Module* module) {
 
   module_ = module;
 
-  // Initialize function and block maps
-  id2function_.clear();
-  for (auto& fn : *module_) 
-    id2function_[fn.result_id()] = &fn;
-
   // Initialize Target Variable Caches
   seen_target_vars_.clear();
   seen_non_target_vars_.clear();
@@ -301,7 +295,6 @@ Pass::Status LocalAccessChainConvertPass::ProcessImpl() {
     if (inst.opcode() == SpvOpTypeInt &&
         inst.GetSingleWordInOperand(kTypeIntWidthInIdx) != 32)
       return Status::SuccessWithoutChange;
-
   // Do not process if module contains OpGroupDecorate. Additional
   // support required in KillNamesAndDecorates().
   // TODO(greg-lunarg): Add support for OpGroupDecorate
@@ -314,15 +307,11 @@ Pass::Status LocalAccessChainConvertPass::ProcessImpl() {
   // Collect all named and decorated ids
   FindNamedOrDecoratedIds();
   // Process all entry point functions.
-  bool modified = false;
-  for (auto& e : module_->entry_points()) {
-    ir::Function* fn =
-        id2function_[e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)];
-    modified = ConvertLocalAccessChains(fn) || modified;
-  }
-
+  ProcessFunction pfn = [this](ir::Function* fp) {
+    return ConvertLocalAccessChains(fp);
+  };
+  bool modified = ProcessEntryPointCallTree(pfn, module_);
   FinalizeNextId(module_);
-
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
index 5f2dd07..d7f51ba 100644 (file)
@@ -40,6 +40,8 @@ class LocalAccessChainConvertPass : public MemPass {
   const char* name() const override { return "convert-local-access-chains"; }
   Status Process(ir::Module*) override;
 
+  using ProcessFunction = std::function<bool(ir::Function*)>;
+
  private:
   // Return true if all refs through |ptrId| are only loads or stores and
   // cache ptrId in supported_ref_ptrs_.
@@ -119,9 +121,6 @@ class LocalAccessChainConvertPass : public MemPass {
   void Initialize(ir::Module* module);
   Pass::Status ProcessImpl();
 
-  // Map from function's result id to function
-  std::unordered_map<uint32_t, ir::Function*> id2function_;
-
   // Variables with only supported references, ie. loads and stores using
   // variable directly or through non-ptr access chains.
   std::unordered_set<uint32_t> supported_ref_ptrs_;
index 69a67b6..deeead6 100644 (file)
@@ -23,7 +23,6 @@ namespace opt {
 
 namespace {
 
-const uint32_t kEntryPointFunctionIdInIdx = 1;
 const uint32_t kStoreValIdInIdx = 1;
 
 } // anonymous namespace
@@ -142,11 +141,6 @@ void LocalSingleBlockLoadStoreElimPass::Initialize(ir::Module* module) {
 
   module_ = module;
 
-  // Initialize function and block maps
-  id2function_.clear();
-  for (auto& fn : *module_) 
-    id2function_[fn.result_id()] = &fn;
-
   // Initialize Target Type Caches
   seen_target_vars_.clear();
   seen_non_target_vars_.clear();
@@ -192,12 +186,10 @@ Pass::Status LocalSingleBlockLoadStoreElimPass::ProcessImpl() {
   // Collect all named and decorated ids
   FindNamedOrDecoratedIds();
   // Process all entry point functions
-  bool modified = false;
-  for (auto& e : module_->entry_points()) {
-    ir::Function* fn =
-        id2function_[e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)];
-    modified = LocalSingleBlockLoadStoreElim(fn) || modified;
-  }
+  ProcessFunction pfn = [this](ir::Function* fp) {
+    return LocalSingleBlockLoadStoreElim(fp);
+  };
+  bool modified = ProcessEntryPointCallTree(pfn, module_);
   FinalizeNextId(module_);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
index f3a92d7..ef744b1 100644 (file)
@@ -72,9 +72,6 @@ class LocalSingleBlockLoadStoreElimPass : public MemPass {
   void Initialize(ir::Module* module);
   Pass::Status ProcessImpl();
 
-  // Map from function's result id to function
-  std::unordered_map<uint32_t, ir::Function*> id2function_;
-
   // Map from function scope variable to a store of that variable in the
   // current block whose value is currently valid. This map is cleared
   // at the start of each block and incrementally updated as the block
index c1e2ef1..c32b027 100644 (file)
@@ -28,7 +28,6 @@ namespace opt {
 
 namespace {
 
-const uint32_t kEntryPointFunctionIdInIdx = 1;
 const uint32_t kStoreValIdInIdx = 1;
 
 } // anonymous namespace
@@ -65,10 +64,6 @@ void LocalSingleStoreElimPass::SingleStoreAnalyze(ir::Function* func) {
         ir::Instruction* ptrInst = GetPtr(&*ii, &varId);
         if (non_ssa_vars_.find(varId) != non_ssa_vars_.end())
           continue;
-        if (!HasOnlySupportedRefs(varId)) {
-          non_ssa_vars_.insert(varId);
-          continue;
-        }
         if (ptrInst->opcode() != SpvOpVariable) {
           non_ssa_vars_.insert(varId);
           ssa_var2store_.erase(varId);
@@ -79,6 +74,10 @@ void LocalSingleStoreElimPass::SingleStoreAnalyze(ir::Function* func) {
           non_ssa_vars_.insert(varId);
           continue;
         }
+        if (!HasOnlySupportedRefs(varId)) {
+          non_ssa_vars_.insert(varId);
+          continue;
+        }
         // Ignore variables with multiple stores
         if (ssa_var2store_.find(varId) != ssa_var2store_.end()) {
           non_ssa_vars_.insert(varId);
@@ -240,10 +239,8 @@ void LocalSingleStoreElimPass::Initialize(ir::Module* module) {
   module_ = module;
 
   // Initialize function and block maps
-  id2function_.clear();
   label2block_.clear();
   for (auto& fn : *module_) {
-    id2function_[fn.result_id()] = &fn;
     for (auto& blk : fn) {
       uint32_t bid = blk.id();
       label2block_[bid] = &blk;
@@ -294,12 +291,10 @@ Pass::Status LocalSingleStoreElimPass::ProcessImpl() {
   // Collect all named and decorated ids
   FindNamedOrDecoratedIds();
   // Process all entry point functions
-  bool modified = false;
-  for (auto& e : module_->entry_points()) {
-    ir::Function* fn =
-        id2function_[e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)];
-    modified = LocalSingleStoreElim(fn) || modified;
-  }
+  ProcessFunction pfn = [this](ir::Function* fp) {
+    return LocalSingleStoreElim(fp);
+  };
+  bool modified = ProcessEntryPointCallTree(pfn, module_);
   FinalizeNextId(module_);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
index 10234a3..4352378 100644 (file)
@@ -109,9 +109,6 @@ class LocalSingleStoreElimPass : public MemPass {
   void Initialize(ir::Module* module);
   Pass::Status ProcessImpl();
 
-  // 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*> label2block_;
 
index 61a6721..5dd4233 100644 (file)
@@ -24,7 +24,6 @@ namespace opt {
 
 namespace {
 
-const uint32_t kEntryPointFunctionIdInIdx = 1;
 const uint32_t kStoreValIdInIdx = 1;
 const uint32_t kTypePointerTypeIdInIdx = 1;
 const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
@@ -491,14 +490,11 @@ void LocalMultiStoreElimPass::Initialize(ir::Module* module) {
   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& fn : *module_)
     for (auto& blk : fn)
       id2block_[blk.id()] = &blk;
-  }
 
   // Clear collections
   seen_target_vars_.clear();
@@ -549,12 +545,10 @@ Pass::Status LocalMultiStoreElimPass::ProcessImpl() {
   // Collect all named and decorated ids
   FindNamedOrDecoratedIds();
   // Process functions
-  bool modified = false;
-  for (auto& e : module_->entry_points()) {
-    ir::Function* fn =
-        id2function_[e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)];
-    modified = EliminateMultiStoreLocal(fn) || modified;
-  }
+  ProcessFunction pfn = [this](ir::Function* fp) {
+    return EliminateMultiStoreLocal(fp);
+  };
+  bool modified = ProcessEntryPointCallTree(pfn, module_);
   FinalizeNextId(module_);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
index 2854155..8b6898c 100644 (file)
@@ -149,9 +149,6 @@ class LocalMultiStoreElimPass : public MemPass {
   void Initialize(ir::Module* module);
   Pass::Status ProcessImpl();
 
-  // 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_;
 
index 34934a4..6768606 100644 (file)
@@ -81,7 +81,13 @@ bool MemPass::IsPtr(uint32_t ptrId) {
     ptrInst = def_use_mgr_->GetDef(varId);
   }
   const SpvOp op = ptrInst->opcode();
-  return op == SpvOpVariable || IsNonPtrAccessChain(op);
+  if (op == SpvOpVariable || IsNonPtrAccessChain(op))
+    return true;
+  if (op != SpvOpFunctionParameter)
+    return false;
+  const uint32_t varTypeId = ptrInst->type_id();
+  const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
+  return varTypeInst->opcode() == SpvOpTypePointer;
 }
 
 ir::Instruction* MemPass::GetPtr(
@@ -93,7 +99,8 @@ ir::Instruction* MemPass::GetPtr(
     ptrInst = def_use_mgr_->GetDef(*varId);
   }
   ir::Instruction* varInst = ptrInst;
-  while (varInst->opcode() != SpvOpVariable) {
+  while (varInst->opcode() != SpvOpVariable && 
+      varInst->opcode() != SpvOpFunctionParameter) {
     if (IsNonPtrAccessChain(varInst->opcode())) {
       *varId = varInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
     }
@@ -209,9 +216,11 @@ bool MemPass::HasLoads(uint32_t varId) const {
 }
 
 bool MemPass::IsLiveVar(uint32_t varId) const {
-  // non-function scope vars are live
   const ir::Instruction* varInst = def_use_mgr_->GetDef(varId);
-  assert(varInst->opcode() == SpvOpVariable);
+  // assume live if not a variable eg. function parameter
+  if (varInst->opcode() != SpvOpVariable)
+    return true;
+  // non-function scope vars are live
   const uint32_t varTypeId = varInst->type_id();
   const ir::Instruction* varTypeInst = def_use_mgr_->GetDef(varTypeId);
   if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) !=
index 9b5bda0..eff6900 100644 (file)
@@ -54,7 +54,7 @@ class MemPass : public Pass {
   bool IsNonPtrAccessChain(const SpvOp opcode) const;
 
   // Given the id |ptrId|, return true if the top-most non-CopyObj is
-  // a pointer. 
+  // a variable, a non-ptr access chain or a parameter of pointer type.
   bool IsPtr(uint32_t ptrId);
 
   // Given the id of a pointer |ptrId|, return the top-most non-CopyObj.
diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp
new file mode 100644 (file)
index 0000000..626b323
--- /dev/null
@@ -0,0 +1,65 @@
+// 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 "pass.h"
+
+#include "iterator.h"
+
+namespace spvtools {
+namespace opt {
+
+namespace {
+
+const uint32_t kEntryPointFunctionIdInIdx = 1;
+
+}  // namespace anonymous
+
+void Pass::AddCalls(ir::Function* func,
+    std::queue<uint32_t>* todo) {
+  for (auto bi = func->begin(); bi != func->end(); ++bi)
+    for (auto ii = bi->begin(); ii != bi->end(); ++ii)
+      if (ii->opcode() == SpvOpFunctionCall)
+        todo->push(ii->GetSingleWordInOperand(0));
+}
+
+bool Pass::ProcessEntryPointCallTree(
+    ProcessFunction& pfn, ir::Module* module) {
+  // Map from function's result id to function
+  std::unordered_map<uint32_t, ir::Function*> id2function;
+  for (auto& fn : *module)
+    id2function[fn.result_id()] = &fn;
+  // Process call tree
+  bool modified = false;
+  std::queue<uint32_t> todo;
+  std::unordered_set<uint32_t> done;
+  for (auto& e : module->entry_points())
+    todo.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
+  while (!todo.empty()) {
+    const uint32_t fi = todo.front();
+    if (done.find(fi) == done.end()) {
+      ir::Function* fn = id2function[fi];
+      modified = pfn(fn) || modified;
+      done.insert(fi);
+      AddCalls(fn, &todo);
+    }
+    todo.pop();
+  }
+  return modified;
+}
+
+}  // namespace opt
+}  // namespace spvtools
+
index 74e3bb8..897203f 100644 (file)
 #ifndef LIBSPIRV_OPT_PASS_H_
 #define LIBSPIRV_OPT_PASS_H_
 
+#include <algorithm>
+#include <map>
+#include <queue>
+#include <unordered_map>
+#include <unordered_set>
+
 #include <utility>
 
 #include "module.h"
@@ -37,6 +43,8 @@ class Pass {
     SuccessWithoutChange = 0x11,
   };
 
+  using ProcessFunction = std::function<bool(ir::Function*)>;
+
   // Constructs a new pass.
   //
   // The constructed instance will have an empty message consumer, which just
@@ -56,6 +64,12 @@ class Pass {
   // Returns the reference to the message consumer for this pass.
   const MessageConsumer& consumer() const { return consumer_; }
 
+  // Add to |todo| all ids of functions called in |func|.
+  void AddCalls(ir::Function* func, std::queue<uint32_t>* todo);
+
+  // 
+  bool ProcessEntryPointCallTree(ProcessFunction& pfn, ir::Module* module);
+
   // Processes the given |module|. Returns Status::Failure if errors occur when
   // processing. Returns the corresponding Status::Success if processing is
   // succesful to indicate whether changes are made to the module.
index cfce582..ac8cf74 100644 (file)
@@ -807,6 +807,136 @@ OpFunctionEnd
       defs_before + func_before, defs_after + func_after, true, true);
 }
 
+TEST_F(AggressiveDCETest, NoParamElim) {
+  // This demonstrates that unused parameters are not eliminated, but
+  // dead uses of them are.
+  // #version 140
+  // 
+  // in vec4 BaseColor;
+  // 
+  // vec4 foo(vec4 v1, vec4 v2)
+  // {
+  //     vec4 t = -v1;
+  //     return v2;
+  // }
+  // 
+  // void main()
+  // {
+  //     vec4 dead;
+  //     gl_FragColor = foo(dead, BaseColor);
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %foo_vf4_vf4_ "foo(vf4;vf4;" 
+OpName %v1 "v1"
+OpName %v2 "v2"
+OpName %t "t"
+OpName %gl_FragColor "gl_FragColor" 
+OpName %dead "dead"
+OpName %BaseColor "BaseColor"
+OpName %param "param" 
+OpName %param_0 "param"
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%17 = OpTypeFunction %v4float %_ptr_Function_v4float %_ptr_Function_v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%main = OpFunction %void None %13
+%20 = OpLabel
+%dead = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%param_0 = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %v4float %dead
+OpStore %param %21
+%22 = OpLoad %v4float %BaseColor
+OpStore %param_0 %22
+%23 = OpFunctionCall %v4float %foo_vf4_vf4_ %param %param_0
+OpStore %gl_FragColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %foo_vf4_vf4_ "foo(vf4;vf4;"
+OpName %v1 "v1"
+OpName %v2 "v2"
+OpName %gl_FragColor "gl_FragColor"
+OpName %dead "dead"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %param_0 "param"
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%17 = OpTypeFunction %v4float %_ptr_Function_v4float %_ptr_Function_v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%main = OpFunction %void None %13
+%20 = OpLabel
+%dead = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%param_0 = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %v4float %dead
+OpStore %param %21
+%22 = OpLoad %v4float %BaseColor
+OpStore %param_0 %22
+%23 = OpFunctionCall %v4float %foo_vf4_vf4_ %param %param_0
+OpStore %gl_FragColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_before =
+      R"(%foo_vf4_vf4_ = OpFunction %v4float None %17
+%v1 = OpFunctionParameter %_ptr_Function_v4float
+%v2 = OpFunctionParameter %_ptr_Function_v4float
+%24 = OpLabel
+%t = OpVariable %_ptr_Function_v4float Function
+%25 = OpLoad %v4float %v1 
+%26 = OpFNegate %v4float %25
+OpStore %t %26 
+%27 = OpLoad %v4float %v2 
+OpReturnValue %27
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%foo_vf4_vf4_ = OpFunction %v4float None %17
+%v1 = OpFunctionParameter %_ptr_Function_v4float
+%v2 = OpFunctionParameter %_ptr_Function_v4float
+%24 = OpLabel
+%27 = OpLoad %v4float %v2
+OpReturnValue %27
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(
+      defs_before + func_before, defs_after + func_after, true, true);
+}
+
 TEST_F(AggressiveDCETest, ElimOpaque) {
   // SPIR-V not representable from GLSL; not generatable from HLSL
   // for the moment.
@@ -907,7 +1037,7 @@ OpFunctionEnd
 )";
 
   const std::string func_after =
-      R"(%main = OpFunction %void None %9
+        R"(%main = OpFunction %void None %9
 %25 = OpLabel
 %26 = OpLoad %v2float %texCoords
 %29 = OpLoad %15 %sampler15
@@ -921,6 +1051,7 @@ OpFunctionEnd
       defs_before + func_before, defs_after + func_after, true, true);
 }
 
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Check that logical addressing required
index 9352a38..cc55d5d 100644 (file)
@@ -685,6 +685,148 @@ OpFunctionEnd
       predefs_before + before, predefs_after + after, true, true);
 }
 
+TEST_F(LocalSingleBlockLoadStoreElimTest, PositiveAndNegativeCallTree) {
+  // Note that the call tree function bar is optimized, but foo is not
+  //
+  // #version 140
+  // 
+  // in vec4 BaseColor;
+  // 
+  // vec4 foo(vec4 v1)
+  // {
+  //     vec4 t = v1;
+  //     return t;
+  // }
+  // 
+  // vec4 bar(vec4 v1)
+  // {
+  //     vec4 t = v1;
+  //     return t;
+  // }
+  // 
+  // void main()
+  // {
+  //     gl_FragColor = bar(BaseColor);
+  // }
+
+  const std::string predefs_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %foo_vf4_ "foo(vf4;"
+OpName %v1 "v1"
+OpName %bar_vf4_ "bar(vf4;"
+OpName %v1_0 "v1"
+OpName %t "t"
+OpName %t_0 "t"
+OpName %gl_FragColor "gl_FragColor" 
+OpName %BaseColor "BaseColor"
+OpName %param "param" 
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%17 = OpTypeFunction %v4float %_ptr_Function_v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%main = OpFunction %void None %13
+%20 = OpLabel
+%param = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %v4float %BaseColor
+OpStore %param %21
+%22 = OpFunctionCall %v4float %bar_vf4_ %param
+OpStore %gl_FragColor %22
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string predefs_after =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %foo_vf4_ "foo(vf4;"
+OpName %v1 "v1"
+OpName %bar_vf4_ "bar(vf4;"
+OpName %v1_0 "v1"
+OpName %t "t"
+OpName %gl_FragColor "gl_FragColor"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%17 = OpTypeFunction %v4float %_ptr_Function_v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%main = OpFunction %void None %13
+%20 = OpLabel
+%param = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %v4float %BaseColor
+OpStore %param %21
+%22 = OpFunctionCall %v4float %bar_vf4_ %param
+OpStore %gl_FragColor %22
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string before =
+      R"(%foo_vf4_ = OpFunction %v4float None %17
+%v1 = OpFunctionParameter %_ptr_Function_v4float
+%23 = OpLabel
+%t = OpVariable %_ptr_Function_v4float Function
+%24 = OpLoad %v4float %v1
+OpStore %t %24
+%25 = OpLoad %v4float %t
+OpReturnValue %25
+OpFunctionEnd
+%bar_vf4_ = OpFunction %v4float None %17
+%v1_0 = OpFunctionParameter %_ptr_Function_v4float
+%26 = OpLabel
+%t_0 = OpVariable %_ptr_Function_v4float Function
+%27 = OpLoad %v4float %v1_0
+OpStore %t_0 %27
+%28 = OpLoad %v4float %t_0
+OpReturnValue %28
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%foo_vf4_ = OpFunction %v4float None %17
+%v1 = OpFunctionParameter %_ptr_Function_v4float
+%23 = OpLabel
+%t = OpVariable %_ptr_Function_v4float Function
+%24 = OpLoad %v4float %v1
+OpStore %t %24
+%25 = OpLoad %v4float %t
+OpReturnValue %25
+OpFunctionEnd
+%bar_vf4_ = OpFunction %v4float None %17
+%v1_0 = OpFunctionParameter %_ptr_Function_v4float
+%26 = OpLabel
+%27 = OpLoad %v4float %v1_0
+OpReturnValue %27
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::LocalSingleBlockLoadStoreElimPass>(
+      predefs_before + before, predefs_after + after, true, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Other target variable types