[reader-spirv] Convert array, runtime array types

Bug: tint:3
Change-Id: If0d7d38cc777bce3d86dfd83669c1572331d4ed6
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/17800
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 1f6b6d4..f0fe50f 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -15,6 +15,7 @@
 #include "src/reader/spirv/parser_impl.h"
 
 #include <cstring>
+#include <limits>
 #include <memory>
 #include <string>
 #include <utility>
@@ -24,6 +25,7 @@
 #include "source/opt/module.h"
 #include "source/opt/type_manager.h"
 #include "spirv-tools/libspirv.hpp"
+#include "src/ast/type/array_type.h"
 #include "src/ast/type/bool_type.h"
 #include "src/ast/type/f32_type.h"
 #include "src/ast/type/i32_type.h"
@@ -46,6 +48,7 @@
 
 ParserImpl::ParserImpl(Context* ctx, const std::vector<uint32_t>& spv_binary)
     : Reader(ctx),
+      ctx_(ctx),
       spv_binary_(spv_binary),
       fail_stream_(&success_, &errors_),
       namer_(fail_stream_),
@@ -180,6 +183,56 @@
       // In the error case, we'll already have emitted a diagnostic.
       break;
     }
+    case spvtools::opt::analysis::Type::kRuntimeArray: {
+      const auto* rtarr_ty = spirv_type->AsRuntimeArray();
+      auto* ast_elem_ty =
+          ConvertType(type_mgr_->GetId(rtarr_ty->element_type()));
+      if (ast_elem_ty != nullptr) {
+        result = ctx_.type_mgr->Get(
+            std::make_unique<ast::type::ArrayType>(ast_elem_ty));
+      }
+      // In the error case, we'll already have emitted a diagnostic.
+      break;
+    }
+    case spvtools::opt::analysis::Type::kArray: {
+      const auto* arr_ty = spirv_type->AsArray();
+      auto* ast_elem_ty = ConvertType(type_mgr_->GetId(arr_ty->element_type()));
+      if (ast_elem_ty == nullptr) {
+        // In the error case, we'll already have emitted a diagnostic.
+        break;
+      }
+      const auto& length_info = arr_ty->length_info();
+      if (length_info.words.empty()) {
+        // The internal representation is invalid. The discriminant vector
+        // is mal-formed.
+        Fail() << "internal error: Array length info is invalid";
+        return nullptr;
+      }
+      if (length_info.words[0] !=
+          spvtools::opt::analysis::Array::LengthInfo::kConstant) {
+        Fail() << "Array type " << type_id
+               << " length is a specialization constant";
+        return nullptr;
+      }
+      const auto* constant =
+          constant_mgr_->FindDeclaredConstant(length_info.id);
+      if (constant == nullptr) {
+        Fail() << "Array type " << type_id << " length ID " << length_info.id
+               << " does not name an OpConstant";
+        return nullptr;
+      }
+      const uint64_t num_elem = constant->GetZeroExtendedValue();
+      // For now, limit to only 32bits.
+      if (num_elem > std::numeric_limits<uint32_t>::max()) {
+        Fail() << "Array type " << type_id
+               << " has too many elements (more than can fit in 32 bits): "
+               << num_elem;
+        return nullptr;
+      }
+      result = ctx_.type_mgr->Get(std::make_unique<ast::type::ArrayType>(
+          ast_elem_ty, static_cast<uint32_t>(num_elem)));
+      break;
+    }
     default:
       // The error diagnostic will be generated below because result is still
       // nullptr.
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index a422cb6..9c4fa58 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -122,6 +122,8 @@
   /// Emit entry point AST nodes.
   bool EmitEntryPoints();
 
+  // The Tint context
+  Context ctx_;
   // The SPIR-V binary we're parsing
   std::vector<uint32_t> spv_binary_;
 
diff --git a/src/reader/spirv/parser_impl_convert_type_test.cc b/src/reader/spirv/parser_impl_convert_type_test.cc
index 4a0dfdf..72c6fc3 100644
--- a/src/reader/spirv/parser_impl_convert_type_test.cc
+++ b/src/reader/spirv/parser_impl_convert_type_test.cc
@@ -17,6 +17,7 @@
 #include <vector>
 
 #include "gmock/gmock.h"
+#include "src/ast/type/array_type.h"
 #include "src/ast/type/matrix_type.h"
 #include "src/ast/type/vector_type.h"
 #include "src/reader/spirv/parser_impl.h"
@@ -324,6 +325,96 @@
   EXPECT_TRUE(p->error().empty());
 }
 
+TEST_F(SpvParserTest, ConvertType_RuntimeArray) {
+  auto p = parser(test::Assemble(R"(
+    %uint = OpTypeInt 32 0
+    %10 = OpTypeRuntimeArray %uint
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_NE(type, nullptr);
+  EXPECT_TRUE(type->IsArray());
+  auto* arr_type = type->AsArray();
+  EXPECT_TRUE(arr_type->IsRuntimeArray());
+  ASSERT_NE(arr_type, nullptr);
+  ASSERT_EQ(arr_type->size(), 0u);
+  auto* elem_type = arr_type->type();
+  ASSERT_NE(elem_type, nullptr);
+  EXPECT_TRUE(elem_type->IsU32());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_Array) {
+  auto p = parser(test::Assemble(R"(
+    %uint = OpTypeInt 32 0
+    %uint_42 = OpConstant %uint 42
+    %10 = OpTypeArray %uint %uint_42
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_NE(type, nullptr);
+  EXPECT_TRUE(type->IsArray());
+  auto* arr_type = type->AsArray();
+  EXPECT_FALSE(arr_type->IsRuntimeArray());
+  ASSERT_NE(arr_type, nullptr);
+  ASSERT_EQ(arr_type->size(), 42u);
+  auto* elem_type = arr_type->type();
+  ASSERT_NE(elem_type, nullptr);
+  EXPECT_TRUE(elem_type->IsU32());
+  EXPECT_TRUE(p->error().empty());
+}
+
+TEST_F(SpvParserTest, ConvertType_ArrayBadLengthIsSpecConstantValue) {
+  auto p = parser(test::Assemble(R"(
+    OpDecorate %uint_42 SpecId 12
+    %uint = OpTypeInt 32 0
+    %uint_42 = OpSpecConstant %uint 42
+    %10 = OpTypeArray %uint %uint_42
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(),
+              Eq("Array type 10 length is a specialization constant"));
+}
+
+TEST_F(SpvParserTest, ConvertType_ArrayBadLengthIsSpecConstantExpr) {
+  auto p = parser(test::Assemble(R"(
+    %uint = OpTypeInt 32 0
+    %uint_42 = OpConstant %uint 42
+    %sum = OpSpecConstantOp %uint IAdd %uint_42 %uint_42
+    %10 = OpTypeArray %uint %sum
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_EQ(type, nullptr);
+  EXPECT_THAT(p->error(),
+              Eq("Array type 10 length is a specialization constant"));
+}
+
+// TODO(dneto): Maybe add a test where the length operand is not a constant.
+// E.g. it's the ID of a type.  That won't validate, and the SPIRV-Tools
+// optimizer representation doesn't handle it and asserts out instead.
+
+TEST_F(SpvParserTest, ConvertType_ArrayBadTooBig) {
+  auto p = parser(test::Assemble(R"(
+    %uint64 = OpTypeInt 64 0
+    %uint64_big = OpConstant %uint64 5000000000
+    %10 = OpTypeArray %uint64 %uint64_big
+  )"));
+  EXPECT_TRUE(p->BuildAndParseInternalModule());
+
+  auto* type = p->ConvertType(10);
+  ASSERT_EQ(type, nullptr);
+  // TODO(dneto): Right now it's rejected earlier in the flow because
+  // we can't even utter the uint64 type.
+  EXPECT_THAT(p->error(), Eq("unhandled integer width: 64"));
+}
+
 }  // namespace
 }  // namespace spirv
 }  // namespace reader
diff --git a/src/reader/spirv/parser_impl_test_helper.h b/src/reader/spirv/parser_impl_test_helper.h
index 97ac096..8c40a2b 100644
--- a/src/reader/spirv/parser_impl_test_helper.h
+++ b/src/reader/spirv/parser_impl_test_helper.h
@@ -42,8 +42,8 @@
   }
 
   /// Retrieves the parser from the helper
-  /// @param input the string to parse
-  /// @returns the parser implementation
+  /// @param input the SPIR-V binary to parse
+  /// @returns a parser for the given binary
   ParserImpl* parser(const std::vector<uint32_t>& input) {
     impl_ = std::make_unique<ParserImpl>(&ctx_, input);
     return impl_.get();