llvm
Who pays for Clang
Clang's funders have changed since 2010: Apple and Google, which wrote most of it until the mid-2010s, now write a small share, and GPU and accelerator vendors, Red Hat, and new sponsors of C++ conformance work write more. This post counts commits, pull-request approvals, and the kind of code each employer touches. The method is unscientific: commit counts are a crude proxy for work, and the attribution is my best guess, so read the numbers as rough trends, not an authoritative record. It grew out of ClangIR and the cost of MLIR, which needed it to explain who wanted an MLIR-based Clang.
…ClangIR and the cost of MLIR
ClangIR (CIR) is an MLIR dialect between the AST and LLVM IR, and in October 2026 the clang area team accepted building it by default. CIR is built on MLIR because each step along the way was locally reasonable, and together the steps locked MLIR in. The costs (build time, binary size, MLIR API churn) land largely on Clang contributors who neither chose MLIR nor use CIR.
This post traces how CIR came to depend on MLIR, collects what the dependency costs, and uses an MLIR-free CIR as a thought experiment to separate the costs inherent to MLIR from those that can be reduced.
…lld 23 ELF changes
LLVM 23.1 has been released. As usual, I maintain lld/ELF and as volunteer work have added some notes to https://github.com/llvm/llvm-project/blob/release/23.x/lld/docs/ReleaseNotes.rst.
Like last time, I used Claude Code to summarize git log llvmorg-23-init..origin/release/23.x -- lld/ELF, excluding changes cherry-picked into 22.x (git rev-list llvmorg-23-init..llvmorg-22.1.8 -- lld), and then edited the draft.
This was a busy cycle: 141 commits landed in lld/ELF between the branch point (2026-01-13) and 23.1.0-rc1 (2026-07-16), compared with 72 in the 22 cycle. Much of the increase is performance work, which I described in Recent lld/ELF performance improvements. lld 23 is the first release that ships all of it.
…Block frequency
Estimating branch probabilities says how one branch splits.
BlockFrequencyInfo turns those local numbers into per-block frequencies, which nearly every profitability decision in LLVM ends up reading.
The core is a linear-time propagation over loop-packaged regions. Where no such structure exists — irreducible control flow — the accuracy goes with it.
…Estimating branch probabilities
LLVM's BranchProbabilityInfo assigns every multi-successor terminator a probability distribution over its successors.
This post describes the estimation used when no profile is available and reimplements it as a standalone program.
Optimizing LLVM's bump allocator
BumpPtrAllocator is LLVM's bump allocator (arena allocator): each allocation bumps a pointer within a slab, and everything is freed at once when the allocator dies.
It backs Clang's ASTContext, lld's make<T> object pools, TableGen records, and many other arenas.
Here is the fast path before three recent changes:
__attribute__((returns_nonnull)) void *Allocate(size_t Size, Align Alignment) {
BytesAllocated += Size; // (3) accounting RMW
uintptr_t AlignedPtr = alignAddr(CurPtr, Alignment); // (1) always realign
size_t SizeToAllocate = Size;
#if LLVM_ADDRESS_SANITIZER_BUILD
SizeToAllocate += RedZoneSize;
#endif
uintptr_t AllocEndPtr = AlignedPtr + SizeToAllocate;
if (LLVM_LIKELY(AllocEndPtr <= uintptr_t(End)
&& CurPtr != nullptr)) { // (2) bound + null check
CurPtr = reinterpret_cast<char *>(AllocEndPtr);
...
return reinterpret_cast<char *>(AlignedPtr);
}
return AllocateSlow(Size, SizeToAllocate, Alignment);
}…
A deep dive into SmallVector::push_back
tl;dr This blog post describes a recent SmallVector::push_back optimization for approximately trivially copyable element types.
SmallVector is LLVM's most-used container, and push_back its hot operation.
For the trivially-copyable specialization the fast path should be fast.
#include <llvm/ADT/SmallVector.h>
void f(llvm::SmallVectorImpl<int> &v, int x) { v.push_back(x); }
clang -S --target=x86_64 -O2 -DNDEBUG a.cc generates:
push rbp # callee-saved spills + a stack realignment,
push rbx # all on the fast path
push rax
mov eax, [rdi + 8] # size
cmp eax, [rdi + 12] # vs capacity
jae .Lgrow
.Lstore: # reached from the fast path AND from .Lgrow
mov rcx, [rdi]
mov [rcx + rax*4], esi
inc dword ptr [rdi + 8]
add rsp, 8
pop rbx
pop rbp
ret
.Lgrow:
mov rbx, rdi # keep `this`/`x` alive across the call
mov ebp, esi
call SmallVectorBase<unsigned>::grow_pod
...
jmp .Lstore…
Recent LLVM hash table improvements
LLVM has several hash tables. They used quadratic probing with in-band sentinel keys (empty, tombstone); recent work has been replacing that with linear probing with tombstone key removed.
DenseMap(replacement forstd::unordered_map):DenseMapInfo::getEmptyKey()/getTombstoneKey().DenseSet: implemented usingDenseMapcompiler-rt/lib/sanitizer_common/sanitizer_dense_map.hports the implementation for sanitizers.
SmallPtrSet(replacement forstd::unordered_set<T *>): hard-coded-1(empty) and-2(tombstone).StringMap(replacement forstd::unordered_map<std::string, V>)StringSet: implemented usingStringMap
FoldingSet(uniquing/hash-consing container, not a general map)
For the open-addressed DenseMap and SmallPtrSet, pointers, references, and iterators are invalidated by insert.
StringMap is different: each entry lives in a heap-allocated StringMapEntry<V> node, so entry pointers survive grow.
std::unordered_map, being node-based, keeps surviving-element pointers valid across both insert and erase and only invalidates the erased element's own iterator.
LLVM code rarely needs that stronger contract — callers do not hold long-lived references into the container across mutation — and that gap is what gives pass to relocating erase and bit-array occupancy.
Recently,
- Tombstones have been removed from DenseMap and SmallPtrSet.
erase()also invalidates pointers. - DenseMap has also retired its empty-key sentinel, leading to significant performance improvements.
DenseMap with integer keys (
int/unsigned/size_t) had-1/-2reserved — a footgun, now fixed. - StringMap got Algorithm R deletion too. Its entries are separately heap-allocated, so erase keeps entry pointers valid but invalidates iterators; erase-while-iterating moved to
remove_if. - FoldingSet dropped chaining for linear probing plus Algorithm R; the intrusive next-in-bucket pointer became a cached 32-bit hash.
Fighting Hyrum's Law in LLVM
With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody. — Hyrum's Law
In a compiler, the most common form of Hyrum's Law is dependence on unspecified behavior — hash bucket order, the order of equal elements after std::sort, padding offsets.
The same framing covers a few cases that are technically undefined behavior (use of an invalidated iterator) or plain incidental properties (ABI struct layout, ELF section offsets).
When the compiler itself harbors such a dependency, the symptom is usually output that varies build-to-build: an unstable sort that lands differently after the standard library changes, a hash map whose iteration order shifts when the hash function does.
Occasionally the variation is run-to-run within a single build — DenseMap<void *, X> keys with an ASLR-derived seed reorder buckets each invocation.
Either way, reproducible builds, bisection, and bug reports all assume same input → same output, and a stealth Hyrum dependency breaks that.
This post surveys some mechanisms that perturb the contract's blind spots so dependencies cannot quietly form.
…Recent lld/ELF performance improvements
Updated in 2026-05.
Since the LLVM 22 branch was cut, I've landed patches that parallelize more link phases and cut task-runtime overhead.
This post compares current main against lld 22.1, mold, and wild.
Headline: a Release+Asserts clang --gc-sections link is 1.34x as fast as lld 22.1; Chromium debug with --gdb-index is 1.09x as fast. mold and wild are still ahead — the last section explains why.
lld 22 ELF changes
For those unfamiliar, lld is the LLVM linker, supporting PE/COFF, ELF, Mach-O, and WebAssembly ports. These object file formats differ significantly, and each port must follow the conventions of the platform's system linker. As a result, the ports share limited code (diagnostics, memory allocation, etc) and have largely separate reviewer groups.
With LLVM 22.1 releasing soon, I've added some notes to the https://github.com/llvm/llvm-project/blob/release/22.x/lld/docs/ReleaseNotes.rst as an lld/ELF maintainer. As usual, I've reviewed almost all the patches not authored by me.
For the first time, I used an LLM agent (Claude Code) to help look through commits (git log release/21.x..release/22.x -- lld/ELF) and draft the release notes.
Despite my request to only read lld/ELF changes, Claude Code also crafted notes for other ports, which I retained since their release notes had been quite sparse for several releases.
Changes back ported to the 21.x release are removed (git log --oneline llvmorg-22-init..llvmorg-21.1.8 -- lld).
I'll delve into some of the key changes.
…Long branches in compilers, assemblers, and linkers
Branch instructions on most architectures use PC-relative addressing with a limited range. When the target is too far away, the branch becomes "out of range" and requires special handling.
Consider a large binary where main() at address 0x10000 calls foo() at address 0x8010000-over 128MiB away.
On AArch64, the bl instruction can only reach ±128MiB, so this call cannot be encoded directly.
Without proper handling, the linker would fail with an error like "relocation out of range."
The toolchain must handle this transparently to produce correct executables.
This article explores how compilers, assemblers, and linkers work together to solve the long branch problem.
- Compiler (IR to assembly): Handles branches within a function that exceed the range of conditional branch instructions
- Assembler (assembly to relocatable file): Handles branches within a section where the distance is known at assembly time
- Linker: Handles cross-section and cross-object branches discovered during final layout
lld 21 ELF changes
LLVM 21.1 have been released. As usual, I maintain lld/ELF and have added some notes to https://github.com/llvm/llvm-project/blob/release/21.x/lld/docs/ReleaseNotes.rst. I've meticulously reviewed nearly all the patches that are not authored by me. I'll delve into some of the key changes.
…Understanding alignment - from source to object file
Updated in 2026-04.
Alignment refers to the practice of placing data or code at memory addresses that are multiples of a specific value, typically a power of 2. This is typically done to meet the requirements of the programming language, ABI, or the underlying hardware. Misaligned memory accesses might be expensive or will cause traps on certain architectures.
This blog post explores how alignment is represented and managed as C++ code is transformed through the compilation pipeline: from source code to LLVM IR, assembly, and finally the object file. We'll focus on alignment for both variables and functions.
…LLVM integrated assembler: Improving sections and symbols
In my previous post, LLVM integrated assembler: Improving expressions and relocations delved into enhancements made to LLVM's expression resolving and relocation generation. This post covers recent refinements to MC, focusing on sections and symbols.
…LLVM integrated assembler: Engineering better fragments
In my previous assembler posts, I've discussed improvements on expression resolving and relocation generation. Now, let's turn our attention to recent refinements within section fragments. Understanding how an assembler utilizes these fragments is key to appreciating the improvements we've made. At a high level, the process unfolds in three main stages:
- Parsing phase: The assembler constructs section fragments. These fragments represent sequences of regular instructions or data, span-dependent instructions, alignment directives, and other elements.
- Section layout phase: Once fragments are built, the assembler assigns offsets to them and finalizes the span-dependent content.
- Relocation decision phase: In the final stage, the assembler evaluates fixups and, if necessary, updates the content of the fragments.
GCC 13.3.0 miscompiles LLVM
For years, I've been involved in updating LLVM's MC layer. A recent journey led me to eliminate the FK_PCRel_ fixup kinds:
MCFixup: Remove FK_PCRel_
The generic FK_Data_ fixup kinds handle both absolute and PC-relative
fixups. ELFObjectWriter sets IsPCRel to true for `.long foo-.`, so the
backend has to handle PC-relative FK_Data_.
However, the existence of FK_PCRel_ encouraged backends to implement it
as a separate fixup type, leading to redundant and error-prone code.
Removing FK_PCRel_ simplifies the overall fixup mechanism.…
LLVM integrated assembler: Improving expressions and relocations
In my previous post, LLVM integrated assembler: Improving MCExpr and MCValue delved into enhancements made to LLVM's internal MCExpr and MCValue representations. This post covers recent refinements to MC, focusing on expression resolving and relocation generation.
…LLVM integrated assembler: Improving MCExpr and MCValue
In my previous post, Relocation Generation in Assemblers, I explored some key concepts behind LLVM’s integrated assemblers. This post dives into recent improvements I’ve made to refine that system.
The LLVM integrated assembler handles fixups and relocatable expressions as distinct entities. Relocatable expressions, in particular, are encoded using the MCValue class, which originally looked like this:
class MCValue {
const MCSymbolRefExpr *SymA = nullptr, *SymB = nullptr;
int64_t Cst = 0;
uint32_t RefKind = 0;
};…
Relocation generation in assemblers
This post explores how GNU Assembler and LLVM integrated assembler generate relocations, an important step to generate a relocatable file. Relocations identify parts of instructions or data that cannot be fully determined during assembly because they depend on the final memory layout, which is only established at link time or load time. These are essentially placeholders that will be filled in (typically with absolute addresses or PC-relative offsets) during the linking process.
Relocation generation: the basics
Symbol references are the primary candidates for relocations.
For instance, in the x86-64 instruction movl sym(%rip), %eax (GNU syntax), the assembler calculates the displacement between the program counter (PC) and sym.
This distance affects the instruction's encoding and typically triggers a R_X86_64_PC32 relocation, unless sym is a local symbol defined within the current section.
Both the GNU assembler and LLVM integrated assembler utilize multiple passes during assembly, with several key phases relevant to relocation generation:
…Compiling C++ with the Clang API
This post describes how to compile a single C++ source file to an object file with the Clang API.
Here is the code. It behaves like a simplified clang executable that handles -c and -S.
lld 20 ELF changes
LLVM 20 will be released. As usual, I maintain lld/ELF and have added some notes to https://github.com/llvm/llvm-project/blob/release/20.x/lld/docs/ReleaseNotes.rst. I've meticulously reviewed nearly all the patches that are not authored by me. I'll delve into some of the key changes.
…Understanding and improving Clang -ftime-report
Clang provides a few options to generate timing report. Among them, -ftime-report and -ftime-trace can be used to analyze the performance of Clang's internal passes.
-fproc-stat-reportrecords time and memory on spawned processes (ld, and gas if-fno-integrated-as).-ftime-trace, introduced in 2019, generates Clang timing information in the Chrome Trace Event format (JSON). The format supports nested events, providing a rich view of the front end.-ftime-report: The option name is borrowed from GCC.
This post focuses on the traditional -ftime-report, which uses a line-based textual format.
Understanding -ftime-report output
The output consists of information about multiple timer groups. The last group spans the largest interval and encompasses timing data from other groups.
Up to Clang 19, the last group is called "Clang front-end time report". You would see something like the following.
…Simplifying disassembly with LLVM tools
Both compiler developers and security researchers have built disassemblers. They often prioritize different aspects. Compiler toolchains, benefiting from direct contributions from CPU vendors, tend to offer more accurate and robust decoding. Security-focused tools, on the other hand, often excel in user interface design.
For quick disassembly tasks, rizin provides a convenient command-line interface.
…Removing global state from LLD
LLD, the LLVM linker, is a mature and fast linker supporting multiple binary formats (ELF, Mach-O, PE/COFF, WebAssembly). Designed as a standalone program, the code base relies heavily on global state, making it less than ideal for library integration. As outlined in RFC: Revisiting LLD-as-a-library design, two main hurdles exist:
- Fatal errors: they exit the process without returning control to the caller. This was actually addressed for most scenarios in 2020 by utilizing
llvm::sys::Process::Exit(val, /*NoCleanup=*/true)andCrashRecoveryContext(longjmpunder the hood). - Global variable conflicts: shared global variables do not allow two concurrent invocation.
I understand that calling a linker API could be convenient, especially when you want to avoid shipping another executable (which can be large when you link against LLVM statically). However, I believe that invoking LLD as a separate process remains the recommended approach. There are several advantages:
- Build system control: Build systems gain greater control over scheduling and resource allocation for LLD. In an edit-compile-link cycle, the link could need more resources and threading is more useful.
- Better parallelism management
- Global state isolation: LLVM's global state (primarily
cl::optandManagedStatic) is isolated.
Keeping pace with LLVM: compatibility strategies
LLVM's C++ API doesn't offer a stability guarantee. This means function signatures can change or be removed between versions, forcing projects to adapt.
On the other hand, LLVM has an extensive API surface. When a library like llvm/lib/Y relies functionality from another library, the API is often exported in header files under llvm/include/llvm/X/, even if it is not intended to be user-facing.
To be compatible with multiple LLVM versions, many projects rely on #if directives based on the LLVM_VERSION_MAJOR macro.
This post explores the specific techniques used by ccls to ensure compatibility with LLVM versions 7 to 19.
For the latest release (ccls 0.20241108), support for LLVM versions 7 to 9 has been discontinued.
Given the tight coupling between LLVM and Clang, the LLVM_VERSION_MAJOR macro can be used for both version detection. There's no need to check CLANG_VERSION_MAJOR.
My involvement with LLVM 19
LLVM 19.1 will soon be released. This post provides a summary of my contributions in this release cycle to record my learning progress.
…lld 19 ELF changes
LLVM 19 will be released. As usual, I maintain lld/ELF and have added some notes to https://github.com/llvm/llvm-project/blob/release/19.x/lld/docs/ReleaseNotes.rst. I've meticulously reviewed nearly all the patches that are not authored by me. I'll delve into some of the key changes.
…Mapping symbols: rethinking for efficiency
In object files, certain code patterns embed data within instructions or transitions occur between instruction sets. This can create hurdles for disassemblers, which might misinterpret data as code, resulting in inaccurate output. Furthermore, code written for one instruction set could be incorrectly disassembled as another. To address these issues, some architectures (Arm, C-SKY, NDS32, RISC-V, etc) define mapping symbols to explicitly denote state transition. Let's explore this concept using an AArch32 code example:
…Integrated assembler improvements in LLVM 19
Within the LLVM project, MC is a library responsible for handling assembly, disassembly, and object file formats. Intro to the LLVM MC Project, which was written back in 2010, remains a good source to understand the high-level structures.
In the latest release cycle, substantial effort has been dedicated to refining MC's internal representation for improved performance and readability. These changes have decreased compile time significantly. This blog post will delve into the details, providing insights into the specific changes.
…Understanding orphan sections
GNU ld's output section layout is determined by a linker script, which can be either internal (default) or external (specified with -T or -dT).
Within the linker script, SECTIONS commands define how input sections are mapped into output sections.
Input sections not explicitly placed by SECTIONS commands are termed "orphan sections".
Orphan sections are sections present in the input files which are not explicitly placed into the output file by the linker script. The linker will still copy these sections into the output file by either finding, or creating a suitable output section in which to place the orphaned input section.
GNU ld's default behavior is to create output sections to hold these orphan sections and insert these output sections into appropriate places.
Orphan section placement is crucial because GNU ld's built-in linker scripts, while understanding common sections like .text/.rodata/.data, are unaware of custom sections.
These custom sections should still be included in the final output file.
- Grouping: Orphan input sections are grouped into orphan output sections that share the same name.
- Placement: These grouped orphan output sections are then inserted into the output sections defined in the linker script. They are placed near similar sections to minimize the number of
PT_LOADsegments needed.
Clang's -O0 output: branch displacement and size increase
tl;dr Clang 19 will remove the -mrelax-all default at -O0, significantly decreasing the text section size for x86.
Span-dependent instructions
In assembly languages, some instructions with an immediate operand can be encoded in two (or more) forms with different sizes. On x86-64, a direct JMP/JCC can be encoded either in 2 bytes with a 8-bit relative offset or 6 bytes with a 32-bit relative offset. A short jump is preferred because it takes less space. However, when the target of the jump is too far away (out of range for a 8-bit relative offset), a near jump must be used.
ja foo # jump short if above, 77 <rel8>
ja foo # jump near if above, 0f 87 <rel32>
.nops 126
foo: ret
A 1978 paper by Thomas G. Szymanski ("Assembling Code for Machines with Span-Dependent Instructions") used the term "span-dependent instructions" to refer to such instructions with short and long forms. Assemblers grapple with the challenge of choosing the optimal size for these instructions, often referred to as the "branch displacement problem" since branches are the most common type. A good resource for understanding Szymanski's work is Assembling Span-Dependent Instructions.
…My involvement with LLVM 18
LLVM 18 will soon be relased. This post provides a summary of my contributions in this release cycle to record my learning progress.
…lld 18 ELF changes
LLVM 18 will be released. As usual, I maintain lld/ELF and have added some notes to https://github.com/llvm/llvm-project/blob/release/18.x/lld/docs/ReleaseNotes.rst. I've meticulously reviewed nearly all the patches that are not authored by me. I'll delve into some of the key changes.
…Toolchain notes on z/Architecture
This article describes some notes about z/Architecture with a focus on the ELF ABI and ELF linkers. An lld/ELF patch sparked my motivation to study the architecture and write this post.
z/Architecture is a big-endian mainframe computer architecture supporting 24-bit, 31-bit, and 64-bit addressing modes.
It is the latest generation in a lineage stretching back to the 1964 with IBM System/360 (32-bit general-purpose registers and 24-bit addressing).
This lineage includes System/370 (1970), System/370 Extended Architecture (1983), Enterprise Systems Architecture/370 (1988), and Enterprise Systems Architecture/390 (1990).
For a deeper dive into the design choices behind z/Architecture's extension from ESA/390, you can refer to "Development and attributes of z/Architecture."

Raw symbol names in inline assembly
For operands in asm statements, GCC has supported the constraints "i" and "s" for a long time (since at least 1992).
// gcc/common.md
(define_constraint "i"
"Matches a general integer constant."
(and (match_test "CONSTANT_P (op)")
(match_test "!flag_pic || LEGITIMATE_PIC_OPERAND_P (op)")))
(define_constraint "s"
"Matches a symbolic integer constant."
(and (match_test "CONSTANT_P (op)")
(match_test "!CONST_SCALAR_INT_P (op)")
(match_test "!flag_pic || LEGITIMATE_PIC_OPERAND_P (op)")))…
Modified condition/decision coverage (MC/DC) and compiler implementations
Key metrics for code coverage include:
- function coverage: determines whether each function been executed.
- line coverage (aka statement coverage): determines whether every line has been executed.
- branch coverage: ensures that both the true and false branches of each conditional statement or the condition of each loop statement been evaluated.
Condition coverage offers a more fine-grained evaluation of branch coverage.
It requires that each individual boolean subexpression (condition) within a compound expression be evaluated to both true and false.
For example, in the boolean expression if (a>0 && f(b) && c==0), each of a>0, f(b), and c==0, condition coverage would require tests that:
reviews.llvm.org became a read-only archive
For approximately 10 years, reviews.llvm.org functioned as the code view site for the LLVM project, utilizing a Phabricator instance.
This website hosted numerous invaluable code review discussions.
However, following LLVM's transition to GitHub pull requests, there arises a necessity for a read-only archive of the existing Phabricator instance.
(https://archive.org/ archives a subset of the reviews.llvm.org/Dxxxxx pages.)
The intent is to eliminate a SQL engine. Phabicator operates on a complex database scheme. To minimize time investment, the most feasible approach seems to involve downloading the static HTML pages and employing a lightweight scraping process.
Raphaël Gomès developed phab-archive to serve a read-only archive for Mercurial's Phabricator instance. I have modified the code to suit reviews.llvm.org.
The DNS records of reviews.llvm.org have been pointed to the archive website.
…Exploring the section layout in linker output
Updated in 2026-03.
This article describes section layout and its interaction with dynamic loaders and huge pages.
Let's begin with a Linux x86-64 example involving global variables exhibiting various properties such as read-only versus writable, zero-initialized versus non-zero, and more.
#include <stdio.h>
const int ro = 1;
int w0, w1 = 1;
int *const pw0 = &w0;
int main() {
printf("%d %d %d %p\n", ro, w0, w1, pw0);
}…
A deep dive into Clang's source file compilation
Clang is a C/C++ compiler that generates LLVM IR and utilizes LLVM to generate relocatable object files. Using the classic three-stage compiler structure, the stages can be described as follows:
C/C++ =(front end)=> LLVM IR =(middle end)=> LLVM IR (optimized) =(back end)=> relocatable object file
If we follow the internal representations of instructions, a more detailed diagram looks like this:
C/C++ =(front end)=> LLVM IR =(middle end)=> LLVM IR (optimized) =(instruction selector)=> MachineInstr =(AsmPrinter)=> MCInst =(assembler)=> relocatable object file
LLVM and Clang are designed as a collection of libraries. This post describes how different libraries work together to create the final relocatable object file. I will focus on how a function goes through the multiple compilation stages.
…Reflections on LLVM's switch to GitHub pull requests
Since 2012, LLVM has relied on its self-hosted Phabricator instance on Google Cloud Platform for code review, but now it's making a transition to GitHub pull requests. In this post, I'll share my perspective on this switch, highlighting GitHub offers significant benefits in some areas while having major drawbacks in the review process.
I may update this article as the process stabilizes further.
Transition to GitHub pull requests
The move to GitHub pull requests has been a topic of discussion over the past few years. Several lengthy threads on the subject have emerged:
…Toolchain notes on MIPS
This article describes some notes about MIPS with a focus on the ELF object file format, GCC, binutils, and LLVM/Clang.
In the llvm-project project, I sometimes find myself assigned as a reviewer for MIPS patches. I want to be transparent that I have no interest in MIPS, but my concern lies with the specific components that are impacted (Clang driver, ld.lld, MC, compiler-rt, etc.). Therefore, regrettably, I have to spend some time studying MIPS.
…Using copper as a mirror, one can straighten their attire; using the past as a mirror, one can understand rise and fall; using people as a mirror, one can discern gains and losses. -- 贞观政要
Clang -Wunused-command-line-argument
clangDriver is the library implementing the compiler driver for Clang. It utilitizes LLVMOption to process command line options. As options are processed when required, as opposed to use a large switch, Clang gets the ability to detect unused options straightforwardly.
…My involvement with LLVM 17
LLVM 17 will soon be relased. This post provides a summary of my contributions in this release cycle to record my learning progress.
…lld 17 ELF changes
LLVM 17 will be released. As usual, I maintain lld/ELF and have added some notes to https://github.com/llvm/llvm-project/blob/release/17.x/lld/docs/ReleaseNotes.rst. Here I will elaborate on some changes.
…Precompiled headers
C/C++ projects can benefit from using precompiled headers to improve compile time. GCC added support for precompiled headers in 2003 (version 3.4), and the current documentation can be found at https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html.
Even with the emergence of C++ modules, precompiled headers remain relevant for several reasons:
- Precompiled headers share implementation aspects with modules (e.g., AST serialization in Clang).
- Many C++ projects rely on the traditional compilation model and are not converted to C++ modules.
- Modules may possibly use some preamble-like technology to accelerate IDE-centric operations.
- C doesn't have C++ modules.
This article focuses on Clang precompiled headers (PCH). Let's begin with an example.
…Compressed arbitrary sections
This article describes SHF_ALLOC|SHF_COMPRESSED sections in ELF and lld's linker option --compress-sections to compress arbitrary sections.
Port LLVM XRay to Apple systems
I do not use Apple products myself, but I sometimes delve into Mach-O due to my interest in object file formats. Additionally, my LLVM/Clang changes sometimes require some understanding of Mach-O. Occasionally, I need to understand the format to some extent to work around its quirks (the old format inherited many problems of "a.out").
Recently, there has been interest (from Oleksii Lozovskyi) in enabling XRay, a function call tracing system in LLVM, to work on Apple systems. Intrigued by this, I decided to delve into the details and investigate the necessary changes. XRay supports many 64-bit architectures on Linux and some BSDs. I became acquainted with XRay back in 2017 and made some casual contributions since then.
…Assemblers
Updated in 2025-05.
This article provides a description of popular assemblers and their architecture-specific differences.
Assemblers
GCC generates assembly code and invokes GNU Assembler (also known as "gas"), which is part of GNU Binutils, to convert the assembly code into machine code. The GCC driver is also capable of accepting assembly input files. Due to GCC's widespread use, GNU Assembler is arguably the most popular assembler.
Within the LLVM project, the LLVM integrated assembler is a library that is linked by Clang, llvm-mc, and lld (for LTO purposes) to generate machine code. It supports a wide range of GNU Assembler syntax and can be used as a drop-in replacement for GNU Assembler.
On the Windows platform, the Microsoft Macro Assembler (MASM) is widely used.
On the IBM AIX platform, the AIX assembler is used. In 2019, IBM developers started to modify LLVM integrated assembler to support the AIX syntax.
On the IBM z/OS platform, the IBM High Level Assembler (HLASM) is used. In 2021, IBM developers started to modify LLVM integrated assembler to support the HLASM syntax.
Concepts
Sections are named, contiguous blocks of code or data within an object file. They allow you to logically group related parts of your program. The assembler places code and data into these sections as it processes the source file.
Symbols are names that represent memory addresses or values.
…Linker notes on AArch32
UNDER CONSTRUCTION
This article describes target-specific details about AArch32 in ELF linkers. I described AArch64 in a previous article.
AArch32 is the 32-bit execution state for the Arm architecture and runs the A32 and T32 instruction sets. A32 refers to the old ISA with a 32-bit fixed width, while T32 refers to the mixed 16-bit and 32-bit Thumb2 instructions.
"AArch32", "A32", and "T32" are new names. Many projects use "ARM", "Arm", or "arm" as their port name.
…lld 16 ELF changes
llvm-project 16 was just released. I added some lld/ELF notes to https://github.com/llvm/llvm-project/blob/release/16.x/lld/docs/ReleaseNotes.rst. Here I will elaborate on some changes.
…Linker notes on AArch64
This article describes target-specific details about AArch64 in ELF linkers. AArch64 is the 64-bit execution state for the Arm architecture. The AArch64 execution state runs the A64 instruction set. The AArch32 and AArch64 execution states use very different instruction sets, so many pieces of software use two ports for the two execution states of the Arm architecture.
There were the "ARM architecture" and the "ARM instruction set", leading to many software projects using "ARM" or "arm" as their port names. In 2011, ARMv8 introduced two execution states, AArch32 and AArch64. The previous instruction sets "ARM" and "Thumb" were renamed to "A32" and "T32", respectively. In 2017, the architecture was renamed to the "Arm architecture" to reflect the rebranding of the company name. So, the "ARMv8-A" architecture profile is now named "Armv8-A".
For the AArch64 execution state, while many projects use "AArch64" as their port name, for legacy reasons, macOS, Windows, the Linux kernel, and some BSD operating systems unfortunately use "arm64". (Support for AArch64 was added to the Linux kernel in version 3.7. Initially, the patch set was named "aarch64", but it was later changed at the request of kernel developers.)
…Linker notes on Power ISA
This article describes target-specific details about Power ISA in ELF linkers. Initially there was IBM POWER. The 1991 Apple–IBM–Motorola alliance created PowerPC. In 2006, the architecture was rebranded as Power ISA. According to the ISA manual, "In 2006, Freescale and IBM collaborated on the creation of the Power ISA Version 2.03, which represented the reunification of the architecture by combining Book E content with the more general purpose PowerPC Version 2.02."
The terms "PowerPC" and "powerpc" remain popular in numerous places, including the powerpc-*-*-* and powerpc64-*-*-* in official target triple names.
The abbreviation "PPC" ("ppc") is used in numerous places as well.
For simplicity, I will refer to the 32-bit architecture as "PPC32" and the 64-bit architecture as "PPC64".
We will see how the lack of PC-relative addressing before Power10 has caused great complexity to the ABI and linkers.
…All about LeakSanitizer
Clang and GCC 4.9 implemented LeakSanitizer in 2013.
LeakSanitizer (LSan) is a memory leak detector.
It intercepts memory allocation functions and by default detects memory leaks at atexit time.
The implementation is purely in the runtime (compiler-rt/lib/lsan) and no instrumentation is needed.
LSan has very little architecture-specific code and supports many 64-bit targets. Some 32-bit targets (e.g. Linux arm/x86-32) are supported as well, but there may be high false negatives because pointers with fewer bits are more easily confused with integers/floating points/other data of a similar pattern. Every supported operating system needs to provide some way to "stop the world".
…Function multi-versioning
Updated in 2023-12.
GCC supports some function attributes for function multi-versioning: a way for a function to have multiple implementations, each using a different set of ISA extensions. A function attribute specifies different requirements of ISA extensions. The generated program decodes the CPU model and features at run-time, and picks the most restrictive implementation that is satisfied by the CPU, assuming that the most restrictive implementation has the best performance.
…All about UndefinedBehaviorSanitizer
Updated in 2025-01.
UndefinedBehaviorSanitizer (UBSan) is an undefined behavior detector for C/C++. It consists of code instrumentation and a runtime. Both components have multiple independent implementations.
Clang implemented the first few checks in 2009-12, initially named -fcatch-undefined-behavior.
In 2012 -fsanitize=undefined was added and -fcatch-undefined-behavior was removed.
GCC 4.9 implemented -fsanitize=undefined in 2013-08.
The runtime used by Clang lives in llvm-project/compiler-rt/lib/ubsan.
GCC from time to time syncs its downstream fork of the sanitizers part of compiler-rt (libsanitizer).
The end of the article lists some alternative runtime implementations.
All about sanitizer interceptors
Updated in 2025-08.
Many sanitizers want to know every function in the program. User functions are instrumented and therefore known by the sanitizer runtime. For library functions, some (e.g. mmap, munmap, memory allocation/deallocation functions, longjmp, vfork) need special treatment. Sanitizers leverage symbol interposition to redirect such function calls to its own implementation: interceptors. Other library functions can be treated as normal user code. Either instrumenting the function or providing an interceptor is fine.
In some cases instrumenting is infeasible:
- Assembly source files usually do not (or are inconvenient to) call sanitizer callbacks
- Many libc implementations cannot be instrumented. When can glibc be built with Clang?
- Some functions have performance issues if instrumented instead of intercepted (mostly
mem*andstr*)
And interceptors may be the practical choice.
This article talks about how interceptors work and the requirements of sanitizer interceptors.
…Control-flow integrity
Updated in 2025-05.
A control-flow graph (CFG) is a graph representation of all paths that might be traversed through a program during its execution. Control-flow integrity (CFI) refers to security policy dictating that program execution must follow a control-flow graph. This article describes some features that compilers and hardware can use to enforce CFI, with a focus on llvm-project implementations.
CFI schemes are typically divided into forward-edge (e.g. indirect calls) and backward-edge (mainly function returns). It should be noted that exception handling and symbol interposition are not included in these categories, as far as my understanding goes.
…Relocatable linking
Updated in 2025-02.
In GNU ld, -r produces a relocatable object file. This is known as relocatable linking or partial linking.
This mode suppresses many passes done for an executable or shared object output (in -no-pie/-pie/-shared modes).
-r, -no-pie, -pie, and -shared specify 4 different modes. The 4 options are mutually exclusive.
The relocatable output can be used for analysis and binary manipulation. Then, the output can be used to link the final executable or shared object.
clang -pie a.o b.o
# ==>
clang -r a.o b.o -o r.o
clang -pie r.o
Let's go through various linker passes and see how relocatable linking changes the operation.
…ODR violation detection
This article describes how to detect C++ One Definition Rule (ODR) violations. There are many good resources on the Internet about how ODR violations can introduce subtle bugs, so I will not repeat that here.
…_FORTIFY_SOURCE
Updated in 2024-08.
glibc 2.3.4 introduced _FORTIFY_SOURCE in 2004 to catch security errors due to misuse of some C library functions.
The initially supported functions were fprintf, gets, memcpy, memmove, mempcpy, memset, printf, snprintf, sprintf, stpcpy, strcat, strcpy, strncat, strncpy, vfprintf, vprintf, vsnprintf, vsprintf and focused on buffer overflow detection and dangerous printf %n uses.
The implementation leverages inline functions and __builtin_object_size (see [PATCH] Object size checking to prevent (some) buffer overflows).
More functions were added over time and __builtin_constant_p was used as well. As of 2022-11 glibc defines 79 default version *_chk functions.
lld linked musl on PowerPC64
I was asked about a segfault related to lld linked musl libc.so on PowerPC64.
/usr/lib/ld-musl-powerpc64le.so.1 /path/to/thingworked. The kernel ELF loader loads rtld and rtld loads the executable./path/to/thingsegfaulted. The kernel ELF loader loads both rtld and the executable.
Therefore the bug is likely due to a difference between the two modes.
…Distribution of debug information
Updated in 2024-08.
Note: The article will likely get frequent updates in the next few days.
This article describes some approaches to distribute debug information. Commands below will use two simple C files for demonstration.
cat > a.c <<eof
void foo(int);
int main() { foo(42); }
eof
cat > b.c <<eof
#include <stdio.h>
void foo(int x) { printf("%d\n", x); }
eof…
zstd compressed debug sections
Updated in 2022-10.
In January I wrote Compressed debug sections.
The venerable zlib shows its age and there are replacements which are better in every metric except adoption and a larger memory footprint.
The obvious choice was Zstandard, but I was not so confident about adoptinig it and solving the ecosystem issue.
At any rate, I slowly removed some legacy .zdebug support from llvm-project so that a new format could be more easily introduced.
lld 15 ELF changes
llvm-project 15 was just released. I added some lld/ELF notes to https://github.com/llvm/llvm-project/blob/release/15.x/lld/docs/ReleaseNotes.rst. Here I will elaborate on some changes.
…DWARF in reproducible builds
Deterministic builds with clang and lld describes several degrees of build determinism. Here is the description of local determinism:
…Like incremental basic determinism, but builds are also independent of the name of the build directory. Builds of the same source code on the same machine produce exactly the same output every time, independent of the location of the source checkout directory or the build directory.
RISC-V linker relaxation in lld
On 2022-07-07, I added a RISC-V linker relaxation framework in ld.lld and implemented R_RISCV_ALIGN/R_RISCV_CALL/R_RISCV_CALL_PLT relaxation.
The changes will be included in the next llvm-project release 15.0.0.
This post describes the implementation.
See The dark side of RISC-V linker relaxation for more information about RISC-V linker relaxation.
…Analysis and introspection options in linkers
Updated in 2026-01.
Reproduce tarball
LLD offers a convenient feature to bundle all input files into a tarball, making it easier to experiment with different linker options. Use either of these commands:
clang -fuse-ld=lld -Wl,--reproduce=/tmp/rep.tar a.o b.o
LLD_REPRODUCE=/tmp/rep.tar clang -fuse-ld=lld a.o b.o
Then unpack the tarball, navigate to the directory, and invoke LLD with the response file:
cd /tmp; tar xf rep.tar; cd rep
ld.lld @response.txt # append options like -y foo
The response file includes the --chroot option, which GNU ld does not support.
In most cases, you can simply remove this option to examine GNU ld's behavior.
ld.bfd @response.txtSymbol related
--trace-symbol=<sym>
Alias: -y sym
lld 14 ELF changes
llvm-project 14 will be released soon. I added some lld/ELF notes to https://github.com/llvm/llvm-project/blob/release/14.x/lld/docs/ReleaseNotes.rst. Here I will elaborate on some changes.
…All about COMMON symbols
Programming language behavior
FORTRAN 77 COMMON blocks compiled to COMMON symbols. You could declare a COMMON block in more than one file, with each specifying the number, type, and size of the variable. The linker allocated enough space to satisfy the largest size.
…Compressed debug sections
Updated in 2024-05.
Binary sizes are important. Filesystem compression is ergonomic but typically does not leverage application information well. Compressing allocable sections (text, data) increases program startup time and introduces memory overhead. In addition, filesystem compression is not sufficiently portable.
Debug sections are large and contribute to a significant portion of the binary size. Therefore, it is appealing to compress debug sections.
Here is a -DCMAKE_BUILD_TYPE=Debug build directory of llvm-project where I just ran ninja clang (on 2022-10-21).
Here are the total sizes of .o files, text sections, and debug sections.
It is typical that the debug information is often much larger than text sections.
% stat -c %s **/*.o | awk '{s+=$1} END{print s}'
1464767464
% readelf -WS **/*.o | awk 'BEGIN{FPAT="\\[.*?\\]|\\S+"} $2~/\.text/{d += strtonum("0x"$6)} END{print d}'
210026370
% readelf -WS **/*.o | awk 'BEGIN{FPAT="\\[.*?\\]|\\S+"} $2~/\.debug_/{d += strtonum("0x"$6)} END{print d}'
631069751
% readelf -WS **/*.o | awk 'BEGIN{FPAT="\\[.*?\\]|\\S+"} $2~/\.rela\.debug_/{d += strtonum("0x"$6)} END{print d}'
78448968
Some assemblers and linkers offer a feature to compress debug sections.
llvm-objcopy supports --compress-debug-sections=zlib to compress debug sections. We can use the option to check what if we compress debug sections for the assembler.
% for i in **/*.o; do /tmp/Rel/bin/llvm-objcopy --compress-debug-sections=zlib $i /tmp/c/o && readelf -WS /tmp/c/o | awk 'BEGIN{FPAT="\\[.*?\\]|\\S+"} $2~/\.debug_/{d += strtonum("0x"$6)} END{print d}'; done | awk '{s+=$1} END{print s}'
161691798
For debug sections, we have a compression ratio of 3.90! The total .o size is 995438992 bytes, 68% of the original.
Then let's check zstd.
% for i in **/*.o; do /tmp/Rel/bin/llvm-objcopy --compress-debug-sections=zstd $i /tmp/c/o && readelf -WS /tmp/c/o | awk 'BEGIN{FPAT="\\[.*?\\]|\\S+"} $2~/\.debug_/{d += strtonum("0x"$6)} END{print d}'; done | awk '{s+=$1} END{print s}'
159341878
To check whether an object file has compressed debug sections, we can use readelf.
% readelf -S a.o
...
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
...
[ 5] .debug_abbrev PROGBITS 0000000000000000 000080 000087 00 C 0 0 8
In the readelf -S output, the Flg column describes sh_flags where C indicates the SHF_COMPRESSED flag.
% readelf -t a.o
...
Section Headers:
[Nr] Name
Type Address Off Size ES Lk Inf Al
Flags
...
[ 5] .debug_abbrev
PROGBITS 0000000000000000 000080 000087 00 0 0 8
[0000000000000800]: COMPRESSED
ZLIB, 0000000000000093, 1History
In 2007-11, Craig Silverstein added --compress-debug-sections=zlib to gold.
When the option was specified, gold compressed the content of a .debug* section with zlib and changed the section name to .debug*.zlib.$uncompressed_size.
Archives and --start-lib
Updated in 2026-02.
.a archives
Unix-like systems represent static libraries as .a archives.
A .a archive consists of a header and a collection of files with file member header.
Its usage is tightly coupled with the linker.
An archive almost always contains only relocatable object files and the linker has built-in support for reading it.
% as /dev/null -o a.o
% rm -f b.a && ar rc b.a a.o
% ar t b.a
a.o
One may add other types of files to .a but that is almost assuredly a bad thing.
% rm -f a.a && ar rc a.a a.o b.a # archive in archive, bad
% ar t a.a
a.o
b.a
% echo hello > a.txt
% rm -f a.a && ar rc a.a a.o a.txt # text file in archive, bad
The original linker designers noticed that for many programs not every member was needed, so they tried to allow the linker to skip unused members. Therefore, they invented the interesting but confusing archive member extraction rule. See Symbol processing#Archive processing for details.
…Why isn't ld.lld faster?
Updated in 2022-11.
LLD is the LLVM linker. Its ELF port is typically installed as ld.lld. This article makes an in-depth analysis of ld.lld's performance.
The topic has been in my mind for a while.
Recently Rui Ueyama released mold 1.0 and people wonder why with multi-threading its ELF port is faster than ld.lld.
So I finally completed the article.
First of all, I am very glad that Rui Ueyama started mold. Our world has a plethora of compilers, but not many people learn or write linkers. As its design documentation says, there are many drastically different designs which haven't been explored. In my view, mold is innovative in that it introduced parallel symbol table initialization, symbol resolution, and relocation scan which to my knowledge hadn't been implemented before, and showed us amazing results. The innovation gives existing and future linkers incentive to optimize further.
…Build glibc with LLD 13
LLD is the LLVM linker.
It started at the end of 2011 as a work-in-progress rewrite of ld64 for the Mach-O binary format based on the atom model.
COFF and ELF ports based on the atom model were contributed subsequently.
They shared one symbol resolution model.
(IMO due to Mach-O's unfortunate limitation of 255 section .subsections_via_symbols was invented. The atom model was an incarnation of the concept but it did not fit into ELF/PE where sections are the better basic units.)
In 2015, both COFF and ELF ports were rewritten. (See "LLD improvement plan") Today, LLD is a mature and fast linker supporting multiple binary formats (ELF, Mach-O, PE/COFF, WebAssembly). FreeBSD, Android, and Chrome OS have adopted it as the main linker.
As a main contributor of LLD's ELF port who has fixed numerous corner cases in recent years, I consider that its x86-64 support has been mature since the 8.0.0 release and is in a great shape since 9.0.0. The AArch64 and PowerPC32/PowerPC64 support has been great since the 10.0.0 release. The 11.0.0 release has very solid linker script support. (When people complain that GNU ld's linker script is not immediately usable with LLD, it is almost assuredly the problem of the script itself.) So, what's next? Build glibc with LLD!
…Extract an archive member to satisfy a DSO undef
In ELF linkers, undefined symbols from DSO participate in symbol resolution, just like undef from .o. The differences with undef from .o are:
- undef purely from DSO do not need .symtab entries
- --no-allow-shlib-undefined can error for such undef (-rpath-link ("load dependent libraries") behavior can suppress some errors.
ELF linkers extract an archive member to satisfy an undefined symbol from a shared object. Here is an example:
…Toolchain testing
Updated in 2025-06.
Some random notes about toolchain testing.
Classification
Testing levels.
…COMDAT and section group
Updated in 2025-09.
The blog post explores how COMDAT (in the PE format) and section groups (in the ELF format) provide a mechanism to group related sections for efficient deduplication and garbage collection.
Vague linkage
In C++, inline functions, template instantiations, and a few other things can be defined in multiple object files but need deduplication at link time. In the dark ages the functionality was implemented by weak definitions: the linker does not report duplicate definition errors and resolves the references to the first definition. The downside is that unneeded copies remained in the linked image.
…SECTIONS and OVERWRITE_SECTIONS
The main task of a linker script is to define extra symbols and define how input sections map into output sections. It has other miscellaneous features which can be implemented via command line options:
- The
ENTRYcommand can be replaced by--entry. - The
OUTPUT_FORMATcommand can usually be replaced by-m. - The
SEARCH_DIRScommand can be replaced by-L. - The
VERSIONcommand can be replaced by--version-script. - The
INPUTandGROUPcommands can add other files as input. This provides a mechanism to split an archive/shared object into multiple files.
Symbol processing
UNDER CONSTRUCTION (COFF, Mach-O)
Symbol processing is a major step in a linker. In most binary formats, the linker maintains a global symbol table and performs symbols resolution for each input file (object file, shared object, archive, LLVM bitcode file). Some command line options can define/undefine symbols as well. The symbol resolution can affect archive processing and many subsequent steps (LTO, relocation processing, as-needed shared objects, etc).
…Dependency related linker options
This article describes the dependency related linker options -z defs, --no-allow-shlib-undefined, and --warn-backrefs.
Deploying them in a build system can improve build health.
-z defs
…
ELF interposition and -Bsymbolic
This article describes ELF interposition, the linker option -Bsymbolic, and its friends. In the end, it will discuss an ambitious plan which I dubbed "the Last Alliance of ELF and Men".
Motivated by a great post by Daniel Colascione ("Python is 1.3x faster when compiled in a way that re-examines shitty technical decisions from the 1990s.") and a recent rant from Linus Torvalds on shared objects' performance issues, I have summarized the current unfortunate ELF state and filed some GCC/binutils feature requests. I believe the performance of our shared object oriented world will be no slower than one with mostly statically linked executables.
(I wrote -fno-semantic-interposition first but then realized reorganization would improve readability, so moved some parts and added some stuff to this new article.)
…-fno-semantic-interposition
Updated in 2022-05.
This article is a continuation to ELF interposition and -Bsymbolic.
It focuses on the GCC/Clang option -fno-semantic-interposition and why it can (sometimes incredibly) optimize -fPIC programs.
Compiler driver and cross compilation
Updated 2023-03.
Compiler driver
The gcc program is a compiler driver. It invokes other programs to do the work of compiling (cc1, cc1plus), assembling (GNU as), and linking (collect2).
The behavior is controlled by spec strings, which are provided by a plain-text spec file.
The dark side of RISC-V linker relaxation
Updated in 2025-07.
This article introduces RISC-V linker relaxation and describes the downside.
Linker optimization/relaxation
Because the linker has a global view and layout information, it can perform some peephole optimizations which are difficult/impossible to do on the compiler side. Generic link-time code sequence transformation is risky, because semantic information is lost and what the linker sees are byte streams. However, if every instruction in the candidate code sequence is associated with one ore more relocations, the ABI and the implementation can assign (additional) semantics to the relocation types and make such transformation safe. This technique is usually called linker optimization or linker relaxation. It seems that the term "linker optimization" is often used when the number of bytes does not change while "linker relaxation" is used when the number of bytes decreases.
…Linker garbage collection
A program may have a lot of unused code and data. There can be many reasons:
…Metadata sections, COMDAT and SHF_LINK_ORDER
Updated in 2025-09.
Modern compilers create metadata sections to support instrumentation, debugging, profiling, and other features.
These sections should be retained when their associated code is kept and discarded when their code is removed-but achieving this requires careful linker considerations.
This post explores the evolution of solutions, from early workarounds to modern mechanisms like section groups and SHF_LINK_ORDER, examining their trade-offs and real-world applications.
Metadata sections: properties and categories
Metadata sections usually have the following property:
- Relocations within a metadata section refer to its associated text section or any other auxiliary metadata sections. In many applications, a metadata section doesn't need additional auxiliary sections.
- Text sections don't have relocations that point to their metadata section. In some cases, a text section might reference its metadata section, but this means that the text section can't be inlined into another.
LLD and GNU linker incompatibilities
Updated in 2024-06.
Subtitle: Is ld.lld a drop-in replacement for GNU ld?
The motivation for this article was someone challenging the "drop-in replacement" claim on LLD's website (the discussion was about Linux-like ELF toolchain):
LLD is a linker from the LLVM project that is a drop-in replacement for system linkers and runs much faster than them. It also provides features that are useful for toolchain developers.
99.9% pieces of software work with ld.lld without a change. Some linker script applications may need an adaption (such adaption is oftentimes due to brittle assumptions: asking too much from GNU ld's behavior which should be fixed anyway). So I defended for this claim.
…C++ exception handling ABI
Updated in 2024-11.
I wrote an article a few weeks ago to introduce stack unwinding in detail. Today I will introduce C++ exception handling, an application of stack unwinding. Exception handling has a variety of ABI (interoperability of C++ implementations), the most widely used of which is Itanium C++ ABI: Exception Handling
…All about symbol versioning
Updated in 2026-01.
Many people just want to know how to define or reference versioned symbols properly. You may jump to Recommended usage below.
In 1995, Solaris' link editor and ld.so introduced the symbol versioning mechanism. Ulrich Drepper and Eric Youngdale borrowed Solaris' symbol versioning in 1997 and designed the GNU style symbol versioning for glibc.
gnu-gabi specification: https://sourceware.org/gnu-gabi/program-loading-and-dynamic-linking.txt#:~:text=versioning
…Stack unwinding
Update in 2025-11.
The main usage of stack unwinding is:
- To obtain a stack trace for debuggers, crash reporters, profilers, garbage collectors, etc.
- To implement C++ exceptions (Itanium C++ ABI) using personality routines and language-specific data area. See C++ exception handling ABI
Some people use "stack walking" to refer to obtaining a stack trace, while "stack unwinding" refers to an enhanced operation that also recovers callee-saved registers. In this post, we don't make the distinction.
…Command line processing in LLVM
Updated in 2023-05.
There are two libraries for processing command line options in LLVM.
llvm/Support/ComandLine.h
See https://llvm.org/docs/CommandLine.html for documentation.
Global variables (mostly llvm::cl::opt<type>, some llvm::cl::list<type>) are most common to represent command-line options.
The llvm::cl::opt constructor registers this command line option in a global registry.
The program calls llvm::cl::ParseCommandLineOptions(argc, argv, ...) in main to parse the command line options.
opt supports various integer types, bool, std::string, etc. Defining some specialization can support support custom class/enum types.
gcov与LLVM中的实现
打算以后不定期写一点LLVM的学习(开发)笔记。写作上不想过多花时间(加语文水平所限...),所以字句不作过多斟酌。
gcov
https://gcc.gnu.org/onlinedocs/gcc/Gcov.html
_Optimally Profiling and Tracing Programs_描述了一个edge-frequency/edge-placement problem。 选择control-flow graph的一些边,加上监控代码,推导所有边的执行次数。 gcov是一种实现。
在gcov的模型中,一个源文件包含若干函数,一个函数包含若干基本块,一个基本块占据若干行,这些信息保存在.gcno文件中。
Instrument程序,在基本块间转移时记录边的执行次数,程序退出时为每个translation unit输出一个.gcda文件。
.gcda文件可以累计多次程序执行的计数。
-fpatchable-function-entry=N[,M]
The Linux kernel uses many GCC options to support ftrace.
…