Index: llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp =================================================================== --- llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp +++ llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp @@ -1,4 +1,5 @@ #include "llvm/CodeGen/AssignmentTrackingAnalysis.h" +#include "LiveDebugValues/LiveDebugValues.h" #include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/IntervalMap.h" #include "llvm/ADT/PostOrderIterator.h" @@ -47,6 +48,12 @@ static cl::opt PrintResults("print-debug-ata", cl::init(false), cl::Hidden); +/// Coalesce adjacent dbg locs describing memory locations that have contiguous +/// fragments. This reduces the cost of LiveDebugValues which does SSA +/// construction for each explicitly stated variable fragment. +static cl::opt + CoalesceAdjacentFragmentsOpt("debug-ata-coalesce-frags", cl::Hidden); + // Implicit conversions are disabled for enum class types, so unfortunately we // need to create a DenseMapInfo wrapper around the specified underlying type. template <> struct llvm::DenseMapInfo { @@ -280,6 +287,24 @@ return DebugAggregate(Var.getVariable(), Var.getInlinedAt()); } +static bool shouldCoalesceFragments(Function &F) { + // Enabling fragment coalescing reduces compiler run time when instruction + // referencing is enabled. However, it may cause LiveDebugVariables to create + // incorrect locations. Since instruction-referencing mode effectively + // bypasses LiveDebugVariables we only enable coalescing if the cl::opt flag + // has not been explicitly set and instruction-referencing is turned on. + switch (CoalesceAdjacentFragmentsOpt) { + case cl::boolOrDefault::BOU_UNSET: + return debuginfoShouldUseDebugInstrRef( + Triple(F.getParent()->getTargetTriple())); + case cl::boolOrDefault::BOU_TRUE: + return true; + case cl::boolOrDefault::BOU_FALSE: + return false; + } + llvm_unreachable("Unknown boolOrDefault value"); +} + namespace { /// In dwarf emission, the following sequence /// 1. dbg.value ... Fragment(0, 64) @@ -303,6 +328,7 @@ Function &Fn; FunctionVarLocsBuilder *FnVarLocs; const DenseSet *VarsWithStackSlot; + bool CoalesceAdjacentFragments; // 0 = no memory location. using BaseAddress = unsigned; @@ -567,6 +593,31 @@ << " bits [" << StartBit << ", " << EndBit << ")\n"); } + /// Inserts a new dbg def if the interval found when looking up \p StartBit + /// in \p FragMap starts before \p StartBit or ends after \p EndBit (which + /// indicates - assuming StartBit->EndBit has just been inserted - that the + /// slice has been coalesced in the map). + void coalesceFragments(BasicBlock &BB, Instruction &Before, unsigned Var, + unsigned StartBit, unsigned EndBit, unsigned Base, + DebugLoc DL, const FragsInMemMap &FragMap) { + if (!CoalesceAdjacentFragments) + return; + // We've inserted the location into the map. The map will have coalesced + // adjacent intervals (variable fragments) that describe the same memory + // location. Use this knowledge to insert a debug location that describes + // that coalesced fragment. This may eclipse other locs we've just + // inserted. This is okay as redundant locs will be cleaned up later. + auto CoalescedFrag = FragMap.find(StartBit); + // Bail if no coalescing has taken place. + if (CoalescedFrag.start() == StartBit && CoalescedFrag.stop() == EndBit) + return; + + LLVM_DEBUG(dbgs() << "- Insert loc for bits " << CoalescedFrag.start() + << " to " << CoalescedFrag.stop() << "\n"); + insertMemLoc(BB, Before, Var, CoalescedFrag.start(), CoalescedFrag.stop(), + Base, DL); + } + void addDef(const VarLocInfo &VarLoc, Instruction &Before, BasicBlock &BB, VarFragMap &LiveSet) { DebugVariable DbgVar = FnVarLocs->getVariable(VarLoc.VariableID); @@ -632,6 +683,8 @@ if (!FragMap.overlaps(StartBit, EndBit)) { LLVM_DEBUG(dbgs() << "- No overlaps\n"); FragMap.insert(StartBit, EndBit, Base); + coalesceFragments(BB, Before, Var, StartBit, EndBit, Base, VarLoc.DL, + FragMap); return; } // There is at least one overlap. @@ -722,6 +775,9 @@ LLVM_DEBUG(dbgs() << "- Insert DEF into now-empty space\n"); FragMap.insert(StartBit, EndBit, Base); } + + coalesceFragments(BB, Before, Var, StartBit, EndBit, Base, VarLoc.DL, + FragMap); } bool skipVariable(const DILocalVariable *V) { return !V->getSizeInBits(); } @@ -739,8 +795,10 @@ public: MemLocFragmentFill(Function &Fn, - const DenseSet *VarsWithStackSlot) - : Fn(Fn), VarsWithStackSlot(VarsWithStackSlot) {} + const DenseSet *VarsWithStackSlot, + bool CoalesceAdjacentFragments) + : Fn(Fn), VarsWithStackSlot(VarsWithStackSlot), + CoalesceAdjacentFragments(CoalesceAdjacentFragments) {} /// Add variable locations to \p FnVarLocs so that any bits of a variable /// with a memory location have that location explicitly reinstated at each @@ -857,8 +915,10 @@ for (auto FragMemLoc : FragMemLocs) { DIExpression *Expr = DIExpression::get(Ctx, std::nullopt); - Expr = *DIExpression::createFragmentExpression( - Expr, FragMemLoc.OffsetInBits, FragMemLoc.SizeInBits); + if (FragMemLoc.SizeInBits != + *Aggregates[FragMemLoc.Var].first->getSizeInBits()) + Expr = *DIExpression::createFragmentExpression( + Expr, FragMemLoc.OffsetInBits, FragMemLoc.SizeInBits); Expr = DIExpression::prepend(Expr, DIExpression::DerefAfter, FragMemLoc.OffsetInBits / 8); DebugVariable Var(Aggregates[FragMemLoc.Var].first, Expr, @@ -2412,7 +2472,8 @@ } if (Changed) { - MemLocFragmentFill Pass(Fn, &VarsWithStackSlot); + MemLocFragmentFill Pass(Fn, &VarsWithStackSlot, + shouldCoalesceFragments(Fn)); Pass.run(FnVarLocs); // Remove redundant entries. As well as reducing memory consumption and Index: llvm/test/DebugInfo/assignment-tracking/X86/coalesce-cfg.ll =================================================================== --- /dev/null +++ llvm/test/DebugInfo/assignment-tracking/X86/coalesce-cfg.ll @@ -0,0 +1,104 @@ +; RUN: llc %s -o - -stop-after=finalize-isel \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ + +;; Test coalescing of contiguous fragments in adjacent location definitions. +;; Further details and check directives inline. + +target triple = "x86_64-unknown-linux-gnu" + +@cond = dso_local global i8 0, align 1 + +;; The final store and linked dbg.assign indicate the whole variable is located +;; on the stack. Coalesce the two fragment defs that are generated (0-32, +;; 32-64) at the final dbg.assign into one (0-64, which covers the whole +;; variable meaning we don't need a fragment expression). And check the two +;; DBG_VALUEs in if.then are not coalesced, since they specify different +;; locations. This is the same as the first test in coalesce-simple.ll except +;; the dbg intrinsics are split up over a simple diamond CFG to check the info +;; is propagated betweeen blocks correctly. + +; CHECK-LABEL: bb.0.entry: +; CHECK-NEXT: successors: +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: TEST8mi +; CHECK-NEXT: JCC_1 %bb.2 +; CHECK-NEXT: JMP_1 %bb.1 + +; CHECK-LABEL: bb.1.if.then: +; CHECK-NEXT: successors: +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: MOV8mi $rip, 1, $noreg, @cond, $noreg, 0 :: (store (s8) into @cond) +; CHECK-NEXT: DBG_VALUE 1, $noreg, ![[#]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: JMP_1 %bb.3 + +; CHECK-LABEL: bb.2.if.else: +; CHECK-NEXT: successors: +; CHECK-NEXT: {{^ *$}} +; CHECK-NEXT: MOV8mi $rip, 1, $noreg, @cond, $noreg, 1 :: (store (s8) into @cond) + +; CHECK-LABEL: bb.3.if.end: +; CHECK-NEXT: MOV32mi %stack.0.a, 1, $noreg, 0, $noreg, 5 :: (store (s32) into %ir.a, align 8) +; CHECK-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: RET 0 + +define dso_local void @_Z3funv() local_unnamed_addr !dbg !16 { +entry: + %a = alloca i64, !DIAssignID !37 + call void @llvm.dbg.assign(metadata i64 poison, metadata !20, metadata !DIExpression(), metadata !37, metadata ptr %a, metadata !DIExpression()), !dbg !25 + %0 = load i8, ptr @cond, align 1 + %tobool = trunc i8 %0 to i1 + br i1 %tobool, label %if.then, label %if.else + +if.then: + store i1 false, ptr @cond + call void @llvm.dbg.value(metadata i32 1, metadata !20, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32)), !dbg !25 + br label %if.end + +if.else: + store i1 true, ptr @cond + br label %if.end + +if.end: + store i32 5, ptr %a, !DIAssignID !38 + call void @llvm.dbg.assign(metadata i32 5, metadata !20, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !38, metadata ptr %a, metadata !DIExpression()), !dbg !25 + ret void +} + +declare void @llvm.dbg.value(metadata, metadata, metadata) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!8, !9, !10, !11, !12, !13, !14} +!llvm.ident = !{!15} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "G", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 17.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{!0, !5} +!5 = !DIGlobalVariableExpression(var: !6, expr: !DIExpression()) +!6 = distinct !DIGlobalVariable(name: "F", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!8 = !{i32 7, !"Dwarf Version", i32 5} +!9 = !{i32 2, !"Debug Info Version", i32 3} +!10 = !{i32 1, !"wchar_size", i32 4} +!11 = !{i32 8, !"PIC Level", i32 2} +!12 = !{i32 7, !"PIE Level", i32 2} +!13 = !{i32 7, !"uwtable", i32 2} +!14 = !{i32 7, !"debug-info-assignment-tracking", i1 true} +!15 = !{!"clang version 17.0.0"} +!16 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funv", scope: !3, file: !3, line: 3, type: !17, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !19) +!17 = !DISubroutineType(types: !18) +!18 = !{null} +!19 = !{!20} +!20 = !DILocalVariable(name: "X", scope: !16, file: !3, line: 4, type: !21) +!21 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Pair", file: !3, line: 2, size: 64, flags: DIFlagTypePassByValue, elements: !22, identifier: "_ZTS4Pair") +!22 = !{} +!25 = !DILocation(line: 0, scope: !16) +!26 = !DILocation(line: 7, column: 7, scope: !27) +!27 = distinct !DILexicalBlock(scope: !16, file: !3, line: 7, column: 7) +!28 = distinct !DIAssignID() +!37 = distinct !DIAssignID() +!38 = distinct !DIAssignID() Index: llvm/test/DebugInfo/assignment-tracking/X86/coalesce-options.ll =================================================================== --- /dev/null +++ llvm/test/DebugInfo/assignment-tracking/X86/coalesce-options.ll @@ -0,0 +1,82 @@ +;; Test coalescing of contiguous fragments in adjacent location definitions. +;; This test contains the first function from coalesce-simple.ll. Just use it +;; to check whether coalescing happens or not with different flag settings. +;; +;; +=================+==============================+======================+ +;; | Coalescing flag | Instruction-Referencing flag | Coalescing behaviour | +;; +=================+==============================+======================+ +;; | default | enabled | enabled | +;; | default | disabled | disabled | +;; | enabled | * | enabled | +;; | disabled | * | disabled | +;; +-----------------+------------------------------+----------------------+ + +;; Coalescing default + instructino-referencing enabled = enable. +; RUN: llc %s -o - -stop-after=finalize-isel -experimental-debug-variable-locations=true \ +; RUN: | FileCheck %s --check-prefixes=CHECK,ENABLE + +;; Coalescing default + instructino-referencing disabled = disable. +; RUN: llc %s -o - -stop-after=finalize-isel -experimental-debug-variable-locations=false \ +; RUN: | FileCheck %s --check-prefixes=CHECK,DISABLE + +;; Coalescing enabled + instructino-referencing disabled = enable. +; RUN: llc %s -o - -stop-after=finalize-isel -experimental-debug-variable-locations=false \ +; RUN: -debug-ata-coalesce-frags=true \ +; RUN: | FileCheck %s --check-prefixes=CHECK,ENABLE + +;; Coalescing disabled + instructino-referencing enabled = disable. +; RUN: llc %s -o - -stop-after=finalize-isel -experimental-debug-variable-locations=true \ +; RUN: -debug-ata-coalesce-frags=false \ +; RUN: | FileCheck %s --check-prefixes=CHECK,DISABLE + +; CHECK: MOV32mi %stack.0.a, 1, $noreg, 0, $noreg, 5 +; ENABLE-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_deref) +; DISABLE-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) + +target triple = "x86_64-unknown-linux-gnu" + +define dso_local void @_Z3funv() local_unnamed_addr !dbg !16 { +entry: + %a = alloca i64, !DIAssignID !37 + call void @llvm.dbg.assign(metadata i64 poison, metadata !20, metadata !DIExpression(), metadata !37, metadata ptr %a, metadata !DIExpression()), !dbg !25 + call void @llvm.dbg.value(metadata i32 1, metadata !20, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32)), !dbg !25 + store i32 5, ptr %a, !DIAssignID !38 + call void @llvm.dbg.assign(metadata i32 5, metadata !20, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !38, metadata ptr %a, metadata !DIExpression()), !dbg !25 + ret void +} + +declare void @llvm.dbg.value(metadata, metadata, metadata) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!8, !9, !10, !11, !12, !13, !14} +!llvm.ident = !{!15} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "G", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 17.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{!0, !5} +!5 = !DIGlobalVariableExpression(var: !6, expr: !DIExpression()) +!6 = distinct !DIGlobalVariable(name: "F", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!8 = !{i32 7, !"Dwarf Version", i32 5} +!9 = !{i32 2, !"Debug Info Version", i32 3} +!10 = !{i32 1, !"wchar_size", i32 4} +!11 = !{i32 8, !"PIC Level", i32 2} +!12 = !{i32 7, !"PIE Level", i32 2} +!13 = !{i32 7, !"uwtable", i32 2} +!14 = !{i32 7, !"debug-info-assignment-tracking", i1 true} +!15 = !{!"clang version 17.0.0"} +!16 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funv", scope: !3, file: !3, line: 3, type: !17, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !19) +!17 = !DISubroutineType(types: !18) +!18 = !{null} +!19 = !{!20} +!20 = !DILocalVariable(name: "X", scope: !16, file: !3, line: 4, type: !21) +!21 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Pair", file: !3, line: 2, size: 64, flags: DIFlagTypePassByValue, elements: !22, identifier: "_ZTS4Pair") +!22 = !{} +!25 = !DILocation(line: 0, scope: !16) +!26 = !DILocation(line: 7, column: 7, scope: !27) +!27 = distinct !DILexicalBlock(scope: !16, file: !3, line: 7, column: 7) +!37 = distinct !DIAssignID() +!38 = distinct !DIAssignID() Index: llvm/test/DebugInfo/assignment-tracking/X86/coalesce-simple.ll =================================================================== --- /dev/null +++ llvm/test/DebugInfo/assignment-tracking/X86/coalesce-simple.ll @@ -0,0 +1,125 @@ +; RUN: llc %s -o - -stop-after=finalize-isel \ +; RUN: | FileCheck %s --implicit-check-not=DBG_ + +;; Test coalescing of contiguous fragments in adjacent location definitions. +;; Further details and check directives inline. + +target triple = "x86_64-unknown-linux-gnu" + +;; The final store and linked dbg.assign indicate the whole variable is located +;; on the stack. Coalesce the two fragment defs that are generated (0-32, +;; 32-64) at the final dbg.assign into one (0-64, which covers the whole +;; variable meaning we don't need a fragment expression). And check the +;; first two DBG_VALUEs are not coalesced, since they specify different +;; locations. +; CHECK: _Z3fun +; CHECK-LABEL: bb.0.entry: +; CHECK-NEXT: DBG_VALUE 1, $noreg, ![[#]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.0.a, 1, $noreg, 0, $noreg, 5 +; CHECK-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: RET +define dso_local void @_Z3funv() local_unnamed_addr !dbg !16 { +entry: + %a = alloca i64, !DIAssignID !37 + call void @llvm.dbg.assign(metadata i64 poison, metadata !20, metadata !DIExpression(), metadata !37, metadata ptr %a, metadata !DIExpression()), !dbg !25 + call void @llvm.dbg.value(metadata i32 1, metadata !20, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32)), !dbg !25 + store i32 5, ptr %a, !DIAssignID !38 + call void @llvm.dbg.assign(metadata i32 5, metadata !20, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !38, metadata ptr %a, metadata !DIExpression()), !dbg !25 + ret void +} + +;; Similar to the test above except that the variable has been split over two +;; allocas, so coalescing should not take place (different memory location for +;; the fragments). +; CHECK: Z3funv2 +; CHECK-LABEL: bb.0.entry: +; CHECK-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE %stack.1.b, $noreg, ![[#]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: DBG_VALUE 1, $noreg, ![[#]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: MOV32mi %stack.0.a, 1, $noreg, 0, $noreg, 5 +;; Both fragments 0-32 and 32-64 are in memory now, but located in different stack slots. +; CHECK-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: RET +define dso_local void @_Z3funv2() local_unnamed_addr !dbg !39 { +entry: + %a = alloca i32, !DIAssignID !42 + call void @llvm.dbg.assign(metadata i64 poison, metadata !41, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !42, metadata ptr %a, metadata !DIExpression()), !dbg !44 + %b = alloca i32, !DIAssignID !45 + call void @llvm.dbg.assign(metadata i64 poison, metadata !41, metadata !DIExpression(DW_OP_LLVM_fragment, 32, 32), metadata !45, metadata ptr %b, metadata !DIExpression()), !dbg !44 + call void @llvm.dbg.value(metadata i32 1, metadata !41, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32)), !dbg !44 + store i32 5, ptr %a, !DIAssignID !43 + call void @llvm.dbg.assign(metadata i32 5, metadata !41, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 32), metadata !43, metadata ptr %a, metadata !DIExpression()), !dbg !44 + ret void +} + +;; Similar to the first test above except the part that it's the slice 16-32 +;; that is "partially promoted". The dbg defs after the alloca cannot be +;; coalesced (slices 0-16 and 32-64 are in memory but 16-32 isn't). The entire +;; variable is on the stack after the store. +; CHECK: _Z2funv3 +; CHECK-LABEL: bb.0.entry: +; CHECK-NEXT: DBG_VALUE 2, $noreg, ![[#]], !DIExpression(DW_OP_LLVM_fragment, 16, 16) +; CHECK-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 16) +; CHECK-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +; CHECK-NEXT: MOV32mi %stack.0.a, 1, $noreg, 0, $noreg, 5 +; CHECK-NEXT: DBG_VALUE %stack.0.a, $noreg, ![[#]], !DIExpression(DW_OP_deref) +; CHECK-NEXT: RET +define dso_local void @_Z2funv3() local_unnamed_addr !dbg !46 { +entry: + %a = alloca i64, !DIAssignID !49 + call void @llvm.dbg.assign(metadata i64 poison, metadata !48, metadata !DIExpression(), metadata !49, metadata ptr %a, metadata !DIExpression()), !dbg !51 + call void @llvm.dbg.value(metadata i32 2, metadata !48, metadata !DIExpression(DW_OP_LLVM_fragment, 16, 16)), !dbg !51 + store i32 5, ptr %a, !DIAssignID !50 + call void @llvm.dbg.assign(metadata i32 5, metadata !48, metadata !DIExpression(DW_OP_LLVM_fragment, 16, 16), metadata !50, metadata ptr %a, metadata !DIExpression(DW_OP_plus_uconst, 2)), !dbg !51 + ret void +} + +declare void @llvm.dbg.value(metadata, metadata, metadata) +declare void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata) + +!llvm.dbg.cu = !{!2} +!llvm.module.flags = !{!8, !9, !10, !11, !12, !13, !14} +!llvm.ident = !{!15} + +!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +!1 = distinct !DIGlobalVariable(name: "G", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !3, producer: "clang version 17.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +!3 = !DIFile(filename: "test.cpp", directory: "/") +!4 = !{!0, !5} +!5 = !DIGlobalVariableExpression(var: !6, expr: !DIExpression()) +!6 = distinct !DIGlobalVariable(name: "F", scope: !2, file: !3, line: 1, type: !7, isLocal: false, isDefinition: true) +!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!8 = !{i32 7, !"Dwarf Version", i32 5} +!9 = !{i32 2, !"Debug Info Version", i32 3} +!10 = !{i32 1, !"wchar_size", i32 4} +!11 = !{i32 8, !"PIC Level", i32 2} +!12 = !{i32 7, !"PIE Level", i32 2} +!13 = !{i32 7, !"uwtable", i32 2} +!14 = !{i32 7, !"debug-info-assignment-tracking", i1 true} +!15 = !{!"clang version 17.0.0"} +!16 = distinct !DISubprogram(name: "fun", linkageName: "_Z3funv", scope: !3, file: !3, line: 3, type: !17, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !19) +!17 = !DISubroutineType(types: !18) +!18 = !{null} +!19 = !{!20} +!20 = !DILocalVariable(name: "X", scope: !16, file: !3, line: 4, type: !21) +!21 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "Pair", file: !3, line: 2, size: 64, flags: DIFlagTypePassByValue, elements: !22, identifier: "_ZTS4Pair") +!22 = !{} +!25 = !DILocation(line: 0, scope: !16) +!26 = !DILocation(line: 7, column: 7, scope: !27) +!27 = distinct !DILexicalBlock(scope: !16, file: !3, line: 7, column: 7) +!37 = distinct !DIAssignID() +!38 = distinct !DIAssignID() +!39 = distinct !DISubprogram(name: "fun2", linkageName: "_Z3funv2", scope: !3, file: !3, line: 3, type: !17, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !40) +!40 = !{!41} +!41 = !DILocalVariable(name: "X", scope: !39, file: !3, line: 10, type: !21) +!42 = distinct !DIAssignID() +!43 = distinct !DIAssignID() +!44 = !DILocation(line: 0, scope: !39) +!45 = distinct !DIAssignID() +!46 = distinct !DISubprogram(name: "fun3", linkageName: "_Z3funv3", scope: !3, file: !3, line: 3, type: !17, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !47) +!47 = !{!48} +!48 = !DILocalVariable(name: "X", scope: !46, file: !3, line: 10, type: !21) +!49 = distinct !DIAssignID() +!50 = distinct !DIAssignID() +!51 = !DILocation(line: 0, scope: !46) Index: llvm/test/DebugInfo/assignment-tracking/X86/lower-to-value.ll =================================================================== --- llvm/test/DebugInfo/assignment-tracking/X86/lower-to-value.ll +++ llvm/test/DebugInfo/assignment-tracking/X86/lower-to-value.ll @@ -1,5 +1,6 @@ ; RUN: llc %s -stop-before finalize-isel -o - \ ; RUN: -experimental-debug-variable-locations=false \ +; RUN: -debug-ata-coalesce-frags=true \ ; RUN: | FileCheck %s --check-prefixes=CHECK,DBGVALUE --implicit-check-not=DBG_VALUE ; RUN: llc %s -stop-before finalize-isel -o - \ ; RUN: -experimental-debug-variable-locations=true \ @@ -37,10 +38,7 @@ ;; Then there is a store to the upper 64 bits. ; CHECK: MOV64mi32 %stack.0.X, 1, $noreg, 8, $noreg, 0, debug-location -;; This DBG_VALUE is added by the mem-loc-frag-fill pass because bits [0, 64) -;; are still live in memory. -; CHECK-NEXT: DBG_VALUE %stack.0.X, $noreg, ![[VAR]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 64) -; CHECK-NEXT: DBG_VALUE %stack.0.X, $noreg, ![[VAR]], !DIExpression(DW_OP_plus_uconst, 8, DW_OP_deref, DW_OP_LLVM_fragment, 64, 64), debug-location +;; No change in variable location: the stack home is still valid. ;; The final assignment (X.B += 2) doesn't get stored back to the alloca. This ;; means that that the stack location isn't valid for the entire lifetime of X. Index: llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill-cfg.ll =================================================================== --- llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill-cfg.ll +++ llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill-cfg.ll @@ -1,5 +1,6 @@ ; RUN: llc %s -stop-before finalize-isel -o - \ ; RUN: -experimental-debug-variable-locations=false \ +; RUN: -debug-ata-coalesce-frags=true \ ; RUN: | FileCheck %s --implicit-check-not=DBG_ ; RUN: llc %s -stop-before finalize-isel -o - \ ; RUN: -experimental-debug-variable-locations=true \ @@ -8,8 +9,8 @@ ;; Check that the mem-loc-frag-fill pseudo-pass works on a simple CFG. When ;; LLVM sees a dbg.value with an overlapping fragment it essentially considers ;; the previous location as valid for all bits in that fragment. The pass -;; inserts dbg.value fragments to preserve memory locations for bits in memory -;; when overlapping fragments are encountered. +;; tracks which bits are in memory and inserts dbg.values preserve memory +;; locations for bits in memory when overlapping fragments are encountered. ;; nums lives in mem, except prior to the second call to step() where there has ;; been some DSE. At this point, the memory loc for nums.c is invalid. But the @@ -82,8 +83,9 @@ call void @llvm.dbg.assign(metadata i32 2, metadata !44, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !56, metadata ptr %c, metadata !DIExpression()), !dbg !46 %call1 = tail call noundef zeroext i1 @_Z4stepv(), !dbg !57 store i32 1, ptr %c, align 4, !dbg !58, !DIAssignID !61 +;; Store to bits 64 to 96 - the whole variable is in memory again. ; CHECK: MOV32mi %stack.0.nums, 1, $noreg, 8, $noreg, 1 -; CHECK-NEXT: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_plus_uconst, 8, DW_OP_deref, DW_OP_LLVM_fragment, 64, 32) +; CHECK-NEXT: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_deref) call void @llvm.dbg.assign(metadata i32 1, metadata !44, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !61, metadata ptr %c, metadata !DIExpression()), !dbg !46 call void @_Z4esc1P4Nums(ptr noundef nonnull %nums), !dbg !62 ret i32 0, !dbg !64 Index: llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll =================================================================== --- llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll +++ llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll @@ -1,5 +1,6 @@ ; RUN: llc %s -stop-before finalize-isel -o - \ ; RUN: -experimental-debug-variable-locations=false \ +; RUN: -debug-ata-coalesce-frags=true \ ; RUN: | FileCheck %s --implicit-check-not=DBG_ ; RUN: llc %s -stop-before finalize-isel -o - \ ; RUN: -experimental-debug-variable-locations=true \ @@ -50,7 +51,7 @@ store i32 2, ptr %c, align 8, !dbg !25, !DIAssignID !31 ; CHECK: MOV32mi %stack.0.nums, 1, $noreg, 8, $noreg, 2 call void @llvm.dbg.assign(metadata i32 2, metadata !12, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 32), metadata !31, metadata ptr %c, metadata !DIExpression()), !dbg !19 -; CHECK-NEXT: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_plus_uconst, 8, DW_OP_deref, DW_OP_LLVM_fragment, 64, 32) +; CHECK-NEXT: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_deref) tail call void @_Z4stepv(), !dbg !32 ;; Next dbg.assign added by hand to test that the bits [64, 32) have been ;; correctly tracked as in memory - we know this has worked if Index: llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-frags.ll =================================================================== --- llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-frags.ll +++ llvm/test/DebugInfo/assignment-tracking/X86/nested-loop-frags.ll @@ -122,8 +122,6 @@ ; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref) ; CHECK-NEXT: MOV64mi32 %stack.0.a.addr, 1, $noreg, 0, $noreg, 1 ; CHECK-NEXT: MOV64mi32 %stack.1.b.addr, 1, $noreg, 0, $noreg, 2 -; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) -; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) ; CHECK-NEXT: MOV32mi %stack.0.a.addr, 1, $noreg, 0, $noreg, 3 ; CHECK-NEXT: DBG_VALUE $noreg, $noreg, ![[a]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) ; CHECK-NEXT: DBG_VALUE %stack.0.a.addr, $noreg, ![[a]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) @@ -156,7 +154,6 @@ ; CHECK-LABEL: bb.2.do.body1: ; CHECK-NEXT: successors ; CHECK-NEXT: {{^ *$}} -; CxHECK-NEXT: DBG_VALUE 11, $noreg, ![[f]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) ; CHECK: JMP_1 ; CHECK-NEXT: {{^ *$}} @@ -174,13 +171,12 @@ ; CHECK-NEXT: {{^ *$}} ; CHECK-NEXT: %5:gr32 = MOV32rm $rip, 1, $noreg, @g_a, $noreg ; CHECK-NEXT: MOV32mr %stack.1.b.addr, 1, $noreg, 0, $noreg, killed %5 -; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK-NEXT: DBG_VALUE %stack.1.b.addr, $noreg, ![[b]], !DIExpression(DW_OP_deref) ; CHECK-NEXT: MOV32mi %stack.3.d.addr, 1, $noreg, 0, $noreg, 6 ; CHECK-NEXT: DBG_VALUE 8, $noreg, ![[e]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) ; CHECK-NEXT: MOV32mi %stack.4.e.addr, 1, $noreg, 0, $noreg, 8 ; CHECK-NEXT: DBG_VALUE %stack.4.e.addr, $noreg, ![[e]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) ; CHECK-NEXT: JMP_1 %bb.5 -; CHECxK-NEXT: {{^ *$}} if.else: ; preds = %do.body1 store i32 6, ptr %d.addr, !DIAssignID !74 ; VAR:d Index: llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def-2.ll =================================================================== --- llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def-2.ll +++ llvm/test/DebugInfo/assignment-tracking/X86/use-known-value-at-early-mem-def-2.ll @@ -18,9 +18,12 @@ ;; live in memory. ; CHECK-NEXT: DBG_VALUE %stack.0.c, $noreg, ![[var]], !DIExpression(DW_OP_plus_uconst, 4, DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +;; After the call to @d there's a dbg.assign linked to the store to bits 0-32 +;; that comes before it. Meaning the stack location for bits 0-32 are valid +;; from here (bits 32-64 for the variable are already located in memory). ; CHECK: CALL64pcrel32 @d ; CHECK-NEXT: ADJCALLSTACKUP64 -; CHECK-NEXT: DBG_VALUE %stack.0.c, $noreg, ![[var]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32), debug-location +; CHECK-NEXT: DBG_VALUE %stack.0.c, $noreg, ![[var]], !DIExpression(DW_OP_deref), debug-location 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"