`-Cpasses` transformations are missing from embedded bitcode with one codegen unit
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
Summary
When an rlib is compiled with -Cembed-bitcode=yes and a custom LLVM pass, the emitted object code reflects the pass but the embedded bitcode may not.
With -Ccodegen-units=1, the embedded bitcode is serialized before -Cpasses runs. With multiple codegen units, it is serialized after the pass.
As a result, a transformation requested through -Cpasses can silently disappear when another crate consumes the rlib through LTO.
Code
#![no_std]
use core::sync::atomic::{AtomicU64, Ordering};
#[unsafe(no_mangle)]
pub unsafe extern "C" fn atomic_add(ptr: *mut AtomicU64, value: u64) -> u64 {
unsafe { &*ptr }.fetch_add(value, Ordering::SeqCst)
}
Compile the same crate with one and two codegen units:
rustc repro.rs \
--edition=2024 \
--crate-type=rlib \
-O \
-Cembed-bitcode=yes \
-Cpasses=lower-atomic \
-Ccodegen-units=1 \
-o cgu1.rlib
rustc repro.rs \
--edition=2024 \
--crate-type=rlib \
-O \
-Cembed-bitcode=yes \
-Cpasses=lower-atomic \
-Ccodegen-units=2 \
-o cgu2.rlib
After extracting each rlib's object member, the embedded bitcode can be read with LLVM tools:
mkdir cgu1 cgu2
(cd cgu1 && llvm-ar x ../cgu1.rlib)
(cd cgu2 && llvm-ar x ../cgu2.rlib)
llvm-objcopy --dump-section .llvmbc=cgu1.bc cgu1/*.o
llvm-objcopy --dump-section .llvmbc=cgu2.bc cgu2/*.o
llvm-dis cgu1.bc -o cgu1.ll
llvm-dis cgu2.bc -o cgu2.ll
On Mach-O, the section name is __LLVM,__bitcode instead of .llvmbc:
llvm-objcopy --dump-section '__LLVM,__bitcode'=cgu1.bc cgu1/*.o
llvm-objcopy --dump-section '__LLVM,__bitcode'=cgu2.bc cgu2/*.o
The result is:
$ rg 'atomicrmw|cmpxchg|load atomic|store atomic' cgu1.ll
%3 = atomicrmw add ptr %0, i64 %1 seq_cst, align 8
$ rg 'atomicrmw|cmpxchg|load atomic|store atomic' cgu2.ll
# no output
The machine-code object is lowered in both cases. On AArch64, both objects contain the same non-atomic sequence:
ldr x8, [x0]
add x9, x8, x1
str x9, [x0]
The inconsistency is between the object code and the bitcode stored alongside it.
Expected behavior
The effect of a pre-link custom pass should be represented consistently in embedded LTO bitcode and should not depend on the number of codegen units.
In particular, because -Cpasses is intentionally not rerun during final LTO, its pre-link transformations need to be present in the bitcode supplied to LTO.
Regression
This worked with Rust 1.86.0: the one-CGU embedded bitcode contained no atomic operations.
It regressed in Rust 1.87.0. Rust 1.88.0 and later retain the atomicrmw in the one-CGU embedded bitcode.
The regression appears related to #133250, which moved embedded-bitcode serialization into the pre-link optimization pipeline.
In the PreLinkNoLTO path, ThinLTOBitcodeWriterPass is scheduled before ExtraPasses. In the pre-link ThinLTO path, the bitcode writer is scheduled after ExtraPasses. This appears to explain the codegen-unit-dependent result.
The final LTO stage does not repair the difference because #97969 intentionally made -Cpasses pre-link-only.
Version
Reproduced on the latest nightly available on 2026-07-18:
rustc 1.99.0-nightly (b6839f4d0 2026-07-17)
binary: rustc
commit-hash: b6839f4d0e2bd63b960bbff8619c6fdea27d81e5
commit-date: 2026-07-17
host: aarch64-apple-darwin
release: 1.99.0-nightly
LLVM version: 22.1.8
@rustbot modify labels: +regression-from-stable-to-stable -regression-untriaged
cc @DianQK, since this appears related to the embedded-bitcode pipeline added in #133250.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the one- and two-codegen-unit commands, extract the embedded bitcode with llvm-ar, llvm-objcopy, and llvm-dis, and compare the results. Trace the PreLinkNoLTO and pre-link ThinLTO scheduling around ThinLTOBitcodeWriterPass and ExtraPasses; done means -Cpasses transformations are represented consistently in embedded bitcode regardless of codegen-unit count.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 43/100