diff --git a/llvm/docs/AssignmentTracking.md b/llvm/docs/AssignmentTracking.md index ae4891236c25..5d6d68367aa8 100644 --- a/llvm/docs/AssignmentTracking.md +++ b/llvm/docs/AssignmentTracking.md @@ -198,9 +198,6 @@ the choice at each instruction, iteratively joining the results for each block. Outstanding improvements: -* As mentioned in test llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll, - the analysis should treat escaping calls like untagged stores. - * The system expects locals to be backed by a local alloca. This isn't always the case - sometimes a pointer to storage is passed into a function (e.g. sret, byval). We need to be able to handle those cases. See diff --git a/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp b/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp index 1ca3a2cc6850..2bd278614f8a 100644 --- a/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp +++ b/llvm/lib/CodeGen/AssignmentTrackingAnalysis.cpp @@ -15,6 +15,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/UniqueVector.h" +#include "llvm/Analysis/ValueTracking.h" #include "llvm/BinaryFormat/Dwarf.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/DataLayout.h" @@ -1078,6 +1079,9 @@ public: SmallVector>>; using UnknownStoreAssignmentMap = DenseMap>; + using EscapingCallVarsMap = + DenseMap>>; private: /// The highest numbered VariableID for partially promoted variables plus 1, @@ -1091,6 +1095,10 @@ private: /// Map untagged unknown stores (e.g. strided/masked store intrinsics) /// to the variables they may assign to. Used by processUntaggedInstruction. UnknownStoreAssignmentMap UnknownStoreVars; + /// Map escaping calls (calls that receive a pointer to a tracked alloca as + /// an argument) to the variables they may modify. Used by + /// processEscapingCall. + EscapingCallVarsMap EscapingCallVars; // Machinery to defer inserting dbg.values. using InstInsertMap = MapVector>; @@ -1326,6 +1334,7 @@ private: void processUntaggedInstruction(Instruction &I, BlockInfo *LiveSet); void processUnknownStoreToVariable(Instruction &I, VariableID &Var, BlockInfo *LiveSet); + void processEscapingCall(Instruction &I, BlockInfo *LiveSet); void processDbgAssign(DbgVariableRecord *Assign, BlockInfo *LiveSet); void processDbgVariableRecord(DbgVariableRecord &DVR, BlockInfo *LiveSet); void processDbgValue(DbgVariableRecord *DbgValue, BlockInfo *LiveSet); @@ -1549,6 +1558,11 @@ void AssignmentTrackingLowering::processNonDbgInstruction( processTaggedInstruction(I, LiveSet); else processUntaggedInstruction(I, LiveSet); + + // Handle calls that pass tracked alloca pointers as arguments. + // The callee may modify the pointed-to memory. + if (isa(I)) + processEscapingCall(I, LiveSet); } void AssignmentTrackingLowering::processUnknownStoreToVariable( @@ -1672,6 +1686,62 @@ void AssignmentTrackingLowering::processUntaggedInstruction( } } +void AssignmentTrackingLowering::processEscapingCall( + Instruction &I, AssignmentTrackingLowering::BlockInfo *LiveSet) { + auto It = EscapingCallVars.find(&I); + if (It == EscapingCallVars.end()) + return; + + LLVM_DEBUG(dbgs() << "processEscapingCall on " << I << "\n"); + + const DataLayout &Layout = Fn.getDataLayout(); + + for (auto &[Var, Addr, AddrExpr] : It->second) { + // An escaping call is treated like an untagged store, whatever value is + // now in memory is the current value of the variable. We set both the + // stack and debug assignments to NoneOrPhi (we don't know which source + // assignment this corresponds to) and set the location to Mem (memory + // is valid). + addMemDef(LiveSet, Var, Assignment::makeNoneOrPhi()); + addDbgDef(LiveSet, Var, Assignment::makeNoneOrPhi()); + setLocKind(LiveSet, Var, LocKind::Mem); + + LLVM_DEBUG(dbgs() << " escaping call may modify " + << FnVarLocs->getVariable(Var).getVariable()->getName() + << ", setting LocKind to Mem\n"); + + // Build the memory location expression using the DVR's address and + // address expression, following the same pattern as emitDbgValue. + DebugVariable V = FnVarLocs->getVariable(Var); + DIExpression *Expr = AddrExpr; + + if (auto Frag = V.getFragment()) { + auto R = DIExpression::createFragmentExpression(Expr, Frag->OffsetInBits, + Frag->SizeInBits); + assert(R && "unexpected createFragmentExpression failure"); + Expr = *R; + } + + Value *Val = Addr; + std::tie(Val, Expr) = walkToAllocaAndPrependOffsetDeref(Layout, Val, Expr); + + auto InsertBefore = getNextNode(&I); + assert(InsertBefore && "Shouldn't be inserting after a terminator"); + + DILocation *InlinedAt = const_cast(V.getInlinedAt()); + const DILocation *DILoc = DILocation::get( + Fn.getContext(), 0, 0, V.getVariable()->getScope(), InlinedAt); + + VarLocInfo VarLoc; + VarLoc.VariableID = Var; + VarLoc.Expr = Expr; + VarLoc.Values = + RawLocationWrapper(ValueAsMetadata::get(const_cast(Val))); + VarLoc.DL = DILoc; + InsertBeforeMap[InsertBefore].push_back(VarLoc); + } +} + void AssignmentTrackingLowering::processTaggedInstruction( Instruction &I, AssignmentTrackingLowering::BlockInfo *LiveSet) { auto LinkedDPAssigns = at::getDVRAssignmentMarkers(&I); @@ -2106,8 +2176,10 @@ AllocaInst *getUnknownStore(const Instruction &I, const DataLayout &Layout) { /// subsequent variables are either stack homed or fully promoted. /// /// Finally, populate UntaggedStoreVars with a mapping of untagged stores to -/// the stored-to variable fragments, and UnknownStoreVars with a mapping -/// of untagged unknown stores to the stored-to variable aggregates. +/// the stored-to variable fragments, UnknownStoreVars with a mapping of +/// untagged unknown stores to the stored-to variable aggregates, and +/// EscapingCallVars with a mapping of calls that receive a pointer to a +/// tracked alloca as an argument to the variables they may modify. /// /// These tasks are bundled together to reduce the number of times we need /// to iterate over the function as they can be achieved together in one pass. @@ -2116,6 +2188,7 @@ static AssignmentTrackingLowering::OverlapMap buildOverlapMapAndRecordDeclares( const DenseSet &VarsWithStackSlot, AssignmentTrackingLowering::UntaggedStoreAssignmentMap &UntaggedStoreVars, AssignmentTrackingLowering::UnknownStoreAssignmentMap &UnknownStoreVars, + AssignmentTrackingLowering::EscapingCallVarsMap &EscapingCallVars, unsigned &TrackedVariablesVectorSize) { DenseSet Seen; // Map of Variable: [Fragments]. @@ -2200,6 +2273,53 @@ static AssignmentTrackingLowering::OverlapMap buildOverlapMapAndRecordDeclares( for (DbgVariableRecord *DVR : at::getDVRAssignmentMarkers(AI)) HandleDbgAssignForUnknownStore(DVR); } + + // Check for escaping calls. + auto *CB = dyn_cast(&I); + if (!CB) + continue; + + // Skip intrinsics. Their memory effects are modeled individually. + if (isa(CB)) + continue; + + // Skip calls that cannot write to memory at all. + if (CB->onlyReadsMemory()) + continue; + + SmallDenseSet SeenVars; + for (unsigned ArgIdx = 0; ArgIdx < CB->arg_size(); ++ArgIdx) { + Value *Arg = CB->getArgOperand(ArgIdx); + if (!Arg->getType()->isPointerTy()) + continue; + // Skip args the callee cannot write through. + if (CB->paramHasAttr(ArgIdx, Attribute::ReadOnly) || + CB->paramHasAttr(ArgIdx, Attribute::ReadNone)) + continue; + // Skip byval args. The callee gets a copy, not the original. + if (CB->paramHasAttr(ArgIdx, Attribute::ByVal)) + continue; + + auto *AI = dyn_cast(getUnderlyingObject(Arg)); + if (!AI) + continue; + + // Find tracked variables on this alloca. We use the whole-variable + // (no fragment) because we don't know which part the callee + // modifies. addMemDef/addDbgDef/setLocKind will propagate to + // contained fragments. + for (DbgVariableRecord *DVR : at::getDVRAssignmentMarkers(AI)) { + DebugVariable DV(DVR->getVariable(), std::nullopt, + DVR->getDebugLoc().getInlinedAt()); + DebugAggregate DA = {DV.getVariable(), DV.getInlinedAt()}; + if (!VarsWithStackSlot.contains(DA)) + continue; + VariableID VarID = FnVarLocs->insertVariable(DV); + if (SeenVars.insert(VarID).second) + EscapingCallVars[&I].push_back( + {VarID, DVR->getAddress(), DVR->getAddressExpression()}); + } + } } } @@ -2271,7 +2391,7 @@ bool AssignmentTrackingLowering::run(FunctionVarLocsBuilder *FnVarLocsBuilder) { // appears to be rare occurance. VarContains = buildOverlapMapAndRecordDeclares( Fn, FnVarLocs, *VarsWithStackSlot, UntaggedStoreVars, UnknownStoreVars, - TrackedVariablesVectorSize); + EscapingCallVars, TrackedVariablesVectorSize); // Prepare for traversal. ReversePostOrderTraversal RPOT(&Fn); diff --git a/llvm/test/DebugInfo/assignment-tracking/AArch64/escaping-call-tag-offset.ll b/llvm/test/DebugInfo/assignment-tracking/AArch64/escaping-call-tag-offset.ll new file mode 100644 index 000000000000..44d6a2f5bcea --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/AArch64/escaping-call-tag-offset.ll @@ -0,0 +1,51 @@ +; RUN: llc %s -stop-after=finalize-isel -o - \ +; RUN: | FileCheck %s + +target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" +target triple = "aarch64-unknown-linux-android24" + +declare void @use(ptr) + +;; Test: Escaping call with DW_OP_LLVM_tag_offset in address expression. +;; +;; The dbg_assigns use !DIExpression(DW_OP_LLVM_tag_offset, 128) as the +;; address expression. After the escaping call to @use, the reinstated +;; DBG_VALUE should include the tag offset in its expression. + +; CHECK-LABEL: name: test_tag_offset_escaping +; CHECK: bb.0.entry: +; CHECK: DBG_VALUE %stack.0.x, $noreg, !{{[0-9]+}}, !DIExpression(DW_OP_LLVM_tag_offset, 128, DW_OP_deref) +; CHECK: STRWui +; CHECK: DBG_VALUE $noreg, $noreg, !{{[0-9]+}}, !DIExpression() +; CHECK: BL @use +; CHECK: DBG_VALUE %stack.0.x, $noreg, !{{[0-9]+}}, !DIExpression(DW_OP_LLVM_tag_offset, 128, DW_OP_deref) + +define void @test_tag_offset_escaping(i32 %a) !dbg !7 { +entry: + %x = alloca i32, align 4, !DIAssignID !20 + #dbg_assign(i1 poison, !11, !DIExpression(), !20, ptr %x, !DIExpression(DW_OP_LLVM_tag_offset, 128), !12) + store i32 1, ptr %x, align 4, !DIAssignID !21 + #dbg_assign(i32 1, !11, !DIExpression(), !21, ptr %x, !DIExpression(DW_OP_LLVM_tag_offset, 128), !12) + #dbg_value(i32 %a, !11, !DIExpression(), !12) + call void @use(ptr %x) + ret void, !dbg !13 +} + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} + +!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) +!1 = !DIFile(filename: "test.c", directory: "/tmp") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 5} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 7, !"debug-info-assignment-tracking", i1 true} +!6 = !DISubroutineType(types: !2) +!14 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) + +!7 = distinct !DISubprogram(name: "test_tag_offset_escaping", scope: !1, file: !1, line: 1, type: !6, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0) +!11 = !DILocalVariable(name: "x", scope: !7, file: !1, line: 2, type: !14) +!12 = !DILocation(line: 2, column: 1, scope: !7) +!13 = !DILocation(line: 5, column: 1, scope: !7) +!20 = distinct !DIAssignID() +!21 = distinct !DIAssignID() \ No newline at end of file diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll b/llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll index b20b166cb9cd..67eee5ceff47 100644 --- a/llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll +++ b/llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll @@ -15,10 +15,9 @@ ;; if.end: ;; mem(a) = !20 ; two preds disagree that !20 is the last assignment, don't ;; ; use mem loc. -;; ; This feels highly unfortunate, and highlights the need to reinstate the -;; ; memory location at call sites leaking the address (in an ideal world, -;; ; the memory location would always be in use at that point and so this -;; ; wouldn't be necessary). +;; ; The escaping call reinstates the memory location. The analysis treats +;; ; calls that leak the alloca address as untagged stores, so the memory +;; ; location is valid after the call. ;; esc(a) ; force the memory location ;; In real world examples this is caused by InstCombine sinking common code @@ -30,10 +29,12 @@ ; CHECK-LABEL: bb.1.if.then: ; CHECK: DBG_VALUE 0, $noreg, ![[A]], !DIExpression() -;; === TODO / WISHLIST === -; LEBAL-KCEHC: bb.2.if.end: -; KCEHC: CALL64pcrel32 target-flags(x86-plt) @es -; KCEHC: DBG_VALUE %stack.0.a.addr, $noreg, ![[A]], !DIExpression(DW_OP_deref) +;; After the escaping call to @es, the memory location for 'a' is reinstated. +;; The analysis treats escaping calls (calls receiving a pointer to a tracked +;; alloca) like untagged stores, validating the memory location. +; CHECK-LABEL: bb.2.if.end: +; CHECK: CALL64pcrel32 {{.*}}@es +; CHECK: DBG_VALUE %stack.0.a.addr, $noreg, ![[A]], !DIExpression(DW_OP_deref) target triple = "x86_64-unknown-linux-gnu" diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/escaping-call.ll b/llvm/test/DebugInfo/assignment-tracking/X86/escaping-call.ll new file mode 100644 index 000000000000..c9db25bf9f69 --- /dev/null +++ b/llvm/test/DebugInfo/assignment-tracking/X86/escaping-call.ll @@ -0,0 +1,224 @@ +; RUN: llc %s -stop-after=finalize-isel -o - \ +; RUN: | FileCheck %s + +;; Test that assignment tracking correctly handles calls where a pointer to a +;; tracked alloca escapes as an argument. After such a call, the memory +;; location should be reinstated because the callee may have modified the +;; variable through the pointer. +;; +;; Each function uses a #dbg_value to force LocKind::Val at some point, which +;; prevents the variable from being "always stack homed" and causes the +;; analysis to emit per-instruction DBG_VALUE records. + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +declare void @clobber(ptr) +declare i32 @readonly_func(ptr readonly) +declare void @byval_func(ptr byval(i32)) +declare void @clobber_pair(ptr) + +;; Test 1: Basic escaping call reinstates memory location. +;; +;; After the #dbg_value switches to Val (DBG_VALUE $noreg because %a has no +;; vreg), the escaping call to @clobber should reinstate the memory location. +;; +; CHECK-LABEL: name: test_basic_escaping_call +; CHECK: bb.0.entry: +; CHECK: DBG_VALUE %stack.0.x, $noreg, !{{[0-9]+}}, !DIExpression(DW_OP_deref) +; CHECK: MOV32mi %stack.0.x +; CHECK: DBG_VALUE $noreg, $noreg, !{{[0-9]+}}, !DIExpression() +; CHECK: CALL64pcrel32 {{.*}}@clobber +;; After the escaping call, memory location is reinstated: +; CHECK: DBG_VALUE %stack.0.x, $noreg, !{{[0-9]+}}, !DIExpression(DW_OP_deref) +; CHECK: RET 0 + +define void @test_basic_escaping_call(i32 %a) !dbg !7 { +entry: + %x = alloca i32, align 4, !DIAssignID !20 + #dbg_assign(i1 poison, !11, !DIExpression(), !20, ptr %x, !DIExpression(), !12) + store i32 1, ptr %x, align 4, !DIAssignID !21 + #dbg_assign(i32 1, !11, !DIExpression(), !21, ptr %x, !DIExpression(), !12) + #dbg_value(i32 %a, !11, !DIExpression(), !12) + call void @clobber(ptr %x) + ret void, !dbg !13 +} + +;; Test 2: Escaping call followed by a tagged store. +;; +;; Verifies that the escaping call resets state so the subsequent tagged +;; store correctly shows Mem (no stale value from before the call). +;; +; CHECK-LABEL: name: test_escaping_then_store +; CHECK: bb.0.entry: +; CHECK: DBG_VALUE %stack.0.y, $noreg, !{{[0-9]+}}, !DIExpression(DW_OP_deref) +; CHECK: MOV32mi %stack.0.y +; CHECK: DBG_VALUE $noreg, $noreg, !{{[0-9]+}}, !DIExpression() +; CHECK: CALL64pcrel32 {{.*}}@clobber +;; After escaping call, memory location reinstated: +; CHECK: DBG_VALUE %stack.0.y, $noreg, !{{[0-9]+}}, !DIExpression(DW_OP_deref) +;; Then the second store (still Mem, redundant DBG_VALUE elided): +; CHECK: MOV32mi %stack.0.y +; CHECK: RET 0 + +define void @test_escaping_then_store(i32 %a) !dbg !30 { +entry: + %y = alloca i32, align 4, !DIAssignID !40 + #dbg_assign(i1 poison, !31, !DIExpression(), !40, ptr %y, !DIExpression(), !32) + store i32 1, ptr %y, align 4, !DIAssignID !41 + #dbg_assign(i32 1, !31, !DIExpression(), !41, ptr %y, !DIExpression(), !32) + #dbg_value(i32 %a, !31, !DIExpression(), !32) + call void @clobber(ptr %y) + store i32 2, ptr %y, align 4, !DIAssignID !42 + #dbg_assign(i32 2, !31, !DIExpression(), !42, ptr %y, !DIExpression(), !32) + ret void, !dbg !33 +} + +;; Test 3: Readonly call should NOT reinstate memory location. +;; +;; A readonly call cannot modify memory, so no DBG_VALUE after the call. +;; +; CHECK-LABEL: name: test_readonly_not_escaping +; CHECK: bb.0.entry: +; CHECK: DBG_VALUE %stack.0.z, $noreg, !{{[0-9]+}}, !DIExpression(DW_OP_deref) +; CHECK: MOV32mi %stack.0.z +; CHECK: DBG_VALUE $noreg, $noreg, !{{[0-9]+}}, !DIExpression() +; CHECK: CALL64pcrel32 {{.*}}@readonly_func +; CHECK-NOT: DBG_VALUE +; CHECK: RET 0 + +define void @test_readonly_not_escaping(i32 %a) !dbg !50 { +entry: + %z = alloca i32, align 4, !DIAssignID !60 + #dbg_assign(i1 poison, !51, !DIExpression(), !60, ptr %z, !DIExpression(), !52) + store i32 42, ptr %z, align 4, !DIAssignID !61 + #dbg_assign(i32 42, !51, !DIExpression(), !61, ptr %z, !DIExpression(), !52) + #dbg_value(i32 %a, !51, !DIExpression(), !52) + %r = call i32 @readonly_func(ptr readonly %z) + ret void, !dbg !53 +} + +;; Test 4: Byval call should NOT reinstate memory location. +;; +;; A byval argument passes a copy. The callee cannot modify the original. +;; +; CHECK-LABEL: name: test_byval_not_escaping +; CHECK: bb.0.entry: +; CHECK: DBG_VALUE %stack.0.w, $noreg, !{{[0-9]+}}, !DIExpression(DW_OP_deref) +; CHECK: MOV32mi %stack.0.w +; CHECK: DBG_VALUE $noreg, $noreg, !{{[0-9]+}}, !DIExpression() +; CHECK: CALL64pcrel32 {{.*}}@byval_func +; CHECK-NOT: DBG_VALUE +; CHECK: RET 0 + +define void @test_byval_not_escaping(i32 %a) !dbg !70 { +entry: + %w = alloca i32, align 4, !DIAssignID !80 + #dbg_assign(i1 poison, !71, !DIExpression(), !80, ptr %w, !DIExpression(), !72) + store i32 10, ptr %w, align 4, !DIAssignID !81 + #dbg_assign(i32 10, !71, !DIExpression(), !81, ptr %w, !DIExpression(), !72) + #dbg_value(i32 %a, !71, !DIExpression(), !72) + call void @byval_func(ptr byval(i32) %w) + ret void, !dbg !73 +} + +;; Test 5: Variable at an offset within its alloca (structured binding). +;; +;; A single variable "p" of struct type {int, int} (64 bits total) is +;; described using two fragments: (0,32) for the first field and (32,32) +;; for the second. After an escaping call, both fragments should be +;; reinstated to memory locations with DW_OP_deref plus their fragment. +;; +;; NOTE: The variable must have the struct type (64 bits) so that the +;; 32-bit fragments are valid sub-ranges. Using int (32 bits) as the +;; type would make the fragment at offset 32 invalid. + +; CHECK-LABEL: name: test_offset_within_alloca +; CHECK: bb.0.entry: +; CHECK-DAG: DBG_VALUE %stack.0.p, $noreg, ![[P:[0-9]+]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 32) +; CHECK-DAG: DBG_VALUE %stack.0.p, $noreg, ![[P]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 32, 32) +;; The two i32 stores may be merged into a single i64 store by ISel: +; CHECK: {{MOV32mi|MOV64mr}} %stack.0.p +; CHECK-DAG: DBG_VALUE $noreg, $noreg, ![[P]], !DIExpression(DW_OP_LLVM_fragment, 0, 32) +; CHECK-DAG: DBG_VALUE $noreg, $noreg, ![[P]], !DIExpression(DW_OP_LLVM_fragment, 32, 32) +; CHECK: CALL64pcrel32 {{.*}}@clobber_pair +;; After the escaping call, the whole variable is reinstated to memory. +;; processEscapingCall uses the whole-variable (no fragment) so a single +;; DW_OP_deref covers both fields: +; CHECK: DBG_VALUE %stack.0.p, $noreg, ![[P]], !DIExpression(DW_OP_deref) +; CHECK: RET 0 + +define void @test_offset_within_alloca(i32 %val) !dbg !90 { +entry: + %p = alloca { i32, i32 }, align 4, !DIAssignID !100 + #dbg_assign(i1 poison, !91, !DIExpression(DW_OP_LLVM_fragment, 0, 32), !100, ptr %p, !DIExpression(), !93) + #dbg_assign(i1 poison, !91, !DIExpression(DW_OP_LLVM_fragment, 32, 32), !100, ptr %p, !DIExpression(), !93) + store i32 1, ptr %p, align 4, !DIAssignID !101 + #dbg_assign(i32 1, !91, !DIExpression(DW_OP_LLVM_fragment, 0, 32), !101, ptr %p, !DIExpression(), !93) + %p.b = getelementptr inbounds i8, ptr %p, i64 4 + store i32 2, ptr %p.b, align 4, !DIAssignID !102 + #dbg_assign(i32 2, !91, !DIExpression(DW_OP_LLVM_fragment, 32, 32), !102, ptr %p, !DIExpression(), !93) + #dbg_value(i32 %val, !91, !DIExpression(DW_OP_LLVM_fragment, 0, 32), !93) + #dbg_value(i32 %val, !91, !DIExpression(DW_OP_LLVM_fragment, 32, 32), !93) + call void @clobber_pair(ptr %p) + ret void, !dbg !94 +} + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} + +!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) +!1 = !DIFile(filename: "test.c", directory: "/tmp") +!2 = !{} +!3 = !{i32 7, !"Dwarf Version", i32 5} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 7, !"debug-info-assignment-tracking", i1 true} +!6 = !DISubroutineType(types: !2) +!14 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) + +; Function 1 metadata +!7 = distinct !DISubprogram(name: "test_basic_escaping_call", scope: !1, file: !1, line: 1, type: !6, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0) +!11 = !DILocalVariable(name: "x", scope: !7, file: !1, line: 2, type: !14) +!12 = !DILocation(line: 2, column: 1, scope: !7) +!13 = !DILocation(line: 5, column: 1, scope: !7) +!20 = distinct !DIAssignID() +!21 = distinct !DIAssignID() + +; Function 2 metadata +!30 = distinct !DISubprogram(name: "test_escaping_then_store", scope: !1, file: !1, line: 10, type: !6, scopeLine: 10, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0) +!31 = !DILocalVariable(name: "y", scope: !30, file: !1, line: 11, type: !14) +!32 = !DILocation(line: 11, column: 1, scope: !30) +!33 = !DILocation(line: 15, column: 1, scope: !30) +!40 = distinct !DIAssignID() +!41 = distinct !DIAssignID() +!42 = distinct !DIAssignID() + +; Function 3 metadata +!50 = distinct !DISubprogram(name: "test_readonly_not_escaping", scope: !1, file: !1, line: 20, type: !6, scopeLine: 20, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0) +!51 = !DILocalVariable(name: "z", scope: !50, file: !1, line: 21, type: !14) +!52 = !DILocation(line: 21, column: 1, scope: !50) +!53 = !DILocation(line: 25, column: 1, scope: !50) +!60 = distinct !DIAssignID() +!61 = distinct !DIAssignID() + +; Function 4 metadata +!70 = distinct !DISubprogram(name: "test_byval_not_escaping", scope: !1, file: !1, line: 30, type: !6, scopeLine: 30, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0) +!71 = !DILocalVariable(name: "w", scope: !70, file: !1, line: 31, type: !14) +!72 = !DILocation(line: 31, column: 1, scope: !70) +!73 = !DILocation(line: 35, column: 1, scope: !70) +!80 = distinct !DIAssignID() +!81 = distinct !DIAssignID() + +; Function 5 metadata +;; Variable "p" has struct type (64 bits) so fragments (0,32) and (32,32) are valid. +!85 = !DICompositeType(tag: DW_TAG_structure_type, name: "Pair", file: !1, line: 40, size: 64, elements: !86) +!86 = !{!87, !88} +!87 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !85, file: !1, line: 41, baseType: !14, size: 32) +!88 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !85, file: !1, line: 42, baseType: !14, size: 32, offset: 32) +!90 = distinct !DISubprogram(name: "test_offset_within_alloca", scope: !1, file: !1, line: 44, type: !6, scopeLine: 44, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0) +!91 = !DILocalVariable(name: "p", scope: !90, file: !1, line: 45, type: !85) +!93 = !DILocation(line: 45, column: 1, scope: !90) +!94 = !DILocation(line: 48, column: 1, scope: !90) +!100 = distinct !DIAssignID() +!101 = distinct !DIAssignID() +!102 = distinct !DIAssignID() \ No newline at end of file diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/loop-hoist.ll b/llvm/test/DebugInfo/assignment-tracking/X86/loop-hoist.ll index 559cdc59dffd..49f2f2a57f5d 100644 --- a/llvm/test/DebugInfo/assignment-tracking/X86/loop-hoist.ll +++ b/llvm/test/DebugInfo/assignment-tracking/X86/loop-hoist.ll @@ -28,6 +28,9 @@ ; CHECK: bb.1.do.body: ; CHECK: DBG_VALUE %stack.0.a.addr, $noreg, ![[A]], !DIExpression(DW_OP_deref) +;; After the escaping call to @_Z2esPi, the memory location is reinstated. +; CHECK: CALL64pcrel32 {{.*}}@_Z2esPi +; CHECK: DBG_VALUE %stack.0.a.addr, $noreg, ![[A]], !DIExpression(DW_OP_deref) target triple = "x86_64-unknown-linux-gnu" diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll b/llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll index 3964ee51382f..23bb78e6a779 100644 --- a/llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll +++ b/llvm/test/DebugInfo/assignment-tracking/X86/mem-loc-frag-fill.ll @@ -65,6 +65,8 @@ entry: ; CHECK-NEXT: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_deref, DW_OP_LLVM_fragment, 0, 80) tail call void @_Z4stepv(), !dbg !32 call void @_Z3escP4Nums(ptr noundef nonnull %nums), !dbg !33 +; CHECK: CALL64pcrel32 @_Z3escP4Nums +; CHECK: DBG_VALUE %stack.0.nums, $noreg, ![[nums]], !DIExpression(DW_OP_deref) ret i32 0, !dbg !35 } diff --git a/llvm/test/DebugInfo/assignment-tracking/X86/negative-offset.ll b/llvm/test/DebugInfo/assignment-tracking/X86/negative-offset.ll index df85e3e50f77..81a058291145 100644 --- a/llvm/test/DebugInfo/assignment-tracking/X86/negative-offset.ll +++ b/llvm/test/DebugInfo/assignment-tracking/X86/negative-offset.ll @@ -34,6 +34,9 @@ ; CHECK-NEXT: successors ; CHECK-NEXT: {{^ *$}} ; CHECK-NEXT: DBG_VALUE 0, $noreg, ![[#]], !DIExpression() +;; After the escaping call to @a, the memory location is reinstated. +; CHECK: CALL64pcrel32 {{.*}}@a +; CHECK: DBG_VALUE %stack.0.c, $noreg, ![[#]], !DIExpression(DW_OP_deref) target triple = "x86_64-unknown-linux-gnu"