Add data structure for DebugScope, DebugDeclare in spirv-opt (#3183)

When DebugScope is given in SPIR-V, each instruction following the
DebugScope is from the lexical scope pointed by the DebugScope in
the high level language. We add DebugScope struction to keep the
scope information in Instruction class. When ir_loader loads
DebugScope/DebugNoScope, it keeps the scope information in
|last_dbg_scope_| and lets following instructions have that scope
information.

In terms of DebugDeclare/DebugValue, if it is in a function body
but outside of a basic block, we keep it in |debug_insts_in_header_|
of Function class. If it is in a basic block, we keep it as a normal
instruction i.e., in a instruction list of BasicBlock.
diff --git a/source/opt/function.cpp b/source/opt/function.cpp
index efda68b..5d50f37 100644
--- a/source/opt/function.cpp
+++ b/source/opt/function.cpp
@@ -34,6 +34,11 @@
       },
       true);
 
+  for (const auto& i : debug_insts_in_header_) {
+    clone->AddDebugInstructionInHeader(
+        std::unique_ptr<Instruction>(i.Clone(ctx)));
+  }
+
   clone->blocks_.reserve(blocks_.size());
   for (const auto& b : blocks_) {
     std::unique_ptr<BasicBlock> bb(b->Clone(ctx));
@@ -79,6 +84,12 @@
     }
   }
 
+  for (auto& di : debug_insts_in_header_) {
+    if (!di.WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
   for (auto& bb : blocks_) {
     if (!bb->WhileEachInst(f, run_on_debug_line_insts)) {
       return false;
@@ -106,6 +117,12 @@
     }
   }
 
+  for (const auto& di : debug_insts_in_header_) {
+    if (!di.WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
   for (const auto& bb : blocks_) {
     if (!static_cast<const BasicBlock*>(bb.get())->WhileEachInst(
             f, run_on_debug_line_insts)) {
diff --git a/source/opt/function.h b/source/opt/function.h
index 3908568..f208d8e 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -56,6 +56,8 @@
 
   // Appends a parameter to this function.
   inline void AddParameter(std::unique_ptr<Instruction> p);
+  // Appends a debug instruction in function header to this function.
+  inline void AddDebugInstructionInHeader(std::unique_ptr<Instruction> p);
   // Appends a basic block to this function.
   inline void AddBasicBlock(std::unique_ptr<BasicBlock> b);
   // Appends a basic block to this function at the position |ip|.
@@ -151,6 +153,8 @@
   std::unique_ptr<Instruction> def_inst_;
   // All parameters to this function.
   std::vector<std::unique_ptr<Instruction>> params_;
+  // All debug instructions in this function's header.
+  InstructionList debug_insts_in_header_;
   // All basic blocks inside this function in specification order
   std::vector<std::unique_ptr<BasicBlock>> blocks_;
   // The OpFunctionEnd instruction.
@@ -167,6 +171,11 @@
   params_.emplace_back(std::move(p));
 }
 
+inline void Function::AddDebugInstructionInHeader(
+    std::unique_ptr<Instruction> p) {
+  debug_insts_in_header_.push_back(std::move(p));
+}
+
 inline void Function::AddBasicBlock(std::unique_ptr<BasicBlock> b) {
   AddBasicBlock(std::move(b), end());
 }
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index 49f9142..b531181 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -16,6 +16,7 @@
 
 #include <initializer_list>
 
+#include "OpenCLDebugInfo100.h"
 #include "source/disassemble.h"
 #include "source/opt/fold.h"
 #include "source/opt/ir_context.h"
@@ -30,6 +31,11 @@
 const uint32_t kLoadBaseIndex = 0;
 const uint32_t kVariableStorageClassIndex = 0;
 const uint32_t kTypeImageSampledIndex = 5;
+
+// Constants for OpenCL.DebugInfo.100 extension instructions.
+const uint32_t kDebugScopeNumWords = 7;
+const uint32_t kDebugScopeNumWordsWithoutInlinedAt = 6;
+const uint32_t kDebugNoScopeNumWords = 5;
 }  // namespace
 
 Instruction::Instruction(IRContext* c)
@@ -38,7 +44,8 @@
       opcode_(SpvOpNop),
       has_type_id_(false),
       has_result_id_(false),
-      unique_id_(c->TakeNextUniqueId()) {}
+      unique_id_(c->TakeNextUniqueId()),
+      dbg_scope_(kNoDebugScope, kNoInlinedAt) {}
 
 Instruction::Instruction(IRContext* c, SpvOp op)
     : utils::IntrusiveNodeBase<Instruction>(),
@@ -46,7 +53,8 @@
       opcode_(op),
       has_type_id_(false),
       has_result_id_(false),
-      unique_id_(c->TakeNextUniqueId()) {}
+      unique_id_(c->TakeNextUniqueId()),
+      dbg_scope_(kNoDebugScope, kNoInlinedAt) {}
 
 Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst,
                          std::vector<Instruction>&& dbg_line)
@@ -55,7 +63,8 @@
       has_type_id_(inst.type_id != 0),
       has_result_id_(inst.result_id != 0),
       unique_id_(c->TakeNextUniqueId()),
-      dbg_line_insts_(std::move(dbg_line)) {
+      dbg_line_insts_(std::move(dbg_line)),
+      dbg_scope_(kNoDebugScope, kNoInlinedAt) {
   assert((!IsDebugLineInst(opcode_) || dbg_line.empty()) &&
          "Op(No)Line attaching to Op(No)Line found");
   for (uint32_t i = 0; i < inst.num_operands; ++i) {
@@ -67,6 +76,23 @@
   }
 }
 
+Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst,
+                         const DebugScope& dbg_scope)
+    : context_(c),
+      opcode_(static_cast<SpvOp>(inst.opcode)),
+      has_type_id_(inst.type_id != 0),
+      has_result_id_(inst.result_id != 0),
+      unique_id_(c->TakeNextUniqueId()),
+      dbg_scope_(dbg_scope) {
+  for (uint32_t i = 0; i < inst.num_operands; ++i) {
+    const auto& current_payload = inst.operands[i];
+    std::vector<uint32_t> words(
+        inst.words + current_payload.offset,
+        inst.words + current_payload.offset + current_payload.num_words);
+    operands_.emplace_back(current_payload.type, std::move(words));
+  }
+}
+
 Instruction::Instruction(IRContext* c, SpvOp op, uint32_t ty_id,
                          uint32_t res_id, const OperandList& in_operands)
     : utils::IntrusiveNodeBase<Instruction>(),
@@ -75,7 +101,8 @@
       has_type_id_(ty_id != 0),
       has_result_id_(res_id != 0),
       unique_id_(c->TakeNextUniqueId()),
-      operands_() {
+      operands_(),
+      dbg_scope_(kNoDebugScope, kNoInlinedAt) {
   if (has_type_id_) {
     operands_.emplace_back(spv_operand_type_t::SPV_OPERAND_TYPE_TYPE_ID,
                            std::initializer_list<uint32_t>{ty_id});
@@ -94,7 +121,12 @@
       has_result_id_(that.has_result_id_),
       unique_id_(that.unique_id_),
       operands_(std::move(that.operands_)),
-      dbg_line_insts_(std::move(that.dbg_line_insts_)) {}
+      dbg_line_insts_(std::move(that.dbg_line_insts_)),
+      dbg_scope_(that.dbg_scope_) {
+  for (auto& i : dbg_line_insts_) {
+    i.dbg_scope_ = that.dbg_scope_;
+  }
+}
 
 Instruction& Instruction::operator=(Instruction&& that) {
   opcode_ = that.opcode_;
@@ -103,6 +135,7 @@
   unique_id_ = that.unique_id_;
   operands_ = std::move(that.operands_);
   dbg_line_insts_ = std::move(that.dbg_line_insts_);
+  dbg_scope_ = that.dbg_scope_;
   return *this;
 }
 
@@ -114,6 +147,7 @@
   clone->unique_id_ = c->TakeNextUniqueId();
   clone->operands_ = operands_;
   clone->dbg_line_insts_ = dbg_line_insts_;
+  clone->dbg_scope_ = dbg_scope_;
   return clone;
 }
 
@@ -735,5 +769,28 @@
   }
 }
 
+void DebugScope::ToBinary(uint32_t type_id, uint32_t result_id,
+                          uint32_t ext_set,
+                          std::vector<uint32_t>* binary) const {
+  uint32_t num_words = kDebugScopeNumWords;
+  OpenCLDebugInfo100Instructions dbg_opcode = OpenCLDebugInfo100DebugScope;
+  if (GetLexicalScope() == kNoDebugScope) {
+    num_words = kDebugNoScopeNumWords;
+    dbg_opcode = OpenCLDebugInfo100DebugNoScope;
+  } else if (GetInlinedAt() == kNoInlinedAt) {
+    num_words = kDebugScopeNumWordsWithoutInlinedAt;
+  }
+  std::vector<uint32_t> operands = {
+      (num_words << 16) | static_cast<uint16_t>(SpvOpExtInst),
+      type_id,
+      result_id,
+      ext_set,
+      static_cast<uint32_t>(dbg_opcode),
+  };
+  binary->insert(binary->end(), operands.begin(), operands.end());
+  if (GetLexicalScope() != kNoDebugScope) binary->push_back(GetLexicalScope());
+  if (GetInlinedAt() != kNoInlinedAt) binary->push_back(GetInlinedAt());
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index 63dfa87..a3342c6 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -32,6 +32,9 @@
 #include "source/opt/reflect.h"
 #include "spirv-tools/libspirv.h"
 
+const uint32_t kNoDebugScope = 0;
+const uint32_t kNoInlinedAt = 0;
+
 namespace spvtools {
 namespace opt {
 
@@ -100,6 +103,44 @@
   return !(o1 == o2);
 }
 
+// This structure is used to represent a DebugScope instruction from
+// the OpenCL.100.DebugInfo extened instruction set. Note that we can
+// ignore the result id of DebugScope instruction because it is not
+// used for anything. We do not keep it to reduce the size of
+// structure.
+// TODO: Let validator check that the result id is not used anywhere.
+class DebugScope {
+ public:
+  DebugScope(uint32_t lexical_scope, uint32_t inlined_at)
+      : lexical_scope_(lexical_scope), inlined_at_(inlined_at) {}
+
+  inline bool operator!=(const DebugScope& d) const {
+    return lexical_scope_ != d.lexical_scope_ || inlined_at_ != d.inlined_at_;
+  }
+
+  // Accessor functions for |lexical_scope_|.
+  uint32_t GetLexicalScope() const { return lexical_scope_; }
+  void SetLexicalScope(uint32_t scope) { lexical_scope_ = scope; }
+
+  // Accessor functions for |inlined_at_|.
+  uint32_t GetInlinedAt() const { return inlined_at_; }
+  void SetInlinedAt(uint32_t at) { inlined_at_ = at; }
+
+  // Pushes the binary segments for this DebugScope instruction into
+  // the back of *|binary|.
+  void ToBinary(uint32_t type_id, uint32_t result_id, uint32_t ext_set,
+                std::vector<uint32_t>* binary) const;
+
+ private:
+  // The result id of the lexical scope in which this debug scope is
+  // contained. The value is kNoDebugScope if there is no scope.
+  uint32_t lexical_scope_;
+
+  // The result id of DebugInlinedAt if instruction in this debug scope
+  // is inlined. The value is kNoInlinedAt if it is not inlined.
+  uint32_t inlined_at_;
+};
+
 // A SPIR-V instruction. It contains the opcode and any additional logical
 // operand, including the result id (if any) and result type id (if any). It
 // may also contain line-related debug instruction (OpLine, OpNoLine) directly
@@ -120,7 +161,8 @@
         opcode_(SpvOpNop),
         has_type_id_(false),
         has_result_id_(false),
-        unique_id_(0) {}
+        unique_id_(0),
+        dbg_scope_(kNoDebugScope, kNoInlinedAt) {}
 
   // Creates a default OpNop instruction.
   Instruction(IRContext*);
@@ -134,6 +176,9 @@
   Instruction(IRContext* c, const spv_parsed_instruction_t& inst,
               std::vector<Instruction>&& dbg_line = {});
 
+  Instruction(IRContext* c, const spv_parsed_instruction_t& inst,
+              const DebugScope& dbg_scope);
+
   // Creates an instruction with the given opcode |op|, type id: |ty_id|,
   // result id: |res_id| and input operands: |in_operands|.
   Instruction(IRContext* c, SpvOp op, uint32_t ty_id, uint32_t res_id,
@@ -230,6 +275,9 @@
   // Sets the result id
   inline void SetResultId(uint32_t res_id);
   inline bool HasResultId() const { return has_result_id_; }
+  // Sets DebugScope.
+  inline void SetDebugScope(const DebugScope& scope);
+  inline const DebugScope& GetDebugScope() const { return dbg_scope_; }
   // Remove the |index|-th operand
   void RemoveOperand(uint32_t index) {
     operands_.erase(operands_.begin() + index);
@@ -482,6 +530,9 @@
   // empty.
   std::vector<Instruction> dbg_line_insts_;
 
+  // DebugScope that wraps this instruction.
+  DebugScope dbg_scope_;
+
   friend InstructionList;
 };
 
@@ -553,6 +604,13 @@
   operands_[ridx].words = {res_id};
 }
 
+inline void Instruction::SetDebugScope(const DebugScope& scope) {
+  dbg_scope_ = scope;
+  for (auto& i : dbg_line_insts_) {
+    i.dbg_scope_ = scope;
+  }
+}
+
 inline void Instruction::SetResultType(uint32_t ty_id) {
   // TODO(dsinclair): Allow setting a type id if there wasn't one
   // previously. Need to make room in the operands_ array to place the result,
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index 836012f..fcde079 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -23,6 +23,10 @@
 #include "source/opt/reflect.h"
 #include "source/util/make_unique.h"
 
+static const uint32_t kExtInstSetIndex = 4;
+static const uint32_t kLexicalScopeIndex = 5;
+static const uint32_t kInlinedAtIndex = 6;
+
 namespace spvtools {
 namespace opt {
 
@@ -30,16 +34,60 @@
     : consumer_(consumer),
       module_(m),
       source_("<instruction>"),
-      inst_index_(0) {}
+      inst_index_(0),
+      last_dbg_scope_(kNoDebugScope, kNoInlinedAt) {}
 
 bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) {
   ++inst_index_;
   const auto opcode = static_cast<SpvOp>(inst->opcode);
   if (IsDebugLineInst(opcode)) {
-    dbg_line_info_.push_back(Instruction(module()->context(), *inst));
+    dbg_line_info_.push_back(
+        Instruction(module()->context(), *inst, last_dbg_scope_));
     return true;
   }
 
+  // If it is a DebugScope or DebugNoScope of debug extension, we do not
+  // create a new instruction, but simply keep the information in
+  // struct DebugScope.
+  if (opcode == SpvOpExtInst && spvExtInstIsDebugInfo(inst->ext_inst_type)) {
+    const uint32_t ext_inst_index = inst->words[kExtInstSetIndex];
+    if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
+      const OpenCLDebugInfo100Instructions ext_inst_key =
+          OpenCLDebugInfo100Instructions(ext_inst_index);
+      if (ext_inst_key == OpenCLDebugInfo100DebugScope) {
+        uint32_t inlined_at = 0;
+        if (inst->num_words > kInlinedAtIndex)
+          inlined_at = inst->words[kInlinedAtIndex];
+        last_dbg_scope_ =
+            DebugScope(inst->words[kLexicalScopeIndex], inlined_at);
+        module()->SetContainsDebugScope();
+        return true;
+      }
+      if (ext_inst_key == OpenCLDebugInfo100DebugNoScope) {
+        last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
+        module()->SetContainsDebugScope();
+        return true;
+      }
+    } else {
+      const DebugInfoInstructions ext_inst_key =
+          DebugInfoInstructions(ext_inst_index);
+      if (ext_inst_key == DebugInfoDebugScope) {
+        uint32_t inlined_at = 0;
+        if (inst->num_words > kInlinedAtIndex)
+          inlined_at = inst->words[kInlinedAtIndex];
+        last_dbg_scope_ =
+            DebugScope(inst->words[kLexicalScopeIndex], inlined_at);
+        module()->SetContainsDebugScope();
+        return true;
+      }
+      if (ext_inst_key == DebugInfoDebugNoScope) {
+        last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
+        module()->SetContainsDebugScope();
+        return true;
+      }
+    }
+  }
+
   std::unique_ptr<Instruction> spv_inst(
       new Instruction(module()->context(), *inst, std::move(dbg_line_info_)));
   dbg_line_info_.clear();
@@ -90,6 +138,7 @@
     block_->AddInstruction(std::move(spv_inst));
     function_->AddBasicBlock(std::move(block_));
     block_ = nullptr;
+    last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
   } else {
     if (function_ == nullptr) {  // Outside function definition
       SPIRV_ASSERT(consumer_, block_ == nullptr);
@@ -131,26 +180,32 @@
         return false;
       }
     } else {
-      if (block_ == nullptr) {  // Inside function but outside blocks
-        if (opcode != SpvOpFunctionParameter) {
-          Errorf(consumer_, src, loc,
-                 "Non-OpFunctionParameter (opcode: %d) found inside "
-                 "function but outside basic block",
-                 opcode);
-          return false;
-        }
-        function_->AddParameter(std::move(spv_inst));
-      } else {
-        if (opcode == SpvOpExtInst &&
-            spvExtInstIsDebugInfo(inst->ext_inst_type)) {
-          const uint32_t ext_inst_index = inst->words[4];
-          if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
-            const OpenCLDebugInfo100Instructions ext_inst_key =
-                OpenCLDebugInfo100Instructions(ext_inst_index);
-            if (ext_inst_key != OpenCLDebugInfo100DebugScope &&
-                ext_inst_key != OpenCLDebugInfo100DebugNoScope &&
-                ext_inst_key != OpenCLDebugInfo100DebugDeclare &&
-                ext_inst_key != OpenCLDebugInfo100DebugValue) {
+      if (opcode == SpvOpLoopMerge || opcode == SpvOpSelectionMerge)
+        last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
+      if (last_dbg_scope_.GetLexicalScope() != kNoDebugScope)
+        spv_inst->SetDebugScope(last_dbg_scope_);
+      if (opcode == SpvOpExtInst &&
+          spvExtInstIsDebugInfo(inst->ext_inst_type)) {
+        const uint32_t ext_inst_index = inst->words[kExtInstSetIndex];
+        if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
+          const OpenCLDebugInfo100Instructions ext_inst_key =
+              OpenCLDebugInfo100Instructions(ext_inst_index);
+          switch (ext_inst_key) {
+            case OpenCLDebugInfo100DebugDeclare: {
+              if (block_ == nullptr)  // Inside function but outside blocks
+                function_->AddDebugInstructionInHeader(std::move(spv_inst));
+              else
+                block_->AddInstruction(std::move(spv_inst));
+              break;
+            }
+            case OpenCLDebugInfo100DebugValue: {
+              if (block_ == nullptr)  // Inside function but outside blocks
+                function_->AddDebugInstructionInHeader(std::move(spv_inst));
+              else
+                block_->AddInstruction(std::move(spv_inst));
+              break;
+            }
+            default: {
               Errorf(consumer_, src, loc,
                      "Debug info extension instruction other than DebugScope, "
                      "DebugNoScope, DebugDeclare, and DebugValue found inside "
@@ -158,13 +213,26 @@
                      opcode);
               return false;
             }
-          } else {
-            const DebugInfoInstructions ext_inst_key =
-                DebugInfoInstructions(ext_inst_index);
-            if (ext_inst_key != DebugInfoDebugScope &&
-                ext_inst_key != DebugInfoDebugNoScope &&
-                ext_inst_key != DebugInfoDebugDeclare &&
-                ext_inst_key != DebugInfoDebugValue) {
+          }
+        } else {
+          const DebugInfoInstructions ext_inst_key =
+              DebugInfoInstructions(ext_inst_index);
+          switch (ext_inst_key) {
+            case DebugInfoDebugDeclare: {
+              if (block_ == nullptr)  // Inside function but outside blocks
+                function_->AddDebugInstructionInHeader(std::move(spv_inst));
+              else
+                block_->AddInstruction(std::move(spv_inst));
+              break;
+            }
+            case DebugInfoDebugValue: {
+              if (block_ == nullptr)  // Inside function but outside blocks
+                function_->AddDebugInstructionInHeader(std::move(spv_inst));
+              else
+                block_->AddInstruction(std::move(spv_inst));
+              break;
+            }
+            default: {
               Errorf(consumer_, src, loc,
                      "Debug info extension instruction other than DebugScope, "
                      "DebugNoScope, DebugDeclare, and DebugValue found inside "
@@ -174,7 +242,19 @@
             }
           }
         }
-        block_->AddInstruction(std::move(spv_inst));
+      } else {
+        if (block_ == nullptr) {  // Inside function but outside blocks
+          if (opcode != SpvOpFunctionParameter) {
+            Errorf(consumer_, src, loc,
+                   "Non-OpFunctionParameter (opcode: %d) found inside "
+                   "function but outside basic block",
+                   opcode);
+            return false;
+          }
+          function_->AddParameter(std::move(spv_inst));
+        } else {
+          block_->AddInstruction(std::move(spv_inst));
+        }
       }
     }
   }
diff --git a/source/opt/ir_loader.h b/source/opt/ir_loader.h
index 940d7b0..5079921 100644
--- a/source/opt/ir_loader.h
+++ b/source/opt/ir_loader.h
@@ -78,6 +78,9 @@
   std::unique_ptr<BasicBlock> block_;
   // Line related debug instructions accumulated thus far.
   std::vector<Instruction> dbg_line_info_;
+
+  // The last DebugScope information that IrLoader::AddInstruction() handled.
+  DebugScope last_dbg_scope_;
 };
 
 }  // namespace opt
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index 4403894..2959d3d 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -137,10 +137,27 @@
   binary->push_back(header_.bound);
   binary->push_back(header_.reserved);
 
-  auto write_inst = [binary, skip_nop](const Instruction* i) {
-    if (!(skip_nop && i->IsNop())) i->ToBinaryWithoutAttachedDebugInsts(binary);
+  size_t bound_idx = binary->size() - 2;
+  DebugScope last_scope(kNoDebugScope, kNoInlinedAt);
+  auto write_inst = [binary, skip_nop, &last_scope,
+                     this](const Instruction* i) {
+    if (!(skip_nop && i->IsNop())) {
+      const auto& scope = i->GetDebugScope();
+      if (scope != last_scope) {
+        // Emit DebugScope |scope| to |binary|.
+        auto dbg_inst = ext_inst_debuginfo_.begin();
+        scope.ToBinary(dbg_inst->type_id(), context()->TakeNextId(),
+                       dbg_inst->GetSingleWordOperand(2), binary);
+        last_scope = scope;
+      }
+
+      i->ToBinaryWithoutAttachedDebugInsts(binary);
+    }
   };
   ForEachInst(write_inst, true);
+
+  // We create new instructions for DebugScope. The bound must be updated.
+  binary->data()[bound_idx] = header_.bound;
 }
 
 uint32_t Module::ComputeIdBound() const {
diff --git a/source/opt/module.h b/source/opt/module.h
index fc53d35..2c96f02 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -17,6 +17,7 @@
 
 #include <functional>
 #include <memory>
+#include <unordered_map>
 #include <utility>
 #include <vector>
 
@@ -48,7 +49,7 @@
   using const_inst_iterator = InstructionList::const_iterator;
 
   // Creates an empty module with zero'd header.
-  Module() : header_({}) {}
+  Module() : header_({}), contains_debug_scope_(false) {}
 
   // Sets the header to the given |header|.
   void SetHeader(const ModuleHeader& header) { header_ = header; }
@@ -118,6 +119,10 @@
   // Appends a function to this module.
   inline void AddFunction(std::unique_ptr<Function> f);
 
+  // Sets |contains_debug_scope_| as true.
+  inline void SetContainsDebugScope();
+  inline bool ContainsDebugScope() { return contains_debug_scope_; }
+
   // Returns a vector of pointers to type-declaration instructions in this
   // module.
   std::vector<Instruction*> GetTypes();
@@ -295,6 +300,9 @@
   // 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_;
+
+  // This module contains DebugScope or DebugNoScope.
+  bool contains_debug_scope_;
 };
 
 // Pretty-prints |module| to |str|. Returns |str|.
@@ -356,6 +364,8 @@
   functions_.emplace_back(std::move(f));
 }
 
+inline void Module::SetContainsDebugScope() { contains_debug_scope_ = true; }
+
 inline Module::inst_iterator Module::capability_begin() {
   return capabilities_.begin();
 }
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 6e271f5..0a937e8 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -569,7 +569,12 @@
   }
 
 #ifndef NDEBUG
-  if (status == opt::Pass::Status::SuccessWithoutChange) {
+  // We do not keep the result id of DebugScope in struct DebugScope.
+  // Instead, we assign random ids for them, which results in sanity
+  // check failures. We want to skip the sanity check when the module
+  // contains DebugScope instructions.
+  if (status == opt::Pass::Status::SuccessWithoutChange &&
+      !context->module()->ContainsDebugScope()) {
     std::vector<uint32_t> optimized_binary_with_nop;
     context->module()->ToBinary(&optimized_binary_with_nop,
                                 /* skip_nop = */ false);
diff --git a/test/opt/ir_loader_test.cpp b/test/opt/ir_loader_test.cpp
index c60e853..50e3a08 100644
--- a/test/opt/ir_loader_test.cpp
+++ b/test/opt/ir_loader_test.cpp
@@ -234,34 +234,33 @@
 %44 = OpExtInst %void %1 DebugLocalVariable %16 %40 %34 6 16 %43 FlagIsLocal 0
 %45 = OpExtInst %void %1 DebugLocalVariable %17 %40 %34 7 16 %43 FlagIsLocal 1
 %46 = OpExtInst %void %1 DebugLocalVariable %18 %36 %34 8 3 %43 FlagIsLocal
-%47 = OpExtInst %void %1 DebugDeclare %44 %pos %42
-%48 = OpExtInst %void %1 DebugDeclare %45 %color %42
 OpLine %7 6 1
 %main = OpFunction %void None %31
-%49 = OpLabel
-%50 = OpExtInst %void %1 DebugScope %43
+%47 = OpLabel
+%60 = OpExtInst %void %1 DebugScope %43
 OpLine %7 8 13
 %vout = OpVariable %_ptr_Function_VS_OUTPUT Function
-%51 = OpExtInst %void %1 DebugDeclare %46 %vout %42
+%49 = OpExtInst %void %1 DebugDeclare %46 %vout %42
 OpLine %7 9 14
-%52 = OpLoad %v4float %pos
+%50 = OpLoad %v4float %pos
 OpLine %7 9 3
-%53 = OpAccessChain %_ptr_Function_v4float %vout %int_0
-%54 = OpExtInst %void %1 DebugValue %46 %53 %42 %int_0
-OpStore %53 %52
+%51 = OpAccessChain %_ptr_Function_v4float %vout %int_0
+%52 = OpExtInst %void %1 DebugValue %46 %51 %42 %int_0
+OpStore %51 %50
 OpLine %7 10 16
-%55 = OpLoad %v4float %color
+%53 = OpLoad %v4float %color
 OpLine %7 10 3
-%56 = OpAccessChain %_ptr_Function_v4float %vout %int_1
-%57 = OpExtInst %void %1 DebugValue %46 %56 %42 %int_1
-OpStore %56 %55
+%54 = OpAccessChain %_ptr_Function_v4float %vout %int_1
+%55 = OpExtInst %void %1 DebugValue %46 %54 %42 %int_1
+OpStore %54 %53
 OpLine %7 11 10
-%58 = OpLoad %VS_OUTPUT %vout
+%56 = OpLoad %VS_OUTPUT %vout
 OpLine %7 11 3
-%59 = OpCompositeExtract %v4float %58 0
-OpStore %gl_Position %59
-%60 = OpCompositeExtract %v4float %58 1
-OpStore %out_var_COLOR %60
+%57 = OpCompositeExtract %v4float %56 0
+OpStore %gl_Position %57
+%58 = OpCompositeExtract %v4float %56 1
+OpStore %out_var_COLOR %58
+%61 = OpExtInst %void %1 DebugNoScope
 OpReturn
 OpFunctionEnd
 )");
@@ -377,7 +376,7 @@
 OpLine %5 12 1
 %main = OpFunction %void None %36
 %bb_entry = OpLabel
-%52 = OpExtInst %void %1 DebugScope %47
+%70 = OpExtInst %void %1 DebugScope %47
 OpLine %5 13 16
 %param_var_arg1 = OpVariable %_ptr_Function_float Function
 %53 = OpLoad %float %pos
@@ -386,6 +385,7 @@
 %54 = OpFunctionCall %v4float %func1 %param_var_arg1
 OpLine %5 13 3
 OpStore %gl_Position %54
+%71 = OpExtInst %void %1 DebugNoScope
 OpReturn
 OpFunctionEnd
 OpLine %5 5 1
@@ -393,41 +393,45 @@
 OpLine %5 5 20
 %arg1 = OpFunctionParameter %_ptr_Function_float
 %bb_entry_0 = OpLabel
-%55 = OpExtInst %void %1 DebugScope %48
+%72 = OpExtInst %void %1 DebugScope %48
 OpLine %5 9 16
 %param_var_arg2 = OpVariable %_ptr_Function_float Function
 OpLine %5 6 7
-%56 = OpLoad %float %arg1
+%57 = OpLoad %float %arg1
 OpLine %5 6 12
-%57 = OpFOrdGreaterThan %bool %56 %float_1
+%58 = OpFOrdGreaterThan %bool %57 %float_1
 OpLine %5 6 17
+%73 = OpExtInst %void %1 DebugNoScope
 OpSelectionMerge %if_merge None
-OpBranchConditional %57 %if_true %if_merge
+OpBranchConditional %58 %if_true %if_merge
 %if_true = OpLabel
-%58 = OpExtInst %void %1 DebugScope %50
+%74 = OpExtInst %void %1 DebugScope %50
 OpLine %5 7 5
+%75 = OpExtInst %void %1 DebugNoScope
 OpReturnValue %32
 %if_merge = OpLabel
-%59 = OpExtInst %void %1 DebugScope %51
+%76 = OpExtInst %void %1 DebugScope %51
 OpLine %5 9 16
-%60 = OpLoad %float %arg1
-OpStore %param_var_arg2 %60
+%63 = OpLoad %float %arg1
+OpStore %param_var_arg2 %63
 OpLine %5 9 10
-%61 = OpFunctionCall %v4float %func2 %param_var_arg2
+%64 = OpFunctionCall %v4float %func2 %param_var_arg2
 OpLine %5 9 3
-OpReturnValue %61
+%77 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %64
 OpFunctionEnd
 OpLine %5 1 1
 %func2 = OpFunction %v4float None %38
 OpLine %5 1 20
 %arg2 = OpFunctionParameter %_ptr_Function_float
 %bb_entry_1 = OpLabel
-%62 = OpExtInst %void %1 DebugScope %49
+%78 = OpExtInst %void %1 DebugScope %49
 OpLine %5 2 17
-%63 = OpLoad %float %arg2
-%64 = OpCompositeConstruct %v4float %63 %float_0 %float_0 %float_0
+%67 = OpLoad %float %arg2
+%68 = OpCompositeConstruct %v4float %67 %float_0 %float_0 %float_0
 OpLine %5 2 3
-OpReturnValue %64
+%79 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %68
 OpFunctionEnd
 )");
 }
@@ -547,28 +551,31 @@
 OpLine %5 12 1
 %main = OpFunction %void None %28
 %bb_entry = OpLabel
-%51 = OpExtInst %void %1 DebugScope %44 %49
+%62 = OpExtInst %void %1 DebugScope %44 %49
 OpLine %5 6 7
 %52 = OpLoad %float %pos
 OpLine %5 6 12
 %53 = OpFOrdGreaterThan %bool %52 %float_1
 OpLine %5 6 17
+%63 = OpExtInst %void %1 DebugNoScope
 OpSelectionMerge %if_merge None
 OpBranchConditional %53 %if_true %if_merge
 %if_true = OpLabel
-%54 = OpExtInst %void %1 DebugScope %46 %49
+%64 = OpExtInst %void %1 DebugScope %46 %49
 OpLine %5 7 5
 OpStore %gl_Position %24
+%65 = OpExtInst %void %1 DebugNoScope
 OpReturn
 %if_merge = OpLabel
-%55 = OpExtInst %void %1 DebugScope %45 %50
+%66 = OpExtInst %void %1 DebugScope %45 %50
 OpLine %5 2 17
-%56 = OpLoad %float %pos
+%58 = OpLoad %float %pos
 OpLine %5 2 10
-%57 = OpCompositeConstruct %v4float %56 %float_0 %float_0 %float_0
-%58 = OpExtInst %void %1 DebugScope %43
+%59 = OpCompositeConstruct %v4float %58 %float_0 %float_0 %float_0
+%67 = OpExtInst %void %1 DebugScope %43
 OpLine %5 13 3
-OpStore %gl_Position %57
+OpStore %gl_Position %59
+%68 = OpExtInst %void %1 DebugNoScope
 OpReturn
 OpFunctionEnd
 )");
@@ -683,8 +690,8 @@
 %51 = OpExtInst %void %1 DebugLexicalBlock %40 9 3 %48
 OpLine %5 12 1
 %main = OpFunction %void None %36
-%52 = OpExtInst %void %1 DebugScope %47
 %bb_entry = OpLabel
+%70 = OpExtInst %void %1 DebugScope %47
 OpLine %5 13 16
 %param_var_arg1 = OpVariable %_ptr_Function_float Function
 %53 = OpLoad %float %pos
@@ -693,6 +700,7 @@
 %54 = OpFunctionCall %v4float %func1 %param_var_arg1
 OpLine %5 13 3
 OpStore %gl_Position %54
+%71 = OpExtInst %void %1 DebugNoScope
 OpReturn
 OpFunctionEnd
 OpLine %5 5 1
@@ -700,48 +708,244 @@
 OpLine %5 5 20
 %arg1 = OpFunctionParameter %_ptr_Function_float
 %bb_entry_0 = OpLabel
-%55 = OpExtInst %void %1 DebugScope %48
+%72 = OpExtInst %void %1 DebugScope %48
 OpLine %5 9 16
 %param_var_arg2 = OpVariable %_ptr_Function_float Function
 OpLine %5 6 7
-%56 = OpLoad %float %arg1
+%57 = OpLoad %float %arg1
 OpLine %5 6 12
-%57 = OpFOrdGreaterThan %bool %56 %float_1
+%58 = OpFOrdGreaterThan %bool %57 %float_1
 OpLine %5 6 17
+%73 = OpExtInst %void %1 DebugNoScope
 OpSelectionMerge %if_merge None
-OpBranchConditional %57 %if_true %if_merge
+OpBranchConditional %58 %if_true %if_merge
 %if_true = OpLabel
-%58 = OpExtInst %void %1 DebugScope %50
+%74 = OpExtInst %void %1 DebugScope %50
 OpLine %5 7 5
+%75 = OpExtInst %void %1 DebugNoScope
 OpReturnValue %32
 %if_merge = OpLabel
-%59 = OpExtInst %void %1 DebugScope %51
+%76 = OpExtInst %void %1 DebugScope %51
 OpLine %5 9 16
-%60 = OpLoad %float %arg1
-OpStore %param_var_arg2 %60
+%63 = OpLoad %float %arg1
+OpStore %param_var_arg2 %63
 OpLine %5 9 10
-%61 = OpFunctionCall %v4float %func2 %param_var_arg2
+%64 = OpFunctionCall %v4float %func2 %param_var_arg2
 OpLine %5 9 3
-OpReturnValue %61
+%77 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %64
 OpFunctionEnd
 OpLine %5 1 1
 %func2 = OpFunction %v4float None %38
 OpLine %5 1 20
 %arg2 = OpFunctionParameter %_ptr_Function_float
 %bb_entry_1 = OpLabel
-%62 = OpExtInst %void %1 DebugScope %49
+%78 = OpExtInst %void %1 DebugScope %49
 OpLine %5 2 17
-%63 = OpLoad %float %arg2
-%64 = OpCompositeConstruct %v4float %63 %float_0 %float_0 %float_0
+%67 = OpLoad %float %arg2
+%68 = OpCompositeConstruct %v4float %67 %float_0 %float_0 %float_0
 OpLine %5 2 3
-OpReturnValue %64
+%79 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %68
 OpFunctionEnd
 )";
 
   SpirvTools t(SPV_ENV_UNIVERSAL_1_1);
   std::unique_ptr<IRContext> context =
       BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
-  ASSERT_EQ(nullptr, context);
+  ASSERT_NE(nullptr, context);
+
+  std::vector<uint32_t> binary;
+  context->module()->ToBinary(&binary, /* skip_nop = */ false);
+
+  std::string disassembled_text;
+  EXPECT_TRUE(t.Disassemble(binary, &disassembled_text));
+  EXPECT_EQ(text, disassembled_text);
+}
+
+TEST(IrBuilder, DebugInfoInstInFunctionOutOfBlock2) {
+  // /* HLSL */
+  //
+  // struct VS_OUTPUT {
+  //   float4 pos : SV_POSITION;
+  //   float4 color : COLOR;
+  // };
+  //
+  // VS_OUTPUT main(float4 pos : POSITION,
+  //                float4 color : COLOR) {
+  //   VS_OUTPUT vout;
+  //   vout.pos = pos;
+  //   vout.color = color;
+  //   return vout;
+  // }
+  const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %in_var_POSITION %in_var_COLOR %gl_Position %out_var_COLOR
+%7 = OpString "vs.hlsl"
+OpSource HLSL 600 %7 "#line 1 \"vs.hlsl\"
+struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+  float4 color : COLOR;
+};
+
+VS_OUTPUT main(float4 pos : POSITION,
+               float4 color : COLOR) {
+  VS_OUTPUT vout;
+  vout.pos = pos;
+  vout.color = color;
+  return vout;
+}
+"
+%8 = OpString "#line 1 \"vs.hlsl\"
+struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+  float4 color : COLOR;
+};
+
+VS_OUTPUT main(float4 pos : POSITION,
+               float4 color : COLOR) {
+  VS_OUTPUT vout;
+  vout.pos = pos;
+  vout.color = color;
+  return vout;
+}
+"
+%9 = OpString "VS_OUTPUT"
+%10 = OpString "float"
+%11 = OpString "src.main"
+%12 = OpString "pos"
+%13 = OpString "color"
+%14 = OpString "vout"
+OpName %in_var_POSITION "in.var.POSITION"
+OpName %in_var_COLOR "in.var.COLOR"
+OpName %out_var_COLOR "out.var.COLOR"
+OpName %main "main"
+OpName %param_var_pos "param.var.pos"
+OpName %param_var_color "param.var.color"
+OpName %VS_OUTPUT "VS_OUTPUT"
+OpMemberName %VS_OUTPUT 0 "pos"
+OpMemberName %VS_OUTPUT 1 "color"
+OpName %src_main "src.main"
+OpName %pos "pos"
+OpName %color "color"
+OpName %bb_entry "bb.entry"
+OpName %vout "vout"
+OpDecorate %gl_Position BuiltIn Position
+OpDecorate %in_var_POSITION Location 0
+OpDecorate %in_var_COLOR Location 1
+OpDecorate %out_var_COLOR Location 0
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%void = OpTypeVoid
+%uint_256 = OpConstant %uint 256
+%uint_0 = OpConstant %uint 0
+%uint_128 = OpConstant %uint 128
+%36 = OpTypeFunction %void
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%VS_OUTPUT = OpTypeStruct %v4float %v4float
+%38 = OpTypeFunction %VS_OUTPUT %_ptr_Function_v4float %_ptr_Function_v4float
+%_ptr_Function_VS_OUTPUT = OpTypePointer Function %VS_OUTPUT
+OpLine %7 6 29
+%in_var_POSITION = OpVariable %_ptr_Input_v4float Input
+OpLine %7 7 31
+%in_var_COLOR = OpVariable %_ptr_Input_v4float Input
+OpLine %7 2 16
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+OpLine %7 3 18
+%out_var_COLOR = OpVariable %_ptr_Output_v4float Output
+%40 = OpExtInst %void %1 DebugExpression
+%41 = OpExtInst %void %1 DebugSource %7 %8
+%42 = OpExtInst %void %1 DebugCompilationUnit 1 4 %41 HLSL
+%43 = OpExtInst %void %1 DebugTypeComposite %9 Structure %41 1 1 %42 %9 %uint_256 FlagIsProtected|FlagIsPrivate %44 %45
+%46 = OpExtInst %void %1 DebugTypeBasic %10 %uint_32 Float
+%47 = OpExtInst %void %1 DebugTypeVector %46 4
+%48 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %43 %47 %47
+%49 = OpExtInst %void %1 DebugFunction %11 %48 %41 6 1 %42 %11 FlagIsProtected|FlagIsPrivate 7 %src_main
+%50 = OpExtInst %void %1 DebugLocalVariable %12 %47 %41 6 23 %49 FlagIsLocal 0
+%51 = OpExtInst %void %1 DebugLocalVariable %13 %47 %41 7 23 %49 FlagIsLocal 1
+%52 = OpExtInst %void %1 DebugLexicalBlock %41 7 38 %49
+%53 = OpExtInst %void %1 DebugLocalVariable %14 %43 %41 8 13 %52 FlagIsLocal
+%44 = OpExtInst %void %1 DebugTypeMember %12 %47 %41 2 3 %43 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate
+%45 = OpExtInst %void %1 DebugTypeMember %13 %47 %41 3 3 %43 %uint_128 %uint_128 FlagIsProtected|FlagIsPrivate
+OpLine %7 6 1
+%main = OpFunction %void None %36
+%54 = OpLabel
+%74 = OpExtInst %void %1 DebugScope %42
+OpLine %7 6 23
+%param_var_pos = OpVariable %_ptr_Function_v4float Function
+OpLine %7 7 23
+%param_var_color = OpVariable %_ptr_Function_v4float Function
+OpLine %7 6 23
+%56 = OpLoad %v4float %in_var_POSITION
+OpStore %param_var_pos %56
+OpLine %7 7 23
+%57 = OpLoad %v4float %in_var_COLOR
+OpStore %param_var_color %57
+OpLine %7 6 1
+%58 = OpFunctionCall %VS_OUTPUT %src_main %param_var_pos %param_var_color
+OpLine %7 6 11
+%59 = OpCompositeExtract %v4float %58 0
+OpLine %7 2 16
+OpStore %gl_Position %59
+OpLine %7 6 11
+%60 = OpCompositeExtract %v4float %58 1
+OpLine %7 3 18
+OpStore %out_var_COLOR %60
+%75 = OpExtInst %void %1 DebugNoScope
+OpReturn
+OpFunctionEnd
+OpLine %7 6 1
+%src_main = OpFunction %VS_OUTPUT None %38
+%76 = OpExtInst %void %1 DebugScope %49
+OpLine %7 6 23
+%pos = OpFunctionParameter %_ptr_Function_v4float
+OpLine %7 7 23
+%color = OpFunctionParameter %_ptr_Function_v4float
+%63 = OpExtInst %void %1 DebugDeclare %50 %pos %40
+%64 = OpExtInst %void %1 DebugDeclare %51 %color %40
+%77 = OpExtInst %void %1 DebugNoScope
+%bb_entry = OpLabel
+%78 = OpExtInst %void %1 DebugScope %52
+OpLine %7 8 13
+%vout = OpVariable %_ptr_Function_VS_OUTPUT Function
+%67 = OpExtInst %void %1 DebugDeclare %53 %vout %40
+OpLine %7 9 14
+%68 = OpLoad %v4float %pos
+OpLine %7 9 3
+%69 = OpAccessChain %_ptr_Function_v4float %vout %int_0
+OpStore %69 %68
+OpLine %7 10 16
+%70 = OpLoad %v4float %color
+OpLine %7 10 3
+%71 = OpAccessChain %_ptr_Function_v4float %vout %int_1
+OpStore %71 %70
+OpLine %7 11 10
+%72 = OpLoad %VS_OUTPUT %vout
+OpLine %7 11 3
+%79 = OpExtInst %void %1 DebugNoScope
+OpReturnValue %72
+OpFunctionEnd
+)";
+
+  SpirvTools t(SPV_ENV_UNIVERSAL_1_1);
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+  ASSERT_NE(nullptr, context);
+
+  std::vector<uint32_t> binary;
+  context->module()->ToBinary(&binary, /* skip_nop = */ false);
+
+  std::string disassembled_text;
+  EXPECT_TRUE(t.Disassemble(binary, &disassembled_text));
+  EXPECT_EQ(text, disassembled_text);
 }
 
 TEST(IrBuilder, LocalGlobalVariables) {