Skip to content

Instantly share code, notes, and snippets.

@Keno
Last active January 6, 2016 22:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Keno/480b8057df1b7c63c321 to your computer and use it in GitHub Desktop.
Save Keno/480b8057df1b7c63c321 to your computer and use it in GitHub Desktop.
Multi Location Debug Info support for LLVM

What is it? / Why do we want this?

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.

Goals of this design

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.

The Proposal

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
@vivekvpandya
Copy link

Have you started implementing this on experimental base ?

@Keno
Copy link
Author

Keno commented Jan 6, 2016

Ah, sorry, there are no notifications on gist comments. I have not implemented this yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment