spirv-opt: fix adce when ns debug info present (#6217) (#6420)

This PR fixes two problems in ADCE while processing NonSemantic debug
info. The first change is to treat DebugDeclare similarly to DebugValue:
if a Variable is dead, replace it with OpUndef. This mirrors the
treatment of DebugValue for the Value operand. This fixes
https://github.com/KhronosGroup/SPIRV-Tools/issues/6217.

Next, we declare all DebugLocalVariable, DebugExpression, and
DebugOperation instructions to be live at initialization since there is
no reason to ever kill them. They do not have variables that, if
declared live, would have follow-on impact to DCE. This fixes
https://github.com/microsoft/DirectXShaderCompiler/issues/7874.
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index be21d79..54b1002 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -44,9 +44,10 @@
 constexpr uint32_t kExtInstOpInIdx = 1;
 constexpr uint32_t kInterpolantInIdx = 2;
 constexpr uint32_t kCooperativeMatrixLoadSourceAddrInIdx = 0;
-constexpr uint32_t kDebugValueLocalVariable = 2;
-constexpr uint32_t kDebugValueValue = 3;
-constexpr uint32_t kDebugValueExpression = 4;
+constexpr uint32_t kDebugDeclareVariableInIdx = 3;
+constexpr uint32_t kDebugValueLocalVariableInIdx = 2;
+constexpr uint32_t kDebugValueValueInIdx = 3;
+constexpr uint32_t kDebugValueExpressionInIdx = 4;
 
 // Sorting functor to present annotation instructions in an easy-to-process
 // order. The functor orders by opcode first and falls back on unique id
@@ -290,40 +291,95 @@
     std::list<BasicBlock*>& structured_order) {
   for (auto bi = structured_order.begin(); bi != structured_order.end(); bi++) {
     bool succeeded = (*bi)->WhileEachInst([this](Instruction* inst) {
-      // DebugDeclare is not dead. It must be converted to DebugValue in a
-      // later pass
-      if (inst->IsNonSemanticInstruction() &&
-          inst->GetShader100DebugOpcode() ==
-              NonSemanticShaderDebugInfo100DebugDeclare) {
-        AddToWorklist(inst);
-        return true;
-      }
+      if (!inst->IsNonSemanticInstruction()) return true;
 
-      // If the Value of a DebugValue is killed, set Value operand to Undef
-      if (inst->IsNonSemanticInstruction() &&
-          inst->GetShader100DebugOpcode() ==
-              NonSemanticShaderDebugInfo100DebugValue) {
-        uint32_t id = inst->GetSingleWordInOperand(kDebugValueValue);
-        auto def = get_def_use_mgr()->GetDef(id);
-        if (!IsLive(def)) {
+      if (inst->GetShader100DebugOpcode() ==
+          NonSemanticShaderDebugInfo100DebugDeclare) {
+        if (IsLive(inst)) return true;
+
+        uint32_t var_id =
+            inst->GetSingleWordInOperand(kDebugDeclareVariableInIdx);
+        auto var_def = get_def_use_mgr()->GetDef(var_id);
+
+        if (IsLive(var_def)) {
           AddToWorklist(inst);
-          uint32_t undef_id = Type2Undef(def->type_id());
-          if (undef_id == 0) {
-            return false;
-          }
-          inst->SetInOperand(kDebugValueValue, {undef_id});
-          context()->get_def_use_mgr()->UpdateDefUse(inst);
-          id = inst->GetSingleWordInOperand(kDebugValueLocalVariable);
-          auto localVar = get_def_use_mgr()->GetDef(id);
-          AddToWorklist(localVar);
-          context()->get_def_use_mgr()->UpdateDefUse(localVar);
-          AddOperandsToWorkList(localVar);
-          id = inst->GetSingleWordInOperand(kDebugValueExpression);
-          auto expression = get_def_use_mgr()->GetDef(id);
-          AddToWorklist(expression);
-          context()->get_def_use_mgr()->UpdateDefUse(expression);
           return true;
         }
+
+        // DebugDeclare Variable is not live. Find the value that was being
+        // stored to this variable. If it's live then create a new DebugValue
+        // with this value. Otherwise let it die in peace.
+        get_def_use_mgr()->ForEachUser(var_id, [this, var_id,
+                                                inst](Instruction* user) {
+          if (user->opcode() == spv::Op::OpStore) {
+            uint32_t stored_value_id = 0;
+            const uint32_t kStoreValueInIdx = 1;
+            stored_value_id = user->GetSingleWordInOperand(kStoreValueInIdx);
+            if (!IsLive(get_def_use_mgr()->GetDef(stored_value_id))) {
+              return true;
+            }
+
+            // value being stored is still live
+            Instruction* next_inst = inst->NextNode();
+            bool added =
+                context()->get_debug_info_mgr()->AddDebugValueForVariable(
+                    user, var_id, stored_value_id, inst);
+            if (added && next_inst) {
+              auto new_debug_value = next_inst->PreviousNode();
+              live_insts_.Set(new_debug_value->unique_id());
+            }
+          }
+          return true;
+        });
+      } else if (inst->GetShader100DebugOpcode() ==
+                 NonSemanticShaderDebugInfo100DebugValue) {
+        uint32_t var_operand_idx = kDebugValueValueInIdx;
+        uint32_t id = inst->GetSingleWordInOperand(var_operand_idx);
+        auto def = get_def_use_mgr()->GetDef(id);
+
+        if (IsLive(def)) {
+          AddToWorklist(inst);
+          return true;
+        }
+
+        // Value operand of DebugValue is not live
+        // Set Value to Undef of appropriate type
+        live_insts_.Set(inst->unique_id());
+
+        uint32_t type_id = def->type_id();
+        auto type_def = get_def_use_mgr()->GetDef(type_id);
+        AddToWorklist(type_def);
+
+        uint32_t undef_id = Type2Undef(type_id);
+        if (undef_id == 0) return false;
+
+        auto undef_inst = get_def_use_mgr()->GetDef(undef_id);
+        live_insts_.Set(undef_inst->unique_id());
+        inst->SetInOperand(var_operand_idx, {undef_id});
+        context()->get_def_use_mgr()->AnalyzeInstUse(inst);
+
+        id = inst->GetSingleWordInOperand(kDebugValueLocalVariableInIdx);
+        auto localVar = get_def_use_mgr()->GetDef(id);
+        AddToWorklist(localVar);
+
+        uint32_t expr_idx = kDebugValueExpressionInIdx;
+        id = inst->GetSingleWordInOperand(expr_idx);
+        auto expression = get_def_use_mgr()->GetDef(id);
+        AddToWorklist(expression);
+
+        for (uint32_t i = expr_idx + 1; i < inst->NumInOperands(); ++i) {
+          id = inst->GetSingleWordInOperand(i);
+          auto index_def = get_def_use_mgr()->GetDef(id);
+          if (index_def) {
+            AddToWorklist(index_def);
+          }
+        }
+
+        for (auto& line_inst : inst->dbg_line_insts()) {
+          if (line_inst.IsDebugLineInst()) {
+            AddToWorklist(&line_inst);
+          }
+        }
       }
       return true;
     });
@@ -731,13 +787,16 @@
     AddToWorklist(dbg_none);
   }
 
-  // Add top level DebugInfo to worklist
+  // Add DebugInfo which should never be eliminated to worklist
   for (auto& dbg : get_module()->ext_inst_debuginfo()) {
     auto op = dbg.GetShader100DebugOpcode();
     if (op == NonSemanticShaderDebugInfo100DebugCompilationUnit ||
         op == NonSemanticShaderDebugInfo100DebugEntryPoint ||
         op == NonSemanticShaderDebugInfo100DebugSource ||
-        op == NonSemanticShaderDebugInfo100DebugSourceContinued) {
+        op == NonSemanticShaderDebugInfo100DebugSourceContinued ||
+        op == NonSemanticShaderDebugInfo100DebugLocalVariable ||
+        op == NonSemanticShaderDebugInfo100DebugExpression ||
+        op == NonSemanticShaderDebugInfo100DebugOperation) {
       AddToWorklist(&dbg);
     }
   }
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index fd978f1..0af2eb1 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -6808,7 +6808,7 @@
                OpStore %100 %111
 ;CHECK-NOT:    OpStore %100 %111
         %114 = OpExtInst %void %1 DebugValue %92 %111 %76
-;CHECK-NOT: {{%\w+}} = OpExtInst %void %1 DebugValue %92 %111 %76
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugValue %92 %111 %76
         %115 = OpExtInst %void %1 DebugLine %79 %uint_20 %uint_20 %uint_25 %uint_32
         %116 = OpLoad %type_2d_image %g_tColor
         %117 = OpExtInst %void %1 DebugLine %79 %uint_20 %uint_20 %uint_41 %uint_48
@@ -8477,7 +8477,7 @@
   SinglePassRunAndMatch<AggressiveDCEPass>(before, true);
 }
 TEST_F(AggressiveDCETest, KeepOnlyLiveDebugValues) {
-  // DebugValue should only be live when Value is live.
+  // DebugValue should replace dead Value with Undef.
   const std::string before =
       R"(OpCapability MinLod
 OpCapability StorageImageWriteWithoutFormat
@@ -8526,6 +8526,7 @@
 %uint_2 = OpConstant %uint 2
 %uint_3 = OpConstant %uint 3
 %int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
 %uint_0 = OpConstant %uint 0
 %uint_32 = OpConstant %uint 32
 %type_buffer_image = OpTypeImage %int Buffer 2 0 0 2 R32i
@@ -8539,17 +8540,23 @@
 %uint_8 = OpConstant %uint 8
 %49 = OpTypeFunction %void
 %_arr_int_uint_3 = OpTypeArray %int %uint_3
+%_ptr_Function__arr_int_uint_3 = OpTypePointer Function %_arr_int_uint_3
 %v4int = OpTypeVector %int 4
 %uint_21 = OpConstant %uint 21
 %uint_27 = OpConstant %uint 27
 %uint_14 = OpConstant %uint 14
 %uint_31 = OpConstant %uint 31
+%_ptr_Function_int = OpTypePointer Function %int
 %uint_6 = OpConstant %uint 6
+%uint_10 = OpConstant %uint 10
 %b = OpVariable %_ptr_UniformConstant_type_buffer_image UniformConstant
 %int_1 = OpConstant %int 1
 %int_2 = OpConstant %int 2
+; CHECK: %281 = OpUndef %int
 %38 = OpExtInst %void %1 DebugInfoNone
 %16 = OpExtInst %void %1 DebugExpression
+; CHECK: %216 = OpExtInst %void %1 DebugOperation %uint_0
+%216 = OpExtInst %void %1 DebugOperation %uint_0
 %18 = OpExtInst %void %1 DebugTypeBasic %17 %uint_32 %uint_4 %uint_0
 %20 = OpExtInst %void %1 DebugTypeArray %18 %uint_3
 %21 = OpExtInst %void %1 DebugTypeFunction %uint_3 %void
@@ -8565,47 +8572,55 @@
 %46 = OpExtInst %void %1 DebugGlobalVariable %45 %44 %22 %uint_1 %uint_15 %23 %45 %b %uint_8
 %37 = OpExtInst %void %1 DebugEntryPoint %34 %23 %35 %36
 %138 = OpExtInst %void %1 DebugInlinedAt %uint_4 %34
+; CHECK: %215 = OpExtInst %void %1 DebugExpression %216
+%215 = OpExtInst %void %1 DebugExpression %216
 %main = OpFunction %void None %49
 %50 = OpLabel
-%311 = OpExtInst %void %1 DebugScope %34
+%205 = OpVariable %_ptr_Function_int Function
+%204 = OpVariable %_ptr_Function_int Function
+%203 = OpVariable %_ptr_Function_int Function
+%255 = OpExtInst %void %1 DebugScope %34
 %52 = OpExtInst %void %1 DebugFunctionDefinition %34 %main
-%312 = OpExtInst %void %1 DebugScope %28 %138
+%256 = OpExtInst %void %1 DebugScope %28 %138
 %155 = OpExtInst %void %1 DebugLine %22 %uint_5 %uint_5 %uint_15 %uint_15
 %141 = OpLoad %type_buffer_image %b
 %142 = OpImageRead %v4int %141 %uint_1 None
 %143 = OpCompositeExtract %int %142 0
-; CHECK: %141 = OpLoad %type_buffer_image %b
-; CHECK: %142 = OpImageRead %v4int %141 %uint_1 None
-; CHECK: %143 = OpCompositeExtract %int %142 0
 %158 = OpExtInst %void %1 DebugLine %22 %uint_5 %uint_5 %uint_21 %uint_21
 %144 = OpLoad %type_buffer_image %b
 %145 = OpImageRead %v4int %144 %uint_2 None
 %146 = OpCompositeExtract %int %145 0
-; CHECK-NOT: %144 = OpLoad %type_buffer_image %b
-; CHECK-NOT: %145 = OpImageRead %v4int %144 %uint_2 None
-; CHECK-NOT: %146 = OpCompositeExtract %int %145 0
 %161 = OpExtInst %void %1 DebugLine %22 %uint_5 %uint_5 %uint_27 %uint_27
 %147 = OpLoad %type_buffer_image %b
 %148 = OpImageRead %v4int %147 %uint_3 None
 %149 = OpCompositeExtract %int %148 0
-; CHECK-NOT: %147 = OpLoad %type_buffer_image %b
-; CHECK-NOT: %148 = OpImageRead %v4int %147 %uint_3 None
-; CHECK-NOT: %149 = OpCompositeExtract %int %148 0
 %164 = OpExtInst %void %1 DebugLine %22 %uint_5 %uint_5 %uint_14 %uint_31
 %150 = OpCompositeConstruct %_arr_int_uint_3 %143 %146 %149
-; CHECK-NOT: %150 = OpCompositeConstruct %_arr_int_uint_3 %143 %146 %149
-%210 = OpExtInst %void %1 DebugLine %22 %uint_5 %uint_5 %uint_3 %uint_31
-%209 = OpUndef %int
+%207 = OpExtInst %void %1 DebugLine %22 %uint_5 %uint_5 %uint_3 %uint_31
+%206 = OpCompositeExtract %int %150 0
+; CHECK-NOT: OpStore %203 %206
+OpStore %203 %206
+; CHECK: %253 = OpExtInst %void %1 DebugValue %31 %206 %16 %int_0
+%253 = OpExtInst %void %1 DebugValue %31 %206 %16 %int_0
+; CHECK-NOT: %209 = OpCompositeExtract %int %150 1
+%209 = OpCompositeExtract %int %150 1
+; CHECK-NOT: OpStore %204 %209
+OpStore %204 %209
+; CHECK: %250 = OpExtInst %void %1 DebugValue %31 %281 %16 %int_1
 %250 = OpExtInst %void %1 DebugValue %31 %209 %16 %int_1
-%247 = OpExtInst %void %1 DebugValue %31 %209 %16 %int_2
-; CHECK-NOT: %247 = OpExtInst %void %1 DebugValue %31 %209 %16 %int_2
+; CHECK-NOT: %212 = OpCompositeExtract %int %150 2
+%212 = OpCompositeExtract %int %150 2
+; CHECK-NOT: OpStore %205 %212
+OpStore %205 %212
+; CHECK: %247 = OpExtInst %void %1 DebugValue %31 %281 %16 %int_2
+%247 = OpExtInst %void %1 DebugValue %31 %212 %16 %int_2
 %169 = OpExtInst %void %1 DebugLine %22 %uint_6 %uint_6 %uint_3 %uint_13
 %154 = OpLoad %type_buffer_image %b
-OpImageWrite %154 %uint_0 %143 None
-%313 = OpExtInst %void %1 DebugScope %34
+OpImageWrite %154 %uint_0 %206 None
+%257 = OpExtInst %void %1 DebugScope %34
 %55 = OpExtInst %void %1 DebugLine %22 %uint_7 %uint_7 %uint_1 %uint_1
 OpReturn
-%314 = OpExtInst %void %1 DebugNoScope
+%258 = OpExtInst %void %1 DebugNoScope
 OpFunctionEnd
 )";