spirv-reader: support OpCompositeInsert

This generates intermediate variable to stuff the component into,
then a constant definition to evaluate the result for later use.

Bug: tint:3
Change-Id: If2e6bb24e2b1e621c3602509eb3237c40f53897b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/41360
Auto-Submit: David Neto <dneto@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 535c502..d1c2cfc 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -3135,6 +3135,10 @@
       // Synthesize a vector insertion in its own statements.
       return MakeVectorInsertDynamic(inst);
 
+    case SpvOpCompositeInsert:
+      // Synthesize a composite insertion in its own statements.
+      return MakeCompositeInsert(inst);
+
     case SpvOpFunctionCall:
       return EmitFunctionCall(inst);
 
@@ -3307,7 +3311,6 @@
   //    OpGenericCastToPtrExplicit // Not in Vulkan
   //
   //    OpArrayLength
-  //    OpCompositeInsert
 
   return {};
 }
@@ -3577,35 +3580,68 @@
   // This is structurally similar to creating an access chain, but
   // the SPIR-V instruction has literal indices instead of IDs for indices.
 
-  // A SPIR-V composite extract is a single instruction with multiple
-  // literal indices walking down into composites. The Tint AST represents
-  // this as ever-deeper nested indexing expressions. Start off with an
-  // expression for the composite, and then bury that inside nested indexing
-  // expressions.
-  auto source = GetSourceForInst(inst);
-  TypedExpression current_expr(MakeOperand(inst, 0));
+  auto composite_index = 0;
+  auto first_index_position = 1;
+  TypedExpression current_expr(MakeOperand(inst, composite_index));
+  const auto composite_id = inst.GetSingleWordInOperand(composite_index);
+  auto current_type_id = def_use_mgr_->GetDef(composite_id)->type_id();
 
-  auto make_index = [this, source](uint32_t literal) {
+  return MakeCompositeValueDecomposition(inst, current_expr, current_type_id,
+                                         first_index_position);
+}
+
+TypedExpression FunctionEmitter::MakeCompositeValueDecomposition(
+    const spvtools::opt::Instruction& inst,
+    TypedExpression composite,
+    uint32_t composite_type_id,
+    int index_start) {
+  // This is structurally similar to creating an access chain, but
+  // the SPIR-V instruction has literal indices instead of IDs for indices.
+
+  // A SPIR-V composite extract is a single instruction with multiple
+  // literal indices walking down into composites.
+  // A SPIR-V composite insert is similar but also tells you what component
+  // to inject. This function is respnosible for the the walking-into part
+  // of composite-insert.
+  //
+  // The Tint AST represents this as ever-deeper nested indexing expressions.
+  // Start off with an expression for the composite, and then bury that inside
+  // nested indexing expressions.
+
+  auto current_expr = composite;
+  auto current_type_id = composite_type_id;
+
+  auto make_index = [this](uint32_t literal) {
     return create<ast::ScalarConstructorExpression>(
-        source, create<ast::UintLiteral>(source, u32_, literal));
+        Source{}, create<ast::UintLiteral>(Source{}, u32_, literal));
   };
 
-  const auto composite = inst.GetSingleWordInOperand(0);
-  auto current_type_id = def_use_mgr_->GetDef(composite)->type_id();
-  // Build up a nested expression for the access chain by walking down the type
+  // Build up a nested expression for the decomposition by walking down the type
   // hierarchy, maintaining |current_type_id| as the SPIR-V ID of the type of
   // the object pointed to after processing the previous indices.
   const auto num_in_operands = inst.NumInOperands();
-  for (uint32_t index = 1; index < num_in_operands; ++index) {
+  for (uint32_t index = index_start; index < num_in_operands; ++index) {
     const uint32_t index_val = inst.GetSingleWordInOperand(index);
 
     const auto* current_type_inst = def_use_mgr_->GetDef(current_type_id);
     if (!current_type_inst) {
       Fail() << "composite type %" << current_type_id
-             << " is invalid after following " << (index - 1)
+             << " is invalid after following " << (index - index_start)
              << " indices: " << inst.PrettyPrint();
       return {};
     }
+    const char* operation_name = nullptr;
+    switch (inst.opcode()) {
+      case SpvOpCompositeExtract:
+        operation_name = "OpCompositeExtract";
+        break;
+      case SpvOpCompositeInsert:
+        operation_name = "OpCompositeInsert";
+        break;
+      default:
+        Fail() << "internal error: unhandled " << inst.PrettyPrint();
+        return {};
+    }
     ast::Expression* next_expr = nullptr;
     switch (current_type_inst->opcode()) {
       case SpvOpTypeVector: {
@@ -3613,8 +3649,9 @@
         // like  "foo.z", which is more idiomatic than "foo[2]".
         const auto num_elems = current_type_inst->GetSingleWordInOperand(1);
         if (num_elems <= index_val) {
-          Fail() << "CompositeExtract %" << inst.result_id() << " index value "
-                 << index_val << " is out of bounds for vector of " << num_elems
+          Fail() << operation_name << " %" << inst.result_id()
+                 << " index value " << index_val
+                 << " is out of bounds for vector of " << num_elems
                  << " elements";
           return {};
         }
@@ -3632,8 +3669,9 @@
         // Check bounds
         const auto num_elems = current_type_inst->GetSingleWordInOperand(1);
         if (num_elems <= index_val) {
-          Fail() << "CompositeExtract %" << inst.result_id() << " index value "
-                 << index_val << " is out of bounds for matrix of " << num_elems
+          Fail() << operation_name << " %" << inst.result_id()
+                 << " index value " << index_val
+                 << " is out of bounds for matrix of " << num_elems
                  << " elements";
           return {};
         }
@@ -3657,14 +3695,16 @@
         current_type_id = current_type_inst->GetSingleWordInOperand(0);
         break;
       case SpvOpTypeRuntimeArray:
-        Fail() << "can't do OpCompositeExtract on a runtime array";
+        Fail() << "can't do " << operation_name
+               << " on a runtime array: " << inst.PrettyPrint();
         return {};
       case SpvOpTypeStruct: {
         const auto num_members = current_type_inst->NumInOperands();
         if (num_members <= index_val) {
-          Fail() << "CompositeExtract %" << inst.result_id() << " index value "
-                 << index_val << " is out of bounds for structure %"
-                 << current_type_id << " having " << num_members << " members";
+          Fail() << operation_name << " %" << inst.result_id()
+                 << " index value " << index_val
+                 << " is out of bounds for structure %" << current_type_id
+                 << " having " << num_members << " members";
           return {};
         }
         auto name = namer_.GetMemberName(current_type_id, uint32_t(index_val));
@@ -3677,8 +3717,8 @@
         break;
       }
       default:
-        Fail() << "CompositeExtract with bad type %" << current_type_id << ": "
-               << current_type_inst->PrettyPrint();
+        Fail() << operation_name << " with bad type %" << current_type_id
+               << ": " << current_type_inst->PrettyPrint();
         return {};
     }
     current_expr =
@@ -4909,6 +4949,59 @@
       {ast_type, create<ast::IdentifierExpression>(registered_temp_name)});
 }
 
+bool FunctionEmitter::MakeCompositeInsert(
+    const spvtools::opt::Instruction& inst) {
+  // For
+  //    %result = OpCompositeInsert %type %object %composite 1 2 3 ...
+  // generate statements like this:
+  //
+  //    var temp : type = composite;
+  //    temp[index].x = object;
+  //    const result : type = temp;
+  //
+  // Then use result everywhere the original SPIR-V id is used.  Using a const
+  // like this avoids constantly reloading the value many times.
+  //
+  // This technique is a combination of:
+  // - making a temporary variable and constant declaration, like  what we do
+  //   for VectorInsertDynamic, and
+  // - building up an access-chain like access like for CompositeExtract, but
+  //   on the left-hand side of the assignment.
+
+  auto* ast_type = parser_impl_.ConvertType(inst.type_id());
+  auto component = MakeOperand(inst, 0);
+  auto src_composite = MakeOperand(inst, 1);
+
+  // Synthesize the temporary variable.
+  // It doesn't correspond to a SPIR-V ID, so we don't use the ordinary
+  // API in parser_impl_.
+  auto result_name = namer_.Name(inst.result_id());
+  auto temp_name = namer_.MakeDerivedName(result_name);
+  auto registered_temp_name = builder_.Symbols().Register(temp_name);
+
+  auto* temp_var = create<ast::Variable>(
+      Source{}, registered_temp_name, ast::StorageClass::kFunction, ast_type,
+      false, src_composite.expr, ast::VariableDecorationList{});
+  AddStatement(create<ast::VariableDeclStatement>(Source{}, temp_var));
+
+  TypedExpression seed_expr{ast_type, create<ast::IdentifierExpression>(
+                                          Source{}, registered_temp_name)};
+
+  // The left-hand side of the assignment *looks* like a decomposition.
+  TypedExpression lhs =
+      MakeCompositeValueDecomposition(inst, seed_expr, inst.type_id(), 2);
+  if (!lhs.expr) {
+    return false;
+  }
+
+  AddStatement(
+      create<ast::AssignmentStatement>(Source{}, lhs.expr, component.expr));
+
+  return EmitConstDefinition(
+      inst,
+      {ast_type, create<ast::IdentifierExpression>(registered_temp_name)});
+}
+
 FunctionEmitter::FunctionDeclaration::FunctionDeclaration() = default;
 FunctionEmitter::FunctionDeclaration::~FunctionDeclaration() = default;
 
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index b562968..5ac476b 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -729,6 +729,21 @@
   /// @returns an AST expression for the instruction, or nullptr.
   TypedExpression MakeCompositeExtract(const spvtools::opt::Instruction& inst);
 
+  /// Creates an expression for indexing into a composite value.  The literal
+  /// indices that step into the value start at instruction input operand
+  /// `start_index` and run to the end of the instruction.
+  /// @param inst the original instruction
+  /// @param composite the typed expression for the composite
+  /// @param composite_type_id the SPIR-V type ID for the composite
+  /// @param index_start the index of the first operand in `inst` that is an
+  /// index into the composite type
+  /// @returns an AST expression for the decomposed composite, or {} on error
+  TypedExpression MakeCompositeValueDecomposition(
+      const spvtools::opt::Instruction& inst,
+      TypedExpression composite,
+      uint32_t composite_type_id,
+      int index_start);
+
   /// Creates an expression for OpVectorShuffle
   /// @param inst an OpVectorShuffle instruction.
   /// @returns an AST expression for the instruction, or nullptr.
@@ -911,6 +926,12 @@
   /// @returns an expression
   bool MakeVectorInsertDynamic(const spvtools::opt::Instruction& inst);
 
+  /// Generates statements for a SPIR-V OpComposite instruction.
+  /// Registers a const declaration for the result.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  bool MakeCompositeInsert(const spvtools::opt::Instruction& inst);
+
   /// Get the SPIR-V instruction for the image memory object declaration for
   /// the image operand to the given instruction.
   /// @param inst the SPIR-V instruction
diff --git a/src/reader/spirv/function_composite_test.cc b/src/reader/spirv/function_composite_test.cc
index adff130..1517c8a 100644
--- a/src/reader/spirv/function_composite_test.cc
+++ b/src/reader/spirv/function_composite_test.cc
@@ -287,7 +287,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(), Eq("CompositeExtract %1 index value 900 is out of "
+  EXPECT_THAT(p->error(), Eq("OpCompositeExtract %1 index value 900 is out of "
                              "bounds for vector of 2 elements"));
 }
 
@@ -338,7 +338,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_FALSE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(p->error(), Eq("CompositeExtract %2 index value 3 is out of "
+  EXPECT_THAT(p->error(), Eq("OpCompositeExtract %2 index value 3 is out of "
                              "bounds for matrix of 3 elements"));
 }
 
@@ -424,7 +424,8 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_FALSE(fe.EmitBody()) << p->error();
-  EXPECT_THAT(p->error(), Eq("can't do OpCompositeExtract on a runtime array"));
+  EXPECT_THAT(p->error(),
+              HasSubstr("can't do OpCompositeExtract on a runtime array: "));
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Struct) {
@@ -530,7 +531,7 @@
   ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
   FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
   EXPECT_FALSE(fe.EmitBody());
-  EXPECT_THAT(p->error(), Eq("CompositeExtract %2 index value 40 is out of "
+  EXPECT_THAT(p->error(), Eq("OpCompositeExtract %2 index value 40 is out of "
                              "bounds for structure %26 having 3 members"));
 }
 
@@ -576,6 +577,465 @@
       << ToString(p->builder(), fe.ast_body());
 }
 
+using SpvParserTest_CompositeInsert = SpvParserTest;
+
+TEST_F(SpvParserTest_CompositeInsert, Vector) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCompositeInsert %v2float %float_70 %v2float_50_60 1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto body_str = ToString(p->builder(), fe.ast_body());
+  EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
+  Variable{
+    x_1_1
+    function
+    __vec_2__f32
+    {
+      TypeConstructor[not set]{
+        __vec_2__f32
+        ScalarConstructor[not set]{50.000000}
+        ScalarConstructor[not set]{60.000000}
+      }
+    }
+  }
+}
+Assignment{
+  MemberAccessor[not set]{
+    Identifier[not set]{x_1_1}
+    Identifier[not set]{y}
+  }
+  ScalarConstructor[not set]{70.000000}
+}
+VariableDeclStatement{
+  VariableConst{
+    x_1
+    none
+    __vec_2__f32
+    {
+      Identifier[not set]{x_1_1}
+    }
+  }
+})")) << body_str;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Vector_IndexTooBigError) {
+  const auto assembly = Preamble() + R"(
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %1 = OpCompositeInsert %v2float %float_70 %v2float_50_60 900
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(), Eq("OpCompositeInsert %1 index value 900 is out of "
+                             "bounds for vector of 2 elements"));
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Matrix) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %m3v2float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %m3v2float %var
+     %2 = OpCompositeInsert %m3v2float %v2float_50_60 %1 2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto body_str = ToString(p->builder(), fe.ast_body());
+  EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
+  Variable{
+    x_2_1
+    function
+    __mat_2_3__f32
+    {
+      Identifier[not set]{x_1}
+    }
+  }
+}
+Assignment{
+  ArrayAccessor[not set]{
+    Identifier[not set]{x_2_1}
+    ScalarConstructor[not set]{2}
+  }
+  TypeConstructor[not set]{
+    __vec_2__f32
+    ScalarConstructor[not set]{50.000000}
+    ScalarConstructor[not set]{60.000000}
+  }
+}
+VariableDeclStatement{
+  VariableConst{
+    x_2
+    none
+    __mat_2_3__f32
+    {
+      Identifier[not set]{x_2_1}
+    }
+  }
+})")) << body_str;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Matrix_IndexTooBigError) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %m3v2float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %m3v2float %var
+     %2 = OpCompositeInsert %m3v2float %v2float_50_60 %1 3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_FALSE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(p->error(), Eq("OpCompositeInsert %2 index value 3 is out of "
+                             "bounds for matrix of 3 elements"));
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Matrix_Vector) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %m3v2float
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %m3v2float %var
+     %2 = OpCompositeInsert %m3v2float %v2float_50_60 %1 2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto body_str = ToString(p->builder(), fe.ast_body());
+  EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
+  Variable{
+    x_2_1
+    function
+    __mat_2_3__f32
+    {
+      Identifier[not set]{x_1}
+    }
+  }
+}
+Assignment{
+  ArrayAccessor[not set]{
+    Identifier[not set]{x_2_1}
+    ScalarConstructor[not set]{2}
+  }
+  TypeConstructor[not set]{
+    __vec_2__f32
+    ScalarConstructor[not set]{50.000000}
+    ScalarConstructor[not set]{60.000000}
+  }
+}
+VariableDeclStatement{
+  VariableConst{
+    x_2
+    none
+    __mat_2_3__f32
+    {
+      Identifier[not set]{x_2_1}
+    }
+  }
+})")) << body_str;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Array) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %a_u_5
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %a_u_5 %var
+     %2 = OpCompositeInsert %a_u_5 %uint_20 %1 3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto body_str = ToString(p->builder(), fe.ast_body());
+  EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
+  Variable{
+    x_2_1
+    function
+    __array__u32_5
+    {
+      Identifier[not set]{x_1}
+    }
+  }
+}
+Assignment{
+  ArrayAccessor[not set]{
+    Identifier[not set]{x_2_1}
+    ScalarConstructor[not set]{3}
+  }
+  ScalarConstructor[not set]{20}
+}
+VariableDeclStatement{
+  VariableConst{
+    x_2
+    none
+    __array__u32_5
+    {
+      Identifier[not set]{x_2_1}
+    }
+  }
+})")) << body_str;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, RuntimeArray_IsError) {
+  const auto assembly = Preamble() + R"(
+     %rtarr = OpTypeRuntimeArray %uint
+     %ptr = OpTypePointer Function %rtarr
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %rtarr %var
+     %2 = OpCompositeInsert %rtarr %uint_20 %1 3
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_FALSE(fe.EmitBody()) << p->error();
+  EXPECT_THAT(p->error(),
+              HasSubstr("can't do OpCompositeInsert on a runtime array: "));
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Struct) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %s_v2f_u_i
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %s_v2f_u_i %var
+     %2 = OpCompositeInsert %s_v2f_u_i %int_30 %1 2
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto body_str = ToString(p->builder(), fe.ast_body());
+  EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
+  Variable{
+    x_2_1
+    function
+    __struct_S
+    {
+      Identifier[not set]{x_1}
+    }
+  }
+}
+Assignment{
+  MemberAccessor[not set]{
+    Identifier[not set]{x_2_1}
+    Identifier[not set]{field2}
+  }
+  ScalarConstructor[not set]{30}
+}
+VariableDeclStatement{
+  VariableConst{
+    x_2
+    none
+    __struct_S
+    {
+      Identifier[not set]{x_2_1}
+    }
+  }
+})")) << body_str;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Struct_DifferOnlyInMemberName) {
+  const auto assembly =
+      R"(
+      OpMemberName %s0 0 "algo"
+      OpMemberName %s1 0 "rithm"
+)" + Preamble() +
+      R"(
+     %s0 = OpTypeStruct %uint
+     %s1 = OpTypeStruct %uint
+     %ptr0 = OpTypePointer Function %s0
+     %ptr1 = OpTypePointer Function %s1
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var0 = OpVariable %ptr0 Function
+     %var1 = OpVariable %ptr1 Function
+     %1 = OpLoad %s0 %var0
+     %2 = OpCompositeInsert %s0 %uint_10 %1 0
+     %3 = OpLoad %s1 %var1
+     %4 = OpCompositeInsert %s1 %uint_10 %3 0
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto body_str = ToString(p->builder(), fe.ast_body());
+  EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
+  Variable{
+    x_2_1
+    function
+    __struct_S_1
+    {
+      Identifier[not set]{x_1}
+    }
+  }
+}
+Assignment{
+  MemberAccessor[not set]{
+    Identifier[not set]{x_2_1}
+    Identifier[not set]{algo}
+  }
+  ScalarConstructor[not set]{10}
+}
+VariableDeclStatement{
+  VariableConst{
+    x_2
+    none
+    __struct_S_1
+    {
+      Identifier[not set]{x_2_1}
+    }
+  }
+}
+)")) << body_str;
+  EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
+  Variable{
+    x_4_1
+    function
+    __struct_S_2
+    {
+      Identifier[not set]{x_3}
+    }
+  }
+}
+Assignment{
+  MemberAccessor[not set]{
+    Identifier[not set]{x_4_1}
+    Identifier[not set]{rithm}
+  }
+  ScalarConstructor[not set]{10}
+}
+VariableDeclStatement{
+  VariableConst{
+    x_4
+    none
+    __struct_S_2
+    {
+      Identifier[not set]{x_4_1}
+    }
+  }
+})")) << body_str;
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Struct_IndexTooBigError) {
+  const auto assembly = Preamble() + R"(
+     %ptr = OpTypePointer Function %s_v2f_u_i
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %s_v2f_u_i %var
+     %2 = OpCompositeInsert %s_v2f_u_i %uint_10 %1 40
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_FALSE(fe.EmitBody());
+  EXPECT_THAT(p->error(), Eq("OpCompositeInsert %2 index value 40 is out of "
+                             "bounds for structure %26 having 3 members"));
+}
+
+TEST_F(SpvParserTest_CompositeInsert, Struct_Array_Matrix_Vector) {
+  const auto assembly = Preamble() + R"(
+     %a_mat = OpTypeArray %m3v2float %uint_3
+     %s = OpTypeStruct %uint %a_mat
+     %ptr = OpTypePointer Function %s
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %var = OpVariable %ptr Function
+     %1 = OpLoad %s %var
+     %2 = OpCompositeInsert %s %float_70 %1 1 2 0 1
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  auto body_str = ToString(p->builder(), fe.ast_body());
+  EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
+  Variable{
+    x_2_1
+    function
+    __struct_S_1
+    {
+      Identifier[not set]{x_1}
+    }
+  }
+}
+Assignment{
+  MemberAccessor[not set]{
+    ArrayAccessor[not set]{
+      ArrayAccessor[not set]{
+        MemberAccessor[not set]{
+          Identifier[not set]{x_2_1}
+          Identifier[not set]{field1}
+        }
+        ScalarConstructor[not set]{2}
+      }
+      ScalarConstructor[not set]{0}
+    }
+    Identifier[not set]{y}
+  }
+  ScalarConstructor[not set]{70.000000}
+}
+VariableDeclStatement{
+  VariableConst{
+    x_2
+    none
+    __struct_S_1
+    {
+      Identifier[not set]{x_2_1}
+    }
+  }
+})")) << body_str;
+}
+
 using SpvParserTest_CopyObject = SpvParserTest;
 
 TEST_F(SpvParserTest_CopyObject, Scalar) {