readers::spirv: Emit entry points

Bug: tint:3
Change-Id: I66b99ad6ecb3f409f9df7bfa9aa6c4da65e3f66b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/17582
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0fcbb43..b459584 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -319,6 +319,7 @@
     reader/spirv/enum_converter_test.cc
     reader/spirv/fail_stream_test.cc
     reader/spirv/namer_test.cc
+    reader/spirv/parser_impl_entry_point_test.cc
     reader/spirv/parser_impl_import_test.cc
     reader/spirv/parser_impl_user_name_test.cc
     reader/spirv/parser_impl_test.cc
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 6391c15..429ceba 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -38,6 +38,7 @@
       spv_binary_(spv_binary),
       fail_stream_(&success_, &errors_),
       namer_(fail_stream_),
+      enum_converter_(fail_stream_),
       tools_context_(kTargetEnv),
       tools_(kTargetEnv) {
   // Create a message consumer to propagate error messages from SPIRV-Tools
@@ -123,8 +124,20 @@
 }
 
 bool ParserImpl::ParseInternalModule() {
-  return RegisterExtendedInstructionImports() && RegisterUserNames();
+  if (!success_) {
+    return false;
+  };
+  if (!RegisterExtendedInstructionImports()) {
+    return false;
+  }
+  if (!RegisterUserNames()) {
+    return false;
+  }
+  if (!EmitEntryPoints()) {
+    return false;
+  }
   // TODO(dneto): fill in the rest
+  return true;
 }
 
 bool ParserImpl::RegisterExtendedInstructionImports() {
@@ -150,6 +163,16 @@
 }
 
 bool ParserImpl::RegisterUserNames() {
+  // Register entry point names. An entry point name is the point of contact
+  // between the API and the shader. It has the highest priority for
+  // preservation, so register it first.
+  for (const spvtools::opt::Instruction& entry_point :
+       module_->entry_points()) {
+    const uint32_t function_id = entry_point.GetSingleWordInOperand(1);
+    const std::string name = entry_point.GetInOperand(2).AsString();
+    namer_.SuggestSanitizedName(function_id, name);
+  }
+
   // Register names from OpName and OpMemberName
   for (const auto& inst : module_->debugs2()) {
     switch (inst.opcode()) {
@@ -178,6 +201,20 @@
   return true;
 }
 
+bool ParserImpl::EmitEntryPoints() {
+  for (const spvtools::opt::Instruction& entry_point :
+       module_->entry_points()) {
+    const auto stage = SpvExecutionModel(entry_point.GetSingleWordInOperand(0));
+    const uint32_t function_id = entry_point.GetSingleWordInOperand(1);
+    const std::string name = namer_.GetName(function_id);
+
+    ast_module_.AddEntryPoint(std::make_unique<ast::EntryPoint>(
+        enum_converter_.ToPipelineStage(stage), "", name));
+  }
+  // The enum conversion could have failed, so return the existing status value.
+  return success_;
+}
+
 }  // namespace spirv
 }  // namespace reader
 }  // namespace tint
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 573292b..67f1d25 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -32,6 +32,7 @@
 #include "src/ast/import.h"
 #include "src/ast/module.h"
 #include "src/reader/reader.h"
+#include "src/reader/spirv/enum_converter.h"
 #include "src/reader/spirv/fail_stream.h"
 #include "src/reader/spirv/namer.h"
 
@@ -108,6 +109,9 @@
   /// SPIR-V IDs, and uniqueness of names of fields within any single struct.
   bool RegisterUserNames();
 
+  /// Emit entry point AST nodes.
+  bool EmitEntryPoints();
+
   // The SPIR-V binary we're parsing
   std::vector<uint32_t> spv_binary_;
 
@@ -123,6 +127,8 @@
 
   // An object used to store and generate names for SPIR-V objects.
   Namer namer_;
+  // An object used to convert SPIR-V enums to Tint enums
+  EnumConverter enum_converter_;
 
   // The internal representation of the SPIR-V module and its context.
   spvtools::Context tools_context_;
diff --git a/src/reader/spirv/parser_impl_entry_point_test.cc b/src/reader/spirv/parser_impl_entry_point_test.cc
new file mode 100644
index 0000000..0e772a4
--- /dev/null
+++ b/src/reader/spirv/parser_impl_entry_point_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 <string>
+
+#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::HasSubstr;
+
+using SpvParseImport = ::testing::Test;
+
+std::string MakeEntryPoint(const std::string& stage,
+                           const std::string& name,
+                           const std::string& id = "42") {
+  return std::string("OpEntryPoint ") + stage + " %" + id + "2 \"" + name +
+         "\"\n";
+}
+
+TEST_F(SpvParseImport, NoEntryPoint) {
+  ParserImpl p(test::Assemble(""));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_TRUE(p.error().empty());
+  const auto module_ast = p.module().to_str();
+  EXPECT_THAT(module_ast, Not(HasSubstr("EntryPoint")));
+}
+
+TEST_F(SpvParseImport, EntryPointVertex) {
+  ParserImpl p(test::Assemble(MakeEntryPoint("GLCompute", "foobar")));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_TRUE(p.error().empty());
+  const auto module_str = p.module().to_str();
+  EXPECT_THAT(module_str, HasSubstr(R"(EntryPoint{compute = foobar})"));
+}
+
+TEST_F(SpvParseImport, EntryPointFragment) {
+  ParserImpl p(test::Assemble(MakeEntryPoint("Fragment", "blitz")));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_TRUE(p.error().empty());
+  const auto module_str = p.module().to_str();
+  EXPECT_THAT(module_str, HasSubstr(R"(EntryPoint{fragment = blitz})"));
+}
+
+TEST_F(SpvParseImport, EntryPointCompute) {
+  ParserImpl p(test::Assemble(MakeEntryPoint("GLCompute", "sort")));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_TRUE(p.error().empty());
+  const auto module_str = p.module().to_str();
+  EXPECT_THAT(module_str, HasSubstr(R"(EntryPoint{compute = sort})"));
+}
+
+TEST_F(SpvParseImport, EntryPointMultiNameConflict) {
+  ParserImpl p(test::Assemble(MakeEntryPoint("GLCompute", "work", "40") +
+                              MakeEntryPoint("Vertex", "work", "50") +
+                              MakeEntryPoint("Fragment", "work", "60")));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_TRUE(p.error().empty());
+  const auto module_str = p.module().to_str();
+  EXPECT_THAT(module_str, HasSubstr(R"(EntryPoint{compute = work})"));
+  EXPECT_THAT(module_str, HasSubstr(R"(EntryPoint{vertex = work_1})"));
+  EXPECT_THAT(module_str, HasSubstr(R"(EntryPoint{fragment = work_2})"));
+}
+
+TEST_F(SpvParseImport, EntryPointNameIsSanitized) {
+  ParserImpl p(test::Assemble(MakeEntryPoint("GLCompute", ".1234")));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_TRUE(p.error().empty());
+  const auto module_str = p.module().to_str();
+  EXPECT_THAT(module_str, HasSubstr(R"(EntryPoint{compute = x_1234})"));
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint