]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | // revisions: cfail1 cfail2 |
2 | // compile-flags: -O -Zhuman-readable-cgu-names -Cllvm-args=-import-instr-limit=10 | |
3 | // build-pass | |
4 | ||
5 | // rust-lang/rust#59535: | |
6 | // | |
7 | // Consider a call-graph like `[A] -> [B -> D] <- [C]` (where the letters are | |
8 | // functions and the modules are enclosed in `[]`) | |
9 | // | |
10 | // In our specific instance, the earlier compilations were inlining the call | |
94222f64 | 11 | // to`B` into `A`; thus `A` ended up with an external reference to the symbol `D` |
dfeec247 XL |
12 | // in its object code, to be resolved at subsequent link time. The LTO import |
13 | // information provided by LLVM for those runs reflected that information: it | |
14 | // explicitly says during those runs, `B` definition and `D` declaration were | |
15 | // imported into `[A]`. | |
16 | // | |
17 | // The change between incremental builds was that the call `D <- C` was removed. | |
18 | // | |
19 | // That change, coupled with other decisions within `rustc`, made the compiler | |
20 | // decide to make `D` an internal symbol (since it was no longer accessed from | |
21 | // other codegen units, this makes sense locally). And then the definition of | |
22 | // `D` was inlined into `B` and `D` itself was eliminated entirely. | |
23 | // | |
24 | // The current LTO import information reported that `B` alone is imported into | |
25 | // `[A]` for the *current compilation*. So when the Rust compiler surveyed the | |
26 | // dependence graph, it determined that nothing `[A]` imports changed since the | |
27 | // last build (and `[A]` itself has not changed either), so it chooses to reuse | |
28 | // the object code generated during the previous compilation. | |
29 | // | |
30 | // But that previous object code has an unresolved reference to `D`, and that | |
31 | // causes a link time failure! | |
32 | ||
33 | fn main() { | |
34 | foo::foo(); | |
35 | bar::baz(); | |
36 | } | |
37 | ||
38 | mod foo { | |
39 | ||
40 | // In cfail1, foo() gets inlined into main. | |
41 | // In cfail2, ThinLTO decides that foo() does not get inlined into main, and | |
42 | // instead bar() gets inlined into foo(). But faulty logic in our incr. | |
43 | // ThinLTO implementation thought that `main()` is unchanged and thus reused | |
74b04a01 | 44 | // the object file still containing a call to the now non-existent bar(). |
dfeec247 XL |
45 | pub fn foo(){ |
46 | bar() | |
47 | } | |
48 | ||
49 | // This function needs to be big so that it does not get inlined by ThinLTO | |
50 | // but *does* get inlined into foo() once it is declared `internal` in | |
51 | // cfail2. | |
52 | pub fn bar(){ | |
53 | println!("quux1"); | |
54 | println!("quux2"); | |
55 | println!("quux3"); | |
56 | println!("quux4"); | |
57 | println!("quux5"); | |
58 | println!("quux6"); | |
59 | println!("quux7"); | |
60 | println!("quux8"); | |
61 | println!("quux9"); | |
62 | } | |
63 | } | |
64 | ||
65 | mod bar { | |
66 | ||
67 | #[inline(never)] | |
68 | pub fn baz() { | |
69 | #[cfg(cfail1)] | |
70 | { | |
71 | crate::foo::bar(); | |
72 | } | |
73 | } | |
74 | } |