|
| 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 |
| 11 | +// to`B` into `A`; thus `A` ended up with a external reference to the symbol `D` |
| 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 |
| 44 | + // the object file still containing a call to the now non-existant bar(). |
| 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 | +} |
0 commit comments