Record trailing line dbg instructions (#2926)

There is nothing in the spir-v spec that says the last
instructions in a module cannot be OpLine or OpNoLine.
However, the code that parses the module will simply drop
these instructions.

We add code that will preserve these instructions.

Strip-debug-info is updated to remove these instructions.

Fixes https://crbug.com/1000689.
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index edd4832..c68b3e2 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -159,6 +159,9 @@
   for (auto& function : *module_) {
     for (auto& bb : function) bb.SetParent(&function);
   }
+
+  // Copy any trailing Op*Line instruction into the module
+  module_->SetTrailingDbgLineInfo(std::move(dbg_line_info_));
 }
 
 }  // namespace opt
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index 04e4e97..c7fc247 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -121,6 +121,9 @@
     static_cast<const Function*>(i.get())->ForEachInst(f,
                                                        run_on_debug_line_insts);
   }
+  if (run_on_debug_line_insts) {
+    for (auto& i : trailing_dbg_line_info_) DELEGATE(i);
+  }
 #undef DELEGATE
 }
 
diff --git a/source/opt/module.h b/source/opt/module.h
index bf23491..aefa2a5 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -245,6 +245,19 @@
   // Gets the associated context for this module
   IRContext* context() const { return context_; }
 
+  // Sets the trailing debug line info to |dbg_line_info|.
+  void SetTrailingDbgLineInfo(std::vector<Instruction>&& dbg_line_info) {
+    trailing_dbg_line_info_ = std::move(dbg_line_info);
+  }
+
+  std::vector<Instruction>& trailing_dbg_line_info() {
+    return trailing_dbg_line_info_;
+  }
+
+  const std::vector<Instruction>& trailing_dbg_line_info() const {
+    return trailing_dbg_line_info_;
+  }
+
  private:
   ModuleHeader header_;  // Module header
 
@@ -265,6 +278,10 @@
   // Type declarations, constants, and global variable declarations.
   InstructionList types_values_;
   std::vector<std::unique_ptr<Function>> functions_;
+
+  // If the module ends with Op*Line instruction, they will not be attached to
+  // any instruction.  We record them here, so they will not be lost.
+  std::vector<Instruction> trailing_dbg_line_info_;
 };
 
 // Pretty-prints |module| to |str|. Returns |str|.
diff --git a/source/opt/strip_debug_info_pass.cpp b/source/opt/strip_debug_info_pass.cpp
index f5fb94f..9e7fad0 100644
--- a/source/opt/strip_debug_info_pass.cpp
+++ b/source/opt/strip_debug_info_pass.cpp
@@ -45,6 +45,11 @@
     inst->dbg_line_insts().clear();
   });
 
+  if (!get_module()->trailing_dbg_line_info().empty()) {
+    modified = true;
+    get_module()->trailing_dbg_line_info().clear();
+  }
+
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
diff --git a/test/opt/module_test.cpp b/test/opt/module_test.cpp
index 569cf9b..406da09 100644
--- a/test/opt/module_test.cpp
+++ b/test/opt/module_test.cpp
@@ -21,6 +21,7 @@
 #include "gtest/gtest.h"
 #include "source/opt/build_module.h"
 #include "source/opt/module.h"
+#include "source/opt/pass.h"
 #include "spirv-tools/libspirv.hpp"
 #include "test/opt/module_utils.h"
 
@@ -228,6 +229,72 @@
   EXPECT_EQ(next_id_bound, 0);
   EXPECT_EQ(current_bound, context->module()->id_bound());
 }
+
+// Tests that "text" does not change when it is assembled, converted into a
+// module, converted back to a binary, and then disassembled.
+void AssembleAndDisassemble(const std::string& text) {
+  std::unique_ptr<IRContext> context = BuildModule(text);
+  std::vector<uint32_t> binary;
+
+  context->module()->ToBinary(&binary, false);
+
+  SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
+  std::string s;
+  tools.Disassemble(binary, &s);
+  EXPECT_EQ(s, text);
+}
+
+TEST(ModuleTest, TrailingOpLine) {
+  const std::string text = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%5 = OpString "file.ext"
+%void = OpTypeVoid
+%2 = OpTypeFunction %void
+%3 = OpFunction %void None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+OpLine %5 1 0
+)";
+
+  AssembleAndDisassemble(text);
+}
+
+TEST(ModuleTest, TrailingOpNoLine) {
+  const std::string text = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%2 = OpTypeFunction %void
+%3 = OpFunction %void None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+OpNoLine
+)";
+
+  AssembleAndDisassemble(text);
+}
+
+TEST(ModuleTest, MulitpleTrailingOpLine) {
+  const std::string text = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%5 = OpString "file.ext"
+%void = OpTypeVoid
+%2 = OpTypeFunction %void
+%3 = OpFunction %void None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+OpLine %5 1 0
+OpNoLine
+OpLine %5 1 1
+)";
+
+  AssembleAndDisassemble(text);
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/strip_debug_info_test.cpp b/test/opt/strip_debug_info_test.cpp
index 330f29b..2f2ff46 100644
--- a/test/opt/strip_debug_info_test.cpp
+++ b/test/opt/strip_debug_info_test.cpp
@@ -51,6 +51,8 @@
                "OpLine %3 4 4",
                "OpNoLine",
                "OpFunctionEnd",
+               "OpNoLine",
+               "OpLine %3 4 5"
       // clang-format on
   };
   SinglePassRunAndCheck<StripDebugInfoPass>(JoinAllInsts(text),