Hmmm…
The primary requirement, to make this work without gobs of relocations, is to minimize references that could “move” if a function is deleted, and of course references directly to a function itself.
A reference could “move” in a case something like this:
function A
type T
function B(T)
References are offsets from the base of the unit, so if function A is removed, the offset of type T will change, and so the reference from function B would have to be updated. We can sidestep this if we guarantee that type T appears in the unit before any function that might be removed.
Offhand there are two places where a function reference happens: references from concrete inlined subprograms to the abstract function, and the call-site stuff. Hand-wave away the call-site stuff, and we’re left with the inlining stuff. In this case I’d think a reasonable plan would be to treat the abstract function instance like a type, and put it before any concrete functions.
Overall, then, we’d end up needing to split the DWARF into three parts.
First, you have the unit header, top-level DIE, and all your type information that isn’t selectively removed by the linker (or already emitted separately as type units). This part would also have the abstract instances of inlined functions. This part is always emitted. You need to arrange to have it end up being first in the post-linker output.
Second, you have your per-function constructs. These ought to be self-contained, except for references to types and abstract functions, which are all in the first part, so those references can remain constant offsets from the top of the compile unit. Because these need to be self-contained, any namespace wrappers would need to be repeated per function. And to get the dead-stripping done correctly, each DWARF fragment would be in the same COMDAT as the function’s .text section.
Third, you need the final closing NULL (terminating the list of children of the compile-unit DIE) which also has a label so the final size of the compile unit can be computed correctly (this size lives in the compile-unit header).
Currently in LLVM, DWARF gets emitted pretty much on-demand, meaning types and functions (concrete and abstract) can be intermixed willy-nilly. It’s likely to require a real lot of effort to rework that into the types-versus-functions organization.
This of course is talking only about the .debug_info section, and there are lots of other sections with per-function contributions. Those are trickier, but also tend to be much smaller, so it might be reasonable to just hand-wave those away as not worth the extra effort.
James might have other observations or recollections from doing the actual experiment.
–paulr