At any given time, the value of a source variable may be available in more than one place. The most common case is that the variable is available in memory as well as loaded in a register, but esp. in higher level languages where the notion of a variable is more disconnected from the physical realities, there can also be situations where you can find the same value in multiple places in memory, or perhaps more commonly, their being multiple ways to get to the same value in memory (e.g. through the GC frame and the argument register).
One can represent this in DWARF (i.e. ranges can overlap), but perhaps more importantly one can avoid having to make a choice about which value to track in mid level optimizations. The answer will generally depend on which value will live longer, but at that stage we do not know that information yet. As a concrete example, InstCombine will currently replace llvm.dbg.declare but llvm.dbg.values on every load and store, expecting the alloca to get removed, but if that assumption is wrong, we get worse debug info than we would have without replacing the declare. With multiple location support, both locations can be described and either both emitted to DWARF, or we can chose the one that is live longer and emit that.
As such, there is two separate but related goals:
1. Allow frontends to describe complex situations where variables may be
available in more than one location.
2. Provide a coherent framework for describing the locations of source
variables in the optimization pipeline to improve debug info quality.
This proposal concerns the IR format for encoding this information. Separately, getting this info into DWARF will require additional work, some of which has already been done in D11933 and D11986. The backend work is outside the scope of this proposal.
I tried to come up with a scheme that is a minimal modification of the existing mechanism to ease upgrading for both frontends and optimizers, but still separates the three concerns I think are required for multiple locations support
- Indicating that a value changed at source level (e.g. because an
assignment occured)
- Indicating that the same value is now available in a new location
- Indicating that a value is no longer available in some location
The last one is required in order to be able describe e.g. stack slot coloring, where a memory location may cease to describe a variable even though the value remained the same at source level.
I propose splitting llvm.dbg.value intrinsic from (note I'm ignoring the i64 offset argument which is already essentially dead and I expect it to be removed soon)
void @llvm.dbg.value(metadata, metadata, metadata)
into three separate intrinsics
token @llvm.dbg.value.new(metadata, metadata, metadata)
token @llvm.dbg.value.add(token, metadata, metadata)
token @llvm.dbg.value.delete(token)
with the semantics being the following:
- A change of value of a variable is indicated by (pseudeo-IR)
%first = call token @llvm.dbg.value.new(metadata %val,
metadata !var, metadata !expr)
for the purpose of this proposal, I'll denote such a call a `key call`.
- To add a location with the same value for the same variable, you pass the
token returned by a key call, as this llvm.dbg.value.add's first argument
E.g. to add another location for the variable above:
%second = call token @llvm.dbg.value.add(token %first, metadata %val2,
metadata !expr2)
- To indicate that a location will no longer hold a value, you do the
following:
call token @llvm.dbg.value.delete(token %second)
- If the !expr passed to a key call, contains a DW_OP_bit_piece, the key call
and all alternative locations added to it may only describe that chunk of
the value indicated by the DW_OP_bit_piece passed to the key function.
- The current set of (piece of a) locations for a (piece of a) variable at
a given instruction are all those llvm.dbg.value instructions that dominate
this location (equivalently all those llvm.dbg.value.(*) calls whose token
you could use at that location without upsetting the Verifier) and have not
been removed using llvm.dbg.value.delete. If more than one key call is dominating,
only the most recent one and all calls associated to it by first argument count.
I think that should encapsulate the semantics, but here are some consequences of and comments on the above that I think would be useful to discuss:
- The upgrade path for existing IR is very simple and just renames llvm.dbg.value
to llvm.dbg.value.new
- In general, if a value gets removed by an optimization, a corresponding
llvm.dbg.value.add call can be removed, but a llvm.dbg.value.new call needs
to have the value should be undefed out. This is necessary both to be
able to keep it around as the first argument to the other calls, and more
importantly to mark the end point of a previous set of locations.
- It should be noted that for optimized (pseudo-C) source like:
if (foo) {
x = a;
} else {
x = b;
}
the IR would have to look like:
if.true:
%xtrue = ... (a)
call token llvm.dbg.value.new(%xtrue, !var, !())
br cont
if.false:
%xfalse = ... (b)
call token llvm.dbg.value.new(%xfalse, !var, !())
br cont
cont:
%x = phi [%xtrue, %if.true], [%xfalse, %if.false]
call token llvm.dbg.value.new(%x, !var, !())
as the live range of the debug value would end at the end of the
respective basic block.
- A related concern is what the following:
call token llvm.dbg.value.new(%xold, !var, !())
if.true:
%xtrue = ... (a)
call token llvm.dbg.value.new(%xtrue, !var, !())
br cont
if.false:
%xfalse = ... (b)
call token llvm.dbg.value.new(%xfalse, !var, !())
br cont
cont:
%x = phi [%xtrue, %if.true], [%xfalse, %if.false]
(i.e. the above but with a forgotten llvm.dbg.value in the cont block).
By the semantics I have written above, `cont` would again have %xold as
the value for %x, even though there was an intermediate assignment. I am
not sure if this represents a problem, but it might at the very least be
unexpected.
- Do we run into problems in whatever MSVC's equivalent for debug info is.
- I think llvm.dbg.declare can be deprecated and it's uses replaced by
llvm.dbg.value with an DW_OP_deref. That would also clarify the sematics
of the operation which have caused some confusion in the past.
- We may want to add an extra pass that does debug info inference (some of
which is done in InstCombine right now)
Here are some of the invariants, the verifier would enforce (included in the hope that they can clarify anything in the above):
1. For llvm.dbg.value.add
a. the first argument must be a call to llvm.dbg.value.new
b. The piece of the variable described by the passed DIExpression
must be a subset of the piece described by the corresponding keycall.
c. There may not be another call to llvm.dbg.value.new
that dominates this instruction, is not the one passed as the first
argument and is dominated by the one passed as the first argument.
3. For llvm.dbg.value.new
a. The piece of the variable described by the passed DW_OP_bit_piece must
entirely cover all pieces of the same variable described by earlier key
calls, that this key call's piece overlaps with and that are still live
(key calls for which no remaining undeleted location exists do not count
as live and neither do those where all described locations are `undef`).
I.e. for a 64bit variable, it is not permissible to first have a key call
that describes the first 32bit and then another that describes bits 16-32,
unless the original location was first deleted or a new key call covering
the entire range with `undef` value happened in between.
2. All other invariants regarding calls to llvm.dbg.value carry over
unchanged to both llvm.dbg.value.new and llvm.dbg.value.add
Ah, sorry, there are no notifications on gist comments. I have not implemented this yet.