[wgsl-reader] Add stride support.

This CL adds stride support to the WGSL reader.

Bug: tint:178
Change-Id: Id6b5163438e562a371255ad3fb992d0a716543e7
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/26040
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/reader/wgsl/lexer.cc b/src/reader/wgsl/lexer.cc
index 224d30b..f38d9b7 100644
--- a/src/reader/wgsl/lexer.cc
+++ b/src/reader/wgsl/lexer.cc
@@ -563,6 +563,8 @@
     return {Token::Type::kSet, source, "set"};
   if (str == "storage_buffer")
     return {Token::Type::kStorageBuffer, source, "storage_buffer"};
+  if (str == "stride")
+    return {Token::Type::kStride, source, "stride"};
   if (str == "struct")
     return {Token::Type::kStruct, source, "struct"};
   if (str == "switch")
diff --git a/src/reader/wgsl/lexer_test.cc b/src/reader/wgsl/lexer_test.cc
index 3a19569..d8ed5ee 100644
--- a/src/reader/wgsl/lexer_test.cc
+++ b/src/reader/wgsl/lexer_test.cc
@@ -458,6 +458,7 @@
                     TokenData{"return", Token::Type::kReturn},
                     TokenData{"set", Token::Type::kSet},
                     TokenData{"storage_buffer", Token::Type::kStorageBuffer},
+                    TokenData{"stride", Token::Type::kStride},
                     TokenData{"struct", Token::Type::kStruct},
                     TokenData{"switch", Token::Type::kSwitch},
                     TokenData{"true", Token::Type::kTrue},
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 2a9b4c9..b3fca96 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -686,8 +686,10 @@
 //   | VEC3 LESS_THAN type_decl GREATER_THAN
 //   | VEC4 LESS_THAN type_decl GREATER_THAN
 //   | PTR LESS_THAN storage_class, type_decl GREATER_THAN
-//   | ARRAY LESS_THAN type_decl COMMA INT_LITERAL GREATER_THAN
-//   | ARRAY LESS_THAN type_decl GREATER_THAN
+//   | array_decoration_list? ARRAY LESS_THAN type_decl COMMA
+//          INT_LITERAL GREATER_THAN
+//   | array_decoration_list? ARRAY LESS_THAN type_decl
+//          GREATER_THAN
 //   | MAT2x2 LESS_THAN type_decl GREATER_THAN
 //   | MAT2x3 LESS_THAN type_decl GREATER_THAN
 //   | MAT2x4 LESS_THAN type_decl GREATER_THAN
@@ -730,8 +732,20 @@
   if (t.IsPtr()) {
     return type_decl_pointer(t);
   }
+
+  auto deco = array_decoration_list();
+  if (has_error()) {
+    return nullptr;
+  }
+  if (deco != 0) {
+    t = peek();
+  }
+  if (deco != 0 && !t.IsArray()) {
+    set_error(t, "found array decoration but no array");
+    return nullptr;
+  }
   if (t.IsArray()) {
-    return type_decl_array(t);
+    return type_decl_array(t, deco);
   }
   if (t.IsMat2x2() || t.IsMat2x3() || t.IsMat2x4() || t.IsMat3x2() ||
       t.IsMat3x3() || t.IsMat3x4() || t.IsMat4x2() || t.IsMat4x3() ||
@@ -815,7 +829,7 @@
       std::make_unique<ast::type::VectorType>(subtype, count));
 }
 
-ast::type::Type* ParserImpl::type_decl_array(Token t) {
+ast::type::Type* ParserImpl::type_decl_array(Token t, uint32_t stride) {
   next();  // Consume the peek
 
   t = next();
@@ -852,8 +866,50 @@
     return nullptr;
   }
 
-  return ctx_.type_mgr().Get(
-      std::make_unique<ast::type::ArrayType>(subtype, size));
+  auto ty = std::make_unique<ast::type::ArrayType>(subtype, size);
+  if (stride != 0) {
+    ty->set_array_stride(stride);
+  }
+  return ctx_.type_mgr().Get(std::move(ty));
+}
+
+// array_decoration_list
+//   : ATTR_LEFT (array_decoration COMMA)* array_decoration ATTR_RIGHT
+// array_decoration
+//   : STRIDE INT_LITERAL
+//
+// As there is currently only one decoration I'm combining these for now.
+// we can split apart later if needed.
+uint32_t ParserImpl::array_decoration_list() {
+  auto t = peek();
+  if (!t.IsAttrLeft()) {
+    return 0;
+  }
+  t = peek(1);
+  if (!t.IsStride()) {
+    return 0;
+  }
+
+  next();  // consume the peek of [[
+  next();  // consume the peek of stride
+
+  t = next();
+  if (!t.IsSintLiteral()) {
+    set_error(t, "missing value for stride decoration");
+    return 0;
+  }
+  if (t.to_i32() < 0) {
+    set_error(t, "invalid stride value: " + t.to_str());
+    return 0;
+  }
+
+  uint32_t stride = static_cast<uint32_t>(t.to_i32());
+  t = next();
+  if (!t.IsAttrRight()) {
+    set_error(t, "missing ]] for array decoration");
+    return 0;
+  }
+  return stride;
 }
 
 ast::type::Type* ParserImpl::type_decl_matrix(Token t) {
@@ -985,16 +1041,16 @@
   if (!t.IsAttrLeft())
     return ast::StructDecoration::kNone;
 
-  next();  // Consume the peek
-
-  auto deco = struct_decoration();
+  auto deco = struct_decoration(peek(1));
   if (has_error())
     return ast::StructDecoration::kNone;
   if (deco == ast::StructDecoration::kNone) {
-    set_error(peek(), "unknown struct decoration");
-    return ast::StructDecoration::kNone;
+    return deco;
   }
 
+  next();  // Consume the peek of [[
+  next();  // Consume the peek from the struct_decoration
+
   t = next();
   if (!t.IsAttrRight()) {
     set_error(t, "missing ]] for struct decoration");
@@ -1006,10 +1062,8 @@
 
 // struct_decoration
 //  : BLOCK
-ast::StructDecoration ParserImpl::struct_decoration() {
-  auto t = peek();
+ast::StructDecoration ParserImpl::struct_decoration(Token t) {
   if (t.IsBlock()) {
-    next();  // Consume the peek
     return ast::StructDecoration::kBlock;
   }
   return ast::StructDecoration::kNone;
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index f31f0b9..18f45fe 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -144,7 +144,7 @@
   ast::StructDecoration struct_decoration_decl();
   /// Parses a `struct_decoration` grammar element
   /// @returns the struct decoration or StructDecoraton::kNone if none matched
-  ast::StructDecoration struct_decoration();
+  ast::StructDecoration struct_decoration(Token t);
   /// Parses a `struct_body_decl` grammar element
   /// @returns the struct members
   ast::StructMemberList struct_body_decl();
@@ -339,7 +339,8 @@
  private:
   ast::type::Type* type_decl_pointer(Token t);
   ast::type::Type* type_decl_vector(Token t);
-  ast::type::Type* type_decl_array(Token t);
+  ast::type::Type* type_decl_array(Token t, uint32_t stride);
+  uint32_t array_decoration_list();
   ast::type::Type* type_decl_matrix(Token t);
 
   Context& ctx_;
diff --git a/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
index c48dacf..bd64e51 100644
--- a/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
@@ -35,11 +35,11 @@
   EXPECT_EQ(p->error(), "1:8: missing ]] for struct decoration");
 }
 
+// Note, this isn't an error because it could be an array decoration
 TEST_F(ParserImplTest, StructDecorationDecl_InvalidDecoration) {
   auto* p = parser("[[invalid]]");
   p->struct_decoration_decl();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(p->error(), "1:3: unknown struct decoration");
+  ASSERT_FALSE(p->has_error());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_struct_decoration_test.cc b/src/reader/wgsl/parser_impl_struct_decoration_test.cc
index 8c5f27e..21102d1 100644
--- a/src/reader/wgsl/parser_impl_struct_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decoration_test.cc
@@ -55,12 +55,9 @@
   auto params = GetParam();
   auto* p = parser(params.input);
 
-  auto deco = p->struct_decoration();
+  auto deco = p->struct_decoration(p->peek());
   ASSERT_FALSE(p->has_error());
   EXPECT_EQ(deco, params.result);
-
-  auto t = p->next();
-  EXPECT_TRUE(t.IsEof());
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplTest,
                          StructDecorationTest,
@@ -69,7 +66,7 @@
 
 TEST_F(ParserImplTest, StructDecoration_NoMatch) {
   auto* p = parser("not-a-stage");
-  auto deco = p->struct_decoration();
+  auto deco = p->struct_decoration(p->peek());
   ASSERT_EQ(deco, ast::StructDecoration::kNone);
 
   auto t = p->next();
diff --git a/src/reader/wgsl/parser_impl_type_alias_test.cc b/src/reader/wgsl/parser_impl_type_alias_test.cc
index fde357e..383a035 100644
--- a/src/reader/wgsl/parser_impl_type_alias_test.cc
+++ b/src/reader/wgsl/parser_impl_type_alias_test.cc
@@ -14,6 +14,7 @@
 
 #include "gtest/gtest.h"
 #include "src/ast/type/alias_type.h"
+#include "src/ast/type/array_type.h"
 #include "src/ast/type/i32_type.h"
 #include "src/ast/type/struct_type.h"
 #include "src/reader/wgsl/parser_impl.h"
@@ -88,6 +89,26 @@
   EXPECT_EQ(p->error(), "1:20: missing struct declaration");
 }
 
+TEST_F(ParserImplTest, TypeDecl_Struct_WithStride) {
+  auto* p = parser(
+      "type a = [[block]] struct { [[offset 0]] data: [[stride 4]] array<f32>; "
+      "}");
+  auto* t = p->type_alias();
+  ASSERT_FALSE(p->has_error());
+  ASSERT_NE(t, nullptr);
+  EXPECT_EQ(t->name(), "a");
+  ASSERT_TRUE(t->type()->IsStruct());
+
+  auto* s = t->type()->AsStruct();
+  EXPECT_EQ(s->impl()->members().size(), 1u);
+
+  const auto* ty = s->impl()->members()[0]->type();
+  ASSERT_TRUE(ty->IsArray());
+  const auto* arr = ty->AsArray();
+  EXPECT_TRUE(arr->has_array_stride());
+  EXPECT_EQ(arr->array_stride(), 4u);
+}
+
 }  // namespace
 }  // namespace wgsl
 }  // namespace reader
diff --git a/src/reader/wgsl/parser_impl_type_decl_test.cc b/src/reader/wgsl/parser_impl_type_decl_test.cc
index 1d08341..785bbdd 100644
--- a/src/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_type_decl_test.cc
@@ -389,6 +389,85 @@
   ASSERT_FALSE(a->IsRuntimeArray());
   ASSERT_EQ(a->size(), 5u);
   ASSERT_TRUE(a->type()->IsF32());
+  ASSERT_FALSE(a->has_array_stride());
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Stride) {
+  auto* p = parser("[[stride 16]] array<f32, 5>");
+  auto* t = p->type_decl();
+  ASSERT_NE(t, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t->IsArray());
+
+  auto* a = t->AsArray();
+  ASSERT_FALSE(a->IsRuntimeArray());
+  ASSERT_EQ(a->size(), 5u);
+  ASSERT_TRUE(a->type()->IsF32());
+  ASSERT_TRUE(a->has_array_stride());
+  EXPECT_EQ(a->array_stride(), 16u);
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Runtime_Stride) {
+  auto* p = parser("[[stride 16]] array<f32>");
+  auto* t = p->type_decl();
+  ASSERT_NE(t, nullptr);
+  ASSERT_FALSE(p->has_error());
+  ASSERT_TRUE(t->IsArray());
+
+  auto* a = t->AsArray();
+  ASSERT_TRUE(a->IsRuntimeArray());
+  ASSERT_TRUE(a->type()->IsF32());
+  ASSERT_TRUE(a->has_array_stride());
+  EXPECT_EQ(a->array_stride(), 16u);
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Decoration_MissingArray) {
+  auto* p = parser("[[stride 16]] f32");
+  auto* t = p->type_decl();
+  ASSERT_EQ(t, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:15: found array decoration but no array");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Decoration_MissingClosingAttr) {
+  auto* p = parser("[[stride 16 array<f32, 5>");
+  auto* t = p->type_decl();
+  ASSERT_EQ(t, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:13: missing ]] for array decoration");
+}
+
+// Note, this isn't an error because it could be a struct decoration, we just
+// don't have an array ...
+TEST_F(ParserImplTest, TypeDecl_Array_Decoration_UnknownDecoration) {
+  auto* p = parser("[[unknown 16]] array<f32, 5>");
+  auto* t = p->type_decl();
+  ASSERT_EQ(t, nullptr);
+  ASSERT_FALSE(p->has_error());
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingValue) {
+  auto* p = parser("[[stride]] array<f32, 5>");
+  auto* t = p->type_decl();
+  ASSERT_EQ(t, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:9: missing value for stride decoration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Stride_InvalidValue) {
+  auto* p = parser("[[stride invalid]] array<f32, 5>");
+  auto* t = p->type_decl();
+  ASSERT_EQ(t, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:10: missing value for stride decoration");
+}
+
+TEST_F(ParserImplTest, TypeDecl_Array_Stride_InvalidValue_Negative) {
+  auto* p = parser("[[stride -1]] array<f32, 5>");
+  auto* t = p->type_decl();
+  ASSERT_EQ(t, nullptr);
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:10: invalid stride value: -1");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Runtime) {
diff --git a/src/reader/wgsl/token.cc b/src/reader/wgsl/token.cc
index 8b82c6f..6f9672e 100644
--- a/src/reader/wgsl/token.cc
+++ b/src/reader/wgsl/token.cc
@@ -199,6 +199,8 @@
       return "set";
     case Token::Type::kStorageBuffer:
       return "storage_buffer";
+    case Token::Type::kStride:
+      return "stride";
     case Token::Type::kStruct:
       return "struct";
     case Token::Type::kSwitch:
diff --git a/src/reader/wgsl/token.h b/src/reader/wgsl/token.h
index 1232260..7fbc177 100644
--- a/src/reader/wgsl/token.h
+++ b/src/reader/wgsl/token.h
@@ -210,6 +210,8 @@
     kSet,
     /// A 'storage_buffer'
     kStorageBuffer,
+    /// A 'stride'
+    kStride,
     /// A 'struct'
     kStruct,
     /// A 'switch'
@@ -463,6 +465,8 @@
   bool IsSet() const { return type_ == Type::kSet; }
   /// @returns true if token is a 'storage_buffer'
   bool IsStorageBuffer() const { return type_ == Type::kStorageBuffer; }
+  /// @returns true if token is a 'stride'
+  bool IsStride() const { return type_ == Type::kStride; }
   /// @returns true if token is a 'struct'
   bool IsStruct() const { return type_ == Type::kStruct; }
   /// @returns true if token is a 'switch'
diff --git a/test/compute_boids.wgsl b/test/compute_boids.wgsl
index 967536d..5752620 100644
--- a/test/compute_boids.wgsl
+++ b/test/compute_boids.wgsl
@@ -57,7 +57,7 @@
 };
 
 type Particles = [[block]] struct {
-  [[offset 0]] particles : array<Particle, 5>;
+  [[offset 0]] particles : [[stride 16]] array<Particle, 5>;
 };
 
 [[binding 0, set 0]] var<uniform> params : SimParams;