Add location data to input/output variable reflection

Adds this data to the Inspector API, as well as needed internal
utility methods.
Updates and expands tests to cover changes.

BUG=tint:452

Change-Id: I598f8149cb6abd13abf606416ae61e615b99e1e1
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/38200
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
diff --git a/src/ast/variable.cc b/src/ast/variable.cc
index 6ac6976..e053ffd 100644
--- a/src/ast/variable.cc
+++ b/src/ast/variable.cc
@@ -71,6 +71,15 @@
   return false;
 }
 
+LocationDecoration* Variable::GetLocationDecoration() const {
+  for (auto* deco : decorations_) {
+    if (deco->Is<LocationDecoration>()) {
+      return deco->As<LocationDecoration>();
+    }
+  }
+  return nullptr;
+}
+
 uint32_t Variable::constant_id() const {
   assert(HasConstantIdDecoration());
   for (auto* deco : decorations_) {
diff --git a/src/ast/variable.h b/src/ast/variable.h
index 134b013..88257b2 100644
--- a/src/ast/variable.h
+++ b/src/ast/variable.h
@@ -30,6 +30,8 @@
 namespace tint {
 namespace ast {
 
+class LocationDecoration;
+
 /// A Variable statement.
 ///
 /// An instance of this class represents one of three constructs in WGSL: "var"
@@ -129,6 +131,9 @@
   /// @returns true if the decorations include a ConstantIdDecoration
   bool HasConstantIdDecoration() const;
 
+  /// @returns pointer to LocationDecoration in decorations, otherwise NULL.
+  LocationDecoration* GetLocationDecoration() const;
+
   /// @returns the constant_id value for the variable. Assumes that
   /// HasConstantIdDecoration() has been called first.
   uint32_t constant_id() const;
diff --git a/src/ast/variable_test.cc b/src/ast/variable_test.cc
index e6475c1..7f19618 100644
--- a/src/ast/variable_test.cc
+++ b/src/ast/variable_test.cc
@@ -123,6 +123,10 @@
   EXPECT_TRUE(var->HasLocationDecoration());
   EXPECT_TRUE(var->HasBuiltinDecoration());
   EXPECT_TRUE(var->HasConstantIdDecoration());
+
+  auto* location = var->GetLocationDecoration();
+  ASSERT_NE(nullptr, location);
+  EXPECT_EQ(1u, location->value());
 }
 
 TEST_F(VariableTest, ConstantId) {
diff --git a/src/inspector/entry_point.h b/src/inspector/entry_point.h
index 13e0234..028f6d8 100644
--- a/src/inspector/entry_point.h
+++ b/src/inspector/entry_point.h
@@ -24,7 +24,18 @@
 namespace tint {
 namespace inspector {
 
-/// Container of reflection data for an entry point in the shader.
+/// Reflection data about an entry point input or output.
+typedef struct {
+  /// Name of the variable in the shader.
+  std::string name;
+  /// Is Location Decoration present
+  bool has_location_decoration;
+  /// Value of Location Decoration, only valid if |has_location_decoration| is
+  /// true.
+  uint32_t location_decoration;
+} StageVariable;
+
+/// Reflection data for an entry point in the shader.
 typedef struct EntryPoint {
   /// Constructors
   EntryPoint();
@@ -47,9 +58,9 @@
   /// The workgroup z size
   uint32_t workgroup_size_z;
   /// List of the input variable accessed via this entry point.
-  std::vector<std::string> input_variables;
+  std::vector<StageVariable> input_variables;
   /// List of the output variable accessed via this entry point.
-  std::vector<std::string> output_variables;
+  std::vector<StageVariable> output_variables;
 
   /// @returns the size of the workgroup in {x,y,z} format
   std::tuple<uint32_t, uint32_t, uint32_t> workgroup_size() {
diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc
index eca911d..f23e954 100644
--- a/src/inspector/inspector.cc
+++ b/src/inspector/inspector.cc
@@ -64,10 +64,20 @@
 
     for (auto* var : func->referenced_module_variables()) {
       auto name = module_.SymbolToName(var->symbol());
-      if (var->storage_class() == ast::StorageClass::kInput) {
-        entry_point.input_variables.push_back(name);
+      StageVariable stage_variable;
+      stage_variable.name = name;
+      auto* location_decoration = var->GetLocationDecoration();
+      if (location_decoration) {
+        stage_variable.has_location_decoration = true;
+        stage_variable.location_decoration = location_decoration->value();
       } else {
-        entry_point.output_variables.push_back(name);
+        stage_variable.has_location_decoration = false;
+      }
+
+      if (var->storage_class() == ast::StorageClass::kInput) {
+        entry_point.input_variables.push_back(stage_variable);
+      } else if (var->storage_class() == ast::StorageClass::kOutput) {
+        entry_point.output_variables.push_back(stage_variable);
       }
     }
     result.push_back(std::move(entry_point));
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index 0dd9ee0..6f6f76c 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -109,12 +109,19 @@
   ///                   global variables
   void AddInOutVariables(
       std::vector<std::tuple<std::string, std::string>> inout_vars) {
+    uint32_t location = 0;
     for (auto inout : inout_vars) {
       std::string in, out;
       std::tie(in, out) = inout;
 
-      mod->AddGlobalVariable(Var(in, ast::StorageClass::kInput, ty.u32));
-      mod->AddGlobalVariable(Var(out, ast::StorageClass::kOutput, ty.u32));
+      mod->AddGlobalVariable(
+          Var(in, ast::StorageClass::kInput, ty.u32, nullptr,
+              ast::VariableDecorationList{
+                  create<ast::LocationDecoration>(location++)}));
+      mod->AddGlobalVariable(
+          Var(out, ast::StorageClass::kOutput, ty.u32, nullptr,
+              ast::VariableDecorationList{
+                  create<ast::LocationDecoration>(location++)}));
     }
   }
 
@@ -214,13 +221,13 @@
     return create<ast::FloatLiteral>(type, *val);
   }
 
-  /// @param vec Vector of strings to be searched
-  /// @param str String to be searching for
-  /// @returns true if str is in vec, otherwise false
-  bool ContainsString(const std::vector<std::string>& vec,
-                      const std::string& str) {
+  /// @param vec Vector of StageVariable to be searched
+  /// @param name Name to be searching for
+  /// @returns true if name is in vec, otherwise false
+  bool ContainsName(const std::vector<StageVariable>& vec,
+                    const std::string& name) {
     for (auto& s : vec) {
-      if (s == str) {
+      if (s.name == name) {
         return true;
       }
     }
@@ -865,9 +872,13 @@
   ASSERT_EQ(1u, result.size());
 
   ASSERT_EQ(1u, result[0].input_variables.size());
-  EXPECT_EQ("in_var", result[0].input_variables[0]);
+  EXPECT_EQ("in_var", result[0].input_variables[0].name);
+  EXPECT_TRUE(result[0].input_variables[0].has_location_decoration);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_decoration);
   ASSERT_EQ(1u, result[0].output_variables.size());
-  EXPECT_EQ("out_var", result[0].output_variables[0]);
+  EXPECT_EQ("out_var", result[0].output_variables[0].name);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_decoration);
+  EXPECT_EQ(1u, result[0].output_variables[0].location_decoration);
 }
 
 TEST_F(InspectorGetEntryPointTest, FunctionInOutVariables) {
@@ -892,9 +903,13 @@
   ASSERT_EQ(1u, result.size());
 
   ASSERT_EQ(1u, result[0].input_variables.size());
-  EXPECT_EQ("in_var", result[0].input_variables[0]);
+  EXPECT_EQ("in_var", result[0].input_variables[0].name);
+  EXPECT_TRUE(result[0].input_variables[0].has_location_decoration);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_decoration);
   ASSERT_EQ(1u, result[0].output_variables.size());
-  EXPECT_EQ("out_var", result[0].output_variables[0]);
+  EXPECT_EQ("out_var", result[0].output_variables[0].name);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_decoration);
+  EXPECT_EQ(1u, result[0].output_variables[0].location_decoration);
 }
 
 TEST_F(InspectorGetEntryPointTest, RepeatedInOutVariables) {
@@ -919,9 +934,13 @@
   ASSERT_EQ(1u, result.size());
 
   ASSERT_EQ(1u, result[0].input_variables.size());
-  EXPECT_EQ("in_var", result[0].input_variables[0]);
+  EXPECT_EQ("in_var", result[0].input_variables[0].name);
+  EXPECT_TRUE(result[0].input_variables[0].has_location_decoration);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_decoration);
   ASSERT_EQ(1u, result[0].output_variables.size());
-  EXPECT_EQ("out_var", result[0].output_variables[0]);
+  EXPECT_EQ("out_var", result[0].output_variables[0].name);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_decoration);
+  EXPECT_EQ(1u, result[0].output_variables[0].location_decoration);
 }
 
 TEST_F(InspectorGetEntryPointTest, EntryPointMultipleInOutVariables) {
@@ -942,11 +961,19 @@
   ASSERT_EQ(1u, result.size());
 
   ASSERT_EQ(2u, result[0].input_variables.size());
-  EXPECT_TRUE(ContainsString(result[0].input_variables, "in_var"));
-  EXPECT_TRUE(ContainsString(result[0].input_variables, "in2_var"));
+  EXPECT_TRUE(ContainsName(result[0].input_variables, "in_var"));
+  EXPECT_TRUE(ContainsName(result[0].input_variables, "in2_var"));
+  EXPECT_TRUE(result[0].input_variables[0].has_location_decoration);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_decoration);
+  EXPECT_TRUE(result[0].input_variables[1].has_location_decoration);
+  EXPECT_EQ(2u, result[0].input_variables[1].location_decoration);
   ASSERT_EQ(2u, result[0].output_variables.size());
-  EXPECT_TRUE(ContainsString(result[0].output_variables, "out_var"));
-  EXPECT_TRUE(ContainsString(result[0].output_variables, "out2_var"));
+  EXPECT_TRUE(ContainsName(result[0].output_variables, "out_var"));
+  EXPECT_TRUE(ContainsName(result[0].output_variables, "out2_var"));
+  EXPECT_TRUE(result[0].output_variables[0].has_location_decoration);
+  EXPECT_EQ(1u, result[0].output_variables[0].location_decoration);
+  EXPECT_TRUE(result[0].output_variables[1].has_location_decoration);
+  EXPECT_EQ(3u, result[0].output_variables[1].location_decoration);
 }
 
 TEST_F(InspectorGetEntryPointTest, FunctionMultipleInOutVariables) {
@@ -971,11 +998,20 @@
   ASSERT_EQ(1u, result.size());
 
   ASSERT_EQ(2u, result[0].input_variables.size());
-  EXPECT_TRUE(ContainsString(result[0].input_variables, "in_var"));
-  EXPECT_TRUE(ContainsString(result[0].input_variables, "in2_var"));
+  EXPECT_TRUE(ContainsName(result[0].input_variables, "in_var"));
+  EXPECT_TRUE(ContainsName(result[0].input_variables, "in2_var"));
+  EXPECT_TRUE(result[0].input_variables[0].has_location_decoration);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_decoration);
+  EXPECT_TRUE(result[0].input_variables[1].has_location_decoration);
+  EXPECT_EQ(2u, result[0].input_variables[1].location_decoration);
   ASSERT_EQ(2u, result[0].output_variables.size());
-  EXPECT_TRUE(ContainsString(result[0].output_variables, "out_var"));
-  EXPECT_TRUE(ContainsString(result[0].output_variables, "out2_var"));
+  ASSERT_EQ(2u, result[0].output_variables.size());
+  EXPECT_TRUE(ContainsName(result[0].output_variables, "out_var"));
+  EXPECT_TRUE(ContainsName(result[0].output_variables, "out2_var"));
+  EXPECT_TRUE(result[0].output_variables[0].has_location_decoration);
+  EXPECT_EQ(1u, result[0].output_variables[0].location_decoration);
+  EXPECT_TRUE(result[0].output_variables[1].has_location_decoration);
+  EXPECT_EQ(3u, result[0].output_variables[1].location_decoration);
 }
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutVariables) {
@@ -1007,16 +1043,24 @@
   ASSERT_EQ("foo", result[0].name);
   ASSERT_EQ("foo", result[0].remapped_name);
   ASSERT_EQ(1u, result[0].input_variables.size());
-  EXPECT_EQ("in_var", result[0].input_variables[0]);
+  EXPECT_EQ("in_var", result[0].input_variables[0].name);
+  EXPECT_TRUE(result[0].input_variables[0].has_location_decoration);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_decoration);
   ASSERT_EQ(1u, result[0].output_variables.size());
-  EXPECT_EQ("out2_var", result[0].output_variables[0]);
+  EXPECT_EQ("out2_var", result[0].output_variables[0].name);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_decoration);
+  EXPECT_EQ(3u, result[0].output_variables[0].location_decoration);
 
   ASSERT_EQ("bar", result[1].name);
   ASSERT_EQ("bar", result[1].remapped_name);
   ASSERT_EQ(1u, result[1].input_variables.size());
-  EXPECT_EQ("in2_var", result[1].input_variables[0]);
+  EXPECT_EQ("in2_var", result[1].input_variables[0].name);
+  EXPECT_TRUE(result[1].input_variables[0].has_location_decoration);
+  EXPECT_EQ(2u, result[1].input_variables[0].location_decoration);
   ASSERT_EQ(1u, result[1].output_variables.size());
-  EXPECT_EQ("out_var", result[1].output_variables[0]);
+  EXPECT_EQ("out_var", result[1].output_variables[0].name);
+  EXPECT_TRUE(result[1].output_variables[0].has_location_decoration);
+  EXPECT_EQ(1u, result[1].output_variables[0].location_decoration);
 }
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsSharedInOutVariables) {
@@ -1052,18 +1096,32 @@
   ASSERT_EQ("foo", result[0].name);
   ASSERT_EQ("foo", result[0].remapped_name);
   EXPECT_EQ(2u, result[0].input_variables.size());
-  EXPECT_TRUE(ContainsString(result[0].input_variables, "in_var"));
-  EXPECT_TRUE(ContainsString(result[0].input_variables, "in2_var"));
+  EXPECT_TRUE(ContainsName(result[0].input_variables, "in_var"));
+  EXPECT_TRUE(ContainsName(result[0].input_variables, "in2_var"));
+  EXPECT_TRUE(result[0].input_variables[0].has_location_decoration);
+  EXPECT_EQ(0u, result[0].input_variables[0].location_decoration);
+  EXPECT_TRUE(result[0].input_variables[1].has_location_decoration);
+  EXPECT_EQ(2u, result[0].input_variables[1].location_decoration);
+
   EXPECT_EQ(2u, result[0].output_variables.size());
-  EXPECT_TRUE(ContainsString(result[0].output_variables, "out_var"));
-  EXPECT_TRUE(ContainsString(result[0].output_variables, "out2_var"));
+  EXPECT_TRUE(ContainsName(result[0].output_variables, "out_var"));
+  EXPECT_TRUE(ContainsName(result[0].output_variables, "out2_var"));
+  EXPECT_TRUE(result[0].output_variables[0].has_location_decoration);
+  EXPECT_EQ(1u, result[0].output_variables[0].location_decoration);
+  EXPECT_TRUE(result[0].output_variables[0].has_location_decoration);
+  EXPECT_EQ(3u, result[0].output_variables[1].location_decoration);
 
   ASSERT_EQ("bar", result[1].name);
   ASSERT_EQ("bar", result[1].remapped_name);
   EXPECT_EQ(1u, result[1].input_variables.size());
-  EXPECT_EQ("in2_var", result[1].input_variables[0]);
+  EXPECT_EQ("in2_var", result[1].input_variables[0].name);
+  EXPECT_TRUE(result[1].input_variables[0].has_location_decoration);
+  EXPECT_EQ(2u, result[1].input_variables[0].location_decoration);
+
   EXPECT_EQ(1u, result[1].output_variables.size());
-  EXPECT_EQ("out2_var", result[1].output_variables[0]);
+  EXPECT_EQ("out2_var", result[1].output_variables[0].name);
+  EXPECT_TRUE(result[1].output_variables[0].has_location_decoration);
+  EXPECT_EQ(3u, result[1].output_variables[0].location_decoration);
 }
 
 // TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass