1 # Debug Info Assignment Tracking
3 Assignment Tracking is an alternative technique for tracking variable location
4 debug info through optimisations in LLVM. It provides accurate variable
5 locations for assignments where a local variable (or a field of one) is the
6 LHS. In rare and complicated circumstances indirect assignments might be
7 optimized away without being tracked, but otherwise we make our best effort to
8 track all variable locations.
10 The core idea is to track more information about source assignments in order
11 and preserve enough information to be able to defer decisions about whether to
12 use non-memory locations (register, constant) or memory locations until after
13 middle end optimisations have run. This is in opposition to using
14 `llvm.dbg.declare` and `llvm.dbg.value`, which is to make the decision for most
15 variables early on, which can result in suboptimal variable locations that may
16 be either incorrect or incomplete.
18 A secondary goal of assignment tracking is to cause minimal additional work for
19 LLVM pass writers, and minimal disruption to LLVM in general.
23 **Status**: Experimental work in progress. Enabling is strongly advised against
24 except for development and testing.
26 **Enable in Clang**: `-Xclang -fexperimental-assignment-tracking`
28 That causes Clang to get LLVM to run the pass `declare-to-assign`. The pass
29 converts conventional debug intrinsics to assignment tracking metadata and sets
30 the module flag `debug-info-assignment-tracking` to the value `i1 true`. To
31 check whether assignment tracking is enabled for a module call
32 `isAssignmentTrackingEnabled(const Module &M)` (from `llvm/IR/DebugInfo.h`).
34 ## Design and implementation
36 ### Assignment markers: `llvm.dbg.assign`
38 `llvm.dbg.value`, a conventional debug intrinsic, marks out a position in the
39 IR where a variable takes a particular value. Similarly, Assignment Tracking
40 marks out the position of assignments with a new intrinsic called
43 In order to know where in IR it is appropriate to use a memory location for a
44 variable, each assignment marker must in some way refer to the store, if any
45 (or multiple!), that performs the assignment. That way, the position of the
46 store and marker can be considered together when making that choice. Another
47 important benefit of referring to the store is that we can then build a two-way
48 mapping of stores<->markers that can be used to find markers that need to be
49 updated when stores are modified.
51 An `llvm.dbg.assign` marker that is not linked to any instruction signals that
52 the store that performed the assignment has been optimised out, and therefore
53 the memory location will not be valid for at least some part of the program.
55 Here's the `llvm.dbg.assign` signature. Each parameter is wrapped in
56 `MetadataAsValue`, and `Value *` type parameters are first wrapped in
60 void @llvm.dbg.assign(Value *Value,
61 DIExpression *ValueExpression,
62 DILocalVariable *Variable,
65 DIExpression *AddressExpression)
68 The first three parameters look and behave like an `llvm.dbg.value`. `ID` is a
69 reference to a store (see next section). `Address` is the destination address
70 of the store and it is modified by `AddressExpression`. An empty/undef/poison
71 address means the address component has been killed (the memory address is no
72 longer a valid location). LLVM currently encodes variable fragment information
73 in `DIExpression`s, so as an implementation quirk the `FragmentInfo` for
74 `Variable` is contained within `ValueExpression` only.
76 The formal LLVM-IR signature is:
78 void @llvm.dbg.assign(metadata, metadata, metadata, metadata, metadata, metadata)
81 ### Instruction link: `DIAssignID`
83 `DIAssignID` metadata is the mechanism that is currently used to encode the
84 store<->marker link. The metadata node has no operands and all instances are
85 `distinct`; equality is checked for by comparing addresses.
87 `llvm.dbg.assign` intrinsics use a `DIAssignID` metadata node instance as an
88 operand. This way it refers to any store-like instruction that has the same
89 `DIAssignID` attachment. E.g. For this test.cpp,
96 compiled without optimisations:
98 $ clang++ test.cpp -o test.ll -emit-llvm -S -g -O0 -Xclang -fexperimental-assignment-tracking
102 define dso_local noundef i32 @_Z3funi(i32 noundef %a) #0 !dbg !8 {
104 %a.addr = alloca i32, align 4, !DIAssignID !13
105 call void @llvm.dbg.assign(metadata i1 undef, metadata !14, metadata !DIExpression(), metadata !13, metadata i32* %a.addr, metadata !DIExpression()), !dbg !15
106 store i32 %a, i32* %a.addr, align 4, !DIAssignID !16
107 call void @llvm.dbg.assign(metadata i32 %a, metadata !14, metadata !DIExpression(), metadata !16, metadata i32* %a.addr, metadata !DIExpression()), !dbg !15
108 %0 = load i32, i32* %a.addr, align 4, !dbg !17
113 !13 = distinct !DIAssignID()
114 !14 = !DILocalVariable(name: "a", ...)
116 !16 = distinct !DIAssignID()
119 The first `llvm.dbg.assign` refers to the `alloca` through `!DIAssignID !13`,
120 and the second refers to the `store` through `!DIAssignID !16`.
122 ### Store-like instructions
124 In the absence of a linked `llvm.dbg.assign`, a store to an address that is
125 known to be the backing storage for a variable is considered to represent an
126 assignment to that variable.
128 This gives us a safe fall-back in cases where `llvm.dbg.assign` intrinsics have
129 been deleted, the `DIAssignID` attachment on the store has been dropped, or the
130 optimiser has made a once-indirect store (not tracked with Assignment Tracking)
133 ### Middle-end: Considerations for pass-writers
135 #### Non-debug instruction updates
137 **Cloning** an instruction: nothing new to do. Cloning automatically clones a
138 `DIAssignID` attachment. Multiple instructions may have the same `DIAssignID`
139 instruction. In this case, the assignment is considered to take place in
140 multiple positions in the program.
142 **Moving** a non-debug instruction: nothing new to do. Instructions linked to an
143 `llvm.dbg.assign` have their initial IR position marked by the position of the
146 **Deleting** a non-debug instruction: nothing new to do. Simple DSE does not
147 require any change; it’s safe to delete an instruction with a `DIAssignID`
148 attachment. An `llvm.dbg.assign` that uses a `DIAssignID` that is not attached
149 to any instruction indicates that the memory location isn’t valid.
151 **Merging** stores: In many cases no change is required as `DIAssignID`
152 attachments are automatically merged if `combineMetadata` is called. One way or
153 another, the `DIAssignID` attachments must be merged such that new store
154 becomes linked to all the `llvm.dbg.assign` intrinsics that the merged stores
155 were linked to. This can be achieved simply by calling a helper function
156 `Instruction::mergeDIAssignID`.
158 **Inlining** stores: As stores are inlined we generate `llvm.dbg.assign`
159 intrinsics and `DIAssignID` attachments as if the stores represent source
160 assignments, just like the in frontend. This isn’t perfect, as stores may have
161 been moved, modified or deleted before inlining, but it does at least keep the
162 information about the variable correct within the non-inlined scope.
164 **Splitting** stores: SROA and passes that split stores treat `llvm.dbg.assign`
165 intrinsics similarly to `llvm.dbg.declare` intrinsics. Clone the
166 `llvm.dbg.assign` intrinsics linked to the store, update the FragmentInfo in
167 the `ValueExpression`, and give the split stores (and cloned intrinsics) new
168 `DIAssignID` attachments each. In other words, treat the split stores as
169 separate assignments. For partial DSE (e.g. shortening a memset), we do the
170 same except that `llvm.dbg.assign` for the dead fragment gets an `Undef`
173 **Promoting** allocas and store/loads: `llvm.dbg.assign` intrinsics implicitly
174 describe joined values in memory locations at CFG joins, but this is not
175 necessarily the case after promoting (or partially promoting) the
176 variable. Passes that promote variables are responsible for inserting
177 `llvm.dbg.assign` intrinsics after the resultant PHIs generated during
178 promotion. `mem2reg` already has to do this (with `llvm.dbg.value`) for
179 `llvm.dbg.declare`s. Where a store has no linked intrinsic, the store is
180 assumed to represent an assignment for variables stored at the destination
183 #### Debug intrinsic updates
185 **Moving** a debug intrinsic: avoid moving `llvm.dbg.assign` intrinsics where
186 possible, as they represent a source-level assignment, whose position in the
187 program should not be affected by optimization passes.
189 **Deleting** a debug intrinsic: Nothing new to do. Just like for conventional
190 debug intrinsics, unless it is unreachable, it’s almost always incorrect to
191 delete a `llvm.dbg.assign` intrinsic.
193 ### Lowering `llvm.dbg.assign` to MIR
195 To begin with only SelectionDAG ISel will be supported. `llvm.dbg.assign`
196 intrinsics are lowered to MIR `DBG_INSTR_REF` instructions. Before this happens
197 we need to decide where it is appropriate to use memory locations and where we
198 must use a non-memory location (or no location) for each variable. In order to
199 make those decisions we run a standard fixed-point dataflow analysis that makes
200 the choice at each instruction, iteratively joining the results for each block.
204 As this is an experimental work in progress so there are some items we still need
207 * As mentioned in test llvm/test/DebugInfo/assignment-tracking/X86/diamond-3.ll,
208 the analysis should treat escaping calls like untagged stores.
210 * The system expects locals to be backed by a local alloca. This isn't always
211 the case - sometimes a pointer to storage is passed into a function
212 (e.g. sret, byval). We need to be able to handle those cases. See
213 llvm/test/DebugInfo/Generic/assignment-tracking/track-assignments.ll and
214 clang/test/CodeGen/assignment-tracking/assignment-tracking.cpp for examples.
216 * `trackAssignments` doesn't yet work for variables that have their
217 `llvm.dbg.declare` location modified by a `DIExpression`, e.g. when the
218 address of the variable is itself stored in an `alloca` with the
219 `llvm.dbg.declare` using `DIExpression(DW_OP_deref)`. See `indirectReturn` in
220 llvm/test/DebugInfo/Generic/assignment-tracking/track-assignments.ll and in
221 clang/test/CodeGen/assignment-tracking/assignment-tracking.cpp for an
224 * In order to solve the first bullet-point we need to be able to specify that a
225 memory location is available without using a `DIAssignID`. This is because
226 the storage address is not computed by an instruction (it's an argument
227 value) and therefore we have nowhere to put the metadata attachment. To solve
228 this we probably need another marker intrinsic to denote "the variable's
229 stack home is X address" - similar to `llvm.dbg.declare` except that it needs
230 to compose with `llvm.dbg.assign` intrinsics such that the stack home address
231 is only selected as a location for the variable when the `llvm.dbg.assign`
232 intrinsics agree it should be.
234 * Given the above (a special "the stack home is X" intrinsic), and the fact
235 that we can only track assignments with fixed offsets and sizes, I think we
236 can probably get rid of the address and address-expression part, since it
237 will always be computable with the info we have.