Add --strip-atomic-counter-memory (#2413)

Adds an optimization pass to remove usages of AtomicCounterMemory
bit. This bit is ignored in Vulkan environments and outright forbidden
in WebGPU ones.

Fixes #2242
diff --git a/Android.mk b/Android.mk
index 8f4f5fe..fe7a93d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -154,6 +154,7 @@
 		source/opt/simplification_pass.cpp \
 		source/opt/ssa_rewrite_pass.cpp \
 		source/opt/strength_reduction_pass.cpp \
+		source/opt/strip_atomic_counter_memory_pass.cpp \
 		source/opt/strip_debug_info_pass.cpp \
 		source/opt/strip_reflect_info_pass.cpp \
 		source/opt/struct_cfg_analysis.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 3038b93..24f5006 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -612,6 +612,8 @@
     "source/opt/ssa_rewrite_pass.h",
     "source/opt/strength_reduction_pass.cpp",
     "source/opt/strength_reduction_pass.h",
+    "source/opt/strip_atomic_counter_memory_pass.cpp",
+    "source/opt/strip_atomic_counter_memory_pass.h",
     "source/opt/strip_debug_info_pass.cpp",
     "source/opt/strip_debug_info_pass.h",
     "source/opt/strip_reflect_info_pass.cpp",
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index e2d5e46..bbc7004 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -208,6 +208,13 @@
 // A null pass does nothing to the SPIR-V module to be optimized.
 Optimizer::PassToken CreateNullPass();
 
+// Creates a strip-atomic-counter-memory pass.
+// A strip-atomic-counter-memory pass removes all usages of the
+// AtomicCounterMemory bit in Memory Semantics bitmasks. This bit is a no-op in
+// Vulkan, so isn't needed in that env. And the related capability is not
+// allowed in WebGPU, so it is not allowed in that env.
+Optimizer::PassToken CreateStripAtomicCounterMemoryPass();
+
 // Creates a strip-debug-info pass.
 // A strip-debug-info pass removes all debug instructions (as documented in
 // Section 3.32.2 of the SPIR-V spec) of the SPIR-V module to be optimized.
diff --git a/source/opcode.cpp b/source/opcode.cpp
index da096a4..ddf2deb 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -605,3 +605,35 @@
       return false;
   }
 }
+
+std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpMemoryBarrier:
+      return {1};
+    case SpvOpAtomicStore:
+    case SpvOpControlBarrier:
+    case SpvOpAtomicFlagClear:
+    case SpvOpMemoryNamedBarrier:
+      return {2};
+    case SpvOpAtomicLoad:
+    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:
+      return {4};
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:
+      return {4, 5};
+    default:
+      return {};
+  }
+}
diff --git a/source/opcode.h b/source/opcode.h
index 76f9a0e..ed64f1b 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -133,4 +133,8 @@
 // Returns true if the given opcode is a debug instruction.
 bool spvOpcodeIsDebug(SpvOp opcode);
 
+// Returns a vector containing the indices of the memory semantics <id>
+// operands for |opcode|.
+std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
+
 #endif  // SOURCE_OPCODE_H_
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index f2ffcca..53901a4 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -96,6 +96,7 @@
   simplification_pass.h
   ssa_rewrite_pass.h
   strength_reduction_pass.h
+  strip_atomic_counter_memory_pass.h
   strip_debug_info_pass.h
   strip_reflect_info_pass.h
   struct_cfg_analysis.h
@@ -189,6 +190,7 @@
   simplification_pass.cpp
   ssa_rewrite_pass.cpp
   strength_reduction_pass.cpp
+  strip_atomic_counter_memory_pass.cpp
   strip_debug_info_pass.cpp
   strip_reflect_info_pass.cpp
   struct_cfg_analysis.cpp
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 344f3dc..5fb4875 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -219,6 +219,8 @@
 
 Optimizer& Optimizer::RegisterWebGPUPasses() {
   return RegisterPass(CreateStripDebugInfoPass())
+      .RegisterPass(CreateStripAtomicCounterMemoryPass())
+      .RegisterPass(CreateEliminateDeadConstantPass())
       .RegisterPass(CreateFlattenDecorationPass())
       .RegisterPass(CreateAggressiveDCEPass())
       .RegisterPass(CreateDeadBranchElimPass());
@@ -266,7 +268,9 @@
   //
   // Both Pass::name() and Pass::desc() should be static class members so they
   // can be invoked without creating a pass instance.
-  if (pass_name == "strip-debug") {
+  if (pass_name == "strip-atomic-counter-memory") {
+    RegisterPass(CreateStripAtomicCounterMemoryPass());
+  } else if (pass_name == "strip-debug") {
     RegisterPass(CreateStripDebugInfoPass());
   } else if (pass_name == "strip-reflect") {
     RegisterPass(CreateStripReflectInfoPass());
@@ -529,6 +533,11 @@
   return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::NullPass>());
 }
 
+Optimizer::PassToken CreateStripAtomicCounterMemoryPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::StripAtomicCounterMemoryPass>());
+}
+
 Optimizer::PassToken CreateStripDebugInfoPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::StripDebugInfoPass>());
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 6f8081c..2b97793 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -63,6 +63,7 @@
 #include "source/opt/simplification_pass.h"
 #include "source/opt/ssa_rewrite_pass.h"
 #include "source/opt/strength_reduction_pass.h"
+#include "source/opt/strip_atomic_counter_memory_pass.h"
 #include "source/opt/strip_debug_info_pass.h"
 #include "source/opt/strip_reflect_info_pass.h"
 #include "source/opt/unify_const_pass.h"
diff --git a/source/opt/strip_atomic_counter_memory_pass.cpp b/source/opt/strip_atomic_counter_memory_pass.cpp
new file mode 100644
index 0000000..46a727c
--- /dev/null
+++ b/source/opt/strip_atomic_counter_memory_pass.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/strip_atomic_counter_memory_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status StripAtomicCounterMemoryPass::Process() {
+  bool changed = false;
+  context()->module()->ForEachInst([this, &changed](Instruction* inst) {
+    auto indices = spvOpcodeMemorySemanticsOperandIndices(inst->opcode());
+    if (indices.empty()) return;
+
+    for (auto idx : indices) {
+      auto mem_sem_id = inst->GetSingleWordOperand(idx);
+      const auto& mem_sem_inst =
+          context()->get_def_use_mgr()->GetDef(mem_sem_id);
+      // The spec explicitly says that this id must be an OpConstant
+      auto mem_sem_val = mem_sem_inst->GetSingleWordOperand(2);
+      if (!(mem_sem_val & SpvMemorySemanticsAtomicCounterMemoryMask)) {
+        continue;
+      }
+      mem_sem_val &= ~SpvMemorySemanticsAtomicCounterMemoryMask;
+
+      analysis::Integer int_type(32, false);
+      const analysis::Type* uint32_type =
+          context()->get_type_mgr()->GetRegisteredType(&int_type);
+      auto* new_const = context()->get_constant_mgr()->GetConstant(
+          uint32_type, {mem_sem_val});
+      auto* new_const_inst =
+          context()->get_constant_mgr()->GetDefiningInstruction(new_const);
+      auto new_const_id = new_const_inst->result_id();
+
+      inst->SetOperand(idx, {new_const_id});
+      context()->UpdateDefUse(inst);
+      changed = true;
+    }
+  });
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/strip_atomic_counter_memory_pass.h b/source/opt/strip_atomic_counter_memory_pass.h
new file mode 100644
index 0000000..d94cf32
--- /dev/null
+++ b/source/opt/strip_atomic_counter_memory_pass.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
+#define SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Removes the AtomicCounterMemory bit from the value being passed into memory
+// semantics. This bit being set is ignored in Vulkan environments and
+// forbidden WebGPU ones.
+class StripAtomicCounterMemoryPass : public Pass {
+ public:
+  const char* name() const override { return "strip-atomic-counter-memory"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index c8deb39..398baa4 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -77,6 +77,7 @@
        set_spec_const_default_value_test.cpp
        simplification_test.cpp
        strength_reduction_test.cpp
+       strip_atomic_counter_memory_test.cpp
        strip_debug_info_test.cpp
        strip_reflect_info_test.cpp
        struct_cfg_analysis_test.cpp
diff --git a/test/opt/optimizer_test.cpp b/test/opt/optimizer_test.cpp
index 52838d5..77d2d1a 100644
--- a/test/opt/optimizer_test.cpp
+++ b/test/opt/optimizer_test.cpp
@@ -231,9 +231,12 @@
   for (auto name = pass_names.begin(); name != pass_names.end(); ++name)
     registered_passes.push_back(*name);
 
-  std::vector<std::string> expected_passes = {
-      "eliminate-dead-branches", "eliminate-dead-code-aggressive",
-      "flatten-decorations", "strip-debug"};
+  std::vector<std::string> expected_passes = {"eliminate-dead-branches",
+                                              "eliminate-dead-code-aggressive",
+                                              "eliminate-dead-const",
+                                              "flatten-decorations",
+                                              "strip-debug",
+                                              "strip-atomic-counter-memory"};
   std::sort(registered_passes.begin(), registered_passes.end());
   std::sort(expected_passes.begin(), expected_passes.end());
 
@@ -265,7 +268,6 @@
   class ValidatorOptions validator_options;
   ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
                       validator_options, true));
-
   std::string disassembly;
   tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
 
@@ -274,7 +276,7 @@
 }
 
 INSTANTIATE_TEST_SUITE_P(
-    WebGPU, WebGPUPassTest,
+    Optimizer, WebGPUPassTest,
     ::testing::ValuesIn(std::vector<WebGPUPassCase>{
         // FlattenDecorations
         {// input
@@ -346,7 +348,95 @@
          "OpReturn\n"
          "OpFunctionEnd\n",
          // pass
-         "strip-debug"}}));
+         "strip-debug"},
+        // Eliminate Dead Constants
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "%u32 = OpTypeInt 32 0\n"
+         "%u32_ptr = OpTypePointer Workgroup %u32\n"
+         "%u32_var = OpVariable %u32_ptr Workgroup\n"
+         "%u32_1 = OpConstant %u32 1\n"
+         "%cross_device = OpConstant %u32 0\n"
+         "%relaxed = OpConstant %u32 0\n"
+         "%acquire_release_atomic_counter_workgroup = OpConstant %u32 1288\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint\n"
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup\n"
+         "%void = OpTypeVoid\n"
+         "%10 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %10\n"
+         "%11 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         "eliminate-dead-const"},
+        // Strip Atomic Counter Memory
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "%u32 = OpTypeInt 32 0\n"
+         "%u32_ptr = OpTypePointer Workgroup %u32\n"
+         "%u32_var = OpVariable %u32_ptr Workgroup\n"
+         "%u32_0 = OpConstant %u32 0\n"
+         "%u32_1 = OpConstant %u32 1\n"
+         "%cross_device = OpConstant %u32 0\n"
+         "%acquire_release_atomic_counter_workgroup = OpConstant %u32 1288\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "%val0 = OpAtomicStore %u32_var %cross_device "
+         "%acquire_release_atomic_counter_workgroup %u32_1\n"
+         "%val1 = OpAtomicIIncrement %u32 %u32_var %cross_device "
+         "%acquire_release_atomic_counter_workgroup\n"
+         "%val2 = OpAtomicCompareExchange %u32 %u32_var %cross_device "
+         "%acquire_release_atomic_counter_workgroup "
+         "%acquire_release_atomic_counter_workgroup %u32_0 %u32_0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint\n"
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup\n"
+         "%uint_0 = OpConstant %uint 0\n"
+         "%uint_1 = OpConstant %uint 1\n"
+         "%uint_0_0 = OpConstant %uint 0\n"
+         "%void = OpTypeVoid\n"
+         "%10 = OpTypeFunction %void\n"
+         "%uint_264 = OpConstant %uint 264\n"
+         "%1 = OpFunction %void None %10\n"
+         "%11 = OpLabel\n"
+         "OpAtomicStore %4 %uint_0_0 %uint_264 %uint_1\n"
+         "%12 = OpAtomicIIncrement %uint %4 %uint_0_0 %uint_264\n"
+         "%13 = OpAtomicCompareExchange %uint %4 %uint_0_0 %uint_264 %uint_264 "
+         "%uint_0 %uint_0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "strip-atomic-counter-memory"}}));
 
 }  // namespace
 }  // namespace opt
diff --git a/test/opt/strip_atomic_counter_memory_test.cpp b/test/opt/strip_atomic_counter_memory_test.cpp
new file mode 100644
index 0000000..8756e81
--- /dev/null
+++ b/test/opt/strip_atomic_counter_memory_test.cpp
@@ -0,0 +1,406 @@
+// Copyright (c) 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+typedef std::tuple<std::string, std::string> StripAtomicCounterMemoryParam;
+
+using MemorySemanticsModified =
+    PassTest<::testing::TestWithParam<StripAtomicCounterMemoryParam>>;
+using NonMemorySemanticsUnmodifiedTest = PassTest<::testing::Test>;
+
+void operator+=(std::vector<const char*>& lhs, const char* rhs) {
+  lhs.push_back(rhs);
+}
+
+std::string GetConstDecl(std::string val) {
+  std::string decl;
+  decl += "%uint_" + val + " = OpConstant %uint " + val;
+  return decl;
+}
+
+std::string GetUnchangedString(std::string(generate_inst)(std::string),
+                               std::string val) {
+  std::string decl = GetConstDecl(val);
+  std::string inst = generate_inst(val);
+
+  std::vector<const char*> result = {
+      // clang-format off
+              "OpCapability Shader",
+              "OpCapability VulkanMemoryModelKHR",
+              "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+              "OpMemoryModel Logical VulkanKHR",
+              "OpEntryPoint Vertex %1 \"shader\"",
+      "%uint = OpTypeInt 32 0",
+"%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint",
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup",
+    "%uint_0 = OpConstant %uint 0",
+    "%uint_1 = OpConstant %uint 1",
+      "%void = OpTypeVoid",
+         "%8 = OpTypeFunction %void",
+               decl.c_str(),
+         "%1 = OpFunction %void None %8",
+        "%10 = OpLabel",
+               inst.c_str(),
+              "OpReturn",
+              "OpFunctionEnd"
+      // clang-format on
+  };
+  return JoinAllInsts(result);
+}
+
+std::string GetChangedString(std::string(generate_inst)(std::string),
+                             std::string orig, std::string changed) {
+  std::string orig_decl = GetConstDecl(orig);
+  std::string changed_decl = GetConstDecl(changed);
+  std::string inst = generate_inst(changed);
+
+  std::vector<const char*> result = {
+      // clang-format off
+              "OpCapability Shader",
+              "OpCapability VulkanMemoryModelKHR",
+              "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+              "OpMemoryModel Logical VulkanKHR",
+              "OpEntryPoint Vertex %1 \"shader\"",
+      "%uint = OpTypeInt 32 0",
+"%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint",
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup",
+    "%uint_0 = OpConstant %uint 0",
+    "%uint_1 = OpConstant %uint 1",
+      "%void = OpTypeVoid",
+         "%8 = OpTypeFunction %void",
+               orig_decl.c_str() };
+  // clang-format on
+  if (changed != "0") result += changed_decl.c_str();
+  result += "%1 = OpFunction %void None %8";
+  result += "%10 = OpLabel";
+  result += inst.c_str();
+  result += "OpReturn";
+  result += "OpFunctionEnd";
+  return JoinAllInsts(result);
+}
+
+std::tuple<std::string, std::string> GetInputAndExpected(
+    std::string(generate_inst)(std::string),
+    StripAtomicCounterMemoryParam param) {
+  std::string orig = std::get<0>(param);
+  std::string changed = std::get<1>(param);
+  std::string input = GetUnchangedString(generate_inst, orig);
+  std::string expected = orig == changed
+                             ? GetUnchangedString(generate_inst, changed)
+                             : GetChangedString(generate_inst, orig, changed);
+  return std::make_tuple(input, expected);
+}
+
+std::string GetOpControlBarrierInst(std::string val) {
+  return "OpControlBarrier %uint_1 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpControlBarrier) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpControlBarrierInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpMemoryBarrierInst(std::string val) {
+  return "OpMemoryBarrier %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpMemoryBarrier) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpMemoryBarrierInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicLoadInst(std::string val) {
+  return "%11 = OpAtomicLoad %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicLoad) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicLoadInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicStoreInst(std::string val) {
+  return "OpAtomicStore %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicStore) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicStoreInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicExchangeInst(std::string val) {
+  return "%11 = OpAtomicExchange %uint %4 %uint_1 %uint_" + val + " %uint_0";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicExchange) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicExchangeInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicCompareExchangeInst(std::string val) {
+  return "%11 = OpAtomicCompareExchange %uint %4 %uint_1 %uint_" + val +
+         " %uint_" + val + " %uint_0 %uint_0";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicCompareExchange) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicCompareExchangeInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicCompareExchangeWeakInst(std::string val) {
+  return "%11 = OpAtomicCompareExchangeWeak %uint %4 %uint_1 %uint_" + val +
+         " %uint_" + val + " %uint_0 %uint_0";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicCompareExchangeWeak) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicCompareExchangeWeakInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicIIncrementInst(std::string val) {
+  return "%11 = OpAtomicIIncrement %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicIIncrement) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicIIncrementInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicIDecrementInst(std::string val) {
+  return "%11 = OpAtomicIDecrement %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicIDecrement) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicIDecrementInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicIAddInst(std::string val) {
+  return "%11 = OpAtomicIAdd %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicIAdd) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicIAddInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicISubInst(std::string val) {
+  return "%11 = OpAtomicISub %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicISub) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicISubInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicSMinInst(std::string val) {
+  return "%11 = OpAtomicSMin %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicSMin) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicSMinInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicUMinInst(std::string val) {
+  return "%11 = OpAtomicUMin %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicUMin) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicUMinInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicSMaxInst(std::string val) {
+  return "%11 = OpAtomicSMax %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicSMax) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicSMaxInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicUMaxInst(std::string val) {
+  return "%11 = OpAtomicUMax %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicUMax) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicUMaxInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicAndInst(std::string val) {
+  return "%11 = OpAtomicAnd %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicAnd) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicAndInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicOrInst(std::string val) {
+  return "%11 = OpAtomicOr %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicOr) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicOrInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicXorInst(std::string val) {
+  return "%11 = OpAtomicXor %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicXor) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicXorInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicFlagTestAndSetInst(std::string val) {
+  return "%11 = OpAtomicFlagTestAndSet %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicFlagTestAndSet) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicFlagTestAndSetInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicFlagClearInst(std::string val) {
+  return "OpAtomicFlagClear %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicFlagClear) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicFlagClearInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpMemoryNamedBarrierInst(std::string val) {
+  return "OpMemoryNamedBarrier %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpMemoryNamedBarrier) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpMemoryNamedBarrierInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(
+    StripAtomicCounterMemoryTest, MemorySemanticsModified,
+    ::testing::ValuesIn(std::vector<StripAtomicCounterMemoryParam>({
+       std::make_tuple("1024", "0"),
+       std::make_tuple("5", "5"),
+       std::make_tuple("1288", "264"),
+       std::make_tuple("264", "264")
+    })));
+// clang-format on
+
+std::string GetNoMemorySemanticsPresentInst(std::string val) {
+  return "%11 = OpVariable %_ptr_Workgroup_uint Workgroup %uint_" + val;
+}
+
+TEST_F(NonMemorySemanticsUnmodifiedTest, NoMemorySemanticsPresent) {
+  std::string input, expected;
+  StripAtomicCounterMemoryParam param = std::make_tuple("1288", "1288");
+  std::tie(input, expected) =
+      GetInputAndExpected(GetNoMemorySemanticsPresentInst, param);
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetMemorySemanticsPresentInst(std::string val) {
+  return "%11 = OpAtomicIAdd %uint %4 %uint_1 %uint_" + val + " %uint_1288";
+}
+
+TEST_F(NonMemorySemanticsUnmodifiedTest, MemorySemanticsPresent) {
+  std::string input, expected;
+  StripAtomicCounterMemoryParam param = std::make_tuple("1288", "264");
+  std::tie(input, expected) =
+      GetInputAndExpected(GetMemorySemanticsPresentInst, param);
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index fb79427..0e77d95 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -340,6 +340,8 @@
                This options should be used rarely, and with caution.
   --strength-reduction
                Replaces instructions with equivalent and less expensive ones.
+  --strip-atomic-counter-memory
+               Removes AtomicCountMemory bit from memory semantics values.
   --strip-debug
                Remove all debug instructions.
   --strip-reflect