spirv-opt: Add specific handling of vulkan debug info differences (#4398)

Co-authored-by: baldurk <baldurk@baldurk.org>
diff --git a/source/operand.cpp b/source/operand.cpp
index c00c9b6..bff36a2 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -578,6 +578,12 @@
 
 std::function<bool(unsigned)> spvDbgInfoExtOperandCanBeForwardDeclaredFunction(
     spv_ext_inst_type_t ext_type, uint32_t key) {
+  // The Vulkan debug info extended instruction set is non-semantic so allows no
+  // forward references ever.
+  if (ext_type == SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100) {
+    return [](unsigned) { return false; };
+  }
+
   // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/532): Forward
   // references for debug info instructions are still in discussion. We must
   // update the following lines of code when we conclude the spec.
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index ce3bf7f..a6bdaaf 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -403,6 +403,14 @@
       callee2caller, inlined_at_ctx, new_blk_ptr, callee_first_block);
 
   while (callee_inst_itr != callee_first_block->end()) {
+    // Don't inline function definition links, the calling function is not a
+    // definition.
+    if (callee_inst_itr->GetVulkan100DebugOpcode() ==
+        NonSemanticVulkanDebugInfo100DebugFunctionDefinition) {
+      ++callee_inst_itr;
+      continue;
+    }
+
     if (!InlineSingleInstruction(
             callee2caller, new_blk_ptr->get(), &*callee_inst_itr,
             context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index 77f90f1..bfdd59b 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -17,6 +17,7 @@
 #include <utility>
 
 #include "DebugInfo.h"
+#include "NonSemanticVulkanDebugInfo100.h"
 #include "OpenCLDebugInfo100.h"
 #include "source/ext_inst.h"
 #include "source/opt/log.h"
@@ -235,6 +236,35 @@
             default: {
               Errorf(consumer_, src, loc,
                      "Debug info extension instruction other than DebugScope, "
+                     "DebugNoScope, DebugFunctionDefinition, DebugDeclare, and "
+                     "DebugValue found inside function",
+                     opcode);
+              return false;
+            }
+          }
+        } else if (inst->ext_inst_type ==
+                   SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100) {
+          const NonSemanticVulkanDebugInfo100Instructions ext_inst_key =
+              NonSemanticVulkanDebugInfo100Instructions(ext_inst_index);
+          switch (ext_inst_key) {
+            case NonSemanticVulkanDebugInfo100DebugDeclare:
+            case NonSemanticVulkanDebugInfo100DebugValue:
+            case NonSemanticVulkanDebugInfo100DebugScope:
+            case NonSemanticVulkanDebugInfo100DebugNoScope:
+            case NonSemanticVulkanDebugInfo100DebugFunctionDefinition: {
+              if (block_ == nullptr) {  // Inside function but outside blocks
+                Errorf(consumer_, src, loc,
+                       "Debug info extension instruction found inside function "
+                       "but outside block",
+                       opcode);
+              } 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 "
                      "function",
                      opcode);
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index 0c88601..6585012 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -145,8 +145,10 @@
   DebugScope last_scope(kNoDebugScope, kNoInlinedAt);
   const Instruction* last_line_inst = nullptr;
   bool between_merge_and_branch = false;
+  bool between_label_and_phi_var = false;
   auto write_inst = [binary, skip_nop, &last_scope, &last_line_inst,
-                     &between_merge_and_branch, this](const Instruction* i) {
+                     &between_merge_and_branch, &between_label_and_phi_var,
+                     this](const Instruction* i) {
     // Skip emitting line instructions between merge and branch instructions.
     auto opcode = i->opcode();
     if (between_merge_and_branch &&
@@ -175,13 +177,29 @@
         last_line_inst = nullptr;
       }
     }
+
+    if (opcode == SpvOpLabel) {
+      between_label_and_phi_var = true;
+    } else if (opcode != SpvOpVariable && opcode != SpvOpPhi &&
+               opcode != SpvOpLine && opcode != SpvOpNoLine) {
+      between_label_and_phi_var = false;
+    }
+
     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);
+        // Can only emit nonsemantic instructions after all phi instructions
+        // in a block so don't emit scope instructions before phi instructions
+        // for Vulkan.NonSemantic.DebugInfo.100.
+        if (!between_label_and_phi_var ||
+            context()
+                ->get_feature_mgr()
+                ->GetExtInstImportId_OpenCL100DebugInfo()) {
+          // 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;
       }