rustc_interface/
util.rs

1use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
2use std::path::{Path, PathBuf};
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::sync::{Arc, OnceLock};
5use std::{env, thread};
6
7use rustc_ast as ast;
8use rustc_codegen_ssa::traits::CodegenBackend;
9use rustc_data_structures::jobserver::Proxy;
10use rustc_data_structures::sync;
11use rustc_metadata::{DylibError, load_symbol_from_dylib};
12use rustc_middle::ty::CurrentGcx;
13use rustc_parse::validate_attr;
14use rustc_session::config::{Cfg, OutFileName, OutputFilenames, OutputTypes, host_tuple};
15use rustc_session::lint::{self, BuiltinLintDiag, LintBuffer};
16use rustc_session::output::{CRATE_TYPES, categorize_crate_type};
17use rustc_session::{EarlyDiagCtxt, Session, filesearch};
18use rustc_span::edit_distance::find_best_match_for_name;
19use rustc_span::edition::Edition;
20use rustc_span::source_map::SourceMapInputs;
21use rustc_span::{SessionGlobals, Symbol, sym};
22use rustc_target::spec::Target;
23use tracing::info;
24
25use crate::errors;
26
27/// Function pointer type that constructs a new CodegenBackend.
28type MakeBackendFn = fn() -> Box<dyn CodegenBackend>;
29
30/// Adds `target_feature = "..."` cfgs for a variety of platform
31/// specific features (SSE, NEON etc.).
32///
33/// This is performed by checking whether a set of permitted features
34/// is available on the target machine, by querying the codegen backend.
35pub(crate) fn add_configuration(
36    cfg: &mut Cfg,
37    sess: &mut Session,
38    codegen_backend: &dyn CodegenBackend,
39) {
40    let tf = sym::target_feature;
41    let tf_cfg = codegen_backend.target_config(sess);
42
43    sess.unstable_target_features.extend(tf_cfg.unstable_target_features.iter().copied());
44    sess.target_features.extend(tf_cfg.target_features.iter().copied());
45
46    cfg.extend(tf_cfg.target_features.into_iter().map(|feat| (tf, Some(feat))));
47
48    if tf_cfg.has_reliable_f16 {
49        cfg.insert((sym::target_has_reliable_f16, None));
50    }
51    if tf_cfg.has_reliable_f16_math {
52        cfg.insert((sym::target_has_reliable_f16_math, None));
53    }
54    if tf_cfg.has_reliable_f128 {
55        cfg.insert((sym::target_has_reliable_f128, None));
56    }
57    if tf_cfg.has_reliable_f128_math {
58        cfg.insert((sym::target_has_reliable_f128_math, None));
59    }
60
61    if sess.crt_static(None) {
62        cfg.insert((tf, Some(sym::crt_dash_static)));
63    }
64}
65
66/// Ensures that all target features required by the ABI are present.
67/// Must be called after `unstable_target_features` has been populated!
68pub(crate) fn check_abi_required_features(sess: &Session) {
69    let abi_feature_constraints = sess.target.abi_required_features();
70    // We check this against `unstable_target_features` as that is conveniently already
71    // back-translated to rustc feature names, taking into account `-Ctarget-cpu` and `-Ctarget-feature`.
72    // Just double-check that the features we care about are actually on our list.
73    for feature in
74        abi_feature_constraints.required.iter().chain(abi_feature_constraints.incompatible.iter())
75    {
76        assert!(
77            sess.target.rust_target_features().iter().any(|(name, ..)| feature == name),
78            "target feature {feature} is required/incompatible for the current ABI but not a recognized feature for this target"
79        );
80    }
81
82    for feature in abi_feature_constraints.required {
83        if !sess.unstable_target_features.contains(&Symbol::intern(feature)) {
84            sess.dcx().emit_warn(errors::AbiRequiredTargetFeature { feature, enabled: "enabled" });
85        }
86    }
87    for feature in abi_feature_constraints.incompatible {
88        if sess.unstable_target_features.contains(&Symbol::intern(feature)) {
89            sess.dcx().emit_warn(errors::AbiRequiredTargetFeature { feature, enabled: "disabled" });
90        }
91    }
92}
93
94pub static STACK_SIZE: OnceLock<usize> = OnceLock::new();
95pub const DEFAULT_STACK_SIZE: usize = 8 * 1024 * 1024;
96
97fn init_stack_size(early_dcx: &EarlyDiagCtxt) -> usize {
98    // Obey the environment setting or default
99    *STACK_SIZE.get_or_init(|| {
100        env::var_os("RUST_MIN_STACK")
101            .as_ref()
102            .map(|os_str| os_str.to_string_lossy())
103            // if someone finds out `export RUST_MIN_STACK=640000` isn't enough stack
104            // they might try to "unset" it by running `RUST_MIN_STACK=  rustc code.rs`
105            // this is wrong, but std would nonetheless "do what they mean", so let's do likewise
106            .filter(|s| !s.trim().is_empty())
107            // rustc is a batch program, so error early on inputs which are unlikely to be intended
108            // so no one thinks we parsed them setting `RUST_MIN_STACK="64 megabytes"`
109            // FIXME: we could accept `RUST_MIN_STACK=64MB`, perhaps?
110            .map(|s| {
111                let s = s.trim();
112                // FIXME(workingjubilee): add proper diagnostics when we factor out "pre-run" setup
113                #[allow(rustc::untranslatable_diagnostic, rustc::diagnostic_outside_of_impl)]
114                s.parse::<usize>().unwrap_or_else(|_| {
115                    let mut err = early_dcx.early_struct_fatal(format!(
116                        r#"`RUST_MIN_STACK` should be a number of bytes, but was "{s}""#,
117                    ));
118                    err.note("you can also unset `RUST_MIN_STACK` to use the default stack size");
119                    err.emit()
120                })
121            })
122            // otherwise pick a consistent default
123            .unwrap_or(DEFAULT_STACK_SIZE)
124    })
125}
126
127fn run_in_thread_with_globals<F: FnOnce(CurrentGcx, Arc<Proxy>) -> R + Send, R: Send>(
128    thread_stack_size: usize,
129    edition: Edition,
130    sm_inputs: SourceMapInputs,
131    extra_symbols: &[&'static str],
132    f: F,
133) -> R {
134    // The "thread pool" is a single spawned thread in the non-parallel
135    // compiler. We run on a spawned thread instead of the main thread (a) to
136    // provide control over the stack size, and (b) to increase similarity with
137    // the parallel compiler, in particular to ensure there is no accidental
138    // sharing of data between the main thread and the compilation thread
139    // (which might cause problems for the parallel compiler).
140    let builder = thread::Builder::new().name("rustc".to_string()).stack_size(thread_stack_size);
141
142    // We build the session globals and run `f` on the spawned thread, because
143    // `SessionGlobals` does not impl `Send` in the non-parallel compiler.
144    thread::scope(|s| {
145        // `unwrap` is ok here because `spawn_scoped` only panics if the thread
146        // name contains null bytes.
147        let r = builder
148            .spawn_scoped(s, move || {
149                rustc_span::create_session_globals_then(
150                    edition,
151                    extra_symbols,
152                    Some(sm_inputs),
153                    || f(CurrentGcx::new(), Proxy::new()),
154                )
155            })
156            .unwrap()
157            .join();
158
159        match r {
160            Ok(v) => v,
161            Err(e) => std::panic::resume_unwind(e),
162        }
163    })
164}
165
166pub(crate) fn run_in_thread_pool_with_globals<
167    F: FnOnce(CurrentGcx, Arc<Proxy>) -> R + Send,
168    R: Send,
169>(
170    thread_builder_diag: &EarlyDiagCtxt,
171    edition: Edition,
172    threads: usize,
173    extra_symbols: &[&'static str],
174    sm_inputs: SourceMapInputs,
175    f: F,
176) -> R {
177    use std::process;
178
179    use rustc_data_structures::defer;
180    use rustc_data_structures::sync::FromDyn;
181    use rustc_middle::ty::tls;
182    use rustc_query_impl::QueryCtxt;
183    use rustc_query_system::query::{QueryContext, break_query_cycles};
184
185    let thread_stack_size = init_stack_size(thread_builder_diag);
186
187    let registry = sync::Registry::new(std::num::NonZero::new(threads).unwrap());
188
189    if !sync::is_dyn_thread_safe() {
190        return run_in_thread_with_globals(
191            thread_stack_size,
192            edition,
193            sm_inputs,
194            extra_symbols,
195            |current_gcx, jobserver_proxy| {
196                // Register the thread for use with the `WorkerLocal` type.
197                registry.register();
198
199                f(current_gcx, jobserver_proxy)
200            },
201        );
202    }
203
204    let current_gcx = FromDyn::from(CurrentGcx::new());
205    let current_gcx2 = current_gcx.clone();
206
207    let proxy = Proxy::new();
208
209    let proxy_ = Arc::clone(&proxy);
210    let proxy__ = Arc::clone(&proxy);
211    let builder = rayon_core::ThreadPoolBuilder::new()
212        .thread_name(|_| "rustc".to_string())
213        .acquire_thread_handler(move || proxy_.acquire_thread())
214        .release_thread_handler(move || proxy__.release_thread())
215        .num_threads(threads)
216        .deadlock_handler(move || {
217            // On deadlock, creates a new thread and forwards information in thread
218            // locals to it. The new thread runs the deadlock handler.
219
220            let current_gcx2 = current_gcx2.clone();
221            let registry = rayon_core::Registry::current();
222            let session_globals = rustc_span::with_session_globals(|session_globals| {
223                session_globals as *const SessionGlobals as usize
224            });
225            thread::Builder::new()
226                .name("rustc query cycle handler".to_string())
227                .spawn(move || {
228                    let on_panic = defer(|| {
229                        eprintln!("internal compiler error: query cycle handler thread panicked, aborting process");
230                        // We need to abort here as we failed to resolve the deadlock,
231                        // otherwise the compiler could just hang,
232                        process::abort();
233                    });
234
235                    // Get a `GlobalCtxt` reference from `CurrentGcx` as we cannot rely on having a
236                    // `TyCtxt` TLS reference here.
237                    current_gcx2.access(|gcx| {
238                        tls::enter_context(&tls::ImplicitCtxt::new(gcx), || {
239                            tls::with(|tcx| {
240                                // Accessing session globals is sound as they outlive `GlobalCtxt`.
241                                // They are needed to hash query keys containing spans or symbols.
242                                let query_map = rustc_span::set_session_globals_then(unsafe { &*(session_globals as *const SessionGlobals) }, || {
243                                    // Ensure there was no errors collecting all active jobs.
244                                    // We need the complete map to ensure we find a cycle to break.
245                                    QueryCtxt::new(tcx).collect_active_jobs().ok().expect("failed to collect active queries in deadlock handler")
246                                });
247                                break_query_cycles(query_map, &registry);
248                            })
249                        })
250                    });
251
252                    on_panic.disable();
253                })
254                .unwrap();
255        })
256        .stack_size(thread_stack_size);
257
258    // We create the session globals on the main thread, then create the thread
259    // pool. Upon creation, each worker thread created gets a copy of the
260    // session globals in TLS. This is possible because `SessionGlobals` impls
261    // `Send` in the parallel compiler.
262    rustc_span::create_session_globals_then(edition, extra_symbols, Some(sm_inputs), || {
263        rustc_span::with_session_globals(|session_globals| {
264            let session_globals = FromDyn::from(session_globals);
265            builder
266                .build_scoped(
267                    // Initialize each new worker thread when created.
268                    move |thread: rayon_core::ThreadBuilder| {
269                        // Register the thread for use with the `WorkerLocal` type.
270                        registry.register();
271
272                        rustc_span::set_session_globals_then(session_globals.into_inner(), || {
273                            thread.run()
274                        })
275                    },
276                    // Run `f` on the first thread in the thread pool.
277                    move |pool: &rayon_core::ThreadPool| {
278                        pool.install(|| f(current_gcx.into_inner(), proxy))
279                    },
280                )
281                .unwrap()
282        })
283    })
284}
285
286#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
287fn load_backend_from_dylib(early_dcx: &EarlyDiagCtxt, path: &Path) -> MakeBackendFn {
288    match unsafe { load_symbol_from_dylib::<MakeBackendFn>(path, "__rustc_codegen_backend") } {
289        Ok(backend_sym) => backend_sym,
290        Err(DylibError::DlOpen(path, err)) => {
291            let err = format!("couldn't load codegen backend {path}{err}");
292            early_dcx.early_fatal(err);
293        }
294        Err(DylibError::DlSym(_path, err)) => {
295            let e = format!(
296                "`__rustc_codegen_backend` symbol lookup in the codegen backend failed{err}",
297            );
298            early_dcx.early_fatal(e);
299        }
300    }
301}
302
303/// Get the codegen backend based on the name and specified sysroot.
304///
305/// A name of `None` indicates that the default backend should be used.
306pub fn get_codegen_backend(
307    early_dcx: &EarlyDiagCtxt,
308    sysroot: &Path,
309    backend_name: Option<&str>,
310    target: &Target,
311) -> Box<dyn CodegenBackend> {
312    static LOAD: OnceLock<unsafe fn() -> Box<dyn CodegenBackend>> = OnceLock::new();
313
314    let load = LOAD.get_or_init(|| {
315        let backend = backend_name
316            .or(target.default_codegen_backend.as_deref())
317            .or(option_env!("CFG_DEFAULT_CODEGEN_BACKEND"))
318            .unwrap_or("llvm");
319
320        match backend {
321            filename if filename.contains('.') => {
322                load_backend_from_dylib(early_dcx, filename.as_ref())
323            }
324            #[cfg(feature = "llvm")]
325            "llvm" => rustc_codegen_llvm::LlvmCodegenBackend::new,
326            backend_name => get_codegen_sysroot(early_dcx, sysroot, backend_name),
327        }
328    });
329
330    // SAFETY: In case of a builtin codegen backend this is safe. In case of an external codegen
331    // backend we hope that the backend links against the same rustc_driver version. If this is not
332    // the case, we get UB.
333    unsafe { load() }
334}
335
336// This is used for rustdoc, but it uses similar machinery to codegen backend
337// loading, so we leave the code here. It is potentially useful for other tools
338// that want to invoke the rustc binary while linking to rustc as well.
339pub fn rustc_path<'a>() -> Option<&'a Path> {
340    static RUSTC_PATH: OnceLock<Option<PathBuf>> = OnceLock::new();
341
342    const BIN_PATH: &str = env!("RUSTC_INSTALL_BINDIR");
343
344    RUSTC_PATH.get_or_init(|| get_rustc_path_inner(BIN_PATH)).as_deref()
345}
346
347fn get_rustc_path_inner(bin_path: &str) -> Option<PathBuf> {
348    let candidate = filesearch::get_or_default_sysroot()
349        .join(bin_path)
350        .join(if cfg!(target_os = "windows") { "rustc.exe" } else { "rustc" });
351    candidate.exists().then_some(candidate)
352}
353
354#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
355fn get_codegen_sysroot(
356    early_dcx: &EarlyDiagCtxt,
357    sysroot: &Path,
358    backend_name: &str,
359) -> MakeBackendFn {
360    // For now we only allow this function to be called once as it'll dlopen a
361    // few things, which seems to work best if we only do that once. In
362    // general this assertion never trips due to the once guard in `get_codegen_backend`,
363    // but there's a few manual calls to this function in this file we protect
364    // against.
365    static LOADED: AtomicBool = AtomicBool::new(false);
366    assert!(
367        !LOADED.fetch_or(true, Ordering::SeqCst),
368        "cannot load the default codegen backend twice"
369    );
370
371    let target = host_tuple();
372    let sysroot_candidates = filesearch::sysroot_with_fallback(&sysroot);
373
374    let sysroot = sysroot_candidates
375        .iter()
376        .map(|sysroot| {
377            filesearch::make_target_lib_path(sysroot, target).with_file_name("codegen-backends")
378        })
379        .find(|f| {
380            info!("codegen backend candidate: {}", f.display());
381            f.exists()
382        })
383        .unwrap_or_else(|| {
384            let candidates = sysroot_candidates
385                .iter()
386                .map(|p| p.display().to_string())
387                .collect::<Vec<_>>()
388                .join("\n* ");
389            let err = format!(
390                "failed to find a `codegen-backends` folder \
391                           in the sysroot candidates:\n* {candidates}"
392            );
393            early_dcx.early_fatal(err);
394        });
395
396    info!("probing {} for a codegen backend", sysroot.display());
397
398    let d = sysroot.read_dir().unwrap_or_else(|e| {
399        let err = format!(
400            "failed to load default codegen backend, couldn't \
401                           read `{}`: {}",
402            sysroot.display(),
403            e
404        );
405        early_dcx.early_fatal(err);
406    });
407
408    let mut file: Option<PathBuf> = None;
409
410    let expected_names = &[
411        format!("rustc_codegen_{}-{}", backend_name, env!("CFG_RELEASE")),
412        format!("rustc_codegen_{backend_name}"),
413    ];
414    for entry in d.filter_map(|e| e.ok()) {
415        let path = entry.path();
416        let Some(filename) = path.file_name().and_then(|s| s.to_str()) else { continue };
417        if !(filename.starts_with(DLL_PREFIX) && filename.ends_with(DLL_SUFFIX)) {
418            continue;
419        }
420        let name = &filename[DLL_PREFIX.len()..filename.len() - DLL_SUFFIX.len()];
421        if !expected_names.iter().any(|expected| expected == name) {
422            continue;
423        }
424        if let Some(ref prev) = file {
425            let err = format!(
426                "duplicate codegen backends found\n\
427                               first:  {}\n\
428                               second: {}\n\
429            ",
430                prev.display(),
431                path.display()
432            );
433            early_dcx.early_fatal(err);
434        }
435        file = Some(path.clone());
436    }
437
438    match file {
439        Some(ref s) => load_backend_from_dylib(early_dcx, s),
440        None => {
441            let err = format!("unsupported builtin codegen backend `{backend_name}`");
442            early_dcx.early_fatal(err);
443        }
444    }
445}
446
447pub(crate) fn check_attr_crate_type(
448    sess: &Session,
449    attrs: &[ast::Attribute],
450    lint_buffer: &mut LintBuffer,
451) {
452    // Unconditionally collect crate types from attributes to make them used
453    for a in attrs.iter() {
454        if a.has_name(sym::crate_type) {
455            if let Some(n) = a.value_str() {
456                if categorize_crate_type(n).is_some() {
457                    return;
458                }
459
460                if let ast::MetaItemKind::NameValue(spanned) = a.meta_kind().unwrap() {
461                    let span = spanned.span;
462                    let candidate = find_best_match_for_name(
463                        &CRATE_TYPES.iter().map(|(k, _)| *k).collect::<Vec<_>>(),
464                        n,
465                        None,
466                    );
467                    lint_buffer.buffer_lint(
468                        lint::builtin::UNKNOWN_CRATE_TYPES,
469                        ast::CRATE_NODE_ID,
470                        span,
471                        BuiltinLintDiag::UnknownCrateTypes { span, candidate },
472                    );
473                }
474            } else {
475                // This is here mainly to check for using a macro, such as
476                // `#![crate_type = foo!()]`. That is not supported since the
477                // crate type needs to be known very early in compilation long
478                // before expansion. Otherwise, validation would normally be
479                // caught during semantic analysis via `TyCtxt::check_mod_attrs`,
480                // but by the time that runs the macro is expanded, and it doesn't
481                // give an error.
482                validate_attr::emit_fatal_malformed_builtin_attribute(
483                    &sess.psess,
484                    a,
485                    sym::crate_type,
486                );
487            }
488        }
489    }
490}
491
492fn multiple_output_types_to_stdout(
493    output_types: &OutputTypes,
494    single_output_file_is_stdout: bool,
495) -> bool {
496    use std::io::IsTerminal;
497    if std::io::stdout().is_terminal() {
498        // If stdout is a tty, check if multiple text output types are
499        // specified by `--emit foo=- --emit bar=-` or `-o - --emit foo,bar`
500        let named_text_types = output_types
501            .iter()
502            .filter(|(f, o)| f.is_text_output() && *o == &Some(OutFileName::Stdout))
503            .count();
504        let unnamed_text_types =
505            output_types.iter().filter(|(f, o)| f.is_text_output() && o.is_none()).count();
506        named_text_types > 1 || unnamed_text_types > 1 && single_output_file_is_stdout
507    } else {
508        // Otherwise, all the output types should be checked
509        let named_types =
510            output_types.values().filter(|o| *o == &Some(OutFileName::Stdout)).count();
511        let unnamed_types = output_types.values().filter(|o| o.is_none()).count();
512        named_types > 1 || unnamed_types > 1 && single_output_file_is_stdout
513    }
514}
515
516pub fn build_output_filenames(attrs: &[ast::Attribute], sess: &Session) -> OutputFilenames {
517    if multiple_output_types_to_stdout(
518        &sess.opts.output_types,
519        sess.io.output_file == Some(OutFileName::Stdout),
520    ) {
521        sess.dcx().emit_fatal(errors::MultipleOutputTypesToStdout);
522    }
523
524    let crate_name = sess
525        .opts
526        .crate_name
527        .clone()
528        .or_else(|| rustc_attr_parsing::find_crate_name(attrs).map(|n| n.to_string()));
529
530    match sess.io.output_file {
531        None => {
532            // "-" as input file will cause the parser to read from stdin so we
533            // have to make up a name
534            // We want to toss everything after the final '.'
535            let dirpath = sess.io.output_dir.clone().unwrap_or_default();
536
537            // If a crate name is present, we use it as the link name
538            let stem = crate_name.clone().unwrap_or_else(|| sess.io.input.filestem().to_owned());
539
540            OutputFilenames::new(
541                dirpath,
542                crate_name.unwrap_or_else(|| stem.replace('-', "_")),
543                stem,
544                None,
545                sess.io.temps_dir.clone(),
546                sess.opts.cg.extra_filename.clone(),
547                sess.opts.output_types.clone(),
548            )
549        }
550
551        Some(ref out_file) => {
552            let unnamed_output_types =
553                sess.opts.output_types.values().filter(|a| a.is_none()).count();
554            let ofile = if unnamed_output_types > 1 {
555                sess.dcx().emit_warn(errors::MultipleOutputTypesAdaption);
556                None
557            } else {
558                if !sess.opts.cg.extra_filename.is_empty() {
559                    sess.dcx().emit_warn(errors::IgnoringExtraFilename);
560                }
561                Some(out_file.clone())
562            };
563            if sess.io.output_dir != None {
564                sess.dcx().emit_warn(errors::IgnoringOutDir);
565            }
566
567            let out_filestem =
568                out_file.filestem().unwrap_or_default().to_str().unwrap().to_string();
569            OutputFilenames::new(
570                out_file.parent().unwrap_or_else(|| Path::new("")).to_path_buf(),
571                crate_name.unwrap_or_else(|| out_filestem.replace('-', "_")),
572                out_filestem,
573                ofile,
574                sess.io.temps_dir.clone(),
575                sess.opts.cg.extra_filename.clone(),
576                sess.opts.output_types.clone(),
577            )
578        }
579    }
580}
581
582/// Returns a version string such as "1.46.0 (04488afe3 2020-08-24)" when invoked by an in-tree tool.
583pub macro version_str() {
584    option_env!("CFG_VERSION")
585}
586
587/// Returns the version string for `rustc` itself (which may be different from a tool version).
588pub fn rustc_version_str() -> Option<&'static str> {
589    version_str!()
590}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy