| //@ revisions: cfail1 cfail2 | |
| //@ compile-flags: -O -Zhuman-readable-cgu-names -Cllvm-args=-import-instr-limit=10 | |
| //@ build-pass | |
| //@ ignore-backends: gcc | |
| // rust-lang/rust#59535: | |
| // | |
| // Consider a call-graph like `[A] -> [B -> D] <- [C]` (where the letters are | |
| // functions and the modules are enclosed in `[]`) | |
| // | |
| // In our specific instance, the earlier compilations were inlining the call | |
| // to`B` into `A`; thus `A` ended up with an external reference to the symbol `D` | |
| // in its object code, to be resolved at subsequent link time. The LTO import | |
| // information provided by LLVM for those runs reflected that information: it | |
| // explicitly says during those runs, `B` definition and `D` declaration were | |
| // imported into `[A]`. | |
| // | |
| // The change between incremental builds was that the call `D <- C` was removed. | |
| // | |
| // That change, coupled with other decisions within `rustc`, made the compiler | |
| // decide to make `D` an internal symbol (since it was no longer accessed from | |
| // other codegen units, this makes sense locally). And then the definition of | |
| // `D` was inlined into `B` and `D` itself was eliminated entirely. | |
| // | |
| // The current LTO import information reported that `B` alone is imported into | |
| // `[A]` for the *current compilation*. So when the Rust compiler surveyed the | |
| // dependence graph, it determined that nothing `[A]` imports changed since the | |
| // last build (and `[A]` itself has not changed either), so it chooses to reuse | |
| // the object code generated during the previous compilation. | |
| // | |
| // But that previous object code has an unresolved reference to `D`, and that | |
| // causes a link time failure! | |
| fn main() { | |
| foo::foo(); | |
| bar::baz(); | |
| } | |
| mod foo { | |
| // In cfail1, foo() gets inlined into main. | |
| // In cfail2, ThinLTO decides that foo() does not get inlined into main, and | |
| // instead bar() gets inlined into foo(). But faulty logic in our incr. | |
| // ThinLTO implementation thought that `main()` is unchanged and thus reused | |
| // the object file still containing a call to the now non-existent bar(). | |
| pub fn foo(){ | |
| bar() | |
| } | |
| // This function needs to be big so that it does not get inlined by ThinLTO | |
| // but *does* get inlined into foo() once it is declared `internal` in | |
| // cfail2. | |
| pub fn bar(){ | |
| println!("quux1"); | |
| println!("quux2"); | |
| println!("quux3"); | |
| println!("quux4"); | |
| println!("quux5"); | |
| println!("quux6"); | |
| println!("quux7"); | |
| println!("quux8"); | |
| println!("quux9"); | |
| } | |
| } | |
| mod bar { | |
| #[inline(never)] | |
| pub fn baz() { | |
| #[cfg(cfail1)] | |
| { | |
| crate::foo::bar(); | |
| } | |
| } | |
| } |