On 12/07/2024 19:04, David Blaikie wrote:
Thanks for all the context (I noticed you replied directly to me - are you happy/OK having this discussion on the mailing list, rather than in private? It'd help to keep all the history visible, linkable, etc)
Yes, apologies, that was just a mistake on my part -- I meant to do this, then realised I accidentally replied to you directly, so went to forward it to the list, and only now realised that I accidentally forwarded it to a completely unrelated list :P
While I can appreciate the desire to make this update O(N) in the number of source lines affected - would it be acceptable for this to be O(N) in the number of machine/object level functions?

Like if we had a feature for resetting the line table program part-way through a line table - would that be adequate? Then your external data could keep track of the line number setting operation at the start of the new/distinct/indepednent sequence and update that?
While not ideal for us, this would certainly be an improvement. Dealing with the relative line number offsets is definitely the biggest pain point wrt constructing the LNP today.
Such a feature would have some more generality/usefulness directly without external/side data - for instance it would make chunks of the line table discardable, which could make it easier for a producer to use comdats to isolate the line table data associated with an inline function and allow the linker to discard such a contribution if desired.

This is a good use case, but I would point out that it would (if I am understanding you correctly) also be solved by my original proposal. To be clear, by "more generality/usefulness", do you mean compared to my proposal, or compared to status quo? With that being said, I can understand the aversion to, as you put it, storing external/side data. Just for the record, I don't think the original issue is unique to the Zig project; any sufficiently incremental compiler (which admittedly is a rarity today, but could perhaps see more adoption as a concept if Zig is able to prove its viability for native builds in a systems language) should benefit from the proposal if the language in question contains some kind of generic/template system (which, of course, most source languages today do). However, I am happy to concede this point and discuss a more tame proposal if you view the external data as too significant of a change for a perhaps seldom-used feature.

On Tue, Jul 9, 2024 at 2:27 AM Matthew Lugg <ml...@mlugg.co.uk> wrote:

    On 08/07/2024 22:18, David Blaikie via Dwarf-discuss wrote:
    > Hi Matthew,
    >
    > I'm looking into your issue & I have some broad questions:
    >
    > This seems like it'd speed up generics-heavy code, to be sure -
    though
    > I assume you hit a perf issue in some prototype tool already, that
    > lead you to this issue? Do you have further details on the tooling
    > you've prototyped/the problems you've encountered? Or is this
    proposal
    > more based on an assumption/predicted issue?

    Thanks very much for the quick response!

    We haven't hit a performance issue thus far, because the current
    implementation of incremental compilation in the Zig compiler is
    incomplete. However, I'm happy to give some further context as to the
    usage of this proposal in the Zig compiler.

    Zig allows a function to be generic by taking certain arguments at
    compile-time, and each separate set of arguments creates a different
    instantiation of the function. Such functions can also create types
    on-the-fly, which can themselves contain more functions, which might
    reference the compile-time known arguments from above, so we get
    distinct instances of the type. This essentially leads to our
    implementation of templates, which use the fact that types are
    first-class values at compile time in Zig. Sorry if that was a little
    badly worded -- it's hard to explain the design of Zig's templates
    without getting into more concepts about the language! That said,
    it's
    not important to really understand this mechanism; what matters is
    that
    it essentially gives us templates a la C++.

    As far as I'm aware, our current incremental linker implementation
    doesn't really handle updating the DWARF LNP at the minute (we've
    mainly
    been focused on trying to make the actual binaries work first).
    The Zig
    project has recently been making heavy progress towards incremental
    compilation, and analysis of the problem space here is what led to
    this
    proposal. Under status quo, an incremental update would require the
    linker to potentially update a lot more line numbers than have
    logically
    changed. Consider making a trivial change to the `ArrayList` code
    in the
    standard library, say moving the `ArrayList.init` function down by a
    single line. In that case, we would need to update the line number
    corresponding to every distinct instantiation of the `ArrayList`
    type,
    which in a complex application, could potentially be in the low
    hundreds. There are also more pathological cases: for instance, if
    the
    standard library code for formatted printing has its line number
    changed, then potentially thousands of LNP entries would have to be
    changed, since every distinct format string creates a separate
    function
    instantiation in our standard library. This is also made more
    complex by
    the fact that line number updates are currently all relative, which I
    will discuss below...

    > Also, while I imagine this feature could simplify the situation
    even
    > when there aren't shared lines/generics (by centralizing the
    place to
    > update) -- I wouldn't've thought a naive... oh, I see, there's no
    > "reset the line number".
    >
    > So even between sequences there's no resetting of the line
    number, so
    > you can't just look for the line at the start of some functions,
    etc.
    Indeed -- our biggest problem at the moment is that all line number
    updates are relative, which means that if, say, one function is
    moved in
    the file, we actually need to perform some arithmetic on the line
    number
    of the preceding function in the LNP so as to compute the new offset.
    So, the linker would need to preserve state about what the `line`
    register is expected to contain going into the LNP fragment for each
    function. This is doable, it just seems like unnecessary extra work,
    since I don't see any reason relative line number offsets would be
    preferable for debuggers and other tooling, so don't immediately
    see an
    issue with having a way to provide an absolute line number offset (or
    just an opcode to reset it to 0). If this specific proposal is
    ultimately rejected, something like that may still be worth
    considering.
    > Equally, though, putting some otherwise "random" line numbers in a
    > list in the header of the line table doesn't seem like a generally
    > usable feature - It'd be unclear how the DWARF modifying-tool would
    > know which line numbers to update (couldn't rely on unique
    numbers -
    > they might collide between different files, and some files might
    need
    > updating and some not, etc)

    In the context of the Zig compiler, we aren't updating the DWARF
    information in a vacuum -- we are able to store our own state about
    which Zig source constructs correspond to which pieces of the binary
    file and DWARF information. (The compiler has mechanisms to
    efficiently
    [de]serialize this state to a separate file across runs of the
    compiler.) The goal of this proposal is essentially to decrease the
    amount of state we need to store. Under this proposal, the Zig
    compiler
    would be able to store a mapping from a specific instruction in an IR
    (the instructions corresponding to source-level declarations, in
    particular function declarations), to the byte offset of the
    corresponding entry in `indirect_lines`. So, when the declaration
    corresponding to an IR instruction is relocated -- which our existing
    infrastructure for incremental compilation allows us to cheaply
    detect
    -- our incremental linker can simply look up that IR instruction
    in the
    mapping, giving it a byte offset into `indirect_lines`, and update
    the
    entry at that offset. Since `indirect_lines` is able to be densely
    packed, the linker can ensure that there will always be 4 or 5 bytes
    available for the ULEB128 value, so that it never has to move around
    other line numbers. Ultimately, this means that the full binary
    update
    overhead of e.g. moving one function in source code down one line
    would
    be this one hashmap lookup and a 4- or 5-byte write to the output
    file.
    Today, however, we would have to maintain additional state about the
    expected value of `line` before each new LNP fragment, keeping this
    up-to-date, and we would have to update potentially arbitrarily many
    line numbers in the file, one for each relevant generic
    instantiation.
    We would also have to update the LNP fragment *following* each
    changed
    region, to correct the offset to that fragment, since the preceding
    value of `line` is now different.

    > @Adrian Prantl <mailto:apra...@apple.com> and @Jonas Devlieghere
    > <mailto:jdevliegh...@apple.com> with the CAS DWARF stuff, how
    are you
    > folks separating line table fragments for functions? My
    understanding
    > was the line table header was too large to have one on every
    > function's line table fragment, so I would've thought you'd figured
    > out a way to make line table fragments that could be added/removed
    > from a line table without disturbing the other flags/state and
    without
    > relying on specific values in/out of the line register?
    >
    > On Wed, Jun 26, 2024 at 4:06 PM Matthew Lugg via Dwarf-discuss
    > <dwarf-discuss@lists.dwarfstd.org> wrote:
    >
    >     # Add DW_LNS_indirect_line - update `line` to absolute value
    stored
    >     indirectly
    >
    >     ## Background
    >
    >     In many source languages, it is possible for many
    program-counter
    >     addresses with arbitrary
    >     separation to correspond to the same source line due to
    features like
    >     templates/generics. When
    >     designing an incremental compiler, the line number program
    must be
    >     updated when line numbers within
    >     a source file are moved. It would be desirable to have the
    >     property that
    >     when moving a source line
    >     corresponding to a large amount of distinct program-counter
    >     addresses,
    >     only one line number value in
    >     the DWARF information needs to be updated. For this to be
    true, the
    >     regions of the line number
    >     program corresponding to each such address must include the line
    >     number
    >     of the source construct not
    >     directly, but through an indirect reference. This allows one
    line
    >     number
    >     value stored in the binary
    >     to be shared across arbitrarily many entries in the line number
    >     matrix.
    >
    >     This is not currently possible: all modifications to the `line`
    >     register
    >     are given by relative
    >     offsets, and all of these offsets are directly included in the
    >     instruction (or implicit in the case
    >     of a special opcode).
    >
    >     ## Overview
    >
    >     Introduce new fields to the line number program header,
    >     `indirect_lines_length` (ULEB128) and
    >     `indirect_lines` (opaque block of bytes containing ULEB128
    >     values). The
    >     `indirect_lines_length`
    >     field is the length in bytes of the `indirect_lines`
    section, rather
    >     than the number of elements.
    >     Introduce a new standard opcode to the line number program,
    >     `DW_LNS_indirect_line`. This opcode
    >     takes a single ULEB128 operand, which represents a byte offset
    >     into the
    >     `indirect_lines` stored in
    >     the header. The effect of this instruction is to set the `line`
    >     register
    >     to the ULEB128 value stored
    >     at the given byte offset into `indirect_lines`. Note that
    >     `indirect_lines` is not itself validated
    >     to be a valid sequence of ULEB128 values; decoding only
    occurs when
    >     `DW_LNS_indirect_line` is used.
    >     This allows an incremental compiler to pre-allocate a large
    amount of
    >     padding space in
    >     `indirect_lines` to fill in later as needed.
    >
    >     Note that an incremental compiler would not necessarily wish
    to use
    >     variable-length integers to
    >     represent this information, since certain changes of line
    numbers
    >     could
    >     cause a line number which
    >     was previously encoded using 1 byte to now require 2. However,
    >     since the
    >     stored values need not be
    >     densely packed, an implementation is free to reserve as much
    space
    >     as is
    >     necessary for each entry.
    >     For instance, the downstream Zig compiler (which is the original
    >     motivator for this proposal) may
    >     choose to reserve 4 or 5 bytes for each line number, as line
    >     numbers in
    >     Zig source files cannot
    >     exceed 1<<32. The use of ULEB128 allows the compiler to make an
    >     appropriate decision here instead of
    >     codifying such a restriction into the DWARF specification.
    >
    >     ## Proposed Changes
    >
    >     Pages and line numbers are given for the 2024-06-16 working
    draft of
    >     DWARF Version 6, which is the
    >     latest draft at the time of writing.
    >
    >     6.2.4 (pg 163; line 27)
    >
    >     21. indirect_lines_length (ULEB128)
    >          The length in bytes of the data stored in the
    >     `indirect_lines` field.
    >     22. indirect_lines (block containing ULEB128 entries)
    >          A collection of line numbers, each stored as a ULEB128
    integer.
    >     These values are referenced by
    >          DW_LNS_indirect_line instructions to modify the state
    of the
    >     line
    >     number information state
    >          machine.
    >
    >          The data stored in this field is not checked to be a valid
    >     sequence
    >     of ULEB128 entries. The
    >          contained data may include padding bytes or otherwise
    invalid
    >     data.
    >     As such, it is expected that
    >          bytes of this field be accessed only when a
    DW_LNS_indirect_line
    >     instruction references them.
    >
    >     6.2.5.2 (pg 170; line 23)
    >
    >     14. DW_LNS_indirect_line
    >          The DW_LNS_indirect_line opcode takes a single unsigned
    LEB128
    >     operand. This operand is
    >          interpreted as a byte offset into the `indirect_lines`
    field
    >     of the
    >     line number program header.
    >          An unsigned LEB128 value is read from `indirect_lines`
    at the
    >     given
    >     offset, and this value is
    >          stored into the state machine's `line` register.
    >
    >     7.22 (pg 246; table 7.25)
    >
    >       Opcode name          | Value
    >     ----------------------+-------
    >             ...            |  ...
    >     DW_LNS_indirect_line  | 0x0d
    >
    >     --
    >     Dwarf-discuss mailing list
    > Dwarf-discuss@lists.dwarfstd.org
    > https://lists.dwarfstd.org/mailman/listinfo/dwarf-discuss
    >
    >

--
Dwarf-discuss mailing list
Dwarf-discuss@lists.dwarfstd.org
https://lists.dwarfstd.org/mailman/listinfo/dwarf-discuss

Reply via email to