spv: Build internal representation of the SPIR-V

For convenience, use the SPIRV-Tools' optimizer representation.

Bug: tint:3
Change-Id: I1b046209584e1e907045d496b0f8d7b36fca79bd
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/16660
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 93001a6..6713085 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -306,14 +306,32 @@
   writer/wgsl/generator_impl_test.cc
 )
 
+function(tint_spvtools_compile_options TARGET)
+  # We'll use the optimizer for its nice SPIR-V in-memory representation
+  target_link_libraries(libtint SPIRV-Tools-opt SPIRV-Tools)
+  # We'll be cheating: using internal interfaces to the SPIRV-Tools
+  # optimizer.
+  target_include_directories(libtint PRIVATE ${spirv-tools_SOURCE_DIR} ${spirv-tools_BINARY_DIR})
+  if (${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
+    # The SPIRV-Tools code is conditioned against C++ and an older version of Clang.
+    # Suppress warnings triggered in our current compilation environment.
+    # TODO(dneto): Fix the issues upstream.
+    target_compile_options(${TARGET} PRIVATE
+      -Wno-newline-eof
+      -Wno-sign-conversion
+      -Wno-old-style-cast
+      -Wno-weak-vtables
+    )
+  endif()
+endfunction()
+
+
 ## Tint library
 add_library(libtint ${TINT_LIB_SRCS})
 tint_default_compile_options(libtint)
 set_target_properties(libtint PROPERTIES OUTPUT_NAME "tint")
-
 if(${TINT_BUILD_SPV_PARSER})
-  # We'll use the optimizer for its nice SPIR-V in-memory representation
-  target_link_libraries(libtint SPIRV-Tools-opt SPIRV-Tools)
+  tint_spvtools_compile_options(libtint)
 endif()
 
 if(${TINT_BUILD_SPV_PARSER})
@@ -325,6 +343,9 @@
 endif()
 
 add_executable(tint_unittests ${TINT_TEST_SRCS})
+if (${TINT_BUILD_SPV_PARSER})
+  tint_spvtools_compile_options(tint_unittests)
+endif()
 if (NOT MSVC)
   target_compile_options(tint_unittests PRIVATE
     -Wno-global-constructors
@@ -335,6 +356,10 @@
 ## Test executable
 target_include_directories(
     tint_unittests PRIVATE ${gmock_SOURCE_DIR}/include)
+if(${TINT_BUILD_SPV_PARSER})
+  target_include_directories(tint_unittests
+	  PRIVATE ${spirv-tools_SOURCE_DIR} ${spirv-tools_BINARY_DIR})
+endif()
 target_link_libraries(tint_unittests libtint gmock_main)
 tint_default_compile_options(tint_unittests)
 
diff --git a/src/reader/spv/parser_impl.cc b/src/reader/spv/parser_impl.cc
index 560f3d1..c31d2c6 100644
--- a/src/reader/spv/parser_impl.cc
+++ b/src/reader/spv/parser_impl.cc
@@ -14,6 +14,7 @@
 
 #include <cstring>
 
+#include "source/opt/build_module.h"
 #include "spirv-tools/libspirv.hpp"
 #include "src/reader/spv/parser_impl.h"
 
@@ -21,8 +22,37 @@
 namespace reader {
 namespace spv {
 
+namespace {
+
+const spv_target_env kTargetEnv = SPV_ENV_WEBGPU_0;
+
+}  // namespace
+
 ParserImpl::ParserImpl(const std::vector<uint32_t>& spv_binary)
-    : Reader(), spv_binary_(spv_binary), fail_stream_(&success_, &errors_) {}
+    : Reader(),
+      spv_binary_(spv_binary),
+      fail_stream_(&success_, &errors_),
+      tools_context_(kTargetEnv),
+      tools_(kTargetEnv) {
+  // Create a message consumer to propagate error messages from SPIRV-Tools
+  // out as our own failures.
+  message_consumer_ = [this](spv_message_level_t level, const char* /*source*/,
+                             const spv_position_t& position,
+                             const char* message) {
+    switch (level) {
+      // Ignore info and warning message.
+      case SPV_MSG_WARNING:
+      case SPV_MSG_INFO:
+        break;
+      // Otherwise, propagate the error.
+      default:
+        // For binary validation errors, we only have the instruction
+        // number.  It's not text, so there is no column number.
+        this->Fail() << "line:" << position.index << ": " << message;
+        this->Fail() << "error: line " << position.index << ": " << message;
+    }
+  };
+}
 
 ParserImpl::~ParserImpl() = default;
 
@@ -32,30 +62,20 @@
   }
 
   // Set up use of SPIRV-Tools utilities.
-  // TODO(dneto): Add option to handle other environments.
-  spvtools::SpirvTools spv_tools(SPV_ENV_WEBGPU_0);
+  spvtools::SpirvTools spv_tools(kTargetEnv);
 
   // Error messages from SPIRV-Tools are forwarded as failures.
-  auto message_consumer =
-      [this](spv_message_level_t level, const char* /*source*/,
-             const spv_position_t& position, const char* message) {
-        switch (level) {
-          // Drop info and warning message.
-          case SPV_MSG_WARNING:
-          case SPV_MSG_INFO:
-          default:
-            // For binary validation errors, we only have the instruction
-            // number.  It's not text, so there is no column number.
-            this->Fail() << "line:" << position.index << ": " << message;
-        }
-      };
-  spv_tools.SetMessageConsumer(message_consumer);
+  spv_tools.SetMessageConsumer(message_consumer_);
 
   // Only consider valid modules.
   if (success_) {
     success_ = spv_tools.Validate(spv_binary_);
   }
 
+  if (success_) {
+    success_ = BuildInternalModule();
+  }
+
   return success_;
 }
 
@@ -65,6 +85,25 @@
   return std::move(ast_module_);
 }
 
+bool ParserImpl::BuildInternalModule() {
+  tools_.SetMessageConsumer(message_consumer_);
+
+  const spv_context& context = tools_context_.CContext();
+  ir_context_ = spvtools::BuildModule(context->target_env, context->consumer,
+                                      spv_binary_.data(), spv_binary_.size());
+  if (!ir_context_) {
+    return Fail() << "internal error: couldn't build the internal "
+                     "representation of the module";
+  }
+  module_ = ir_context_->module();
+  def_use_mgr_ = ir_context_->get_def_use_mgr();
+  constant_mgr_ = ir_context_->get_constant_mgr();
+  type_mgr_ = ir_context_->get_type_mgr();
+  deco_mgr_ = ir_context_->get_decoration_mgr();
+
+  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 33c84c2..f67d5ef 100644
--- a/src/reader/spv/parser_impl.h
+++ b/src/reader/spv/parser_impl.h
@@ -20,6 +20,12 @@
 #include <sstream>
 #include <vector>
 
+#include "source/opt/constants.h"
+#include "source/opt/decoration_manager.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/type_manager.h"
+#include "spirv-tools/libspirv.hpp"
 #include "src/reader/reader.h"
 #include "src/reader/spv/fail_stream.h"
 
@@ -56,6 +62,13 @@
   const std::string error() { return errors_.str(); }
 
  private:
+  /// Builds the internal representation of the SPIR-V module.
+  /// Assumes the module is somewhat well-formed.  Normally you
+  /// would want to validate the SPIR-V module before attempting
+  /// to build this internal representation.
+  /// @returns true if successful.
+  bool BuildInternalModule();
+
   // The SPIR-V binary we're parsing
   std::vector<uint32_t> spv_binary_;
 
@@ -67,6 +80,19 @@
   // Collector for diagnostic messages.
   std::stringstream errors_;
   FailStream fail_stream_;
+  spvtools::MessageConsumer message_consumer_;
+
+  // The internal representation of the SPIR-V module and its context.
+  spvtools::Context tools_context_;
+  spvtools::SpirvTools tools_;
+  // All the state is owned by ir_context_.
+  std::unique_ptr<spvtools::opt::IRContext> ir_context_;
+  // The following are borrowed pointers to the internal state of ir_context_.
+  spvtools::opt::Module* module_ = nullptr;
+  spvtools::opt::analysis::DefUseManager* def_use_mgr_ = nullptr;
+  spvtools::opt::analysis::ConstantManager* constant_mgr_ = nullptr;
+  spvtools::opt::analysis::TypeManager* type_mgr_ = nullptr;
+  spvtools::opt::analysis::DecorationManager* deco_mgr_ = nullptr;
 };
 
 }  // namespace spv