Skip to content

Cannot reproduce object on one architecture by cross-compiling on another with -flto=thin #139995

Open
@iskunk

Description

@iskunk

This is an issue discovered in the course of cross-compiling Chromium. I am attempting to reproduce, bit-for-bit, a native x86-64 Linux build of Chromium by cross-compiling it from a different Linux architecture. If I do not use LTO, then this works as intended: all the resulting x86-64 objects, libraries, and executables match exactly on both systems. If I specify -flto=thin, however, then I get different outputs on the two architectures, starting with .o files. The output is still deterministic within each architecture, but of course I want to see output that is the same irrespective of the build system.

I have verified this issue using the project's pre-compiled binaries of LLVM 20.1.5 for X64 and ARM64. I don't have an ARM hardware system, so I used a Docker ARM64 container running via QEMU. My test environment was Ubuntu 25.04/plucky, one instance on amd64, another on arm64. The latter has appropriate cross-compilation packages installed, e.g. libstdc++-14-dev-amd64-cross. (Note that I originally encountered this issue in a cross build from ppc64el, but no upstream LLVM binaries are available for that platform.)

Here is a small "kit" to reproduce the problem:

cross-test.zip

It uses a cut-down source file spinlock.cc (plus its dependencies) taken from Chromium, since the issue does not occur with a trivial "hello world"-type program. This file was chosen as it is relatively simple, and was among the first ones to come out differently between the two builds. There is a makefile with a small set of compiler flags that allow the preprocessed source output to match exactly regardless of whether the source is being compiled natively on x86-64, or cross-compiled from ARM64. Running make CXX=/path/to/clang++ will show the MD5-hashed results of compiling the source file.

Here is the output I see on the amd64 system:

$ make clean; make CXX=/opt/llvm/bin/clang++ 
rm -f spinlock spinlock.dep spinlock.ii spinlock.o
/opt/llvm/bin/clang++ --target=x86_64-unknown-linux-gnu -I. --no-line-commands -ffile-prefix-map=/usr/bin/../lib/gcc-cross/=/usr/bin/../lib/gcc/ -ffile-prefix-map=/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/=/usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../ -flto=thin -c spinlock.cc
/opt/llvm/bin/clang++ --target=x86_64-unknown-linux-gnu -I. --no-line-commands -ffile-prefix-map=/usr/bin/../lib/gcc-cross/=/usr/bin/../lib/gcc/ -ffile-prefix-map=/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/=/usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../ -flto=thin -E spinlock.cc >spinlock.ii
ls -l spinlock.o
-rw-r--r-- 1 build users 4276 May 15 03:06 spinlock.o
7eee0e28d709caf76f71a2b249eef3b8  spinlock.cc
3120df26bdad524adbb170fa4c635be7  spinlock.cc.orig
3737b72697300f721ef4c52ebfe109ed  spinlock.ii
2f2cc887fc8dee159ed401da301d387c  spinlock.o

And then on arm64:

$ make clean; make CXX=/opt/llvm/bin/clang++ 
rm -f spinlock spinlock.dep spinlock.ii spinlock.o
/opt/llvm/bin/clang++ --target=x86_64-unknown-linux-gnu -I. --no-line-commands -ffile-prefix-map=/usr/bin/../lib/gcc-cross/=/usr/bin/../lib/gcc/ -ffile-prefix-map=/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/=/usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../ -flto=thin -c spinlock.cc
/opt/llvm/bin/clang++ --target=x86_64-unknown-linux-gnu -I. --no-line-commands -ffile-prefix-map=/usr/bin/../lib/gcc-cross/=/usr/bin/../lib/gcc/ -ffile-prefix-map=/usr/bin/../lib/gcc-cross/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/=/usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../ -flto=thin -E spinlock.cc >spinlock.ii
ls -l spinlock.o
-rw-r--r-- 1 build users 4276 May 15 03:03 spinlock.o
7eee0e28d709caf76f71a2b249eef3b8  spinlock.cc
3120df26bdad524adbb170fa4c635be7  spinlock.cc.orig
3737b72697300f721ef4c52ebfe109ed  spinlock.ii
af549dab0aa1daafdfcec288defc4a72  spinlock.o

Notice how the preprocessed output (.ii) is identical on both systems, yet the compiled object (.o) is not. For inspection purposes, I've placed both object files in this archive:

spinlock-objects.zip

And in case it is needed, here is the preprocessed source:

spinlock.ii.txt

Metadata

Metadata

Assignees

No one assigned

    Labels

    LTOLink time optimization (regular/full LTO or ThinLTO)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions