1use crate::CharonFailure;
3use crate::toolchain::toolchain_version;
4use crate::translate::translate_crate;
5use charon_lib::errors::ErrorCtx;
6use charon_lib::options::{self, CliOpts};
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;
17use std::num::NonZero;
18use std::path::PathBuf;
19use std::process::Command;
20use std::sync::atomic::{AtomicBool, Ordering};
21use std::{env, fmt};
22
23fn run_compiler_with_callbacks(
25 args: Vec<String>,
26 callbacks: &mut (dyn Callbacks + Send),
27) -> Result<(), CharonFailure> {
28 rustc_driver::catch_fatal_errors(|| rustc_driver::run_compiler(&args, callbacks))
29 .map_err(|_| CharonFailure::RustcError)
30}
31
32fn set_mir_options(config: &mut Config) {
34 config.opts.unstable_opts.always_encode_mir = true;
35 config.opts.unstable_opts.mir_opt_level = Some(0);
36 config.opts.unstable_opts.mir_preserve_ub = true;
37 let disabled_mir_passes = ["CheckAlignment", "CheckNull"];
38 for pass in disabled_mir_passes {
39 config
40 .opts
41 .unstable_opts
42 .mir_enable_passes
43 .push((pass.to_owned(), false));
44 }
45}
46
47fn set_parallel_frontend(config: &mut Config) {
49 if config.opts.jobs.frontend.is_none() {
50 const RUSTC_MAX_THREADS_CAP: usize = u8::MAX as usize;
52 let threads = std::thread::available_parallelism()
53 .map_or(1, |n| n.get())
54 .min(RUSTC_MAX_THREADS_CAP);
55 config.opts.jobs.frontend = NonZero::new(threads).filter(|n| n.get() > 1);
56 }
57}
58
59static SKIP_BORROWCK: AtomicBool = AtomicBool::new(false);
61fn set_skip_borrowck() {
62 SKIP_BORROWCK.store(true, Ordering::SeqCst);
63}
64fn skip_borrowck_if_set(providers: &mut Providers) {
65 if SKIP_BORROWCK.load(Ordering::SeqCst) {
66 providers.queries.mir_borrowck = |tcx, _def_id| {
67 Ok(tcx.arena.alloc(Default::default()))
69 }
70 }
71}
72
73fn setup_compiler(
74 config: &mut Config,
75 options: &CliOpts,
76 do_translate: bool,
77 emit_artifacts: bool,
78 codegen: bool,
79) {
80 if do_translate {
81 if options.skip_borrowck {
82 set_skip_borrowck();
84 }
85
86 config.override_queries = Some(|_sess, providers| {
87 skip_borrowck_if_set(providers);
88
89 });
97
98 config.opts.unstable_opts.no_codegen = !codegen;
99 if !emit_artifacts {
100 config.opts.output_types = OutputTypes::new(&[(OutputType::Object, None)]);
101 }
102 set_parallel_frontend(config);
103 }
104 set_mir_options(config);
105}
106
107fn precheck_rustc_errors(tcx: TyCtxt<'_>) -> bool {
111 type QueryResult = Result<(), ErrorGuaranteed>;
112
113 tcx.par_hir_for_each_module(|module| {
114 tcx.ensure_ok().check_mod_attrs(module);
115 tcx.ensure_ok().check_mod_unstable_api_usage(module);
116 });
117
118 let _: QueryResult = tcx.ensure_result().check_type_wf(());
119 for &trait_def_id in tcx.all_local_trait_impls(()).keys() {
120 let _: QueryResult = tcx.ensure_result().coherent_trait(trait_def_id);
121 }
122 let _: QueryResult = tcx.ensure_result().crate_inherent_impls_validity_check(());
123 let _: QueryResult = tcx.ensure_result().crate_inherent_impls_overlap_check(());
124
125 tcx.par_hir_body_owners(|def_id| {
126 let def_kind = tcx.def_kind(def_id);
127 if !matches!(def_kind, rustc_hir::def::DefKind::AnonConst)
128 && !tcx.is_typeck_child(def_id.to_def_id())
129 {
130 tcx.ensure_ok().typeck(def_id);
131 }
132 });
133
134 tcx.dcx().has_errors().is_some()
135}
136
137fn check_late_rustc_errors(tcx: TyCtxt<'_>) {
140 tcx.par_hir_body_owners(|def_id| {
141 let _ = tcx.instance_mir(InstanceKind::Item(def_id.to_def_id()));
142 });
143
144 if tcx.dcx().err_count() == 0 {
145 let _ = tcx.collect_and_partition_mono_items(());
146 }
147}
148
149fn sysroot_has_target(sysroot: &std::path::Path, target: &str) -> bool {
151 sysroot.join("lib").join("rustlib").join(target).is_dir()
152}
153
154fn miri_sysroot_cache_file(target: &str) -> Option<PathBuf> {
156 let toolchain = toolchain_version();
157 let cache_dir = match env::var_os("CHARON_CACHE_DIR") {
158 Some(dir) => PathBuf::from(dir),
159 None => env::home_dir()?.join(".cache").join("charon"),
160 };
161 Some(
162 cache_dir
163 .join("full-mir-sysroot-cache")
164 .join(format!("{toolchain}-{target}")),
165 )
166}
167
168fn setup_miri_sysroot(target: &str) -> Option<PathBuf> {
171 if let Some(root) = env::var_os("CHARON_MIRI_SYSROOTS")
172 && let sysroot = PathBuf::from(root)
173 && sysroot_has_target(&sysroot, target)
174 {
175 return Some(sysroot);
176 }
177
178 if let Some(cache_file) = miri_sysroot_cache_file(target)
180 && let Ok(contents) = std::fs::read_to_string(&cache_file)
181 && let sysroot = PathBuf::from(contents.trim())
182 && sysroot_has_target(&sysroot, target)
183 {
184 return Some(sysroot);
185 }
186
187 let mut cmd = Command::new("cargo");
188 cmd.arg("miri")
189 .arg("setup")
190 .arg(format!("--target={target}"))
191 .arg("--print-sysroot")
192 .env_remove("RUSTC_WORKSPACE_WRAPPER")
193 .env_remove("RUSTC_WRAPPER");
194
195 let output = match cmd.output() {
196 Ok(output) => output,
197 Err(err) => {
198 eprintln!(
199 "warning: failed to run `cargo miri setup` for target `{target}`; \
200 falling back to rustc's default sysroot: {err}"
201 );
202 return None;
203 }
204 };
205
206 if !output.status.success() {
207 let stderr = String::from_utf8_lossy(&output.stderr);
208 eprintln!(
209 "warning: `cargo miri setup` failed for target `{target}`; \
210 falling back to rustc's default sysroot: {}",
211 stderr.trim()
212 );
213 return None;
214 }
215
216 let stdout = String::from_utf8_lossy(&output.stdout);
217 let sysroot = stdout.lines().map(str::trim).find(|line| !line.is_empty());
218 match sysroot {
219 Some(sysroot) => {
220 if let Some(cache_file) = miri_sysroot_cache_file(target)
222 && let Some(cache_dir) = cache_file.parent()
223 && std::fs::create_dir_all(cache_dir).is_ok()
224 {
225 let _ = std::fs::write(&cache_file, sysroot);
226 }
227 Some(PathBuf::from(sysroot))
228 }
229 None => {
230 eprintln!(
231 "warning: `cargo miri setup --print-sysroot` printed no sysroot for target \
232 `{target}`; falling back to rustc's default sysroot"
233 );
234 None
235 }
236 }
237}
238
239pub fn run_rustc_driver() -> Result<Option<(TransformCtx, CliOpts)>, CharonFailure> {
243 let mut compiler_args: Vec<String> = env::args().skip(1).collect();
246 if compiler_args
249 .first()
250 .is_some_and(|arg| arg.starts_with("charon-dont-cache-this-"))
251 {
252 compiler_args.remove(0);
253 }
254 trace!(
255 "charon-driver called with args: {}",
256 compiler_args.iter().format(" ")
257 );
258
259 let is_workspace_dependency =
264 env::var("CHARON_USING_CARGO").is_ok() && env::var("CARGO_PRIMARY_PACKAGE").is_err();
265 let emit_artifacts =
268 env::var("CHARON_USING_CARGO").is_ok() || env::var("CHARON_EMIT_ARTIFACTS").is_ok();
269 let mut codegen = emit_artifacts;
271 let target = arg_value(&compiler_args, "--target");
279 let is_selected_crate = !is_workspace_dependency && target.is_some();
281
282 let mut error_ctx = ErrorCtx::new();
283
284 let mut options = match env::var(options::CHARON_ARGS)
288 .ok()
289 .map(|opts| serde_json::from_str::<options::CliOpts>(&opts).unwrap())
290 {
291 Some(options) => options,
292 None if !is_selected_crate => Default::default(),
293 None => {
294 register_error!(
295 error_ctx,
296 no_crate,
297 "environment variable `CHARON_ARGS` not set; \
298 don't call `charon-driver` directly, call `charon rustc` instead"
299 );
300 return Err(CharonFailure::CharonError(1));
301 }
302 };
303
304 if options.sysroot.as_deref() == Some("default") {
305 } else if let Some(sysroot) = options.sysroot.as_ref()
307 && sysroot != "miri"
308 {
309 compiler_args.push(format!("--sysroot={sysroot}"));
310 } else if let Some(target) = target
311 && let Some(sysroot) = setup_miri_sysroot(target)
312 {
313 compiler_args.push(format!("--sysroot={}", sysroot.display()));
315 codegen = false;
317 }
318
319 let output = if !is_selected_crate {
320 trace!("Skipping charon; running compiler normally instead.");
321 run_compiler_with_callbacks(compiler_args, &mut RunCompilerNormallyCallbacks)?;
323 None
324 } else {
325 options.apply_preset();
326
327 error_ctx.continue_on_failure = !options.abort_on_error;
328 error_ctx.error_on_warnings = options.error_on_warnings;
329
330 for extra_flag in options.rustc_args.iter().cloned() {
331 compiler_args.push(extra_flag);
332 }
333
334 let mut callback = CharonCallbacks {
336 options: &options,
337 emit_artifacts,
338 codegen,
339 error_ctx: Some(error_ctx),
340 transform_ctx: None,
341 };
342 charon_lib::timing::time("rustc-driver", || {
343 run_compiler_with_callbacks(compiler_args, &mut callback)
344 })?;
345 let ctx = callback.transform_ctx.ok_or(CharonFailure::RustcError)?;
347 Some((ctx, options))
348 };
349 Ok(output)
350}
351
352pub struct CharonCallbacks<'a> {
354 options: &'a CliOpts,
355 emit_artifacts: bool,
358 codegen: bool,
360 error_ctx: Option<ErrorCtx>,
362 transform_ctx: Option<TransformCtx>,
365}
366impl<'a> Callbacks for CharonCallbacks<'a> {
367 fn config(&mut self, config: &mut Config) {
368 setup_compiler(
369 config,
370 self.options,
371 true,
372 self.emit_artifacts,
373 self.codegen,
374 );
375 }
376
377 fn after_expansion<'tcx>(&mut self, compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
382 rustc_hir::def_id::DEF_ID_DEBUG
384 .swap(&(def_id_debug as fn(_, &mut fmt::Formatter<'_>) -> _));
385
386 if charon_lib::timing::time("rustc-precheck-errors", || precheck_rustc_errors(tcx)) {
387 return Compilation::Continue;
388 }
389
390 self.transform_ctx = charon_lib::timing::time("translate-crate", || {
391 translate_crate::translate(
392 tcx,
393 self.options,
394 self.error_ctx.take().unwrap(),
395 compiler.sess.opts.sysroot.path().to_owned(),
396 )
397 })
398 .ok();
399
400 Compilation::Continue
401 }
402 fn after_analysis<'tcx>(&mut self, _compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
403 if !self.emit_artifacts {
404 charon_lib::timing::time("rustc-late-checks", || check_late_rustc_errors(tcx));
405 }
406 Compilation::Continue
407 }
408}
409
410pub struct RunCompilerNormallyCallbacks;
412
413impl Callbacks for RunCompilerNormallyCallbacks {
414 fn config(&mut self, config: &mut Config) {
415 setup_compiler(config, &Default::default(), false, true, true);
416 }
417}
418
419fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421 rustc_middle::ty::tls::with_opt(|opt_tcx| {
422 if let Some(tcx) = opt_tcx {
423 let crate_name = if def_id.is_local() {
424 tcx.crate_name(rustc_hir::def_id::LOCAL_CRATE)
425 } else {
426 tcx.cstore_untracked().crate_name(def_id.krate)
427 };
428 write!(
429 f,
430 "{}{}",
431 crate_name,
432 tcx.def_path(def_id).to_string_no_crate_verbose()
433 )?;
434 } else {
435 write!(f, "<can't access `tcx` to print `DefId` path>")?;
436 }
437 Ok(())
438 })
439}