Sink (#2284)

Add code sinking pass. It will move OpLoad and OpAccessChain instructions as close as possible to their uses.

Part of #1611.
diff --git a/Android.mk b/Android.mk
index 8597c50..179cf26 100644
--- a/Android.mk
+++ b/Android.mk
@@ -80,6 +80,7 @@
 		source/opt/cfg.cpp \
 		source/opt/cfg_cleanup_pass.cpp \
 		source/opt/ccp_pass.cpp \
+		source/opt/code_sink.cpp \
 		source/opt/combine_access_chains.cpp \
 		source/opt/common_uniform_elim_pass.cpp \
 		source/opt/compact_ids_pass.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 5f0eba9..69e3dc8 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -460,6 +460,8 @@
     "source/opt/cfg.h",
     "source/opt/cfg_cleanup_pass.cpp",
     "source/opt/cfg_cleanup_pass.h",
+    "source/opt/code_sink.cpp",
+    "source/opt/code_sink.h",
     "source/opt/combine_access_chains.cpp",
     "source/opt/combine_access_chains.h",
     "source/opt/common_uniform_elim_pass.cpp",
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index b2865a8..b59329d 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -722,6 +722,10 @@
 // conform to that model's requirements.
 Optimizer::PassToken CreateUpgradeMemoryModelPass();
 
+// Create a pass to do code sinking.  Code sinking is a transformation
+// where an instruction is moved into a more deeply nested construct.
+Optimizer::PassToken CreateCodeSinkingPass();
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 96ee8b3..0f3835c 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -19,6 +19,7 @@
   ccp_pass.h
   cfg_cleanup_pass.h
   cfg.h
+  code_sink.h
   combine_access_chains.h
   common_uniform_elim_pass.h
   compact_ids_pass.h
@@ -111,6 +112,7 @@
   ccp_pass.cpp
   cfg_cleanup_pass.cpp
   cfg.cpp
+  code_sink.cpp
   combine_access_chains.cpp
   common_uniform_elim_pass.cpp
   compact_ids_pass.cpp
diff --git a/source/opt/code_sink.cpp b/source/opt/code_sink.cpp
new file mode 100644
index 0000000..e1e8190
--- /dev/null
+++ b/source/opt/code_sink.cpp
@@ -0,0 +1,316 @@
+// Copyright (c) 2019 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.
+
+#include "code_sink.h"
+
+#include <set>
+#include <vector>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_builder.h"
+#include "source/opt/ir_context.h"
+#include "source/util/bit_vector.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status CodeSinkingPass::Process() {
+  bool modified = false;
+  for (Function& function : *get_module()) {
+    cfg()->ForEachBlockInPostOrder(function.entry().get(),
+                                   [&modified, this](BasicBlock* bb) {
+                                     if (SinkInstructionsInBB(bb)) {
+                                       modified = true;
+                                     }
+                                   });
+  }
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool CodeSinkingPass::SinkInstructionsInBB(BasicBlock* bb) {
+  bool modified = false;
+  for (auto inst = bb->rbegin(); inst != bb->rend(); ++inst) {
+    if (SinkInstruction(&*inst)) {
+      inst = bb->rbegin();
+      modified = true;
+    }
+  }
+  return modified;
+}
+
+bool CodeSinkingPass::SinkInstruction(Instruction* inst) {
+  if (inst->opcode() != SpvOpLoad && inst->opcode() != SpvOpAccessChain) {
+    return false;
+  }
+
+  if (ReferencesMutableMemory(inst)) {
+    return false;
+  }
+
+  if (BasicBlock* target_bb = FindNewBasicBlockFor(inst)) {
+    Instruction* pos = &*target_bb->begin();
+    while (pos->opcode() == SpvOpPhi) {
+      pos = pos->NextNode();
+    }
+
+    inst->InsertBefore(pos);
+    context()->set_instr_block(inst, target_bb);
+    return true;
+  }
+  return false;
+}
+
+BasicBlock* CodeSinkingPass::FindNewBasicBlockFor(Instruction* inst) {
+  assert(inst->result_id() != 0 && "Instruction should have a result.");
+  BasicBlock* original_bb = context()->get_instr_block(inst);
+  BasicBlock* bb = original_bb;
+
+  std::unordered_set<uint32_t> bbs_with_uses;
+  get_def_use_mgr()->ForEachUse(
+      inst, [&bbs_with_uses, this](Instruction* use, uint32_t idx) {
+        if (use->opcode() != SpvOpPhi) {
+          bbs_with_uses.insert(context()->get_instr_block(use)->id());
+        } else {
+          bbs_with_uses.insert(use->GetSingleWordOperand(idx + 1));
+        }
+      });
+
+  while (true) {
+    // If |inst| is used in |bb|, then |inst| cannot be moved any further.
+    if (bbs_with_uses.count(bb->id())) {
+      break;
+    }
+
+    // If |bb| has one successor (succ_bb), and |bb| is the only predecessor
+    // of succ_bb, then |inst| can be moved to succ_bb.  If succ_bb, has move
+    // then one predecessor, then moving |inst| into succ_bb could cause it to
+    // be executed more often, so the search has to stop.
+    if (bb->terminator()->opcode() == SpvOpBranch) {
+      uint32_t succ_bb_id = bb->terminator()->GetSingleWordInOperand(0);
+      if (cfg()->preds(succ_bb_id).size() == 1) {
+        bb = context()->get_instr_block(succ_bb_id);
+        continue;
+      } else {
+        break;
+      }
+    }
+
+    // The remaining checks need to know the merge node.  If there is no merge
+    // instruction or an OpLoopMerge, then it is a break or continue.  We could
+    // figure it out, but not worth doing it now.
+    Instruction* merge_inst = bb->GetMergeInst();
+    if (merge_inst == nullptr || merge_inst->opcode() != SpvOpSelectionMerge) {
+      break;
+    }
+
+    // Check all of the successors of |bb| it see which lead to a use of |inst|
+    // before reaching the merge node.
+    bool used_in_multiple_blocks = false;
+    uint32_t bb_used_in = 0;
+    bb->ForEachSuccessorLabel([this, bb, &bb_used_in, &used_in_multiple_blocks,
+                               &bbs_with_uses](uint32_t* succ_bb_id) {
+      if (IntersectsPath(*succ_bb_id, bb->MergeBlockIdIfAny(), bbs_with_uses)) {
+        if (bb_used_in == 0) {
+          bb_used_in = *succ_bb_id;
+        } else {
+          used_in_multiple_blocks = true;
+        }
+      }
+    });
+
+    // If more than one successor, which is not the merge block, uses |inst|
+    // then we have to leave |inst| in bb because there is none of the
+    // successors dominate all uses of |inst|.
+    if (used_in_multiple_blocks) {
+      break;
+    }
+
+    if (bb_used_in == 0) {
+      // If |inst| is not used before reaching the merge node, then we can move
+      // |inst| to the merge node.
+      bb = context()->get_instr_block(bb->MergeBlockIdIfAny());
+    } else {
+      // If the only successor that leads to a used of |inst| has more than 1
+      // predecessor, then moving |inst| could cause it to be executed more
+      // often, so we cannot move it.
+      if (cfg()->preds(bb_used_in).size() != 1) {
+        break;
+      }
+
+      // If |inst| is used after the merge block, then |bb_used_in| does not
+      // dominate all of the uses.  So we cannot move |inst| any further.
+      if (IntersectsPath(bb->MergeBlockIdIfAny(), original_bb->id(),
+                         bbs_with_uses)) {
+        break;
+      }
+
+      // Otherwise, |bb_used_in| dominates all uses, so move |inst| into that
+      // block.
+      bb = context()->get_instr_block(bb_used_in);
+    }
+    continue;
+  }
+  return (bb != original_bb ? bb : nullptr);
+}
+
+bool CodeSinkingPass::ReferencesMutableMemory(Instruction* inst) {
+  if (!inst->IsLoad()) {
+    return false;
+  }
+
+  Instruction* base_ptr = inst->GetBaseAddress();
+  if (base_ptr->opcode() != SpvOpVariable) {
+    return true;
+  }
+
+  if (base_ptr->IsReadOnlyVariable()) {
+    return false;
+  }
+
+  if (HasUniformMemorySync()) {
+    return true;
+  }
+
+  if (base_ptr->GetSingleWordInOperand(0) != SpvStorageClassUniform) {
+    return true;
+  }
+
+  return HasPossibleStore(base_ptr);
+}
+
+bool CodeSinkingPass::HasUniformMemorySync() {
+  if (checked_for_uniform_sync_) {
+    return has_uniform_sync_;
+  }
+
+  bool has_sync = false;
+  get_module()->ForEachInst([this, &has_sync](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpMemoryBarrier: {
+        uint32_t mem_semantics_id = inst->GetSingleWordInOperand(1);
+        if (IsSyncOnUniform(mem_semantics_id)) {
+          has_sync = true;
+        }
+        break;
+      }
+      case SpvOpControlBarrier:
+      case SpvOpAtomicLoad:
+      case SpvOpAtomicStore:
+      case SpvOpAtomicExchange:
+      case SpvOpAtomicIIncrement:
+      case SpvOpAtomicIDecrement:
+      case SpvOpAtomicIAdd:
+      case SpvOpAtomicISub:
+      case SpvOpAtomicSMin:
+      case SpvOpAtomicUMin:
+      case SpvOpAtomicSMax:
+      case SpvOpAtomicUMax:
+      case SpvOpAtomicAnd:
+      case SpvOpAtomicOr:
+      case SpvOpAtomicXor:
+      case SpvOpAtomicFlagTestAndSet:
+      case SpvOpAtomicFlagClear: {
+        uint32_t mem_semantics_id = inst->GetSingleWordInOperand(2);
+        if (IsSyncOnUniform(mem_semantics_id)) {
+          has_sync = true;
+        }
+        break;
+      }
+      case SpvOpAtomicCompareExchange:
+      case SpvOpAtomicCompareExchangeWeak:
+        if (IsSyncOnUniform(inst->GetSingleWordInOperand(2)) ||
+            IsSyncOnUniform(inst->GetSingleWordInOperand(3))) {
+          has_sync = true;
+        }
+        break;
+      default:
+        break;
+    }
+  });
+  has_uniform_sync_ = has_sync;
+  return has_sync;
+}
+
+bool CodeSinkingPass::IsSyncOnUniform(uint32_t mem_semantics_id) const {
+  const analysis::Constant* mem_semantics_const =
+      context()->get_constant_mgr()->FindDeclaredConstant(mem_semantics_id);
+  assert(mem_semantics_const != nullptr &&
+         "Expecting memory semantics id to be a constant.");
+  assert(mem_semantics_const->AsIntConstant() &&
+         "Memory semantics should be an integer.");
+  uint32_t mem_semantics_int = mem_semantics_const->GetU32();
+
+  // If it does not affect uniform memory, then it is does not apply to uniform
+  // memory.
+  if ((mem_semantics_int & SpvMemorySemanticsUniformMemoryMask) == 0) {
+    return false;
+  }
+
+  // Check if there is an acquire or release.  If so not, this it does not add
+  // any memory constraints.
+  return (mem_semantics_int & (SpvMemorySemanticsAcquireMask |
+                               SpvMemorySemanticsAcquireReleaseMask |
+                               SpvMemorySemanticsReleaseMask)) != 0;
+}
+
+bool CodeSinkingPass::HasPossibleStore(Instruction* var_inst) {
+  assert(var_inst->opcode() == SpvOpVariable ||
+         var_inst->opcode() == SpvOpAccessChain ||
+         var_inst->opcode() == SpvOpPtrAccessChain);
+
+  return get_def_use_mgr()->WhileEachUser(var_inst, [this](Instruction* use) {
+    switch (use->opcode()) {
+      case SpvOpStore:
+        return true;
+      case SpvOpAccessChain:
+      case SpvOpPtrAccessChain:
+        return HasPossibleStore(use);
+      default:
+        return false;
+    }
+  });
+}
+
+bool CodeSinkingPass::IntersectsPath(uint32_t start, uint32_t end,
+                                     const std::unordered_set<uint32_t>& set) {
+  std::vector<uint32_t> worklist;
+  worklist.push_back(start);
+  std::unordered_set<uint32_t> already_done;
+  already_done.insert(start);
+
+  while (!worklist.empty()) {
+    BasicBlock* bb = context()->get_instr_block(worklist.back());
+    worklist.pop_back();
+
+    if (bb->id() == end) {
+      continue;
+    }
+
+    if (set.count(bb->id())) {
+      return true;
+    }
+
+    bb->ForEachSuccessorLabel([&already_done, &worklist](uint32_t* succ_bb_id) {
+      if (already_done.insert(*succ_bb_id).second) {
+        worklist.push_back(*succ_bb_id);
+      }
+    });
+  }
+  return false;
+}
+
+// namespace opt
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/code_sink.h b/source/opt/code_sink.h
new file mode 100644
index 0000000..d24df03
--- /dev/null
+++ b/source/opt/code_sink.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2019 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 SOURCE_OPT_CODE_SINK_H_
+#define SOURCE_OPT_CODE_SINK_H_
+
+#include <unordered_map>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This pass does code sinking for OpAccessChain and OpLoad on variables in
+// uniform storage or in read only memory.  Code sinking is a transformation
+// where an instruction is moved into a more deeply nested construct.
+//
+// The goal is to move these instructions as close as possible to their uses
+// without having to execute them more often or to replicate the instruction.
+// Moving the instruction in this way can lead to shorter live ranges, which can
+// lead to less register pressure.  It can also cause instructions to be
+// executed less often because they could be moved into one path of a selection
+// construct.
+//
+// This optimization can cause register pressure to rise if the operands of the
+// instructions go dead after the instructions being moved. That is why we only
+// move certain OpLoad and OpAccessChain instructions.  They generally have
+// constants, loop induction variables, and global pointers as operands.  The
+// operands are live for a longer time in most cases.
+class CodeSinkingPass : public Pass {
+ public:
+  const char* name() const override { return "code-sink"; }
+  Status Process() override;
+
+  // Return the mask of preserved Analyses.
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+  }
+
+ private:
+  // Sinks the instructions in |bb| as much as possible.  Returns true if
+  // something changes.
+  bool SinkInstructionsInBB(BasicBlock* bb);
+
+  // Tries the sink |inst| as much as possible.  Returns true if the instruction
+  // is moved.
+  bool SinkInstruction(Instruction* inst);
+
+  // Returns the basic block in which to move |inst| to move is as close as
+  // possible to the uses of |inst| without increasing the number of times
+  // |inst| will be executed.  Return |nullptr| if there is no need to move
+  // |inst|.
+  BasicBlock* FindNewBasicBlockFor(Instruction* inst);
+
+  // Return true if |inst| reference memory and it is possible that the data in
+  // the memory changes at some point.
+  bool ReferencesMutableMemory(Instruction* inst);
+
+  // Returns true if the module contains an instruction that has a memory
+  // semantics id as an operand, and the memory semantics enforces a
+  // synchronization of uniform memory.  See section 3.25 of the SPIR-V
+  // specification.
+  bool HasUniformMemorySync();
+
+  // Returns true if there may be a store to the variable |var_inst|.
+  bool HasPossibleStore(Instruction* var_inst);
+
+  // Returns true if one of the basic blocks in |set| exists on a path from the
+  // basic block |start| to |end|.
+  bool IntersectsPath(uint32_t start, uint32_t end,
+                      const std::unordered_set<uint32_t>& set);
+
+  // Returns true if |mem_semantics_id| is the id of a constant that, when
+  // interpreted as a memory semantics mask enforces synchronization of uniform
+  // memory.  See section 3.25 of the SPIR-V specification.
+  bool IsSyncOnUniform(uint32_t mem_semantics_id) const;
+
+  // True if a check has for uniform storage has taken place.
+  bool checked_for_uniform_sync_;
+
+  // Cache of whether or not the module has a memory sync on uniform storage.
+  // only valid if |check_for_uniform_sync_| is true.
+  bool has_uniform_sync_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_CODE_SINK_H_
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index fb58eed..30e80d7 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include <source/spirv_optimizer_options.h>
+#include "code_sink.h"
 #include "source/opt/build_module.h"
 #include "source/opt/log.h"
 #include "source/opt/pass_manager.h"
@@ -181,7 +182,8 @@
       .RegisterPass(CreateRedundancyEliminationPass())
       .RegisterPass(CreateDeadBranchElimPass())
       .RegisterPass(CreateBlockMergePass())
-      .RegisterPass(CreateSimplificationPass());
+      .RegisterPass(CreateSimplificationPass())
+      .RegisterPass(CreateCodeSinkingPass());
   // Currently exposing driver bugs resulting in crashes (#946)
   // .RegisterPass(CreateCommonUniformElimPass())
 }
@@ -439,6 +441,8 @@
     }
   } else if (pass_name == "ccp") {
     RegisterPass(CreateCCPPass());
+  } else if (pass_name == "code-sink") {
+    RegisterPass(CreateCodeSinkingPass());
   } else if (pass_name == "O") {
     RegisterPerformancePasses();
   } else if (pass_name == "Os") {
@@ -790,4 +794,9 @@
       MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
 }
 
+Optimizer::PassToken CreateCodeSinkingPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::CodeSinkingPass>());
+}
+
 }  // namespace spvtools
diff --git a/source/opt/passes.h b/source/opt/passes.h
index b9f30ae..f7b675e 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -21,6 +21,7 @@
 #include "source/opt/block_merge_pass.h"
 #include "source/opt/ccp_pass.h"
 #include "source/opt/cfg_cleanup_pass.h"
+#include "source/opt/code_sink.h"
 #include "source/opt/combine_access_chains.h"
 #include "source/opt/common_uniform_elim_pass.h"
 #include "source/opt/compact_ids_pass.h"
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 827f501..6315385 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -21,6 +21,7 @@
        block_merge_test.cpp
        ccp_test.cpp
        cfg_cleanup_test.cpp
+       code_sink_test.cpp
        combine_access_chains_test.cpp
        common_uniform_elim_test.cpp
        compact_ids_test.cpp
diff --git a/test/opt/code_sink_test.cpp b/test/opt/code_sink_test.cpp
new file mode 100644
index 0000000..9b86c66
--- /dev/null
+++ b/test/opt/code_sink_test.cpp
@@ -0,0 +1,533 @@
+// Copyright (c) 2019 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.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using CodeSinkTest = PassTest<::testing::Test>;
+
+TEST_F(CodeSinkTest, MoveToNextBlock) {
+  const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpLabel
+;CHECK: [[ac:%\w+]] = OpAccessChain
+;CHECK: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK: OpCopyObject %uint [[ld]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+          %9 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %10 = OpTypeFunction %void
+          %1 = OpFunction %void None %10
+         %11 = OpLabel
+         %12 = OpAccessChain %_ptr_Uniform_uint %9 %uint_0
+         %13 = OpLoad %uint %12
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpCopyObject %uint %13
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, MovePastSelection) {
+  const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpSelectionMerge [[merge_bb:%\w+]]
+;CHECK: [[merge_bb]] = OpLabel
+;CHECK: [[ac:%\w+]] = OpAccessChain
+;CHECK: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK: OpCopyObject %uint [[ld]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %16
+         %17 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, MoveIntoSelection) {
+  const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpSelectionMerge [[merge_bb:%\w+]]
+;CHECK-NEXT: OpBranchConditional %true [[bb:%\w+]] [[merge_bb]]
+;CHECK: [[bb]] = OpLabel
+;CHECK-NEXT: [[ac:%\w+]] = OpAccessChain
+;CHECK-NEXT: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK-NEXT: OpCopyObject %uint [[ld]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, LeaveBeforeSelection) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+         %19 = OpCopyObject %uint %15
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, LeaveAloneUseInSameBlock) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+       %cond = OpIEqual %bool %15 %uint_0
+               OpSelectionMerge %16 None
+               OpBranchConditional %cond %17 %16
+         %17 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %19 = OpCopyObject %uint %15
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveIntoLoop) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpBranch %17
+         %17 = OpLabel
+               OpLoopMerge %merge %cont None
+               OpBranch %cont
+       %cont = OpLabel
+       %cond = OpIEqual %bool %15 %uint_0
+               OpBranchConditional %cond %merge %17
+      %merge = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveIntoLoop2) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %16
+         %17 = OpLabel
+               OpLoopMerge %merge %cont None
+               OpBranch %cont
+       %cont = OpLabel
+       %cond = OpIEqual %bool %15 %uint_0
+               OpBranchConditional %cond %merge %17
+      %merge = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveSelectionUsedInBothSides) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+         %19 = OpCopyObject %uint %15
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfStore) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpStore %14 %15
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, MoveReadOnlyLoadWithSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpMemoryBarrier %uint_4 %mem_semantics
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpDecorate %_arr_uint_uint_4 BufferBlock
+               OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpMemoryBarrier %uint_4 %mem_semantics
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfAtomicWithSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpDecorate %_arr_uint_uint_4 BufferBlock
+               OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+         %al = OpAtomicLoad %uint %14 %uint_4 %mem_semantics
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, MoveWithAtomicWithoutSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpDecorate %_arr_uint_uint_4 BufferBlock
+               OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+         %al = OpAtomicLoad %uint %14 %uint_4 %uint_0
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools