Skip to main content

charon_driver/
driver.rs

1//! Run the rustc compiler with our custom options and hooks.
2use crate::CharonFailure;
3use crate::toolchain::CHARON_SYSROOT_VAR;
4use crate::translate::translate_crate;
5use charon_lib::errors::ErrorCtx;
6use charon_lib::options::{self, CliOpts, selected_crate_var};
7use charon_lib::transform::TransformCtx;
8use charon_lib::utils::arg_value;
9use itertools::Itertools;
10use rustc_driver::{Callbacks, Compilation};
11use rustc_interface::Config;
12use rustc_interface::interface::Compiler;
13use rustc_middle::ty::{InstanceKind, TyCtxt};
14use rustc_middle::util::Providers;
15use rustc_session::config::{OutputType, OutputTypes};
16use rustc_span::{ErrorGuaranteed, Symbol};
17use std::num::NonZero;
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::{env, fmt};
20
21/// Helper that runs the compiler and catches its fatal errors.
22fn run_compiler_with_callbacks(
23    args: Vec<String>,
24    callbacks: &mut (dyn Callbacks + Send),
25) -> Result<(), CharonFailure> {
26    rustc_driver::catch_fatal_errors(|| rustc_driver::run_compiler(&args, callbacks))
27        .map_err(|_| CharonFailure::RustcError)
28}
29
30/// Tweak options to get usable MIR even for foreign crates.
31fn set_mir_options(config: &mut Config) {
32    config.opts.unstable_opts.always_encode_mir = true;
33    config.opts.unstable_opts.mir_opt_level = Some(0);
34    config.opts.unstable_opts.mir_preserve_ub = true;
35    let disabled_mir_passes = ["CheckAlignment", "CheckNull"];
36    for pass in disabled_mir_passes {
37        config
38            .opts
39            .unstable_opts
40            .mir_enable_passes
41            .push((pass.to_owned(), false));
42    }
43}
44
45/// Enable rustc's parallel front-end.
46fn set_parallel_frontend(config: &mut Config) {
47    if config.opts.jobs.frontend.is_none() {
48        // Match rustc's `--jobs-frontend=0` behavior.
49        const RUSTC_MAX_THREADS_CAP: usize = u8::MAX as usize;
50        let threads = std::thread::available_parallelism()
51            .map_or(1, |n| n.get())
52            .min(RUSTC_MAX_THREADS_CAP);
53        config.opts.jobs.frontend = NonZero::new(threads).filter(|n| n.get() > 1);
54    }
55}
56
57// We use a static to be able to pass data to `override_queries`.
58static SKIP_BORROWCK: AtomicBool = AtomicBool::new(false);
59fn set_skip_borrowck() {
60    SKIP_BORROWCK.store(true, Ordering::SeqCst);
61}
62fn skip_borrowck_if_set(providers: &mut Providers) {
63    if SKIP_BORROWCK.load(Ordering::SeqCst) {
64        providers.queries.mir_borrowck = |tcx, _def_id| {
65            // Empty result, which is what is used for custom_mir bodies.
66            Ok(tcx.arena.alloc(Default::default()))
67        }
68    }
69}
70
71fn setup_compiler(
72    config: &mut Config,
73    options: &CliOpts,
74    is_target_crate: bool,
75    do_translate: bool,
76    emit_artifacts: bool,
77    codegen: bool,
78) {
79    if is_target_crate && let Some(crate_name) = &config.opts.crate_name {
80        // Rerun compilation if this variable changes. We don't do that for dependencies built for
81        // the host, like build scripts.
82        let var = selected_crate_var(crate_name);
83        config.track_state = Some(Box::new(move |sess| {
84            let value = env::var(&var).ok().map(|value| Symbol::intern(&value));
85            let var = Symbol::intern(&var);
86            sess.env_depinfo.borrow_mut().insert((var, value));
87        }));
88    }
89
90    if do_translate {
91        if options.skip_borrowck {
92            // We use a static to be able to pass data to `override_queries`.
93            set_skip_borrowck();
94        }
95
96        config.override_queries = Some(|_sess, providers| {
97            skip_borrowck_if_set(providers);
98
99            // TODO: catch the MIR in-flight to avoid stealing issues?
100            // providers.mir_built = |tcx, def_id| {
101            //     let mir = (rustc_interface::DEFAULT_QUERY_PROVIDERS.mir_built)(tcx, def_id);
102            //     let mut mir = mir.steal();
103            //     // use the mir
104            //     tcx.alloc_steal_mir(mir)
105            // };
106        });
107
108        config.opts.unstable_opts.no_codegen = !codegen;
109        if !emit_artifacts {
110            config.opts.output_types = OutputTypes::new(&[(OutputType::Object, None)]);
111        }
112        set_parallel_frontend(config);
113    }
114    set_mir_options(config);
115}
116
117/// Run a couple of rustc queries that don't involve MIR (so that they don't steal it). Returns
118/// whether rustc reported errors. This lets us avoid running Charon translation on crates rustc
119/// already rejects.
120fn precheck_rustc_errors(tcx: TyCtxt<'_>) -> bool {
121    type QueryResult = Result<(), ErrorGuaranteed>;
122
123    tcx.par_hir_for_each_module(|module| {
124        tcx.ensure_ok().check_mod_attrs(module);
125        tcx.ensure_ok().check_mod_unstable_api_usage(module);
126    });
127
128    let _: QueryResult = tcx.ensure_result().check_type_wf(());
129    for &trait_def_id in tcx.all_local_trait_impls(()).keys() {
130        let _: QueryResult = tcx.ensure_result().coherent_trait(trait_def_id);
131    }
132    let _: QueryResult = tcx.ensure_result().crate_inherent_impls_validity_check(());
133    let _: QueryResult = tcx.ensure_result().crate_inherent_impls_overlap_check(());
134
135    tcx.par_hir_body_owners(|def_id| {
136        let def_kind = tcx.def_kind(def_id);
137        if !matches!(def_kind, rustc_hir::def::DefKind::AnonConst)
138            && !tcx.is_typeck_child(def_id.to_def_id())
139        {
140            tcx.ensure_ok().typeck(def_id);
141        }
142    });
143
144    tcx.dcx().has_errors().is_some()
145}
146
147/// Run rustc checks that normally happen close to codegen, so that we get all the post-mono errors
148/// etc.
149fn check_late_rustc_errors(tcx: TyCtxt<'_>) {
150    tcx.par_hir_body_owners(|def_id| {
151        let _ = tcx.instance_mir(InstanceKind::Item(def_id.to_def_id()));
152    });
153
154    if tcx.dcx().err_count() == 0 {
155        let _ = tcx.collect_and_partition_mono_items(());
156    }
157}
158
159/// Run the rustc driver with our custom hooks. Returns `None` if the crate was not compiled with
160/// charon (e.g. because it was a dependency). Otherwise returns the translated crate, ready for
161/// post-processing transformations.
162pub fn run_rustc_driver() -> Result<Option<(TransformCtx, CliOpts)>, CharonFailure> {
163    // Retreive the command-line arguments pased to `charon_driver`. The first arg is the path to
164    // the current executable, we skip it.
165    let mut compiler_args: Vec<String> = env::args().skip(1).collect();
166    trace!(
167        "charon-driver called with args: {}",
168        compiler_args.iter().format(" ")
169    );
170
171    // When called using cargo, we tell cargo to use `charon-driver` by setting the `RUSTC_WRAPPER`
172    // env var. This uses `charon-driver` for all the crates being compiled.
173    // We may however not want to be calling charon on all crates; `CARGO_PRIMARY_PACKAGE` tells us
174    // whether the crate was specifically selected or is a dependency.
175    let is_workspace_dependency =
176        env::var("CHARON_USING_CARGO").is_ok() && env::var("CARGO_PRIMARY_PACKAGE").is_err();
177    // Let rustc emit artifacts (metadata, binaries) normally if invoked by `cargo` or when
178    // explicitly requested.
179    let emit_artifacts =
180        env::var("CHARON_USING_CARGO").is_ok() || env::var("CHARON_EMIT_ARTIFACTS").is_ok();
181    // Let rustc emit codegen artifacts, more specifically.
182    let mut codegen = emit_artifacts;
183    // Determines if we are being invoked to build a crate for the "target" architecture, in
184    // contrast to the "host" architecture. Host crates are for build scripts and proc macros and
185    // still need to be built like normal; target crates need to be processed by Charon.
186    //
187    // Currently, we detect this by checking for "--target=", which is never set for host crates.
188    // This matches what Miri does, which hopefully makes it reliable enough. This relies on us
189    // always invoking cargo itself with `--target`, which `charon` ensures.
190    let is_target_crate = arg_value(&compiler_args, "--target").is_some();
191    // Whether this is the crate we want to translate.
192    let is_selected_crate = !is_workspace_dependency && is_target_crate;
193
194    let mut error_ctx = ErrorCtx::new();
195
196    // Retrieve the Charon options by deserializing them from the environment variable
197    // (cargo-charon serialized the arguments and stored them in a specific environment
198    // variable before calling cargo with `RUSTC_WRAPPER=charon-driver`).
199    let options = match env::var(options::CHARON_ARGS)
200        .ok()
201        .map(|opts| serde_json::from_str::<options::CliOpts>(&opts).unwrap())
202    {
203        Some(options) => options,
204        None if !is_selected_crate => Default::default(),
205        None => {
206            register_error!(
207                error_ctx,
208                no_crate,
209                "environment variable `CHARON_ARGS` not set; \
210                don't call `charon-driver` directly, call `charon rustc` instead"
211            );
212            return Err(CharonFailure::CharonError(1));
213        }
214    };
215
216    if options.sysroot.as_deref() == Some("default") {
217        // Do nothing
218    } else if let Some(sysroot) = options.sysroot.as_ref()
219        && sysroot != "miri"
220    {
221        compiler_args.push(format!("--sysroot={sysroot}"));
222    } else if is_target_crate && let Ok(sysroot) = env::var(CHARON_SYSROOT_VAR) {
223        // Charon built a sysroot for us to use.
224        compiler_args.push(format!("--sysroot={sysroot}"));
225        // The Miri sysroot (which is what Charon builds atm) doesn't support codegen.
226        codegen = false;
227    }
228
229    let output = if !is_selected_crate {
230        trace!("Skipping charon; running compiler normally instead.");
231        // Run the compiler normally.
232        let mut callback = RunCompilerNormallyCallbacks { is_target_crate };
233        run_compiler_with_callbacks(compiler_args, &mut callback)?;
234        None
235    } else {
236        error_ctx.continue_on_failure = !options.abort_on_error;
237        error_ctx.error_on_warnings = options.error_on_warnings;
238
239        for extra_flag in options.rustc_args.iter().cloned() {
240            compiler_args.push(extra_flag);
241        }
242
243        // Call the Rust compiler with our custom callback.
244        let mut callback = CharonCallbacks {
245            options: &options,
246            emit_artifacts,
247            codegen,
248            error_ctx: Some(error_ctx),
249            transform_ctx: None,
250        };
251        charon_lib::timing::time("rustc-driver", || {
252            run_compiler_with_callbacks(compiler_args, &mut callback)
253        })?;
254        // If `transform_ctx` is not set here, there was a fatal error.
255        let ctx = callback.transform_ctx.ok_or(CharonFailure::RustcError)?;
256        Some((ctx, options))
257    };
258    Ok(output)
259}
260
261/// The callbacks for Charon
262pub struct CharonCallbacks<'a> {
263    options: &'a CliOpts,
264    /// Whether rustc should emit the artifacts (metadata, binaries) it normally would. This is
265    /// needed under cargo so later crate invocations can consume earlier selected crates.
266    emit_artifacts: bool,
267    /// Whether to let rustc run codegen as it normally would.
268    codegen: bool,
269    /// Context for errors; `take()`n by translation.
270    error_ctx: Option<ErrorCtx>,
271    /// This is to be filled during the extraction; it contains the translated crate. `None` at the
272    /// start or if we couldn't translate anything.
273    transform_ctx: Option<TransformCtx>,
274}
275impl<'a> Callbacks for CharonCallbacks<'a> {
276    fn config(&mut self, config: &mut Config) {
277        setup_compiler(
278            config,
279            self.options,
280            true,
281            true,
282            self.emit_artifacts,
283            self.codegen,
284        );
285    }
286
287    /// The MIR is modified in place: borrow-checking requires the "promoted" MIR, which causes the
288    /// "built" MIR (which results from the conversion to HIR to MIR) to become unaccessible.
289    /// Because we require built MIR at the moment, we hook ourselves before MIR-based analysis
290    /// passes.
291    fn after_expansion<'tcx>(&mut self, compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
292        // Set up our own `DefId` debug routine.
293        rustc_hir::def_id::DEF_ID_DEBUG
294            .swap(&(def_id_debug as fn(_, &mut fmt::Formatter<'_>) -> _));
295
296        if charon_lib::timing::time("rustc-precheck-errors", || precheck_rustc_errors(tcx)) {
297            return Compilation::Continue;
298        }
299
300        self.transform_ctx = charon_lib::timing::time("translate-crate", || {
301            translate_crate::translate(
302                tcx,
303                self.options,
304                self.error_ctx.take().unwrap(),
305                compiler.sess.opts.sysroot.path().to_owned(),
306            )
307        })
308        .ok();
309
310        Compilation::Continue
311    }
312    fn after_analysis<'tcx>(&mut self, _compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
313        if !self.emit_artifacts {
314            charon_lib::timing::time("rustc-late-checks", || check_late_rustc_errors(tcx));
315        }
316        Compilation::Continue
317    }
318}
319
320/// Dummy callbacks used to run the compiler normally when we shouldn't be analyzing the crate.
321pub struct RunCompilerNormallyCallbacks {
322    is_target_crate: bool,
323}
324
325impl Callbacks for RunCompilerNormallyCallbacks {
326    fn config(&mut self, config: &mut Config) {
327        setup_compiler(
328            config,
329            &Default::default(),
330            self.is_target_crate,
331            false,
332            true,
333            true,
334        );
335    }
336}
337
338/// Custom `DefId` debug routine that doesn't print unstable values like ids and hashes.
339fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340    rustc_middle::ty::tls::with_opt(|opt_tcx| {
341        if let Some(tcx) = opt_tcx {
342            let crate_name = if def_id.is_local() {
343                tcx.crate_name(rustc_hir::def_id::LOCAL_CRATE)
344            } else {
345                tcx.cstore_untracked().crate_name(def_id.krate)
346            };
347            write!(
348                f,
349                "{}{}",
350                crate_name,
351                tcx.def_path(def_id).to_string_no_crate_verbose()
352            )?;
353        } else {
354            write!(f, "<can't access `tcx` to print `DefId` path>")?;
355        }
356        Ok(())
357    })
358}