reader::spirv::Parser: register user names

Bug: tint:3
Change-Id: I4391aa733079d24fb19ff2cba9f612406e63a203
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/17580
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 39156b5..a64b78b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -317,6 +317,7 @@
     reader/spirv/fail_stream_test.cc
     reader/spirv/namer_test.cc
     reader/spirv/parser_impl_import_test.cc
+    reader/spirv/parser_impl_user_name_test.cc
     reader/spirv/parser_impl_test.cc
     reader/spirv/parser_test.cc
     reader/spirv/spirv_tools_helpers_test.cc
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index f30d048..6391c15 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -19,6 +19,8 @@
 #include <utility>
 
 #include "source/opt/build_module.h"
+#include "source/opt/instruction.h"
+#include "source/opt/module.h"
 #include "spirv-tools/libspirv.hpp"
 
 namespace tint {
@@ -35,6 +37,7 @@
     : Reader(),
       spv_binary_(spv_binary),
       fail_stream_(&success_, &errors_),
+      namer_(fail_stream_),
       tools_context_(kTargetEnv),
       tools_(kTargetEnv) {
   // Create a message consumer to propagate error messages from SPIRV-Tools
@@ -120,7 +123,7 @@
 }
 
 bool ParserImpl::ParseInternalModule() {
-  return RegisterExtendedInstructionImports();
+  return RegisterExtendedInstructionImports() && RegisterUserNames();
   // TODO(dneto): fill in the rest
 }
 
@@ -146,6 +149,35 @@
   return true;
 }
 
+bool ParserImpl::RegisterUserNames() {
+  // Register names from OpName and OpMemberName
+  for (const auto& inst : module_->debugs2()) {
+    switch (inst.opcode()) {
+      case SpvOpName:
+        namer_.SuggestSanitizedName(inst.GetSingleWordInOperand(0),
+                                    inst.GetInOperand(1).AsString());
+        break;
+      case SpvOpMemberName:
+        namer_.SuggestSanitizedMemberName(inst.GetSingleWordInOperand(0),
+                                          inst.GetSingleWordInOperand(1),
+                                          inst.GetInOperand(2).AsString());
+        break;
+      default:
+        break;
+    }
+  }
+
+  // Fill in struct member names, and disambiguate them.
+  for (const auto* type_inst : module_->GetTypes()) {
+    if (type_inst->opcode() == SpvOpTypeStruct) {
+      namer_.ResolveMemberNamesForStruct(type_inst->result_id(),
+                                         type_inst->NumInOperands());
+    }
+  }
+
+  return true;
+}
+
 }  // namespace spirv
 }  // namespace reader
 }  // namespace tint
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 3c1b921..573292b 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -33,6 +33,7 @@
 #include "src/ast/module.h"
 #include "src/reader/reader.h"
 #include "src/reader/spirv/fail_stream.h"
+#include "src/reader/spirv/namer.h"
 
 namespace tint {
 namespace reader {
@@ -80,6 +81,9 @@
     return glsl_std_450_imports_;
   }
 
+  /// @returns the namer object
+  Namer& namer() { return namer_; }
+
  private:
   /// Builds the internal representation of the SPIR-V module.
   /// Assumes the module is somewhat well-formed.  Normally you
@@ -99,6 +103,11 @@
   /// Registers extended instruction imports.  Only "GLSL.std.450" is supported.
   bool RegisterExtendedInstructionImports();
 
+  /// Registers user names for SPIR-V objects, from OpName, and OpMemberName.
+  /// Also synthesizes struct field names.  Ensures uniqueness for names for
+  /// SPIR-V IDs, and uniqueness of names of fields within any single struct.
+  bool RegisterUserNames();
+
   // The SPIR-V binary we're parsing
   std::vector<uint32_t> spv_binary_;
 
@@ -112,6 +121,9 @@
   FailStream fail_stream_;
   spvtools::MessageConsumer message_consumer_;
 
+  // An object used to store and generate names for SPIR-V objects.
+  Namer namer_;
+
   // The internal representation of the SPIR-V module and its context.
   spvtools::Context tools_context_;
   spvtools::SpirvTools tools_;
diff --git a/src/reader/spirv/parser_impl_user_name_test.cc b/src/reader/spirv/parser_impl_user_name_test.cc
new file mode 100644
index 0000000..b20c021
--- /dev/null
+++ b/src/reader/spirv/parser_impl_user_name_test.cc
@@ -0,0 +1,92 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <memory>
+#include <sstream>
+
+#include "gmock/gmock.h"
+#include "src/reader/spirv/parser_impl.h"
+#include "src/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+
+using SpvParseUserNameTest = ::testing::Test;
+
+TEST_F(SpvParseUserNameTest, RespectOpName) {
+  ParserImpl p(test::Assemble(R"(
+     OpName %1 "the_void_type"
+     %1 = OpTypeVoid
+  )"));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_THAT(p.namer().GetName(1), Eq("the_void_type"));
+}
+
+TEST_F(SpvParseUserNameTest, DistinguishDuplicateSuggestion) {
+  ParserImpl p(test::Assemble(R"(
+     OpName %1 "vanilla"
+     OpName %2 "vanilla"
+     %1 = OpTypeVoid
+     %2 = OpTypeInt 32 0
+  )"));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_THAT(p.namer().GetName(1), Eq("vanilla"));
+  EXPECT_THAT(p.namer().GetName(2), Eq("vanilla_1"));
+}
+
+TEST_F(SpvParseUserNameTest, RespectOpMemberName) {
+  ParserImpl p(test::Assemble(R"(
+     OpMemberName %3 0 "strawberry"
+     OpMemberName %3 1 "vanilla"
+     OpMemberName %3 2 "chocolate"
+     %2 = OpTypeInt 32 0
+     %3 = OpTypeStruct %2 %2 %2
+  )"));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_THAT(p.namer().GetMemberName(3, 0), Eq("strawberry"));
+  EXPECT_THAT(p.namer().GetMemberName(3, 1), Eq("vanilla"));
+  EXPECT_THAT(p.namer().GetMemberName(3, 2), Eq("chocolate"));
+}
+
+TEST_F(SpvParseUserNameTest, SynthesizeMemberNames) {
+  ParserImpl p(test::Assemble(R"(
+     %2 = OpTypeInt 32 0
+     %3 = OpTypeStruct %2 %2 %2
+  )"));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_THAT(p.namer().GetMemberName(3, 0), Eq("field0"));
+  EXPECT_THAT(p.namer().GetMemberName(3, 1), Eq("field1"));
+  EXPECT_THAT(p.namer().GetMemberName(3, 2), Eq("field2"));
+}
+
+TEST_F(SpvParseUserNameTest, MemberNamesMixUserAndSynthesized) {
+  ParserImpl p(test::Assemble(R"(
+     OpMemberName %3 1 "vanilla"
+     %2 = OpTypeInt 32 0
+     %3 = OpTypeStruct %2 %2 %2
+  )"));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_THAT(p.namer().GetMemberName(3, 0), Eq("field0"));
+  EXPECT_THAT(p.namer().GetMemberName(3, 1), Eq("vanilla"));
+  EXPECT_THAT(p.namer().GetMemberName(3, 2), Eq("field2"));
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint