Squashed 'third_party/SPIRV-Tools/' changes from 1a7f71afb..597631b69

597631b69 spirv-opt: Handle null CompositeInsert (#4998)
81ec2aaa0 Add option to ADCE to remove output variables from interface. (#4994)
46ca66e69 Add support for tesc, tese and geom to EliminateDead*Components (#4990)

git-subtree-dir: third_party/SPIRV-Tools
git-subtree-split: 597631b6934397c5f2ed221e04d6ebe451ba0361
Change-Id: Ibb74f6ea53f18869eeea215370232678e6276ef4
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 41752d6..aa6a614 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -521,8 +521,12 @@
 // interface are considered live and are not eliminated. This mode is needed
 // by GPU-Assisted validation instrumentation, where a change in the interface
 // is not allowed.
-Optimizer::PassToken CreateAggressiveDCEPass();
-Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface);
+//
+// If |remove_outputs| is true, allow outputs to be removed from the interface.
+// This is only safe if the caller knows that there is no corresponding input
+// variable in the following shader. It is false by default.
+Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface = false,
+                                             bool remove_outputs = false);
 
 // Creates a remove-unused-interface-variables pass.
 // Removes variables referenced on the |OpEntryPoint| instruction that are not
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index f68032d..53d13f1 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -579,8 +579,10 @@
         auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
         auto storage_class = var->GetSingleWordInOperand(0u);
         // Vulkan support outputs without an associated input, but not inputs
-        // without an associated output.
-        if (spv::StorageClass(storage_class) == spv::StorageClass::Output) {
+        // without an associated output. Don't remove outputs unless explicitly
+        // allowed.
+        if (!remove_outputs_ &&
+            spv::StorageClass(storage_class) == spv::StorageClass::Output) {
           AddToWorklist(var);
         }
       }
diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h
index 99c4739..fbe08ad 100644
--- a/source/opt/aggressive_dead_code_elim_pass.h
+++ b/source/opt/aggressive_dead_code_elim_pass.h
@@ -44,8 +44,10 @@
   using GetBlocksFunction =
       std::function<std::vector<BasicBlock*>*(const BasicBlock*)>;
 
-  AggressiveDCEPass(bool preserve_interface = false)
-      : preserve_interface_(preserve_interface) {}
+  AggressiveDCEPass(bool preserve_interface = false,
+                    bool remove_outputs = false)
+      : preserve_interface_(preserve_interface),
+        remove_outputs_(remove_outputs) {}
 
   const char* name() const override { return "eliminate-dead-code-aggressive"; }
   Status Process() override;
@@ -63,6 +65,11 @@
   // is not allowed.
   bool preserve_interface_;
 
+  // Output variables can be removed from the interface if this is true.
+  // This is safe if the caller knows that the corresponding input variable
+  // in the following shader has been removed. It is false by default.
+  bool remove_outputs_;
+
   // Return true if |varId| is a variable of |storageClass|. |varId| must either
   // be 0 or the result of an instruction.
   bool IsVarOfStorage(uint32_t varId, spv::StorageClass storageClass);
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index e91455e..19b39d6 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -140,6 +140,12 @@
     // Work down hierarchy and add all the indexes, not including the final
     // index.
     for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
+      if (composite->AsNullConstant()) {
+        // Return Null for the return type.
+        analysis::TypeManager* type_mgr = context->get_type_mgr();
+        return const_mgr->GetConstant(type_mgr->GetType(inst->type_id()), {});
+      }
+
       if (i != inst->NumInOperands() - 1) {
         chain.push_back(composite);
       }
diff --git a/source/opt/eliminate_dead_input_components_pass.cpp b/source/opt/eliminate_dead_input_components_pass.cpp
index 637a33b..fa019ac 100644
--- a/source/opt/eliminate_dead_input_components_pass.cpp
+++ b/source/opt/eliminate_dead_input_components_pass.cpp
@@ -28,17 +28,26 @@
 namespace {
 constexpr uint32_t kAccessChainBaseInIdx = 0;
 constexpr uint32_t kAccessChainIndex0InIdx = 1;
+constexpr uint32_t kAccessChainIndex1InIdx = 2;
 constexpr uint32_t kConstantValueInIdx = 0;
 }  // namespace
 
 Pass::Status EliminateDeadInputComponentsPass::Process() {
   // Process non-vertex only if explicitly allowed.
-  auto stage = context()->GetStage();
+  const auto stage = context()->GetStage();
   if (stage != spv::ExecutionModel::Vertex && vertex_shader_only_)
     return Status::SuccessWithoutChange;
   // Current functionality assumes shader capability.
   if (!context()->get_feature_mgr()->HasCapability(spv::Capability::Shader))
     return Status::SuccessWithoutChange;
+  // Current functionality assumes vert, frag, tesc, tese or geom shader.
+  // TODO(issue #4988): Add GLCompute.
+  if (stage != spv::ExecutionModel::Vertex &&
+      stage != spv::ExecutionModel::Fragment &&
+      stage != spv::ExecutionModel::TessellationControl &&
+      stage != spv::ExecutionModel::TessellationEvaluation &&
+      stage != spv::ExecutionModel::Geometry)
+    return Status::SuccessWithoutChange;
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
   analysis::TypeManager* type_mgr = context()->get_type_mgr();
   bool modified = false;
@@ -52,23 +61,39 @@
     if (ptr_type == nullptr) {
       continue;
     }
+    const auto sclass = ptr_type->storage_class();
     if (output_instead_) {
-      if (ptr_type->storage_class() != spv::StorageClass::Output) {
+      if (sclass != spv::StorageClass::Output) {
         continue;
       }
     } else {
-      if (ptr_type->storage_class() != spv::StorageClass::Input) {
+      if (sclass != spv::StorageClass::Input) {
         continue;
       }
     }
-    const analysis::Array* arr_type = ptr_type->pointee_type()->AsArray();
+    // For tesc, or input variables in tese or geom shaders,
+    // there is a outer per-vertex-array that must be ignored
+    // for the purposes of this analysis/optimization. Do the
+    // analysis on the inner type in these cases.
+    bool skip_first_index = false;
+    auto core_type = ptr_type->pointee_type();
+    if (stage == spv::ExecutionModel::TessellationControl ||
+        (sclass == spv::StorageClass::Input &&
+         (stage == spv::ExecutionModel::TessellationEvaluation ||
+          stage == spv::ExecutionModel::Geometry))) {
+      auto arr_type = core_type->AsArray();
+      if (!arr_type) continue;
+      core_type = arr_type->element_type();
+      skip_first_index = true;
+    }
+    const analysis::Array* arr_type = core_type->AsArray();
     if (arr_type != nullptr) {
       // Only process array if input of vertex shader, or output of
       // fragment shader. Otherwise, if one shader has a runtime index and the
       // other does not, interface incompatibility can occur.
-      if (!((ptr_type->storage_class() == spv::StorageClass::Input &&
+      if (!((sclass == spv::StorageClass::Input &&
              stage == spv::ExecutionModel::Vertex) ||
-            (ptr_type->storage_class() == spv::StorageClass::Output &&
+            (sclass == spv::StorageClass::Output &&
              stage == spv::ExecutionModel::Fragment)))
         continue;
       unsigned arr_len_id = arr_type->LengthId();
@@ -88,13 +113,13 @@
       }
       continue;
     }
-    const analysis::Struct* struct_type = ptr_type->pointee_type()->AsStruct();
+    const analysis::Struct* struct_type = core_type->AsStruct();
     if (struct_type == nullptr) continue;
     const auto elt_types = struct_type->element_types();
     unsigned original_max = static_cast<unsigned>(elt_types.size()) - 1;
-    unsigned max_idx = FindMaxIndex(var, original_max);
+    unsigned max_idx = FindMaxIndex(var, original_max, skip_first_index);
     if (max_idx != original_max) {
-      ChangeStructLength(var, max_idx + 1);
+      ChangeIOVarStructLength(var, max_idx + 1);
       vars_to_move.push_back(&var);
       modified = true;
     }
@@ -112,13 +137,15 @@
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
-unsigned EliminateDeadInputComponentsPass::FindMaxIndex(Instruction& var,
-                                                        unsigned original_max) {
+unsigned EliminateDeadInputComponentsPass::FindMaxIndex(
+    const Instruction& var, const unsigned original_max,
+    const bool skip_first_index) {
   unsigned max = 0;
   bool seen_non_const_ac = false;
   assert(var.opcode() == spv::Op::OpVariable && "must be variable");
   context()->get_def_use_mgr()->WhileEachUser(
-      var.result_id(), [&max, &seen_non_const_ac, var, this](Instruction* use) {
+      var.result_id(), [&max, &seen_non_const_ac, var, skip_first_index,
+                        this](Instruction* use) {
         auto use_opcode = use->opcode();
         if (use_opcode == spv::Op::OpLoad || use_opcode == spv::Op::OpStore ||
             use_opcode == spv::Op::OpCopyMemory ||
@@ -132,13 +159,17 @@
           return true;
         }
         // OpAccessChain with no indices currently not optimized
-        if (use->NumInOperands() == 1) {
+        if (use->NumInOperands() == 1 ||
+            (skip_first_index && use->NumInOperands() == 2)) {
           seen_non_const_ac = true;
           return false;
         }
-        unsigned base_id = use->GetSingleWordInOperand(kAccessChainBaseInIdx);
+        const unsigned base_id =
+            use->GetSingleWordInOperand(kAccessChainBaseInIdx);
         USE_ASSERT(base_id == var.result_id() && "unexpected base");
-        unsigned idx_id = use->GetSingleWordInOperand(kAccessChainIndex0InIdx);
+        const unsigned in_idx = skip_first_index ? kAccessChainIndex1InIdx
+                                                 : kAccessChainIndex0InIdx;
+        const unsigned idx_id = use->GetSingleWordInOperand(in_idx);
         Instruction* idx_inst = context()->get_def_use_mgr()->GetDef(idx_id);
         if (idx_inst->opcode() != spv::Op::OpConstant) {
           seen_non_const_ac = true;
@@ -171,12 +202,17 @@
   def_use_mgr->AnalyzeInstUse(&arr_var);
 }
 
-void EliminateDeadInputComponentsPass::ChangeStructLength(
-    Instruction& struct_var, unsigned length) {
+void EliminateDeadInputComponentsPass::ChangeIOVarStructLength(
+    Instruction& io_var, unsigned length) {
   analysis::TypeManager* type_mgr = context()->get_type_mgr();
   analysis::Pointer* ptr_type =
-      type_mgr->GetType(struct_var.type_id())->AsPointer();
-  const analysis::Struct* struct_ty = ptr_type->pointee_type()->AsStruct();
+      type_mgr->GetType(io_var.type_id())->AsPointer();
+  auto core_type = ptr_type->pointee_type();
+  // Check for per-vertex-array of struct from tesc, tese and geom and grab
+  // embedded struct type.
+  const auto arr_type = core_type->AsArray();
+  if (arr_type) core_type = arr_type->element_type();
+  const analysis::Struct* struct_ty = core_type->AsStruct();
   assert(struct_ty && "expecting struct type");
   const auto orig_elt_types = struct_ty->element_types();
   std::vector<const analysis::Type*> new_elt_types;
@@ -194,14 +230,19 @@
     }
     type_mgr->AttachDecoration(*dec, &new_struct_ty);
   }
-  analysis::Type* reg_new_struct_ty =
-      type_mgr->GetRegisteredType(&new_struct_ty);
-  analysis::Pointer new_ptr_ty(reg_new_struct_ty, ptr_type->storage_class());
+  analysis::Type* reg_new_var_ty = type_mgr->GetRegisteredType(&new_struct_ty);
+  if (arr_type) {
+    analysis::Array new_arr_ty(reg_new_var_ty, arr_type->length_info());
+    reg_new_var_ty = type_mgr->GetRegisteredType(&new_arr_ty);
+  }
+  auto sclass =
+      output_instead_ ? spv::StorageClass::Output : spv::StorageClass::Input;
+  analysis::Pointer new_ptr_ty(reg_new_var_ty, sclass);
   analysis::Type* reg_new_ptr_ty = type_mgr->GetRegisteredType(&new_ptr_ty);
   uint32_t new_ptr_ty_id = type_mgr->GetTypeInstruction(reg_new_ptr_ty);
-  struct_var.SetResultType(new_ptr_ty_id);
+  io_var.SetResultType(new_ptr_ty_id);
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
-  def_use_mgr->AnalyzeInstUse(&struct_var);
+  def_use_mgr->AnalyzeInstUse(&io_var);
 }
 
 }  // namespace opt
diff --git a/source/opt/eliminate_dead_input_components_pass.h b/source/opt/eliminate_dead_input_components_pass.h
index 111366e..bdfdc21 100644
--- a/source/opt/eliminate_dead_input_components_pass.h
+++ b/source/opt/eliminate_dead_input_components_pass.h
@@ -52,13 +52,15 @@
   // Find the max constant used to index the variable declared by |var|
   // through OpAccessChain or OpInBoundsAccessChain. If any non-constant
   // indices or non-Op*AccessChain use of |var|, return |original_max|.
-  unsigned FindMaxIndex(Instruction& var, unsigned original_max);
+  unsigned FindMaxIndex(const Instruction& var, const unsigned original_max,
+                        const bool skip_first_index = false);
 
   // Change the length of the array |inst| to |length|
   void ChangeArrayLength(Instruction& inst, unsigned length);
 
-  // Change the length of the struct |struct_var| to |length|
-  void ChangeStructLength(Instruction& struct_var, unsigned length);
+  // Change the length of the struct in |io_var| to |length|. |io_var|
+  // is either the struct or a per-vertex-array of the struct.
+  void ChangeIOVarStructLength(Instruction& io_var, unsigned length);
 
   // Process output variables instead
   bool output_instead_;
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 4cf3292..8828c70 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -785,14 +785,10 @@
       MakeUnique<opt::SSARewritePass>());
 }
 
-Optimizer::PassToken CreateAggressiveDCEPass() {
+Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface,
+                                             bool remove_outputs) {
   return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::AggressiveDCEPass>(false));
-}
-
-Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface) {
-  return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::AggressiveDCEPass>(preserve_interface));
+      MakeUnique<opt::AggressiveDCEPass>(preserve_interface, remove_outputs));
 }
 
 Optimizer::PassToken CreateRemoveUnusedInterfaceVariablesPass() {
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index e51098e..0d94151 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -7777,6 +7777,86 @@
   SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
 }
 
+TEST_F(AggressiveDCETest, RemoveOutputTrue) {
+  // Remove dead n_out output variable from module
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %c_out %c_in %n_out
+;CHECK: OpEntryPoint Vertex %main "main" %c_out %c_in
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %c_out "c_out"
+               OpName %c_in "c_in"
+               OpName %n_out "n_out"
+               OpDecorate %c_out Location 0
+               OpDecorate %c_in Location 0
+               OpDecorate %n_out Location 1
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %c_out = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+       %c_in = OpVariable %_ptr_Input_v4float Input
+    %v3float = OpTypeVector %float 3
+%_ptr_Output_v3float = OpTypePointer Output %v3float
+      %n_out = OpVariable %_ptr_Output_v3float Output
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %12 = OpLoad %v4float %c_in
+               OpStore %c_out %12
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<AggressiveDCEPass>(text, true, false, true);
+}
+
+TEST_F(AggressiveDCETest, RemoveOutputFalse) {
+  // Remove dead n_out output variable from module
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %c_out %c_in %n_out
+;CHECK: OpEntryPoint Vertex %main "main" %c_out %c_in %n_out
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %c_out "c_out"
+               OpName %c_in "c_in"
+               OpName %n_out "n_out"
+               OpDecorate %c_out Location 0
+               OpDecorate %c_in Location 0
+               OpDecorate %n_out Location 1
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %c_out = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+       %c_in = OpVariable %_ptr_Input_v4float Input
+    %v3float = OpTypeVector %float 3
+%_ptr_Output_v3float = OpTypePointer Output %v3float
+      %n_out = OpVariable %_ptr_Output_v3float Output
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %12 = OpLoad %v4float %c_in
+               OpStore %c_out %12
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<AggressiveDCEPass>(text, true, false, false);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/eliminate_dead_input_components_test.cpp b/test/opt/eliminate_dead_input_components_test.cpp
index 667350d..48bda22 100644
--- a/test/opt/eliminate_dead_input_components_test.cpp
+++ b/test/opt/eliminate_dead_input_components_test.cpp
@@ -686,6 +686,569 @@
                                                           true);
 }
 
+TEST_F(ElimDeadInputComponentsTest, TescInput) {
+  // Eliminate PointSize, ClipDistance, CullDistance from gl_in[]
+  //
+  // #version 450
+  //
+  // layout (vertices = 4) out;
+  //
+  // void main()
+  // {
+  //     vec4 pos = gl_in[gl_InvocationID].gl_Position;
+  //     gl_out[gl_InvocationID].gl_Position = pos;
+  // }
+  const std::string text = R"(
+               OpCapability Tessellation
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TessellationControl %main "main" %gl_in %gl_InvocationID %gl_out
+               OpExecutionMode %main OutputVertices 4
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %pos "pos"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpMemberName %gl_PerVertex 1 "gl_PointSize"
+               OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
+               OpMemberName %gl_PerVertex 3 "gl_CullDistance"
+               OpName %gl_in "gl_in"
+               OpName %gl_InvocationID "gl_InvocationID"
+               OpName %gl_PerVertex_0 "gl_PerVertex"
+               OpMemberName %gl_PerVertex_0 0 "gl_Position"
+               OpName %gl_out "gl_out"
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
+               OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
+               OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
+               OpDecorate %gl_PerVertex Block
+               OpDecorate %gl_InvocationID BuiltIn InvocationId
+               OpMemberDecorate %gl_PerVertex_0 0 BuiltIn Position
+               OpDecorate %gl_PerVertex_0 Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+    %uint_32 = OpConstant %uint 32
+%_arr_gl_PerVertex_uint_32 = OpTypeArray %gl_PerVertex %uint_32
+%_ptr_Input__arr_gl_PerVertex_uint_32 = OpTypePointer Input %_arr_gl_PerVertex_uint_32
+      %gl_in = OpVariable %_ptr_Input__arr_gl_PerVertex_uint_32 Input
+        %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_InvocationID = OpVariable %_ptr_Input_int Input
+      %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_PerVertex_0 = OpTypeStruct %v4float
+     %uint_4 = OpConstant %uint 4
+%_arr_gl_PerVertex_0_uint_4 = OpTypeArray %gl_PerVertex_0 %uint_4
+%_ptr_Output__arr_gl_PerVertex_0_uint_4 = OpTypePointer Output %_arr_gl_PerVertex_0_uint_4
+     %gl_out = OpVariable %_ptr_Output__arr_gl_PerVertex_0_uint_4 Output
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+; CHECK: %gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+; CHECK: [[sty:%\w+]] = OpTypeStruct %v4float
+; CHECK: [[asty:%\w+]] = OpTypeArray [[sty]] %uint_32
+; CHECK: [[pasty:%\w+]] = OpTypePointer Input [[asty]]
+; CHECK: %gl_in = OpVariable [[pasty]] Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+        %pos = OpVariable %_ptr_Function_v4float Function
+         %21 = OpLoad %int %gl_InvocationID
+         %24 = OpAccessChain %_ptr_Input_v4float %gl_in %21 %int_0
+         %25 = OpLoad %v4float %24
+               OpStore %pos %25
+         %31 = OpLoad %int %gl_InvocationID
+         %32 = OpLoad %v4float %pos
+         %34 = OpAccessChain %_ptr_Output_v4float %gl_out %31 %int_0
+               OpStore %34 %32
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true, false,
+                                                          false);
+}
+
+TEST_F(ElimDeadInputComponentsTest, TescOutput) {
+  // Eliminate PointSize, ClipDistance, CullDistance from gl_out[]
+  //
+  // #version 450
+  //
+  // layout (vertices = 4) out;
+  //
+  // void main()
+  // {
+  //     vec4 pos = gl_in[gl_InvocationID].gl_Position;
+  //     gl_out[gl_InvocationID].gl_Position = pos;
+  // }
+  const std::string text = R"(
+               OpCapability Tessellation
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TessellationControl %main "main" %gl_in %gl_InvocationID %gl_out
+               OpExecutionMode %main OutputVertices 4
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %pos "pos"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpName %gl_in "gl_in"
+               OpName %gl_InvocationID "gl_InvocationID"
+               OpName %gl_PerVertex_0 "gl_PerVertex"
+               OpMemberName %gl_PerVertex_0 0 "gl_Position"
+               OpMemberName %gl_PerVertex_0 1 "gl_PointSize"
+               OpMemberName %gl_PerVertex_0 2 "gl_ClipDistance"
+               OpMemberName %gl_PerVertex_0 3 "gl_CullDistance"
+               OpName %gl_out "gl_out"
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpDecorate %gl_PerVertex Block
+               OpDecorate %gl_InvocationID BuiltIn InvocationId
+               OpMemberDecorate %gl_PerVertex_0 0 BuiltIn Position
+               OpMemberDecorate %gl_PerVertex_0 1 BuiltIn PointSize
+               OpMemberDecorate %gl_PerVertex_0 2 BuiltIn ClipDistance
+               OpMemberDecorate %gl_PerVertex_0 3 BuiltIn CullDistance
+               OpDecorate %gl_PerVertex_0 Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float 
+    %uint_32 = OpConstant %uint 32
+%_arr_gl_PerVertex_uint_32 = OpTypeArray %gl_PerVertex %uint_32
+%_ptr_Input__arr_gl_PerVertex_uint_32 = OpTypePointer Input %_arr_gl_PerVertex_uint_32
+      %gl_in = OpVariable %_ptr_Input__arr_gl_PerVertex_uint_32 Input
+        %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_InvocationID = OpVariable %_ptr_Input_int Input
+      %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_PerVertex_0 = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+     %uint_4 = OpConstant %uint 4
+%_arr_gl_PerVertex_0_uint_4 = OpTypeArray %gl_PerVertex_0 %uint_4
+%_ptr_Output__arr_gl_PerVertex_0_uint_4 = OpTypePointer Output %_arr_gl_PerVertex_0_uint_4
+     %gl_out = OpVariable %_ptr_Output__arr_gl_PerVertex_0_uint_4 Output
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+; CHECK: [[sty:%\w+]] = OpTypeStruct %v4float
+; CHECK: [[asty:%\w+]] = OpTypeArray [[sty]] %uint_4
+; CHECK: [[pasty:%\w+]] = OpTypePointer Output [[asty]]
+; CHECK: %gl_out = OpVariable [[pasty]] Output
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+        %pos = OpVariable %_ptr_Function_v4float Function
+         %21 = OpLoad %int %gl_InvocationID
+         %24 = OpAccessChain %_ptr_Input_v4float %gl_in %21 %int_0
+         %25 = OpLoad %v4float %24
+               OpStore %pos %25
+         %31 = OpLoad %int %gl_InvocationID
+         %32 = OpLoad %v4float %pos
+         %34 = OpAccessChain %_ptr_Output_v4float %gl_out %31 %int_0
+               OpStore %34 %32
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true, true,
+                                                          false);
+}
+
+TEST_F(ElimDeadInputComponentsTest, TeseInput) {
+  // Eliminate PointSize, ClipDistance, CullDistance from gl_in[]
+  //
+  // #version 450
+  //
+  // layout(triangles, ccw) in;
+  // layout(fractional_odd_spacing) in;
+  // layout(point_mode) in;
+  //
+  // void main()
+  // {
+  //     vec4 p = gl_in[1].gl_Position;
+  //     gl_Position = p;
+  // }
+  const std::string text = R"(
+               OpCapability Tessellation
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TessellationEvaluation %main "main" %gl_in %_
+               OpExecutionMode %main Triangles
+               OpExecutionMode %main SpacingFractionalOdd
+               OpExecutionMode %main VertexOrderCcw
+               OpExecutionMode %main PointMode
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %p "p"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpMemberName %gl_PerVertex 1 "gl_PointSize"
+               OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
+               OpMemberName %gl_PerVertex 3 "gl_CullDistance"
+               OpName %gl_in "gl_in"
+               OpName %gl_PerVertex_0 "gl_PerVertex"
+               OpMemberName %gl_PerVertex_0 0 "gl_Position"
+               OpName %_ ""
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
+               OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
+               OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
+               OpDecorate %gl_PerVertex Block
+               OpMemberDecorate %gl_PerVertex_0 0 BuiltIn Position
+               OpDecorate %gl_PerVertex_0 Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+    %uint_32 = OpConstant %uint 32
+%_arr_gl_PerVertex_uint_32 = OpTypeArray %gl_PerVertex %uint_32
+%_ptr_Input__arr_gl_PerVertex_uint_32 = OpTypePointer Input %_arr_gl_PerVertex_uint_32
+      %gl_in = OpVariable %_ptr_Input__arr_gl_PerVertex_uint_32 Input
+        %int = OpTypeInt 32 1
+      %int_1 = OpConstant %int 1
+      %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_PerVertex_0 = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex_0 = OpTypePointer Output %gl_PerVertex_0
+          %_ = OpVariable %_ptr_Output_gl_PerVertex_0 Output
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+; CHECK: %gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+; CHECK: [[sty:%\w+]] = OpTypeStruct %v4float
+; CHECK: [[asty:%\w+]] = OpTypeArray [[sty]] %uint_32
+; CHECK: [[pasty:%\w+]] = OpTypePointer Input [[asty]]
+; CHECK: %gl_in = OpVariable [[pasty]] Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %p = OpVariable %_ptr_Function_v4float Function
+         %22 = OpAccessChain %_ptr_Input_v4float %gl_in %int_1 %int_0
+         %23 = OpLoad %v4float %22
+               OpStore %p %23
+         %27 = OpLoad %v4float %p
+         %29 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+               OpStore %29 %27
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true, false,
+                                                          false);
+}
+
+TEST_F(ElimDeadInputComponentsTest, TeseOutput) {
+  // Eliminate PointSize, ClipDistance, CullDistance from gl_out
+  //
+  // #version 450
+  //
+  // layout(triangles, ccw) in;
+  // layout(fractional_odd_spacing) in;
+  // layout(point_mode) in;
+  //
+  // void main()
+  // {
+  //     vec4 p = gl_in[1].gl_Position;
+  //     gl_Position = p;
+  // }
+  const std::string text = R"(
+               OpCapability Tessellation
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TessellationEvaluation %main "main" %gl_in %_
+               OpExecutionMode %main Triangles
+               OpExecutionMode %main SpacingFractionalOdd
+               OpExecutionMode %main VertexOrderCcw
+               OpExecutionMode %main PointMode
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %p "p"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpName %gl_in "gl_in"
+               OpName %gl_PerVertex_0 "gl_PerVertex"
+               OpMemberName %gl_PerVertex_0 0 "gl_Position"
+               OpMemberName %gl_PerVertex_0 1 "gl_PointSize"
+               OpMemberName %gl_PerVertex_0 2 "gl_ClipDistance"
+               OpMemberName %gl_PerVertex_0 3 "gl_CullDistance"
+               OpName %_ ""
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpDecorate %gl_PerVertex Block
+               OpMemberDecorate %gl_PerVertex_0 0 BuiltIn Position
+               OpMemberDecorate %gl_PerVertex_0 1 BuiltIn PointSize
+               OpMemberDecorate %gl_PerVertex_0 2 BuiltIn ClipDistance
+               OpMemberDecorate %gl_PerVertex_0 3 BuiltIn CullDistance
+               OpDecorate %gl_PerVertex_0 Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float
+    %uint_32 = OpConstant %uint 32
+%_arr_gl_PerVertex_uint_32 = OpTypeArray %gl_PerVertex %uint_32
+%_ptr_Input__arr_gl_PerVertex_uint_32 = OpTypePointer Input %_arr_gl_PerVertex_uint_32
+      %gl_in = OpVariable %_ptr_Input__arr_gl_PerVertex_uint_32 Input
+        %int = OpTypeInt 32 1
+      %int_1 = OpConstant %int 1
+      %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_PerVertex_0 = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+%_ptr_Output_gl_PerVertex_0 = OpTypePointer Output %gl_PerVertex_0
+          %_ = OpVariable %_ptr_Output_gl_PerVertex_0 Output
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+; CHECK: %_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+; CHECK: %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %p = OpVariable %_ptr_Function_v4float Function
+         %22 = OpAccessChain %_ptr_Input_v4float %gl_in %int_1 %int_0
+         %23 = OpLoad %v4float %22
+               OpStore %p %23
+         %27 = OpLoad %v4float %p
+         %29 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+               OpStore %29 %27
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true, true,
+                                                          false);
+}
+
+TEST_F(ElimDeadInputComponentsTest, GeomInput) {
+  // Eliminate PointSize, ClipDistance, CullDistance from gl_in[]
+  //
+  // #version 450
+  //
+  // layout(triangle_strip, max_vertices = 3) out;
+  // layout(triangles) in;
+  //
+  // void main()
+  // {
+  //         for (int i = 0; i < 3; i++)
+  //         {
+  //                 gl_Position = gl_in[i].gl_Position;
+  //                 EmitVertex();
+  //         }
+  //         EndPrimitive();
+  // }
+  const std::string text = R"(
+               OpCapability Geometry
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Geometry %main "main" %_ %gl_in
+               OpExecutionMode %main Triangles
+               OpExecutionMode %main Invocations 1
+               OpExecutionMode %main OutputTriangleStrip
+               OpExecutionMode %main OutputVertices 3
+               OpSource GLSL 460
+               OpName %main "main"
+               OpName %i "i"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpName %_ ""
+               OpName %gl_PerVertex_0 "gl_PerVertex"
+               OpMemberName %gl_PerVertex_0 0 "gl_Position"
+               OpMemberName %gl_PerVertex_0 1 "gl_PointSize"
+               OpMemberName %gl_PerVertex_0 2 "gl_ClipDistance"
+               OpMemberName %gl_PerVertex_0 3 "gl_CullDistance"
+               OpName %gl_in "gl_in"
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpDecorate %gl_PerVertex Block
+               OpMemberDecorate %gl_PerVertex_0 0 BuiltIn Position
+               OpMemberDecorate %gl_PerVertex_0 1 BuiltIn PointSize
+               OpMemberDecorate %gl_PerVertex_0 2 BuiltIn ClipDistance
+               OpMemberDecorate %gl_PerVertex_0 3 BuiltIn CullDistance
+               OpDecorate %gl_PerVertex_0 Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+      %int_0 = OpConstant %int 0
+      %int_3 = OpConstant %int 3
+       %bool = OpTypeBool
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+          %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+%gl_PerVertex_0 = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+     %uint_3 = OpConstant %uint 3
+%_arr_gl_PerVertex_0_uint_3 = OpTypeArray %gl_PerVertex_0 %uint_3
+%_ptr_Input__arr_gl_PerVertex_0_uint_3 = OpTypePointer Input %_arr_gl_PerVertex_0_uint_3
+      %gl_in = OpVariable %_ptr_Input__arr_gl_PerVertex_0_uint_3 Input
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %int_1 = OpConstant %int 1
+; CHECK: [[asty:%\w+]] = OpTypeArray %gl_PerVertex %uint_3
+; CHECK: [[pasty:%\w+]] = OpTypePointer Input [[asty]]
+; CHECK: %gl_in = OpVariable [[pasty]] Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %i = OpVariable %_ptr_Function_int Function
+               OpStore %i %int_0
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %int %i
+         %18 = OpSLessThan %bool %15 %int_3
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %32 = OpLoad %int %i
+         %34 = OpAccessChain %_ptr_Input_v4float %gl_in %32 %int_0
+         %35 = OpLoad %v4float %34
+         %37 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+               OpStore %37 %35
+               OpEmitVertex
+               OpBranch %13
+         %13 = OpLabel
+         %38 = OpLoad %int %i
+         %40 = OpIAdd %int %38 %int_1
+               OpStore %i %40
+               OpBranch %10
+         %12 = OpLabel
+               OpEndPrimitive
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true, false,
+                                                          false);
+}
+
+TEST_F(ElimDeadInputComponentsTest, GeomOutput) {
+  // Eliminate PointSize, ClipDistance, CullDistance from gl_out
+  //
+  // #version 450
+  //
+  // layout(triangle_strip, max_vertices = 3) out;
+  // layout(triangles) in;
+  //
+  // void main()
+  // {
+  //         for (int i = 0; i < 3; i++)
+  //         {
+  //                 gl_Position = gl_in[i].gl_Position;
+  //                 EmitVertex();
+  //         }
+  //         EndPrimitive();
+  // }
+  const std::string text = R"(
+               OpCapability Geometry
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Geometry %main "main" %_ %gl_in
+               OpExecutionMode %main Triangles
+               OpExecutionMode %main Invocations 1
+               OpExecutionMode %main OutputTriangleStrip
+               OpExecutionMode %main OutputVertices 3
+               OpSource GLSL 460
+               OpName %main "main"
+               OpName %i "i"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpMemberName %gl_PerVertex 1 "gl_PointSize"
+               OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
+               OpMemberName %gl_PerVertex 3 "gl_CullDistance"
+               OpName %_ ""
+               OpName %gl_PerVertex_0 "gl_PerVertex"
+               OpMemberName %gl_PerVertex_0 0 "gl_Position"
+               OpName %gl_in "gl_in"
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
+               OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
+               OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
+               OpDecorate %gl_PerVertex Block
+               OpMemberDecorate %gl_PerVertex_0 0 BuiltIn Position
+               OpDecorate %gl_PerVertex_0 Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+      %int_0 = OpConstant %int 0
+      %int_3 = OpConstant %int 3
+       %bool = OpTypeBool
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+          %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+%gl_PerVertex_0 = OpTypeStruct %v4float
+     %uint_3 = OpConstant %uint 3
+%_arr_gl_PerVertex_0_uint_3 = OpTypeArray %gl_PerVertex_0 %uint_3
+%_ptr_Input__arr_gl_PerVertex_0_uint_3 = OpTypePointer Input %_arr_gl_PerVertex_0_uint_3
+      %gl_in = OpVariable %_ptr_Input__arr_gl_PerVertex_0_uint_3 Input
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %int_1 = OpConstant %int 1
+; CHECK: %_ptr_Output_gl_PerVertex_0 = OpTypePointer Output %gl_PerVertex_0
+; CHECK: %_ = OpVariable %_ptr_Output_gl_PerVertex_0 Output
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %i = OpVariable %_ptr_Function_int Function
+               OpStore %i %int_0
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %int %i
+         %18 = OpSLessThan %bool %15 %int_3
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %32 = OpLoad %int %i
+         %34 = OpAccessChain %_ptr_Input_v4float %gl_in %32 %int_0
+         %35 = OpLoad %v4float %34
+         %37 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+               OpStore %37 %35
+               OpEmitVertex
+               OpBranch %13
+         %13 = OpLabel
+         %38 = OpLoad %int %i
+         %40 = OpIAdd %int %38 %int_1
+               OpStore %i %40
+               OpBranch %10
+         %12 = OpLabel
+               OpEndPrimitive
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true, true,
+                                                          false);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/fold_spec_const_op_composite_test.cpp b/test/opt/fold_spec_const_op_composite_test.cpp
index e2374c5..aae9eb2 100644
--- a/test/opt/fold_spec_const_op_composite_test.cpp
+++ b/test/opt/fold_spec_const_op_composite_test.cpp
@@ -374,6 +374,33 @@
   SinglePassRunAndMatch<FoldSpecConstantOpAndCompositePass>(test, false);
 }
 
+TEST_F(FoldSpecConstantOpAndCompositePassBasicTest, CompositeInsertNull) {
+  const std::string test =
+      R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+%null = OpConstantNull %mat2v2float
+    %float_1 = OpConstant %float 1
+  %v2float_1 = OpConstantComposite %v2float %float_1 %float_1
+   %mat2v2_1 = OpConstantComposite %mat2v2float %v2float_1 %v2float_1
+ ; CHECK: %13 = OpConstantNull %mat2v2float
+         %14 = OpSpecConstantOp %mat2v2float CompositeInsert %mat2v2_1 %null 0 0
+          %1 = OpFunction %void None %3
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FoldSpecConstantOpAndCompositePass>(test, false);
+}
+
 // All types and some common constants that are potentially required in
 // FoldSpecConstantOpAndCompositeTest.
 std::vector<std::string> CommonTypesAndConstants() {