[spirv-reader] Track line numbers

If OpLine debug instructions are present, use their line and column
numbers.  Otherwise, use the instruction number as the line number, starting
counting from 1.

Bug: tint:3
Change-Id: Ia46c10732922b80b5737b13af1ef71f4259a3555
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/27680
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 4f6df45..ee755f2 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -460,6 +460,7 @@
   if (!success_) {
     return false;
   }
+  RegisterLineNumbers();
   if (!ParseInternalModuleExceptFunctions()) {
     return false;
   }
@@ -469,6 +470,47 @@
   return success_;
 }
 
+void ParserImpl::RegisterLineNumbers() {
+  Source instruction_number{0, 0};
+
+  // Has there been an OpLine since the last OpNoLine or start of the module?
+  bool in_op_line_scope = false;
+  // The source location provided by the most recent OpLine instruction.
+  Source op_line_source{0, 0};
+  const bool run_on_debug_insts = true;
+  module_->ForEachInst(
+      [this, &in_op_line_scope, &op_line_source,
+       &instruction_number](const spvtools::opt::Instruction* inst) {
+        ++instruction_number.line;
+        switch (inst->opcode()) {
+          case SpvOpLine:
+            in_op_line_scope = true;
+            // TODO(dneto): This ignores the File ID (operand 0), since the Tint
+            // Source concept doesn't represent that.
+            op_line_source.line = inst->GetSingleWordInOperand(1);
+            op_line_source.column = inst->GetSingleWordInOperand(2);
+            break;
+          case SpvOpNoLine:
+            in_op_line_scope = false;
+            break;
+          default:
+            break;
+        }
+        this->inst_source_[inst] =
+            in_op_line_scope ? op_line_source : instruction_number;
+      },
+      run_on_debug_insts);
+}
+
+Source ParserImpl::GetSourceForResultIdForTest(uint32_t id) {
+  const auto* inst = def_use_mgr_->GetDef(id);
+  auto where = inst_source_.find(inst);
+  if (where == inst_source_.end()) {
+    return {};
+  }
+  return where->second;
+}
+
 bool ParserImpl::ParseInternalModuleExceptFunctions() {
   if (!success_) {
     return false;
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index f1bcd64..28c3523 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -41,6 +41,7 @@
 #include "src/reader/spirv/enum_converter.h"
 #include "src/reader/spirv/fail_stream.h"
 #include "src/reader/spirv/namer.h"
+#include "src/source.h"
 
 namespace tint {
 namespace reader {
@@ -214,6 +215,9 @@
   /// @returns true if the parser is still successful.
   bool ParseInternalModule();
 
+  /// Records line numbers for each instruction.
+  void RegisterLineNumbers();
+
   /// Walks the internal representation of the module, except for function
   /// definitions, to populate the AST form of the module.
   /// This is a no-op if the parser has already failed.
@@ -358,6 +362,12 @@
     return builtin_position_;
   }
 
+  /// Look up the source record for the SPIR-V instruction with the given
+  /// result ID.
+  /// @param id the SPIR-V result id.
+  /// @return the Source record, or a default one
+  Source GetSourceForResultIdForTest(uint32_t id);
+
  private:
   /// Converts a specific SPIR-V type to a Tint type. Integer case
   ast::type::Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);
@@ -435,6 +445,12 @@
   spvtools::opt::analysis::TypeManager* type_mgr_ = nullptr;
   spvtools::opt::analysis::DecorationManager* deco_mgr_ = nullptr;
 
+  // Maps an instruction to its source location. If no OpLine information
+  // is in effect for the instruction, map the instruction to its position
+  // in the SPIR-V module, counting by instructions, where the first
+  // instruction is line 1.
+  std::unordered_map<const spvtools::opt::Instruction*, Source> inst_source_;
+
   /// 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
diff --git a/src/reader/spirv/parser_impl_test.cc b/src/reader/spirv/parser_impl_test.cc
index bc80675..d1facdc 100644
--- a/src/reader/spirv/parser_impl_test.cc
+++ b/src/reader/spirv/parser_impl_test.cc
@@ -119,6 +119,92 @@
   EXPECT_THAT(p->error(), HasSubstr("Capability Kernel is not allowed"));
 }
 
+TEST_F(SpvParserTest, Impl_Source_NoOpLine) {
+  auto spv = test::Assemble(R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint GLCompute %main "main"
+  OpExecutionMode %main LocalSize 1 1 1
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  %5 = OpTypeInt 32 0
+  %60 = OpConstantNull %5
+  %main = OpFunction %void None %voidfn
+  %1 = OpLabel
+  OpReturn
+  OpFunctionEnd
+)");
+  auto* p = parser(spv);
+  EXPECT_TRUE(p->Parse());
+  EXPECT_TRUE(p->error().empty());
+  // Use instruction counting.
+  auto s5 = p->GetSourceForResultIdForTest(5);
+  EXPECT_EQ(7u, s5.line);
+  EXPECT_EQ(0u, s5.column);
+  auto s60 = p->GetSourceForResultIdForTest(60);
+  EXPECT_EQ(8u, s60.line);
+  EXPECT_EQ(0u, s60.column);
+  auto s1 = p->GetSourceForResultIdForTest(1);
+  EXPECT_EQ(10u, s1.line);
+  EXPECT_EQ(0u, s1.column);
+}
+
+TEST_F(SpvParserTest, Impl_Source_WithOpLine_WithOpNoLine) {
+  auto spv = test::Assemble(R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint GLCompute %main "main"
+  OpExecutionMode %main LocalSize 1 1 1
+  %15 = OpString "myfile"
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  OpLine %15 42 53
+  %5 = OpTypeInt 32 0
+  %60 = OpConstantNull %5
+  OpNoLine
+  %main = OpFunction %void None %voidfn
+  %1 = OpLabel
+  OpReturn
+  OpFunctionEnd
+)");
+  auto* p = parser(spv);
+  EXPECT_TRUE(p->Parse());
+  EXPECT_TRUE(p->error().empty());
+  // Use the information from the OpLine that is still in scope.
+  auto s5 = p->GetSourceForResultIdForTest(5);
+  EXPECT_EQ(42u, s5.line);
+  EXPECT_EQ(53u, s5.column);
+  auto s60 = p->GetSourceForResultIdForTest(60);
+  EXPECT_EQ(42u, s60.line);
+  EXPECT_EQ(53u, s60.column);
+  // After OpNoLine, revert back to instruction counting.
+  auto s1 = p->GetSourceForResultIdForTest(1);
+  EXPECT_EQ(13u, s1.line);
+  EXPECT_EQ(0u, s1.column);
+}
+
+TEST_F(SpvParserTest, Impl_Source_InvalidId) {
+  auto spv = test::Assemble(R"(
+  OpCapability Shader
+  OpMemoryModel Logical Simple
+  OpEntryPoint GLCompute %main "main"
+  OpExecutionMode %main LocalSize 1 1 1
+  %15 = OpString "myfile"
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  %main = OpFunction %void None %voidfn
+  %1 = OpLabel
+  OpReturn
+  OpFunctionEnd
+)");
+  auto* p = parser(spv);
+  EXPECT_TRUE(p->Parse());
+  EXPECT_TRUE(p->error().empty());
+  auto s99 = p->GetSourceForResultIdForTest(99);
+  EXPECT_EQ(0u, s99.line);
+  EXPECT_EQ(0u, s99.column);
+}
+
 }  // namespace
 }  // namespace spirv
 }  // namespace reader