1use 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 ams.cgu_reuse_tracker.check_expected_reuse(tcx.sess);
73 });
74}
75
76struct AssertModuleSource<'tcx> {
77 tcx: TyCtxt<'tcx>,
78 available_cgus: UnordSet<Symbol>,
79 cgu_reuse_tracker: CguReuseTracker,
80}
81
82impl<'tcx> AssertModuleSource<'tcx> {
83 fn check_attr(&mut self, attr: &hir::Attribute) {
84 let (expected_reuse, comp_kind) = if attr.has_name(sym::rustc_partition_reused) {
85 (CguReuse::PreLto, ComparisonKind::AtLeast)
86 } else if attr.has_name(sym::rustc_partition_codegened) {
87 (CguReuse::No, ComparisonKind::Exact)
88 } else if attr.has_name(sym::rustc_expected_cgu_reuse) {
89 match self.field(attr, sym::kind) {
90 sym::no => (CguReuse::No, ComparisonKind::Exact),
91 sym::pre_dash_lto => (CguReuse::PreLto, ComparisonKind::Exact),
92 sym::post_dash_lto => (CguReuse::PostLto, ComparisonKind::Exact),
93 sym::any => (CguReuse::PreLto, ComparisonKind::AtLeast),
94 other => {
95 self.tcx
96 .dcx()
97 .emit_fatal(errors::UnknownReuseKind { span: attr.span, kind: other });
98 }
99 }
100 } else {
101 return;
102 };
103
104 if !self.tcx.sess.opts.unstable_opts.query_dep_graph {
105 self.tcx.dcx().emit_fatal(errors::MissingQueryDepGraph { span: attr.span });
106 }
107
108 if !self.check_config(attr) {
109 debug!("check_attr: config does not match, ignoring attr");
110 return;
111 }
112
113 let user_path = self.field(attr, sym::module).to_string();
114 let crate_name = self.tcx.crate_name(LOCAL_CRATE).to_string();
115
116 if !user_path.starts_with(&crate_name) {
117 self.tcx.dcx().emit_fatal(errors::MalformedCguName {
118 span: attr.span,
119 user_path,
120 crate_name,
121 });
122 }
123
124 let (user_path, cgu_special_suffix) = if let Some(index) = user_path.rfind('.') {
126 (&user_path[..index], Some(&user_path[index + 1..]))
127 } else {
128 (&user_path[..], None)
129 };
130
131 let mut iter = user_path.split('-');
132
133 assert_eq!(iter.next().unwrap(), crate_name);
135
136 let cgu_path_components = iter.collect::<Vec<_>>();
137
138 let cgu_name_builder = &mut CodegenUnitNameBuilder::new(self.tcx);
139 let cgu_name =
140 cgu_name_builder.build_cgu_name(LOCAL_CRATE, cgu_path_components, cgu_special_suffix);
141
142 debug!("mapping '{}' to cgu name '{}'", self.field(attr, sym::module), cgu_name);
143
144 if !self.available_cgus.contains(&cgu_name) {
145 let cgu_names: Vec<&str> =
146 self.available_cgus.items().map(|cgu| cgu.as_str()).into_sorted_stable_ord();
147 self.tcx.dcx().emit_err(errors::NoModuleNamed {
148 span: attr.span,
149 user_path,
150 cgu_name,
151 cgu_names: cgu_names.join(", "),
152 });
153 }
154
155 self.cgu_reuse_tracker.set_expectation(
156 cgu_name,
157 user_path,
158 attr.span,
159 expected_reuse,
160 comp_kind,
161 );
162 }
163
164 fn field(&self, attr: &hir::Attribute, name: Symbol) -> Symbol {
165 for item in attr.meta_item_list().unwrap_or_else(ThinVec::new) {
166 if item.has_name(name) {
167 if let Some(value) = item.value_str() {
168 return value;
169 } else {
170 self.tcx.dcx().emit_fatal(errors::FieldAssociatedValueExpected {
171 span: item.span(),
172 name,
173 });
174 }
175 }
176 }
177
178 self.tcx.dcx().emit_fatal(errors::NoField { span: attr.span, name });
179 }
180
181 fn check_config(&self, attr: &hir::Attribute) -> bool {
184 let config = &self.tcx.sess.psess.config;
185 let value = self.field(attr, sym::cfg);
186 debug!("check_config(config={:?}, value={:?})", config, value);
187 if config.iter().any(|&(name, _)| name == value) {
188 debug!("check_config: matched");
189 return true;
190 }
191 debug!("check_config: no match found");
192 false
193 }
194}
195
196#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
197pub enum CguReuse {
198 No,
199 PreLto,
200 PostLto,
201}
202
203impl fmt::Display for CguReuse {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match *self {
206 CguReuse::No => write!(f, "No"),
207 CguReuse::PreLto => write!(f, "PreLto"),
208 CguReuse::PostLto => write!(f, "PostLto"),
209 }
210 }
211}
212
213impl IntoDiagArg for CguReuse {
214 fn into_diag_arg(self) -> DiagArgValue {
215 DiagArgValue::Str(Cow::Owned(self.to_string()))
216 }
217}
218
219#[derive(Copy, Clone, Debug, PartialEq)]
220pub enum ComparisonKind {
221 Exact,
222 AtLeast,
223}
224
225struct TrackerData {
226 actual_reuse: UnordMap<String, CguReuse>,
227 expected_reuse: UnordMap<String, (String, Span, CguReuse, ComparisonKind)>,
228}
229
230pub struct CguReuseTracker {
231 data: Option<TrackerData>,
232}
233
234impl CguReuseTracker {
235 fn new() -> CguReuseTracker {
236 let data =
237 TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() };
238
239 CguReuseTracker { data: Some(data) }
240 }
241
242 fn new_disabled() -> CguReuseTracker {
243 CguReuseTracker { data: None }
244 }
245
246 pub fn set_actual_reuse(&mut self, cgu_name: &str, kind: CguReuse) {
247 if let Some(data) = &mut self.data {
248 debug!("set_actual_reuse({cgu_name:?}, {kind:?})");
249
250 let prev_reuse = data.actual_reuse.insert(cgu_name.to_string(), kind);
251 assert!(prev_reuse.is_none());
252 }
253 }
254
255 fn set_expectation(
256 &mut self,
257 cgu_name: Symbol,
258 cgu_user_name: &str,
259 error_span: Span,
260 expected_reuse: CguReuse,
261 comparison_kind: ComparisonKind,
262 ) {
263 if let Some(data) = &mut self.data {
264 debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})");
265
266 data.expected_reuse.insert(
267 cgu_name.to_string(),
268 (cgu_user_name.to_string(), error_span, expected_reuse, comparison_kind),
269 );
270 }
271 }
272
273 fn check_expected_reuse(&self, sess: &Session) {
274 if let Some(ref data) = self.data {
275 let keys = data.expected_reuse.keys().into_sorted_stable_ord();
276 for cgu_name in keys {
277 let &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind) =
278 data.expected_reuse.get(cgu_name).unwrap();
279
280 if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) {
281 let (error, at_least) = match comparison_kind {
282 ComparisonKind::Exact => (expected_reuse != actual_reuse, false),
283 ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true),
284 };
285
286 if error {
287 let at_least = if at_least { 1 } else { 0 };
288 sess.dcx().emit_err(errors::IncorrectCguReuseType {
289 span: *error_span,
290 cgu_user_name,
291 actual_reuse,
292 expected_reuse,
293 at_least,
294 });
295 }
296 } else {
297 sess.dcx().emit_fatal(errors::CguNotRecorded { cgu_user_name, cgu_name });
298 }
299 }
300 }
301 }
302}