• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::errors;
2 use info;
3 use libloading::Library;
4 use rustc_ast as ast;
5 use rustc_codegen_ssa::traits::CodegenBackend;
6 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
7 #[cfg(parallel_compiler)]
8 use rustc_data_structures::sync;
9 use rustc_errors::registry::Registry;
10 use rustc_parse::validate_attr;
11 use rustc_session as session;
12 use rustc_session::config::CheckCfg;
13 use rustc_session::config::{self, CrateType};
14 use rustc_session::config::{OutFileName, OutputFilenames, OutputTypes};
15 use rustc_session::filesearch::sysroot_candidates;
16 use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer};
17 use rustc_session::parse::CrateConfig;
18 use rustc_session::{filesearch, output, Session};
19 use rustc_span::edit_distance::find_best_match_for_name;
20 use rustc_span::edition::Edition;
21 use rustc_span::source_map::FileLoader;
22 use rustc_span::symbol::{sym, Symbol};
23 use session::{CompilerIO, EarlyErrorHandler};
24 use std::env;
25 use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
26 use std::mem;
27 use std::path::{Path, PathBuf};
28 use std::sync::atomic::{AtomicBool, Ordering};
29 use std::sync::OnceLock;
30 use std::thread;
31 
32 /// Function pointer type that constructs a new CodegenBackend.
33 pub type MakeBackendFn = fn() -> Box<dyn CodegenBackend>;
34 
35 /// Adds `target_feature = "..."` cfgs for a variety of platform
36 /// specific features (SSE, NEON etc.).
37 ///
38 /// This is performed by checking whether a set of permitted features
39 /// is available on the target machine, by querying the codegen backend.
add_configuration( cfg: &mut CrateConfig, sess: &mut Session, codegen_backend: &dyn CodegenBackend, )40 pub fn add_configuration(
41     cfg: &mut CrateConfig,
42     sess: &mut Session,
43     codegen_backend: &dyn CodegenBackend,
44 ) {
45     let tf = sym::target_feature;
46 
47     let unstable_target_features = codegen_backend.target_features(sess, true);
48     sess.unstable_target_features.extend(unstable_target_features.iter().cloned());
49 
50     let target_features = codegen_backend.target_features(sess, false);
51     sess.target_features.extend(target_features.iter().cloned());
52 
53     cfg.extend(target_features.into_iter().map(|feat| (tf, Some(feat))));
54 
55     if sess.crt_static(None) {
56         cfg.insert((tf, Some(sym::crt_dash_static)));
57     }
58 }
59 
create_session( handler: &EarlyErrorHandler, sopts: config::Options, cfg: FxHashSet<(String, Option<String>)>, check_cfg: CheckCfg, locale_resources: &'static [&'static str], file_loader: Option<Box<dyn FileLoader + Send + Sync + 'static>>, io: CompilerIO, lint_caps: FxHashMap<lint::LintId, lint::Level>, make_codegen_backend: Option< Box<dyn FnOnce(&config::Options) -> Box<dyn CodegenBackend> + Send>, >, descriptions: Registry, ) -> (Session, Box<dyn CodegenBackend>)60 pub fn create_session(
61     handler: &EarlyErrorHandler,
62     sopts: config::Options,
63     cfg: FxHashSet<(String, Option<String>)>,
64     check_cfg: CheckCfg,
65     locale_resources: &'static [&'static str],
66     file_loader: Option<Box<dyn FileLoader + Send + Sync + 'static>>,
67     io: CompilerIO,
68     lint_caps: FxHashMap<lint::LintId, lint::Level>,
69     make_codegen_backend: Option<
70         Box<dyn FnOnce(&config::Options) -> Box<dyn CodegenBackend> + Send>,
71     >,
72     descriptions: Registry,
73 ) -> (Session, Box<dyn CodegenBackend>) {
74     let codegen_backend = if let Some(make_codegen_backend) = make_codegen_backend {
75         make_codegen_backend(&sopts)
76     } else {
77         get_codegen_backend(
78             handler,
79             &sopts.maybe_sysroot,
80             sopts.unstable_opts.codegen_backend.as_deref(),
81         )
82     };
83 
84     // target_override is documented to be called before init(), so this is okay
85     let target_override = codegen_backend.target_override(&sopts);
86 
87     let bundle = match rustc_errors::fluent_bundle(
88         sopts.maybe_sysroot.clone(),
89         sysroot_candidates().to_vec(),
90         sopts.unstable_opts.translate_lang.clone(),
91         sopts.unstable_opts.translate_additional_ftl.as_deref(),
92         sopts.unstable_opts.translate_directionality_markers,
93     ) {
94         Ok(bundle) => bundle,
95         Err(e) => {
96             handler.early_error(format!("failed to load fluent bundle: {e}"));
97         }
98     };
99 
100     let mut locale_resources = Vec::from(locale_resources);
101     locale_resources.push(codegen_backend.locale_resource());
102 
103     let mut sess = session::build_session(
104         handler,
105         sopts,
106         io,
107         bundle,
108         descriptions,
109         locale_resources,
110         lint_caps,
111         file_loader,
112         target_override,
113         rustc_version_str().unwrap_or("unknown"),
114     );
115 
116     codegen_backend.init(&sess);
117 
118     let mut cfg = config::build_configuration(&sess, config::to_crate_config(cfg));
119     add_configuration(&mut cfg, &mut sess, &*codegen_backend);
120 
121     let mut check_cfg = config::to_crate_check_config(check_cfg);
122     check_cfg.fill_well_known(&sess.target);
123 
124     sess.parse_sess.config = cfg;
125     sess.parse_sess.check_config = check_cfg;
126 
127     (sess, codegen_backend)
128 }
129 
130 const STACK_SIZE: usize = 8 * 1024 * 1024;
131 
get_stack_size() -> Option<usize>132 fn get_stack_size() -> Option<usize> {
133     // FIXME: Hacks on hacks. If the env is trying to override the stack size
134     // then *don't* set it explicitly.
135     env::var_os("RUST_MIN_STACK").is_none().then_some(STACK_SIZE)
136 }
137 
138 #[cfg(not(parallel_compiler))]
run_in_thread_pool_with_globals<F: FnOnce() -> R + Send, R: Send>( edition: Edition, _threads: usize, f: F, ) -> R139 pub(crate) fn run_in_thread_pool_with_globals<F: FnOnce() -> R + Send, R: Send>(
140     edition: Edition,
141     _threads: usize,
142     f: F,
143 ) -> R {
144     // The "thread pool" is a single spawned thread in the non-parallel
145     // compiler. We run on a spawned thread instead of the main thread (a) to
146     // provide control over the stack size, and (b) to increase similarity with
147     // the parallel compiler, in particular to ensure there is no accidental
148     // sharing of data between the main thread and the compilation thread
149     // (which might cause problems for the parallel compiler).
150     let mut builder = thread::Builder::new().name("rustc".to_string());
151     if let Some(size) = get_stack_size() {
152         builder = builder.stack_size(size);
153     }
154 
155     // We build the session globals and run `f` on the spawned thread, because
156     // `SessionGlobals` does not impl `Send` in the non-parallel compiler.
157     thread::scope(|s| {
158         // `unwrap` is ok here because `spawn_scoped` only panics if the thread
159         // name contains null bytes.
160         let r = builder
161             .spawn_scoped(s, move || rustc_span::create_session_globals_then(edition, f))
162             .unwrap()
163             .join();
164 
165         match r {
166             Ok(v) => v,
167             Err(e) => std::panic::resume_unwind(e),
168         }
169     })
170 }
171 
172 #[cfg(parallel_compiler)]
run_in_thread_pool_with_globals<F: FnOnce() -> R + Send, R: Send>( edition: Edition, threads: usize, f: F, ) -> R173 pub(crate) fn run_in_thread_pool_with_globals<F: FnOnce() -> R + Send, R: Send>(
174     edition: Edition,
175     threads: usize,
176     f: F,
177 ) -> R {
178     use rustc_data_structures::jobserver;
179     use rustc_middle::ty::tls;
180     use rustc_query_impl::QueryCtxt;
181     use rustc_query_system::query::{deadlock, QueryContext};
182 
183     let registry = sync::Registry::new(threads);
184     let mut builder = rayon::ThreadPoolBuilder::new()
185         .thread_name(|_| "rustc".to_string())
186         .acquire_thread_handler(jobserver::acquire_thread)
187         .release_thread_handler(jobserver::release_thread)
188         .num_threads(threads)
189         .deadlock_handler(|| {
190             // On deadlock, creates a new thread and forwards information in thread
191             // locals to it. The new thread runs the deadlock handler.
192             let query_map = tls::with(|tcx| {
193                 QueryCtxt::new(tcx)
194                     .try_collect_active_jobs()
195                     .expect("active jobs shouldn't be locked in deadlock handler")
196             });
197             let registry = rayon_core::Registry::current();
198             thread::spawn(move || deadlock(query_map, &registry));
199         });
200     if let Some(size) = get_stack_size() {
201         builder = builder.stack_size(size);
202     }
203 
204     // We create the session globals on the main thread, then create the thread
205     // pool. Upon creation, each worker thread created gets a copy of the
206     // session globals in TLS. This is possible because `SessionGlobals` impls
207     // `Send` in the parallel compiler.
208     rustc_span::create_session_globals_then(edition, || {
209         rustc_span::with_session_globals(|session_globals| {
210             builder
211                 .build_scoped(
212                     // Initialize each new worker thread when created.
213                     move |thread: rayon::ThreadBuilder| {
214                         // Register the thread for use with the `WorkerLocal` type.
215                         registry.register();
216 
217                         rustc_span::set_session_globals_then(session_globals, || thread.run())
218                     },
219                     // Run `f` on the first thread in the thread pool.
220                     move |pool: &rayon::ThreadPool| pool.install(f),
221                 )
222                 .unwrap()
223         })
224     })
225 }
226 
load_backend_from_dylib(handler: &EarlyErrorHandler, path: &Path) -> MakeBackendFn227 fn load_backend_from_dylib(handler: &EarlyErrorHandler, path: &Path) -> MakeBackendFn {
228     let lib = unsafe { Library::new(path) }.unwrap_or_else(|err| {
229         let err = format!("couldn't load codegen backend {path:?}: {err}");
230         handler.early_error(err);
231     });
232 
233     let backend_sym = unsafe { lib.get::<MakeBackendFn>(b"__rustc_codegen_backend") }
234         .unwrap_or_else(|e| {
235             let err = format!("couldn't load codegen backend: {e}");
236             handler.early_error(err);
237         });
238 
239     // Intentionally leak the dynamic library. We can't ever unload it
240     // since the library can make things that will live arbitrarily long.
241     let backend_sym = unsafe { backend_sym.into_raw() };
242     mem::forget(lib);
243 
244     *backend_sym
245 }
246 
247 /// Get the codegen backend based on the name and specified sysroot.
248 ///
249 /// A name of `None` indicates that the default backend should be used.
get_codegen_backend( handler: &EarlyErrorHandler, maybe_sysroot: &Option<PathBuf>, backend_name: Option<&str>, ) -> Box<dyn CodegenBackend>250 pub fn get_codegen_backend(
251     handler: &EarlyErrorHandler,
252     maybe_sysroot: &Option<PathBuf>,
253     backend_name: Option<&str>,
254 ) -> Box<dyn CodegenBackend> {
255     static LOAD: OnceLock<unsafe fn() -> Box<dyn CodegenBackend>> = OnceLock::new();
256 
257     let load = LOAD.get_or_init(|| {
258         let default_codegen_backend = option_env!("CFG_DEFAULT_CODEGEN_BACKEND").unwrap_or("llvm");
259 
260         match backend_name.unwrap_or(default_codegen_backend) {
261             filename if filename.contains('.') => {
262                 load_backend_from_dylib(handler, filename.as_ref())
263             }
264             #[cfg(feature = "llvm")]
265             "llvm" => rustc_codegen_llvm::LlvmCodegenBackend::new,
266             backend_name => get_codegen_sysroot(handler, maybe_sysroot, backend_name),
267         }
268     });
269 
270     // SAFETY: In case of a builtin codegen backend this is safe. In case of an external codegen
271     // backend we hope that the backend links against the same rustc_driver version. If this is not
272     // the case, we get UB.
273     unsafe { load() }
274 }
275 
276 // This is used for rustdoc, but it uses similar machinery to codegen backend
277 // loading, so we leave the code here. It is potentially useful for other tools
278 // that want to invoke the rustc binary while linking to rustc as well.
rustc_path<'a>() -> Option<&'a Path>279 pub fn rustc_path<'a>() -> Option<&'a Path> {
280     static RUSTC_PATH: OnceLock<Option<PathBuf>> = OnceLock::new();
281 
282     const BIN_PATH: &str = env!("RUSTC_INSTALL_BINDIR");
283 
284     RUSTC_PATH.get_or_init(|| get_rustc_path_inner(BIN_PATH)).as_deref()
285 }
286 
get_rustc_path_inner(bin_path: &str) -> Option<PathBuf>287 fn get_rustc_path_inner(bin_path: &str) -> Option<PathBuf> {
288     sysroot_candidates().iter().find_map(|sysroot| {
289         let candidate = sysroot.join(bin_path).join(if cfg!(target_os = "windows") {
290             "rustc.exe"
291         } else {
292             "rustc"
293         });
294         candidate.exists().then_some(candidate)
295     })
296 }
297 
get_codegen_sysroot( handler: &EarlyErrorHandler, maybe_sysroot: &Option<PathBuf>, backend_name: &str, ) -> MakeBackendFn298 fn get_codegen_sysroot(
299     handler: &EarlyErrorHandler,
300     maybe_sysroot: &Option<PathBuf>,
301     backend_name: &str,
302 ) -> MakeBackendFn {
303     // For now we only allow this function to be called once as it'll dlopen a
304     // few things, which seems to work best if we only do that once. In
305     // general this assertion never trips due to the once guard in `get_codegen_backend`,
306     // but there's a few manual calls to this function in this file we protect
307     // against.
308     static LOADED: AtomicBool = AtomicBool::new(false);
309     assert!(
310         !LOADED.fetch_or(true, Ordering::SeqCst),
311         "cannot load the default codegen backend twice"
312     );
313 
314     let target = session::config::host_triple();
315     let sysroot_candidates = sysroot_candidates();
316 
317     let sysroot = maybe_sysroot
318         .iter()
319         .chain(sysroot_candidates.iter())
320         .map(|sysroot| {
321             filesearch::make_target_lib_path(sysroot, target).with_file_name("codegen-backends")
322         })
323         .find(|f| {
324             info!("codegen backend candidate: {}", f.display());
325             f.exists()
326         });
327     let sysroot = sysroot.unwrap_or_else(|| {
328         let candidates = sysroot_candidates
329             .iter()
330             .map(|p| p.display().to_string())
331             .collect::<Vec<_>>()
332             .join("\n* ");
333         let err = format!(
334             "failed to find a `codegen-backends` folder \
335                            in the sysroot candidates:\n* {candidates}"
336         );
337         handler.early_error(err);
338     });
339     info!("probing {} for a codegen backend", sysroot.display());
340 
341     let d = sysroot.read_dir().unwrap_or_else(|e| {
342         let err = format!(
343             "failed to load default codegen backend, couldn't \
344                            read `{}`: {}",
345             sysroot.display(),
346             e
347         );
348         handler.early_error(err);
349     });
350 
351     let mut file: Option<PathBuf> = None;
352 
353     let expected_names = &[
354         format!("rustc_codegen_{}-{}", backend_name, env!("CFG_RELEASE")),
355         format!("rustc_codegen_{backend_name}"),
356     ];
357     for entry in d.filter_map(|e| e.ok()) {
358         let path = entry.path();
359         let Some(filename) = path.file_name().and_then(|s| s.to_str()) else { continue };
360         if !(filename.starts_with(DLL_PREFIX) && filename.ends_with(DLL_SUFFIX)) {
361             continue;
362         }
363         let name = &filename[DLL_PREFIX.len()..filename.len() - DLL_SUFFIX.len()];
364         if !expected_names.iter().any(|expected| expected == name) {
365             continue;
366         }
367         if let Some(ref prev) = file {
368             let err = format!(
369                 "duplicate codegen backends found\n\
370                                first:  {}\n\
371                                second: {}\n\
372             ",
373                 prev.display(),
374                 path.display()
375             );
376             handler.early_error(err);
377         }
378         file = Some(path.clone());
379     }
380 
381     match file {
382         Some(ref s) => load_backend_from_dylib(handler, s),
383         None => {
384             let err = format!("unsupported builtin codegen backend `{backend_name}`");
385             handler.early_error(err);
386         }
387     }
388 }
389 
check_attr_crate_type( sess: &Session, attrs: &[ast::Attribute], lint_buffer: &mut LintBuffer, )390 pub(crate) fn check_attr_crate_type(
391     sess: &Session,
392     attrs: &[ast::Attribute],
393     lint_buffer: &mut LintBuffer,
394 ) {
395     // Unconditionally collect crate types from attributes to make them used
396     for a in attrs.iter() {
397         if a.has_name(sym::crate_type) {
398             if let Some(n) = a.value_str() {
399                 if categorize_crate_type(n).is_some() {
400                     return;
401                 }
402 
403                 if let ast::MetaItemKind::NameValue(spanned) = a.meta_kind().unwrap() {
404                     let span = spanned.span;
405                     let lev_candidate = find_best_match_for_name(
406                         &CRATE_TYPES.iter().map(|(k, _)| *k).collect::<Vec<_>>(),
407                         n,
408                         None,
409                     );
410                     if let Some(candidate) = lev_candidate {
411                         lint_buffer.buffer_lint_with_diagnostic(
412                             lint::builtin::UNKNOWN_CRATE_TYPES,
413                             ast::CRATE_NODE_ID,
414                             span,
415                             "invalid `crate_type` value",
416                             BuiltinLintDiagnostics::UnknownCrateTypes(
417                                 span,
418                                 "did you mean".to_string(),
419                                 format!("\"{candidate}\""),
420                             ),
421                         );
422                     } else {
423                         lint_buffer.buffer_lint(
424                             lint::builtin::UNKNOWN_CRATE_TYPES,
425                             ast::CRATE_NODE_ID,
426                             span,
427                             "invalid `crate_type` value",
428                         );
429                     }
430                 }
431             } else {
432                 // This is here mainly to check for using a macro, such as
433                 // #![crate_type = foo!()]. That is not supported since the
434                 // crate type needs to be known very early in compilation long
435                 // before expansion. Otherwise, validation would normally be
436                 // caught in AstValidator (via `check_builtin_attribute`), but
437                 // by the time that runs the macro is expanded, and it doesn't
438                 // give an error.
439                 validate_attr::emit_fatal_malformed_builtin_attribute(
440                     &sess.parse_sess,
441                     a,
442                     sym::crate_type,
443                 );
444             }
445         }
446     }
447 }
448 
449 const CRATE_TYPES: &[(Symbol, CrateType)] = &[
450     (sym::rlib, CrateType::Rlib),
451     (sym::dylib, CrateType::Dylib),
452     (sym::cdylib, CrateType::Cdylib),
453     (sym::lib, config::default_lib_output()),
454     (sym::staticlib, CrateType::Staticlib),
455     (sym::proc_dash_macro, CrateType::ProcMacro),
456     (sym::bin, CrateType::Executable),
457 ];
458 
categorize_crate_type(s: Symbol) -> Option<CrateType>459 fn categorize_crate_type(s: Symbol) -> Option<CrateType> {
460     Some(CRATE_TYPES.iter().find(|(key, _)| *key == s)?.1)
461 }
462 
collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<CrateType>463 pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<CrateType> {
464     // Unconditionally collect crate types from attributes to make them used
465     let attr_types: Vec<CrateType> = attrs
466         .iter()
467         .filter_map(|a| {
468             if a.has_name(sym::crate_type) {
469                 match a.value_str() {
470                     Some(s) => categorize_crate_type(s),
471                     _ => None,
472                 }
473             } else {
474                 None
475             }
476         })
477         .collect();
478 
479     // If we're generating a test executable, then ignore all other output
480     // styles at all other locations
481     if session.opts.test {
482         return vec![CrateType::Executable];
483     }
484 
485     // Only check command line flags if present. If no types are specified by
486     // command line, then reuse the empty `base` Vec to hold the types that
487     // will be found in crate attributes.
488     // JUSTIFICATION: before wrapper fn is available
489     #[allow(rustc::bad_opt_access)]
490     let mut base = session.opts.crate_types.clone();
491     if base.is_empty() {
492         base.extend(attr_types);
493         if base.is_empty() {
494             base.push(output::default_output_for_target(session));
495         } else {
496             base.sort();
497             base.dedup();
498         }
499     }
500 
501     base.retain(|crate_type| {
502         if output::invalid_output_for_target(session, *crate_type) {
503             session.emit_warning(errors::UnsupportedCrateTypeForTarget {
504                 crate_type: *crate_type,
505                 target_triple: &session.opts.target_triple,
506             });
507             false
508         } else {
509             true
510         }
511     });
512 
513     base
514 }
515 
multiple_output_types_to_stdout( output_types: &OutputTypes, single_output_file_is_stdout: bool, ) -> bool516 fn multiple_output_types_to_stdout(
517     output_types: &OutputTypes,
518     single_output_file_is_stdout: bool,
519 ) -> bool {
520     if atty::is(atty::Stream::Stdout) {
521         // If stdout is a tty, check if multiple text output types are
522         // specified by `--emit foo=- --emit bar=-` or `-o - --emit foo,bar`
523         let named_text_types = output_types
524             .iter()
525             .filter(|(f, o)| f.is_text_output() && *o == &Some(OutFileName::Stdout))
526             .count();
527         let unnamed_text_types =
528             output_types.iter().filter(|(f, o)| f.is_text_output() && o.is_none()).count();
529         named_text_types > 1 || unnamed_text_types > 1 && single_output_file_is_stdout
530     } else {
531         // Otherwise, all the output types should be checked
532         let named_types =
533             output_types.values().filter(|o| *o == &Some(OutFileName::Stdout)).count();
534         let unnamed_types = output_types.values().filter(|o| o.is_none()).count();
535         named_types > 1 || unnamed_types > 1 && single_output_file_is_stdout
536     }
537 }
538 
build_output_filenames(attrs: &[ast::Attribute], sess: &Session) -> OutputFilenames539 pub fn build_output_filenames(attrs: &[ast::Attribute], sess: &Session) -> OutputFilenames {
540     if multiple_output_types_to_stdout(
541         &sess.opts.output_types,
542         sess.io.output_file == Some(OutFileName::Stdout),
543     ) {
544         sess.emit_fatal(errors::MultipleOutputTypesToStdout);
545     }
546     match sess.io.output_file {
547         None => {
548             // "-" as input file will cause the parser to read from stdin so we
549             // have to make up a name
550             // We want to toss everything after the final '.'
551             let dirpath = sess.io.output_dir.clone().unwrap_or_default();
552 
553             // If a crate name is present, we use it as the link name
554             let stem = sess
555                 .opts
556                 .crate_name
557                 .clone()
558                 .or_else(|| rustc_attr::find_crate_name(attrs).map(|n| n.to_string()))
559                 .unwrap_or_else(|| sess.io.input.filestem().to_owned());
560 
561             OutputFilenames::new(
562                 dirpath,
563                 stem,
564                 None,
565                 sess.io.temps_dir.clone(),
566                 sess.opts.cg.extra_filename.clone(),
567                 sess.opts.output_types.clone(),
568             )
569         }
570 
571         Some(ref out_file) => {
572             let unnamed_output_types =
573                 sess.opts.output_types.values().filter(|a| a.is_none()).count();
574             let ofile = if unnamed_output_types > 1 {
575                 sess.emit_warning(errors::MultipleOutputTypesAdaption);
576                 None
577             } else {
578                 if !sess.opts.cg.extra_filename.is_empty() {
579                     sess.emit_warning(errors::IgnoringExtraFilename);
580                 }
581                 Some(out_file.clone())
582             };
583             if sess.io.output_dir != None {
584                 sess.emit_warning(errors::IgnoringOutDir);
585             }
586 
587             OutputFilenames::new(
588                 out_file.parent().unwrap_or_else(|| Path::new("")).to_path_buf(),
589                 out_file.filestem().unwrap_or_default().to_str().unwrap().to_string(),
590                 ofile,
591                 sess.io.temps_dir.clone(),
592                 sess.opts.cg.extra_filename.clone(),
593                 sess.opts.output_types.clone(),
594             )
595         }
596     }
597 }
598 
599 /// Returns a version string such as "1.46.0 (04488afe3 2020-08-24)" when invoked by an in-tree tool.
600 pub macro version_str() {
601     option_env!("CFG_VERSION")
602 }
603 
604 /// Returns the version string for `rustc` itself (which may be different from a tool version).
rustc_version_str() -> Option<&'static str>605 pub fn rustc_version_str() -> Option<&'static str> {
606     version_str!()
607 }
608