Skip to content

[DebugInfo] Don't apply is_stmt on MBB branches that preserve lines #108251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 142 additions & 2 deletions llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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<MachineBasicBlock *, 4> PredMBBsToExamine;
SmallDenseMap<MachineBasicBlock *, MachineInstr *> 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<MachineFunction *>(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<MachineBasicBlock *, 2> SuccessorBBs;
auto MIIt = MBB->rbegin();
{
MachineBasicBlock *TBB = nullptr, *FBB = nullptr;
SmallVector<MachineOperand, 4> 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) {
Expand All @@ -2326,6 +2464,8 @@ void DwarfDebug::beginFunctionImpl(const MachineFunction *MF) {
// Record beginning of function.
PrologEndLoc = emitInitialLocDirective(
*MF, Asm->OutStreamer->getContext().getDwarfCompileUnitID());

findForceIsStmtInstrs(MF);
}

unsigned
Expand Down
4 changes: 4 additions & 0 deletions llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ class DwarfDebug : public DebugHandlerBase {
SmallPtrSet<const MDNode *, 2>>;
DenseMap<const DILocalScope *, MDNodeSet> LocalDeclsPerLS;

SmallDenseSet<const MachineInstr *> ForceIsStmtInstrs;

/// If nonnull, stores the current machine function we're processing.
const MachineFunction *CurFn = nullptr;

Expand Down Expand Up @@ -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;
Expand Down
62 changes: 62 additions & 0 deletions llvm/test/DebugInfo/X86/is_stmt-at-block-start.ll
Original file line number Diff line number Diff line change
@@ -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.

Comment on lines +1 to +6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convention is to put the RUN lines above this IIRC? Either way I endorse every test having an explanation of what it's testing.

; 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)
Loading