Skip to content

Commit 6f70adc

Browse files
committed
Auto merge of #63352 - jgalenson:reproducible-lto, r=alexcrichton
Sort the fat LTO modules to produce deterministic output. Some projects that use LTO for their release builds are not reproducible. We can fix this by sorting the fat LTO modules before using them. It might also be useful to do this for thin LTO, but I couldn't get that to work to test it so I didn't do it.
2 parents d19a359 + b6767b3 commit 6f70adc

File tree

5 files changed

+209
-2
lines changed

5 files changed

+209
-2
lines changed

Diff for: src/librustc_codegen_llvm/back/lto.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ fn fat_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
265265
// and we want to move everything to the same LLVM context. Currently the
266266
// way we know of to do that is to serialize them to a string and them parse
267267
// them later. Not great but hey, that's why it's "fat" LTO, right?
268-
serialized_modules.extend(modules.into_iter().map(|module| {
268+
let mut new_modules = modules.into_iter().map(|module| {
269269
match module {
270270
FatLTOInput::InMemory(module) => {
271271
let buffer = ModuleBuffer::new(module.module_llvm.llmod());
@@ -277,7 +277,10 @@ fn fat_lto(cgcx: &CodegenContext<LlvmCodegenBackend>,
277277
(SerializedModule::Local(buffer), llmod_id)
278278
}
279279
}
280-
}));
280+
}).collect::<Vec<_>>();
281+
// Sort the modules to ensure we produce deterministic results.
282+
new_modules.sort_by(|module1, module2| module1.1.partial_cmp(&module2.1).unwrap());
283+
serialized_modules.extend(new_modules);
281284
serialized_modules.extend(cached_modules.into_iter().map(|(buffer, wp)| {
282285
(buffer, CString::new(wp.cgu_name).unwrap())
283286
}));
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-include ../tools.mk
2+
3+
# ignore-musl
4+
# ignore-windows
5+
# Objects are reproducible but their path is not.
6+
7+
all: \
8+
fat_lto
9+
10+
fat_lto:
11+
rm -rf $(TMPDIR) && mkdir $(TMPDIR)
12+
$(RUSTC) reproducible-build-aux.rs
13+
$(RUSTC) reproducible-build.rs -C lto=fat
14+
cp $(TMPDIR)/reproducible-build $(TMPDIR)/reproducible-build-a
15+
$(RUSTC) reproducible-build.rs -C lto=fat
16+
cmp "$(TMPDIR)/reproducible-build-a" "$(TMPDIR)/reproducible-build" || exit 1
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use std::env;
2+
use std::path::Path;
3+
use std::fs::File;
4+
use std::io::{Read, Write};
5+
6+
fn main() {
7+
let mut dst = env::current_exe().unwrap();
8+
dst.pop();
9+
dst.push("linker-arguments1");
10+
if dst.exists() {
11+
dst.pop();
12+
dst.push("linker-arguments2");
13+
assert!(!dst.exists());
14+
}
15+
16+
let mut out = String::new();
17+
for arg in env::args().skip(1) {
18+
let path = Path::new(&arg);
19+
if !path.is_file() {
20+
out.push_str(&arg);
21+
out.push_str("\n");
22+
continue
23+
}
24+
25+
let mut contents = Vec::new();
26+
File::open(path).unwrap().read_to_end(&mut contents).unwrap();
27+
28+
out.push_str(&format!("{}: {}\n", arg, hash(&contents)));
29+
}
30+
31+
File::create(dst).unwrap().write_all(out.as_bytes()).unwrap();
32+
}
33+
34+
// fnv hash for now
35+
fn hash(contents: &[u8]) -> u64 {
36+
let mut hash = 0xcbf29ce484222325;
37+
38+
for byte in contents {
39+
hash = hash ^ (*byte as u64);
40+
hash = hash.wrapping_mul(0x100000001b3);
41+
}
42+
43+
hash
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![crate_type="lib"]
2+
3+
pub static STATIC: i32 = 1234;
4+
5+
pub struct Struct<T1, T2> {
6+
_t1: std::marker::PhantomData<T1>,
7+
_t2: std::marker::PhantomData<T2>,
8+
}
9+
10+
pub fn regular_fn(_: i32) {}
11+
12+
pub fn generic_fn<T1, T2>() {}
13+
14+
impl<T1, T2> Drop for Struct<T1, T2> {
15+
fn drop(&mut self) {}
16+
}
17+
18+
pub enum Enum {
19+
Variant1,
20+
Variant2(u32),
21+
Variant3 { x: u32 }
22+
}
23+
24+
pub struct TupleStruct(pub i8, pub i16, pub i32, pub i64);
25+
26+
pub trait Trait<T1, T2> {
27+
fn foo(&self);
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// This test case makes sure that two identical invocations of the compiler
2+
// (i.e., same code base, same compile-flags, same compiler-versions, etc.)
3+
// produce the same output. In the past, symbol names of monomorphized functions
4+
// were not deterministic (which we want to avoid).
5+
//
6+
// The test tries to exercise as many different paths into symbol name
7+
// generation as possible:
8+
//
9+
// - regular functions
10+
// - generic functions
11+
// - methods
12+
// - statics
13+
// - closures
14+
// - enum variant constructors
15+
// - tuple struct constructors
16+
// - drop glue
17+
// - FnOnce adapters
18+
// - Trait object shims
19+
// - Fn Pointer shims
20+
21+
#![allow(dead_code, warnings)]
22+
23+
extern crate reproducible_build_aux;
24+
25+
static STATIC: i32 = 1234;
26+
27+
pub struct Struct<T1, T2> {
28+
x: T1,
29+
y: T2,
30+
}
31+
32+
fn regular_fn(_: i32) {}
33+
34+
fn generic_fn<T1, T2>() {}
35+
36+
impl<T1, T2> Drop for Struct<T1, T2> {
37+
fn drop(&mut self) {}
38+
}
39+
40+
pub enum Enum {
41+
Variant1,
42+
Variant2(u32),
43+
Variant3 { x: u32 }
44+
}
45+
46+
struct TupleStruct(i8, i16, i32, i64);
47+
48+
impl TupleStruct {
49+
pub fn bar(&self) {}
50+
}
51+
52+
trait Trait<T1, T2> {
53+
fn foo(&self);
54+
}
55+
56+
impl Trait<i32, u64> for u64 {
57+
fn foo(&self) {}
58+
}
59+
60+
impl reproducible_build_aux::Trait<char, String> for TupleStruct {
61+
fn foo(&self) {}
62+
}
63+
64+
fn main() {
65+
regular_fn(STATIC);
66+
generic_fn::<u32, char>();
67+
generic_fn::<char, Struct<u32, u64>>();
68+
generic_fn::<Struct<u64, u32>, reproducible_build_aux::Struct<u32, u64>>();
69+
70+
let dropped = Struct {
71+
x: "",
72+
y: 'a',
73+
};
74+
75+
let _ = Enum::Variant1;
76+
let _ = Enum::Variant2(0);
77+
let _ = Enum::Variant3 { x: 0 };
78+
let _ = TupleStruct(1, 2, 3, 4);
79+
80+
let closure = |x| {
81+
x + 1i32
82+
};
83+
84+
fn inner<F: Fn(i32) -> i32>(f: F) -> i32 {
85+
f(STATIC)
86+
}
87+
88+
println!("{}", inner(closure));
89+
90+
let object_shim: &Trait<i32, u64> = &0u64;
91+
object_shim.foo();
92+
93+
fn with_fn_once_adapter<F: FnOnce(i32)>(f: F) {
94+
f(0);
95+
}
96+
97+
with_fn_once_adapter(|_:i32| { });
98+
99+
reproducible_build_aux::regular_fn(STATIC);
100+
reproducible_build_aux::generic_fn::<u32, char>();
101+
reproducible_build_aux::generic_fn::<char, Struct<u32, u64>>();
102+
reproducible_build_aux::generic_fn::<Struct<u64, u32>,
103+
reproducible_build_aux::Struct<u32, u64>>();
104+
105+
let _ = reproducible_build_aux::Enum::Variant1;
106+
let _ = reproducible_build_aux::Enum::Variant2(0);
107+
let _ = reproducible_build_aux::Enum::Variant3 { x: 0 };
108+
let _ = reproducible_build_aux::TupleStruct(1, 2, 3, 4);
109+
110+
let object_shim: &reproducible_build_aux::Trait<char, String> = &TupleStruct(0, 1, 2, 3);
111+
object_shim.foo();
112+
113+
let pointer_shim: &Fn(i32) = &regular_fn;
114+
115+
TupleStruct(1, 2, 3, 4).bar();
116+
}

0 commit comments

Comments
 (0)