rustc_incremental/persist/
save.rs

1use std::fs;
2use std::sync::Arc;
3
4use rustc_data_structures::fx::FxIndexMap;
5use rustc_data_structures::sync::join;
6use rustc_middle::dep_graph::{
7    DepGraph, SerializedDepGraph, WorkProduct, WorkProductId, WorkProductMap,
8};
9use rustc_middle::ty::TyCtxt;
10use rustc_serialize::Encodable as RustcEncodable;
11use rustc_serialize::opaque::{FileEncodeResult, FileEncoder};
12use rustc_session::Session;
13use tracing::debug;
14
15use super::data::*;
16use super::fs::*;
17use super::{dirty_clean, file_format, work_product};
18use crate::assert_dep_graph::assert_dep_graph;
19use crate::errors;
20
21/// Saves and writes the [`DepGraph`] to the file system.
22///
23/// This function saves both the dep-graph and the query result cache,
24/// and drops the result cache.
25///
26/// This function should only run after all queries have completed.
27/// Trying to execute a query afterwards would attempt to read the result cache we just dropped.
28pub(crate) fn save_dep_graph(tcx: TyCtxt<'_>) {
29    debug!("save_dep_graph()");
30    tcx.dep_graph.with_ignore(|| {
31        let sess = tcx.sess;
32        if sess.opts.incremental.is_none() {
33            return;
34        }
35        // This is going to be deleted in finalize_session_directory, so let's not create it.
36        if sess.dcx().has_errors_or_delayed_bugs().is_some() {
37            return;
38        }
39
40        let query_cache_path = query_cache_path(sess);
41        let dep_graph_path = dep_graph_path(sess);
42        let staging_dep_graph_path = staging_dep_graph_path(sess);
43
44        sess.time("assert_dep_graph", || assert_dep_graph(tcx));
45        sess.time("check_dirty_clean", || dirty_clean::check_dirty_clean_annotations(tcx));
46
47        if sess.opts.unstable_opts.incremental_info {
48            tcx.dep_graph.print_incremental_info()
49        }
50
51        join(
52            move || {
53                sess.time("incr_comp_persist_dep_graph", || {
54                    if let Err(err) = fs::rename(&staging_dep_graph_path, &dep_graph_path) {
55                        sess.dcx().emit_err(errors::MoveDepGraph {
56                            from: &staging_dep_graph_path,
57                            to: &dep_graph_path,
58                            err,
59                        });
60                    }
61                });
62            },
63            move || {
64                // We execute this after `incr_comp_persist_dep_graph` for the serial compiler
65                // to catch any potential query execution writing to the dep graph.
66                sess.time("incr_comp_persist_result_cache", || {
67                    // Drop the memory map so that we can remove the file and write to it.
68                    if let Some(odc) = &tcx.query_system.on_disk_cache {
69                        odc.drop_serialized_data(tcx);
70                    }
71
72                    file_format::save_in(sess, query_cache_path, "query cache", |e| {
73                        encode_query_cache(tcx, e)
74                    });
75                });
76            },
77        );
78    })
79}
80
81/// Saves the work product index.
82pub fn save_work_product_index(
83    sess: &Session,
84    dep_graph: &DepGraph,
85    new_work_products: FxIndexMap<WorkProductId, WorkProduct>,
86) {
87    if sess.opts.incremental.is_none() {
88        return;
89    }
90    // This is going to be deleted in finalize_session_directory, so let's not create it
91    if sess.dcx().has_errors().is_some() {
92        return;
93    }
94
95    debug!("save_work_product_index()");
96    dep_graph.assert_ignored();
97    let path = work_products_path(sess);
98    file_format::save_in(sess, path, "work product index", |mut e| {
99        encode_work_product_index(&new_work_products, &mut e);
100        e.finish()
101    });
102
103    // We also need to clean out old work-products, as not all of them are
104    // deleted during invalidation. Some object files don't change their
105    // content, they are just not needed anymore.
106    let previous_work_products = dep_graph.previous_work_products();
107    for (id, wp) in previous_work_products.to_sorted_stable_ord() {
108        if !new_work_products.contains_key(id) {
109            work_product::delete_workproduct_files(sess, wp);
110            debug_assert!(
111                !wp.saved_files.items().all(|(_, path)| in_incr_comp_dir_sess(sess, path).exists())
112            );
113        }
114    }
115
116    // Check that we did not delete one of the current work-products:
117    debug_assert!({
118        new_work_products.iter().all(|(_, wp)| {
119            wp.saved_files.items().all(|(_, path)| in_incr_comp_dir_sess(sess, path).exists())
120        })
121    });
122}
123
124fn encode_work_product_index(
125    work_products: &FxIndexMap<WorkProductId, WorkProduct>,
126    encoder: &mut FileEncoder,
127) {
128    let serialized_products: Vec<_> = work_products
129        .iter()
130        .map(|(id, work_product)| SerializedWorkProduct {
131            id: *id,
132            work_product: work_product.clone(),
133        })
134        .collect();
135
136    serialized_products.encode(encoder)
137}
138
139fn encode_query_cache(tcx: TyCtxt<'_>, encoder: FileEncoder) -> FileEncodeResult {
140    tcx.sess.time("incr_comp_serialize_result_cache", || tcx.serialize_query_result_cache(encoder))
141}
142
143/// Builds the dependency graph.
144///
145/// This function creates the *staging dep-graph*. When the dep-graph is modified by a query
146/// execution, the new dependency information is not kept in memory but directly
147/// output to this file. `save_dep_graph` then finalizes the staging dep-graph
148/// and moves it to the permanent dep-graph path
149pub(crate) fn build_dep_graph(
150    sess: &Session,
151    prev_graph: Arc<SerializedDepGraph>,
152    prev_work_products: WorkProductMap,
153) -> Option<DepGraph> {
154    if sess.opts.incremental.is_none() {
155        // No incremental compilation.
156        return None;
157    }
158
159    // Stream the dep-graph to an alternate file, to avoid overwriting anything in case of errors.
160    let path_buf = staging_dep_graph_path(sess);
161
162    let mut encoder = match FileEncoder::new(&path_buf) {
163        Ok(encoder) => encoder,
164        Err(err) => {
165            sess.dcx().emit_err(errors::CreateDepGraph { path: &path_buf, err });
166            return None;
167        }
168    };
169
170    file_format::write_file_header(&mut encoder, sess);
171
172    // First encode the commandline arguments hash
173    sess.opts.dep_tracking_hash(false).encode(&mut encoder);
174
175    Some(DepGraph::new(
176        &sess.prof,
177        prev_graph,
178        prev_work_products,
179        encoder,
180        sess.opts.unstable_opts.query_dep_graph,
181        sess.opts.unstable_opts.incremental_info,
182    ))
183}