LTO with linker scripts, as discussed previously ([llvm-dev] [RFC] (Thin)LTO with Linker Scripts etc.), is a set of LTO features designed to make linker script behavior with LTO enabled align more closely to the behavior without LTO enabled. Among other things, it:
- Allows naming specific filenames in the linker script, even if those files are bitcode.
- Disables inlining, and similar transforms, across linker output sections.
- Makes KEEP work correctly with symbols defined in bitcode.
This is broadly along the same lines as previous proposals, although a few of the details are slightly different.
Along with the RFC, I’m posting a draft patch which implements this proposal in LLVM and LLD: [WIP] LTO with linker scripts, end-to-end. by efriedma-quic · Pull Request #204282 · llvm/llvm-project · GitHub. This is based heavily on the approach Qualcomm has used for a long time internally. The approach is adapted a bit for upstream, and the lld implementation is new.
The initial implementation is guarded under flags: a flag -flto-linker-scripts for clang, and --lto-linker-scripts for lld. This ensures that code which expects existing LTO semantics is not affected by these changes.
An architectural outline follows.
Annotating bitcode files before LTO
Making these features work requires the linker to know section names before LTO runs. To accomplish this, the existing “section” field on LLVM IR symbols, and in the symbol table, is reused. The user passes a flag -flto-linker-scripts to the compiler to tell it to add these annotations. A pass runs before bitcode emission to add these annotations, querying the backend for the appropriate mapping for symbols.
(Alternatively, we could delay this computation to the LTO step, but that’s awkward to layer correctly because we only have the symbol table loaded at the point where we first need the section names.)
Running linker rules before LTO
To allow the mapping to work correctly, the linker needs to use linker script rules: it needs to compute the compute the correct output section for each symbol, and it needs to compute whether each symbol is subject to a KEEP rule. This uses the section information from the symbol table. This information is then passed to the LTO API for each symbol, using the SymbolResolution struct.
There are a couple other minor changes to how symbol processing works before LTO: the linker now process symbols which are not externally visible (“internal” or “private”), to compute additional information, and computes whether each symbol is the target of a KEEP directive.
Changes to IR passes
During LTO, there’s one significant change to IR optimizations: a new “output_section” metadata on global variables and functions. This metadata modifies the behavior of a few passes. Most importantly, it prevents passes like inlining from changing the output section of code. This is important for allowing the user to ensure that certain functions are emitted in a specific section.
Passing symbol information back to the linker
After optimizations run, the backend emits an ELF file. This ELF file needs to contain information about the original, pre-LTO files. The LTO step takes care of this by modifying the section names: each section name is annotated with a marker at the end indicating the original source file. The syntax is appending “^^” followed by the name of the file to the section name. The linker then uses this to map symbols to source files when performing final processing of linker script rules. (This mangling is only done when the linker is using the new additions to the LTO interface.)
After LTO, linking works mostly the same as usual, except for the modified processing of linker script rules.