[DebugInfo] Add support for variadic DBG_INSTR_REFs in LiveDebugValues
authorStephen Tozer <Stephen.Tozer@Sony.com>
Tue, 3 Jan 2023 13:53:25 +0000 (13:53 +0000)
committerStephen Tozer <Stephen.Tozer@Sony.com>
Fri, 6 Jan 2023 23:01:25 +0000 (23:01 +0000)
Following support from the previous patches in this stack being added for
variadic DBG_INSTR_REFs to exist, this patch modifies LiveDebugValues to
handle those instructions. Support already exists for DBG_VALUE_LISTs, which
covers most of the work needed to handle these instructions; this patch only
modifies the transferDebugInstrRef function to correctly track them.

Reviewed By: jmorse

Differential Revision: https://reviews.llvm.org/D133927

llvm/include/llvm/IR/DebugInfoMetadata.h
llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp
llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.h
llvm/lib/IR/DebugInfoMetadata.cpp
llvm/test/DebugInfo/MIR/InstrRef/livedebugvalues-transfer-variadic-instr-ref.mir [new file with mode: 0644]
llvm/test/DebugInfo/MIR/InstrRef/survives-livedebugvars.mir
llvm/unittests/IR/MetadataTest.cpp

index 19bd6fd..f3602b6 100644 (file)
@@ -2805,6 +2805,13 @@ public:
   static const DIExpression *
   convertToVariadicExpression(const DIExpression *Expr);
 
+  /// If \p Expr is a valid single-location expression, i.e. it refers to only a
+  /// single debug operand at the start of the expression, then return that
+  /// expression in a non-variadic form by removing DW_OP_LLVM_arg from the
+  /// expression if it is present; otherwise returns std::nullopt.
+  static std::optional<const DIExpression *>
+  convertToNonVariadicExpression(const DIExpression *Expr);
+
   /// Inserts the elements of \p Expr into \p Ops modified to a canonical form,
   /// which uses DW_OP_LLVM_arg (i.e. is a variadic expression) and folds the
   /// implied derefence from the \p IsIndirect flag into the expression. This
index 4d3fa1e..11e6de9 100644 (file)
@@ -1132,12 +1132,11 @@ static bool emitDebugValueComment(const MachineInstr *MI, AsmPrinter &AP) {
   OS << " <- ";
 
   const DIExpression *Expr = MI->getDebugExpression();
-  if (Expr->getNumElements() && Expr->isSingleLocationExpression() &&
-      Expr->expr_op_begin()->getOp() == dwarf::DW_OP_LLVM_arg) {
-    SmallVector<uint64_t> Ops(
-        make_range(Expr->elements_begin() + 2, Expr->elements_end()));
-    Expr = DIExpression::get(Expr->getContext(), Ops);
-  }
+  // First convert this to a non-variadic expression if possible, to simplify
+  // the output.
+  if (auto NonVariadicExpr = DIExpression::convertToNonVariadicExpression(Expr))
+    Expr = *NonVariadicExpr;
+  // Then, output the possibly-simplified expression.
   if (Expr->getNumElements()) {
     OS << '[';
     ListSeparator LS;
index 01e810e..cf724c1 100644 (file)
@@ -630,12 +630,21 @@ public:
     if (!ShouldEmitDebugEntryValues)
       return false;
 
+    const DIExpression *DIExpr = Prop.DIExpr;
+
     // We don't currently emit entry values for DBG_VALUE_LISTs.
-    if (Prop.IsVariadic)
-      return false;
+    if (Prop.IsVariadic) {
+      // If this debug value can be converted to be non-variadic, then do so;
+      // otherwise give up.
+      auto NonVariadicExpression =
+          DIExpression::convertToNonVariadicExpression(DIExpr);
+      if (!NonVariadicExpression)
+        return false;
+      DIExpr = *NonVariadicExpression;
+    }
 
     // Is the variable appropriate for entry values (i.e., is a parameter).
-    if (!isEntryValueVariable(Var, Prop.DIExpr))
+    if (!isEntryValueVariable(Var, DIExpr))
       return false;
 
     // Is the value assigned to this variable still the entry value?
@@ -644,12 +653,12 @@ public:
 
     // Emit a variable location using an entry value expression.
     DIExpression *NewExpr =
-        DIExpression::prepend(Prop.DIExpr, DIExpression::EntryValue);
+        DIExpression::prepend(DIExpr, DIExpression::EntryValue);
     Register Reg = MTracker->LocIdxToLocID[Num.getLoc()];
     MachineOperand MO = MachineOperand::CreateReg(Reg, false);
 
     PendingDbgValues.push_back(
-        emitMOLoc(MO, Var, {NewExpr, Prop.Indirect, Prop.IsVariadic}));
+        emitMOLoc(MO, Var, {NewExpr, Prop.Indirect, false}));
     return true;
   }
 
@@ -809,8 +818,8 @@ public:
     for (const auto &Var : ActiveMLocIt->second) {
       auto ActiveVLocIt = ActiveVLocs.find(Var);
       // Re-state the variable location: if there's no replacement then NewLoc
-      // is None and a $noreg DBG_VALUE will be created. Otherwise, a DBG_VALUE
-      // identifying the alternative location will be emitted.
+      // is std::nullopt and a $noreg DBG_VALUE will be created. Otherwise, a
+      // DBG_VALUE identifying the alternative location will be emitted.
       const DbgValueProperties &Properties = ActiveVLocIt->second.Properties;
 
       // Produce the new list of debug ops - an empty list if no new location
@@ -1418,39 +1427,14 @@ bool InstrRefBasedLDV::transferDebugValue(const MachineInstr &MI) {
   return true;
 }
 
-bool InstrRefBasedLDV::transferDebugInstrRef(MachineInstr &MI,
-                                             const ValueTable *MLiveOuts,
-                                             const ValueTable *MLiveIns) {
-  if (!MI.isDebugRef())
-    return false;
-
-  // Only handle this instruction when we are building the variable value
-  // transfer function.
-  if (!VTracker && !TTracker)
-    return false;
-
-  unsigned InstNo = MI.getDebugOperand(0).getInstrRefInstrIndex();
-  unsigned OpNo = MI.getDebugOperand(0).getInstrRefOpIndex();
-
-  const DILocalVariable *Var = MI.getDebugVariable();
-  const DIExpression *Expr = MI.getDebugExpression();
-  const DILocation *DebugLoc = MI.getDebugLoc();
-  const DILocation *InlinedAt = DebugLoc->getInlinedAt();
-  assert(Var->isValidLocationForIntrinsic(DebugLoc) &&
-         "Expected inlined-at fields to agree");
-
-  DebugVariable V(Var, Expr, InlinedAt);
-
-  auto *Scope = LS.findLexicalScope(MI.getDebugLoc().get());
-  if (Scope == nullptr)
-    return true; // Handled by doing nothing. This variable is never in scope.
-
-  const MachineFunction &MF = *MI.getParent()->getParent();
-
+std::optional<ValueIDNum> InstrRefBasedLDV::getValueForInstrRef(
+    unsigned InstNo, unsigned OpNo, MachineInstr &MI,
+    const ValueTable *MLiveOuts, const ValueTable *MLiveIns) {
   // Various optimizations may have happened to the value during codegen,
   // recorded in the value substitution table. Apply any substitutions to
   // the instruction / operand number in this DBG_INSTR_REF, and collect
   // any subregister extractions performed during optimization.
+  const MachineFunction &MF = *MI.getParent()->getParent();
 
   // Create dummy substitution with Src set, for lookup.
   auto SoughtSub =
@@ -1586,14 +1570,64 @@ bool InstrRefBasedLDV::transferDebugInstrRef(MachineInstr &MI,
     }
   }
 
-  // We, we have a value number or std::nullopt. Tell the variable value tracker
-  // about it. The rest of this LiveDebugValues implementation acts exactly the
-  // same for DBG_INSTR_REFs as DBG_VALUEs (just, the former can refer to values
-  // that aren't immediately available).
-  DbgValueProperties Properties(Expr, false, true);
+  return NewID;
+}
+
+bool InstrRefBasedLDV::transferDebugInstrRef(MachineInstr &MI,
+                                             const ValueTable *MLiveOuts,
+                                             const ValueTable *MLiveIns) {
+  if (!MI.isDebugRef())
+    return false;
+
+  // Only handle this instruction when we are building the variable value
+  // transfer function.
+  if (!VTracker && !TTracker)
+    return false;
+
+  const DILocalVariable *Var = MI.getDebugVariable();
+  const DIExpression *Expr = MI.getDebugExpression();
+  const DILocation *DebugLoc = MI.getDebugLoc();
+  const DILocation *InlinedAt = DebugLoc->getInlinedAt();
+  assert(Var->isValidLocationForIntrinsic(DebugLoc) &&
+         "Expected inlined-at fields to agree");
+
+  DebugVariable V(Var, Expr, InlinedAt);
+
+  auto *Scope = LS.findLexicalScope(MI.getDebugLoc().get());
+  if (Scope == nullptr)
+    return true; // Handled by doing nothing. This variable is never in scope.
+
   SmallVector<DbgOpID> DbgOpIDs;
-  if (NewID)
-    DbgOpIDs.push_back(DbgOpStore.insert(*NewID));
+  for (const MachineOperand &MO : MI.debug_operands()) {
+    if (!MO.isDbgInstrRef()) {
+      assert(!MO.isReg() && "DBG_INSTR_REF should not contain registers");
+      DbgOpID ConstOpID = DbgOpStore.insert(DbgOp(MO));
+      DbgOpIDs.push_back(ConstOpID);
+      continue;
+    }
+
+    unsigned InstNo = MO.getInstrRefInstrIndex();
+    unsigned OpNo = MO.getInstrRefOpIndex();
+
+    // Default machine value number is <None> -- if no instruction defines
+    // the corresponding value, it must have been optimized out.
+    std::optional<ValueIDNum> NewID =
+        getValueForInstrRef(InstNo, OpNo, MI, MLiveOuts, MLiveIns);
+    // We have a value number or std::nullopt. If the latter, then kill the
+    // entire debug value.
+    if (NewID) {
+      DbgOpIDs.push_back(DbgOpStore.insert(*NewID));
+    } else {
+      DbgOpIDs.clear();
+      break;
+    }
+  }
+
+  // We have a DbgOpID for every value or for none. Tell the variable value
+  // tracker about it. The rest of this LiveDebugValues implementation acts
+  // exactly the same for DBG_INSTR_REFs as DBG_VALUEs (just, the former can
+  // refer to values that aren't immediately available).
+  DbgValueProperties Properties(Expr, false, true);
   if (VTracker)
     VTracker->defVar(MI, Properties, DbgOpIDs);
 
@@ -1602,40 +1636,84 @@ bool InstrRefBasedLDV::transferDebugInstrRef(MachineInstr &MI,
   if (!TTracker)
     return true;
 
+  // Fetch the concrete DbgOps now, as we will need them later.
+  SmallVector<DbgOp> DbgOps;
+  for (DbgOpID OpID : DbgOpIDs) {
+    DbgOps.push_back(DbgOpStore.find(OpID));
+  }
+
   // Pick a location for the machine value number, if such a location exists.
   // (This information could be stored in TransferTracker to make it faster).
-  TransferTracker::LocationAndQuality FoundLoc;
+  SmallDenseMap<ValueIDNum, TransferTracker::LocationAndQuality> FoundLocs;
+  SmallVector<ValueIDNum> ValuesToFind;
+  // Initialized the preferred-location map with illegal locations, to be
+  // filled in later.
+  for (const DbgOp &Op : DbgOps) {
+    if (!Op.IsConst)
+      if (FoundLocs.insert({Op.ID, TransferTracker::LocationAndQuality()})
+              .second)
+        ValuesToFind.push_back(Op.ID);
+  }
+
   for (auto Location : MTracker->locations()) {
     LocIdx CurL = Location.Idx;
     ValueIDNum ID = MTracker->readMLoc(CurL);
-    if (NewID && ID == NewID) {
-      // If this is the first location with that value, pick it. Otherwise,
-      // consider whether it's a "longer term" location.
-      std::optional<TransferTracker::LocationQuality> ReplacementQuality =
-          TTracker->getLocQualityIfBetter(CurL, FoundLoc.getQuality());
-      if (ReplacementQuality) {
-        FoundLoc =
-            TransferTracker::LocationAndQuality(CurL, *ReplacementQuality);
-        if (FoundLoc.isBest())
+    auto ValueToFindIt = find(ValuesToFind, ID);
+    if (ValueToFindIt == ValuesToFind.end())
+      continue;
+    auto &Previous = FoundLocs.find(ID)->second;
+    // If this is the first location with that value, pick it. Otherwise,
+    // consider whether it's a "longer term" location.
+    std::optional<TransferTracker::LocationQuality> ReplacementQuality =
+        TTracker->getLocQualityIfBetter(CurL, Previous.getQuality());
+    if (ReplacementQuality) {
+      Previous = TransferTracker::LocationAndQuality(CurL, *ReplacementQuality);
+      if (Previous.isBest()) {
+        ValuesToFind.erase(ValueToFindIt);
+        if (ValuesToFind.empty())
           break;
       }
     }
   }
 
   SmallVector<ResolvedDbgOp> NewLocs;
-  if (!FoundLoc.isIllegal())
-    NewLocs.push_back(FoundLoc.getLoc());
+  for (const DbgOp &DbgOp : DbgOps) {
+    if (DbgOp.IsConst) {
+      NewLocs.push_back(DbgOp.MO);
+      continue;
+    }
+    LocIdx FoundLoc = FoundLocs.find(DbgOp.ID)->second.getLoc();
+    if (FoundLoc.isIllegal()) {
+      NewLocs.clear();
+      break;
+    }
+    NewLocs.push_back(FoundLoc);
+  }
   // Tell transfer tracker that the variable value has changed.
   TTracker->redefVar(MI, Properties, NewLocs);
 
-  // If there was a value with no location; but the value is defined in a
-  // later instruction in this block, this is a block-local use-before-def.
-  if (FoundLoc.isIllegal() && NewID && NewID->getBlock() == CurBB &&
-      NewID->getInst() > CurInst) {
-    SmallVector<DbgOp> UseBeforeDefLocs;
-    UseBeforeDefLocs.push_back(*NewID);
-    TTracker->addUseBeforeDef(V, {MI.getDebugExpression(), false, true},
-                              UseBeforeDefLocs, NewID->getInst());
+  // If there were values with no location, but all such values are defined in
+  // later instructions in this block, this is a block-local use-before-def.
+  if (!DbgOps.empty() && NewLocs.empty()) {
+    bool IsValidUseBeforeDef = true;
+    uint64_t LastUseBeforeDef = 0;
+    for (auto ValueLoc : FoundLocs) {
+      ValueIDNum NewID = ValueLoc.first;
+      LocIdx FoundLoc = ValueLoc.second.getLoc();
+      if (!FoundLoc.isIllegal())
+        continue;
+      // If we have an value with no location that is not defined in this block,
+      // then it has no location in this block, leaving this value undefined.
+      if (NewID.getBlock() != CurBB || NewID.getInst() <= CurInst) {
+        IsValidUseBeforeDef = false;
+        break;
+      }
+      LastUseBeforeDef = std::max(LastUseBeforeDef, NewID.getInst());
+    }
+    if (IsValidUseBeforeDef) {
+      TTracker->addUseBeforeDef(V, {MI.getDebugExpression(), false, true},
+                                DbgOps, LastUseBeforeDef);
+    }
   }
 
   // Produce a DBG_VALUE representing what this DBG_INSTR_REF meant.
@@ -4004,13 +4082,13 @@ std::optional<ValueIDNum> InstrRefBasedLDV::resolveDbgPHIs(
 
   // This function will be called twice per DBG_INSTR_REF, and might end up
   // computing lots of SSA information: memoize it.
-  auto SeenDbgPHIIt = SeenDbgPHIs.find(&Here);
+  auto SeenDbgPHIIt = SeenDbgPHIs.find(std::make_pair(&Here, InstrNum));
   if (SeenDbgPHIIt != SeenDbgPHIs.end())
     return SeenDbgPHIIt->second;
 
   std::optional<ValueIDNum> Result =
       resolveDbgPHIsImpl(MF, MLiveOuts, MLiveIns, Here, InstrNum);
-  SeenDbgPHIs.insert({&Here, Result});
+  SeenDbgPHIs.insert({std::make_pair(&Here, InstrNum), Result});
   return Result;
 }
 
index 39584db..2fdc37c 100644 (file)
@@ -1156,7 +1156,8 @@ private:
   /// DBG_INSTR_REFs that call resolveDbgPHIs. These variable references solve
   /// a mini SSA problem caused by DBG_PHIs being cloned, this collection caches
   /// the result.
-  DenseMap<MachineInstr *, std::optional<ValueIDNum>> SeenDbgPHIs;
+  DenseMap<std::pair<MachineInstr *, unsigned>, std::optional<ValueIDNum>>
+      SeenDbgPHIs;
 
   DbgOpIDMap DbgOpStore;
 
@@ -1194,6 +1195,14 @@ private:
   std::optional<SpillLocationNo>
   extractSpillBaseRegAndOffset(const MachineInstr &MI);
 
+  /// For an instruction reference given by \p InstNo and \p OpNo in instruction
+  /// \p MI returns the Value pointed to by that instruction reference if any
+  /// exists, otherwise returns None.
+  std::optional<ValueIDNum> getValueForInstrRef(unsigned InstNo, unsigned OpNo,
+                                                MachineInstr &MI,
+                                                const ValueTable *MLiveOuts,
+                                                const ValueTable *MLiveIns);
+
   /// Observe a single instruction while stepping through a block.
   void process(MachineInstr &MI, const ValueTable *MLiveOuts,
                const ValueTable *MLiveIns);
index 41f9ca2..905a188 100644 (file)
@@ -1475,6 +1475,27 @@ DIExpression::convertToVariadicExpression(const DIExpression *Expr) {
   return DIExpression::get(Expr->getContext(), NewOps);
 }
 
+std::optional<const DIExpression *>
+DIExpression::convertToNonVariadicExpression(const DIExpression *Expr) {
+  // Check for `isValid` covered by `isSingleLocationExpression`.
+  if (!Expr->isSingleLocationExpression())
+    return std::nullopt;
+
+  // An empty expression is already non-variadic.
+  if (!Expr->getNumElements())
+    return Expr;
+
+  auto ElementsBegin = Expr->elements_begin();
+  // If Expr does not have a leading DW_OP_LLVM_arg then we don't need to do
+  // anything.
+  if (*ElementsBegin != dwarf::DW_OP_LLVM_arg)
+    return Expr;
+
+  SmallVector<uint64_t> NonVariadicOps(
+      make_range(ElementsBegin + 2, Expr->elements_end()));
+  return DIExpression::get(Expr->getContext(), NonVariadicOps);
+}
+
 void DIExpression::canonicalizeExpressionOps(SmallVectorImpl<uint64_t> &Ops,
                                              const DIExpression *Expr,
                                              bool IsIndirect) {
diff --git a/llvm/test/DebugInfo/MIR/InstrRef/livedebugvalues-transfer-variadic-instr-ref.mir b/llvm/test/DebugInfo/MIR/InstrRef/livedebugvalues-transfer-variadic-instr-ref.mir
new file mode 100644 (file)
index 0000000..72f9d96
--- /dev/null
@@ -0,0 +1,281 @@
+# RUN: llc %s -o - -experimental-debug-variable-locations=true \
+# RUN:     -run-pass=livedebugvalues  | \
+# RUN: FileCheck %s --implicit-check-not=DBG_VALUE
+#
+## Check that LiveDebugValues can track and join DBG_INSTR_REFs that use
+## registers, stack slots, and constants in a single instruction.
+#
+# CHECK: ![[VAR_D:[0-9]+]] = !DILocalVariable(name: "d"
+
+# CHECK-LABEL: bb.0.entry
+#
+## Value at end of first block should use the registers given by DBG_PHI, and
+## fold the offset+deref from stack slot [$rsp+12] into the expression.
+# CHECK:       DBG_VALUE_LIST ![[VAR_D]]
+# CHECK-SAME:  !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus_uconst, 12, DW_OP_deref, DW_OP_plus, DW_OP_LLVM_arg, 3, DW_OP_minus, DW_OP_stack_value)
+# CHECK-SAME:  $ebp, $rsp, $ebx, 6
+
+# CHECK-LABEL: bb.1.while.body
+#
+## Prior DBG_VALUE_LIST should be live-in to this bb.1.
+# CHECK:       DBG_VALUE_LIST ![[VAR_D]]
+# CHECK-SAME:  !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus_uconst, 12, DW_OP_deref, DW_OP_plus, DW_OP_LLVM_arg, 3, DW_OP_minus, DW_OP_stack_value)
+# CHECK-SAME:  $ebp, $rsp, $ebx, 6
+#
+## When $ebp is spilled to a stack slot, a new debug value using the stack slot
+## should be inserted.
+# CHECK:       MOV32mr $rsp, {{.+}} $ebp
+# CHECK-NEXT:  DBG_VALUE_LIST ![[VAR_D]]
+# CHECK-SAME:  !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_plus_uconst, 16, DW_OP_deref, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus_uconst, 12, DW_OP_deref, DW_OP_plus, DW_OP_LLVM_arg, 3, DW_OP_minus, DW_OP_stack_value)
+# CHECK-SAME:  $rsp, $rsp, $ebx, 6
+#
+## Similarly produce a new debug value when $ebx is spilled.
+# CHECK:       MOV32mr $rsp, {{.+}} $ebx
+# CHECK-NEXT:  DBG_VALUE_LIST ![[VAR_D]]
+# CHECK-SAME:  !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_plus_uconst, 16, DW_OP_deref, DW_OP_LLVM_arg, 2, DW_OP_plus_uconst, 20, DW_OP_deref, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus_uconst, 12, DW_OP_deref, DW_OP_plus, DW_OP_LLVM_arg, 3, DW_OP_minus, DW_OP_stack_value)
+# CHECK-SAME:  $rsp, $rsp, $rsp, 6
+#
+## When the value in the stack slot is restored and then that slot is clobbered, the debug value should use the restored register.
+# CHECK:       MOV32mi $rsp
+# CHECK-NEXT:  DBG_VALUE_LIST ![[VAR_D]]
+# CHECK-SAME:  !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_plus_uconst, 16, DW_OP_deref, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus_uconst, 12, DW_OP_deref, DW_OP_plus, DW_OP_LLVM_arg, 3, DW_OP_minus, DW_OP_stack_value)
+# CHECK-SAME:  $rsp, $rsp, $ebx, 6
+#
+## Repeat for the next stack slot that gets restored and then clobbered.
+# CHECK:       MOV32mi $rsp
+# CHECK-NEXT:  DBG_VALUE_LIST ![[VAR_D]]
+# CHECK-SAME:  !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus_uconst, 12, DW_OP_deref, DW_OP_plus, DW_OP_LLVM_arg, 3, DW_OP_minus, DW_OP_stack_value)
+# CHECK-SAME:  $ebp, $rsp, $ebx, 6
+#
+## TODO: The debug value range should be terminated when the value in $ebx is clobbered.
+#
+## At the end of the block a new DBG_INSTR_REF begins a new range.
+# CHECK:       DBG_VALUE_LIST ![[VAR_D]]
+# CHECK-SAME:  !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus_uconst, 12, DW_OP_deref, DW_OP_plus, DW_OP_LLVM_arg, 3, DW_OP_minus, DW_OP_stack_value)
+# CHECK-SAME:  $ebp, $rsp, $ebx, 6
+
+# CHECK-LABEL: bb.2.while.end
+#
+## Finally, the two entry debug values should be joined, as they have identical
+## expressions, their respective stack slots and constants are identical, and
+## the remaining operands occupy the same registers out of each block.
+# CHECK:       DBG_VALUE_LIST ![[VAR_D]]
+# CHECK-SAME:  !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus_uconst, 12, DW_OP_deref, DW_OP_plus, DW_OP_LLVM_arg, 3, DW_OP_minus, DW_OP_stack_value)
+# CHECK-SAME:  $ebp, $rsp, $ebx, 6
+
+
+--- |
+
+  ; ModuleID = 'test.ll'
+  source_filename = "test.cpp"
+  target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+  target triple = "x86_64-unknown-linux-gnu"
+  
+  ; Function Attrs: mustprogress uwtable
+  define dso_local noundef i32 @_Z3fooii(i32 noundef %a, i32 noundef %b) local_unnamed_addr !dbg !9 {
+  entry:
+    %call = tail call noundef i32 @_Z3bazv(), !dbg !18
+    call void @llvm.dbg.value(metadata !DIArgList(i32 %a, i32 %call, i32 %b), metadata !17, metadata !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_constu, 6, DW_OP_minus, DW_OP_stack_value)), !dbg !19
+    %cmp19 = icmp sgt i32 %b, %a, !dbg !20
+    br i1 %cmp19, label %while.body.preheader, label %while.end, !dbg !21
+  
+  while.body.preheader:                             ; preds = %entry
+    br label %while.body, !dbg !21
+  
+  while.body:                                       ; preds = %while.body.preheader, %while.body
+    %a.addr.021 = phi i32 [ %mul3, %while.body ], [ %a, %while.body.preheader ]
+    %b.addr.020 = phi i32 [ %add2, %while.body ], [ %b, %while.body.preheader ]
+    %call1 = tail call noundef i32 @_Z3bazv(), !dbg !22
+    tail call void asm sideeffect "", "~{rax},~{rbx},~{rcx},~{rdx},~{rsi},~{rdi},~{rbp},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{dirflag},~{fpsr},~{flags}"() #3, !dbg !24, !srcloc !25
+    %add2 = add i32 %b.addr.020, 2, !dbg !26
+    %mul3 = shl nsw i32 %a.addr.021, 1, !dbg !27
+    call void @llvm.dbg.value(metadata !DIArgList(i32 %mul3, i32 %call, i32 %add2), metadata !17, metadata !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_constu, 6, DW_OP_minus, DW_OP_stack_value)), !dbg !19
+    %cmp = icmp sgt i32 %add2, %mul3, !dbg !20
+    br i1 %cmp, label %while.body, label %while.end, !dbg !21, !llvm.loop !28
+  
+  while.end:                                        ; preds = %while.body, %entry
+    %b.addr.0.lcssa = phi i32 [ %b, %entry ], [ %add2, %while.body ]
+    %a.addr.0.lcssa = phi i32 [ %a, %entry ], [ %mul3, %while.body ]
+    %add7 = sub i32 %b.addr.0.lcssa, %call, !dbg !31
+    %sub8 = add i32 %add7, %a.addr.0.lcssa, !dbg !32
+    ret i32 %sub8, !dbg !33
+  }
+  
+  declare !dbg !34 noundef i32 @_Z3bazv() local_unnamed_addr
+  
+  ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
+  declare void @llvm.dbg.value(metadata, metadata, metadata)
+  
+  !llvm.dbg.cu = !{!0}
+  !llvm.module.flags = !{!2, !3, !4, !5, !6, !7}
+  !llvm.ident = !{!8}
+  
+  !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 16.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+  !1 = !DIFile(filename: "test.cpp", directory: "/")
+  !2 = !{i32 7, !"Dwarf Version", i32 5}
+  !3 = !{i32 2, !"Debug Info Version", i32 3}
+  !4 = !{i32 1, !"wchar_size", i32 4}
+  !5 = !{i32 8, !"PIC Level", i32 2}
+  !6 = !{i32 7, !"PIE Level", i32 2}
+  !7 = !{i32 7, !"uwtable", i32 2}
+  !8 = !{!"clang version 16.0.0"}
+  !9 = distinct !DISubprogram(name: "foo", linkageName: "_Z3fooii", scope: !1, file: !1, line: 3, type: !10, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !13)
+  !10 = !DISubroutineType(types: !11)
+  !11 = !{!12, !12, !12}
+  !12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+  !13 = !{!14, !15, !16, !17}
+  !14 = !DILocalVariable(name: "a", arg: 1, scope: !9, file: !1, line: 3, type: !12)
+  !15 = !DILocalVariable(name: "b", arg: 2, scope: !9, file: !1, line: 3, type: !12)
+  !16 = !DILocalVariable(name: "c", scope: !9, file: !1, line: 4, type: !12)
+  !17 = !DILocalVariable(name: "d", scope: !9, file: !1, line: 5, type: !12)
+  !18 = !DILocation(line: 4, column: 11, scope: !9)
+  !19 = !DILocation(line: 0, scope: !9)
+  !20 = !DILocation(line: 6, column: 12, scope: !9)
+  !21 = !DILocation(line: 6, column: 3, scope: !9)
+  !22 = !DILocation(line: 7, column: 5, scope: !23)
+  !23 = distinct !DILexicalBlock(scope: !9, file: !1, line: 6, column: 17)
+  !24 = !DILocation(line: 9, column: 5, scope: !23)
+  !25 = !{i64 129}
+  !26 = !DILocation(line: 8, column: 7, scope: !23)
+  !27 = !DILocation(line: 10, column: 7, scope: !23)
+  !28 = distinct !{!28, !21, !29, !30}
+  !29 = !DILocation(line: 12, column: 3, scope: !9)
+  !30 = !{!"llvm.loop.mustprogress"}
+  !31 = !DILocation(line: 13, column: 12, scope: !9)
+  !32 = !DILocation(line: 13, column: 16, scope: !9)
+  !33 = !DILocation(line: 13, column: 3, scope: !9)
+  !34 = !DISubprogram(name: "baz", linkageName: "_Z3bazv", scope: !1, file: !1, line: 1, type: !35, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !37)
+  !35 = !DISubroutineType(types: !36)
+  !36 = !{!12}
+  !37 = !{}
+
+...
+---
+name:            _Z3fooii
+alignment:       16
+tracksRegLiveness: true
+tracksDebugUserValues: true
+registers:       []
+liveins:
+  - { reg: '$edi', virtual-reg: '' }
+  - { reg: '$esi', virtual-reg: '' }
+frameInfo:
+  stackSize:       72
+  offsetAdjustment: -72
+  maxAlignment:    4
+  adjustsStack:    true
+  hasCalls:        true
+  stackProtector:  ''
+  functionContext: ''
+  cvBytesOfCalleeSavedRegisters: 48
+  savePoint:       ''
+  restorePoint:    ''
+fixedStack:
+  - { id: 0, type: spill-slot, offset: -56, size: 8, alignment: 8, stack-id: default, 
+      callee-saved-register: '$rbx', callee-saved-restored: true, debug-info-variable: '', 
+      debug-info-expression: '', debug-info-location: '' }
+  - { id: 1, type: spill-slot, offset: -48, size: 8, alignment: 16, stack-id: default, 
+      callee-saved-register: '$r12', callee-saved-restored: true, debug-info-variable: '', 
+      debug-info-expression: '', debug-info-location: '' }
+  - { id: 2, type: spill-slot, offset: -40, size: 8, alignment: 8, stack-id: default, 
+      callee-saved-register: '$r13', callee-saved-restored: true, debug-info-variable: '', 
+      debug-info-expression: '', debug-info-location: '' }
+  - { id: 3, type: spill-slot, offset: -32, size: 8, alignment: 16, stack-id: default, 
+      callee-saved-register: '$r14', callee-saved-restored: true, debug-info-variable: '', 
+      debug-info-expression: '', debug-info-location: '' }
+  - { id: 4, type: spill-slot, offset: -24, size: 8, alignment: 8, stack-id: default, 
+      callee-saved-register: '$r15', callee-saved-restored: true, debug-info-variable: '', 
+      debug-info-expression: '', debug-info-location: '' }
+  - { id: 5, type: spill-slot, offset: -16, size: 8, alignment: 16, stack-id: default, 
+      callee-saved-register: '$rbp', callee-saved-restored: true, debug-info-variable: '', 
+      debug-info-expression: '', debug-info-location: '' }
+stack:
+  - { id: 0, name: '', type: spill-slot, offset: -60, size: 4, alignment: 4, 
+      stack-id: default, callee-saved-register: '', callee-saved-restored: true, 
+      debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
+  - { id: 1, name: '', type: spill-slot, offset: -64, size: 4, alignment: 4, 
+      stack-id: default, callee-saved-register: '', callee-saved-restored: true, 
+      debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
+  - { id: 2, name: '', type: spill-slot, offset: -68, size: 4, alignment: 4, 
+      stack-id: default, callee-saved-register: '', callee-saved-restored: true, 
+      debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
+callSites:       []
+debugValueSubstitutions: []
+constants:       []
+machineFunctionInfo: {}
+body:             |
+  bb.0.entry:
+    successors: %bb.1(0x78e38e39), %bb.3(0x071c71c7)
+    liveins: $edi, $esi, $rbp, $r15, $r14, $r13, $r12, $rbx
+  
+    frame-setup PUSH64r killed $rbp, implicit-def $rsp, implicit $rsp
+    frame-setup CFI_INSTRUCTION def_cfa_offset 16
+    frame-setup PUSH64r killed $r15, implicit-def $rsp, implicit $rsp
+    frame-setup CFI_INSTRUCTION def_cfa_offset 24
+    frame-setup PUSH64r killed $r14, implicit-def $rsp, implicit $rsp
+    frame-setup CFI_INSTRUCTION def_cfa_offset 32
+    frame-setup PUSH64r killed $r13, implicit-def $rsp, implicit $rsp
+    frame-setup CFI_INSTRUCTION def_cfa_offset 40
+    frame-setup PUSH64r killed $r12, implicit-def $rsp, implicit $rsp
+    frame-setup CFI_INSTRUCTION def_cfa_offset 48
+    frame-setup PUSH64r killed $rbx, implicit-def $rsp, implicit $rsp
+    frame-setup CFI_INSTRUCTION def_cfa_offset 56
+    $rsp = frame-setup SUB64ri8 $rsp, 24, implicit-def dead $eflags
+    frame-setup CFI_INSTRUCTION def_cfa_offset 80
+    CFI_INSTRUCTION offset $rbx, -56
+    CFI_INSTRUCTION offset $r12, -48
+    CFI_INSTRUCTION offset $r13, -40
+    CFI_INSTRUCTION offset $r14, -32
+    CFI_INSTRUCTION offset $r15, -24
+    CFI_INSTRUCTION offset $rbp, -16
+    DBG_PHI $esi, 5
+    DBG_PHI $edi, 3
+    $ebx = MOV32rr $esi
+    $ebp = MOV32rr $edi
+    CALL64pcrel32 target-flags(x86-plt) @_Z3bazv, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, implicit-def $eax, debug-instr-number 4, debug-location !18
+    MOV32mr $rsp, 1, $noreg, 12, $noreg, $eax :: (store (s32) into %stack.2)
+    DBG_INSTR_REF !17, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_LLVM_arg, 3, DW_OP_minus, DW_OP_stack_value), dbg-instr-ref(3, 0), dbg-instr-ref(4, 6), dbg-instr-ref(5, 0), 6, debug-location !19
+    CMP32rr renamable $ebx, renamable $ebp, implicit-def $eflags, debug-location !20
+    JCC_1 %bb.3, 14, implicit $eflags, debug-location !21
+  
+  bb.1.while.body (align 16):
+    successors: %bb.1(0x78e38e39), %bb.3(0x071c71c7)
+    liveins: $ebp, $ebx
+  
+    MOV32mr $rsp, 1, $noreg, 16, $noreg, killed renamable $ebp :: (store (s32) into %stack.1)
+    MOV32mr $rsp, 1, $noreg, 20, $noreg, killed renamable $ebx :: (store (s32) into %stack.0)
+    CALL64pcrel32 target-flags(x86-plt) @_Z3bazv, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, implicit-def dead $eax, debug-location !22
+    INLINEASM &"", 1 /* sideeffect attdialect */, 12 /* clobber */, implicit-def dead early-clobber $rax, 12 /* clobber */, implicit-def dead early-clobber $rbx, 12 /* clobber */, implicit-def dead early-clobber $rcx, 12 /* clobber */, implicit-def dead early-clobber $rdx, 12 /* clobber */, implicit-def dead early-clobber $rsi, 12 /* clobber */, implicit-def dead early-clobber $rdi, 12 /* clobber */, implicit-def dead early-clobber $rbp, 12 /* clobber */, implicit-def dead early-clobber $r8, 12 /* clobber */, implicit-def dead early-clobber $r9, 12 /* clobber */, implicit-def dead early-clobber $r10, 12 /* clobber */, implicit-def dead early-clobber $r11, 12 /* clobber */, implicit-def dead early-clobber $r12, 12 /* clobber */, implicit-def dead early-clobber $r13, 12 /* clobber */, implicit-def dead early-clobber $r14, 12 /* clobber */, implicit-def dead early-clobber $r15, 12 /* clobber */, implicit-def dead early-clobber $df, 12 /* clobber */, implicit-def early-clobber $fpsw, 12 /* clobber */, implicit-def dead early-clobber $eflags, !25, debug-location !24
+    renamable $ebp = MOV32rm $rsp, 1, $noreg, 16, $noreg :: (load (s32) from %stack.1)
+    renamable $ebx = MOV32rm $rsp, 1, $noreg, 20, $noreg :: (load (s32) from %stack.0)
+    MOV32mi $rsp, 1, $noreg, 20, $noreg, 0 :: (store (s32) into %stack.0)
+    MOV32mi $rsp, 1, $noreg, 16, $noreg, 0 :: (store (s32) into %stack.1)
+    renamable $ebx = ADD32ri8 killed renamable $ebx, 2, implicit-def dead $eflags, debug-instr-number 2, debug-location !26
+    renamable $ebp = nsw ADD32rr killed renamable $ebp, renamable $ebp, implicit-def dead $eflags, debug-instr-number 1, debug-location !27
+    DBG_INSTR_REF !17, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 2, DW_OP_mul, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_LLVM_arg, 3, DW_OP_minus, DW_OP_stack_value), dbg-instr-ref(1, 0), dbg-instr-ref(4, 6), dbg-instr-ref(2, 0), 6, debug-location !19
+    CMP32rr renamable $ebx, renamable $ebp, implicit-def $eflags, debug-location !20
+    JCC_1 %bb.1, 15, implicit $eflags, debug-location !21
+  
+  bb.3.while.end:
+    liveins: $ebp, $ebx
+  
+    renamable $ebx = SUB32rm killed renamable $ebx, $rsp, 1, $noreg, 12, $noreg, implicit-def dead $eflags, debug-location !31 :: (load (s32) from %stack.2)
+    renamable $ebx = ADD32rr killed renamable $ebx, killed renamable $ebp, implicit-def dead $eflags, debug-location !32
+    $eax = MOV32rr killed $ebx, debug-location !33
+    $rsp = frame-destroy ADD64ri8 $rsp, 24, implicit-def dead $eflags, debug-location !33
+    frame-destroy CFI_INSTRUCTION def_cfa_offset 56, debug-location !33
+    $rbx = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !33
+    frame-destroy CFI_INSTRUCTION def_cfa_offset 48, debug-location !33
+    $r12 = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !33
+    frame-destroy CFI_INSTRUCTION def_cfa_offset 40, debug-location !33
+    $r13 = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !33
+    frame-destroy CFI_INSTRUCTION def_cfa_offset 32, debug-location !33
+    $r14 = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !33
+    frame-destroy CFI_INSTRUCTION def_cfa_offset 24, debug-location !33
+    $r15 = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !33
+    frame-destroy CFI_INSTRUCTION def_cfa_offset 16, debug-location !33
+    $rbp = frame-destroy POP64r implicit-def $rsp, implicit $rsp, debug-location !33
+    frame-destroy CFI_INSTRUCTION def_cfa_offset 8, debug-location !33
+    RET64 $eax, debug-location !33
+
+...
index ed4c6a7..c4ee8c9 100644 (file)
@@ -6,16 +6,20 @@
 # livedebugvars-crossbb-interval.mir.
 #
 # CHECK-LABEL: bb.0:
-# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(1, 0)\r
+# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(1, 0)
+# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(1, 0), dbg-instr-ref(2, 0)
 # CHECK-NEXT:  JMP_1
 # CHECK-LABEL: bb.1:
-# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(2, 0)\r
+# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(2, 0)
+# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(2, 0), dbg-instr-ref(3, 0)
 # CHECK-NEXT:  JMP_1
 # CHECK-LABEL: bb.2:
-# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(3, 0)\r
+# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(3, 0)
+# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(3, 0), dbg-instr-ref(4, 0)
 # CHECK-NEXT:  CALL64pcrel32
 # CHECK-LABEL: bb.3:
-# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(4, 0)\r
+# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(4, 0)
+# CHECK:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(4, 0), dbg-instr-ref(5, 0)
 # CHECK-NEXT:  JMP_1
 #
 #
 # the DBG_INSTR_REF lands on.
 #
 # FASTREG-LABEL: bb.0:
-# FASTREG-DAG:   DBG_INSTR_REF {{.+}}, dbg-instr-ref(1, 0)\r
+# FASTREG-DAG:   DBG_INSTR_REF {{.+}}, dbg-instr-ref(1, 0)
+# FASTREG-DAG:   DBG_INSTR_REF {{.+}}, dbg-instr-ref(1, 0), dbg-instr-ref(2, 0)
 # FASTREG-DAG:   MOV64mr
 # FASTREG-DAG:   MOV32mr
 # FASTREG-NEXT:  JMP_1
 # FASTREG-LABEL: bb.1:
-# FASTREG:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(2, 0)\r
+# FASTREG:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(2, 0)
+# FASTREG-DAG:   DBG_INSTR_REF {{.+}}, dbg-instr-ref(2, 0), dbg-instr-ref(3, 0)
 # FASTREG-NEXT:  JMP_1
 # FASTREG-LABEL: bb.2:
-# FASTREG:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(3, 0)\r
+# FASTREG:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(3, 0)
+# FASTREG-DAG:   DBG_INSTR_REF {{.+}}, dbg-instr-ref(3, 0), dbg-instr-ref(4, 0)
 # FASTREG-NEXT:  CALL64pcrel32
 # FASTREG-LABEL: bb.3:
 # FASTREG-DAG:   MOV32rm
-# FASTREG-DAG:   DBG_INSTR_REF {{.+}}, dbg-instr-ref(4, 0)\r
+# FASTREG-DAG:   DBG_INSTR_REF {{.+}}, dbg-instr-ref(4, 0)
+# FASTREG-DAG:   DBG_INSTR_REF {{.+}}, dbg-instr-ref(4, 0), dbg-instr-ref(5, 0)
 # FASTREG-DAG:   MOV32mr
 # FASTREG-NEXT:  JMP_1
 # FASTREG-LABEL: bb.4:
-# FASTREG:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(5, 0)\r
+# FASTREG:       DBG_INSTR_REF {{.+}}, dbg-instr-ref(5, 0)
+# FASTREG-DAG:   DBG_INSTR_REF {{.+}}, dbg-instr-ref(5, 0), dbg-instr-ref(1, 0)
 # FASTREG-NEXT:  RET64
 
 --- |
@@ -83,6 +92,7 @@
   !17 = !DILocation(line: 6, column: 14, scope: !15)
   !18 = !DILocation(line: 8, column: 2, scope: !15)
   !19 = !DILocation(line: 7, column: 2, scope: !15)
+  !29 = !DILocalVariable(name: "c", scope: !4, file: !1, line: 5, type: !10)
 
 ...
 ---
@@ -112,18 +122,21 @@ body:             |
     %2:gr64 = COPY $rdi
     %3:gr64 = COPY killed %2
     %5:gr32 = COPY killed %4
-    DBG_INSTR_REF !9, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(1, 0), debug-location !16\r
+    DBG_INSTR_REF !9, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(1, 0), debug-location !16
+    DBG_INSTR_REF !29, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus), dbg-instr-ref(1, 0), dbg-instr-ref(2, 0), debug-location !16
     JMP_1 %bb.3
 
   bb.1:
-    DBG_INSTR_REF !9, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(2, 0), debug-location !16\r
+    DBG_INSTR_REF !9, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(2, 0), debug-location !16
+    DBG_INSTR_REF !29, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus), dbg-instr-ref(2, 0), dbg-instr-ref(3, 0), debug-location !16
     JMP_1 %bb.4
 
   bb.2:
     ADJCALLSTACKDOWN64 0, 0, 0, implicit-def $rsp, implicit-def $eflags, implicit-def $ssp, implicit $rsp, implicit $ssp, debug-location !19
     $edi = COPY %6, debug-location !19
     $al = MOV8ri 0, debug-location !19
-    DBG_INSTR_REF !9, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(3, 0), debug-location !16\r
+    DBG_INSTR_REF !9, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(3, 0), debug-location !16
+    DBG_INSTR_REF !29, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus), dbg-instr-ref(3, 0), dbg-instr-ref(4, 0), debug-location !16
     CALL64pcrel32 @foo, csr_64, implicit $rsp, implicit $ssp, implicit $al, implicit $edi, implicit-def $eax, debug-location !19
     ADJCALLSTACKUP64 0, 0, implicit-def $rsp, implicit-def $eflags, implicit-def $ssp, implicit $rsp, implicit $ssp, debug-location !19
     %7:gr32 = COPY $eax, debug-location !19
@@ -131,12 +144,14 @@ body:             |
 
   bb.3:
     %6:gr32 = MOV32rm %3, 1, $noreg, 0, $noreg, debug-location !17
-    DBG_INSTR_REF !9, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(4, 0), debug-location !16\r
+    DBG_INSTR_REF !9, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(4, 0), debug-location !16
+    DBG_INSTR_REF !29, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus), dbg-instr-ref(4, 0), dbg-instr-ref(5, 0), debug-location !16
     JMP_1 %bb.2
 
   bb.4:
     $eax = COPY %5, debug-location !18
-    DBG_INSTR_REF !9, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(5, 0), debug-location !16\r
+    DBG_INSTR_REF !9, !DIExpression(DW_OP_LLVM_arg, 0), dbg-instr-ref(5, 0), debug-location !16
+    DBG_INSTR_REF !29, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus), dbg-instr-ref(5, 0), dbg-instr-ref(1, 0), debug-location !16
     RET64 implicit $eax, debug-location !18
 
 ...
index c5b0060..f1d86da 100644 (file)
@@ -3142,6 +3142,91 @@ TEST_F(DIExpressionTest, convertToVariadicExpression) {
 #undef GET_EXPR
 }
 
+TEST_F(DIExpressionTest, convertToNonVariadicExpression) {
+#define EXPECT_CONVERT_IS_NOOP(TestExpr)                                       \
+  do {                                                                         \
+    std::optional<const DIExpression *> NonVariadic =                          \
+        DIExpression::convertToNonVariadicExpression(TestExpr);                \
+    EXPECT_TRUE(NonVariadic.has_value());                                      \
+    EXPECT_EQ(*NonVariadic, TestExpr);                                         \
+  } while (false)
+#define EXPECT_NON_VARIADIC_OPS_EQUAL(TestExpr, Expected)                      \
+  do {                                                                         \
+    std::optional<const DIExpression *> NonVariadic =                          \
+        DIExpression::convertToNonVariadicExpression(TestExpr);                \
+    EXPECT_TRUE(NonVariadic.has_value());                                      \
+    EXPECT_EQ(*NonVariadic, Expected);                                         \
+  } while (false)
+#define EXPECT_INVALID_CONVERSION(TestExpr)                                    \
+  do {                                                                         \
+    std::optional<const DIExpression *> NonVariadic =                          \
+        DIExpression::convertToNonVariadicExpression(TestExpr);                \
+    EXPECT_FALSE(NonVariadic.has_value());                                     \
+  } while (false)
+#define GET_EXPR(...) DIExpression::get(Context, {__VA_ARGS__})
+
+  // Expressions which are already non-variadic should be unaffected.
+  EXPECT_CONVERT_IS_NOOP(GET_EXPR());
+  EXPECT_CONVERT_IS_NOOP(GET_EXPR(dwarf::DW_OP_plus_uconst, 4));
+  EXPECT_CONVERT_IS_NOOP(
+      GET_EXPR(dwarf::DW_OP_plus_uconst, 4, dwarf::DW_OP_stack_value));
+  EXPECT_CONVERT_IS_NOOP(GET_EXPR(dwarf::DW_OP_plus_uconst, 6,
+                                  dwarf::DW_OP_stack_value,
+                                  dwarf::DW_OP_LLVM_fragment, 32, 32));
+  EXPECT_CONVERT_IS_NOOP(GET_EXPR(dwarf::DW_OP_plus_uconst, 14,
+                                  dwarf::DW_OP_LLVM_fragment, 32, 32));
+
+  // Variadic expressions with a single leading `LLVM_arg 0` and no other
+  // LLVM_args should have the leading arg removed.
+  EXPECT_NON_VARIADIC_OPS_EQUAL(GET_EXPR(dwarf::DW_OP_LLVM_arg, 0), GET_EXPR());
+  EXPECT_NON_VARIADIC_OPS_EQUAL(
+      GET_EXPR(dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_stack_value),
+      GET_EXPR(dwarf::DW_OP_stack_value));
+  EXPECT_NON_VARIADIC_OPS_EQUAL(
+      GET_EXPR(dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_LLVM_fragment, 16, 32),
+      GET_EXPR(dwarf::DW_OP_LLVM_fragment, 16, 32));
+  EXPECT_NON_VARIADIC_OPS_EQUAL(
+      GET_EXPR(dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_stack_value,
+               dwarf::DW_OP_LLVM_fragment, 24, 32),
+      GET_EXPR(dwarf::DW_OP_stack_value, dwarf::DW_OP_LLVM_fragment, 24, 32));
+  EXPECT_NON_VARIADIC_OPS_EQUAL(
+      GET_EXPR(dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_plus_uconst, 4),
+      GET_EXPR(dwarf::DW_OP_plus_uconst, 4));
+  EXPECT_NON_VARIADIC_OPS_EQUAL(
+      GET_EXPR(dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_plus_uconst, 4,
+               dwarf::DW_OP_stack_value),
+      GET_EXPR(dwarf::DW_OP_plus_uconst, 4, dwarf::DW_OP_stack_value));
+  EXPECT_NON_VARIADIC_OPS_EQUAL(
+      GET_EXPR(dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_plus_uconst, 6,
+               dwarf::DW_OP_stack_value, dwarf::DW_OP_LLVM_fragment, 32, 32),
+      GET_EXPR(dwarf::DW_OP_plus_uconst, 6, dwarf::DW_OP_stack_value,
+               dwarf::DW_OP_LLVM_fragment, 32, 32));
+  EXPECT_NON_VARIADIC_OPS_EQUAL(GET_EXPR(dwarf::DW_OP_LLVM_arg, 0,
+                                         dwarf::DW_OP_plus_uconst, 14,
+                                         dwarf::DW_OP_LLVM_fragment, 32, 32),
+                                GET_EXPR(dwarf::DW_OP_plus_uconst, 14,
+                                         dwarf::DW_OP_LLVM_fragment, 32, 32));
+
+  // Variadic expressions that have any LLVM_args other than a leading
+  // `LLVM_arg 0` cannot be converted and so should return std::nullopt.
+  EXPECT_INVALID_CONVERSION(GET_EXPR(
+      dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_LLVM_arg, 1, dwarf::DW_OP_mul));
+  EXPECT_INVALID_CONVERSION(
+      GET_EXPR(dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_LLVM_arg, 1,
+               dwarf::DW_OP_plus, dwarf::DW_OP_stack_value));
+  EXPECT_INVALID_CONVERSION(
+      GET_EXPR(dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_LLVM_arg, 0,
+               dwarf::DW_OP_minus, dwarf::DW_OP_stack_value));
+  EXPECT_INVALID_CONVERSION(GET_EXPR(dwarf::DW_OP_constu, 5,
+                                     dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_div,
+                                     dwarf::DW_OP_stack_value));
+
+#undef EXPECT_CONVERT_IS_NOOP
+#undef EXPECT_NON_VARIADIC_OPS_EQUAL
+#undef EXPECT_INVALID_CONVERSION
+#undef GET_EXPR
+}
+
 TEST_F(DIExpressionTest, replaceArg) {
 #define EXPECT_REPLACE_ARG_EQ(Expr, OldArg, NewArg, ...)                       \
   do {                                                                         \