rustc_borrowck/
nll.rs

1//! The entry point of the NLL borrow checker.
2
3use std::io;
4use std::path::PathBuf;
5use std::rc::Rc;
6use std::str::FromStr;
7
8use polonius_engine::{Algorithm, Output};
9use rustc_index::IndexSlice;
10use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options};
11use rustc_middle::mir::{Body, PassWhere, Promoted, create_dump_file, dump_enabled, dump_mir};
12use rustc_middle::ty::print::with_no_trimmed_paths;
13use rustc_middle::ty::{self, TyCtxt};
14use rustc_mir_dataflow::ResultsCursor;
15use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
16use rustc_mir_dataflow::move_paths::MoveData;
17use rustc_mir_dataflow::points::DenseLocationMap;
18use rustc_session::config::MirIncludeSpans;
19use rustc_span::sym;
20use tracing::{debug, instrument};
21
22use crate::borrow_set::BorrowSet;
23use crate::consumers::ConsumerOptions;
24use crate::diagnostics::RegionErrors;
25use crate::polonius::PoloniusDiagnosticsContext;
26use crate::polonius::legacy::{
27    PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput,
28};
29use crate::region_infer::RegionInferenceContext;
30use crate::type_check::{self, MirTypeckResults};
31use crate::universal_regions::UniversalRegions;
32use crate::{
33    BorrowCheckRootCtxt, BorrowckInferCtxt, ClosureOutlivesSubject, ClosureRegionRequirements,
34    polonius, renumber,
35};
36
37/// The output of `nll::compute_regions`. This includes the computed `RegionInferenceContext`, any
38/// closure requirements to propagate, and any generated errors.
39pub(crate) struct NllOutput<'tcx> {
40    pub regioncx: RegionInferenceContext<'tcx>,
41    pub polonius_input: Option<Box<PoloniusFacts>>,
42    pub polonius_output: Option<Box<PoloniusOutput>>,
43    pub opt_closure_req: Option<ClosureRegionRequirements<'tcx>>,
44    pub nll_errors: RegionErrors<'tcx>,
45
46    /// When using `-Zpolonius=next`: the data used to compute errors and diagnostics, e.g.
47    /// localized typeck and liveness constraints.
48    pub polonius_diagnostics: Option<PoloniusDiagnosticsContext>,
49}
50
51/// Rewrites the regions in the MIR to use NLL variables, also scraping out the set of universal
52/// regions (e.g., region parameters) declared on the function. That set will need to be given to
53/// `compute_regions`.
54#[instrument(skip(infcx, body, promoted), level = "debug")]
55pub(crate) fn replace_regions_in_mir<'tcx>(
56    infcx: &BorrowckInferCtxt<'tcx>,
57    body: &mut Body<'tcx>,
58    promoted: &mut IndexSlice<Promoted, Body<'tcx>>,
59) -> UniversalRegions<'tcx> {
60    let def = body.source.def_id().expect_local();
61
62    debug!(?def);
63
64    // Compute named region information. This also renumbers the inputs/outputs.
65    let universal_regions = UniversalRegions::new(infcx, def);
66
67    // Replace all remaining regions with fresh inference variables.
68    renumber::renumber_mir(infcx, body, promoted);
69
70    dump_mir(infcx.tcx, false, "renumber", &0, body, |_, _| Ok(()));
71
72    universal_regions
73}
74
75/// Computes the (non-lexical) regions from the input MIR.
76///
77/// This may result in errors being reported.
78pub(crate) fn compute_regions<'a, 'tcx>(
79    root_cx: &mut BorrowCheckRootCtxt<'tcx>,
80    infcx: &BorrowckInferCtxt<'tcx>,
81    universal_regions: UniversalRegions<'tcx>,
82    body: &Body<'tcx>,
83    promoted: &IndexSlice<Promoted, Body<'tcx>>,
84    location_table: &PoloniusLocationTable,
85    flow_inits: ResultsCursor<'a, 'tcx, MaybeInitializedPlaces<'a, 'tcx>>,
86    move_data: &MoveData<'tcx>,
87    borrow_set: &BorrowSet<'tcx>,
88    consumer_options: Option<ConsumerOptions>,
89) -> NllOutput<'tcx> {
90    let is_polonius_legacy_enabled = infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled();
91    let polonius_input = consumer_options.map(|c| c.polonius_input()).unwrap_or_default()
92        || is_polonius_legacy_enabled;
93    let polonius_output = consumer_options.map(|c| c.polonius_output()).unwrap_or_default()
94        || is_polonius_legacy_enabled;
95    let mut polonius_facts =
96        (polonius_input || PoloniusFacts::enabled(infcx.tcx)).then_some(PoloniusFacts::default());
97
98    let location_map = Rc::new(DenseLocationMap::new(body));
99
100    // Run the MIR type-checker.
101    let MirTypeckResults {
102        constraints,
103        universal_region_relations,
104        opaque_type_values,
105        polonius_context,
106    } = type_check::type_check(
107        root_cx,
108        infcx,
109        body,
110        promoted,
111        universal_regions,
112        location_table,
113        borrow_set,
114        &mut polonius_facts,
115        flow_inits,
116        move_data,
117        Rc::clone(&location_map),
118    );
119
120    // If requested, emit legacy polonius facts.
121    polonius::legacy::emit_facts(
122        &mut polonius_facts,
123        infcx.tcx,
124        location_table,
125        body,
126        borrow_set,
127        move_data,
128        &universal_region_relations,
129        &constraints,
130    );
131
132    let mut regioncx =
133        RegionInferenceContext::new(infcx, constraints, universal_region_relations, location_map);
134
135    // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints
136    // and use them to compute loan liveness.
137    let polonius_diagnostics = polonius_context.map(|polonius_context| {
138        polonius_context.compute_loan_liveness(infcx.tcx, &mut regioncx, body, borrow_set)
139    });
140
141    // If requested: dump NLL facts, and run legacy polonius analysis.
142    let polonius_output = polonius_facts.as_ref().and_then(|polonius_facts| {
143        if infcx.tcx.sess.opts.unstable_opts.nll_facts {
144            let def_id = body.source.def_id();
145            let def_path = infcx.tcx.def_path(def_id);
146            let dir_path = PathBuf::from(&infcx.tcx.sess.opts.unstable_opts.nll_facts_dir)
147                .join(def_path.to_filename_friendly_no_crate());
148            polonius_facts.write_to_dir(dir_path, location_table).unwrap();
149        }
150
151        if polonius_output {
152            let algorithm = infcx.tcx.env_var("POLONIUS_ALGORITHM").unwrap_or("Hybrid");
153            let algorithm = Algorithm::from_str(algorithm).unwrap();
154            debug!("compute_regions: using polonius algorithm {:?}", algorithm);
155            let _prof_timer = infcx.tcx.prof.generic_activity("polonius_analysis");
156            Some(Box::new(Output::compute(polonius_facts, algorithm, false)))
157        } else {
158            None
159        }
160    });
161
162    // Solve the region constraints.
163    let (closure_region_requirements, nll_errors) =
164        regioncx.solve(infcx, body, polonius_output.clone());
165
166    if let Some(guar) = nll_errors.has_errors() {
167        // Suppress unhelpful extra errors in `infer_opaque_types`.
168        infcx.set_tainted_by_errors(guar);
169    }
170
171    regioncx.infer_opaque_types(root_cx, infcx, opaque_type_values);
172
173    NllOutput {
174        regioncx,
175        polonius_input: polonius_facts.map(Box::new),
176        polonius_output,
177        opt_closure_req: closure_region_requirements,
178        nll_errors,
179        polonius_diagnostics,
180    }
181}
182
183/// `-Zdump-mir=nll` dumps MIR annotated with NLL specific information:
184/// - free regions
185/// - inferred region values
186/// - region liveness
187/// - inference constraints and their causes
188///
189/// As well as graphviz `.dot` visualizations of:
190/// - the region constraints graph
191/// - the region SCC graph
192pub(super) fn dump_nll_mir<'tcx>(
193    infcx: &BorrowckInferCtxt<'tcx>,
194    body: &Body<'tcx>,
195    regioncx: &RegionInferenceContext<'tcx>,
196    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
197    borrow_set: &BorrowSet<'tcx>,
198) {
199    let tcx = infcx.tcx;
200    if !dump_enabled(tcx, "nll", body.source.def_id()) {
201        return;
202    }
203
204    // We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in
205    // #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example,
206    // they're always disabled in mir-opt tests to make working with blessed dumps easier.
207    let options = PrettyPrintMirOptions {
208        include_extra_comments: matches!(
209            infcx.tcx.sess.opts.unstable_opts.mir_include_spans,
210            MirIncludeSpans::On | MirIncludeSpans::Nll
211        ),
212    };
213    dump_mir_with_options(
214        tcx,
215        false,
216        "nll",
217        &0,
218        body,
219        |pass_where, out| {
220            emit_nll_mir(tcx, regioncx, closure_region_requirements, borrow_set, pass_where, out)
221        },
222        options,
223    );
224
225    // Also dump the region constraint graph as a graphviz file.
226    let _: io::Result<()> = try {
227        let mut file = create_dump_file(tcx, "regioncx.all.dot", false, "nll", &0, body)?;
228        regioncx.dump_graphviz_raw_constraints(&mut file)?;
229    };
230
231    // Also dump the region constraint SCC graph as a graphviz file.
232    let _: io::Result<()> = try {
233        let mut file = create_dump_file(tcx, "regioncx.scc.dot", false, "nll", &0, body)?;
234        regioncx.dump_graphviz_scc_constraints(&mut file)?;
235    };
236}
237
238/// Produces the actual NLL MIR sections to emit during the dumping process.
239pub(crate) fn emit_nll_mir<'tcx>(
240    tcx: TyCtxt<'tcx>,
241    regioncx: &RegionInferenceContext<'tcx>,
242    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
243    borrow_set: &BorrowSet<'tcx>,
244    pass_where: PassWhere,
245    out: &mut dyn io::Write,
246) -> io::Result<()> {
247    match pass_where {
248        // Before the CFG, dump out the values for each region variable.
249        PassWhere::BeforeCFG => {
250            regioncx.dump_mir(tcx, out)?;
251            writeln!(out, "|")?;
252
253            if let Some(closure_region_requirements) = closure_region_requirements {
254                writeln!(out, "| Free Region Constraints")?;
255                for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| {
256                    writeln!(out, "| {msg}")
257                })?;
258                writeln!(out, "|")?;
259            }
260
261            if borrow_set.len() > 0 {
262                writeln!(out, "| Borrows")?;
263                for (borrow_idx, borrow_data) in borrow_set.iter_enumerated() {
264                    writeln!(
265                        out,
266                        "| {:?}: issued at {:?} in {:?}",
267                        borrow_idx, borrow_data.reserve_location, borrow_data.region
268                    )?;
269                }
270                writeln!(out, "|")?;
271            }
272        }
273
274        PassWhere::BeforeLocation(_) => {}
275
276        PassWhere::AfterTerminator(_) => {}
277
278        PassWhere::BeforeBlock(_) | PassWhere::AfterLocation(_) | PassWhere::AfterCFG => {}
279    }
280    Ok(())
281}
282
283#[allow(rustc::diagnostic_outside_of_impl)]
284#[allow(rustc::untranslatable_diagnostic)]
285pub(super) fn dump_annotation<'tcx, 'infcx>(
286    infcx: &'infcx BorrowckInferCtxt<'tcx>,
287    body: &Body<'tcx>,
288    regioncx: &RegionInferenceContext<'tcx>,
289    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
290) {
291    let tcx = infcx.tcx;
292    let base_def_id = tcx.typeck_root_def_id(body.source.def_id());
293    if !tcx.has_attr(base_def_id, sym::rustc_regions) {
294        return;
295    }
296
297    // When the enclosing function is tagged with `#[rustc_regions]`,
298    // we dump out various bits of state as warnings. This is useful
299    // for verifying that the compiler is behaving as expected. These
300    // warnings focus on the closure region requirements -- for
301    // viewing the intraprocedural state, the -Zdump-mir output is
302    // better.
303
304    let def_span = tcx.def_span(body.source.def_id());
305    let err = if let Some(closure_region_requirements) = closure_region_requirements {
306        let mut err = infcx.dcx().struct_span_note(def_span, "external requirements");
307
308        regioncx.annotate(tcx, &mut err);
309
310        err.note(format!(
311            "number of external vids: {}",
312            closure_region_requirements.num_external_vids
313        ));
314
315        // Dump the region constraints we are imposing *between* those
316        // newly created variables.
317        for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| {
318            err.note(msg);
319            Ok(())
320        })
321        .unwrap();
322
323        err
324    } else {
325        let mut err = infcx.dcx().struct_span_note(def_span, "no external requirements");
326        regioncx.annotate(tcx, &mut err);
327        err
328    };
329
330    // FIXME(@lcnr): We currently don't dump the inferred hidden types here.
331    err.emit();
332}
333
334fn for_each_region_constraint<'tcx>(
335    tcx: TyCtxt<'tcx>,
336    closure_region_requirements: &ClosureRegionRequirements<'tcx>,
337    with_msg: &mut dyn FnMut(String) -> io::Result<()>,
338) -> io::Result<()> {
339    for req in &closure_region_requirements.outlives_requirements {
340        let subject = match req.subject {
341            ClosureOutlivesSubject::Region(subject) => format!("{subject:?}"),
342            ClosureOutlivesSubject::Ty(ty) => {
343                with_no_trimmed_paths!(format!(
344                    "{}",
345                    ty.instantiate(tcx, |vid| ty::Region::new_var(tcx, vid))
346                ))
347            }
348        };
349        with_msg(format!("where {}: {:?}", subject, req.outlived_free_region,))?;
350    }
351    Ok(())
352}
353
354pub(crate) trait ConstraintDescription {
355    fn description(&self) -> &'static str;
356}