diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp index 7ccd0bc9828bf..65be55b2840c3 100644 --- a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp @@ -2075,7 +2075,8 @@ void DwarfDebug::beginInstruction(const MachineInstr *MI) { bool PrevInstInSameSection = (!PrevInstBB || PrevInstBB->getSectionID() == MI->getParent()->getSectionID()); - if (DL == PrevInstLoc && PrevInstInSameSection) { + bool ForceIsStmt = ForceIsStmtInstrs.contains(MI); + if (DL == PrevInstLoc && PrevInstInSameSection && !ForceIsStmt) { // If we have an ongoing unspecified location, nothing to do here. if (!DL) return; @@ -2132,7 +2133,7 @@ void DwarfDebug::beginInstruction(const MachineInstr *MI) { // If the line changed, we call that a new statement; unless we went to // line 0 and came back, in which case it is not a new statement. unsigned OldLine = PrevInstLoc ? PrevInstLoc.getLine() : LastAsmLine; - if (DL.getLine() && DL.getLine() != OldLine) + if (DL.getLine() && (DL.getLine() != OldLine || ForceIsStmt)) Flags |= DWARF2_FLAG_IS_STMT; const MDNode *Scope = DL.getScope(); @@ -2308,6 +2309,143 @@ DwarfDebug::emitInitialLocDirective(const MachineFunction &MF, unsigned CUID) { return PrologEndLoc; } +/// For the function \p MF, finds the set of instructions which may represent a +/// change in line number from one or more of the preceding MBBs. Stores the +/// resulting set of instructions, which should have is_stmt set, in +/// ForceIsStmtInstrs. +void DwarfDebug::findForceIsStmtInstrs(const MachineFunction *MF) { + ForceIsStmtInstrs.clear(); + + // For this function, we try to find MBBs where the last source line in every + // block predecessor matches the first line seen in the block itself; for + // every such MBB, we set is_stmt=false on the first line in the block, and + // for every other block we set is_stmt=true on the first line. + // For example, if we have the block %bb.3, which has 2 predecesors %bb.1 and + // %bb.2: + // bb.1: + // $r3 = MOV64ri 12, debug-location !DILocation(line: 4) + // JMP %bb.3, debug-location !DILocation(line: 5) + // bb.2: + // $r3 = MOV64ri 24, debug-location !DILocation(line: 5) + // JMP %bb.3 + // bb.3: + // $r2 = MOV64ri 1 + // $r1 = ADD $r2, $r3, debug-location !DILocation(line: 5) + // When we examine %bb.3, we first check to see if it contains any + // instructions with debug locations, and select the first such instruction; + // in this case, the ADD, with line=5. We then examine both of its + // predecessors to see what the last debug-location in them is. For each + // predecessor, if they do not contain any debug-locations, or if the last + // debug-location before jumping to %bb.3 does not have line=5, then the ADD + // in %bb.3 must use IsStmt. In this case, all predecessors have a + // debug-location with line=5 as the last debug-location before jumping to + // %bb.3, so we do not set is_stmt for the ADD instruction - we know that + // whichever MBB we have arrived from, the line has not changed. + + const auto *TII = MF->getSubtarget().getInstrInfo(); + + // We only need to the predecessors of MBBs that could have is_stmt set by + // this logic. + SmallDenseSet PredMBBsToExamine; + SmallDenseMap PotentialIsStmtMBBInstrs; + // We use const_cast even though we won't actually modify MF, because some + // methods we need take a non-const MBB. + for (auto &MBB : *const_cast(MF)) { + if (MBB.empty() || MBB.pred_empty()) + continue; + for (auto &MI : MBB) { + if (MI.getDebugLoc() && MI.getDebugLoc()->getLine()) { + for (auto *Pred : MBB.predecessors()) + PredMBBsToExamine.insert(Pred); + PotentialIsStmtMBBInstrs.insert({&MBB, &MI}); + break; + } + } + } + + // For each predecessor MBB, we examine the last line seen before each branch + // or logical fallthrough. We use analyzeBranch to handle cases where + // different branches have different outgoing lines (i.e. if there are + // multiple branches that each have their own source location); otherwise we + // just use the last line in the block. + for (auto *MBB : PredMBBsToExamine) { + auto CheckMBBEdge = [&](MachineBasicBlock *Succ, unsigned OutgoingLine) { + auto MBBInstrIt = PotentialIsStmtMBBInstrs.find(Succ); + if (MBBInstrIt == PotentialIsStmtMBBInstrs.end()) + return; + MachineInstr *MI = MBBInstrIt->second; + if (MI->getDebugLoc()->getLine() == OutgoingLine) + return; + PotentialIsStmtMBBInstrs.erase(MBBInstrIt); + ForceIsStmtInstrs.insert(MI); + }; + // If this block is empty, we conservatively assume that its fallthrough + // successor needs is_stmt; we could check MBB's predecessors to see if it + // has a consistent entry line, but this seems unlikely to be worthwhile. + if (MBB->empty()) { + for (auto *Succ : MBB->successors()) + CheckMBBEdge(Succ, 0); + continue; + } + // If MBB has no successors that are in the "potential" set, due to one or + // more of them having confirmed is_stmt, we can skip this check early. + if (none_of(MBB->successors(), [&](auto *SuccMBB) { + return PotentialIsStmtMBBInstrs.contains(SuccMBB); + })) + continue; + // If we can't determine what DLs this branch's successors use, just treat + // all the successors as coming from the last DebugLoc. + SmallVector SuccessorBBs; + auto MIIt = MBB->rbegin(); + { + MachineBasicBlock *TBB = nullptr, *FBB = nullptr; + SmallVector Cond; + bool AnalyzeFailed = TII->analyzeBranch(*MBB, TBB, FBB, Cond); + // For a conditional branch followed by unconditional branch where the + // unconditional branch has a DebugLoc, that loc is the outgoing loc to + // the the false destination only; otherwise, both destinations share an + // outgoing loc. + if (!AnalyzeFailed && !Cond.empty() && FBB != nullptr && + MBB->back().getDebugLoc() && MBB->back().getDebugLoc()->getLine()) { + unsigned FBBLine = MBB->back().getDebugLoc()->getLine(); + assert(MIIt->isBranch() && "Bad result from analyzeBranch?"); + CheckMBBEdge(FBB, FBBLine); + ++MIIt; + SuccessorBBs.push_back(TBB); + } else { + // For all other cases, all successors share the last outgoing DebugLoc. + SuccessorBBs.assign(MBB->succ_begin(), MBB->succ_end()); + } + } + + // If we don't find an outgoing loc, this block will start with a line 0. + // It is possible that we have a block that has no DebugLoc, but acts as a + // simple passthrough between two blocks that end and start with the same + // line, e.g.: + // bb.1: + // JMP %bb.2, debug-location !10 + // bb.2: + // JMP %bb.3 + // bb.3: + // $r1 = ADD $r2, $r3, debug-location !10 + // If these blocks were merged into a single block, we would not attach + // is_stmt to the ADD, but with this logic that only checks the immediate + // predecessor, we will; we make this tradeoff because doing a full dataflow + // analysis would be expensive, and these situations are probably not common + // enough for this to be worthwhile. + unsigned LastLine = 0; + while (MIIt != MBB->rend()) { + if (auto DL = MIIt->getDebugLoc(); DL && DL->getLine()) { + LastLine = DL->getLine(); + break; + } + ++MIIt; + } + for (auto *Succ : SuccessorBBs) + CheckMBBEdge(Succ, LastLine); + } +} + // Gather pre-function debug information. Assumes being called immediately // after the function entry point has been emitted. void DwarfDebug::beginFunctionImpl(const MachineFunction *MF) { @@ -2326,6 +2464,8 @@ void DwarfDebug::beginFunctionImpl(const MachineFunction *MF) { // Record beginning of function. PrologEndLoc = emitInitialLocDirective( *MF, Asm->OutStreamer->getContext().getDwarfCompileUnitID()); + + findForceIsStmtInstrs(MF); } unsigned diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h index 6ed03124a2626..1ed20cb8ba57a 100644 --- a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h +++ b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h @@ -382,6 +382,8 @@ class DwarfDebug : public DebugHandlerBase { SmallPtrSet>; DenseMap LocalDeclsPerLS; + SmallDenseSet ForceIsStmtInstrs; + /// If nonnull, stores the current machine function we're processing. const MachineFunction *CurFn = nullptr; @@ -694,6 +696,8 @@ class DwarfDebug : public DebugHandlerBase { /// Emit the reference to the section. void emitSectionReference(const DwarfCompileUnit &CU); + void findForceIsStmtInstrs(const MachineFunction *MF); + protected: /// Gather pre-function debug information. void beginFunctionImpl(const MachineFunction *MF) override; diff --git a/llvm/test/DebugInfo/X86/is_stmt-at-block-start.ll b/llvm/test/DebugInfo/X86/is_stmt-at-block-start.ll new file mode 100644 index 0000000000000..ca9305d476d01 --- /dev/null +++ b/llvm/test/DebugInfo/X86/is_stmt-at-block-start.ll @@ -0,0 +1,62 @@ +;; Checks that when an instruction at the start of a BasicBlock has the same +;; DebugLoc as the instruction at the end of the previous BasicBlock, we add +;; is_stmt to the new line, to ensure that we still step on it if we arrive from +;; a BasicBlock other than the immediately preceding one, unless all known +;; predecessor BasicBlocks end with the same line. + +; RUN: %llc_dwarf -mtriple=x86_64-unknown-linux -O0 -filetype=obj < %s | llvm-dwarfdump --debug-line - | FileCheck %s + +; CHECK: {{0x[0-9a-f]+}} 13 5 {{.+}} is_stmt +; CHECK-NEXT: {{0x[0-9a-f]+}} 13 5 {{.+}} is_stmt + +define void @_Z1fi(i1 %cond) !dbg !21 { +entry: + br i1 %cond, label %if.then2, label %if.else4 + +if.then2: ; preds = %entry + br label %if.end8, !dbg !24 + +if.else4: ; preds = %entry + %0 = load i32, ptr null, align 4, !dbg !24 + %call5 = call i1 null(i32 %0) + ret void + +if.end8: ; preds = %if.then2 + ret void +} + +; CHECK: {{0x[0-9a-f]+}} 113 5 {{.+}} is_stmt +; CHECK-NOT: {{0x[0-9a-f]+}} 113 5 + +define void @_Z1gi(i1 %cond) !dbg !31 { +entry: + br i1 %cond, label %if.then2, label %if.else4, !dbg !34 + +if.then2: ; preds = %entry + br label %if.end8, !dbg !34 + +if.else4: ; preds = %entry + %0 = load i32, ptr null, align 4, !dbg !34 + %call5 = call i1 null(i32 %0) + ret void + +if.end8: ; preds = %if.then2 + ret void +} + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!20} + +!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 20.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.cpp", directory: "/home/gbtozers/dev/upstream-llvm") +!20 = !{i32 2, !"Debug Info Version", i32 3} +!21 = distinct !DISubprogram(name: "f", linkageName: "_Z1fi", scope: !1, file: !1, line: 7, type: !22, scopeLine: 7, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0) +!22 = distinct !DISubroutineType(types: !23) +!23 = !{null} +!24 = !DILocation(line: 13, column: 5, scope: !25) +!25 = distinct !DILexicalBlock(scope: !21, file: !1, line: 11, column: 27) +!31 = distinct !DISubprogram(name: "g", linkageName: "_Z1gi", scope: !1, file: !1, line: 107, type: !32, scopeLine: 7, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0) +!32 = distinct !DISubroutineType(types: !33) +!33 = !{null} +!34 = !DILocation(line: 113, column: 5, scope: !35) +!35 = distinct !DILexicalBlock(scope: !31, file: !1, line: 111, column: 27)