spirv-reader: support OpArrayLength

Fixed: tint:431
Change-Id: I727ca8200118e0b93b42c5f7d9e97bc4afd97830
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/36460
Commit-Queue: David Neto <dneto@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Auto-Submit: David Neto <dneto@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 0e88128..b72b55a 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -3143,6 +3143,10 @@
     return MakeSimpleSelect(inst);
   }
 
+  if (opcode == SpvOpArrayLength) {
+    return MakeArrayLength(inst);
+  }
+
   // builtin readonly function
   // glsl.std.450 readonly function
 
@@ -4490,6 +4494,45 @@
   return ToI32(value);
 }
 
+TypedExpression FunctionEmitter::MakeArrayLength(
+    const spvtools::opt::Instruction& inst) {
+  if (inst.NumInOperands() != 2) {
+    // Binary parsing will fail on this anyway.
+    Fail() << "invalid array length: requires 2 operands: "
+           << inst.PrettyPrint();
+    return {};
+  }
+  const auto struct_ptr_id = inst.GetSingleWordInOperand(0);
+  const auto field_index = inst.GetSingleWordInOperand(1);
+  const auto struct_ptr_type_id =
+      def_use_mgr_->GetDef(struct_ptr_id)->type_id();
+  // Trace through the pointer type to get to the struct type.
+  const auto struct_type_id =
+      def_use_mgr_->GetDef(struct_ptr_type_id)->GetSingleWordInOperand(1);
+  const auto field_name = namer_.GetMemberName(struct_type_id, field_index);
+  if (field_name.empty()) {
+    Fail() << "struct index out of bounds for array length: "
+           << inst.PrettyPrint();
+  }
+
+  auto* member_ident = create<ast::IdentifierExpression>(
+      Source{}, ast_module_.RegisterSymbol(field_name), field_name);
+  auto* member_access = create<ast::MemberAccessorExpression>(
+      Source{}, MakeExpression(struct_ptr_id).expr, member_ident);
+
+  // Generate the intrinsic function call.
+  std::string call_ident_str = "arrayLength";
+  auto* call_ident = create<ast::IdentifierExpression>(
+      Source{}, ast_module_.RegisterSymbol(call_ident_str), call_ident_str);
+  call_ident->set_intrinsic(ast::Intrinsic::kArrayLength);
+
+  ast::ExpressionList params{member_access};
+  auto* call_expr =
+      create<ast::CallExpression>(Source{}, call_ident, std::move(params));
+
+  return {parser_impl_.ConvertType(inst.type_id()), call_expr};
+}
+
 FunctionEmitter::FunctionDeclaration::FunctionDeclaration() = default;
 FunctionEmitter::FunctionDeclaration::~FunctionDeclaration() = default;
 
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index c82fa91..c27cde5 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -834,6 +834,11 @@
   /// @returns an expression
   TypedExpression MakeIntrinsicCall(const spvtools::opt::Instruction& inst);
 
+  /// Returns an expression for a SPIR-V OpArrayLength instruction.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  TypedExpression MakeArrayLength(const spvtools::opt::Instruction& inst);
+
   /// Emits a texture builtin function call for a SPIR-V instruction that
   /// accesses an image or sampled image.
   /// @param inst the SPIR-V instruction
diff --git a/src/reader/spirv/function_memory_test.cc b/src/reader/spirv/function_memory_test.cc
index 733e4db..37dede2 100644
--- a/src/reader/spirv/function_memory_test.cc
+++ b/src/reader/spirv/function_memory_test.cc
@@ -1011,6 +1011,69 @@
   // TODO(dneto): Blocked on OpFunctionCall support.
 }
 
+std::string RuntimeArrayPreamble() {
+  return R"(
+     OpName %myvar "myvar"
+     OpMemberName %struct 0 "first"
+     OpMemberName %struct 1 "rtarr"
+
+     OpDecorate %struct Block
+     OpMemberDecorate %struct 0 Offset 0
+     OpMemberDecorate %struct 1 Offset 4
+     OpDecorate %arr ArrayStride 4
+
+     %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+     %uint = OpTypeInt 32 0
+
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+
+     %arr = OpTypeRuntimeArray %uint
+     %struct = OpTypeStruct %uint %arr
+     %ptr_struct = OpTypePointer StorageBuffer %struct
+     %ptr_uint = OpTypePointer StorageBuffer %uint
+
+     %myvar = OpVariable %ptr_struct StorageBuffer
+  )";
+}
+
+TEST_F(SpvParserTest, ArrayLength) {
+  const auto assembly = RuntimeArrayPreamble() + R"(
+
+  %100 = OpFunction %void None %voidfn
+
+  %entry = OpLabel
+  %1 = OpArrayLength %uint %myvar 1
+  OpReturn
+  OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error();
+  FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+  EXPECT_TRUE(fe.EmitBody()) << p->error();
+  const auto body_str = ToString(p->get_module(), fe.ast_body());
+  EXPECT_THAT(body_str, HasSubstr(R"(VariableDeclStatement{
+  VariableConst{
+    x_1
+    none
+    __u32
+    {
+      Call[not set]{
+        Identifier[not set]{arrayLength}
+        (
+          MemberAccessor[not set]{
+            Identifier[not set]{myvar}
+            Identifier[not set]{rtarr}
+          }
+        )
+      }
+    }
+  }
+}
+)")) << body_str;
+}
+
 }  // namespace
 }  // namespace spirv
 }  // namespace reader
diff --git a/src/reader/spirv/namer.h b/src/reader/spirv/namer.h
index ff36925..715e3d0 100644
--- a/src/reader/spirv/namer.h
+++ b/src/reader/spirv/namer.h
@@ -78,7 +78,7 @@
   /// Gets the registered name for a struct member. If no name has
   /// been registered for this member, then returns the empty string.
   /// member index is in bounds.
-  /// @param id the SPIR-V ID of the struct
+  /// @param id the SPIR-V ID of the struct type
   /// @param member_index the index of the member, counting from 0
   /// @returns the registered name for the ID, or an empty string if
   /// nothing has been registered.