diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h --- a/llvm/include/llvm/IR/DebugInfoMetadata.h +++ b/llvm/include/llvm/IR/DebugInfoMetadata.h @@ -2805,6 +2805,13 @@ 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 + 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 diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp --- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp @@ -1132,12 +1132,11 @@ OS << " <- "; const DIExpression *Expr = MI->getDebugExpression(); - if (Expr->getNumElements() && Expr->isSingleLocationExpression() && - Expr->expr_op_begin()->getOp() == dwarf::DW_OP_LLVM_arg) { - SmallVector 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; diff --git a/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.h b/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.h --- a/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.h +++ b/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.h @@ -1156,7 +1156,8 @@ /// 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> SeenDbgPHIs; + DenseMap, std::optional> + SeenDbgPHIs; DbgOpIDMap DbgOpStore; @@ -1194,6 +1195,14 @@ std::optional 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 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); diff --git a/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp b/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp --- a/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp +++ b/llvm/lib/CodeGen/LiveDebugValues/InstrRefBasedImpl.cpp @@ -630,12 +630,21 @@ 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 @@ // 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 @@ 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 @@ 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 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 @@ } } - // 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 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 -- if no instruction defines + // the corresponding value, it must have been optimized out. + std::optional 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 @@ if (!TTracker) return true; + // Fetch the concrete DbgOps now, as we will need them later. + SmallVector 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 FoundLocs; + SmallVector 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 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 ReplacementQuality = + TTracker->getLocQualityIfBetter(CurL, Previous.getQuality()); + if (ReplacementQuality) { + Previous = TransferTracker::LocationAndQuality(CurL, *ReplacementQuality); + if (Previous.isBest()) { + ValuesToFind.erase(ValueToFindIt); + if (ValuesToFind.empty()) break; } } } SmallVector 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 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 @@ // 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 Result = resolveDbgPHIsImpl(MF, MLiveOuts, MLiveIns, Here, InstrNum); - SeenDbgPHIs.insert({&Here, Result}); + SeenDbgPHIs.insert({std::make_pair(&Here, InstrNum), Result}); return Result; } diff --git a/llvm/lib/IR/DebugInfoMetadata.cpp b/llvm/lib/IR/DebugInfoMetadata.cpp --- a/llvm/lib/IR/DebugInfoMetadata.cpp +++ b/llvm/lib/IR/DebugInfoMetadata.cpp @@ -1475,6 +1475,27 @@ return DIExpression::get(Expr->getContext(), NewOps); } +std::optional +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 NonVariadicOps( + make_range(ElementsBegin + 2, Expr->elements_end())); + return DIExpression::get(Expr->getContext(), NonVariadicOps); +} + void DIExpression::canonicalizeExpressionOps(SmallVectorImpl &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 --- /dev/null +++ b/llvm/test/DebugInfo/MIR/InstrRef/livedebugvalues-transfer-variadic-instr-ref.mir @@ -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 + +... diff --git a/llvm/test/DebugInfo/MIR/InstrRef/survives-livedebugvars.mir b/llvm/test/DebugInfo/MIR/InstrRef/survives-livedebugvars.mir --- a/llvm/test/DebugInfo/MIR/InstrRef/survives-livedebugvars.mir +++ b/llvm/test/DebugInfo/MIR/InstrRef/survives-livedebugvars.mir @@ -6,16 +6,20 @@ # livedebugvars-crossbb-interval.mir. # # CHECK-LABEL: bb.0: -# CHECK: DBG_INSTR_REF {{.+}}, dbg-instr-ref(1, 0) +# 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) +# 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) +# 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) +# 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 # # @@ -24,23 +28,28 @@ # the DBG_INSTR_REF lands on. # # FASTREG-LABEL: bb.0: -# FASTREG-DAG: DBG_INSTR_REF {{.+}}, dbg-instr-ref(1, 0) +# 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) +# 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) +# 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) +# 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) +# 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 @@ %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 + 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 + 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 + 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 @@ 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 + 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 + 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 ... diff --git a/llvm/unittests/IR/MetadataTest.cpp b/llvm/unittests/IR/MetadataTest.cpp --- a/llvm/unittests/IR/MetadataTest.cpp +++ b/llvm/unittests/IR/MetadataTest.cpp @@ -3142,6 +3142,91 @@ #undef GET_EXPR } +TEST_F(DIExpressionTest, convertToNonVariadicExpression) { +#define EXPECT_CONVERT_IS_NOOP(TestExpr) \ + do { \ + std::optional 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 NonVariadic = \ + DIExpression::convertToNonVariadicExpression(TestExpr); \ + EXPECT_TRUE(NonVariadic.has_value()); \ + EXPECT_EQ(*NonVariadic, Expected); \ + } while (false) +#define EXPECT_INVALID_CONVERSION(TestExpr) \ + do { \ + std::optional 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 { \