spv: Parse OpExtInst for GLSL.std.450

It will always use the "std::glsl" import name.

Remember all the IDs of such imports.

Never add more than one GLSL.std.450 import to the AST.

Also refactor the Assemble test helper into its own file.

Bug: tint:3
Change-Id: I5b2b70ea0f00d44aacf553aa009756dff2a4cecf
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/16662
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c47a190..ae349de 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -353,8 +353,11 @@
 if(${TINT_BUILD_SPV_PARSER})
   list (APPEND TINT_TEST_SRCS
     reader/spv/fail_stream_test.cc
+    reader/spv/parser_impl_import_test.cc
     reader/spv/parser_impl_test.cc
     reader/spv/parser_test.cc
+    reader/spv/spirv_tools_helpers_test.cc
+    reader/spv/spirv_tools_helpers_test.h
   )
 endif()
 
diff --git a/src/reader/spv/fail_stream.h b/src/reader/spv/fail_stream.h
index aa2c588..714359b 100644
--- a/src/reader/spv/fail_stream.h
+++ b/src/reader/spv/fail_stream.h
@@ -1,4 +1,4 @@
-// Copyright 2020 Google LLC
+// 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.
diff --git a/src/reader/spv/parser_impl.cc b/src/reader/spv/parser_impl.cc
index c31d2c6..8adba19 100644
--- a/src/reader/spv/parser_impl.cc
+++ b/src/reader/spv/parser_impl.cc
@@ -104,6 +104,45 @@
   return true;
 }
 
+void ParserImpl::ResetInternalModule() {
+  ir_context_.reset(nullptr);
+  module_ = nullptr;
+  def_use_mgr_ = nullptr;
+  constant_mgr_ = nullptr;
+  type_mgr_ = nullptr;
+  deco_mgr_ = nullptr;
+
+  import_map_.clear();
+  glsl_std_450_imports_.clear();
+}
+
+bool ParserImpl::ParseInternalModule() {
+  return ParseExtendedInstructionImports();
+  // TODO(dneto): fill in the rest
+}
+
+bool ParserImpl::ParseExtendedInstructionImports() {
+  for (const spvtools::opt::Instruction& import : module_->ext_inst_imports()) {
+    std::string name(
+        reinterpret_cast<const char*>(import.GetInOperand(0).words.data()));
+    // TODO(dneto): Handle other extended instruction sets when needed.
+    if (name == "GLSL.std.450") {
+      // Only create the AST import once, so we can use import name 'std::glsl'.
+      // This is a canonicalization.
+      if (glsl_std_450_imports_.empty()) {
+        auto ast_import =
+            std::make_unique<tint::ast::Import>(name, "std::glsl");
+        import_map_[import.result_id()] = ast_import.get();
+        ast_module_.AddImport(std::move(ast_import));
+      }
+      glsl_std_450_imports_.insert(import.result_id());
+    } else {
+      return Fail() << "Unrecognized extended instruction set: " << name;
+    }
+  }
+  return true;
+}
+
 }  // namespace spv
 }  // namespace reader
 }  // namespace tint
diff --git a/src/reader/spv/parser_impl.h b/src/reader/spv/parser_impl.h
index f67d5ef..8c5de47 100644
--- a/src/reader/spv/parser_impl.h
+++ b/src/reader/spv/parser_impl.h
@@ -18,6 +18,8 @@
 #include <cstdint>
 #include <memory>
 #include <sstream>
+#include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 #include "source/opt/constants.h"
@@ -26,6 +28,8 @@
 #include "source/opt/module.h"
 #include "source/opt/type_manager.h"
 #include "spirv-tools/libspirv.hpp"
+#include "src/ast/import.h"
+#include "src/ast/module.h"
 #include "src/reader/reader.h"
 #include "src/reader/spv/fail_stream.h"
 
@@ -61,6 +65,20 @@
   /// @returns the accumulated error string
   const std::string error() { return errors_.str(); }
 
+  /// Builds an internal representation of the SPIR-V binary,
+  /// and parses it into a Tint AST module.  Diagnostics are emitted
+  /// to the error stream.
+  /// @returns true if it was successful.
+  bool BuildAndParseInternalModule() {
+    return BuildInternalModule() && ParseInternalModule();
+  }
+
+  /// @returns the set of SPIR-V IDs for imports of the "GLSL.std.450"
+  /// extended instruction set.
+  const std::unordered_set<uint32_t>& glsl_std_450_imports() const {
+    return glsl_std_450_imports_;
+  }
+
  private:
   /// Builds the internal representation of the SPIR-V module.
   /// Assumes the module is somewhat well-formed.  Normally you
@@ -69,6 +87,17 @@
   /// @returns true if successful.
   bool BuildInternalModule();
 
+  /// Walks the internal representation of the module to populate
+  /// the AST form of the module.
+  /// @returns true on success
+  bool ParseInternalModule();
+
+  /// Destroys the internal representation of the SPIR-V module.
+  void ResetInternalModule();
+
+  /// Parses OpExtInstImport instructions.
+  bool ParseExtendedInstructionImports();
+
   // The SPIR-V binary we're parsing
   std::vector<uint32_t> spv_binary_;
 
@@ -93,6 +122,12 @@
   spvtools::opt::analysis::ConstantManager* constant_mgr_ = nullptr;
   spvtools::opt::analysis::TypeManager* type_mgr_ = nullptr;
   spvtools::opt::analysis::DecorationManager* deco_mgr_ = nullptr;
+
+  /// Maps a SPIR-V ID for an external instruction import to an AST import
+  std::unordered_map<uint32_t, ast::Import*> import_map_;
+  // The set of IDs that are imports of the GLSL.std.450 extended instruction
+  // sets.
+  std::unordered_set<uint32_t> glsl_std_450_imports_;
 };
 
 }  // namespace spv
diff --git a/src/reader/spv/parser_impl_import_test.cc b/src/reader/spv/parser_impl_import_test.cc
new file mode 100644
index 0000000..b6ee771
--- /dev/null
+++ b/src/reader/spv/parser_impl_import_test.cc
@@ -0,0 +1,74 @@
+// 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 "src/reader/spv/parser_impl.h"
+
+#include <memory>
+#include <sstream>
+
+#include "gmock/gmock.h"
+#include "src/reader/spv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spv {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Not;
+using ::testing::UnorderedElementsAre;
+
+using SpvParseImport = ::testing::Test;
+
+TEST_F(SpvParseImport, NoImport) {
+  ParserImpl p(test::Assemble("%1 = OpTypeVoid"));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_TRUE(p.error().empty());
+  const auto module_ast = p.module().to_str();
+  EXPECT_THAT(module_ast, Not(HasSubstr("Import")));
+}
+
+TEST_F(SpvParseImport, ImportGlslStd450) {
+  ParserImpl p(test::Assemble(R"(%1 = OpExtInstImport "GLSL.std.450")"));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_TRUE(p.error().empty());
+  EXPECT_THAT(p.glsl_std_450_imports(), ElementsAre(1));
+  const auto module_ast = p.module().to_str();
+  EXPECT_THAT(module_ast, HasSubstr(R"(Import{"GLSL.std.450" as std::glsl})"));
+}
+
+TEST_F(SpvParseImport, ImportGlslStd450Twice) {
+  ParserImpl p(test::Assemble(R"(
+    %1  = OpExtInstImport "GLSL.std.450"
+    %42 = OpExtInstImport "GLSL.std.450"
+  )"));
+  EXPECT_TRUE(p.BuildAndParseInternalModule());
+  EXPECT_TRUE(p.error().empty());
+  EXPECT_THAT(p.glsl_std_450_imports(), UnorderedElementsAre(1, 42));
+  const auto module = p.module();
+  EXPECT_EQ(module.imports().size(), 1);
+  const auto module_ast = module.to_str();
+  // TODO(dneto): Use a matcher to show there is only one import.
+  EXPECT_THAT(module_ast, HasSubstr(R"(Import{"GLSL.std.450" as std::glsl})"));
+}
+
+// TODO(dneto): We don't currently support other kinds of extended instruction
+// imports.
+
+}  // namespace
+}  // namespace spv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/reader/spv/parser_impl_test.cc b/src/reader/spv/parser_impl_test.cc
index 9881fa3..5604e8e 100644
--- a/src/reader/spv/parser_impl_test.cc
+++ b/src/reader/spv/parser_impl_test.cc
@@ -18,7 +18,7 @@
 #include <vector>
 
 #include "gmock/gmock.h"
-#include "spirv-tools/libspirv.hpp"
+#include "src/reader/spv/spirv_tools_helpers_test.h"
 
 namespace tint {
 namespace reader {
@@ -37,31 +37,8 @@
   // TODO(dneto): What message?
 }
 
-/// @returns the SPIR-V module assembled from the given text.  Numeric IDs
-/// are preserved.
-std::vector<uint32_t> Assemble(const std::string& spirv_assembly) {
-  // TODO(dneto): Use ScopedTrace?
-
-  // (The target environment doesn't affect assembly.
-  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
-  std::stringstream errors;
-  std::vector<uint32_t> result;
-  tools.SetMessageConsumer([&errors](spv_message_level_t, const char*,
-                                     const spv_position_t& position,
-                                     const char* message) {
-    errors << "assembly error:" << position.line << ":" << position.column
-           << ": " << message;
-  });
-
-  const auto success = tools.Assemble(
-      spirv_assembly, &result, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  EXPECT_TRUE(success) << errors.str();
-
-  return result;
-}
-
 TEST_F(SpvParserImplTest, InvalidModuleFails) {
-  auto invalid_spv = Assemble("%ty = OpTypeInt 3 0");
+  auto invalid_spv = test::Assemble("%ty = OpTypeInt 3 0");
   ParserImpl p{invalid_spv};
   EXPECT_FALSE(p.Parse());
   EXPECT_THAT(
diff --git a/src/reader/spv/spirv_tools_helpers_test.cc b/src/reader/spv/spirv_tools_helpers_test.cc
new file mode 100644
index 0000000..00d6d51
--- /dev/null
+++ b/src/reader/spv/spirv_tools_helpers_test.cc
@@ -0,0 +1,55 @@
+// 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 "src/reader/spv/spirv_tools_helpers_test.h"
+
+#include <cstdint>
+#include <sstream>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace tint {
+namespace reader {
+namespace spv {
+namespace test {
+
+/// @returns the SPIR-V module assembled from the given text.  Numeric IDs
+/// are preserved.
+std::vector<uint32_t> Assemble(const std::string& spirv_assembly) {
+  // TODO(dneto): Use ScopedTrace?
+
+  // (The target environment doesn't affect assembly.
+  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+  std::stringstream errors;
+  std::vector<uint32_t> result;
+  tools.SetMessageConsumer([&errors](spv_message_level_t, const char*,
+                                     const spv_position_t& position,
+                                     const char* message) {
+    errors << "assembly error:" << position.line << ":" << position.column
+           << ": " << message;
+  });
+
+  const auto success = tools.Assemble(
+      spirv_assembly, &result, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_TRUE(success) << errors.str();
+
+  return result;
+}
+
+}  // namespace test
+}  // namespace spv
+}  // namespace reader
+}  // namespace tint
diff --git a/src/reader/spv/spirv_tools_helpers_test.h b/src/reader/spv/spirv_tools_helpers_test.h
new file mode 100644
index 0000000..b77d954
--- /dev/null
+++ b/src/reader/spv/spirv_tools_helpers_test.h
@@ -0,0 +1,36 @@
+// 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.
+
+#ifndef SRC_READER_SPV_SPIRV_TOOLS_HELPERS_TEST_H_
+#define SRC_READER_SPV_SPIRV_TOOLS_HELPERS_TEST_H_
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace tint {
+namespace reader {
+namespace spv {
+namespace test {
+
+/// @returns the SPIR-V module assembled from the given text.  Numeric IDs
+/// are preserved.
+std::vector<uint32_t> Assemble(const std::string& spirv_assembly);
+
+}  // namespace test
+}  // namespace spv
+}  // namespace reader
+}  // namespace tint
+
+#endif  // SRC_READER_SPV_SPIRV_TOOLS_HELPERS_TEST_H_