rustc_codegen_ssa/
assert_module_sources.rs

1//! This pass is only used for UNIT TESTS related to incremental
2//! compilation. It tests whether a particular `.o` file will be re-used
3//! from a previous compilation or whether it must be regenerated.
4//!
5//! The user adds annotations to the crate of the following form:
6//!
7//! ```
8//! # #![feature(rustc_attrs)]
9//! # #![allow(internal_features)]
10//! #![rustc_partition_reused(module="spike", cfg="rpass2")]
11//! #![rustc_partition_codegened(module="spike-x", cfg="rpass2")]
12//! ```
13//!
14//! The first indicates (in the cfg `rpass2`) that `spike.o` will be
15//! reused, the second that `spike-x.o` will be recreated. If these
16//! annotations are inaccurate, errors are reported.
17//!
18//! The reason that we use `cfg=...` and not `#[cfg_attr]` is so that
19//! the HIR doesn't change as a result of the annotations, which might
20//! perturb the reuse results.
21//!
22//! `#![rustc_expected_cgu_reuse(module="spike", cfg="rpass2", kind="post-lto")]`
23//! allows for doing a more fine-grained check to see if pre- or post-lto data
24//! was re-used.
25
26use std::borrow::Cow;
27use std::fmt;
28
29use rustc_data_structures::unord::{UnordMap, UnordSet};
30use rustc_errors::{DiagArgValue, IntoDiagArg};
31use rustc_hir as hir;
32use rustc_hir::def_id::LOCAL_CRATE;
33use rustc_middle::mir::mono::CodegenUnitNameBuilder;
34use rustc_middle::ty::TyCtxt;
35use rustc_session::Session;
36use rustc_span::{Span, Symbol, sym};
37use thin_vec::ThinVec;
38use tracing::debug;
39
40use crate::errors;
41
42#[allow(missing_docs)]
43pub fn assert_module_sources(tcx: TyCtxt<'_>, set_reuse: &dyn Fn(&mut CguReuseTracker)) {
44    tcx.dep_graph.with_ignore(|| {
45        if tcx.sess.opts.incremental.is_none() {
46            return;
47        }
48
49        let available_cgus = tcx
50            .collect_and_partition_mono_items(())
51            .codegen_units
52            .iter()
53            .map(|cgu| cgu.name())
54            .collect();
55
56        let mut ams = AssertModuleSource {
57            tcx,
58            available_cgus,
59            cgu_reuse_tracker: if tcx.sess.opts.unstable_opts.query_dep_graph {
60                CguReuseTracker::new()
61            } else {
62                CguReuseTracker::new_disabled()
63            },
64        };
65
66        for attr in tcx.hir_attrs(rustc_hir::CRATE_HIR_ID) {
67            ams.check_attr(attr);
68        }
69
70        set_reuse(&mut ams.cgu_reuse_tracker);
71
72        if tcx.sess.opts.unstable_opts.print_mono_items
73            && let Some(data) = &ams.cgu_reuse_tracker.data
74        {
75            data.actual_reuse.items().all(|(cgu, reuse)| {
76                println!("CGU_REUSE {cgu} {reuse}");
77                true
78            });
79        }
80
81        ams.cgu_reuse_tracker.check_expected_reuse(tcx.sess);
82    });
83}
84
85struct AssertModuleSource<'tcx> {
86    tcx: TyCtxt<'tcx>,
87    available_cgus: UnordSet<Symbol>,
88    cgu_reuse_tracker: CguReuseTracker,
89}
90
91impl<'tcx> AssertModuleSource<'tcx> {
92    fn check_attr(&mut self, attr: &hir::Attribute) {
93        let (expected_reuse, comp_kind) = if attr.has_name(sym::rustc_partition_reused) {
94            (CguReuse::PreLto, ComparisonKind::AtLeast)
95        } else if attr.has_name(sym::rustc_partition_codegened) {
96            (CguReuse::No, ComparisonKind::Exact)
97        } else if attr.has_name(sym::rustc_expected_cgu_reuse) {
98            match self.field(attr, sym::kind) {
99                sym::no => (CguReuse::No, ComparisonKind::Exact),
100                sym::pre_dash_lto => (CguReuse::PreLto, ComparisonKind::Exact),
101                sym::post_dash_lto => (CguReuse::PostLto, ComparisonKind::Exact),
102                sym::any => (CguReuse::PreLto, ComparisonKind::AtLeast),
103                other => {
104                    self.tcx
105                        .dcx()
106                        .emit_fatal(errors::UnknownReuseKind { span: attr.span(), kind: other });
107                }
108            }
109        } else {
110            return;
111        };
112
113        if !self.tcx.sess.opts.unstable_opts.query_dep_graph {
114            self.tcx.dcx().emit_fatal(errors::MissingQueryDepGraph { span: attr.span() });
115        }
116
117        if !self.check_config(attr) {
118            debug!("check_attr: config does not match, ignoring attr");
119            return;
120        }
121
122        let user_path = self.field(attr, sym::module).to_string();
123        let crate_name = self.tcx.crate_name(LOCAL_CRATE).to_string();
124
125        if !user_path.starts_with(&crate_name) {
126            self.tcx.dcx().emit_fatal(errors::MalformedCguName {
127                span: attr.span(),
128                user_path,
129                crate_name,
130            });
131        }
132
133        // Split of the "special suffix" if there is one.
134        let (user_path, cgu_special_suffix) = if let Some(index) = user_path.rfind('.') {
135            (&user_path[..index], Some(&user_path[index + 1..]))
136        } else {
137            (&user_path[..], None)
138        };
139
140        let mut iter = user_path.split('-');
141
142        // Remove the crate name
143        assert_eq!(iter.next().unwrap(), crate_name);
144
145        let cgu_path_components = iter.collect::<Vec<_>>();
146
147        let cgu_name_builder = &mut CodegenUnitNameBuilder::new(self.tcx);
148        let cgu_name =
149            cgu_name_builder.build_cgu_name(LOCAL_CRATE, cgu_path_components, cgu_special_suffix);
150
151        debug!("mapping '{}' to cgu name '{}'", self.field(attr, sym::module), cgu_name);
152
153        if !self.available_cgus.contains(&cgu_name) {
154            let cgu_names: Vec<&str> =
155                self.available_cgus.items().map(|cgu| cgu.as_str()).into_sorted_stable_ord();
156            self.tcx.dcx().emit_err(errors::NoModuleNamed {
157                span: attr.span(),
158                user_path,
159                cgu_name,
160                cgu_names: cgu_names.join(", "),
161            });
162        }
163
164        self.cgu_reuse_tracker.set_expectation(
165            cgu_name,
166            user_path,
167            attr.span(),
168            expected_reuse,
169            comp_kind,
170        );
171    }
172
173    fn field(&self, attr: &hir::Attribute, name: Symbol) -> Symbol {
174        for item in attr.meta_item_list().unwrap_or_else(ThinVec::new) {
175            if item.has_name(name) {
176                if let Some(value) = item.value_str() {
177                    return value;
178                } else {
179                    self.tcx.dcx().emit_fatal(errors::FieldAssociatedValueExpected {
180                        span: item.span(),
181                        name,
182                    });
183                }
184            }
185        }
186
187        self.tcx.dcx().emit_fatal(errors::NoField { span: attr.span(), name });
188    }
189
190    /// Scan for a `cfg="foo"` attribute and check whether we have a
191    /// cfg flag called `foo`.
192    fn check_config(&self, attr: &hir::Attribute) -> bool {
193        let config = &self.tcx.sess.psess.config;
194        let value = self.field(attr, sym::cfg);
195        debug!("check_config(config={:?}, value={:?})", config, value);
196        if config.iter().any(|&(name, _)| name == value) {
197            debug!("check_config: matched");
198            return true;
199        }
200        debug!("check_config: no match found");
201        false
202    }
203}
204
205#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
206pub enum CguReuse {
207    No,
208    PreLto,
209    PostLto,
210}
211
212impl fmt::Display for CguReuse {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        match *self {
215            CguReuse::No => write!(f, "No"),
216            CguReuse::PreLto => write!(f, "PreLto"),
217            CguReuse::PostLto => write!(f, "PostLto"),
218        }
219    }
220}
221
222impl IntoDiagArg for CguReuse {
223    fn into_diag_arg(self, _: &mut Option<std::path::PathBuf>) -> DiagArgValue {
224        DiagArgValue::Str(Cow::Owned(self.to_string()))
225    }
226}
227
228#[derive(Copy, Clone, Debug, PartialEq)]
229pub enum ComparisonKind {
230    Exact,
231    AtLeast,
232}
233
234struct TrackerData {
235    actual_reuse: UnordMap<String, CguReuse>,
236    expected_reuse: UnordMap<String, (String, Span, CguReuse, ComparisonKind)>,
237}
238
239pub struct CguReuseTracker {
240    data: Option<TrackerData>,
241}
242
243impl CguReuseTracker {
244    fn new() -> CguReuseTracker {
245        let data =
246            TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() };
247
248        CguReuseTracker { data: Some(data) }
249    }
250
251    fn new_disabled() -> CguReuseTracker {
252        CguReuseTracker { data: None }
253    }
254
255    pub fn set_actual_reuse(&mut self, cgu_name: &str, kind: CguReuse) {
256        if let Some(data) = &mut self.data {
257            debug!("set_actual_reuse({cgu_name:?}, {kind:?})");
258
259            let prev_reuse = data.actual_reuse.insert(cgu_name.to_string(), kind);
260            assert!(prev_reuse.is_none());
261        }
262    }
263
264    fn set_expectation(
265        &mut self,
266        cgu_name: Symbol,
267        cgu_user_name: &str,
268        error_span: Span,
269        expected_reuse: CguReuse,
270        comparison_kind: ComparisonKind,
271    ) {
272        if let Some(data) = &mut self.data {
273            debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})");
274
275            data.expected_reuse.insert(
276                cgu_name.to_string(),
277                (cgu_user_name.to_string(), error_span, expected_reuse, comparison_kind),
278            );
279        }
280    }
281
282    fn check_expected_reuse(&self, sess: &Session) {
283        if let Some(ref data) = self.data {
284            let keys = data.expected_reuse.keys().into_sorted_stable_ord();
285            for cgu_name in keys {
286                let &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind) =
287                    data.expected_reuse.get(cgu_name).unwrap();
288
289                if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) {
290                    let (error, at_least) = match comparison_kind {
291                        ComparisonKind::Exact => (expected_reuse != actual_reuse, false),
292                        ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true),
293                    };
294
295                    if error {
296                        let at_least = if at_least { 1 } else { 0 };
297                        sess.dcx().emit_err(errors::IncorrectCguReuseType {
298                            span: *error_span,
299                            cgu_user_name,
300                            actual_reuse,
301                            expected_reuse,
302                            at_least,
303                        });
304                    }
305                } else {
306                    sess.dcx().emit_fatal(errors::CguNotRecorded { cgu_user_name, cgu_name });
307                }
308            }
309        }
310    }
311}