Mehdi Akiki
← Work

Upstream work · rust-lang/rust

Changing what goes inside a Rust rlib

A Rust library is an archive that the compiler reads at two different times: early, to compile the crates that depend on it, and late, when it links the final binary. Some facts were only needed at link time but lived in the early metadata, and some were guessed from file names. I worked on a series of compiler changes that move them into a new archive member that only the linking code reads.

How I came to this

I came to this work by reading the current compiler code: how rustc writes an rlib, reads it back, and links it. That is how I found the problem and started working on it.

For me, understanding Rust was more than learning Rust. It was a way to become a much better software engineer, interested in hard and practical problems.

The problem

An rlib is an ar archive. It contains the crate metadata that other crates compile against, the object files produced by code generation, and sometimes native libraries bundled into it.

When rustc needed to know which members are Rust object files, for example to read them for LTO, it looked at the member name: a Rust object file ends in .rcgu.o. But a bundled native library can put a member with any name into the archive, including one that ends in .rcgu.o. And facts that only the linker needs, like the filenames of bundled native libraries, were computed and decoded with the early metadata.

The tracking issue, opened by Vadim Petrochenkov, proposed one more metadata member for rlibs: link-time facts, read only when rustc links the rlib into something.

How far down it went

The work sits in the compiler backend and the archive format, not in language features: the archive writer and reader in rustc_codegen_ssa, the LTO readers of the LLVM and GCC backends, native library handling in rustc_metadata, and how the member is found in different object file formats.

The changes

Adds the lib.rmeta-link archive member, which lists the members that are Rust object files. The LTO readers of the LLVM and GCC backends read it instead of looking at file names, and the filename heuristic is deleted. The member sits right after lib.rmeta.

The filename of a bundled native library is only needed at link time, so it left the crate metadata. The member stores the filenames as a list aligned by position with the crate's native libraries, and only the linking code reads them.

#158194 · RmetaLinkCache

Merged June 28, 2026

A cache for one link, keyed by rlib path, so each lib.rmeta-link member is decoded at most once. It was split out of the review of the previous change after a performance run regressed.

After the move, one branch computed bundled library filenames for the local crate that nothing read. Cleanup only, no change in behavior.

On XCOFF the payload is written to a different section than on other formats, and the reader did not account for it. Review asked for the target's AIX flag instead of parsing the file, and one change remains before it can merge. It is not tested on AIX: CI has no AIX runner and I do not have access to a machine.

What review changed

The pull request descriptions describe the first versions. The merged code is different, and the differences are the interesting part.

  • No fallback for old rlibs. The first version kept the filename check for rlibs without the new member. The reviewers pointed out that an rlib only works with the compiler version that wrote it, so every rlib a new compiler reads has the member. The heuristic was deleted.
  • Right after the crate metadata, not at the end. The issue proposed writing the member last. In review it moved next to lib.rmeta.
  • Read only by the linking code. My first version of the native library change decoded the filenames through the metadata loader and patched them back. Review asked for the opposite: remove the field and read the member only during linking.
  • Position, not name, as the key. A library name is not a reliable key, so the filenames are stored in the same order as the crate's native libraries.
  • No metadata format version bump. I proposed one, and review showed the change did not need it.
  • A performance regression. A benchmark run regressed on a large workspace. The read is now skipped for crates without bundled libraries, and the cache became its own pull request.

Where the change went next

Once the member existed, other work started to use it. bjorn3 added global assembly objects to the list (#157051). Vadim Petrochenkov gave archive entries an explicit kind (#157263), which later work on hiding internal symbols in static libraries relies on, and moved native library search out of rustc_metadata (#160272), noting that the tracking issue was now partially implemented.

Not everything in the tracking issue is done. Bundling object files into the rlib for link-time cfg has not started, and the AIX read path is still open.

Review

The first change went through several review cycles with Vadim Petrochenkov and bjorn3 before it merged. The later changes were reviewed by Vadim Petrochenkov. The evidence for everything on this page is in the pull requests and their review threads.

What is next

A longer technical write-up is planned: the archive layout before and after, why the filename heuristic existed, and how each review comment changed the design.