Propagate OpLine to all applied instructions in spirv-opt (#3951)

Based on the OpLine spec, an OpLine instruction must be applied to
the instructions physically following it up to the first occurrence
of the next end of block, the next OpLine instruction, or the next
OpNoLine instruction.

```
OpLine %file 0 0
OpNoLine
OpLine %file 1 1
OpStore %foo %int_1
%value = OpLoad %int %foo
OpLine %file 2 2
```

For the above code, the current spirv-opt keeps three line
instructions `OpLine %file 0 0`, `OpNoLine`, and `OpLine %file 1 1`
in `std::vector<Instruction> dbg_line_insts_` of Instruction class
for `OpStore %foo %int_1`. It does not put any line instruction to
`std::vector<Instruction> dbg_line_insts_` of
`%value = OpLoad %int %foo` even though `OpLine %file 1 1` must be
applied to `%value = OpLoad %int %foo` based on the spec.

This results in the missing line information for
`%value = OpLoad %int %foo` while each spirv-opt pass optimizes the
code. We have to put `OpLine %file 1 1` to
`std::vector<Instruction> dbg_line_insts_` of
both `%value = OpLoad %int %foo` and `OpStore %foo %int_1`.

This commit conducts the line instruction propagation and skips
emitting the eliminated line instructions at the end, which are the same
with PropagateLineInfoPass and RedundantLineInfoElimPass. This
commit removes PropagateLineInfoPass and RedundantLineInfoElimPass.

KhronosGroup/glslang#2440 is a related PR that stop using
PropagateLineInfoPass and RedundantLineInfoElimPass from glslang.
When the code in this PR applied, the glslang tests will pass.
diff --git a/Android.mk b/Android.mk
index 54360f0..0b64ea6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -149,7 +149,6 @@
 		source/opt/pass.cpp \
 		source/opt/pass_manager.cpp \
 		source/opt/private_to_local_pass.cpp \
-		source/opt/process_lines_pass.cpp \
 		source/opt/propagator.cpp \
 		source/opt/reduce_load_size.cpp \
 		source/opt/redundancy_elimination.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index de4e48e..55eb9ad 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -654,8 +654,6 @@
     "source/opt/passes.h",
     "source/opt/private_to_local_pass.cpp",
     "source/opt/private_to_local_pass.h",
-    "source/opt/process_lines_pass.cpp",
-    "source/opt/process_lines_pass.h",
     "source/opt/propagator.cpp",
     "source/opt/propagator.h",
     "source/opt/reduce_load_size.cpp",
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index d89d3b6..7f993cc 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -536,30 +536,6 @@
 // eliminated with standard dead code elimination.
 Optimizer::PassToken CreateAggressiveDCEPass();
 
-// Create line propagation pass
-// This pass propagates line information based on the rules for OpLine and
-// OpNoline and clones an appropriate line instruction into every instruction
-// which does not already have debug line instructions.
-//
-// This pass is intended to maximize preservation of source line information
-// through passes which delete, move and clone instructions. Ideally it should
-// be run before any such pass. It is a bookend pass with EliminateDeadLines
-// which can be used to remove redundant line instructions at the end of a
-// run of such passes and reduce final output file size.
-Optimizer::PassToken CreatePropagateLineInfoPass();
-
-// Create dead line elimination pass
-// This pass eliminates redundant line instructions based on the rules for
-// OpLine and OpNoline. Its main purpose is to reduce the size of the file
-// need to store the SPIR-V without losing line information.
-//
-// This is a bookend pass with PropagateLines which attaches line instructions
-// to every instruction to preserve line information during passes which
-// delete, move and clone instructions. DeadLineElim should be run after
-// PropagateLines and all such subsequent passes. Normally it would be one
-// of the last passes to be run.
-Optimizer::PassToken CreateRedundantLineInfoElimPass();
-
 // Creates a compact ids pass.
 // The pass remaps result ids to a compact and gapless range starting from %1.
 Optimizer::PassToken CreateCompactIdsPass();
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index fa74690..3ab43af 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -89,7 +89,6 @@
   pass.h
   pass_manager.h
   private_to_local_pass.h
-  process_lines_pass.h
   propagator.h
   reduce_load_size.h
   redundancy_elimination.h
@@ -196,7 +195,6 @@
   pass.cpp
   pass_manager.cpp
   private_to_local_pass.cpp
-  process_lines_pass.cpp
   propagator.cpp
   reduce_load_size.cpp
   redundancy_elimination.cpp
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index a10812e..4a44309 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -41,6 +41,7 @@
   ++inst_index_;
   const auto opcode = static_cast<SpvOp>(inst->opcode);
   if (IsDebugLineInst(opcode)) {
+    last_line_inst_.reset();
     dbg_line_info_.push_back(
         Instruction(module()->context(), *inst, last_dbg_scope_));
     return true;
@@ -90,7 +91,16 @@
 
   std::unique_ptr<Instruction> spv_inst(
       new Instruction(module()->context(), *inst, std::move(dbg_line_info_)));
-  dbg_line_info_.clear();
+  if (!spv_inst->dbg_line_insts().empty()) {
+    if (spv_inst->dbg_line_insts().back().opcode() != SpvOpNoLine) {
+      last_line_inst_ = std::unique_ptr<Instruction>(
+          spv_inst->dbg_line_insts().back().Clone(module()->context()));
+    }
+    dbg_line_info_.clear();
+  } else if (last_line_inst_ != nullptr) {
+    last_line_inst_->SetDebugScope(last_dbg_scope_);
+    spv_inst->dbg_line_insts().push_back(*last_line_inst_);
+  }
 
   const char* src = source_.c_str();
   spv_position_t loc = {inst_index_, 0, 0};
@@ -141,6 +151,8 @@
     function_->AddBasicBlock(std::move(block_));
     block_ = nullptr;
     last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
+    last_line_inst_.reset();
+    dbg_line_info_.clear();
   } else {
     if (function_ == nullptr) {  // Outside function definition
       SPIRV_ASSERT(consumer_, block_ == nullptr);
diff --git a/source/opt/ir_loader.h b/source/opt/ir_loader.h
index 5079921..d0610f1 100644
--- a/source/opt/ir_loader.h
+++ b/source/opt/ir_loader.h
@@ -78,6 +78,8 @@
   std::unique_ptr<BasicBlock> block_;
   // Line related debug instructions accumulated thus far.
   std::vector<Instruction> dbg_line_info_;
+  // Line instruction that should be applied to the next instruction.
+  std::unique_ptr<Instruction> last_line_inst_;
 
   // The last DebugScope information that IrLoader::AddInstruction() handled.
   DebugScope last_dbg_scope_;
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index 6707631..9d3b0ed 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -143,8 +143,38 @@
 
   size_t bound_idx = binary->size() - 2;
   DebugScope last_scope(kNoDebugScope, kNoInlinedAt);
-  auto write_inst = [binary, skip_nop, &last_scope,
-                     this](const Instruction* i) {
+  const Instruction* last_line_inst = nullptr;
+  bool between_merge_and_branch = false;
+  auto write_inst = [binary, skip_nop, &last_scope, &last_line_inst,
+                     &between_merge_and_branch, this](const Instruction* i) {
+    // Skip emitting line instructions between merge and branch instructions.
+    auto opcode = i->opcode();
+    if (between_merge_and_branch &&
+        (opcode == SpvOpLine || opcode == SpvOpNoLine)) {
+      return;
+    }
+    between_merge_and_branch = false;
+    if (last_line_inst != nullptr) {
+      // If the current instruction is OpLine and it is the same with
+      // the last line instruction that is still effective (can be applied
+      // to the next instruction), we skip writing the current instruction.
+      if (opcode == SpvOpLine) {
+        uint32_t operand_index = 0;
+        if (last_line_inst->WhileEachInOperand(
+                [&operand_index, i](const uint32_t* word) {
+                  assert(i->NumInOperandWords() > operand_index);
+                  return *word == i->GetSingleWordInOperand(operand_index++);
+                })) {
+          return;
+        }
+      } else if (opcode != SpvOpNoLine && i->dbg_line_insts().empty()) {
+        // If the current instruction does not have the line information,
+        // the last line information is not effective any more. Emit OpNoLine
+        // to specify it.
+        binary->push_back((1 << 16) | static_cast<uint16_t>(SpvOpNoLine));
+        last_line_inst = nullptr;
+      }
+    }
     if (!(skip_nop && i->IsNop())) {
       const auto& scope = i->GetDebugScope();
       if (scope != last_scope) {
@@ -157,6 +187,15 @@
 
       i->ToBinaryWithoutAttachedDebugInsts(binary);
     }
+    // Update the last line instruction.
+    if (IsTerminatorInst(opcode) || opcode == SpvOpNoLine) {
+      last_line_inst = nullptr;
+    } else if (opcode == SpvOpLoopMerge || opcode == SpvOpSelectionMerge) {
+      between_merge_and_branch = true;
+      last_line_inst = nullptr;
+    } else if (opcode == SpvOpLine) {
+      last_line_inst = i;
+    }
   };
   ForEachInst(write_inst, true);
 
diff --git a/source/opt/module.h b/source/opt/module.h
index 2c96f02..75da870 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -246,6 +246,12 @@
   // If |skip_nop| is true and this is a OpNop, do nothing.
   void ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const;
 
+  // Pushes the binary segments for this instruction into the back of *|binary|
+  // including all OpLine and OpNoLine even if we can skip emitting some line
+  // instructions. If |skip_nop| is true and this is a OpNop, do nothing.
+  void ToBinaryWithAllOpLines(std::vector<uint32_t>* binary,
+                              bool skip_nop) const;
+
   // Returns 1 more than the maximum Id value mentioned in the module.
   uint32_t ComputeIdBound() const;
 
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index b891124..7a6a33b 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -339,10 +339,6 @@
     RegisterPass(CreateDescriptorScalarReplacementPass());
   } else if (pass_name == "eliminate-dead-code-aggressive") {
     RegisterPass(CreateAggressiveDCEPass());
-  } else if (pass_name == "propagate-line-info") {
-    RegisterPass(CreatePropagateLineInfoPass());
-  } else if (pass_name == "eliminate-redundant-line-info") {
-    RegisterPass(CreateRedundantLineInfoElimPass());
   } else if (pass_name == "eliminate-insert-extract") {
     RegisterPass(CreateInsertExtractElimPass());
   } else if (pass_name == "eliminate-local-single-block") {
@@ -757,16 +753,6 @@
       MakeUnique<opt::AggressiveDCEPass>());
 }
 
-Optimizer::PassToken CreatePropagateLineInfoPass() {
-  return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::ProcessLinesPass>(opt::kLinesPropagateLines));
-}
-
-Optimizer::PassToken CreateRedundantLineInfoElimPass() {
-  return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::ProcessLinesPass>(opt::kLinesEliminateDeadLines));
-}
-
 Optimizer::PassToken CreateCompactIdsPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::CompactIdsPass>());
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 5b4ab89..acc30e1 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -61,7 +61,6 @@
 #include "source/opt/merge_return_pass.h"
 #include "source/opt/null_pass.h"
 #include "source/opt/private_to_local_pass.h"
-#include "source/opt/process_lines_pass.h"
 #include "source/opt/reduce_load_size.h"
 #include "source/opt/redundancy_elimination.h"
 #include "source/opt/relax_float_ops_pass.h"
diff --git a/source/opt/process_lines_pass.cpp b/source/opt/process_lines_pass.cpp
deleted file mode 100644
index 0ae2f75..0000000
--- a/source/opt/process_lines_pass.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright (c) 2018 The Khronos Group Inc.
-// Copyright (c) 2018 Valve Corporation
-// Copyright (c) 2018 LunarG Inc.
-//
-// 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 "source/opt/process_lines_pass.h"
-
-#include <set>
-#include <unordered_set>
-#include <vector>
-
-namespace {
-
-// Input Operand Indices
-static const int kSpvLineFileInIdx = 0;
-static const int kSpvLineLineInIdx = 1;
-static const int kSpvLineColInIdx = 2;
-
-}  // anonymous namespace
-
-namespace spvtools {
-namespace opt {
-
-Pass::Status ProcessLinesPass::Process() {
-  bool modified = ProcessLines();
-  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
-}
-
-bool ProcessLinesPass::ProcessLines() {
-  bool modified = false;
-  uint32_t file_id = 0;
-  uint32_t line = 0;
-  uint32_t col = 0;
-  // Process types, globals, constants
-  for (Instruction& inst : get_module()->types_values())
-    modified |= line_process_func_(&inst, &file_id, &line, &col);
-  // Process functions
-  for (Function& function : *get_module()) {
-    modified |= line_process_func_(&function.DefInst(), &file_id, &line, &col);
-    function.ForEachParam(
-        [this, &modified, &file_id, &line, &col](Instruction* param) {
-          modified |= line_process_func_(param, &file_id, &line, &col);
-        });
-    for (BasicBlock& block : function) {
-      modified |=
-          line_process_func_(block.GetLabelInst(), &file_id, &line, &col);
-      for (Instruction& inst : block) {
-        modified |= line_process_func_(&inst, &file_id, &line, &col);
-        // Don't process terminal instruction if preceeded by merge
-        if (inst.opcode() == SpvOpSelectionMerge ||
-            inst.opcode() == SpvOpLoopMerge)
-          break;
-      }
-      // Nullify line info after each block.
-      file_id = 0;
-    }
-    modified |= line_process_func_(function.EndInst(), &file_id, &line, &col);
-  }
-  return modified;
-}
-
-bool ProcessLinesPass::PropagateLine(Instruction* inst, uint32_t* file_id,
-                                     uint32_t* line, uint32_t* col) {
-  bool modified = false;
-  // only the last debug instruction needs to be considered
-  auto line_itr = inst->dbg_line_insts().rbegin();
-  // if no line instructions, propagate previous info
-  if (line_itr == inst->dbg_line_insts().rend()) {
-    // if no current line info, add OpNoLine, else OpLine
-    if (*file_id == 0)
-      inst->dbg_line_insts().push_back(Instruction(context(), SpvOpNoLine));
-    else
-      inst->dbg_line_insts().push_back(Instruction(
-          context(), SpvOpLine, 0, 0,
-          {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {*file_id}},
-           {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {*line}},
-           {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {*col}}}));
-    modified = true;
-  } else {
-    // else pre-existing line instruction, so update source line info
-    if (line_itr->opcode() == SpvOpNoLine) {
-      *file_id = 0;
-    } else {
-      assert(line_itr->opcode() == SpvOpLine && "unexpected debug inst");
-      *file_id = line_itr->GetSingleWordInOperand(kSpvLineFileInIdx);
-      *line = line_itr->GetSingleWordInOperand(kSpvLineLineInIdx);
-      *col = line_itr->GetSingleWordInOperand(kSpvLineColInIdx);
-    }
-  }
-  return modified;
-}
-
-bool ProcessLinesPass::EliminateDeadLines(Instruction* inst, uint32_t* file_id,
-                                          uint32_t* line, uint32_t* col) {
-  // If no debug line instructions, return without modifying lines
-  if (inst->dbg_line_insts().empty()) return false;
-  // Only the last debug instruction needs to be considered; delete all others
-  bool modified = inst->dbg_line_insts().size() > 1;
-  Instruction last_inst = inst->dbg_line_insts().back();
-  inst->dbg_line_insts().clear();
-  // If last line is OpNoLine
-  if (last_inst.opcode() == SpvOpNoLine) {
-    // If no propagated line info, throw away redundant OpNoLine
-    if (*file_id == 0) {
-      modified = true;
-      // Else replace OpNoLine and propagate no line info
-    } else {
-      inst->dbg_line_insts().push_back(last_inst);
-      *file_id = 0;
-    }
-  } else {
-    // Else last line is OpLine
-    assert(last_inst.opcode() == SpvOpLine && "unexpected debug inst");
-    // If propagated info matches last line, throw away last line
-    if (*file_id == last_inst.GetSingleWordInOperand(kSpvLineFileInIdx) &&
-        *line == last_inst.GetSingleWordInOperand(kSpvLineLineInIdx) &&
-        *col == last_inst.GetSingleWordInOperand(kSpvLineColInIdx)) {
-      modified = true;
-    } else {
-      // Else replace last line and propagate line info
-      *file_id = last_inst.GetSingleWordInOperand(kSpvLineFileInIdx);
-      *line = last_inst.GetSingleWordInOperand(kSpvLineLineInIdx);
-      *col = last_inst.GetSingleWordInOperand(kSpvLineColInIdx);
-      inst->dbg_line_insts().push_back(last_inst);
-    }
-  }
-  return modified;
-}
-
-ProcessLinesPass::ProcessLinesPass(uint32_t func_id) {
-  if (func_id == kLinesPropagateLines) {
-    line_process_func_ = [this](Instruction* inst, uint32_t* file_id,
-                                uint32_t* line, uint32_t* col) {
-      return PropagateLine(inst, file_id, line, col);
-    };
-  } else {
-    assert(func_id == kLinesEliminateDeadLines && "unknown Lines param");
-    line_process_func_ = [this](Instruction* inst, uint32_t* file_id,
-                                uint32_t* line, uint32_t* col) {
-      return EliminateDeadLines(inst, file_id, line, col);
-    };
-  }
-}
-
-}  // namespace opt
-}  // namespace spvtools
diff --git a/source/opt/process_lines_pass.h b/source/opt/process_lines_pass.h
deleted file mode 100644
index c988bfd..0000000
--- a/source/opt/process_lines_pass.h
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2018 The Khronos Group Inc.
-// Copyright (c) 2018 Valve Corporation
-// Copyright (c) 2018 LunarG Inc.
-//
-// 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 SOURCE_OPT_PROPAGATE_LINES_PASS_H_
-#define SOURCE_OPT_PROPAGATE_LINES_PASS_H_
-
-#include "source/opt/function.h"
-#include "source/opt/ir_context.h"
-#include "source/opt/pass.h"
-
-namespace spvtools {
-namespace opt {
-
-namespace {
-
-// Constructor Parameters
-static const int kLinesPropagateLines = 0;
-static const int kLinesEliminateDeadLines = 1;
-
-}  // anonymous namespace
-
-// See optimizer.hpp for documentation.
-class ProcessLinesPass : public Pass {
-  using LineProcessFunction =
-      std::function<bool(Instruction*, uint32_t*, uint32_t*, uint32_t*)>;
-
- public:
-  ProcessLinesPass(uint32_t func_id);
-  ~ProcessLinesPass() override = default;
-
-  const char* name() const override { return "propagate-lines"; }
-
-  // See optimizer.hpp for this pass' user documentation.
-  Status Process() override;
-
-  IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse |
-           IRContext::kAnalysisInstrToBlockMapping |
-           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
-           IRContext::kAnalysisTypes;
-  }
-
- private:
-  // If |inst| has no debug line instruction, create one with
-  // |file_id, line, col|. If |inst| has debug line instructions, set
-  // |file_id, line, col| from the last. |file_id| equals 0 indicates no line
-  // info is available. Return true if |inst| modified.
-  bool PropagateLine(Instruction* inst, uint32_t* file_id, uint32_t* line,
-                     uint32_t* col);
-
-  // If last debug line instruction of |inst| matches |file_id, line, col|,
-  // delete all debug line instructions of |inst|. If they do not match,
-  // replace all debug line instructions of |inst| with new line instruction
-  // set from |file_id, line, col|. If |inst| has no debug line instructions,
-  // do not modify |inst|. |file_id| equals 0 indicates no line info is
-  // available. Return true if |inst| modified.
-  bool EliminateDeadLines(Instruction* inst, uint32_t* file_id, uint32_t* line,
-                          uint32_t* col);
-
-  // Apply lpfn() to all type, constant, global variable and function
-  // instructions in their physical order.
-  bool ProcessLines();
-
-  // A function that calls either PropagateLine or EliminateDeadLines.
-  // Initialized by the class constructor.
-  LineProcessFunction line_process_func_;
-};
-
-}  // namespace opt
-}  // namespace spvtools
-
-#endif  // SOURCE_OPT_PROPAGATE_LINES_PASS_H_
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 21a6529..3426958 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -78,7 +78,6 @@
        pass_remove_duplicates_test.cpp
        pass_utils.cpp
        private_to_local_test.cpp
-       process_lines_test.cpp
        propagator_test.cpp
        reduce_load_size_test.cpp
        redundancy_elimination_test.cpp
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
index 3abb53d..41ce31d 100644
--- a/test/opt/dead_branch_elim_test.cpp
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -3377,7 +3377,7 @@
 %18 = OpLabel
 
 ; CHECK: DebugScope [[bb3]]
-; CHECK-NOT: OpLine {{%\w+}} 3 0
+; CHECK: OpLine {{%\w+}} 3 0
 ; CHECK: DebugValue [[dbg_foo]] [[value]]
 ; CHECK: OpLine {{%\w+}} 4 0
 ; CHECK: OpStore %gl_FragColor [[value]]
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
index 506a2ce..67a4968 100644
--- a/test/opt/inst_bindless_check_test.cpp
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -2103,7 +2103,7 @@
 %uint_7 = OpConstant %uint 7
 %uint_8 = OpConstant %uint 8
 %uint_9 = OpConstant %uint 9
-%uint_93 = OpConstant %uint 93
+%uint_109 = OpConstant %uint 109
 %125 = OpConstantNull %v4float
 )";
 
@@ -2180,19 +2180,23 @@
 %54 = OpSampledImage %37 %52 %53
 %55 = OpAccessChain %_ptr_Function_v2float %i %int_0
 %56 = OpLoad %v2float %55
+OpNoLine
 %62 = OpULessThan %bool %50 %uint_128
 OpSelectionMerge %63 None
 OpBranchConditional %62 %64 %65
 %64 = OpLabel
 %66 = OpLoad %27 %51
 %67 = OpSampledImage %37 %66 %53
+OpLine %5 24 0
 %68 = OpImageSampleImplicitLod %v4float %67 %56
+OpNoLine
 OpBranch %63
 %65 = OpLabel
-%124 = OpFunctionCall %void %69 %uint_93 %uint_0 %50 %uint_128
+%124 = OpFunctionCall %void %69 %uint_109 %uint_0 %50 %uint_128
 OpBranch %63
 %63 = OpLabel
 %126 = OpPhi %v4float %68 %64 %125 %65
+OpLine %5 24 0
 %58 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
 OpStore %58 %126
 OpLine %5 25 0
diff --git a/test/opt/ir_loader_test.cpp b/test/opt/ir_loader_test.cpp
index 2104492..8b77aa3 100644
--- a/test/opt/ir_loader_test.cpp
+++ b/test/opt/ir_loader_test.cpp
@@ -21,6 +21,7 @@
 
 #include "gtest/gtest.h"
 #include "source/opt/build_module.h"
+#include "source/opt/def_use_manager.h"
 #include "source/opt/ir_context.h"
 #include "spirv-tools/libspirv.hpp"
 
@@ -28,6 +29,8 @@
 namespace opt {
 namespace {
 
+constexpr uint32_t kOpLineOperandLineIndex = 1;
+
 void DoRoundTripCheck(const std::string& text) {
   SpirvTools t(SPV_ENV_UNIVERSAL_1_1);
   std::unique_ptr<IRContext> context =
@@ -129,6 +132,110 @@
   // clang-format on
 }
 
+TEST(IrBuilder, DistributeLineDebugInfo) {
+  const std::string text =
+      // clang-format off
+               "OpCapability Shader\n"
+          "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+               "OpMemoryModel Logical GLSL450\n"
+               "OpEntryPoint Vertex %main \"main\"\n"
+               "OpSource ESSL 310\n"
+       "%file = OpString \"test\"\n"
+               "OpName %main \"main\"\n"
+               "OpName %f_ \"f(\"\n"
+               "OpName %gv1 \"gv1\"\n"
+               "OpName %gv2 \"gv2\"\n"
+               "OpName %lv1 \"lv1\"\n"
+               "OpName %lv2 \"lv2\"\n"
+               "OpName %lv1_0 \"lv1\"\n"
+       "%void = OpTypeVoid\n"
+         "%10 = OpTypeFunction %void\n"
+               "OpLine %file 10 0\n"
+      "%float = OpTypeFloat 32\n"
+         "%12 = OpTypeFunction %float\n"
+ "%_ptr_Private_float = OpTypePointer Private %float\n"
+        "%gv1 = OpVariable %_ptr_Private_float Private\n"
+   "%float_10 = OpConstant %float 10\n"
+        "%gv2 = OpVariable %_ptr_Private_float Private\n"
+  "%float_100 = OpConstant %float 100\n"
+ "%_ptr_Function_float = OpTypePointer Function %float\n"
+       "%main = OpFunction %void None %10\n"
+         "%17 = OpLabel\n"
+      "%lv1_0 = OpVariable %_ptr_Function_float Function\n"
+               "OpStore %gv1 %float_10\n"
+               "OpStore %gv2 %float_100\n"
+               "OpLine %file 1 0\n"
+               "OpNoLine\n"
+               "OpLine %file 2 0\n"
+         "%18 = OpLoad %float %gv1\n"
+         "%19 = OpLoad %float %gv2\n"
+         "%20 = OpFSub %float %18 %19\n"
+               "OpStore %lv1_0 %20\n"
+               "OpReturn\n"
+               "OpFunctionEnd\n"
+         "%f_ = OpFunction %float None %12\n"
+         "%21 = OpLabel\n"
+        "%lv1 = OpVariable %_ptr_Function_float Function\n"
+        "%lv2 = OpVariable %_ptr_Function_float Function\n"
+               "OpLine %file 3 0\n"
+               "OpLine %file 4 0\n"
+         "%22 = OpLoad %float %gv1\n"
+         "%23 = OpLoad %float %gv2\n"
+         "%24 = OpFAdd %float %22 %23\n"
+               "OpStore %lv1 %24\n"
+               "OpLine %file 5 0\n"
+               "OpLine %file 6 0\n"
+               "OpNoLine\n"
+         "%25 = OpLoad %float %gv1\n"
+         "%26 = OpLoad %float %gv2\n"
+         "%27 = OpFMul %float %25 %26\n"
+               "OpBranch %28\n"
+         "%28 = OpLabel\n"
+               "OpStore %lv2 %27\n"
+         "%29 = OpLoad %float %lv1\n"
+               "OpLine %file 7 0\n"
+         "%30 = OpLoad %float %lv2\n"
+         "%31 = OpFDiv %float %28 %29\n"
+               "OpReturnValue %30\n"
+               "OpFunctionEnd\n";
+  // clang-format on
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  struct LineInstrCheck {
+    uint32_t id;
+    std::vector<uint32_t> line_numbers;
+  };
+  const uint32_t kNoLine = 0;
+  const LineInstrCheck line_checks[] = {
+      {12, {10}},   {18, {1, kNoLine, 2}},
+      {19, {2}},    {20, {2}},
+      {22, {3, 4}}, {23, {4}},
+      {24, {4}},    {25, {5, 6, kNoLine}},
+      {26, {}},     {27, {}},
+      {28, {}},     {29, {}},
+      {30, {7}},    {31, {7}},
+  };
+
+  spvtools::opt::analysis::DefUseManager* def_use_mgr =
+      context->get_def_use_mgr();
+  for (const LineInstrCheck& check : line_checks) {
+    auto& lines = def_use_mgr->GetDef(check.id)->dbg_line_insts();
+    for (uint32_t i = 0; i < check.line_numbers.size(); ++i) {
+      if (check.line_numbers[i] == kNoLine) {
+        EXPECT_EQ(lines[i].opcode(), SpvOpNoLine);
+        continue;
+      }
+      EXPECT_EQ(lines[i].opcode(), SpvOpLine);
+      EXPECT_EQ(lines[i].GetSingleWordOperand(kOpLineOperandLineIndex),
+                check.line_numbers[i]);
+    }
+  }
+}
+
 TEST(IrBuilder, ConsumeDebugInfoInst) {
   // /* HLSL */
   //
diff --git a/test/opt/local_access_chain_convert_test.cpp b/test/opt/local_access_chain_convert_test.cpp
index 521e0c8..6fcf23f 100644
--- a/test/opt/local_access_chain_convert_test.cpp
+++ b/test/opt/local_access_chain_convert_test.cpp
@@ -160,13 +160,10 @@
 ; CHECK: [[st_id:%\w+]] = OpLoad %v4float %BaseColor
 ; CHECK: OpLine {{%\w+}} 1 0
 ; CHECK: [[ld1:%\w+]] = OpLoad %S_t %s0
-; CHECK: OpLine {{%\w+}} 1 0
 ; CHECK: [[ex1:%\w+]] = OpCompositeInsert %S_t [[st_id]] [[ld1]] 1
-; CHECK: OpLine {{%\w+}} 1 0
 ; CHECK: OpStore %s0 [[ex1]]
 ; CHECK: OpLine {{%\w+}} 3 0
 ; CHECK: [[ld2:%\w+]] = OpLoad %S_t %s0
-; CHECK: OpLine {{%\w+}} 3 0
 ; CHECK: [[ex2:%\w+]] = OpCompositeExtract %v4float [[ld2]] 1
 ; CHECK: OpLine {{%\w+}} 4 0
 ; CHECK: OpStore %gl_FragColor [[ex2]]
@@ -263,15 +260,12 @@
 ; CHECK: DebugValue [[dbg_s0:%\w+]] [[s0_1_ptr]]
 ; CHECK: OpLine {{%\w+}} 1 0
 ; CHECK: [[s0:%\w+]] = OpLoad %S_t %s0
-; CHECK: OpLine {{%\w+}} 1 0
 ; CHECK: [[comp:%\w+]] = OpCompositeInsert %S_t [[st_id]] [[s0]] 1
-; CHECK: OpLine {{%\w+}} 1 0
 ; CHECK: OpStore %s0 [[comp]]
 ; CHECK: OpLine {{%\w+}} 2 0
 ; CHECK: [[s0_2_ptr:%\w+]] = OpAccessChain %_ptr_Function_v4float %s0 %int_1
 ; CHECK: OpLine {{%\w+}} 3 0
 ; CHECK: [[s0:%\w+]] = OpLoad %S_t %s0
-; CHECK: OpLine {{%\w+}} 3 0
 ; CHECK: [[s0_2_val:%\w+]] = OpCompositeExtract %v4float [[s0]] 1
 ; CHECK: DebugValue [[dbg_s0]] [[s0_2_val]]
 ; CHECK: OpLine {{%\w+}} 4 0
diff --git a/test/opt/process_lines_test.cpp b/test/opt/process_lines_test.cpp
deleted file mode 100644
index 33ad4be..0000000
--- a/test/opt/process_lines_test.cpp
+++ /dev/null
@@ -1,695 +0,0 @@
-// Copyright (c) 2017 Valve Corporation
-// Copyright (c) 2017 LunarG Inc.
-//
-// 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 <memory>
-#include <string>
-#include <vector>
-
-#include "test/opt/pass_fixture.h"
-#include "test/opt/pass_utils.h"
-
-namespace spvtools {
-namespace opt {
-namespace {
-
-using ProcessLinesTest = PassTest<::testing::Test>;
-
-TEST_F(ProcessLinesTest, SimplePropagation) {
-  // Texture2D g_tColor[128];
-  //
-  // layout(push_constant) cbuffer PerViewConstantBuffer_t
-  // {
-  //   uint g_nDataIdx;
-  //   uint g_nDataIdx2;
-  //   bool g_B;
-  // };
-  //
-  // SamplerState g_sAniso;
-  //
-  // struct PS_INPUT
-  // {
-  //   float2 vTextureCoords : TEXCOORD2;
-  // };
-  //
-  // struct PS_OUTPUT
-  // {
-  //   float4 vColor : SV_Target0;
-  // };
-  //
-  // PS_OUTPUT MainPs(PS_INPUT i)
-  // {
-  //   PS_OUTPUT ps_output;
-  //
-  //   uint u;
-  //   if (g_B)
-  //     u = g_nDataIdx;
-  //   else
-  //     u = g_nDataIdx2;
-  //   ps_output.vColor = g_tColor[u].Sample(g_sAniso, i.vTextureCoords.xy);
-  //   return ps_output;
-  // }
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
-OpExecutionMode %MainPs OriginUpperLeft
-%5 = OpString "foo.frag"
-OpSource HLSL 500
-OpName %MainPs "MainPs"
-OpName %PS_INPUT "PS_INPUT"
-OpMemberName %PS_INPUT 0 "vTextureCoords"
-OpName %PS_OUTPUT "PS_OUTPUT"
-OpMemberName %PS_OUTPUT 0 "vColor"
-OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
-OpName %i "i"
-OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
-OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
-OpMemberName %PerViewConstantBuffer_t 1 "g_nDataIdx2"
-OpMemberName %PerViewConstantBuffer_t 2 "g_B"
-OpName %_ ""
-OpName %u "u"
-OpName %ps_output "ps_output"
-OpName %g_tColor "g_tColor"
-OpName %g_sAniso "g_sAniso"
-OpName %i_0 "i"
-OpName %i_vTextureCoords "i.vTextureCoords"
-OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
-OpName %param "param"
-OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
-OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
-OpMemberDecorate %PerViewConstantBuffer_t 2 Offset 8
-OpDecorate %PerViewConstantBuffer_t Block
-OpDecorate %g_tColor DescriptorSet 0
-OpDecorate %g_sAniso DescriptorSet 0
-OpDecorate %i_vTextureCoords Location 0
-OpDecorate %_entryPointOutput_vColor Location 0
-)";
-
-  const std::string before =
-      R"(%void = OpTypeVoid
-%19 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%PS_INPUT = OpTypeStruct %v2float
-%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
-%v4float = OpTypeVector %float 4
-%PS_OUTPUT = OpTypeStruct %v4float
-%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
-%uint = OpTypeInt 32 0
-%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%int = OpTypeInt 32 1
-%int_2 = OpConstant %int 2
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%bool = OpTypeBool
-%uint_0 = OpConstant %uint 0
-%_ptr_Function_uint = OpTypePointer Function %uint
-%int_0 = OpConstant %int 0
-%int_1 = OpConstant %int 1
-%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
-%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint_128 = OpConstant %uint 128
-%_arr_36_uint_128 = OpTypeArray %36 %uint_128
-%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
-%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
-%41 = OpTypeSampler
-%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
-%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
-%43 = OpTypeSampledImage %36
-%_ptr_Function_v2float = OpTypePointer Function %v2float
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%MainPs = OpFunction %void None %19
-%48 = OpLabel
-%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
-%param = OpVariable %_ptr_Function_PS_INPUT Function
-OpLine %5 23 0
-%49 = OpLoad %v2float %i_vTextureCoords
-%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
-OpStore %50 %49
-%51 = OpLoad %PS_INPUT %i_0
-OpStore %param %51
-%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
-%53 = OpCompositeExtract %v4float %52 0
-OpStore %_entryPointOutput_vColor %53
-OpReturn
-OpFunctionEnd
-%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
-%i = OpFunctionParameter %_ptr_Function_PS_INPUT
-%54 = OpLabel
-%u = OpVariable %_ptr_Function_uint Function
-%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
-OpLine %5 27 0
-%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
-%56 = OpLoad %uint %55
-%57 = OpINotEqual %bool %56 %uint_0
-OpSelectionMerge %58 None
-OpBranchConditional %57 %59 %60
-%59 = OpLabel
-OpLine %5 28 0
-%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%62 = OpLoad %uint %61
-OpStore %u %62
-OpBranch %58
-%60 = OpLabel
-OpLine %5 30 0
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
-%64 = OpLoad %uint %63
-OpStore %u %64
-OpBranch %58
-%58 = OpLabel
-OpLine %5 31 0
-%65 = OpLoad %uint %u
-%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
-%67 = OpLoad %36 %66
-%68 = OpLoad %41 %g_sAniso
-%69 = OpSampledImage %43 %67 %68
-%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
-%71 = OpLoad %v2float %70
-%72 = OpImageSampleImplicitLod %v4float %69 %71
-%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
-OpStore %73 %72
-OpLine %5 32 0
-%74 = OpLoad %PS_OUTPUT %ps_output
-OpReturnValue %74
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(OpNoLine
-%void = OpTypeVoid
-OpNoLine
-%19 = OpTypeFunction %void
-OpNoLine
-%float = OpTypeFloat 32
-OpNoLine
-%v2float = OpTypeVector %float 2
-OpNoLine
-%PS_INPUT = OpTypeStruct %v2float
-OpNoLine
-%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
-OpNoLine
-%v4float = OpTypeVector %float 4
-OpNoLine
-%PS_OUTPUT = OpTypeStruct %v4float
-OpNoLine
-%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
-OpNoLine
-%uint = OpTypeInt 32 0
-OpNoLine
-%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
-OpNoLine
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-OpNoLine
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-OpNoLine
-%int = OpTypeInt 32 1
-OpNoLine
-%int_2 = OpConstant %int 2
-OpNoLine
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-OpNoLine
-%bool = OpTypeBool
-OpNoLine
-%uint_0 = OpConstant %uint 0
-OpNoLine
-%_ptr_Function_uint = OpTypePointer Function %uint
-OpNoLine
-%int_0 = OpConstant %int 0
-OpNoLine
-%int_1 = OpConstant %int 1
-OpNoLine
-%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
-OpNoLine
-%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
-OpNoLine
-%uint_128 = OpConstant %uint 128
-OpNoLine
-%_arr_36_uint_128 = OpTypeArray %36 %uint_128
-OpNoLine
-%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
-OpNoLine
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
-OpNoLine
-%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
-OpNoLine
-%41 = OpTypeSampler
-OpNoLine
-%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
-OpNoLine
-%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
-OpNoLine
-%43 = OpTypeSampledImage %36
-OpNoLine
-%_ptr_Function_v2float = OpTypePointer Function %v2float
-OpNoLine
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-OpNoLine
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-OpNoLine
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-OpNoLine
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-OpNoLine
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-OpNoLine
-%MainPs = OpFunction %void None %19
-OpNoLine
-%48 = OpLabel
-OpNoLine
-%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
-OpNoLine
-%param = OpVariable %_ptr_Function_PS_INPUT Function
-OpLine %5 23 0
-%49 = OpLoad %v2float %i_vTextureCoords
-OpLine %5 23 0
-%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
-OpLine %5 23 0
-OpStore %50 %49
-OpLine %5 23 0
-%51 = OpLoad %PS_INPUT %i_0
-OpLine %5 23 0
-OpStore %param %51
-OpLine %5 23 0
-%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
-OpLine %5 23 0
-%53 = OpCompositeExtract %v4float %52 0
-OpLine %5 23 0
-OpStore %_entryPointOutput_vColor %53
-OpLine %5 23 0
-OpReturn
-OpNoLine
-OpFunctionEnd
-OpNoLine
-%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
-OpNoLine
-%i = OpFunctionParameter %_ptr_Function_PS_INPUT
-OpNoLine
-%54 = OpLabel
-OpNoLine
-%u = OpVariable %_ptr_Function_uint Function
-OpNoLine
-%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
-OpLine %5 27 0
-%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
-OpLine %5 27 0
-%56 = OpLoad %uint %55
-OpLine %5 27 0
-%57 = OpINotEqual %bool %56 %uint_0
-OpLine %5 27 0
-OpSelectionMerge %58 None
-OpBranchConditional %57 %59 %60
-OpNoLine
-%59 = OpLabel
-OpLine %5 28 0
-%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-OpLine %5 28 0
-%62 = OpLoad %uint %61
-OpLine %5 28 0
-OpStore %u %62
-OpLine %5 28 0
-OpBranch %58
-OpNoLine
-%60 = OpLabel
-OpLine %5 30 0
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
-OpLine %5 30 0
-%64 = OpLoad %uint %63
-OpLine %5 30 0
-OpStore %u %64
-OpLine %5 30 0
-OpBranch %58
-OpNoLine
-%58 = OpLabel
-OpLine %5 31 0
-%65 = OpLoad %uint %u
-OpLine %5 31 0
-%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
-OpLine %5 31 0
-%67 = OpLoad %36 %66
-OpLine %5 31 0
-%68 = OpLoad %41 %g_sAniso
-OpLine %5 31 0
-%69 = OpSampledImage %43 %67 %68
-OpLine %5 31 0
-%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
-OpLine %5 31 0
-%71 = OpLoad %v2float %70
-OpLine %5 31 0
-%72 = OpImageSampleImplicitLod %v4float %69 %71
-OpLine %5 31 0
-%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
-OpLine %5 31 0
-OpStore %73 %72
-OpLine %5 32 0
-%74 = OpLoad %PS_OUTPUT %ps_output
-OpLine %5 32 0
-OpReturnValue %74
-OpNoLine
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<ProcessLinesPass>(predefs + before, predefs + after,
-                                          false, true, kLinesPropagateLines);
-}
-
-TEST_F(ProcessLinesTest, SimpleElimination) {
-  // Previous test with before and after reversed
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
-OpExecutionMode %MainPs OriginUpperLeft
-%5 = OpString "foo.frag"
-OpSource HLSL 500
-OpName %MainPs "MainPs"
-OpName %PS_INPUT "PS_INPUT"
-OpMemberName %PS_INPUT 0 "vTextureCoords"
-OpName %PS_OUTPUT "PS_OUTPUT"
-OpMemberName %PS_OUTPUT 0 "vColor"
-OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
-OpName %i "i"
-OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
-OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
-OpMemberName %PerViewConstantBuffer_t 1 "g_nDataIdx2"
-OpMemberName %PerViewConstantBuffer_t 2 "g_B"
-OpName %_ ""
-OpName %u "u"
-OpName %ps_output "ps_output"
-OpName %g_tColor "g_tColor"
-OpName %g_sAniso "g_sAniso"
-OpName %i_0 "i"
-OpName %i_vTextureCoords "i.vTextureCoords"
-OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
-OpName %param "param"
-OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
-OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
-OpMemberDecorate %PerViewConstantBuffer_t 2 Offset 8
-OpDecorate %PerViewConstantBuffer_t Block
-OpDecorate %g_tColor DescriptorSet 0
-OpDecorate %g_sAniso DescriptorSet 0
-OpDecorate %i_vTextureCoords Location 0
-OpDecorate %_entryPointOutput_vColor Location 0
-)";
-
-  const std::string before =
-      R"(OpNoLine
-%void = OpTypeVoid
-OpNoLine
-%19 = OpTypeFunction %void
-OpNoLine
-%float = OpTypeFloat 32
-OpNoLine
-%v2float = OpTypeVector %float 2
-OpNoLine
-%PS_INPUT = OpTypeStruct %v2float
-OpNoLine
-%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
-OpNoLine
-%v4float = OpTypeVector %float 4
-OpNoLine
-%PS_OUTPUT = OpTypeStruct %v4float
-OpNoLine
-%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
-OpNoLine
-%uint = OpTypeInt 32 0
-OpNoLine
-%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
-OpNoLine
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-OpNoLine
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-OpNoLine
-%int = OpTypeInt 32 1
-OpNoLine
-%int_2 = OpConstant %int 2
-OpNoLine
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-OpNoLine
-%bool = OpTypeBool
-OpNoLine
-%uint_0 = OpConstant %uint 0
-OpNoLine
-%_ptr_Function_uint = OpTypePointer Function %uint
-OpNoLine
-%int_0 = OpConstant %int 0
-OpNoLine
-%int_1 = OpConstant %int 1
-OpNoLine
-%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
-OpNoLine
-%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
-OpNoLine
-%uint_128 = OpConstant %uint 128
-OpNoLine
-%_arr_36_uint_128 = OpTypeArray %36 %uint_128
-OpNoLine
-%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
-OpNoLine
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
-OpNoLine
-%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
-OpNoLine
-%41 = OpTypeSampler
-OpNoLine
-%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
-OpNoLine
-%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
-OpNoLine
-%43 = OpTypeSampledImage %36
-OpNoLine
-%_ptr_Function_v2float = OpTypePointer Function %v2float
-OpNoLine
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-OpNoLine
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-OpNoLine
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-OpNoLine
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-OpNoLine
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-OpNoLine
-%MainPs = OpFunction %void None %19
-OpNoLine
-%48 = OpLabel
-OpNoLine
-%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
-OpNoLine
-%param = OpVariable %_ptr_Function_PS_INPUT Function
-OpLine %5 23 0
-%49 = OpLoad %v2float %i_vTextureCoords
-OpLine %5 23 0
-%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
-OpLine %5 23 0
-OpStore %50 %49
-OpLine %5 23 0
-%51 = OpLoad %PS_INPUT %i_0
-OpLine %5 23 0
-OpStore %param %51
-OpLine %5 23 0
-%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
-OpLine %5 23 0
-%53 = OpCompositeExtract %v4float %52 0
-OpLine %5 23 0
-OpStore %_entryPointOutput_vColor %53
-OpLine %5 23 0
-OpReturn
-OpNoLine
-OpFunctionEnd
-OpNoLine
-%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
-OpNoLine
-%i = OpFunctionParameter %_ptr_Function_PS_INPUT
-OpNoLine
-%54 = OpLabel
-OpNoLine
-%u = OpVariable %_ptr_Function_uint Function
-OpNoLine
-%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
-OpLine %5 27 0
-%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
-OpLine %5 27 0
-%56 = OpLoad %uint %55
-OpLine %5 27 0
-%57 = OpINotEqual %bool %56 %uint_0
-OpLine %5 27 0
-OpSelectionMerge %58 None
-OpBranchConditional %57 %59 %60
-OpNoLine
-%59 = OpLabel
-OpLine %5 28 0
-%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-OpLine %5 28 0
-%62 = OpLoad %uint %61
-OpLine %5 28 0
-OpStore %u %62
-OpLine %5 28 0
-OpBranch %58
-OpNoLine
-%60 = OpLabel
-OpLine %5 30 0
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
-OpLine %5 30 0
-%64 = OpLoad %uint %63
-OpLine %5 30 0
-OpStore %u %64
-OpLine %5 30 0
-OpBranch %58
-OpNoLine
-%58 = OpLabel
-OpLine %5 31 0
-%65 = OpLoad %uint %u
-OpLine %5 31 0
-%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
-OpLine %5 31 0
-%67 = OpLoad %36 %66
-OpLine %5 31 0
-%68 = OpLoad %41 %g_sAniso
-OpLine %5 31 0
-%69 = OpSampledImage %43 %67 %68
-OpLine %5 31 0
-%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
-OpLine %5 31 0
-%71 = OpLoad %v2float %70
-OpLine %5 31 0
-%72 = OpImageSampleImplicitLod %v4float %69 %71
-OpLine %5 31 0
-%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
-OpLine %5 31 0
-OpStore %73 %72
-OpLine %5 32 0
-%74 = OpLoad %PS_OUTPUT %ps_output
-OpLine %5 32 0
-OpReturnValue %74
-OpNoLine
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%void = OpTypeVoid
-%19 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%PS_INPUT = OpTypeStruct %v2float
-%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
-%v4float = OpTypeVector %float 4
-%PS_OUTPUT = OpTypeStruct %v4float
-%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
-%uint = OpTypeInt 32 0
-%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%int = OpTypeInt 32 1
-%int_2 = OpConstant %int 2
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%bool = OpTypeBool
-%uint_0 = OpConstant %uint 0
-%_ptr_Function_uint = OpTypePointer Function %uint
-%int_0 = OpConstant %int 0
-%int_1 = OpConstant %int 1
-%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
-%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint_128 = OpConstant %uint 128
-%_arr_36_uint_128 = OpTypeArray %36 %uint_128
-%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
-%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
-%41 = OpTypeSampler
-%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
-%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
-%43 = OpTypeSampledImage %36
-%_ptr_Function_v2float = OpTypePointer Function %v2float
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%MainPs = OpFunction %void None %19
-%48 = OpLabel
-%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
-%param = OpVariable %_ptr_Function_PS_INPUT Function
-OpLine %5 23 0
-%49 = OpLoad %v2float %i_vTextureCoords
-%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
-OpStore %50 %49
-%51 = OpLoad %PS_INPUT %i_0
-OpStore %param %51
-%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
-%53 = OpCompositeExtract %v4float %52 0
-OpStore %_entryPointOutput_vColor %53
-OpReturn
-OpFunctionEnd
-%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
-%i = OpFunctionParameter %_ptr_Function_PS_INPUT
-%54 = OpLabel
-%u = OpVariable %_ptr_Function_uint Function
-%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
-OpLine %5 27 0
-%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
-%56 = OpLoad %uint %55
-%57 = OpINotEqual %bool %56 %uint_0
-OpSelectionMerge %58 None
-OpBranchConditional %57 %59 %60
-%59 = OpLabel
-OpLine %5 28 0
-%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%62 = OpLoad %uint %61
-OpStore %u %62
-OpBranch %58
-%60 = OpLabel
-OpLine %5 30 0
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
-%64 = OpLoad %uint %63
-OpStore %u %64
-OpBranch %58
-%58 = OpLabel
-OpLine %5 31 0
-%65 = OpLoad %uint %u
-%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
-%67 = OpLoad %36 %66
-%68 = OpLoad %41 %g_sAniso
-%69 = OpSampledImage %43 %67 %68
-%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
-%71 = OpLoad %v2float %70
-%72 = OpImageSampleImplicitLod %v4float %69 %71
-%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
-OpStore %73 %72
-OpLine %5 32 0
-%74 = OpLoad %PS_OUTPUT %ps_output
-OpReturnValue %74
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<ProcessLinesPass>(
-      predefs + before, predefs + after, false, true, kLinesEliminateDeadLines);
-}
-
-// TODO(greg-lunarg): Add tests to verify handling of these cases:
-//
-//    TODO(greg-lunarg): Think about other tests :)
-
-}  // namespace
-}  // namespace opt
-}  // namespace spvtools
diff --git a/test/opt/redundancy_elimination_test.cpp b/test/opt/redundancy_elimination_test.cpp
index b544de4..474f466 100644
--- a/test/opt/redundancy_elimination_test.cpp
+++ b/test/opt/redundancy_elimination_test.cpp
@@ -315,8 +315,10 @@
 ; After removing one `OpFAdd %float %26 %26`, two DebugValues are the same.
 ; One must be removed.
 ;
-; CHECK:      [[add:%\w+]] = OpFAdd %float [[value:%\w+]]
+; CHECK:      OpLine {{%\w+}} 0 0
+; CHECK-NEXT: [[add:%\w+]] = OpFAdd %float [[value:%\w+]]
 ; CHECK-NEXT: DebugValue [[dbg_local_var]] [[add]]
+; CHECK-NEXT: OpLine {{%\w+}} 1 0
 ; CHECK-NEXT: OpFAdd %float [[add]] [[value]]
 ; CHECK-NEXT: OpReturn
          %27 = OpFAdd %float %26 %26
diff --git a/test/opt/wrap_opkill_test.cpp b/test/opt/wrap_opkill_test.cpp
index e944109..e40d701 100644
--- a/test/opt/wrap_opkill_test.cpp
+++ b/test/opt/wrap_opkill_test.cpp
@@ -903,7 +903,7 @@
 ; CHECK-NEXT: {{%\d+}} = OpExtInst %void [[ext:%\d+]] DebugScope
 ; CHECK-NEXT: OpLine [[file:%\d+]] 100 200
 ; CHECK-NEXT: OpFunctionCall %void [[new_kill:%\w+]]
-; CHECK-NEXT: {{%\d+}} = OpExtInst %void [[ext]] DebugNoScope
+; CHECK:      {{%\d+}} = OpExtInst %void [[ext]] DebugNoScope
 ; CHECK-NEXT: OpReturn
 ; CHECK: [[new_kill]] = OpFunction
 ; CHECK-NEXT: OpLabel