Add spirv-opt pass to replace descriptor accesses based on variable indices (#4574)

This commit adds a spirv-opt pass to replace accesses to
descriptor array based on variable indices with constant
elements.

Before:
```
%descriptor = OpVariable %_ptr_array_Image Uniform
...
%ac = OpAccessChain %_ptr_Image %descriptor %variable_index
(some image instructions using %ac)
```
After:
```
%descriptor = OpVariable %_ptr_array_Image Uniform
...
OpSwitch %variable_index 0 %case0 1 %case1 ...
...
%case0 = OpLabel
%ac = OpAccessChain %_ptr_Image %descriptor %uint_0
...
%case1 = OpLabel
%ac = OpAccessChain %_ptr_Image %descriptor %uint_1
...
(use OpPhi for value with concrete type)
```
diff --git a/Android.mk b/Android.mk
index b616654..bc748e5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -100,6 +100,7 @@
 		source/opt/debug_info_manager.cpp \
 		source/opt/def_use_manager.cpp \
 		source/opt/desc_sroa.cpp \
+		source/opt/desc_sroa_util.cpp \
 		source/opt/dominator_analysis.cpp \
 		source/opt/dominator_tree.cpp \
 		source/opt/eliminate_dead_constant_pass.cpp \
@@ -157,6 +158,7 @@
 		source/opt/relax_float_ops_pass.cpp \
 		source/opt/remove_duplicates_pass.cpp \
 		source/opt/remove_unused_interface_variables_pass.cpp \
+		source/opt/replace_desc_array_access_using_var_index.cpp \
 		source/opt/replace_invalid_opc.cpp \
 		source/opt/scalar_analysis.cpp \
 		source/opt/scalar_analysis_simplification.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 4bf3f79..309d513 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -597,6 +597,8 @@
     "source/opt/def_use_manager.h",
     "source/opt/desc_sroa.cpp",
     "source/opt/desc_sroa.h",
+    "source/opt/desc_sroa_util.cpp",
+    "source/opt/desc_sroa_util.h",
     "source/opt/dominator_analysis.cpp",
     "source/opt/dominator_analysis.h",
     "source/opt/dominator_tree.cpp",
@@ -716,6 +718,8 @@
     "source/opt/remove_duplicates_pass.h",
     "source/opt/remove_unused_interface_variables_pass.cpp",
     "source/opt/remove_unused_interface_variables_pass.h",
+    "source/opt/replace_desc_array_access_using_var_index.cpp",
+    "source/opt/replace_desc_array_access_using_var_index.h",
     "source/opt/replace_invalid_opc.cpp",
     "source/opt/replace_invalid_opc.h",
     "source/opt/scalar_analysis.cpp",
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 42eb644..21059cb 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -833,6 +833,13 @@
 //   inclusive.
 Optimizer::PassToken CreateGraphicsRobustAccessPass();
 
+// Create a pass to replace a descriptor access using variable index.
+// This pass replaces every access using a variable index to array variable
+// |desc| that has a DescriptorSet and Binding decorations with a constant
+// element of the array. In order to replace the access using a variable index
+// with the constant element, it uses a switch statement.
+Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass();
+
 // Create descriptor scalar replacement pass.
 // This pass replaces every array variable |desc| that has a DescriptorSet and
 // Binding decorations with a new variable for each element of the array.
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 63af5c1..7d522fb 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -39,6 +39,7 @@
   debug_info_manager.h
   def_use_manager.h
   desc_sroa.h
+  desc_sroa_util.h
   dominator_analysis.h
   dominator_tree.h
   eliminate_dead_constant_pass.h
@@ -100,6 +101,7 @@
   relax_float_ops_pass.h
   remove_duplicates_pass.h
   remove_unused_interface_variables_pass.h
+  replace_desc_array_access_using_var_index.h
   replace_invalid_opc.h
   scalar_analysis.h
   scalar_analysis_nodes.h
@@ -148,6 +150,7 @@
   debug_info_manager.cpp
   def_use_manager.cpp
   desc_sroa.cpp
+  desc_sroa_util.cpp
   dominator_analysis.cpp
   dominator_tree.cpp
   eliminate_dead_constant_pass.cpp
@@ -205,6 +208,7 @@
   relax_float_ops_pass.cpp
   remove_duplicates_pass.cpp
   remove_unused_interface_variables_pass.cpp
+  replace_desc_array_access_using_var_index.cpp
   replace_invalid_opc.cpp
   scalar_analysis.cpp
   scalar_analysis_simplification.cpp
diff --git a/source/opt/decoration_manager.cpp b/source/opt/decoration_manager.cpp
index 4bf026e..2146c35 100644
--- a/source/opt/decoration_manager.cpp
+++ b/source/opt/decoration_manager.cpp
@@ -490,6 +490,14 @@
   });
 }
 
+bool DecorationManager::HasDecoration(uint32_t id, uint32_t decoration) {
+  bool has_decoration = false;
+  ForEachDecoration(id, decoration, [&has_decoration](const Instruction&) {
+    has_decoration = true;
+  });
+  return has_decoration;
+}
+
 bool DecorationManager::FindDecoration(
     uint32_t id, uint32_t decoration,
     std::function<bool(const Instruction&)> f) {
diff --git a/source/opt/decoration_manager.h b/source/opt/decoration_manager.h
index b753e6b..fe78f2c 100644
--- a/source/opt/decoration_manager.h
+++ b/source/opt/decoration_manager.h
@@ -90,6 +90,10 @@
   bool AreDecorationsTheSame(const Instruction* inst1, const Instruction* inst2,
                              bool ignore_target) const;
 
+  // Returns whether a decoration instruction for |id| with decoration
+  // |decoration| exists or not.
+  bool HasDecoration(uint32_t id, uint32_t decoration);
+
   // |f| is run on each decoration instruction for |id| with decoration
   // |decoration|. Processed are all decorations which target |id| either
   // directly or indirectly by Decoration Groups.
diff --git a/source/opt/desc_sroa.cpp b/source/opt/desc_sroa.cpp
index 5e95006..0b83937 100644
--- a/source/opt/desc_sroa.cpp
+++ b/source/opt/desc_sroa.cpp
@@ -14,6 +14,7 @@
 
 #include "source/opt/desc_sroa.h"
 
+#include "source/opt/desc_sroa_util.h"
 #include "source/util/string_utils.h"
 
 namespace spvtools {
@@ -25,7 +26,7 @@
   std::vector<Instruction*> vars_to_kill;
 
   for (Instruction& var : context()->types_values()) {
-    if (IsCandidate(&var)) {
+    if (descsroautil::IsDescriptorArray(context(), &var)) {
       modified = true;
       if (!ReplaceCandidate(&var)) {
         return Status::Failure;
@@ -41,72 +42,6 @@
   return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
 }
 
-bool DescriptorScalarReplacement::IsCandidate(Instruction* var) {
-  if (var->opcode() != SpvOpVariable) {
-    return false;
-  }
-
-  uint32_t ptr_type_id = var->type_id();
-  Instruction* ptr_type_inst =
-      context()->get_def_use_mgr()->GetDef(ptr_type_id);
-  if (ptr_type_inst->opcode() != SpvOpTypePointer) {
-    return false;
-  }
-
-  uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1);
-  Instruction* var_type_inst =
-      context()->get_def_use_mgr()->GetDef(var_type_id);
-  if (var_type_inst->opcode() != SpvOpTypeArray &&
-      var_type_inst->opcode() != SpvOpTypeStruct) {
-    return false;
-  }
-
-  // All structures with descriptor assignments must be replaced by variables,
-  // one for each of their members - with the exceptions of buffers.
-  if (IsTypeOfStructuredBuffer(var_type_inst)) {
-    return false;
-  }
-
-  bool has_desc_set_decoration = false;
-  context()->get_decoration_mgr()->ForEachDecoration(
-      var->result_id(), SpvDecorationDescriptorSet,
-      [&has_desc_set_decoration](const Instruction&) {
-        has_desc_set_decoration = true;
-      });
-  if (!has_desc_set_decoration) {
-    return false;
-  }
-
-  bool has_binding_decoration = false;
-  context()->get_decoration_mgr()->ForEachDecoration(
-      var->result_id(), SpvDecorationBinding,
-      [&has_binding_decoration](const Instruction&) {
-        has_binding_decoration = true;
-      });
-  if (!has_binding_decoration) {
-    return false;
-  }
-
-  return true;
-}
-
-bool DescriptorScalarReplacement::IsTypeOfStructuredBuffer(
-    const Instruction* type) const {
-  if (type->opcode() != SpvOpTypeStruct) {
-    return false;
-  }
-
-  // All buffers have offset decorations for members of their structure types.
-  // This is how we distinguish it from a structure of descriptors.
-  bool has_offset_decoration = false;
-  context()->get_decoration_mgr()->ForEachDecoration(
-      type->result_id(), SpvDecorationOffset,
-      [&has_offset_decoration](const Instruction&) {
-        has_offset_decoration = true;
-      });
-  return has_offset_decoration;
-}
-
 bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) {
   std::vector<Instruction*> access_chain_work_list;
   std::vector<Instruction*> load_work_list;
@@ -162,16 +97,15 @@
     return false;
   }
 
-  uint32_t idx_id = use->GetSingleWordInOperand(1);
-  const analysis::Constant* idx_const =
-      context()->get_constant_mgr()->FindDeclaredConstant(idx_id);
-  if (idx_const == nullptr) {
+  const analysis::Constant* const_index =
+      descsroautil::GetAccessChainIndexAsConst(context(), use);
+  if (const_index == nullptr) {
     context()->EmitErrorMessage("Variable cannot be replaced: invalid index",
                                 use);
     return false;
   }
 
-  uint32_t idx = idx_const->GetU32();
+  uint32_t idx = const_index->GetU32();
   uint32_t replacement_var = GetReplacementVariable(var, idx);
 
   if (use->NumInOperands() == 2) {
@@ -208,39 +142,12 @@
                                                              uint32_t idx) {
   auto replacement_vars = replacement_variables_.find(var);
   if (replacement_vars == replacement_variables_.end()) {
-    uint32_t ptr_type_id = var->type_id();
-    Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
-    assert(ptr_type_inst->opcode() == SpvOpTypePointer &&
-           "Variable should be a pointer to an array or structure.");
-    uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
-    Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(pointee_type_id);
-    const bool is_array = pointee_type_inst->opcode() == SpvOpTypeArray;
-    const bool is_struct = pointee_type_inst->opcode() == SpvOpTypeStruct;
-    assert((is_array || is_struct) &&
-           "Variable should be a pointer to an array or structure.");
-
-    // For arrays, each array element should be replaced with a new replacement
-    // variable
-    if (is_array) {
-      uint32_t array_len_id = pointee_type_inst->GetSingleWordInOperand(1);
-      const analysis::Constant* array_len_const =
-          context()->get_constant_mgr()->FindDeclaredConstant(array_len_id);
-      assert(array_len_const != nullptr && "Array length must be a constant.");
-      uint32_t array_len = array_len_const->GetU32();
-
-      replacement_vars = replacement_variables_
-                             .insert({var, std::vector<uint32_t>(array_len, 0)})
-                             .first;
-    }
-    // For structures, each member should be replaced with a new replacement
-    // variable
-    if (is_struct) {
-      const uint32_t num_members = pointee_type_inst->NumInOperands();
-      replacement_vars =
-          replacement_variables_
-              .insert({var, std::vector<uint32_t>(num_members, 0)})
-              .first;
-    }
+    uint32_t number_of_elements =
+        descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var);
+    replacement_vars =
+        replacement_variables_
+            .insert({var, std::vector<uint32_t>(number_of_elements, 0)})
+            .first;
   }
 
   if (replacement_vars->second[idx] == 0) {
@@ -377,7 +284,7 @@
   // The number of bindings consumed by a structure is the sum of the bindings
   // used by its members.
   if (type_inst->opcode() == SpvOpTypeStruct &&
-      !IsTypeOfStructuredBuffer(type_inst)) {
+      !descsroautil::IsTypeOfStructuredBuffer(context(), type_inst)) {
     uint32_t sum = 0;
     for (uint32_t i = 0; i < type_inst->NumInOperands(); i++)
       sum += GetNumBindingsUsedByType(type_inst->GetSingleWordInOperand(i));
diff --git a/source/opt/desc_sroa.h b/source/opt/desc_sroa.h
index cd72fd3..70fd381 100644
--- a/source/opt/desc_sroa.h
+++ b/source/opt/desc_sroa.h
@@ -46,10 +46,6 @@
   }
 
  private:
-  // Returns true if |var| is an OpVariable instruction that represents a
-  // descriptor array.  These are the variables that we want to replace.
-  bool IsCandidate(Instruction* var);
-
   // Replaces all references to |var| by new variables, one for each element of
   // the array |var|.  The binding for the new variables corresponding to
   // element i will be the binding of |var| plus i.  Returns true if successful.
@@ -93,11 +89,6 @@
   // bindings used by its members.
   uint32_t GetNumBindingsUsedByType(uint32_t type_id);
 
-  // Returns true if |type| is a type that could be used for a structured buffer
-  // as opposed to a type that would be used for a structure of resource
-  // descriptors.
-  bool IsTypeOfStructuredBuffer(const Instruction* type) const;
-
   // A map from an OpVariable instruction to the set of variables that will be
   // used to replace it. The entry |replacement_variables_[var][i]| is the id of
   // a variable that will be used in the place of the the ith element of the
diff --git a/source/opt/desc_sroa_util.cpp b/source/opt/desc_sroa_util.cpp
new file mode 100644
index 0000000..1954e2c
--- /dev/null
+++ b/source/opt/desc_sroa_util.cpp
@@ -0,0 +1,117 @@
+// Copyright (c) 2021 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 "source/opt/desc_sroa_util.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+const uint32_t kOpAccessChainInOperandIndexes = 1;
+
+// Returns the length of array type |type|.
+uint32_t GetLengthOfArrayType(IRContext* context, Instruction* type) {
+  assert(type->opcode() == SpvOpTypeArray && "type must be array");
+  uint32_t length_id = type->GetSingleWordInOperand(1);
+  const analysis::Constant* length_const =
+      context->get_constant_mgr()->FindDeclaredConstant(length_id);
+  assert(length_const != nullptr);
+  return length_const->GetU32();
+}
+
+}  // namespace
+
+namespace descsroautil {
+
+bool IsDescriptorArray(IRContext* context, Instruction* var) {
+  if (var->opcode() != SpvOpVariable) {
+    return false;
+  }
+
+  uint32_t ptr_type_id = var->type_id();
+  Instruction* ptr_type_inst = context->get_def_use_mgr()->GetDef(ptr_type_id);
+  if (ptr_type_inst->opcode() != SpvOpTypePointer) {
+    return false;
+  }
+
+  uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1);
+  Instruction* var_type_inst = context->get_def_use_mgr()->GetDef(var_type_id);
+  if (var_type_inst->opcode() != SpvOpTypeArray &&
+      var_type_inst->opcode() != SpvOpTypeStruct) {
+    return false;
+  }
+
+  // All structures with descriptor assignments must be replaced by variables,
+  // one for each of their members - with the exceptions of buffers.
+  if (IsTypeOfStructuredBuffer(context, var_type_inst)) {
+    return false;
+  }
+
+  if (!context->get_decoration_mgr()->HasDecoration(
+          var->result_id(), SpvDecorationDescriptorSet)) {
+    return false;
+  }
+
+  return context->get_decoration_mgr()->HasDecoration(var->result_id(),
+                                                      SpvDecorationBinding);
+}
+
+bool IsTypeOfStructuredBuffer(IRContext* context, const Instruction* type) {
+  if (type->opcode() != SpvOpTypeStruct) {
+    return false;
+  }
+
+  // All buffers have offset decorations for members of their structure types.
+  // This is how we distinguish it from a structure of descriptors.
+  return context->get_decoration_mgr()->HasDecoration(type->result_id(),
+                                                      SpvDecorationOffset);
+}
+
+const analysis::Constant* GetAccessChainIndexAsConst(
+    IRContext* context, Instruction* access_chain) {
+  if (access_chain->NumInOperands() <= 1) {
+    return nullptr;
+  }
+  uint32_t idx_id = GetFirstIndexOfAccessChain(access_chain);
+  const analysis::Constant* idx_const =
+      context->get_constant_mgr()->FindDeclaredConstant(idx_id);
+  return idx_const;
+}
+
+uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain) {
+  assert(access_chain->NumInOperands() > 1 &&
+         "OpAccessChain does not have Indexes operand");
+  return access_chain->GetSingleWordInOperand(kOpAccessChainInOperandIndexes);
+}
+
+uint32_t GetNumberOfElementsForArrayOrStruct(IRContext* context,
+                                             Instruction* var) {
+  uint32_t ptr_type_id = var->type_id();
+  Instruction* ptr_type_inst = context->get_def_use_mgr()->GetDef(ptr_type_id);
+  assert(ptr_type_inst->opcode() == SpvOpTypePointer &&
+         "Variable should be a pointer to an array or structure.");
+  uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
+  Instruction* pointee_type_inst =
+      context->get_def_use_mgr()->GetDef(pointee_type_id);
+  if (pointee_type_inst->opcode() == SpvOpTypeArray) {
+    return GetLengthOfArrayType(context, pointee_type_inst);
+  }
+  assert(pointee_type_inst->opcode() == SpvOpTypeStruct &&
+         "Variable should be a pointer to an array or structure.");
+  return pointee_type_inst->NumInOperands();
+}
+
+}  // namespace descsroautil
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/desc_sroa_util.h b/source/opt/desc_sroa_util.h
new file mode 100644
index 0000000..2f45c0c
--- /dev/null
+++ b/source/opt/desc_sroa_util.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2021 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_DESC_SROA_UTIL_H_
+#define SOURCE_OPT_DESC_SROA_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functions for the descriptor array SROA.
+namespace descsroautil {
+
+// Returns true if |var| is an OpVariable instruction that represents a
+// descriptor array.
+bool IsDescriptorArray(IRContext* context, Instruction* var);
+
+// Returns true if |type| is a type that could be used for a structured buffer
+// as opposed to a type that would be used for a structure of resource
+// descriptors.
+bool IsTypeOfStructuredBuffer(IRContext* context, const Instruction* type);
+
+// Returns the first index of the OpAccessChain instruction |access_chain| as
+// a constant. Returns nullptr if it is not a constant.
+const analysis::Constant* GetAccessChainIndexAsConst(IRContext* context,
+                                                     Instruction* access_chain);
+
+// Returns the number of elements of an OpVariable instruction |var| whose type
+// must be a pointer to an array or a struct.
+uint32_t GetNumberOfElementsForArrayOrStruct(IRContext* context,
+                                             Instruction* var);
+
+// Returns the first Indexes operand id of the OpAccessChain or
+// OpInBoundsAccessChain instruction |access_chain|. The access chain must have
+// at least 1 index.
+uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain);
+
+}  // namespace descsroautil
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_DESC_SROA_UTIL_H_
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 990be2e..e74db26 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -320,6 +320,8 @@
     RegisterPass(CreateCombineAccessChainsPass());
   } else if (pass_name == "convert-local-access-chains") {
     RegisterPass(CreateLocalAccessChainConvertPass());
+  } else if (pass_name == "replace-desc-array-access-using-var-index") {
+    RegisterPass(CreateReplaceDescArrayAccessUsingVarIndexPass());
   } else if (pass_name == "descriptor-scalar-replacement") {
     RegisterPass(CreateDescriptorScalarReplacementPass());
   } else if (pass_name == "eliminate-dead-code-aggressive") {
@@ -958,6 +960,11 @@
       MakeUnique<opt::GraphicsRobustAccessPass>());
 }
 
+Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::ReplaceDescArrayAccessUsingVarIndex>());
+}
+
 Optimizer::PassToken CreateDescriptorScalarReplacementPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::DescriptorScalarReplacement>());
diff --git a/source/opt/passes.h b/source/opt/passes.h
index da837f2..f3c30d5 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -66,6 +66,7 @@
 #include "source/opt/relax_float_ops_pass.h"
 #include "source/opt/remove_duplicates_pass.h"
 #include "source/opt/remove_unused_interface_variables_pass.h"
+#include "source/opt/replace_desc_array_access_using_var_index.h"
 #include "source/opt/replace_invalid_opc.h"
 #include "source/opt/scalar_replacement_pass.h"
 #include "source/opt/set_spec_constant_default_value_pass.h"
diff --git a/source/opt/replace_desc_array_access_using_var_index.cpp b/source/opt/replace_desc_array_access_using_var_index.cpp
new file mode 100644
index 0000000..1082e67
--- /dev/null
+++ b/source/opt/replace_desc_array_access_using_var_index.cpp
@@ -0,0 +1,423 @@
+// Copyright (c) 2021 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 "source/opt/replace_desc_array_access_using_var_index.h"
+
+#include "source/opt/desc_sroa_util.h"
+#include "source/opt/ir_builder.h"
+#include "source/util/string_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+const uint32_t kOpAccessChainInOperandIndexes = 1;
+const uint32_t kOpTypePointerInOperandType = 1;
+const uint32_t kOpTypeArrayInOperandType = 0;
+const uint32_t kOpTypeStructInOperandMember = 0;
+IRContext::Analysis kAnalysisDefUseAndInstrToBlockMapping =
+    IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+
+uint32_t GetValueWithKeyExistenceCheck(
+    uint32_t key, const std::unordered_map<uint32_t, uint32_t>& map) {
+  auto itr = map.find(key);
+  assert(itr != map.end() && "Key does not exist");
+  return itr->second;
+}
+
+}  // namespace
+
+Pass::Status ReplaceDescArrayAccessUsingVarIndex::Process() {
+  Status status = Status::SuccessWithoutChange;
+  for (Instruction& var : context()->types_values()) {
+    if (descsroautil::IsDescriptorArray(context(), &var)) {
+      if (ReplaceVariableAccessesWithConstantElements(&var))
+        status = Status::SuccessWithChange;
+    }
+  }
+  return status;
+}
+
+bool ReplaceDescArrayAccessUsingVarIndex::
+    ReplaceVariableAccessesWithConstantElements(Instruction* var) const {
+  std::vector<Instruction*> work_list;
+  get_def_use_mgr()->ForEachUser(var, [&work_list](Instruction* use) {
+    switch (use->opcode()) {
+      case SpvOpAccessChain:
+      case SpvOpInBoundsAccessChain:
+        work_list.push_back(use);
+        break;
+      default:
+        break;
+    }
+  });
+
+  bool updated = false;
+  for (Instruction* access_chain : work_list) {
+    if (descsroautil::GetAccessChainIndexAsConst(context(), access_chain) ==
+        nullptr) {
+      ReplaceAccessChain(var, access_chain);
+      updated = true;
+    }
+  }
+  // Note that we do not consider OpLoad and OpCompositeExtract because
+  // OpCompositeExtract always has constant literals for indices.
+  return updated;
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::ReplaceAccessChain(
+    Instruction* var, Instruction* access_chain) const {
+  uint32_t number_of_elements =
+      descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var);
+  assert(number_of_elements != 0 && "Number of element is 0");
+  if (number_of_elements == 1) {
+    UseConstIndexForAccessChain(access_chain, 0);
+    get_def_use_mgr()->AnalyzeInstUse(access_chain);
+    return;
+  }
+  ReplaceUsersOfAccessChain(access_chain, number_of_elements);
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::ReplaceUsersOfAccessChain(
+    Instruction* access_chain, uint32_t number_of_elements) const {
+  std::vector<Instruction*> final_users;
+  CollectRecursiveUsersWithConcreteType(access_chain, &final_users);
+  for (auto* inst : final_users) {
+    std::deque<Instruction*> insts_to_be_cloned =
+        CollectRequiredImageInsts(inst);
+    ReplaceNonUniformAccessWithSwitchCase(
+        inst, access_chain, number_of_elements, insts_to_be_cloned);
+  }
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::CollectRecursiveUsersWithConcreteType(
+    Instruction* access_chain, std::vector<Instruction*>* final_users) const {
+  std::queue<Instruction*> work_list;
+  work_list.push(access_chain);
+  while (!work_list.empty()) {
+    auto* inst_from_work_list = work_list.front();
+    work_list.pop();
+    get_def_use_mgr()->ForEachUser(
+        inst_from_work_list, [this, final_users, &work_list](Instruction* use) {
+          // TODO: Support Boolean type as well.
+          if (!use->HasResultId() || IsConcreteType(use->type_id())) {
+            final_users->push_back(use);
+          } else {
+            work_list.push(use);
+          }
+        });
+  }
+}
+
+std::deque<Instruction*>
+ReplaceDescArrayAccessUsingVarIndex::CollectRequiredImageInsts(
+    Instruction* user_of_image_insts) const {
+  std::unordered_set<uint32_t> seen_inst_ids;
+  std::queue<Instruction*> work_list;
+
+  auto decision_to_include_operand = [this, &seen_inst_ids,
+                                      &work_list](uint32_t* idp) {
+    if (!seen_inst_ids.insert(*idp).second) return;
+    Instruction* operand = get_def_use_mgr()->GetDef(*idp);
+    if (context()->get_instr_block(operand) != nullptr &&
+        HasImageOrImagePtrType(operand)) {
+      work_list.push(operand);
+    }
+  };
+
+  std::deque<Instruction*> required_image_insts;
+  required_image_insts.push_front(user_of_image_insts);
+  user_of_image_insts->ForEachInId(decision_to_include_operand);
+  while (!work_list.empty()) {
+    auto* inst_from_work_list = work_list.front();
+    work_list.pop();
+    required_image_insts.push_front(inst_from_work_list);
+    inst_from_work_list->ForEachInId(decision_to_include_operand);
+  }
+  return required_image_insts;
+}
+
+bool ReplaceDescArrayAccessUsingVarIndex::HasImageOrImagePtrType(
+    const Instruction* inst) const {
+  assert(inst != nullptr && inst->type_id() != 0 && "Invalid instruction");
+  return IsImageOrImagePtrType(get_def_use_mgr()->GetDef(inst->type_id()));
+}
+
+bool ReplaceDescArrayAccessUsingVarIndex::IsImageOrImagePtrType(
+    const Instruction* type_inst) const {
+  if (type_inst->opcode() == SpvOpTypeImage ||
+      type_inst->opcode() == SpvOpTypeSampler ||
+      type_inst->opcode() == SpvOpTypeSampledImage) {
+    return true;
+  }
+  if (type_inst->opcode() == SpvOpTypePointer) {
+    Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(
+        type_inst->GetSingleWordInOperand(kOpTypePointerInOperandType));
+    return IsImageOrImagePtrType(pointee_type_inst);
+  }
+  if (type_inst->opcode() == SpvOpTypeArray) {
+    Instruction* element_type_inst = get_def_use_mgr()->GetDef(
+        type_inst->GetSingleWordInOperand(kOpTypeArrayInOperandType));
+    return IsImageOrImagePtrType(element_type_inst);
+  }
+  if (type_inst->opcode() != SpvOpTypeStruct) return false;
+  for (uint32_t in_operand_idx = kOpTypeStructInOperandMember;
+       in_operand_idx < type_inst->NumInOperands(); ++in_operand_idx) {
+    Instruction* member_type_inst = get_def_use_mgr()->GetDef(
+        type_inst->GetSingleWordInOperand(kOpTypeStructInOperandMember));
+    if (IsImageOrImagePtrType(member_type_inst)) return true;
+  }
+  return false;
+}
+
+bool ReplaceDescArrayAccessUsingVarIndex::IsConcreteType(
+    uint32_t type_id) const {
+  Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+  if (type_inst->opcode() == SpvOpTypeInt ||
+      type_inst->opcode() == SpvOpTypeFloat) {
+    return true;
+  }
+  if (type_inst->opcode() == SpvOpTypeVector ||
+      type_inst->opcode() == SpvOpTypeMatrix ||
+      type_inst->opcode() == SpvOpTypeArray) {
+    return IsConcreteType(type_inst->GetSingleWordInOperand(0));
+  }
+  if (type_inst->opcode() == SpvOpTypeStruct) {
+    for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+      if (!IsConcreteType(type_inst->GetSingleWordInOperand(i))) return false;
+    }
+    return true;
+  }
+  return false;
+}
+
+BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateCaseBlock(
+    Instruction* access_chain, uint32_t element_index,
+    const std::deque<Instruction*>& insts_to_be_cloned,
+    uint32_t branch_target_id,
+    std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const {
+  auto* case_block = CreateNewBlock();
+  AddConstElementAccessToCaseBlock(case_block, access_chain, element_index,
+                                   old_ids_to_new_ids);
+  CloneInstsToBlock(case_block, access_chain, insts_to_be_cloned,
+                    old_ids_to_new_ids);
+  AddBranchToBlock(case_block, branch_target_id);
+  UseNewIdsInBlock(case_block, *old_ids_to_new_ids);
+  return case_block;
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::CloneInstsToBlock(
+    BasicBlock* block, Instruction* inst_to_skip_cloning,
+    const std::deque<Instruction*>& insts_to_be_cloned,
+    std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const {
+  for (auto* inst_to_be_cloned : insts_to_be_cloned) {
+    if (inst_to_be_cloned == inst_to_skip_cloning) continue;
+    std::unique_ptr<Instruction> clone(inst_to_be_cloned->Clone(context()));
+    if (inst_to_be_cloned->HasResultId()) {
+      uint32_t new_id = context()->TakeNextId();
+      clone->SetResultId(new_id);
+      (*old_ids_to_new_ids)[inst_to_be_cloned->result_id()] = new_id;
+    }
+    get_def_use_mgr()->AnalyzeInstDefUse(clone.get());
+    context()->set_instr_block(clone.get(), block);
+    block->AddInstruction(std::move(clone));
+  }
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::UseNewIdsInBlock(
+    BasicBlock* block,
+    const std::unordered_map<uint32_t, uint32_t>& old_ids_to_new_ids) const {
+  for (auto block_itr = block->begin(); block_itr != block->end();
+       ++block_itr) {
+    (&*block_itr)->ForEachInId([&old_ids_to_new_ids](uint32_t* idp) {
+      auto old_ids_to_new_ids_itr = old_ids_to_new_ids.find(*idp);
+      if (old_ids_to_new_ids_itr == old_ids_to_new_ids.end()) return;
+      *idp = old_ids_to_new_ids_itr->second;
+    });
+    get_def_use_mgr()->AnalyzeInstUse(&*block_itr);
+  }
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::ReplaceNonUniformAccessWithSwitchCase(
+    Instruction* access_chain_final_user, Instruction* access_chain,
+    uint32_t number_of_elements,
+    const std::deque<Instruction*>& insts_to_be_cloned) const {
+  // Create merge block and add terminator
+  auto* block = context()->get_instr_block(access_chain_final_user);
+  auto* merge_block = SeparateInstructionsIntoNewBlock(
+      block, access_chain_final_user->NextNode());
+
+  auto* function = block->GetParent();
+
+  // Add case blocks
+  std::vector<uint32_t> phi_operands;
+  std::vector<uint32_t> case_block_ids;
+  for (uint32_t idx = 0; idx < number_of_elements; ++idx) {
+    std::unordered_map<uint32_t, uint32_t> old_ids_to_new_ids_for_cloned_insts;
+    std::unique_ptr<BasicBlock> case_block(CreateCaseBlock(
+        access_chain, idx, insts_to_be_cloned, merge_block->id(),
+        &old_ids_to_new_ids_for_cloned_insts));
+    case_block_ids.push_back(case_block->id());
+    function->InsertBasicBlockBefore(std::move(case_block), merge_block);
+
+    // Keep the operand for OpPhi
+    if (!access_chain_final_user->HasResultId()) continue;
+    uint32_t phi_operand =
+        GetValueWithKeyExistenceCheck(access_chain_final_user->result_id(),
+                                      old_ids_to_new_ids_for_cloned_insts);
+    phi_operands.push_back(phi_operand);
+  }
+
+  // Create default block
+  std::unique_ptr<BasicBlock> default_block(
+      CreateDefaultBlock(access_chain_final_user->HasResultId(), &phi_operands,
+                         merge_block->id()));
+  uint32_t default_block_id = default_block->id();
+  function->InsertBasicBlockBefore(std::move(default_block), merge_block);
+
+  // Create OpSwitch
+  uint32_t access_chain_index_var_id =
+      descsroautil::GetFirstIndexOfAccessChain(access_chain);
+  AddSwitchForAccessChain(block, access_chain_index_var_id, default_block_id,
+                          merge_block->id(), case_block_ids);
+
+  // Create phi instructions
+  if (!phi_operands.empty()) {
+    uint32_t phi_id = CreatePhiInstruction(merge_block, phi_operands,
+                                           case_block_ids, default_block_id);
+    context()->ReplaceAllUsesWith(access_chain_final_user->result_id(), phi_id);
+  }
+
+  // Replace OpPhi incoming block operand that uses |block| with |merge_block|
+  ReplacePhiIncomingBlock(block->id(), merge_block->id());
+}
+
+BasicBlock*
+ReplaceDescArrayAccessUsingVarIndex::SeparateInstructionsIntoNewBlock(
+    BasicBlock* block, Instruction* separation_begin_inst) const {
+  auto separation_begin = block->begin();
+  while (separation_begin != block->end() &&
+         &*separation_begin != separation_begin_inst) {
+    ++separation_begin;
+  }
+  return block->SplitBasicBlock(context(), context()->TakeNextId(),
+                                separation_begin);
+}
+
+BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateNewBlock() const {
+  auto* new_block = new BasicBlock(std::unique_ptr<Instruction>(
+      new Instruction(context(), SpvOpLabel, 0, context()->TakeNextId(), {})));
+  get_def_use_mgr()->AnalyzeInstDefUse(new_block->GetLabelInst());
+  context()->set_instr_block(new_block->GetLabelInst(), new_block);
+  return new_block;
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::UseConstIndexForAccessChain(
+    Instruction* access_chain, uint32_t const_element_idx) const {
+  uint32_t const_element_idx_id =
+      context()->get_constant_mgr()->GetUIntConst(const_element_idx);
+  access_chain->SetInOperand(kOpAccessChainInOperandIndexes,
+                             {const_element_idx_id});
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::AddConstElementAccessToCaseBlock(
+    BasicBlock* case_block, Instruction* access_chain,
+    uint32_t const_element_idx,
+    std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const {
+  std::unique_ptr<Instruction> access_clone(access_chain->Clone(context()));
+  UseConstIndexForAccessChain(access_clone.get(), const_element_idx);
+
+  uint32_t new_access_id = context()->TakeNextId();
+  (*old_ids_to_new_ids)[access_clone->result_id()] = new_access_id;
+  access_clone->SetResultId(new_access_id);
+  get_def_use_mgr()->AnalyzeInstDefUse(access_clone.get());
+
+  context()->set_instr_block(access_clone.get(), case_block);
+  case_block->AddInstruction(std::move(access_clone));
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::AddBranchToBlock(
+    BasicBlock* parent_block, uint32_t branch_destination) const {
+  InstructionBuilder builder{context(), parent_block,
+                             kAnalysisDefUseAndInstrToBlockMapping};
+  builder.AddBranch(branch_destination);
+}
+
+BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateDefaultBlock(
+    bool null_const_for_phi_is_needed, std::vector<uint32_t>* phi_operands,
+    uint32_t merge_block_id) const {
+  auto* default_block = CreateNewBlock();
+  AddBranchToBlock(default_block, merge_block_id);
+  if (!null_const_for_phi_is_needed) return default_block;
+
+  // Create null value for OpPhi
+  Instruction* inst = context()->get_def_use_mgr()->GetDef((*phi_operands)[0]);
+  auto* null_const_inst = GetConstNull(inst->type_id());
+  phi_operands->push_back(null_const_inst->result_id());
+  return default_block;
+}
+
+Instruction* ReplaceDescArrayAccessUsingVarIndex::GetConstNull(
+    uint32_t type_id) const {
+  assert(type_id != 0 && "Result type is expected");
+  auto* type = context()->get_type_mgr()->GetType(type_id);
+  auto* null_const = context()->get_constant_mgr()->GetConstant(type, {});
+  return context()->get_constant_mgr()->GetDefiningInstruction(null_const);
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::AddSwitchForAccessChain(
+    BasicBlock* parent_block, uint32_t access_chain_index_var_id,
+    uint32_t default_id, uint32_t merge_id,
+    const std::vector<uint32_t>& case_block_ids) const {
+  InstructionBuilder builder{context(), parent_block,
+                             kAnalysisDefUseAndInstrToBlockMapping};
+  std::vector<std::pair<Operand::OperandData, uint32_t>> cases;
+  for (uint32_t i = 0; i < static_cast<uint32_t>(case_block_ids.size()); ++i) {
+    cases.emplace_back(Operand::OperandData{i}, case_block_ids[i]);
+  }
+  builder.AddSwitch(access_chain_index_var_id, default_id, cases, merge_id);
+}
+
+uint32_t ReplaceDescArrayAccessUsingVarIndex::CreatePhiInstruction(
+    BasicBlock* parent_block, const std::vector<uint32_t>& phi_operands,
+    const std::vector<uint32_t>& case_block_ids,
+    uint32_t default_block_id) const {
+  std::vector<uint32_t> incomings;
+  assert(case_block_ids.size() + 1 == phi_operands.size() &&
+         "Number of Phi operands must be exactly 1 bigger than the one of case "
+         "blocks");
+  for (size_t i = 0; i < case_block_ids.size(); ++i) {
+    incomings.push_back(phi_operands[i]);
+    incomings.push_back(case_block_ids[i]);
+  }
+  incomings.push_back(phi_operands.back());
+  incomings.push_back(default_block_id);
+
+  InstructionBuilder builder{context(), &*parent_block->begin(),
+                             kAnalysisDefUseAndInstrToBlockMapping};
+  uint32_t phi_result_type_id =
+      context()->get_def_use_mgr()->GetDef(phi_operands[0])->type_id();
+  auto* phi = builder.AddPhi(phi_result_type_id, incomings);
+  return phi->result_id();
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::ReplacePhiIncomingBlock(
+    uint32_t old_incoming_block_id, uint32_t new_incoming_block_id) const {
+  context()->ReplaceAllUsesWithPredicate(
+      old_incoming_block_id, new_incoming_block_id,
+      [](Instruction* use) { return use->opcode() == SpvOpPhi; });
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/replace_desc_array_access_using_var_index.h b/source/opt/replace_desc_array_access_using_var_index.h
new file mode 100644
index 0000000..e18222c
--- /dev/null
+++ b/source/opt/replace_desc_array_access_using_var_index.h
@@ -0,0 +1,204 @@
+// Copyright (c) 2021 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_REPLACE_DESC_VAR_INDEX_ACCESS_H_
+#define SOURCE_OPT_REPLACE_DESC_VAR_INDEX_ACCESS_H_
+
+#include <cstdio>
+#include <memory>
+#include <queue>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "source/opt/function.h"
+#include "source/opt/pass.h"
+#include "source/opt/type_manager.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class ReplaceDescArrayAccessUsingVarIndex : public Pass {
+ public:
+  ReplaceDescArrayAccessUsingVarIndex() {}
+
+  const char* name() const override {
+    return "replace-desc-array-access-using-var-index";
+  }
+
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+  }
+
+ private:
+  // Replaces all acceses to |var| using variable indices with constant
+  // elements of the array |var|. Creates switch-case statements to determine
+  // the value of the variable index for all the possible cases. Returns
+  // whether replacement is done or not.
+  bool ReplaceVariableAccessesWithConstantElements(Instruction* var) const;
+
+  // Replaces the OpAccessChain or OpInBoundsAccessChain instruction |use| that
+  // uses the descriptor variable |var| with the OpAccessChain or
+  // OpInBoundsAccessChain instruction with a constant Indexes operand.
+  void ReplaceAccessChain(Instruction* var, Instruction* use) const;
+
+  // Updates the first Indexes operand of the OpAccessChain or
+  // OpInBoundsAccessChain instruction |access_chain| to let it use a constant
+  // index |const_element_idx|.
+  void UseConstIndexForAccessChain(Instruction* access_chain,
+                                   uint32_t const_element_idx) const;
+
+  // Replaces users of the OpAccessChain or OpInBoundsAccessChain instruction
+  // |access_chain| that accesses an array descriptor variable using variable
+  // indices with constant elements. |number_of_elements| is the number
+  // of array elements.
+  void ReplaceUsersOfAccessChain(Instruction* access_chain,
+                                 uint32_t number_of_elements) const;
+
+  // Puts all the recursive users of |access_chain| with concrete result types
+  // or the ones without result it in |final_users|.
+  void CollectRecursiveUsersWithConcreteType(
+      Instruction* access_chain, std::vector<Instruction*>* final_users) const;
+
+  // Recursively collects the operands of |user_of_image_insts| (and operands
+  // of the operands) whose result types are images/samplers or pointers/array/
+  // struct of them and returns them.
+  std::deque<Instruction*> CollectRequiredImageInsts(
+      Instruction* user_of_image_insts) const;
+
+  // Returns whether result type of |inst| is an image/sampler/pointer of image
+  // or sampler or not.
+  bool HasImageOrImagePtrType(const Instruction* inst) const;
+
+  // Returns whether |type_inst| is an image/sampler or pointer/array/struct of
+  // image or sampler or not.
+  bool IsImageOrImagePtrType(const Instruction* type_inst) const;
+
+  // Returns whether the type with |type_id| is a concrete type or not.
+  bool IsConcreteType(uint32_t type_id) const;
+
+  // Replaces the non-uniform access to a descriptor variable
+  // |access_chain_final_user| with OpSwitch instruction and case blocks. Each
+  // case block will contain a clone of |access_chain| and clones of
+  // |non_uniform_accesses_to_clone| that are recursively used by
+  // |access_chain_final_user|. The clone of |access_chain| (or
+  // OpInBoundsAccessChain) will have a constant index for its first index. The
+  // OpSwitch instruction will have the cases for the variable index of
+  // |access_chain| from 0 to |number_of_elements| - 1.
+  void ReplaceNonUniformAccessWithSwitchCase(
+      Instruction* access_chain_final_user, Instruction* access_chain,
+      uint32_t number_of_elements,
+      const std::deque<Instruction*>& non_uniform_accesses_to_clone) const;
+
+  // Creates and returns a new basic block that contains all instructions of
+  // |block| after |separation_begin_inst|. The new basic block is added to the
+  // function in this method.
+  BasicBlock* SeparateInstructionsIntoNewBlock(
+      BasicBlock* block, Instruction* separation_begin_inst) const;
+
+  // Creates and returns a new block.
+  BasicBlock* CreateNewBlock() const;
+
+  // Returns the first operand id of the OpAccessChain or OpInBoundsAccessChain
+  // instruction |access_chain|.
+  uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain) const;
+
+  // Adds a clone of the OpAccessChain or OpInBoundsAccessChain instruction
+  // |access_chain| to |case_block|. The clone of |access_chain| will use
+  // |const_element_idx| for its first index. |old_ids_to_new_ids| keeps the
+  // mapping from the result id of |access_chain| to the result of its clone.
+  void AddConstElementAccessToCaseBlock(
+      BasicBlock* case_block, Instruction* access_chain,
+      uint32_t const_element_idx,
+      std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const;
+
+  // Clones all instructions in |insts_to_be_cloned| and put them to |block|.
+  // |old_ids_to_new_ids| keeps the mapping from the result id of each
+  // instruction of |insts_to_be_cloned| to the result of their clones.
+  void CloneInstsToBlock(
+      BasicBlock* block, Instruction* inst_to_skip_cloning,
+      const std::deque<Instruction*>& insts_to_be_cloned,
+      std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const;
+
+  // Adds OpBranch to |branch_destination| at the end of |parent_block|.
+  void AddBranchToBlock(BasicBlock* parent_block,
+                        uint32_t branch_destination) const;
+
+  // Replaces in-operands of all instructions in the basic block |block| using
+  // |old_ids_to_new_ids|. It conducts the replacement only if the in-operand
+  // id is a key of |old_ids_to_new_ids|.
+  void UseNewIdsInBlock(
+      BasicBlock* block,
+      const std::unordered_map<uint32_t, uint32_t>& old_ids_to_new_ids) const;
+
+  // Creates a case block for |element_index| case. It adds clones of
+  // |insts_to_be_cloned| and a clone of |access_chain| with |element_index| as
+  // its first index. The termination instruction of the created case block will
+  // be a branch to |branch_target_id|. Puts old ids to new ids map for the
+  // cloned instructions in |old_ids_to_new_ids|.
+  BasicBlock* CreateCaseBlock(
+      Instruction* access_chain, uint32_t element_index,
+      const std::deque<Instruction*>& insts_to_be_cloned,
+      uint32_t branch_target_id,
+      std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const;
+
+  // Creates a default block for switch-case statement that has only a single
+  // instruction OpBranch whose target is a basic block with |merge_block_id|.
+  // If |null_const_for_phi_is_needed| is true, gets or creates a default null
+  // constant value for a phi instruction whose operands are |phi_operands| and
+  // puts it in |phi_operands|.
+  BasicBlock* CreateDefaultBlock(bool null_const_for_phi_is_needed,
+                                 std::vector<uint32_t>* phi_operands,
+                                 uint32_t merge_block_id) const;
+
+  // Creates and adds an OpSwitch used for the selection of OpAccessChain whose
+  // first Indexes operand is |access_chain_index_var_id|. The OpSwitch will be
+  // added at the end of |parent_block|. It will jump to |default_id| for the
+  // default case and jumps to one of case blocks whoes ids are |case_block_ids|
+  // if |access_chain_index_var_id| matches the case number. |merge_id| is the
+  // merge block id.
+  void AddSwitchForAccessChain(
+      BasicBlock* parent_block, uint32_t access_chain_index_var_id,
+      uint32_t default_id, uint32_t merge_id,
+      const std::vector<uint32_t>& case_block_ids) const;
+
+  // Creates a phi instruction with |phi_operands| as values and
+  // |case_block_ids| and |default_block_id| as incoming blocks. The size of
+  // |phi_operands| must be exactly 1 larger than the size of |case_block_ids|.
+  // The last element of |phi_operands| will be used for |default_block_id|. It
+  // adds the phi instruction to the beginning of |parent_block|.
+  uint32_t CreatePhiInstruction(BasicBlock* parent_block,
+                                const std::vector<uint32_t>& phi_operands,
+                                const std::vector<uint32_t>& case_block_ids,
+                                uint32_t default_block_id) const;
+
+  // Replaces the incoming block operand of OpPhi instructions with
+  // |new_incoming_block_id| if the incoming block operand is
+  // |old_incoming_block_id|.
+  void ReplacePhiIncomingBlock(uint32_t old_incoming_block_id,
+                               uint32_t new_incoming_block_id) const;
+
+  // Create an OpConstantNull instruction whose result type id is |type_id|.
+  Instruction* GetConstNull(uint32_t type_id) const;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_REPLACE_DESC_VAR_INDEX_ACCESS_H_
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 1129975..bc44e8d 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -85,6 +85,7 @@
 	   remove_unused_interface_variables_test.cpp
        register_liveness.cpp
        relax_float_ops_test.cpp
+       replace_desc_array_access_using_var_index_test.cpp
        replace_invalid_opc_test.cpp
        scalar_analysis.cpp
        scalar_replacement_test.cpp
diff --git a/test/opt/replace_desc_array_access_using_var_index_test.cpp b/test/opt/replace_desc_array_access_using_var_index_test.cpp
new file mode 100644
index 0000000..ca62581
--- /dev/null
+++ b/test/opt/replace_desc_array_access_using_var_index_test.cpp
@@ -0,0 +1,411 @@
+// Copyright (c) 2021 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 ReplaceDescArrayAccessUsingVarIndexTest = PassTest<::testing::Test>;
+
+TEST_F(ReplaceDescArrayAccessUsingVarIndexTest,
+       ReplaceAccessChainToTextureArray) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %psmain "psmain" %gl_FragCoord %in_var_INSTANCEID %out_var_SV_TARGET
+               OpExecutionMode %psmain OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_sampler "type.sampler"
+               OpName %Sampler0 "Sampler0"
+               OpName %type_2d_image "type.2d.image"
+               OpName %Tex0 "Tex0"
+               OpName %in_var_INSTANCEID "in.var.INSTANCEID"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %psmain "psmain"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %gl_FragCoord BuiltIn FragCoord
+               OpDecorate %in_var_INSTANCEID Flat
+               OpDecorate %in_var_INSTANCEID Location 0
+               OpDecorate %out_var_SV_TARGET Location 0
+               OpDecorate %Sampler0 DescriptorSet 0
+               OpDecorate %Sampler0 Binding 1
+               OpDecorate %Tex0 DescriptorSet 0
+               OpDecorate %Tex0 Binding 2
+       %bool = OpTypeBool
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+       %uint = OpTypeInt 32 0
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 0 Unknown
+%_arr_type_2d_image_uint_3 = OpTypeArray %type_2d_image %uint_3
+%_ptr_UniformConstant__arr_type_2d_image_uint_3 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_3
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Input_uint = OpTypePointer Input %uint
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+    %v2float = OpTypeVector %float 2
+     %v2uint = OpTypeVector %uint 2
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+         %27 = OpConstantComposite %v2uint %uint_0 %uint_1
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+   %Sampler0 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+       %Tex0 = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_3 UniformConstant
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%in_var_INSTANCEID = OpVariable %_ptr_Input_uint Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+     %uint_2 = OpConstant %uint 2
+         %66 = OpConstantNull %v4float
+
+; CHECK: [[null_value:%\w+]] = OpConstantNull %v4float
+
+     %psmain = OpFunction %void None %21
+         %39 = OpLabel
+         %29 = OpLoad %v4float %gl_FragCoord
+         %30 = OpLoad %uint %in_var_INSTANCEID
+         %37 = OpIEqual %bool %30 %uint_2
+               OpSelectionMerge %38 None
+               OpBranchConditional %37 %28 %40
+
+; CHECK: [[var_index:%\w+]] = OpLoad %uint %in_var_INSTANCEID
+; CHECK: OpSelectionMerge [[cond_branch_merge:%\w+]] None
+; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[bb_cond_br:%\w+]]
+
+         %28 = OpLabel
+         %31 = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %30
+         %32 = OpLoad %type_2d_image %31
+               OpImageWrite %32 %27 %29
+
+; CHECK: OpSelectionMerge [[merge:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default:%\w+]] 0 [[case0:%\w+]] 1 [[case1:%\w+]] 2 [[case2:%\w+]]
+; CHECK: [[case0]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK: OpImageWrite
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case1]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK: OpImageWrite
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case2]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK: OpImageWrite
+; CHECK: OpBranch [[merge]]
+; CHECK: [[default]] = OpLabel
+; CHECK: OpBranch [[merge]]
+; CHECK: [[merge]] = OpLabel
+
+         %33 = OpLoad %type_sampler %Sampler0
+         %34 = OpVectorShuffle %v2float %29 %29 0 1
+         %35 = OpSampledImage %type_sampled_image %32 %33
+         %36 = OpImageSampleImplicitLod %v4float %35 %34 None
+
+; CHECK: OpSelectionMerge [[merge:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default:%\w+]] 0 [[case0:%\w+]] 1 [[case1:%\w+]] 2 [[case2:%\w+]]
+; CHECK: [[case0]] = OpLabel
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0
+; CHECK: [[sam:%\w+]] = OpLoad %type_sampler %Sampler0
+; CHECK: [[img:%\w+]] = OpLoad %type_2d_image [[ac]]
+; CHECK: [[sampledImg:%\w+]] = OpSampledImage %type_sampled_image [[img]] [[sam]]
+; CHECK: [[value0:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg]]
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case1]] = OpLabel
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_1
+; CHECK: [[sam:%\w+]] = OpLoad %type_sampler %Sampler0
+; CHECK: [[img:%\w+]] = OpLoad %type_2d_image [[ac]]
+; CHECK: [[sampledImg:%\w+]] = OpSampledImage %type_sampled_image [[img]] [[sam]]
+; CHECK: [[value1:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg]]
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case2]] = OpLabel
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_2
+; CHECK: [[sam:%\w+]] = OpLoad %type_sampler %Sampler0
+; CHECK: [[img:%\w+]] = OpLoad %type_2d_image [[ac]]
+; CHECK: [[sampledImg:%\w+]] = OpSampledImage %type_sampled_image [[img]] [[sam]]
+; CHECK: [[value2:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg]]
+; CHECK: OpBranch [[merge]]
+; CHECK: [[default]] = OpLabel
+; CHECK: OpBranch [[merge]]
+; CHECK: [[merge]] = OpLabel
+; CHECK: [[phi0:%\w+]] = OpPhi %v4float [[value0]] [[case0]] [[value1]] [[case1]] [[value2]] [[case2]] [[null_value]] [[default]]
+
+               OpBranch %38
+         %40 = OpLabel
+               OpBranch %38
+         %38 = OpLabel
+         %41 = OpPhi %v4float %36 %28 %29 %40
+
+; CHECK: OpBranch [[cond_branch_merge]]
+; CHECK: [[bb_cond_br]] = OpLabel
+; CHECK: OpBranch [[cond_branch_merge]]
+; CHECK: [[cond_branch_merge]] = OpLabel
+; CHECK: [[phi1:%\w+]] = OpPhi %v4float [[phi0]] [[merge]] {{%\w+}} [[bb_cond_br]]
+; CHECK: OpStore {{%\w+}} [[phi1]]
+
+               OpStore %out_var_SV_TARGET %41
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true);
+}
+
+TEST_F(ReplaceDescArrayAccessUsingVarIndexTest,
+       ReplaceAccessChainToTextureArrayAndSamplerArray) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %psmain "psmain" %gl_FragCoord %in_var_INSTANCEID %out_var_SV_TARGET
+               OpExecutionMode %psmain OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_sampler "type.sampler"
+               OpName %Sampler0 "Sampler0"
+               OpName %type_2d_image "type.2d.image"
+               OpName %Tex0 "Tex0"
+               OpName %in_var_INSTANCEID "in.var.INSTANCEID"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %psmain "psmain"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %gl_FragCoord BuiltIn FragCoord
+               OpDecorate %in_var_INSTANCEID Flat
+               OpDecorate %in_var_INSTANCEID Location 0
+               OpDecorate %out_var_SV_TARGET Location 0
+               OpDecorate %Sampler0 DescriptorSet 0
+               OpDecorate %Sampler0 Binding 1
+               OpDecorate %Tex0 DescriptorSet 0
+               OpDecorate %Tex0 Binding 2
+%type_sampler = OpTypeSampler
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+%_arr_type_sampler_uint_2 = OpTypeArray %type_sampler %uint_2
+%_ptr_UniformConstant__arr_type_sampler_uint_2 = OpTypePointer UniformConstant %_arr_type_sampler_uint_2
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 0 Unknown
+%_arr_type_2d_image_uint_2 = OpTypeArray %type_2d_image %uint_2
+%_ptr_UniformConstant__arr_type_2d_image_uint_2 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_2
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Input_uint = OpTypePointer Input %uint
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+    %v2float = OpTypeVector %float 2
+     %v2uint = OpTypeVector %uint 2
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+         %27 = OpConstantComposite %v2uint %uint_0 %uint_1
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+   %Sampler0 = OpVariable %_ptr_UniformConstant__arr_type_sampler_uint_2 UniformConstant
+       %Tex0 = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_2 UniformConstant
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%in_var_INSTANCEID = OpVariable %_ptr_Input_uint Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+         %66 = OpConstantNull %v4float
+     %psmain = OpFunction %void None %21
+         %28 = OpLabel
+         %29 = OpLoad %v4float %gl_FragCoord
+         %30 = OpLoad %uint %in_var_INSTANCEID
+         %31 = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %30
+         %32 = OpLoad %type_2d_image %31
+               OpImageWrite %32 %27 %29
+
+; CHECK: [[null_value:%\w+]] = OpConstantNull %v4float
+
+; CHECK: [[var_index:%\w+]] = OpLoad %uint %in_var_INSTANCEID
+; CHECK: OpSelectionMerge [[merge:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default:%\w+]] 0 [[case0:%\w+]] 1 [[case1:%\w+]]
+; CHECK: [[case0]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK: OpImageWrite
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case1]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK: OpImageWrite
+; CHECK: OpBranch [[merge]]
+; CHECK: [[default]] = OpLabel
+; CHECK: OpBranch [[merge]]
+; CHECK: [[merge]] = OpLabel
+
+         %33 = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %30
+         %37 = OpLoad %type_sampler %33
+         %34 = OpVectorShuffle %v2float %29 %29 0 1
+         %35 = OpSampledImage %type_sampled_image %32 %37
+         %36 = OpImageSampleImplicitLod %v4float %35 %34 None
+
+; SPIR-V instructions to be replaced (will be killed by ADCE)
+; CHECK: OpSelectionMerge
+; CHECK: OpSwitch
+
+; CHECK: OpSelectionMerge [[merge_sampler:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default_sampler:%\w+]] 0 [[case_sampler0:%\w+]] 1 [[case_sampler1:%\w+]]
+
+; CHECK: [[case_sampler0]] = OpLabel
+; CHECK: OpSelectionMerge [[merge_texture0:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default_texture:%\w+]] 0 [[case_texture0:%\w+]] 1 [[case_texture1:%\w+]]
+; CHECK: [[case_texture0]] = OpLabel
+; CHECK: [[pt0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0
+; CHECK: [[ps0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_0
+; CHECK: [[s0:%\w+]] = OpLoad %type_sampler [[ps0]]
+; CHECK: [[t0:%\w+]] = OpLoad %type_2d_image [[pt0]]
+; CHECK: [[sampledImg0:%\w+]] = OpSampledImage %type_sampled_image [[t0]] [[s0]]
+; CHECK: [[value0:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg0]]
+; CHECK: OpBranch [[merge_texture0]]
+; CHECK: [[case_texture1]] = OpLabel
+; CHECK: [[pt1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_1
+; CHECK: [[ps0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_0
+; CHECK: [[s0:%\w+]] = OpLoad %type_sampler [[ps0]]
+; CHECK: [[t1:%\w+]] = OpLoad %type_2d_image [[pt1]]
+; CHECK: [[sampledImg1:%\w+]] = OpSampledImage %type_sampled_image [[t1]] [[s0]]
+; CHECK: [[value1:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg1]]
+; CHECK: OpBranch [[merge_texture0]]
+; CHECK: [[default_texture]] = OpLabel
+; CHECK: OpBranch [[merge_texture0]]
+; CHECK: [[merge_texture0]] = OpLabel
+; CHECK: [[phi0:%\w+]] = OpPhi %v4float [[value0]] [[case_texture0]] [[value1]] [[case_texture1]] [[null_value]] [[default_texture]]
+; CHECK: OpBranch [[merge_sampler]]
+
+; CHECK: [[case_sampler1]] = OpLabel
+; CHECK: OpSelectionMerge [[merge_texture1:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default_texture:%\w+]] 0 [[case_texture0:%\w+]] 1 [[case_texture1:%\w+]]
+; CHECK: [[case_texture0]] = OpLabel
+; CHECK: [[pt0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0
+; CHECK: [[ps1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_1
+; CHECK: [[s1:%\w+]] = OpLoad %type_sampler [[ps1]]
+; CHECK: [[t0:%\w+]] = OpLoad %type_2d_image [[pt0]]
+; CHECK: [[sampledImg0:%\w+]] = OpSampledImage %type_sampled_image [[t0]] [[s1]]
+; CHECK: [[value0:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg0]]
+; CHECK: OpBranch [[merge_texture1]]
+; CHECK: [[case_texture1]] = OpLabel
+; CHECK: [[pt1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_1
+; CHECK: [[ps1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_1
+; CHECK: [[s1:%\w+]] = OpLoad %type_sampler [[ps1]]
+; CHECK: [[t1:%\w+]] = OpLoad %type_2d_image [[pt1]]
+; CHECK: [[sampledImg1:%\w+]] = OpSampledImage %type_sampled_image [[t1]] [[s1]]
+; CHECK: [[value1:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg1]]
+; CHECK: OpBranch [[merge_texture1]]
+; CHECK: [[default_texture]] = OpLabel
+; CHECK: OpBranch [[merge_texture1]]
+; CHECK: [[merge_texture1]] = OpLabel
+; CHECK: [[phi1:%\w+]] = OpPhi %v4float [[value0]] [[case_texture0]] [[value1]] [[case_texture1]] [[null_value]] [[default_texture]]
+
+; CHECK: [[default_sampler]] = OpLabel
+; CHECK: OpBranch [[merge_sampler]]
+; CHECK: [[merge_sampler]] = OpLabel
+; CHECK: OpPhi %v4float [[phi0]] [[merge_texture0]] [[phi1]] [[merge_texture1]] [[null_value]] [[default_sampler]]
+; CHECK: OpStore
+
+               OpStore %out_var_SV_TARGET %36
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true);
+}
+
+TEST_F(ReplaceDescArrayAccessUsingVarIndexTest,
+       ReplaceAccessChainToTextureArrayWithSingleElement) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %psmain "psmain" %gl_FragCoord %in_var_INSTANCEID %out_var_SV_TARGET
+               OpExecutionMode %psmain OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_sampler "type.sampler"
+               OpName %Sampler0 "Sampler0"
+               OpName %type_2d_image "type.2d.image"
+               OpName %Tex0 "Tex0"
+               OpName %in_var_INSTANCEID "in.var.INSTANCEID"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %psmain "psmain"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %gl_FragCoord BuiltIn FragCoord
+               OpDecorate %in_var_INSTANCEID Flat
+               OpDecorate %in_var_INSTANCEID Location 0
+               OpDecorate %out_var_SV_TARGET Location 0
+               OpDecorate %Sampler0 DescriptorSet 0
+               OpDecorate %Sampler0 Binding 1
+               OpDecorate %Tex0 DescriptorSet 0
+               OpDecorate %Tex0 Binding 2
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 0 Unknown
+%_arr_type_2d_image_uint_1 = OpTypeArray %type_2d_image %uint_1
+%_ptr_UniformConstant__arr_type_2d_image_uint_1 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_1
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Input_uint = OpTypePointer Input %uint
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+    %v2float = OpTypeVector %float 2
+     %v2uint = OpTypeVector %uint 2
+     %uint_0 = OpConstant %uint 0
+         %27 = OpConstantComposite %v2uint %uint_0 %uint_1
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+   %Sampler0 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+       %Tex0 = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_1 UniformConstant
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%in_var_INSTANCEID = OpVariable %_ptr_Input_uint Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+     %uint_2 = OpConstant %uint 2
+         %66 = OpConstantNull %v4float
+     %psmain = OpFunction %void None %21
+         %28 = OpLabel
+         %29 = OpLoad %v4float %gl_FragCoord
+         %30 = OpLoad %uint %in_var_INSTANCEID
+         %31 = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %30
+         %32 = OpLoad %type_2d_image %31
+               OpImageWrite %32 %27 %29
+
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0
+; CHECK-NOT: OpAccessChain
+; CHECK-NOT: OpSwitch
+; CHECK-NOT: OpPhi
+
+         %33 = OpLoad %type_sampler %Sampler0
+         %34 = OpVectorShuffle %v2float %29 %29 0 1
+         %35 = OpSampledImage %type_sampled_image %32 %33
+         %36 = OpImageSampleImplicitLod %v4float %35 %34 None
+
+               OpStore %out_var_SV_TARGET %36
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index d5036fc..04f81b8 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -163,6 +163,11 @@
                around known issues with some Vulkan drivers for initialize
                variables.)");
   printf(R"(
+  --replace-desc-array-access-using-var-index
+               Replaces accesses to descriptor arrays based on a variable index
+               with a switch that has a case for every possible value of the
+               index.)");
+  printf(R"(
   --descriptor-scalar-replacement
                Replaces every array variable |desc| that has a DescriptorSet
                and Binding decorations with a new variable for each element of