From 839fd9177e99cd2e1eafe7109cc96efa860e2537 Mon Sep 17 00:00:00 2001 From: Shivam Kunwar <75530356+phyBrackets@users.noreply.github.com> Date: Thu, 12 Mar 2026 20:47:53 +0530 Subject: [PATCH] [DebugInfo][AT] Treat escaping calls as untagged stores in assignment tracking (#183979) When a pointer to a tracked alloca is passed to a call that may write through it (e.g. foo(&x)), the callee can modify the variable's stack home. The assignment tracking analysis didn't account for this, which could cause the debugger to show stale values after such calls. Consider: ``` int x = 1; foo(&x); // might set x to 99 x = 2; // store deleted by DSE ``` Without this patch, the analysis still thinks the stack home holds assignment `!id1` after the call. When it later sees the `dbg_assign` for the deleted store, the mismatch causes it to fall back to the old debug value (1) , which is wrong. Fix this by detecting calls where a tracked `alloca` escapes as an argument and treating them the same way we already treat untagged stores, set both `StackHome` and `Debug` to NoneOrPhi (unknown assignment) and keep `LocKind` as Mem (the stack slot is still the right place to look). This causes a `DBG_VALUE` with `DW_OP_deref` to be emitted after the call, telling the debugger to read whatever is actually in memory. Pointer arguments with readonly, readnone, or byval attributes are skipped since the callee either cannot modify the original memory or receives a copy. Intrinsics are also skipped since their memory effects are already modeled individually (e.g. memset/memcpy as stores, lifetime markers as no-ops). --------- Co-authored-by: Shivam Kunwar --- llvm/docs/AssignmentTracking.md | 3 - .../CodeGen/AssignmentTrackingAnalysis.cpp | 126 +++++++++- .../AArch64/escaping-call-tag-offset.ll | 51 ++++ .../assignment-tracking/X86/diamond-3.ll | 17 +- .../assignment-tracking/X86/escaping-call.ll | 224 ++++++++++++++++++ .../assignment-tracking/X86/loop-hoist.ll | 3 + .../X86/mem-loc-frag-fill.ll | 2 + .../X86/negative-offset.ll | 3 + 8 files changed, 415 insertions(+), 14 deletions(-) create mode 100644 llvm/test/DebugInfo/assignment-tracking/AArch64/escaping-call-tag-offset.ll create mode 100644 llvm/test/DebugInfo/assignment-tracking/X86/escaping-call.ll 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"