]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_monomorphize/src/partitioning/mod.rs
Auto merge of #106519 - estebank:tail-unit, r=cjgillot
[rust.git] / compiler / rustc_monomorphize / src / partitioning / mod.rs
1 //! Partitioning Codegen Units for Incremental Compilation
2 //! ======================================================
3 //!
4 //! The task of this module is to take the complete set of monomorphizations of
5 //! a crate and produce a set of codegen units from it, where a codegen unit
6 //! is a named set of (mono-item, linkage) pairs. That is, this module
7 //! decides which monomorphization appears in which codegen units with which
8 //! linkage. The following paragraphs describe some of the background on the
9 //! partitioning scheme.
10 //!
11 //! The most important opportunity for saving on compilation time with
12 //! incremental compilation is to avoid re-codegenning and re-optimizing code.
13 //! Since the unit of codegen and optimization for LLVM is "modules" or, how
14 //! we call them "codegen units", the particulars of how much time can be saved
15 //! by incremental compilation are tightly linked to how the output program is
16 //! partitioned into these codegen units prior to passing it to LLVM --
17 //! especially because we have to treat codegen units as opaque entities once
18 //! they are created: There is no way for us to incrementally update an existing
19 //! LLVM module and so we have to build any such module from scratch if it was
20 //! affected by some change in the source code.
21 //!
22 //! From that point of view it would make sense to maximize the number of
23 //! codegen units by, for example, putting each function into its own module.
24 //! That way only those modules would have to be re-compiled that were actually
25 //! affected by some change, minimizing the number of functions that could have
26 //! been re-used but just happened to be located in a module that is
27 //! re-compiled.
28 //!
29 //! However, since LLVM optimization does not work across module boundaries,
30 //! using such a highly granular partitioning would lead to very slow runtime
31 //! code since it would effectively prohibit inlining and other inter-procedure
32 //! optimizations. We want to avoid that as much as possible.
33 //!
34 //! Thus we end up with a trade-off: The bigger the codegen units, the better
35 //! LLVM's optimizer can do its work, but also the smaller the compilation time
36 //! reduction we get from incremental compilation.
37 //!
38 //! Ideally, we would create a partitioning such that there are few big codegen
39 //! units with few interdependencies between them. For now though, we use the
40 //! following heuristic to determine the partitioning:
41 //!
42 //! - There are two codegen units for every source-level module:
43 //! - One for "stable", that is non-generic, code
44 //! - One for more "volatile" code, i.e., monomorphized instances of functions
45 //!   defined in that module
46 //!
47 //! In order to see why this heuristic makes sense, let's take a look at when a
48 //! codegen unit can get invalidated:
49 //!
50 //! 1. The most straightforward case is when the BODY of a function or global
51 //! changes. Then any codegen unit containing the code for that item has to be
52 //! re-compiled. Note that this includes all codegen units where the function
53 //! has been inlined.
54 //!
55 //! 2. The next case is when the SIGNATURE of a function or global changes. In
56 //! this case, all codegen units containing a REFERENCE to that item have to be
57 //! re-compiled. This is a superset of case 1.
58 //!
59 //! 3. The final and most subtle case is when a REFERENCE to a generic function
60 //! is added or removed somewhere. Even though the definition of the function
61 //! might be unchanged, a new REFERENCE might introduce a new monomorphized
62 //! instance of this function which has to be placed and compiled somewhere.
63 //! Conversely, when removing a REFERENCE, it might have been the last one with
64 //! that particular set of generic arguments and thus we have to remove it.
65 //!
66 //! From the above we see that just using one codegen unit per source-level
67 //! module is not such a good idea, since just adding a REFERENCE to some
68 //! generic item somewhere else would invalidate everything within the module
69 //! containing the generic item. The heuristic above reduces this detrimental
70 //! side-effect of references a little by at least not touching the non-generic
71 //! code of the module.
72 //!
73 //! A Note on Inlining
74 //! ------------------
75 //! As briefly mentioned above, in order for LLVM to be able to inline a
76 //! function call, the body of the function has to be available in the LLVM
77 //! module where the call is made. This has a few consequences for partitioning:
78 //!
79 //! - The partitioning algorithm has to take care of placing functions into all
80 //!   codegen units where they should be available for inlining. It also has to
81 //!   decide on the correct linkage for these functions.
82 //!
83 //! - The partitioning algorithm has to know which functions are likely to get
84 //!   inlined, so it can distribute function instantiations accordingly. Since
85 //!   there is no way of knowing for sure which functions LLVM will decide to
86 //!   inline in the end, we apply a heuristic here: Only functions marked with
87 //!   `#[inline]` are considered for inlining by the partitioner. The current
88 //!   implementation will not try to determine if a function is likely to be
89 //!   inlined by looking at the functions definition.
90 //!
91 //! Note though that as a side-effect of creating a codegen units per
92 //! source-level module, functions from the same module will be available for
93 //! inlining, even when they are not marked `#[inline]`.
94
95 mod default;
96 mod merging;
97
98 use std::cmp;
99 use std::fs::{self, File};
100 use std::io::{BufWriter, Write};
101 use std::path::{Path, PathBuf};
102
103 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
104 use rustc_data_structures::sync;
105 use rustc_hir::def_id::{DefIdSet, LOCAL_CRATE};
106 use rustc_middle::mir;
107 use rustc_middle::mir::mono::MonoItem;
108 use rustc_middle::mir::mono::{CodegenUnit, Linkage};
109 use rustc_middle::ty::print::with_no_trimmed_paths;
110 use rustc_middle::ty::query::Providers;
111 use rustc_middle::ty::TyCtxt;
112 use rustc_session::config::{DumpMonoStatsFormat, SwitchWithOptPath};
113 use rustc_span::symbol::Symbol;
114
115 use crate::collector::InliningMap;
116 use crate::collector::{self, MonoItemCollectionMode};
117 use crate::errors::{CouldntDumpMonoStats, SymbolAlreadyDefined, UnknownPartitionStrategy};
118
119 pub struct PartitioningCx<'a, 'tcx> {
120     tcx: TyCtxt<'tcx>,
121     target_cgu_count: usize,
122     inlining_map: &'a InliningMap<'tcx>,
123 }
124
125 trait Partitioner<'tcx> {
126     fn place_root_mono_items(
127         &mut self,
128         cx: &PartitioningCx<'_, 'tcx>,
129         mono_items: &mut dyn Iterator<Item = MonoItem<'tcx>>,
130     ) -> PreInliningPartitioning<'tcx>;
131
132     fn merge_codegen_units(
133         &mut self,
134         cx: &PartitioningCx<'_, 'tcx>,
135         initial_partitioning: &mut PreInliningPartitioning<'tcx>,
136     );
137
138     fn place_inlined_mono_items(
139         &mut self,
140         cx: &PartitioningCx<'_, 'tcx>,
141         initial_partitioning: PreInliningPartitioning<'tcx>,
142     ) -> PostInliningPartitioning<'tcx>;
143
144     fn internalize_symbols(
145         &mut self,
146         cx: &PartitioningCx<'_, 'tcx>,
147         partitioning: &mut PostInliningPartitioning<'tcx>,
148     );
149 }
150
151 fn get_partitioner<'tcx>(tcx: TyCtxt<'tcx>) -> Box<dyn Partitioner<'tcx>> {
152     let strategy = match &tcx.sess.opts.unstable_opts.cgu_partitioning_strategy {
153         None => "default",
154         Some(s) => &s[..],
155     };
156
157     match strategy {
158         "default" => Box::new(default::DefaultPartitioning),
159         _ => {
160             tcx.sess.emit_fatal(UnknownPartitionStrategy);
161         }
162     }
163 }
164
165 pub fn partition<'tcx>(
166     tcx: TyCtxt<'tcx>,
167     mono_items: &mut dyn Iterator<Item = MonoItem<'tcx>>,
168     max_cgu_count: usize,
169     inlining_map: &InliningMap<'tcx>,
170 ) -> Vec<CodegenUnit<'tcx>> {
171     let _prof_timer = tcx.prof.generic_activity("cgu_partitioning");
172
173     let mut partitioner = get_partitioner(tcx);
174     let cx = &PartitioningCx { tcx, target_cgu_count: max_cgu_count, inlining_map };
175     // In the first step, we place all regular monomorphizations into their
176     // respective 'home' codegen unit. Regular monomorphizations are all
177     // functions and statics defined in the local crate.
178     let mut initial_partitioning = {
179         let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_roots");
180         partitioner.place_root_mono_items(cx, mono_items)
181     };
182
183     initial_partitioning.codegen_units.iter_mut().for_each(|cgu| cgu.estimate_size(tcx));
184
185     debug_dump(tcx, "INITIAL PARTITIONING:", initial_partitioning.codegen_units.iter());
186
187     // Merge until we have at most `max_cgu_count` codegen units.
188     {
189         let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_merge_cgus");
190         partitioner.merge_codegen_units(cx, &mut initial_partitioning);
191         debug_dump(tcx, "POST MERGING:", initial_partitioning.codegen_units.iter());
192     }
193
194     // In the next step, we use the inlining map to determine which additional
195     // monomorphizations have to go into each codegen unit. These additional
196     // monomorphizations can be drop-glue, functions from external crates, and
197     // local functions the definition of which is marked with `#[inline]`.
198     let mut post_inlining = {
199         let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_inline_items");
200         partitioner.place_inlined_mono_items(cx, initial_partitioning)
201     };
202
203     post_inlining.codegen_units.iter_mut().for_each(|cgu| cgu.estimate_size(tcx));
204
205     debug_dump(tcx, "POST INLINING:", post_inlining.codegen_units.iter());
206
207     // Next we try to make as many symbols "internal" as possible, so LLVM has
208     // more freedom to optimize.
209     if !tcx.sess.link_dead_code() {
210         let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_internalize_symbols");
211         partitioner.internalize_symbols(cx, &mut post_inlining);
212     }
213
214     let instrument_dead_code =
215         tcx.sess.instrument_coverage() && !tcx.sess.instrument_coverage_except_unused_functions();
216
217     if instrument_dead_code {
218         assert!(
219             post_inlining.codegen_units.len() > 0,
220             "There must be at least one CGU that code coverage data can be generated in."
221         );
222
223         // Find the smallest CGU that has exported symbols and put the dead
224         // function stubs in that CGU. We look for exported symbols to increase
225         // the likelihood the linker won't throw away the dead functions.
226         // FIXME(#92165): In order to truly resolve this, we need to make sure
227         // the object file (CGU) containing the dead function stubs is included
228         // in the final binary. This will probably require forcing these
229         // function symbols to be included via `-u` or `/include` linker args.
230         let mut cgus: Vec<_> = post_inlining.codegen_units.iter_mut().collect();
231         cgus.sort_by_key(|cgu| cgu.size_estimate());
232
233         let dead_code_cgu =
234             if let Some(cgu) = cgus.into_iter().rev().find(|cgu| {
235                 cgu.items().iter().any(|(_, (linkage, _))| *linkage == Linkage::External)
236             }) {
237                 cgu
238             } else {
239                 // If there are no CGUs that have externally linked items,
240                 // then we just pick the first CGU as a fallback.
241                 &mut post_inlining.codegen_units[0]
242             };
243         dead_code_cgu.make_code_coverage_dead_code_cgu();
244     }
245
246     // Finally, sort by codegen unit name, so that we get deterministic results.
247     let PostInliningPartitioning {
248         codegen_units: mut result,
249         mono_item_placements: _,
250         internalization_candidates: _,
251     } = post_inlining;
252
253     result.sort_by(|a, b| a.name().as_str().partial_cmp(b.name().as_str()).unwrap());
254
255     result
256 }
257
258 pub struct PreInliningPartitioning<'tcx> {
259     codegen_units: Vec<CodegenUnit<'tcx>>,
260     roots: FxHashSet<MonoItem<'tcx>>,
261     internalization_candidates: FxHashSet<MonoItem<'tcx>>,
262 }
263
264 /// For symbol internalization, we need to know whether a symbol/mono-item is
265 /// accessed from outside the codegen unit it is defined in. This type is used
266 /// to keep track of that.
267 #[derive(Clone, PartialEq, Eq, Debug)]
268 enum MonoItemPlacement {
269     SingleCgu { cgu_name: Symbol },
270     MultipleCgus,
271 }
272
273 struct PostInliningPartitioning<'tcx> {
274     codegen_units: Vec<CodegenUnit<'tcx>>,
275     mono_item_placements: FxHashMap<MonoItem<'tcx>, MonoItemPlacement>,
276     internalization_candidates: FxHashSet<MonoItem<'tcx>>,
277 }
278
279 fn debug_dump<'a, 'tcx, I>(tcx: TyCtxt<'tcx>, label: &str, cgus: I)
280 where
281     I: Iterator<Item = &'a CodegenUnit<'tcx>>,
282     'tcx: 'a,
283 {
284     let dump = move || {
285         use std::fmt::Write;
286
287         let s = &mut String::new();
288         let _ = writeln!(s, "{label}");
289         for cgu in cgus {
290             let _ =
291                 writeln!(s, "CodegenUnit {} estimated size {} :", cgu.name(), cgu.size_estimate());
292
293             for (mono_item, linkage) in cgu.items() {
294                 let symbol_name = mono_item.symbol_name(tcx).name;
295                 let symbol_hash_start = symbol_name.rfind('h');
296                 let symbol_hash = symbol_hash_start.map_or("<no hash>", |i| &symbol_name[i..]);
297
298                 let _ = writeln!(
299                     s,
300                     " - {} [{:?}] [{}] estimated size {}",
301                     mono_item,
302                     linkage,
303                     symbol_hash,
304                     mono_item.size_estimate(tcx)
305                 );
306             }
307
308             let _ = writeln!(s);
309         }
310
311         std::mem::take(s)
312     };
313
314     debug!("{}", dump());
315 }
316
317 #[inline(never)] // give this a place in the profiler
318 fn assert_symbols_are_distinct<'a, 'tcx, I>(tcx: TyCtxt<'tcx>, mono_items: I)
319 where
320     I: Iterator<Item = &'a MonoItem<'tcx>>,
321     'tcx: 'a,
322 {
323     let _prof_timer = tcx.prof.generic_activity("assert_symbols_are_distinct");
324
325     let mut symbols: Vec<_> =
326         mono_items.map(|mono_item| (mono_item, mono_item.symbol_name(tcx))).collect();
327
328     symbols.sort_by_key(|sym| sym.1);
329
330     for &[(mono_item1, ref sym1), (mono_item2, ref sym2)] in symbols.array_windows() {
331         if sym1 == sym2 {
332             let span1 = mono_item1.local_span(tcx);
333             let span2 = mono_item2.local_span(tcx);
334
335             // Deterministically select one of the spans for error reporting
336             let span = match (span1, span2) {
337                 (Some(span1), Some(span2)) => {
338                     Some(if span1.lo().0 > span2.lo().0 { span1 } else { span2 })
339                 }
340                 (span1, span2) => span1.or(span2),
341             };
342
343             tcx.sess.emit_fatal(SymbolAlreadyDefined { span, symbol: sym1.to_string() });
344         }
345     }
346 }
347
348 fn collect_and_partition_mono_items(tcx: TyCtxt<'_>, (): ()) -> (&DefIdSet, &[CodegenUnit<'_>]) {
349     let collection_mode = match tcx.sess.opts.unstable_opts.print_mono_items {
350         Some(ref s) => {
351             let mode_string = s.to_lowercase();
352             let mode_string = mode_string.trim();
353             if mode_string == "eager" {
354                 MonoItemCollectionMode::Eager
355             } else {
356                 if mode_string != "lazy" {
357                     let message = format!(
358                         "Unknown codegen-item collection mode '{mode_string}'. \
359                                            Falling back to 'lazy' mode."
360                     );
361                     tcx.sess.warn(&message);
362                 }
363
364                 MonoItemCollectionMode::Lazy
365             }
366         }
367         None => {
368             if tcx.sess.link_dead_code() {
369                 MonoItemCollectionMode::Eager
370             } else {
371                 MonoItemCollectionMode::Lazy
372             }
373         }
374     };
375
376     let (items, inlining_map) = collector::collect_crate_mono_items(tcx, collection_mode);
377
378     tcx.sess.abort_if_errors();
379
380     let (codegen_units, _) = tcx.sess.time("partition_and_assert_distinct_symbols", || {
381         sync::join(
382             || {
383                 let mut codegen_units = partition(
384                     tcx,
385                     &mut items.iter().cloned(),
386                     tcx.sess.codegen_units(),
387                     &inlining_map,
388                 );
389                 codegen_units[0].make_primary();
390                 &*tcx.arena.alloc_from_iter(codegen_units)
391             },
392             || assert_symbols_are_distinct(tcx, items.iter()),
393         )
394     });
395
396     if tcx.prof.enabled() {
397         // Record CGU size estimates for self-profiling.
398         for cgu in codegen_units {
399             tcx.prof.artifact_size(
400                 "codegen_unit_size_estimate",
401                 cgu.name().as_str(),
402                 cgu.size_estimate() as u64,
403             );
404         }
405     }
406
407     let mono_items: DefIdSet = items
408         .iter()
409         .filter_map(|mono_item| match *mono_item {
410             MonoItem::Fn(ref instance) => Some(instance.def_id()),
411             MonoItem::Static(def_id) => Some(def_id),
412             _ => None,
413         })
414         .collect();
415
416     // Output monomorphization stats per def_id
417     if let SwitchWithOptPath::Enabled(ref path) = tcx.sess.opts.unstable_opts.dump_mono_stats {
418         if let Err(err) =
419             dump_mono_items_stats(tcx, &codegen_units, path, tcx.crate_name(LOCAL_CRATE))
420         {
421             tcx.sess.emit_fatal(CouldntDumpMonoStats { error: err.to_string() });
422         }
423     }
424
425     if tcx.sess.opts.unstable_opts.print_mono_items.is_some() {
426         let mut item_to_cgus: FxHashMap<_, Vec<_>> = Default::default();
427
428         for cgu in codegen_units {
429             for (&mono_item, &linkage) in cgu.items() {
430                 item_to_cgus.entry(mono_item).or_default().push((cgu.name(), linkage));
431             }
432         }
433
434         let mut item_keys: Vec<_> = items
435             .iter()
436             .map(|i| {
437                 let mut output = with_no_trimmed_paths!(i.to_string());
438                 output.push_str(" @@");
439                 let mut empty = Vec::new();
440                 let cgus = item_to_cgus.get_mut(i).unwrap_or(&mut empty);
441                 cgus.sort_by_key(|(name, _)| *name);
442                 cgus.dedup();
443                 for &(ref cgu_name, (linkage, _)) in cgus.iter() {
444                     output.push(' ');
445                     output.push_str(cgu_name.as_str());
446
447                     let linkage_abbrev = match linkage {
448                         Linkage::External => "External",
449                         Linkage::AvailableExternally => "Available",
450                         Linkage::LinkOnceAny => "OnceAny",
451                         Linkage::LinkOnceODR => "OnceODR",
452                         Linkage::WeakAny => "WeakAny",
453                         Linkage::WeakODR => "WeakODR",
454                         Linkage::Appending => "Appending",
455                         Linkage::Internal => "Internal",
456                         Linkage::Private => "Private",
457                         Linkage::ExternalWeak => "ExternalWeak",
458                         Linkage::Common => "Common",
459                     };
460
461                     output.push('[');
462                     output.push_str(linkage_abbrev);
463                     output.push(']');
464                 }
465                 output
466             })
467             .collect();
468
469         item_keys.sort();
470
471         for item in item_keys {
472             println!("MONO_ITEM {item}");
473         }
474     }
475
476     (tcx.arena.alloc(mono_items), codegen_units)
477 }
478
479 /// Outputs stats about instantation counts and estimated size, per `MonoItem`'s
480 /// def, to a file in the given output directory.
481 fn dump_mono_items_stats<'tcx>(
482     tcx: TyCtxt<'tcx>,
483     codegen_units: &[CodegenUnit<'tcx>],
484     output_directory: &Option<PathBuf>,
485     crate_name: Symbol,
486 ) -> Result<(), Box<dyn std::error::Error>> {
487     let output_directory = if let Some(ref directory) = output_directory {
488         fs::create_dir_all(directory)?;
489         directory
490     } else {
491         Path::new(".")
492     };
493
494     let format = tcx.sess.opts.unstable_opts.dump_mono_stats_format;
495     let ext = format.extension();
496     let filename = format!("{crate_name}.mono_items.{ext}");
497     let output_path = output_directory.join(&filename);
498     let file = File::create(&output_path)?;
499     let mut file = BufWriter::new(file);
500
501     // Gather instantiated mono items grouped by def_id
502     let mut items_per_def_id: FxHashMap<_, Vec<_>> = Default::default();
503     for cgu in codegen_units {
504         for (&mono_item, _) in cgu.items() {
505             // Avoid variable-sized compiler-generated shims
506             if mono_item.is_user_defined() {
507                 items_per_def_id.entry(mono_item.def_id()).or_default().push(mono_item);
508             }
509         }
510     }
511
512     #[derive(serde::Serialize)]
513     struct MonoItem {
514         name: String,
515         instantiation_count: usize,
516         size_estimate: usize,
517         total_estimate: usize,
518     }
519
520     // Output stats sorted by total instantiated size, from heaviest to lightest
521     let mut stats: Vec<_> = items_per_def_id
522         .into_iter()
523         .map(|(def_id, items)| {
524             let name = with_no_trimmed_paths!(tcx.def_path_str(def_id));
525             let instantiation_count = items.len();
526             let size_estimate = items[0].size_estimate(tcx);
527             let total_estimate = instantiation_count * size_estimate;
528             MonoItem { name, instantiation_count, size_estimate, total_estimate }
529         })
530         .collect();
531     stats.sort_unstable_by_key(|item| cmp::Reverse(item.total_estimate));
532
533     if !stats.is_empty() {
534         match format {
535             DumpMonoStatsFormat::Json => serde_json::to_writer(file, &stats)?,
536             DumpMonoStatsFormat::Markdown => {
537                 writeln!(
538                     file,
539                     "| Item | Instantiation count | Estimated Cost Per Instantiation | Total Estimated Cost |"
540                 )?;
541                 writeln!(file, "| --- | ---: | ---: | ---: |")?;
542
543                 for MonoItem { name, instantiation_count, size_estimate, total_estimate } in stats {
544                     writeln!(
545                         file,
546                         "| `{name}` | {instantiation_count} | {size_estimate} | {total_estimate} |"
547                     )?;
548                 }
549             }
550         }
551     }
552
553     Ok(())
554 }
555
556 fn codegened_and_inlined_items(tcx: TyCtxt<'_>, (): ()) -> &DefIdSet {
557     let (items, cgus) = tcx.collect_and_partition_mono_items(());
558     let mut visited = DefIdSet::default();
559     let mut result = items.clone();
560
561     for cgu in cgus {
562         for (item, _) in cgu.items() {
563             if let MonoItem::Fn(ref instance) = item {
564                 let did = instance.def_id();
565                 if !visited.insert(did) {
566                     continue;
567                 }
568                 let body = tcx.instance_mir(instance.def);
569                 for block in body.basic_blocks.iter() {
570                     for statement in &block.statements {
571                         let mir::StatementKind::Coverage(_) = statement.kind else { continue };
572                         let scope = statement.source_info.scope;
573                         if let Some(inlined) = scope.inlined_instance(&body.source_scopes) {
574                             result.insert(inlined.def_id());
575                         }
576                     }
577                 }
578             }
579         }
580     }
581
582     tcx.arena.alloc(result)
583 }
584
585 pub fn provide(providers: &mut Providers) {
586     providers.collect_and_partition_mono_items = collect_and_partition_mono_items;
587     providers.codegened_and_inlined_items = codegened_and_inlined_items;
588
589     providers.is_codegened_item = |tcx, def_id| {
590         let (all_mono_items, _) = tcx.collect_and_partition_mono_items(());
591         all_mono_items.contains(&def_id)
592     };
593
594     providers.codegen_unit = |tcx, name| {
595         let (_, all) = tcx.collect_and_partition_mono_items(());
596         all.iter()
597             .find(|cgu| cgu.name() == name)
598             .unwrap_or_else(|| panic!("failed to find cgu with name {name:?}"))
599     };
600 }