]>
Commit | Line | Data |
---|---|---|
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 | ||
26 | use crate::errors; | |
27 | use rustc_ast as ast; | |
28 | use rustc_data_structures::fx::FxHashMap; | |
29 | use rustc_data_structures::unord::UnordSet; | |
30 | use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; | |
31 | use rustc_hir::def_id::LOCAL_CRATE; | |
32 | use rustc_middle::mir::mono::CodegenUnitNameBuilder; | |
33 | use rustc_middle::ty::TyCtxt; | |
34 | use rustc_session::Session; | |
35 | use rustc_span::symbol::sym; | |
36 | use rustc_span::{Span, Symbol}; | |
37 | use std::borrow::Cow; | |
38 | use std::fmt; | |
39 | use thin_vec::ThinVec; | |
40 | ||
41 | #[allow(missing_docs)] | |
42 | pub fn assert_module_sources(tcx: TyCtxt<'_>, set_reuse: &dyn Fn(&mut CguReuseTracker)) { | |
43 | tcx.dep_graph.with_ignore(|| { | |
44 | if tcx.sess.opts.incremental.is_none() { | |
45 | return; | |
46 | } | |
47 | ||
48 | let available_cgus = | |
49 | tcx.collect_and_partition_mono_items(()).1.iter().map(|cgu| cgu.name()).collect(); | |
50 | ||
51 | let mut ams = AssertModuleSource { | |
52 | tcx, | |
53 | available_cgus, | |
54 | cgu_reuse_tracker: if tcx.sess.opts.unstable_opts.query_dep_graph { | |
55 | CguReuseTracker::new() | |
56 | } else { | |
57 | CguReuseTracker::new_disabled() | |
58 | }, | |
59 | }; | |
60 | ||
61 | for attr in tcx.hir().attrs(rustc_hir::CRATE_HIR_ID) { | |
62 | ams.check_attr(attr); | |
63 | } | |
64 | ||
65 | set_reuse(&mut ams.cgu_reuse_tracker); | |
66 | ||
67 | ams.cgu_reuse_tracker.check_expected_reuse(tcx.sess); | |
68 | }); | |
69 | } | |
70 | ||
71 | struct AssertModuleSource<'tcx> { | |
72 | tcx: TyCtxt<'tcx>, | |
73 | available_cgus: UnordSet<Symbol>, | |
74 | cgu_reuse_tracker: CguReuseTracker, | |
75 | } | |
76 | ||
77 | impl<'tcx> AssertModuleSource<'tcx> { | |
78 | fn check_attr(&mut self, attr: &ast::Attribute) { | |
79 | let (expected_reuse, comp_kind) = if attr.has_name(sym::rustc_partition_reused) { | |
80 | (CguReuse::PreLto, ComparisonKind::AtLeast) | |
81 | } else if attr.has_name(sym::rustc_partition_codegened) { | |
82 | (CguReuse::No, ComparisonKind::Exact) | |
83 | } else if attr.has_name(sym::rustc_expected_cgu_reuse) { | |
84 | match self.field(attr, sym::kind) { | |
85 | sym::no => (CguReuse::No, ComparisonKind::Exact), | |
86 | sym::pre_dash_lto => (CguReuse::PreLto, ComparisonKind::Exact), | |
87 | sym::post_dash_lto => (CguReuse::PostLto, ComparisonKind::Exact), | |
88 | sym::any => (CguReuse::PreLto, ComparisonKind::AtLeast), | |
89 | other => { | |
90 | self.tcx | |
91 | .dcx() | |
92 | .emit_fatal(errors::UnknownReuseKind { span: attr.span, kind: other }); | |
93 | } | |
94 | } | |
95 | } else { | |
96 | return; | |
97 | }; | |
98 | ||
99 | if !self.tcx.sess.opts.unstable_opts.query_dep_graph { | |
100 | self.tcx.dcx().emit_fatal(errors::MissingQueryDepGraph { span: attr.span }); | |
101 | } | |
102 | ||
103 | if !self.check_config(attr) { | |
104 | debug!("check_attr: config does not match, ignoring attr"); | |
105 | return; | |
106 | } | |
107 | ||
108 | let user_path = self.field(attr, sym::module).to_string(); | |
109 | let crate_name = self.tcx.crate_name(LOCAL_CRATE).to_string(); | |
110 | ||
111 | if !user_path.starts_with(&crate_name) { | |
112 | self.tcx.dcx().emit_fatal(errors::MalformedCguName { | |
113 | span: attr.span, | |
114 | user_path, | |
115 | crate_name, | |
116 | }); | |
117 | } | |
118 | ||
119 | // Split of the "special suffix" if there is one. | |
120 | let (user_path, cgu_special_suffix) = if let Some(index) = user_path.rfind('.') { | |
121 | (&user_path[..index], Some(&user_path[index + 1..])) | |
122 | } else { | |
123 | (&user_path[..], None) | |
124 | }; | |
125 | ||
126 | let mut iter = user_path.split('-'); | |
127 | ||
128 | // Remove the crate name | |
129 | assert_eq!(iter.next().unwrap(), crate_name); | |
130 | ||
131 | let cgu_path_components = iter.collect::<Vec<_>>(); | |
132 | ||
133 | let cgu_name_builder = &mut CodegenUnitNameBuilder::new(self.tcx); | |
134 | let cgu_name = | |
135 | cgu_name_builder.build_cgu_name(LOCAL_CRATE, cgu_path_components, cgu_special_suffix); | |
136 | ||
137 | debug!("mapping '{}' to cgu name '{}'", self.field(attr, sym::module), cgu_name); | |
138 | ||
139 | if !self.available_cgus.contains(&cgu_name) { | |
140 | let cgu_names: Vec<&str> = | |
141 | self.available_cgus.items().map(|cgu| cgu.as_str()).into_sorted_stable_ord(); | |
142 | self.tcx.dcx().emit_err(errors::NoModuleNamed { | |
143 | span: attr.span, | |
144 | user_path, | |
145 | cgu_name, | |
146 | cgu_names: cgu_names.join(", "), | |
147 | }); | |
148 | } | |
149 | ||
150 | self.cgu_reuse_tracker.set_expectation( | |
151 | cgu_name, | |
152 | user_path, | |
153 | attr.span, | |
154 | expected_reuse, | |
155 | comp_kind, | |
156 | ); | |
157 | } | |
158 | ||
159 | fn field(&self, attr: &ast::Attribute, name: Symbol) -> Symbol { | |
160 | for item in attr.meta_item_list().unwrap_or_else(ThinVec::new) { | |
161 | if item.has_name(name) { | |
162 | if let Some(value) = item.value_str() { | |
163 | return value; | |
164 | } else { | |
165 | self.tcx.dcx().emit_fatal(errors::FieldAssociatedValueExpected { | |
166 | span: item.span(), | |
167 | name, | |
168 | }); | |
169 | } | |
170 | } | |
171 | } | |
172 | ||
173 | self.tcx.dcx().emit_fatal(errors::NoField { span: attr.span, name }); | |
174 | } | |
175 | ||
176 | /// Scan for a `cfg="foo"` attribute and check whether we have a | |
177 | /// cfg flag called `foo`. | |
178 | fn check_config(&self, attr: &ast::Attribute) -> bool { | |
179 | let config = &self.tcx.sess.parse_sess.config; | |
180 | let value = self.field(attr, sym::cfg); | |
181 | debug!("check_config(config={:?}, value={:?})", config, value); | |
182 | if config.iter().any(|&(name, _)| name == value) { | |
183 | debug!("check_config: matched"); | |
184 | return true; | |
185 | } | |
186 | debug!("check_config: no match found"); | |
187 | false | |
188 | } | |
189 | } | |
190 | ||
191 | #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] | |
192 | pub enum CguReuse { | |
193 | No, | |
194 | PreLto, | |
195 | PostLto, | |
196 | } | |
197 | ||
198 | impl fmt::Display for CguReuse { | |
199 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
200 | match *self { | |
201 | CguReuse::No => write!(f, "No"), | |
202 | CguReuse::PreLto => write!(f, "PreLto"), | |
203 | CguReuse::PostLto => write!(f, "PostLto"), | |
204 | } | |
205 | } | |
206 | } | |
207 | ||
208 | impl IntoDiagnosticArg for CguReuse { | |
209 | fn into_diagnostic_arg(self) -> DiagnosticArgValue { | |
210 | DiagnosticArgValue::Str(Cow::Owned(self.to_string())) | |
211 | } | |
212 | } | |
213 | ||
214 | #[derive(Copy, Clone, Debug, PartialEq)] | |
215 | pub enum ComparisonKind { | |
216 | Exact, | |
217 | AtLeast, | |
218 | } | |
219 | ||
220 | struct TrackerData { | |
221 | actual_reuse: FxHashMap<String, CguReuse>, | |
222 | expected_reuse: FxHashMap<String, (String, Span, CguReuse, ComparisonKind)>, | |
223 | } | |
224 | ||
225 | pub struct CguReuseTracker { | |
226 | data: Option<TrackerData>, | |
227 | } | |
228 | ||
229 | impl CguReuseTracker { | |
230 | fn new() -> CguReuseTracker { | |
231 | let data = | |
232 | TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() }; | |
233 | ||
234 | CguReuseTracker { data: Some(data) } | |
235 | } | |
236 | ||
237 | fn new_disabled() -> CguReuseTracker { | |
238 | CguReuseTracker { data: None } | |
239 | } | |
240 | ||
241 | pub fn set_actual_reuse(&mut self, cgu_name: &str, kind: CguReuse) { | |
242 | if let Some(data) = &mut self.data { | |
243 | debug!("set_actual_reuse({cgu_name:?}, {kind:?})"); | |
244 | ||
245 | let prev_reuse = data.actual_reuse.insert(cgu_name.to_string(), kind); | |
246 | assert!(prev_reuse.is_none()); | |
247 | } | |
248 | } | |
249 | ||
250 | fn set_expectation( | |
251 | &mut self, | |
252 | cgu_name: Symbol, | |
253 | cgu_user_name: &str, | |
254 | error_span: Span, | |
255 | expected_reuse: CguReuse, | |
256 | comparison_kind: ComparisonKind, | |
257 | ) { | |
258 | if let Some(data) = &mut self.data { | |
259 | debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})"); | |
260 | ||
261 | data.expected_reuse.insert( | |
262 | cgu_name.to_string(), | |
263 | (cgu_user_name.to_string(), error_span, expected_reuse, comparison_kind), | |
264 | ); | |
265 | } | |
266 | } | |
267 | ||
268 | fn check_expected_reuse(&self, sess: &Session) { | |
269 | if let Some(ref data) = self.data { | |
270 | #[allow(rustc::potential_query_instability)] | |
271 | let mut keys = data.expected_reuse.keys().collect::<Vec<_>>(); | |
272 | keys.sort_unstable(); | |
273 | for cgu_name in keys { | |
274 | let &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind) = | |
275 | data.expected_reuse.get(cgu_name).unwrap(); | |
276 | ||
277 | if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) { | |
278 | let (error, at_least) = match comparison_kind { | |
279 | ComparisonKind::Exact => (expected_reuse != actual_reuse, false), | |
280 | ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true), | |
281 | }; | |
282 | ||
283 | if error { | |
284 | let at_least = if at_least { 1 } else { 0 }; | |
285 | sess.dcx().emit_err(errors::IncorrectCguReuseType { | |
286 | span: *error_span, | |
287 | cgu_user_name, | |
288 | actual_reuse, | |
289 | expected_reuse, | |
290 | at_least, | |
291 | }); | |
292 | } | |
293 | } else { | |
294 | sess.dcx().emit_fatal(errors::CguNotRecorded { cgu_user_name, cgu_name }); | |
295 | } | |
296 | } | |
297 | } | |
298 | } | |
299 | } |