[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 <phyBrackets@users.noreply.github.com>
This commit is contained in:
Shivam Kunwar 2026-03-12 20:47:53 +05:30 committed by GitHub
parent 641ab6ef83
commit 839fd9177e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 415 additions and 14 deletions

View File

@ -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

View File

@ -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<std::pair<VariableID, at::AssignmentInfo>>>;
using UnknownStoreAssignmentMap =
DenseMap<const Instruction *, SmallVector<VariableID>>;
using EscapingCallVarsMap =
DenseMap<const Instruction *,
SmallVector<std::tuple<VariableID, Value *, DIExpression *>>>;
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<VarLocInsertPt, SmallVector<VarLocInfo>>;
@ -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<CallBase>(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<DILocation *>(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<Value *>(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<DebugAggregate> &VarsWithStackSlot,
AssignmentTrackingLowering::UntaggedStoreAssignmentMap &UntaggedStoreVars,
AssignmentTrackingLowering::UnknownStoreAssignmentMap &UnknownStoreVars,
AssignmentTrackingLowering::EscapingCallVarsMap &EscapingCallVars,
unsigned &TrackedVariablesVectorSize) {
DenseSet<DebugVariable> 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<CallBase>(&I);
if (!CB)
continue;
// Skip intrinsics. Their memory effects are modeled individually.
if (isa<IntrinsicInst>(CB))
continue;
// Skip calls that cannot write to memory at all.
if (CB->onlyReadsMemory())
continue;
SmallDenseSet<VariableID, 4> 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<AllocaInst>(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<Function *> RPOT(&Fn);

View File

@ -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()

View File

@ -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"

View File

@ -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()

View File

@ -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"

View File

@ -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
}

View File

@ -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"