tl;dr following on from our (Sony’s) prior proposal to use function-local storage for source location metadata, we propose a fully-defined storage model, and a new textual IR representation for the new format:
; Old
%c = add i32 %a, %b, !dbg !10
!10 = !DILocation(line: 10, column: 4, scope: !9, inlinedAt: !11)
; New
%c = add i32 %a, %b, !!dbgLoc(line: 10, column: 4, scope: !9, inlinedAt: 2)
Background
The motivation for changing the underlying representation of source locations (i.e. DILocation metadata) is given in the prior RFC, but to briefly summarize: there are significant inefficiencies in the current metadata model for source locations, and we could see great savings in terms of overall memory usage and number of allocations by using a function-local format for source location information. Changing the in-memory IR format strongly pushes us to change the textual IR format accordingly: converting between these formats is not free, it would add significant overhead to printing/parsing, and would also not be a faithful representation of the in-memory IR.
Design
The design proposed in this RFC extends and modifies the prior proposal, although the basic concept is the same. The storage model is more fully fleshed out as “function-local metadata” (FLMD), and is used to store source locations and loop metadata (!llvm.loop). In exchange for more indirection, this format compresses source location information further than the initial design by deduplicating source locations across inlined function instances, which greatly reduces the cost of inlining - often a significant contributor to expensive source location information in optimized debug builds.
While this project will change the debug location API and the textual IR output, it should have no effect on final compiler output. We are storing the same information in a different shape, which reduces overhead and enables some deduplication. All existing API methods will either continue to work as-is or have trivial replacements (see below). Further explanation is given in the design document found here.
API changes
This change unavoidably changes the API for dealing with source locations, as storage is no longer based on Metadata, and references are now integers rather than pointers, which means any code that previously created debug line information is affected.
- Existing code will work without modification, although it will be relatively inefficient (as we end up paying the cost of both representations), and existing methods that convert between
DILocationandDebugLocwill be deprecated. DILocationitself will be converted into a “wrapper” class that references function-local metadata, andDILocation::getwill write to FLMD storage.- Loop metadata will similarly be made into a function-local metadata type, and any code that accesses it as an
MDNodewill be deprecated. - In almost all cases, every instance of
DILocationcan be trivially replaced withDebugLoc.
Updating downstream/consumer code (e.g. frontends, or forks of LLVM) should be quite straightforward; as mentioned above, existing code will continue to work (but will be marked deprecated), and the vast majority of updates are a simple replacement of all DILocation* variables with DebugLoc variables, and all static DILocation method calls with DebugLoc method calls. Practically, >90% of updates can be performed with text substitution, and the remainder should still be trivial (e.g. replacing a deprecated method with a substitute). New API additions (e.g. builder methods) will also be added, which will allow for more efficient use of FLMD.
IR changes
The textual IR must also change; the key points of the change to the textual IR are:
- A new syntax is provided for function-local metadata (FLMD):
!!<type>, !dbg !<num>attachments are replaced with (mostly) in-line printed location information, e.g.!!dbgLoc(line: 10, column: 4, scope: !9, inlinedAt: 2),!llvm.loop !<num>attachments are replaced with a FLMD reference, e.g.!!loop 4,- A new metadata type is created to store FLMD, aptly named
!DIFunctionLocalMetadata; this type is always distinct, and each instance is associated with exactly one concreteFunction, inlinedAtandloopvalues are integer indices, referring to array entries in the current function’s!DIFunctionLocalMetadata.- A flag,
--use-deprecated-function-local-metadata, causesDILocationandllvm.loopmetadata to be printed out in their current form, to smooth over the transition for tooling.
Example
Presented below is a short example showing how each old IR element translates to the newly proposed IR. One cost is that the in-line representation adds visual clutter - the !!dbgLoc attachment often ranges from 1-2x the original length of the lines where it is present, which makes it harder to read the original program. On the other hand, printing the debug location information in-line makes it much easier to cross reference the IR with the original source code. The flag --output-compact-debug-locations is available, which removes some of the annotative comments and uses indirection to reduce the printed size of dbgLocs, at the cost of making source locations harder to read, especially when inlined. To give a better view of this change in practice, some more extensive examples are provided here.
Current LLVM IR
; Simple DILocation
%exitcond.not = icmp eq i32 %inc, %i, !dbg !24
; Inlined DILocation
%mul.i = mul nsw i32 %call.i, %x.addr.013, !dbg !32
; DILocation + Loop attachment
br i1 %exitcond.not, label %cleanup2, label %for.body, !dbg !25, !llvm.loop !26
!20 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 9, type: !13, scopeLine: 9, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, keyInstructions: true)
!24 = !DILocation(line: 10, column: 21, scope: !20, atomGroup: 2, atomRank: 1)
!25 = !DILocation(line: 10, column: 3, scope: !20, atomGroup: 3, atomRank: 1)
!26 = distinct !{!26, !27, !28, !29}
!27 = !DILocation(line: 10, column: 3, scope: !20)
!28 = !DILocation(line: 16, column: 3, scope: !20)
!29 = !{!"llvm.loop.mustprogress"}
!31 = distinct !DILocation(line: 11, column: 16, scope: !20)
!32 = !DILocation(line: 6, column: 12, scope: !12, inlinedAt: !31)
New LLVM IR (Function-Local Metadata)
; DILocation information is printed in-line.
; To make the repesentation a bit less cluttered, the atom-related fields are compressed into one.
%exitcond.not = icmp eq i32 %inc, %i, !!dbgLoc(line: 10, column: 21, scope: !20, atom: [2, 1])
; DILocation information is printed in-line, with an "inlinedAt" field that references the function-local metadata object.
; Line ends with a comment that can be grepped to find the function-local metadata entry corresponding to the inlinedAt location.
%mul.i = mul nsw i32 %call.i, %x.addr.013, !!dbgLoc(line: 6, column: 12, scope: !12, inlinedAt: 0) ; inlinedAt: foo@inlinedCallLoc@0
; Loop metadata is not printed in-line, instead printed as an index which references the function-local metadata object.
; Again, the line ends with a greppable comment to help lookup the function-local metadata entry.
br i1 %exitcond.not, label %cleanup2, label %for.body, !!dbgLoc(line: 10, column: 3, scope: !20, atom: [3, 1]), !!loop 0 ; loop: foo@loops@0
; New "functionLocalMetadata" field on !DISubprogram.
!20 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 9, type: !13, scopeLine: 9, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, keyInstructions: true, functionLocalMetadata: !38)
!29 = !{!"llvm.loop.mustprogress"}
; Each DIFunctionLocalMetadata is distinct and may contain arrays of `inlinedCallLocs` and `loops`.
; Each entry in these arrays is printed on a separate line, and ends with a line comment that matches the line comments for "users" of those entries.
!38 = distinct !DIFunctionLocalMetadata(
; Each inlinedCallLoc has a dbgLoc and a reference to the inlined function's DIFunctionLocalMetadata.
; An inlinedCallLoc may itself be inlined; if so, the inlinedAt index always refers to an index in *this* DIFunctionLocalMetadata.
inlinedCallLocs: [
!!inlinedCallLoc(dbgLoc: !!dbgLoc(line: 11, column: 16, scope: !20), inlinedFLMD: !37) ; foo@inlinedCallLoc@0
],
loops: [
!!loop(start: !!dbgLoc(line: 10, column: 3, scope: !20), end: !!dbgLoc(line: 16, column: 3, scope: !20), properties: !29) ; foo@loops@0
]
)