diff options
| author | bors <bors@rust-lang.org> | 2020-08-30 15:57:57 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2020-08-30 15:57:57 +0000 |
| commit | 85fbf49ce0e2274d0acf798f6e703747674feec3 (patch) | |
| tree | 158a05eb3f204a8e72939b58427d0c2787a4eade /compiler/rustc_interface/src | |
| parent | db534b3ac286cf45688c3bbae6aa6e77439e52d2 (diff) | |
| parent | 9e5f7d5631b8f4009ac1c693e585d4b7108d4275 (diff) | |
| download | rust-85fbf49ce0e2274d0acf798f6e703747674feec3.tar.gz rust-85fbf49ce0e2274d0acf798f6e703747674feec3.zip | |
Auto merge of #74862 - mark-i-m:mv-compiler, r=petrochenkov
Move almost all compiler crates to compiler/ This PR implements https://github.com/rust-lang/compiler-team/issues/336 and moves all `rustc_*` crates from `src` to the new `compiler` directory. `librustc_foo` directories are renamed to `rustc_foo`. `src` directories are introduced inside `rustc_*` directories to mirror the scheme already use for `library` crates.
Diffstat (limited to 'compiler/rustc_interface/src')
| -rw-r--r-- | compiler/rustc_interface/src/callbacks.rs | 61 | ||||
| -rw-r--r-- | compiler/rustc_interface/src/interface.rs | 209 | ||||
| -rw-r--r-- | compiler/rustc_interface/src/lib.rs | 21 | ||||
| -rw-r--r-- | compiler/rustc_interface/src/passes.rs | 1004 | ||||
| -rw-r--r-- | compiler/rustc_interface/src/proc_macro_decls.rs | 40 | ||||
| -rw-r--r-- | compiler/rustc_interface/src/queries.rs | 397 | ||||
| -rw-r--r-- | compiler/rustc_interface/src/tests.rs | 599 | ||||
| -rw-r--r-- | compiler/rustc_interface/src/util.rs | 770 |
8 files changed, 3101 insertions, 0 deletions
diff --git a/compiler/rustc_interface/src/callbacks.rs b/compiler/rustc_interface/src/callbacks.rs new file mode 100644 index 00000000000..7fa1a3eb0f5 --- /dev/null +++ b/compiler/rustc_interface/src/callbacks.rs @@ -0,0 +1,61 @@ +//! Throughout the compiler tree, there are several places which want to have +//! access to state or queries while being inside crates that are dependencies +//! of librustc_middle. To facilitate this, we have the +//! `rustc_data_structures::AtomicRef` type, which allows us to setup a global +//! static which can then be set in this file at program startup. +//! +//! See `SPAN_DEBUG` for an example of how to set things up. +//! +//! The functions in this file should fall back to the default set in their +//! origin crate when the `TyCtxt` is not present in TLS. + +use rustc_errors::{Diagnostic, TRACK_DIAGNOSTICS}; +use rustc_middle::ty::tls; +use std::fmt; + +/// This is a callback from librustc_ast as it cannot access the implicit state +/// in librustc_middle otherwise. +fn span_debug(span: rustc_span::Span, f: &mut fmt::Formatter<'_>) -> fmt::Result { + tls::with_opt(|tcx| { + if let Some(tcx) = tcx { + rustc_span::debug_with_source_map(span, f, tcx.sess.source_map()) + } else { + rustc_span::default_span_debug(span, f) + } + }) +} + +/// This is a callback from librustc_ast as it cannot access the implicit state +/// in librustc_middle otherwise. It is used to when diagnostic messages are +/// emitted and stores them in the current query, if there is one. +fn track_diagnostic(diagnostic: &Diagnostic) { + tls::with_context_opt(|icx| { + if let Some(icx) = icx { + if let Some(ref diagnostics) = icx.diagnostics { + let mut diagnostics = diagnostics.lock(); + diagnostics.extend(Some(diagnostic.clone())); + } + } + }) +} + +/// This is a callback from librustc_hir as it cannot access the implicit state +/// in librustc_middle otherwise. +fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "DefId({}:{}", def_id.krate, def_id.index.index())?; + tls::with_opt(|opt_tcx| { + if let Some(tcx) = opt_tcx { + write!(f, " ~ {}", tcx.def_path_debug_str(def_id))?; + } + Ok(()) + })?; + write!(f, ")") +} + +/// Sets up the callbacks in prior crates which we want to refer to the +/// TyCtxt in. +pub fn setup_callbacks() { + rustc_span::SPAN_DEBUG.swap(&(span_debug as fn(_, &mut fmt::Formatter<'_>) -> _)); + rustc_hir::def_id::DEF_ID_DEBUG.swap(&(def_id_debug as fn(_, &mut fmt::Formatter<'_>) -> _)); + TRACK_DIAGNOSTICS.swap(&(track_diagnostic as fn(&_))); +} diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs new file mode 100644 index 00000000000..4d84462c42b --- /dev/null +++ b/compiler/rustc_interface/src/interface.rs @@ -0,0 +1,209 @@ +pub use crate::passes::BoxedResolver; +use crate::util; + +use rustc_ast::token; +use rustc_ast::{self as ast, MetaItemKind}; +use rustc_codegen_ssa::traits::CodegenBackend; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::sync::Lrc; +use rustc_data_structures::OnDrop; +use rustc_errors::registry::Registry; +use rustc_errors::ErrorReported; +use rustc_lint::LintStore; +use rustc_middle::ty; +use rustc_parse::new_parser_from_source_str; +use rustc_session::config::{self, ErrorOutputType, Input, OutputFilenames}; +use rustc_session::early_error; +use rustc_session::lint; +use rustc_session::parse::{CrateConfig, ParseSess}; +use rustc_session::{DiagnosticOutput, Session}; +use rustc_span::source_map::{FileLoader, FileName}; +use std::path::PathBuf; +use std::result; +use std::sync::{Arc, Mutex}; + +pub type Result<T> = result::Result<T, ErrorReported>; + +/// Represents a compiler session. +/// Can be used to run `rustc_interface` queries. +/// Created by passing `Config` to `run_compiler`. +pub struct Compiler { + pub(crate) sess: Lrc<Session>, + codegen_backend: Lrc<Box<dyn CodegenBackend>>, + pub(crate) input: Input, + pub(crate) input_path: Option<PathBuf>, + pub(crate) output_dir: Option<PathBuf>, + pub(crate) output_file: Option<PathBuf>, + pub(crate) crate_name: Option<String>, + pub(crate) register_lints: Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>>, + pub(crate) override_queries: + Option<fn(&Session, &mut ty::query::Providers, &mut ty::query::Providers)>, +} + +impl Compiler { + pub fn session(&self) -> &Lrc<Session> { + &self.sess + } + pub fn codegen_backend(&self) -> &Lrc<Box<dyn CodegenBackend>> { + &self.codegen_backend + } + pub fn input(&self) -> &Input { + &self.input + } + pub fn output_dir(&self) -> &Option<PathBuf> { + &self.output_dir + } + pub fn output_file(&self) -> &Option<PathBuf> { + &self.output_file + } + pub fn build_output_filenames( + &self, + sess: &Session, + attrs: &[ast::Attribute], + ) -> OutputFilenames { + util::build_output_filenames( + &self.input, + &self.output_dir, + &self.output_file, + &attrs, + &sess, + ) + } +} + +/// Converts strings provided as `--cfg [cfgspec]` into a `crate_cfg`. +pub fn parse_cfgspecs(cfgspecs: Vec<String>) -> FxHashSet<(String, Option<String>)> { + rustc_span::with_default_session_globals(move || { + let cfg = cfgspecs + .into_iter() + .map(|s| { + let sess = ParseSess::with_silent_emitter(); + let filename = FileName::cfg_spec_source_code(&s); + let mut parser = new_parser_from_source_str(&sess, filename, s.to_string()); + + macro_rules! error { + ($reason: expr) => { + early_error( + ErrorOutputType::default(), + &format!(concat!("invalid `--cfg` argument: `{}` (", $reason, ")"), s), + ); + }; + } + + match &mut parser.parse_meta_item() { + Ok(meta_item) if parser.token == token::Eof => { + if meta_item.path.segments.len() != 1 { + error!("argument key must be an identifier"); + } + match &meta_item.kind { + MetaItemKind::List(..) => { + error!(r#"expected `key` or `key="value"`"#); + } + MetaItemKind::NameValue(lit) if !lit.kind.is_str() => { + error!("argument value must be a string"); + } + MetaItemKind::NameValue(..) | MetaItemKind::Word => { + let ident = meta_item.ident().expect("multi-segment cfg key"); + return (ident.name, meta_item.value_str()); + } + } + } + Ok(..) => {} + Err(err) => err.cancel(), + } + + error!(r#"expected `key` or `key="value"`"#); + }) + .collect::<CrateConfig>(); + cfg.into_iter().map(|(a, b)| (a.to_string(), b.map(|b| b.to_string()))).collect() + }) +} + +/// The compiler configuration +pub struct Config { + /// Command line options + pub opts: config::Options, + + /// cfg! configuration in addition to the default ones + pub crate_cfg: FxHashSet<(String, Option<String>)>, + + pub input: Input, + pub input_path: Option<PathBuf>, + pub output_dir: Option<PathBuf>, + pub output_file: Option<PathBuf>, + pub file_loader: Option<Box<dyn FileLoader + Send + Sync>>, + pub diagnostic_output: DiagnosticOutput, + + /// Set to capture stderr output during compiler execution + pub stderr: Option<Arc<Mutex<Vec<u8>>>>, + + pub crate_name: Option<String>, + pub lint_caps: FxHashMap<lint::LintId, lint::Level>, + + /// This is a callback from the driver that is called when we're registering lints; + /// it is called during plugin registration when we have the LintStore in a non-shared state. + /// + /// Note that if you find a Some here you probably want to call that function in the new + /// function being registered. + pub register_lints: Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>>, + + /// This is a callback from the driver that is called just after we have populated + /// the list of queries. + /// + /// The second parameter is local providers and the third parameter is external providers. + pub override_queries: + Option<fn(&Session, &mut ty::query::Providers, &mut ty::query::Providers)>, + + /// Registry of diagnostics codes. + pub registry: Registry, +} + +pub fn create_compiler_and_run<R>(config: Config, f: impl FnOnce(&Compiler) -> R) -> R { + let registry = &config.registry; + let (sess, codegen_backend) = util::create_session( + config.opts, + config.crate_cfg, + config.diagnostic_output, + config.file_loader, + config.input_path.clone(), + config.lint_caps, + registry.clone(), + ); + + let compiler = Compiler { + sess, + codegen_backend, + input: config.input, + input_path: config.input_path, + output_dir: config.output_dir, + output_file: config.output_file, + crate_name: config.crate_name, + register_lints: config.register_lints, + override_queries: config.override_queries, + }; + + rustc_span::with_source_map(compiler.sess.parse_sess.clone_source_map(), move || { + let r = { + let _sess_abort_error = OnDrop(|| { + compiler.sess.finish_diagnostics(registry); + }); + + f(&compiler) + }; + + let prof = compiler.sess.prof.clone(); + prof.generic_activity("drop_compiler").run(move || drop(compiler)); + r + }) +} + +pub fn run_compiler<R: Send>(mut config: Config, f: impl FnOnce(&Compiler) -> R + Send) -> R { + tracing::trace!("run_compiler"); + let stderr = config.stderr.take(); + util::setup_callbacks_and_run_in_thread_pool_with_globals( + config.opts.edition, + config.opts.debugging_opts.threads, + &stderr, + || create_compiler_and_run(config, f), + ) +} diff --git a/compiler/rustc_interface/src/lib.rs b/compiler/rustc_interface/src/lib.rs new file mode 100644 index 00000000000..fe40c615f79 --- /dev/null +++ b/compiler/rustc_interface/src/lib.rs @@ -0,0 +1,21 @@ +#![feature(bool_to_option)] +#![feature(box_syntax)] +#![feature(set_stdio)] +#![feature(nll)] +#![feature(generator_trait)] +#![feature(generators)] +#![recursion_limit = "256"] + +mod callbacks; +pub mod interface; +mod passes; +mod proc_macro_decls; +mod queries; +pub mod util; + +pub use interface::{run_compiler, Config}; +pub use passes::{DEFAULT_EXTERN_QUERY_PROVIDERS, DEFAULT_QUERY_PROVIDERS}; +pub use queries::Queries; + +#[cfg(test)] +mod tests; diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs new file mode 100644 index 00000000000..403aea8b304 --- /dev/null +++ b/compiler/rustc_interface/src/passes.rs @@ -0,0 +1,1004 @@ +use crate::interface::{Compiler, Result}; +use crate::proc_macro_decls; +use crate::util; + +use once_cell::sync::Lazy; +use rustc_ast::mut_visit::MutVisitor; +use rustc_ast::{self as ast, visit}; +use rustc_codegen_ssa::back::link::emit_metadata; +use rustc_codegen_ssa::traits::CodegenBackend; +use rustc_data_structures::sync::{par_iter, Lrc, OnceCell, ParallelIterator, WorkerLocal}; +use rustc_data_structures::temp_dir::MaybeTempDir; +use rustc_data_structures::{box_region_allow_access, declare_box_region_type, parallel}; +use rustc_errors::{ErrorReported, PResult}; +use rustc_expand::base::ExtCtxt; +use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; +use rustc_hir::definitions::Definitions; +use rustc_hir::Crate; +use rustc_lint::LintStore; +use rustc_middle::arena::Arena; +use rustc_middle::dep_graph::DepGraph; +use rustc_middle::middle; +use rustc_middle::middle::cstore::{CrateStore, MetadataLoader, MetadataLoaderDyn}; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::steal::Steal; +use rustc_middle::ty::{self, GlobalCtxt, ResolverOutputs, TyCtxt}; +use rustc_mir as mir; +use rustc_mir_build as mir_build; +use rustc_parse::{parse_crate_from_file, parse_crate_from_source_str}; +use rustc_passes::{self, hir_stats, layout_test}; +use rustc_plugin_impl as plugin; +use rustc_resolve::{Resolver, ResolverArenas}; +use rustc_session::config::{CrateType, Input, OutputFilenames, OutputType, PpMode, PpSourceMode}; +use rustc_session::output::{filename_for_input, filename_for_metadata}; +use rustc_session::search_paths::PathKind; +use rustc_session::Session; +use rustc_span::symbol::Symbol; +use rustc_span::{FileName, RealFileName}; +use rustc_trait_selection::traits; +use rustc_typeck as typeck; +use tracing::{info, warn}; + +use rustc_serialize::json; +use tempfile::Builder as TempFileBuilder; + +use std::any::Any; +use std::cell::RefCell; +use std::ffi::OsString; +use std::io::{self, BufWriter, Write}; +use std::path::PathBuf; +use std::rc::Rc; +use std::{env, fs, iter, mem}; + +pub fn parse<'a>(sess: &'a Session, input: &Input) -> PResult<'a, ast::Crate> { + let krate = sess.time("parse_crate", || match input { + Input::File(file) => parse_crate_from_file(file, &sess.parse_sess), + Input::Str { input, name } => { + parse_crate_from_source_str(name.clone(), input.clone(), &sess.parse_sess) + } + })?; + + if sess.opts.debugging_opts.ast_json_noexpand { + println!("{}", json::as_json(&krate)); + } + + if sess.opts.debugging_opts.input_stats { + println!("Lines of code: {}", sess.source_map().count_lines()); + println!("Pre-expansion node count: {}", count_nodes(&krate)); + } + + if let Some(ref s) = sess.opts.debugging_opts.show_span { + rustc_ast_passes::show_span::run(sess.diagnostic(), s, &krate); + } + + if sess.opts.debugging_opts.hir_stats { + hir_stats::print_ast_stats(&krate, "PRE EXPANSION AST STATS"); + } + + Ok(krate) +} + +fn count_nodes(krate: &ast::Crate) -> usize { + let mut counter = rustc_ast_passes::node_count::NodeCounter::new(); + visit::walk_crate(&mut counter, krate); + counter.count +} + +declare_box_region_type!( + pub BoxedResolver, + for(), + (&mut Resolver<'_>) -> (Result<ast::Crate>, ResolverOutputs) +); + +/// Runs the "early phases" of the compiler: initial `cfg` processing, loading compiler plugins, +/// syntax expansion, secondary `cfg` expansion, synthesis of a test +/// harness if one is to be provided, injection of a dependency on the +/// standard library and prelude, and name resolution. +/// +/// Returns `None` if we're aborting after handling -W help. +pub fn configure_and_expand( + sess: Lrc<Session>, + lint_store: Lrc<LintStore>, + metadata_loader: Box<MetadataLoaderDyn>, + krate: ast::Crate, + crate_name: &str, +) -> Result<(ast::Crate, BoxedResolver)> { + tracing::trace!("configure_and_expand"); + // Currently, we ignore the name resolution data structures for the purposes of dependency + // tracking. Instead we will run name resolution and include its output in the hash of each + // item, much like we do for macro expansion. In other words, the hash reflects not just + // its contents but the results of name resolution on those contents. Hopefully we'll push + // this back at some point. + let crate_name = crate_name.to_string(); + let (result, resolver) = BoxedResolver::new(static move |mut action| { + let _ = action; + let sess = &*sess; + let resolver_arenas = Resolver::arenas(); + let res = configure_and_expand_inner( + sess, + &lint_store, + krate, + &crate_name, + &resolver_arenas, + &*metadata_loader, + ); + let mut resolver = match res { + Err(v) => { + yield BoxedResolver::initial_yield(Err(v)); + panic!() + } + Ok((krate, resolver)) => { + action = yield BoxedResolver::initial_yield(Ok(krate)); + resolver + } + }; + box_region_allow_access!(for(), (&mut Resolver<'_>), (&mut resolver), action); + resolver.into_outputs() + }); + result.map(|k| (k, resolver)) +} + +impl BoxedResolver { + pub fn to_resolver_outputs(resolver: Rc<RefCell<BoxedResolver>>) -> ResolverOutputs { + match Rc::try_unwrap(resolver) { + Ok(resolver) => resolver.into_inner().complete(), + Err(resolver) => resolver.borrow_mut().access(|resolver| resolver.clone_outputs()), + } + } +} + +pub fn register_plugins<'a>( + sess: &'a Session, + metadata_loader: &'a dyn MetadataLoader, + register_lints: impl Fn(&Session, &mut LintStore), + mut krate: ast::Crate, + crate_name: &str, +) -> Result<(ast::Crate, Lrc<LintStore>)> { + krate = sess.time("attributes_injection", || { + rustc_builtin_macros::cmdline_attrs::inject( + krate, + &sess.parse_sess, + &sess.opts.debugging_opts.crate_attr, + ) + }); + + let (krate, features) = rustc_expand::config::features(sess, krate); + // these need to be set "early" so that expansion sees `quote` if enabled. + sess.init_features(features); + + let crate_types = util::collect_crate_types(sess, &krate.attrs); + sess.init_crate_types(crate_types); + + let disambiguator = util::compute_crate_disambiguator(sess); + sess.crate_disambiguator.set(disambiguator).expect("not yet initialized"); + rustc_incremental::prepare_session_directory(sess, &crate_name, disambiguator); + + if sess.opts.incremental.is_some() { + sess.time("incr_comp_garbage_collect_session_directories", || { + if let Err(e) = rustc_incremental::garbage_collect_session_directories(sess) { + warn!( + "Error while trying to garbage collect incremental \ + compilation cache directory: {}", + e + ); + } + }); + } + + sess.time("recursion_limit", || { + middle::limits::update_limits(sess, &krate); + }); + + let mut lint_store = rustc_lint::new_lint_store( + sess.opts.debugging_opts.no_interleave_lints, + sess.unstable_options(), + ); + register_lints(&sess, &mut lint_store); + + let registrars = + sess.time("plugin_loading", || plugin::load::load_plugins(sess, metadata_loader, &krate)); + sess.time("plugin_registration", || { + let mut registry = plugin::Registry { lint_store: &mut lint_store }; + for registrar in registrars { + registrar(&mut registry); + } + }); + + Ok((krate, Lrc::new(lint_store))) +} + +fn pre_expansion_lint(sess: &Session, lint_store: &LintStore, krate: &ast::Crate) { + sess.time("pre_AST_expansion_lint_checks", || { + rustc_lint::check_ast_crate( + sess, + lint_store, + &krate, + true, + None, + rustc_lint::BuiltinCombinedPreExpansionLintPass::new(), + ); + }); +} + +fn configure_and_expand_inner<'a>( + sess: &'a Session, + lint_store: &'a LintStore, + mut krate: ast::Crate, + crate_name: &str, + resolver_arenas: &'a ResolverArenas<'a>, + metadata_loader: &'a MetadataLoaderDyn, +) -> Result<(ast::Crate, Resolver<'a>)> { + tracing::trace!("configure_and_expand_inner"); + pre_expansion_lint(sess, lint_store, &krate); + + let mut resolver = Resolver::new(sess, &krate, crate_name, metadata_loader, &resolver_arenas); + rustc_builtin_macros::register_builtin_macros(&mut resolver, sess.edition()); + + krate = sess.time("crate_injection", || { + let alt_std_name = sess.opts.alt_std_name.as_ref().map(|s| Symbol::intern(s)); + let (krate, name) = rustc_builtin_macros::standard_library_imports::inject( + krate, + &mut resolver, + &sess, + alt_std_name, + ); + if let Some(name) = name { + sess.parse_sess.injected_crate_name.set(name).expect("not yet initialized"); + } + krate + }); + + util::check_attr_crate_type(&sess, &krate.attrs, &mut resolver.lint_buffer()); + + // Expand all macros + krate = sess.time("macro_expand_crate", || { + // Windows dlls do not have rpaths, so they don't know how to find their + // dependencies. It's up to us to tell the system where to find all the + // dependent dlls. Note that this uses cfg!(windows) as opposed to + // targ_cfg because syntax extensions are always loaded for the host + // compiler, not for the target. + // + // This is somewhat of an inherently racy operation, however, as + // multiple threads calling this function could possibly continue + // extending PATH far beyond what it should. To solve this for now we + // just don't add any new elements to PATH which are already there + // within PATH. This is basically a targeted fix at #17360 for rustdoc + // which runs rustc in parallel but has been seen (#33844) to cause + // problems with PATH becoming too long. + let mut old_path = OsString::new(); + if cfg!(windows) { + old_path = env::var_os("PATH").unwrap_or(old_path); + let mut new_path = sess.host_filesearch(PathKind::All).search_path_dirs(); + for path in env::split_paths(&old_path) { + if !new_path.contains(&path) { + new_path.push(path); + } + } + env::set_var( + "PATH", + &env::join_paths( + new_path.iter().filter(|p| env::join_paths(iter::once(p)).is_ok()), + ) + .unwrap(), + ); + } + + // Create the config for macro expansion + let features = sess.features_untracked(); + let cfg = rustc_expand::expand::ExpansionConfig { + features: Some(&features), + recursion_limit: sess.recursion_limit(), + trace_mac: sess.opts.debugging_opts.trace_macros, + should_test: sess.opts.test, + span_debug: sess.opts.debugging_opts.span_debug, + ..rustc_expand::expand::ExpansionConfig::default(crate_name.to_string()) + }; + + let extern_mod_loaded = |k: &ast::Crate| pre_expansion_lint(sess, lint_store, k); + let mut ecx = ExtCtxt::new(&sess, cfg, &mut resolver, Some(&extern_mod_loaded)); + + // Expand macros now! + let krate = sess.time("expand_crate", || ecx.monotonic_expander().expand_crate(krate)); + + // The rest is error reporting + + sess.time("check_unused_macros", || { + ecx.check_unused_macros(); + }); + + if cfg!(windows) { + env::set_var("PATH", &old_path); + } + + let recursion_limit_hit = ecx.reduced_recursion_limit.is_some(); + if recursion_limit_hit { + // If we hit a recursion limit, exit early to avoid later passes getting overwhelmed + // with a large AST + Err(ErrorReported) + } else { + Ok(krate) + } + })?; + + sess.time("maybe_building_test_harness", || { + rustc_builtin_macros::test_harness::inject(&sess, &mut resolver, &mut krate) + }); + + if let Some(PpMode::PpmSource(PpSourceMode::PpmEveryBodyLoops)) = sess.opts.pretty { + tracing::debug!("replacing bodies with loop {{}}"); + util::ReplaceBodyWithLoop::new(&mut resolver).visit_crate(&mut krate); + } + + let has_proc_macro_decls = sess.time("AST_validation", || { + rustc_ast_passes::ast_validation::check_crate(sess, &krate, &mut resolver.lint_buffer()) + }); + + let crate_types = sess.crate_types(); + let is_proc_macro_crate = crate_types.contains(&CrateType::ProcMacro); + + // For backwards compatibility, we don't try to run proc macro injection + // if rustdoc is run on a proc macro crate without '--crate-type proc-macro' being + // specified. This should only affect users who manually invoke 'rustdoc', as + // 'cargo doc' will automatically pass the proper '--crate-type' flags. + // However, we do emit a warning, to let such users know that they should + // start passing '--crate-type proc-macro' + if has_proc_macro_decls && sess.opts.actually_rustdoc && !is_proc_macro_crate { + let mut msg = sess.diagnostic().struct_warn( + &"Trying to document proc macro crate \ + without passing '--crate-type proc-macro to rustdoc", + ); + + msg.warn("The generated documentation may be incorrect"); + msg.emit() + } else { + krate = sess.time("maybe_create_a_macro_crate", || { + let num_crate_types = crate_types.len(); + let is_test_crate = sess.opts.test; + rustc_builtin_macros::proc_macro_harness::inject( + &sess, + &mut resolver, + krate, + is_proc_macro_crate, + has_proc_macro_decls, + is_test_crate, + num_crate_types, + sess.diagnostic(), + ) + }); + } + + // Done with macro expansion! + + if sess.opts.debugging_opts.input_stats { + println!("Post-expansion node count: {}", count_nodes(&krate)); + } + + if sess.opts.debugging_opts.hir_stats { + hir_stats::print_ast_stats(&krate, "POST EXPANSION AST STATS"); + } + + if sess.opts.debugging_opts.ast_json { + println!("{}", json::as_json(&krate)); + } + + resolver.resolve_crate(&krate); + + // Needs to go *after* expansion to be able to check the results of macro expansion. + sess.time("complete_gated_feature_checking", || { + rustc_ast_passes::feature_gate::check_crate(&krate, sess); + }); + + // Add all buffered lints from the `ParseSess` to the `Session`. + sess.parse_sess.buffered_lints.with_lock(|buffered_lints| { + info!("{} parse sess buffered_lints", buffered_lints.len()); + for early_lint in buffered_lints.drain(..) { + resolver.lint_buffer().add_early_lint(early_lint); + } + }); + + Ok((krate, resolver)) +} + +pub fn lower_to_hir<'res, 'tcx>( + sess: &'tcx Session, + lint_store: &LintStore, + resolver: &'res mut Resolver<'_>, + dep_graph: &'res DepGraph, + krate: &'res ast::Crate, + arena: &'tcx rustc_ast_lowering::Arena<'tcx>, +) -> Crate<'tcx> { + // We're constructing the HIR here; we don't care what we will + // read, since we haven't even constructed the *input* to + // incr. comp. yet. + dep_graph.assert_ignored(); + + // Lower AST to HIR. + let hir_crate = rustc_ast_lowering::lower_crate( + sess, + &krate, + resolver, + rustc_parse::nt_to_tokenstream, + arena, + ); + + if sess.opts.debugging_opts.hir_stats { + hir_stats::print_hir_stats(&hir_crate); + } + + sess.time("early_lint_checks", || { + rustc_lint::check_ast_crate( + sess, + lint_store, + &krate, + false, + Some(std::mem::take(resolver.lint_buffer())), + rustc_lint::BuiltinCombinedEarlyLintPass::new(), + ) + }); + + // Discard hygiene data, which isn't required after lowering to HIR. + if !sess.opts.debugging_opts.keep_hygiene_data { + rustc_span::hygiene::clear_syntax_context_map(); + } + + hir_crate +} + +// Returns all the paths that correspond to generated files. +fn generated_output_paths( + sess: &Session, + outputs: &OutputFilenames, + exact_name: bool, + crate_name: &str, +) -> Vec<PathBuf> { + let mut out_filenames = Vec::new(); + for output_type in sess.opts.output_types.keys() { + let file = outputs.path(*output_type); + match *output_type { + // If the filename has been overridden using `-o`, it will not be modified + // by appending `.rlib`, `.exe`, etc., so we can skip this transformation. + OutputType::Exe if !exact_name => { + for crate_type in sess.crate_types().iter() { + let p = filename_for_input(sess, *crate_type, crate_name, outputs); + out_filenames.push(p); + } + } + OutputType::DepInfo if sess.opts.debugging_opts.dep_info_omit_d_target => { + // Don't add the dep-info output when omitting it from dep-info targets + } + _ => { + out_filenames.push(file); + } + } + } + out_filenames +} + +// Runs `f` on every output file path and returns the first non-None result, or None if `f` +// returns None for every file path. +fn check_output<F, T>(output_paths: &[PathBuf], f: F) -> Option<T> +where + F: Fn(&PathBuf) -> Option<T>, +{ + for output_path in output_paths { + if let Some(result) = f(output_path) { + return Some(result); + } + } + None +} + +fn output_contains_path(output_paths: &[PathBuf], input_path: &PathBuf) -> bool { + let input_path = input_path.canonicalize().ok(); + if input_path.is_none() { + return false; + } + let check = |output_path: &PathBuf| { + if output_path.canonicalize().ok() == input_path { Some(()) } else { None } + }; + check_output(output_paths, check).is_some() +} + +fn output_conflicts_with_dir(output_paths: &[PathBuf]) -> Option<PathBuf> { + let check = |output_path: &PathBuf| output_path.is_dir().then(|| output_path.clone()); + check_output(output_paths, check) +} + +fn escape_dep_filename(filename: &FileName) -> String { + // Apparently clang and gcc *only* escape spaces: + // http://llvm.org/klaus/clang/commit/9d50634cfc268ecc9a7250226dd5ca0e945240d4 + filename.to_string().replace(" ", "\\ ") +} + +// Makefile comments only need escaping newlines and `\`. +// The result can be unescaped by anything that can unescape `escape_default` and friends. +fn escape_dep_env(symbol: Symbol) -> String { + let s = symbol.as_str(); + let mut escaped = String::with_capacity(s.len()); + for c in s.chars() { + match c { + '\n' => escaped.push_str(r"\n"), + '\r' => escaped.push_str(r"\r"), + '\\' => escaped.push_str(r"\\"), + _ => escaped.push(c), + } + } + escaped +} + +fn write_out_deps( + sess: &Session, + boxed_resolver: &Steal<Rc<RefCell<BoxedResolver>>>, + outputs: &OutputFilenames, + out_filenames: &[PathBuf], +) { + // Write out dependency rules to the dep-info file if requested + if !sess.opts.output_types.contains_key(&OutputType::DepInfo) { + return; + } + let deps_filename = outputs.path(OutputType::DepInfo); + + let result = (|| -> io::Result<()> { + // Build a list of files used to compile the output and + // write Makefile-compatible dependency rules + let mut files: Vec<String> = sess + .source_map() + .files() + .iter() + .filter(|fmap| fmap.is_real_file()) + .filter(|fmap| !fmap.is_imported()) + .map(|fmap| escape_dep_filename(&fmap.unmapped_path.as_ref().unwrap_or(&fmap.name))) + .collect(); + + if sess.binary_dep_depinfo() { + boxed_resolver.borrow().borrow_mut().access(|resolver| { + for cnum in resolver.cstore().crates_untracked() { + let source = resolver.cstore().crate_source_untracked(cnum); + if let Some((path, _)) = source.dylib { + let file_name = FileName::Real(RealFileName::Named(path)); + files.push(escape_dep_filename(&file_name)); + } + if let Some((path, _)) = source.rlib { + let file_name = FileName::Real(RealFileName::Named(path)); + files.push(escape_dep_filename(&file_name)); + } + if let Some((path, _)) = source.rmeta { + let file_name = FileName::Real(RealFileName::Named(path)); + files.push(escape_dep_filename(&file_name)); + } + } + }); + } + + let mut file = BufWriter::new(fs::File::create(&deps_filename)?); + for path in out_filenames { + writeln!(file, "{}: {}\n", path.display(), files.join(" "))?; + } + + // Emit a fake target for each input file to the compilation. This + // prevents `make` from spitting out an error if a file is later + // deleted. For more info see #28735 + for path in files { + writeln!(file, "{}:", path)?; + } + + // Emit special comments with information about accessed environment variables. + let env_depinfo = sess.parse_sess.env_depinfo.borrow(); + if !env_depinfo.is_empty() { + let mut envs: Vec<_> = env_depinfo + .iter() + .map(|(k, v)| (escape_dep_env(*k), v.map(escape_dep_env))) + .collect(); + envs.sort_unstable(); + writeln!(file)?; + for (k, v) in envs { + write!(file, "# env-dep:{}", k)?; + if let Some(v) = v { + write!(file, "={}", v)?; + } + writeln!(file)?; + } + } + + Ok(()) + })(); + + match result { + Ok(_) => { + if sess.opts.json_artifact_notifications { + sess.parse_sess + .span_diagnostic + .emit_artifact_notification(&deps_filename, "dep-info"); + } + } + Err(e) => sess.fatal(&format!( + "error writing dependencies to `{}`: {}", + deps_filename.display(), + e + )), + } +} + +pub fn prepare_outputs( + sess: &Session, + compiler: &Compiler, + krate: &ast::Crate, + boxed_resolver: &Steal<Rc<RefCell<BoxedResolver>>>, + crate_name: &str, +) -> Result<OutputFilenames> { + let _timer = sess.timer("prepare_outputs"); + + // FIXME: rustdoc passes &[] instead of &krate.attrs here + let outputs = util::build_output_filenames( + &compiler.input, + &compiler.output_dir, + &compiler.output_file, + &krate.attrs, + sess, + ); + + let output_paths = + generated_output_paths(sess, &outputs, compiler.output_file.is_some(), &crate_name); + + // Ensure the source file isn't accidentally overwritten during compilation. + if let Some(ref input_path) = compiler.input_path { + if sess.opts.will_create_output_file() { + if output_contains_path(&output_paths, input_path) { + sess.err(&format!( + "the input file \"{}\" would be overwritten by the generated \ + executable", + input_path.display() + )); + return Err(ErrorReported); + } + if let Some(dir_path) = output_conflicts_with_dir(&output_paths) { + sess.err(&format!( + "the generated executable for the input file \"{}\" conflicts with the \ + existing directory \"{}\"", + input_path.display(), + dir_path.display() + )); + return Err(ErrorReported); + } + } + } + + write_out_deps(sess, boxed_resolver, &outputs, &output_paths); + + let only_dep_info = sess.opts.output_types.contains_key(&OutputType::DepInfo) + && sess.opts.output_types.len() == 1; + + if !only_dep_info { + if let Some(ref dir) = compiler.output_dir { + if fs::create_dir_all(dir).is_err() { + sess.err("failed to find or create the directory specified by `--out-dir`"); + return Err(ErrorReported); + } + } + } + + Ok(outputs) +} + +pub static DEFAULT_QUERY_PROVIDERS: Lazy<Providers> = Lazy::new(|| { + let providers = &mut Providers::default(); + providers.analysis = analysis; + proc_macro_decls::provide(providers); + plugin::build::provide(providers); + rustc_middle::hir::provide(providers); + mir::provide(providers); + mir_build::provide(providers); + rustc_privacy::provide(providers); + typeck::provide(providers); + ty::provide(providers); + traits::provide(providers); + rustc_passes::provide(providers); + rustc_resolve::provide(providers); + rustc_traits::provide(providers); + rustc_ty::provide(providers); + rustc_metadata::provide(providers); + rustc_lint::provide(providers); + rustc_symbol_mangling::provide(providers); + rustc_codegen_ssa::provide(providers); + *providers +}); + +pub static DEFAULT_EXTERN_QUERY_PROVIDERS: Lazy<Providers> = Lazy::new(|| { + let mut extern_providers = *DEFAULT_QUERY_PROVIDERS; + rustc_metadata::provide_extern(&mut extern_providers); + rustc_codegen_ssa::provide_extern(&mut extern_providers); + extern_providers +}); + +pub struct QueryContext<'tcx>(&'tcx GlobalCtxt<'tcx>); + +impl<'tcx> QueryContext<'tcx> { + pub fn enter<F, R>(&mut self, f: F) -> R + where + F: FnOnce(TyCtxt<'tcx>) -> R, + { + let icx = ty::tls::ImplicitCtxt::new(self.0); + ty::tls::enter_context(&icx, |_| f(icx.tcx)) + } + + pub fn print_stats(&mut self) { + self.enter(ty::query::print_stats) + } +} + +pub fn create_global_ctxt<'tcx>( + compiler: &'tcx Compiler, + lint_store: Lrc<LintStore>, + krate: &'tcx Crate<'tcx>, + dep_graph: DepGraph, + mut resolver_outputs: ResolverOutputs, + outputs: OutputFilenames, + crate_name: &str, + global_ctxt: &'tcx OnceCell<GlobalCtxt<'tcx>>, + arena: &'tcx WorkerLocal<Arena<'tcx>>, +) -> QueryContext<'tcx> { + let sess = &compiler.session(); + let defs: &'tcx Definitions = arena.alloc(mem::replace( + &mut resolver_outputs.definitions, + Definitions::new(crate_name, sess.local_crate_disambiguator()), + )); + + let query_result_on_disk_cache = rustc_incremental::load_query_result_cache(sess); + + let codegen_backend = compiler.codegen_backend(); + let mut local_providers = *DEFAULT_QUERY_PROVIDERS; + codegen_backend.provide(&mut local_providers); + + let mut extern_providers = *DEFAULT_EXTERN_QUERY_PROVIDERS; + codegen_backend.provide(&mut extern_providers); + codegen_backend.provide_extern(&mut extern_providers); + + if let Some(callback) = compiler.override_queries { + callback(sess, &mut local_providers, &mut extern_providers); + } + + let gcx = sess.time("setup_global_ctxt", || { + global_ctxt.get_or_init(|| { + TyCtxt::create_global_ctxt( + sess, + lint_store, + local_providers, + extern_providers, + arena, + resolver_outputs, + krate, + defs, + dep_graph, + query_result_on_disk_cache, + &crate_name, + &outputs, + ) + }) + }); + + // Do some initialization of the DepGraph that can only be done with the tcx available. + let icx = ty::tls::ImplicitCtxt::new(&gcx); + ty::tls::enter_context(&icx, |_| { + icx.tcx.sess.time("dep_graph_tcx_init", || rustc_incremental::dep_graph_tcx_init(icx.tcx)); + }); + + QueryContext(gcx) +} + +/// Runs the resolution, type-checking, region checking and other +/// miscellaneous analysis passes on the crate. +fn analysis(tcx: TyCtxt<'_>, cnum: CrateNum) -> Result<()> { + assert_eq!(cnum, LOCAL_CRATE); + + rustc_passes::hir_id_validator::check_crate(tcx); + + let sess = tcx.sess; + let mut entry_point = None; + + sess.time("misc_checking_1", || { + parallel!( + { + entry_point = sess + .time("looking_for_entry_point", || rustc_passes::entry::find_entry_point(tcx)); + + sess.time("looking_for_plugin_registrar", || { + plugin::build::find_plugin_registrar(tcx) + }); + + sess.time("looking_for_derive_registrar", || proc_macro_decls::find(tcx)); + }, + { + par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| { + let local_def_id = tcx.hir().local_def_id(module); + tcx.ensure().check_mod_loops(local_def_id); + tcx.ensure().check_mod_attrs(local_def_id); + tcx.ensure().check_mod_unstable_api_usage(local_def_id); + tcx.ensure().check_mod_const_bodies(local_def_id); + }); + } + ); + }); + + // passes are timed inside typeck + typeck::check_crate(tcx)?; + + sess.time("misc_checking_2", || { + parallel!( + { + sess.time("match_checking", || { + tcx.par_body_owners(|def_id| { + tcx.ensure().check_match(def_id.to_def_id()); + }); + }); + }, + { + sess.time("liveness_and_intrinsic_checking", || { + par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| { + // this must run before MIR dump, because + // "not all control paths return a value" is reported here. + // + // maybe move the check to a MIR pass? + let local_def_id = tcx.hir().local_def_id(module); + + tcx.ensure().check_mod_liveness(local_def_id); + tcx.ensure().check_mod_intrinsics(local_def_id); + }); + }); + } + ); + }); + + sess.time("MIR_borrow_checking", || { + tcx.par_body_owners(|def_id| tcx.ensure().mir_borrowck(def_id)); + }); + + sess.time("MIR_effect_checking", || { + for def_id in tcx.body_owners() { + mir::transform::check_unsafety::check_unsafety(tcx, def_id); + + if tcx.hir().body_const_context(def_id).is_some() { + tcx.ensure() + .mir_drops_elaborated_and_const_checked(ty::WithOptConstParam::unknown(def_id)); + } + } + }); + + sess.time("layout_testing", || layout_test::test_layout(tcx)); + + // Avoid overwhelming user with errors if borrow checking failed. + // I'm not sure how helpful this is, to be honest, but it avoids a + // lot of annoying errors in the compile-fail tests (basically, + // lint warnings and so on -- kindck used to do this abort, but + // kindck is gone now). -nmatsakis + if sess.has_errors() { + return Err(ErrorReported); + } + + sess.time("misc_checking_3", || { + parallel!( + { + tcx.ensure().privacy_access_levels(LOCAL_CRATE); + + parallel!( + { + tcx.ensure().check_private_in_public(LOCAL_CRATE); + }, + { + sess.time("death_checking", || rustc_passes::dead::check_crate(tcx)); + }, + { + sess.time("unused_lib_feature_checking", || { + rustc_passes::stability::check_unused_or_stable_features(tcx) + }); + }, + { + sess.time("lint_checking", || { + rustc_lint::check_crate(tcx, || { + rustc_lint::BuiltinCombinedLateLintPass::new() + }); + }); + } + ); + }, + { + sess.time("privacy_checking_modules", || { + par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| { + tcx.ensure().check_mod_privacy(tcx.hir().local_def_id(module)); + }); + }); + } + ); + }); + + Ok(()) +} + +fn encode_and_write_metadata( + tcx: TyCtxt<'_>, + outputs: &OutputFilenames, +) -> (middle::cstore::EncodedMetadata, bool) { + #[derive(PartialEq, Eq, PartialOrd, Ord)] + enum MetadataKind { + None, + Uncompressed, + Compressed, + } + + let metadata_kind = tcx + .sess + .crate_types() + .iter() + .map(|ty| match *ty { + CrateType::Executable | CrateType::Staticlib | CrateType::Cdylib => MetadataKind::None, + + CrateType::Rlib => MetadataKind::Uncompressed, + + CrateType::Dylib | CrateType::ProcMacro => MetadataKind::Compressed, + }) + .max() + .unwrap_or(MetadataKind::None); + + let metadata = match metadata_kind { + MetadataKind::None => middle::cstore::EncodedMetadata::new(), + MetadataKind::Uncompressed | MetadataKind::Compressed => tcx.encode_metadata(), + }; + + let _prof_timer = tcx.sess.prof.generic_activity("write_crate_metadata"); + + let need_metadata_file = tcx.sess.opts.output_types.contains_key(&OutputType::Metadata); + if need_metadata_file { + let crate_name = &tcx.crate_name(LOCAL_CRATE).as_str(); + let out_filename = filename_for_metadata(tcx.sess, crate_name, outputs); + // To avoid races with another rustc process scanning the output directory, + // we need to write the file somewhere else and atomically move it to its + // final destination, with an `fs::rename` call. In order for the rename to + // always succeed, the temporary file needs to be on the same filesystem, + // which is why we create it inside the output directory specifically. + let metadata_tmpdir = TempFileBuilder::new() + .prefix("rmeta") + .tempdir_in(out_filename.parent().unwrap()) + .unwrap_or_else(|err| tcx.sess.fatal(&format!("couldn't create a temp dir: {}", err))); + let metadata_tmpdir = MaybeTempDir::new(metadata_tmpdir, tcx.sess.opts.cg.save_temps); + let metadata_filename = emit_metadata(tcx.sess, &metadata, &metadata_tmpdir); + if let Err(e) = fs::rename(&metadata_filename, &out_filename) { + tcx.sess.fatal(&format!("failed to write {}: {}", out_filename.display(), e)); + } + if tcx.sess.opts.json_artifact_notifications { + tcx.sess + .parse_sess + .span_diagnostic + .emit_artifact_notification(&out_filename, "metadata"); + } + } + + let need_metadata_module = metadata_kind == MetadataKind::Compressed; + + (metadata, need_metadata_module) +} + +/// Runs the codegen backend, after which the AST and analysis can +/// be discarded. +pub fn start_codegen<'tcx>( + codegen_backend: &dyn CodegenBackend, + tcx: TyCtxt<'tcx>, + outputs: &OutputFilenames, +) -> Box<dyn Any> { + info!("Pre-codegen\n{:?}", tcx.debug_stats()); + + let (metadata, need_metadata_module) = encode_and_write_metadata(tcx, outputs); + + let codegen = tcx.sess.time("codegen_crate", move || { + codegen_backend.codegen_crate(tcx, metadata, need_metadata_module) + }); + + info!("Post-codegen\n{:?}", tcx.debug_stats()); + + if tcx.sess.opts.output_types.contains_key(&OutputType::Mir) { + if let Err(e) = mir::transform::dump_mir::emit_mir(tcx, outputs) { + tcx.sess.err(&format!("could not emit MIR: {}", e)); + tcx.sess.abort_if_errors(); + } + } + + codegen +} diff --git a/compiler/rustc_interface/src/proc_macro_decls.rs b/compiler/rustc_interface/src/proc_macro_decls.rs new file mode 100644 index 00000000000..d56115fd6ac --- /dev/null +++ b/compiler/rustc_interface/src/proc_macro_decls.rs @@ -0,0 +1,40 @@ +use rustc_hir as hir; +use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; +use rustc_hir::itemlikevisit::ItemLikeVisitor; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_span::symbol::sym; + +pub fn find(tcx: TyCtxt<'_>) -> Option<DefId> { + tcx.proc_macro_decls_static(LOCAL_CRATE) +} + +fn proc_macro_decls_static(tcx: TyCtxt<'_>, cnum: CrateNum) -> Option<DefId> { + assert_eq!(cnum, LOCAL_CRATE); + + let mut finder = Finder { tcx, decls: None }; + tcx.hir().krate().visit_all_item_likes(&mut finder); + + finder.decls.map(|id| tcx.hir().local_def_id(id).to_def_id()) +} + +struct Finder<'tcx> { + tcx: TyCtxt<'tcx>, + decls: Option<hir::HirId>, +} + +impl<'v> ItemLikeVisitor<'v> for Finder<'_> { + fn visit_item(&mut self, item: &hir::Item<'_>) { + if self.tcx.sess.contains_name(&item.attrs, sym::rustc_proc_macro_decls) { + self.decls = Some(item.hir_id); + } + } + + fn visit_trait_item(&mut self, _trait_item: &hir::TraitItem<'_>) {} + + fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem<'_>) {} +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { proc_macro_decls_static, ..*providers }; +} diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs new file mode 100644 index 00000000000..8b82217a91a --- /dev/null +++ b/compiler/rustc_interface/src/queries.rs @@ -0,0 +1,397 @@ +use crate::interface::{Compiler, Result}; +use crate::passes::{self, BoxedResolver, QueryContext}; + +use rustc_ast as ast; +use rustc_codegen_ssa::traits::CodegenBackend; +use rustc_data_structures::sync::{Lrc, OnceCell, WorkerLocal}; +use rustc_errors::ErrorReported; +use rustc_hir::def_id::LOCAL_CRATE; +use rustc_hir::Crate; +use rustc_incremental::DepGraphFuture; +use rustc_lint::LintStore; +use rustc_middle::arena::Arena; +use rustc_middle::dep_graph::DepGraph; +use rustc_middle::ty::steal::Steal; +use rustc_middle::ty::{GlobalCtxt, ResolverOutputs, TyCtxt}; +use rustc_session::config::{OutputFilenames, OutputType}; +use rustc_session::{output::find_crate_name, Session}; +use rustc_span::symbol::sym; +use std::any::Any; +use std::cell::{Ref, RefCell, RefMut}; +use std::rc::Rc; + +/// Represent the result of a query. +/// This result can be stolen with the `take` method and generated with the `compute` method. +pub struct Query<T> { + result: RefCell<Option<Result<T>>>, +} + +impl<T> Query<T> { + fn compute<F: FnOnce() -> Result<T>>(&self, f: F) -> Result<&Query<T>> { + let mut result = self.result.borrow_mut(); + if result.is_none() { + *result = Some(f()); + } + result.as_ref().unwrap().as_ref().map(|_| self).map_err(|err| *err) + } + + /// Takes ownership of the query result. Further attempts to take or peek the query + /// result will panic unless it is generated by calling the `compute` method. + pub fn take(&self) -> T { + self.result.borrow_mut().take().expect("missing query result").unwrap() + } + + /// Borrows the query result using the RefCell. Panics if the result is stolen. + pub fn peek(&self) -> Ref<'_, T> { + Ref::map(self.result.borrow(), |r| { + r.as_ref().unwrap().as_ref().expect("missing query result") + }) + } + + /// Mutably borrows the query result using the RefCell. Panics if the result is stolen. + pub fn peek_mut(&self) -> RefMut<'_, T> { + RefMut::map(self.result.borrow_mut(), |r| { + r.as_mut().unwrap().as_mut().expect("missing query result") + }) + } +} + +impl<T> Default for Query<T> { + fn default() -> Self { + Query { result: RefCell::new(None) } + } +} + +pub struct Queries<'tcx> { + compiler: &'tcx Compiler, + gcx: OnceCell<GlobalCtxt<'tcx>>, + + arena: WorkerLocal<Arena<'tcx>>, + hir_arena: WorkerLocal<rustc_ast_lowering::Arena<'tcx>>, + + dep_graph_future: Query<Option<DepGraphFuture>>, + parse: Query<ast::Crate>, + crate_name: Query<String>, + register_plugins: Query<(ast::Crate, Lrc<LintStore>)>, + expansion: Query<(ast::Crate, Steal<Rc<RefCell<BoxedResolver>>>, Lrc<LintStore>)>, + dep_graph: Query<DepGraph>, + lower_to_hir: Query<(&'tcx Crate<'tcx>, Steal<ResolverOutputs>)>, + prepare_outputs: Query<OutputFilenames>, + global_ctxt: Query<QueryContext<'tcx>>, + ongoing_codegen: Query<Box<dyn Any>>, +} + +impl<'tcx> Queries<'tcx> { + pub fn new(compiler: &'tcx Compiler) -> Queries<'tcx> { + Queries { + compiler, + gcx: OnceCell::new(), + arena: WorkerLocal::new(|_| Arena::default()), + hir_arena: WorkerLocal::new(|_| rustc_ast_lowering::Arena::default()), + dep_graph_future: Default::default(), + parse: Default::default(), + crate_name: Default::default(), + register_plugins: Default::default(), + expansion: Default::default(), + dep_graph: Default::default(), + lower_to_hir: Default::default(), + prepare_outputs: Default::default(), + global_ctxt: Default::default(), + ongoing_codegen: Default::default(), + } + } + + fn session(&self) -> &Lrc<Session> { + &self.compiler.sess + } + fn codegen_backend(&self) -> &Lrc<Box<dyn CodegenBackend>> { + &self.compiler.codegen_backend() + } + + pub fn dep_graph_future(&self) -> Result<&Query<Option<DepGraphFuture>>> { + self.dep_graph_future.compute(|| { + Ok(self + .session() + .opts + .build_dep_graph() + .then(|| rustc_incremental::load_dep_graph(self.session()))) + }) + } + + pub fn parse(&self) -> Result<&Query<ast::Crate>> { + self.parse.compute(|| { + passes::parse(self.session(), &self.compiler.input).map_err(|mut parse_error| { + parse_error.emit(); + ErrorReported + }) + }) + } + + pub fn register_plugins(&self) -> Result<&Query<(ast::Crate, Lrc<LintStore>)>> { + self.register_plugins.compute(|| { + let crate_name = self.crate_name()?.peek().clone(); + let krate = self.parse()?.take(); + + let empty: &(dyn Fn(&Session, &mut LintStore) + Sync + Send) = &|_, _| {}; + let result = passes::register_plugins( + self.session(), + &*self.codegen_backend().metadata_loader(), + self.compiler.register_lints.as_deref().unwrap_or_else(|| empty), + krate, + &crate_name, + ); + + // Compute the dependency graph (in the background). We want to do + // this as early as possible, to give the DepGraph maximum time to + // load before dep_graph() is called, but it also can't happen + // until after rustc_incremental::prepare_session_directory() is + // called, which happens within passes::register_plugins(). + self.dep_graph_future().ok(); + + result + }) + } + + pub fn crate_name(&self) -> Result<&Query<String>> { + self.crate_name.compute(|| { + Ok(match self.compiler.crate_name { + Some(ref crate_name) => crate_name.clone(), + None => { + let parse_result = self.parse()?; + let krate = parse_result.peek(); + find_crate_name(self.session(), &krate.attrs, &self.compiler.input) + } + }) + }) + } + + pub fn expansion( + &self, + ) -> Result<&Query<(ast::Crate, Steal<Rc<RefCell<BoxedResolver>>>, Lrc<LintStore>)>> { + tracing::trace!("expansion"); + self.expansion.compute(|| { + let crate_name = self.crate_name()?.peek().clone(); + let (krate, lint_store) = self.register_plugins()?.take(); + let _timer = self.session().timer("configure_and_expand"); + passes::configure_and_expand( + self.session().clone(), + lint_store.clone(), + self.codegen_backend().metadata_loader(), + krate, + &crate_name, + ) + .map(|(krate, resolver)| { + (krate, Steal::new(Rc::new(RefCell::new(resolver))), lint_store) + }) + }) + } + + pub fn dep_graph(&self) -> Result<&Query<DepGraph>> { + self.dep_graph.compute(|| { + Ok(match self.dep_graph_future()?.take() { + None => DepGraph::new_disabled(), + Some(future) => { + let (prev_graph, prev_work_products) = + self.session().time("blocked_on_dep_graph_loading", || { + future + .open() + .unwrap_or_else(|e| rustc_incremental::LoadResult::Error { + message: format!("could not decode incremental cache: {:?}", e), + }) + .open(self.session()) + }); + DepGraph::new(prev_graph, prev_work_products) + } + }) + }) + } + + pub fn lower_to_hir(&'tcx self) -> Result<&Query<(&'tcx Crate<'tcx>, Steal<ResolverOutputs>)>> { + self.lower_to_hir.compute(|| { + let expansion_result = self.expansion()?; + let peeked = expansion_result.peek(); + let krate = &peeked.0; + let resolver = peeked.1.steal(); + let lint_store = &peeked.2; + let hir = resolver.borrow_mut().access(|resolver| { + Ok(passes::lower_to_hir( + self.session(), + lint_store, + resolver, + &*self.dep_graph()?.peek(), + &krate, + &self.hir_arena, + )) + })?; + let hir = self.hir_arena.alloc(hir); + Ok((hir, Steal::new(BoxedResolver::to_resolver_outputs(resolver)))) + }) + } + + pub fn prepare_outputs(&self) -> Result<&Query<OutputFilenames>> { + self.prepare_outputs.compute(|| { + let expansion_result = self.expansion()?; + let (krate, boxed_resolver, _) = &*expansion_result.peek(); + let crate_name = self.crate_name()?; + let crate_name = crate_name.peek(); + passes::prepare_outputs( + self.session(), + self.compiler, + &krate, + &boxed_resolver, + &crate_name, + ) + }) + } + + pub fn global_ctxt(&'tcx self) -> Result<&Query<QueryContext<'tcx>>> { + self.global_ctxt.compute(|| { + let crate_name = self.crate_name()?.peek().clone(); + let outputs = self.prepare_outputs()?.peek().clone(); + let lint_store = self.expansion()?.peek().2.clone(); + let hir = self.lower_to_hir()?.peek(); + let dep_graph = self.dep_graph()?.peek().clone(); + let (ref krate, ref resolver_outputs) = &*hir; + let _timer = self.session().timer("create_global_ctxt"); + Ok(passes::create_global_ctxt( + self.compiler, + lint_store, + krate, + dep_graph, + resolver_outputs.steal(), + outputs, + &crate_name, + &self.gcx, + &self.arena, + )) + }) + } + + pub fn ongoing_codegen(&'tcx self) -> Result<&Query<Box<dyn Any>>> { + self.ongoing_codegen.compute(|| { + let outputs = self.prepare_outputs()?; + self.global_ctxt()?.peek_mut().enter(|tcx| { + tcx.analysis(LOCAL_CRATE).ok(); + + // Don't do code generation if there were any errors + self.session().compile_status()?; + + // Hook for compile-fail tests. + Self::check_for_rustc_errors_attr(tcx); + + Ok(passes::start_codegen(&***self.codegen_backend(), tcx, &*outputs.peek())) + }) + }) + } + + /// Check for the `#[rustc_error]` annotation, which forces an error in codegen. This is used + /// to write compile-fail tests that actually test that compilation succeeds without reporting + /// an error. + fn check_for_rustc_errors_attr(tcx: TyCtxt<'_>) { + let def_id = match tcx.entry_fn(LOCAL_CRATE) { + Some((def_id, _)) => def_id, + _ => return, + }; + + let attrs = &*tcx.get_attrs(def_id.to_def_id()); + let attrs = attrs.iter().filter(|attr| tcx.sess.check_name(attr, sym::rustc_error)); + for attr in attrs { + match attr.meta_item_list() { + // Check if there is a `#[rustc_error(delay_span_bug_from_inside_query)]`. + Some(list) + if list.iter().any(|list_item| { + matches!( + list_item.ident().map(|i| i.name), + Some(sym::delay_span_bug_from_inside_query) + ) + }) => + { + tcx.ensure().trigger_delay_span_bug(def_id); + } + + // Bare `#[rustc_error]`. + None => { + tcx.sess.span_fatal( + tcx.def_span(def_id), + "fatal error triggered by #[rustc_error]", + ); + } + + // Some other attribute. + Some(_) => { + tcx.sess.span_warn( + tcx.def_span(def_id), + "unexpected annotation used with `#[rustc_error(...)]!", + ); + } + } + } + } + + pub fn linker(&'tcx self) -> Result<Linker> { + let dep_graph = self.dep_graph()?; + let prepare_outputs = self.prepare_outputs()?; + let ongoing_codegen = self.ongoing_codegen()?; + + let sess = self.session().clone(); + let codegen_backend = self.codegen_backend().clone(); + + Ok(Linker { + sess, + dep_graph: dep_graph.peek().clone(), + prepare_outputs: prepare_outputs.take(), + ongoing_codegen: ongoing_codegen.take(), + codegen_backend, + }) + } +} + +pub struct Linker { + sess: Lrc<Session>, + dep_graph: DepGraph, + prepare_outputs: OutputFilenames, + ongoing_codegen: Box<dyn Any>, + codegen_backend: Lrc<Box<dyn CodegenBackend>>, +} + +impl Linker { + pub fn link(self) -> Result<()> { + let codegen_results = + self.codegen_backend.join_codegen(self.ongoing_codegen, &self.sess, &self.dep_graph)?; + let prof = self.sess.prof.clone(); + let dep_graph = self.dep_graph; + prof.generic_activity("drop_dep_graph").run(move || drop(dep_graph)); + + if !self + .sess + .opts + .output_types + .keys() + .any(|&i| i == OutputType::Exe || i == OutputType::Metadata) + { + return Ok(()); + } + self.codegen_backend.link(&self.sess, codegen_results, &self.prepare_outputs) + } +} + +impl Compiler { + pub fn enter<F, T>(&self, f: F) -> T + where + F: for<'tcx> FnOnce(&'tcx Queries<'tcx>) -> T, + { + let mut _timer = None; + let queries = Queries::new(&self); + let ret = f(&queries); + + if self.session().opts.debugging_opts.query_stats { + if let Ok(gcx) = queries.global_ctxt() { + gcx.peek_mut().print_stats(); + } + } + + _timer = Some(self.session().timer("free_global_ctxt")); + + ret + } +} diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs new file mode 100644 index 00000000000..e94745519a4 --- /dev/null +++ b/compiler/rustc_interface/src/tests.rs @@ -0,0 +1,599 @@ +use crate::interface::parse_cfgspecs; + +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::{emitter::HumanReadableErrorType, registry, ColorConfig}; +use rustc_session::config::Strip; +use rustc_session::config::{build_configuration, build_session_options, to_crate_config}; +use rustc_session::config::{rustc_optgroups, ErrorOutputType, ExternLocation, Options, Passes}; +use rustc_session::config::{CFGuard, ExternEntry, LinkerPluginLto, LtoCli, SwitchWithOptPath}; +use rustc_session::config::{ + Externs, OutputType, OutputTypes, SanitizerSet, SymbolManglingVersion, +}; +use rustc_session::lint::Level; +use rustc_session::search_paths::SearchPath; +use rustc_session::utils::NativeLibKind; +use rustc_session::{build_session, getopts, DiagnosticOutput, Session}; +use rustc_span::edition::{Edition, DEFAULT_EDITION}; +use rustc_span::symbol::sym; +use rustc_span::SourceFileHashAlgorithm; +use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy}; +use rustc_target::spec::{RelocModel, RelroLevel, TlsModel}; +use std::collections::{BTreeMap, BTreeSet}; +use std::iter::FromIterator; +use std::path::PathBuf; + +type CfgSpecs = FxHashSet<(String, Option<String>)>; + +fn build_session_options_and_crate_config(matches: getopts::Matches) -> (Options, CfgSpecs) { + let sessopts = build_session_options(&matches); + let cfg = parse_cfgspecs(matches.opt_strs("cfg")); + (sessopts, cfg) +} + +fn mk_session(matches: getopts::Matches) -> (Session, CfgSpecs) { + let registry = registry::Registry::new(&[]); + let (sessopts, cfg) = build_session_options_and_crate_config(matches); + let sess = build_session( + sessopts, + None, + registry, + DiagnosticOutput::Default, + Default::default(), + None, + ); + (sess, cfg) +} + +fn new_public_extern_entry<S, I>(locations: I) -> ExternEntry +where + S: Into<String>, + I: IntoIterator<Item = S>, +{ + let locations: BTreeSet<_> = locations.into_iter().map(|s| s.into()).collect(); + + ExternEntry { + location: ExternLocation::ExactPaths(locations), + is_private_dep: false, + add_prelude: true, + } +} + +fn optgroups() -> getopts::Options { + let mut opts = getopts::Options::new(); + for group in rustc_optgroups() { + (group.apply)(&mut opts); + } + return opts; +} + +fn mk_map<K: Ord, V>(entries: Vec<(K, V)>) -> BTreeMap<K, V> { + BTreeMap::from_iter(entries.into_iter()) +} + +// When the user supplies --test we should implicitly supply --cfg test +#[test] +fn test_switch_implies_cfg_test() { + rustc_span::with_default_session_globals(|| { + let matches = optgroups().parse(&["--test".to_string()]).unwrap(); + let (sess, cfg) = mk_session(matches); + let cfg = build_configuration(&sess, to_crate_config(cfg)); + assert!(cfg.contains(&(sym::test, None))); + }); +} + +// When the user supplies --test and --cfg test, don't implicitly add another --cfg test +#[test] +fn test_switch_implies_cfg_test_unless_cfg_test() { + rustc_span::with_default_session_globals(|| { + let matches = optgroups().parse(&["--test".to_string(), "--cfg=test".to_string()]).unwrap(); + let (sess, cfg) = mk_session(matches); + let cfg = build_configuration(&sess, to_crate_config(cfg)); + let mut test_items = cfg.iter().filter(|&&(name, _)| name == sym::test); + assert!(test_items.next().is_some()); + assert!(test_items.next().is_none()); + }); +} + +#[test] +fn test_can_print_warnings() { + rustc_span::with_default_session_globals(|| { + let matches = optgroups().parse(&["-Awarnings".to_string()]).unwrap(); + let (sess, _) = mk_session(matches); + assert!(!sess.diagnostic().can_emit_warnings()); + }); + + rustc_span::with_default_session_globals(|| { + let matches = + optgroups().parse(&["-Awarnings".to_string(), "-Dwarnings".to_string()]).unwrap(); + let (sess, _) = mk_session(matches); + assert!(sess.diagnostic().can_emit_warnings()); + }); + + rustc_span::with_default_session_globals(|| { + let matches = optgroups().parse(&["-Adead_code".to_string()]).unwrap(); + let (sess, _) = mk_session(matches); + assert!(sess.diagnostic().can_emit_warnings()); + }); +} + +#[test] +fn test_output_types_tracking_hash_different_paths() { + let mut v1 = Options::default(); + let mut v2 = Options::default(); + let mut v3 = Options::default(); + + v1.output_types = OutputTypes::new(&[(OutputType::Exe, Some(PathBuf::from("./some/thing")))]); + v2.output_types = OutputTypes::new(&[(OutputType::Exe, Some(PathBuf::from("/some/thing")))]); + v3.output_types = OutputTypes::new(&[(OutputType::Exe, None)]); + + assert!(v1.dep_tracking_hash() != v2.dep_tracking_hash()); + assert!(v1.dep_tracking_hash() != v3.dep_tracking_hash()); + assert!(v2.dep_tracking_hash() != v3.dep_tracking_hash()); + + // Check clone + assert_eq!(v1.dep_tracking_hash(), v1.clone().dep_tracking_hash()); + assert_eq!(v2.dep_tracking_hash(), v2.clone().dep_tracking_hash()); + assert_eq!(v3.dep_tracking_hash(), v3.clone().dep_tracking_hash()); +} + +#[test] +fn test_output_types_tracking_hash_different_construction_order() { + let mut v1 = Options::default(); + let mut v2 = Options::default(); + + v1.output_types = OutputTypes::new(&[ + (OutputType::Exe, Some(PathBuf::from("./some/thing"))), + (OutputType::Bitcode, Some(PathBuf::from("./some/thing.bc"))), + ]); + + v2.output_types = OutputTypes::new(&[ + (OutputType::Bitcode, Some(PathBuf::from("./some/thing.bc"))), + (OutputType::Exe, Some(PathBuf::from("./some/thing"))), + ]); + + assert_eq!(v1.dep_tracking_hash(), v2.dep_tracking_hash()); + + // Check clone + assert_eq!(v1.dep_tracking_hash(), v1.clone().dep_tracking_hash()); +} + +#[test] +fn test_externs_tracking_hash_different_construction_order() { + let mut v1 = Options::default(); + let mut v2 = Options::default(); + let mut v3 = Options::default(); + + v1.externs = Externs::new(mk_map(vec![ + (String::from("a"), new_public_extern_entry(vec!["b", "c"])), + (String::from("d"), new_public_extern_entry(vec!["e", "f"])), + ])); + + v2.externs = Externs::new(mk_map(vec![ + (String::from("d"), new_public_extern_entry(vec!["e", "f"])), + (String::from("a"), new_public_extern_entry(vec!["b", "c"])), + ])); + + v3.externs = Externs::new(mk_map(vec![ + (String::from("a"), new_public_extern_entry(vec!["b", "c"])), + (String::from("d"), new_public_extern_entry(vec!["f", "e"])), + ])); + + assert_eq!(v1.dep_tracking_hash(), v2.dep_tracking_hash()); + assert_eq!(v1.dep_tracking_hash(), v3.dep_tracking_hash()); + assert_eq!(v2.dep_tracking_hash(), v3.dep_tracking_hash()); + + // Check clone + assert_eq!(v1.dep_tracking_hash(), v1.clone().dep_tracking_hash()); + assert_eq!(v2.dep_tracking_hash(), v2.clone().dep_tracking_hash()); + assert_eq!(v3.dep_tracking_hash(), v3.clone().dep_tracking_hash()); +} + +#[test] +fn test_lints_tracking_hash_different_values() { + let mut v1 = Options::default(); + let mut v2 = Options::default(); + let mut v3 = Options::default(); + + v1.lint_opts = vec![ + (String::from("a"), Level::Allow), + (String::from("b"), Level::Warn), + (String::from("c"), Level::Deny), + (String::from("d"), Level::Forbid), + ]; + + v2.lint_opts = vec![ + (String::from("a"), Level::Allow), + (String::from("b"), Level::Warn), + (String::from("X"), Level::Deny), + (String::from("d"), Level::Forbid), + ]; + + v3.lint_opts = vec![ + (String::from("a"), Level::Allow), + (String::from("b"), Level::Warn), + (String::from("c"), Level::Forbid), + (String::from("d"), Level::Deny), + ]; + + assert!(v1.dep_tracking_hash() != v2.dep_tracking_hash()); + assert!(v1.dep_tracking_hash() != v3.dep_tracking_hash()); + assert!(v2.dep_tracking_hash() != v3.dep_tracking_hash()); + + // Check clone + assert_eq!(v1.dep_tracking_hash(), v1.clone().dep_tracking_hash()); + assert_eq!(v2.dep_tracking_hash(), v2.clone().dep_tracking_hash()); + assert_eq!(v3.dep_tracking_hash(), v3.clone().dep_tracking_hash()); +} + +#[test] +fn test_lints_tracking_hash_different_construction_order() { + let mut v1 = Options::default(); + let mut v2 = Options::default(); + + v1.lint_opts = vec![ + (String::from("a"), Level::Allow), + (String::from("b"), Level::Warn), + (String::from("c"), Level::Deny), + (String::from("d"), Level::Forbid), + ]; + + v2.lint_opts = vec![ + (String::from("a"), Level::Allow), + (String::from("c"), Level::Deny), + (String::from("b"), Level::Warn), + (String::from("d"), Level::Forbid), + ]; + + assert_eq!(v1.dep_tracking_hash(), v2.dep_tracking_hash()); + + // Check clone + assert_eq!(v1.dep_tracking_hash(), v1.clone().dep_tracking_hash()); + assert_eq!(v2.dep_tracking_hash(), v2.clone().dep_tracking_hash()); +} + +#[test] +fn test_search_paths_tracking_hash_different_order() { + let mut v1 = Options::default(); + let mut v2 = Options::default(); + let mut v3 = Options::default(); + let mut v4 = Options::default(); + + const JSON: ErrorOutputType = ErrorOutputType::Json { + pretty: false, + json_rendered: HumanReadableErrorType::Default(ColorConfig::Never), + }; + + // Reference + v1.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON)); + v1.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON)); + v1.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON)); + v1.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON)); + v1.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON)); + + v2.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON)); + v2.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON)); + v2.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON)); + v2.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON)); + v2.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON)); + + v3.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON)); + v3.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON)); + v3.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON)); + v3.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON)); + v3.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON)); + + v4.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON)); + v4.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON)); + v4.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON)); + v4.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON)); + v4.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON)); + + assert!(v1.dep_tracking_hash() == v2.dep_tracking_hash()); + assert!(v1.dep_tracking_hash() == v3.dep_tracking_hash()); + assert!(v1.dep_tracking_hash() == v4.dep_tracking_hash()); + + // Check clone + assert_eq!(v1.dep_tracking_hash(), v1.clone().dep_tracking_hash()); + assert_eq!(v2.dep_tracking_hash(), v2.clone().dep_tracking_hash()); + assert_eq!(v3.dep_tracking_hash(), v3.clone().dep_tracking_hash()); + assert_eq!(v4.dep_tracking_hash(), v4.clone().dep_tracking_hash()); +} + +#[test] +fn test_native_libs_tracking_hash_different_values() { + let mut v1 = Options::default(); + let mut v2 = Options::default(); + let mut v3 = Options::default(); + let mut v4 = Options::default(); + + // Reference + v1.libs = vec![ + (String::from("a"), None, NativeLibKind::StaticBundle), + (String::from("b"), None, NativeLibKind::Framework), + (String::from("c"), None, NativeLibKind::Unspecified), + ]; + + // Change label + v2.libs = vec![ + (String::from("a"), None, NativeLibKind::StaticBundle), + (String::from("X"), None, NativeLibKind::Framework), + (String::from("c"), None, NativeLibKind::Unspecified), + ]; + + // Change kind + v3.libs = vec![ + (String::from("a"), None, NativeLibKind::StaticBundle), + (String::from("b"), None, NativeLibKind::StaticBundle), + (String::from("c"), None, NativeLibKind::Unspecified), + ]; + + // Change new-name + v4.libs = vec![ + (String::from("a"), None, NativeLibKind::StaticBundle), + (String::from("b"), Some(String::from("X")), NativeLibKind::Framework), + (String::from("c"), None, NativeLibKind::Unspecified), + ]; + + assert!(v1.dep_tracking_hash() != v2.dep_tracking_hash()); + assert!(v1.dep_tracking_hash() != v3.dep_tracking_hash()); + assert!(v1.dep_tracking_hash() != v4.dep_tracking_hash()); + + // Check clone + assert_eq!(v1.dep_tracking_hash(), v1.clone().dep_tracking_hash()); + assert_eq!(v2.dep_tracking_hash(), v2.clone().dep_tracking_hash()); + assert_eq!(v3.dep_tracking_hash(), v3.clone().dep_tracking_hash()); + assert_eq!(v4.dep_tracking_hash(), v4.clone().dep_tracking_hash()); +} + +#[test] +fn test_native_libs_tracking_hash_different_order() { + let mut v1 = Options::default(); + let mut v2 = Options::default(); + let mut v3 = Options::default(); + + // Reference + v1.libs = vec![ + (String::from("a"), None, NativeLibKind::StaticBundle), + (String::from("b"), None, NativeLibKind::Framework), + (String::from("c"), None, NativeLibKind::Unspecified), + ]; + + v2.libs = vec![ + (String::from("b"), None, NativeLibKind::Framework), + (String::from("a"), None, NativeLibKind::StaticBundle), + (String::from("c"), None, NativeLibKind::Unspecified), + ]; + + v3.libs = vec![ + (String::from("c"), None, NativeLibKind::Unspecified), + (String::from("a"), None, NativeLibKind::StaticBundle), + (String::from("b"), None, NativeLibKind::Framework), + ]; + + assert!(v1.dep_tracking_hash() == v2.dep_tracking_hash()); + assert!(v1.dep_tracking_hash() == v3.dep_tracking_hash()); + assert!(v2.dep_tracking_hash() == v3.dep_tracking_hash()); + + // Check clone + assert_eq!(v1.dep_tracking_hash(), v1.clone().dep_tracking_hash()); + assert_eq!(v2.dep_tracking_hash(), v2.clone().dep_tracking_hash()); + assert_eq!(v3.dep_tracking_hash(), v3.clone().dep_tracking_hash()); +} + +#[test] +fn test_codegen_options_tracking_hash() { + let reference = Options::default(); + let mut opts = Options::default(); + + macro_rules! untracked { + ($name: ident, $non_default_value: expr) => { + opts.cg.$name = $non_default_value; + assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); + }; + } + + // Make sure that changing an [UNTRACKED] option leaves the hash unchanged. + // This list is in alphabetical order. + untracked!(ar, String::from("abc")); + untracked!(codegen_units, Some(42)); + untracked!(default_linker_libraries, true); + untracked!(extra_filename, String::from("extra-filename")); + untracked!(incremental, Some(String::from("abc"))); + // `link_arg` is omitted because it just forwards to `link_args`. + untracked!(link_args, vec![String::from("abc"), String::from("def")]); + untracked!(link_dead_code, Some(true)); + untracked!(linker, Some(PathBuf::from("linker"))); + untracked!(linker_flavor, Some(LinkerFlavor::Gcc)); + untracked!(no_stack_check, true); + untracked!(remark, Passes::Some(vec![String::from("pass1"), String::from("pass2")])); + untracked!(rpath, true); + untracked!(save_temps, true); + + macro_rules! tracked { + ($name: ident, $non_default_value: expr) => { + opts = reference.clone(); + opts.cg.$name = $non_default_value; + assert_ne!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); + }; + } + + // Make sure that changing a [TRACKED] option changes the hash. + // This list is in alphabetical order. + tracked!(code_model, Some(CodeModel::Large)); + tracked!(control_flow_guard, CFGuard::Checks); + tracked!(debug_assertions, Some(true)); + tracked!(debuginfo, 0xdeadbeef); + tracked!(embed_bitcode, false); + tracked!(force_frame_pointers, Some(false)); + tracked!(force_unwind_tables, Some(true)); + tracked!(inline_threshold, Some(0xf007ba11)); + tracked!(linker_plugin_lto, LinkerPluginLto::LinkerPluginAuto); + tracked!(llvm_args, vec![String::from("1"), String::from("2")]); + tracked!(lto, LtoCli::Fat); + tracked!(metadata, vec![String::from("A"), String::from("B")]); + tracked!(no_prepopulate_passes, true); + tracked!(no_redzone, Some(true)); + tracked!(no_vectorize_loops, true); + tracked!(no_vectorize_slp, true); + tracked!(opt_level, "3".to_string()); + tracked!(overflow_checks, Some(true)); + tracked!(panic, Some(PanicStrategy::Abort)); + tracked!(passes, vec![String::from("1"), String::from("2")]); + tracked!(prefer_dynamic, true); + tracked!(profile_generate, SwitchWithOptPath::Enabled(None)); + tracked!(profile_use, Some(PathBuf::from("abc"))); + tracked!(relocation_model, Some(RelocModel::Pic)); + tracked!(soft_float, true); + tracked!(target_cpu, Some(String::from("abc"))); + tracked!(target_feature, String::from("all the features, all of them")); +} + +#[test] +fn test_debugging_options_tracking_hash() { + let reference = Options::default(); + let mut opts = Options::default(); + + macro_rules! untracked { + ($name: ident, $non_default_value: expr) => { + opts.debugging_opts.$name = $non_default_value; + assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); + }; + } + + // Make sure that changing an [UNTRACKED] option leaves the hash unchanged. + // This list is in alphabetical order. + untracked!(ast_json, true); + untracked!(ast_json_noexpand, true); + untracked!(borrowck, String::from("other")); + untracked!(borrowck_stats, true); + untracked!(deduplicate_diagnostics, true); + untracked!(dep_tasks, true); + untracked!(dont_buffer_diagnostics, true); + untracked!(dump_dep_graph, true); + untracked!(dump_mir, Some(String::from("abc"))); + untracked!(dump_mir_dataflow, true); + untracked!(dump_mir_dir, String::from("abc")); + untracked!(dump_mir_exclude_pass_number, true); + untracked!(dump_mir_graphviz, true); + untracked!(emit_stack_sizes, true); + untracked!(hir_stats, true); + untracked!(identify_regions, true); + untracked!(incremental_ignore_spans, true); + untracked!(incremental_info, true); + untracked!(incremental_verify_ich, true); + untracked!(input_stats, true); + untracked!(keep_hygiene_data, true); + untracked!(link_native_libraries, false); + untracked!(llvm_time_trace, true); + untracked!(ls, true); + untracked!(macro_backtrace, true); + untracked!(meta_stats, true); + untracked!(nll_facts, true); + untracked!(no_analysis, true); + untracked!(no_interleave_lints, true); + untracked!(no_leak_check, true); + untracked!(no_parallel_llvm, true); + untracked!(parse_only, true); + untracked!(perf_stats, true); + untracked!(polonius, true); + // `pre_link_arg` is omitted because it just forwards to `pre_link_args`. + untracked!(pre_link_args, vec![String::from("abc"), String::from("def")]); + untracked!(print_link_args, true); + untracked!(print_llvm_passes, true); + untracked!(print_mono_items, Some(String::from("abc"))); + untracked!(print_type_sizes, true); + untracked!(query_dep_graph, true); + untracked!(query_stats, true); + untracked!(save_analysis, true); + untracked!(self_profile, SwitchWithOptPath::Enabled(None)); + untracked!(self_profile_events, Some(vec![String::new()])); + untracked!(span_debug, true); + untracked!(span_free_formats, true); + untracked!(strip, Strip::None); + untracked!(terminal_width, Some(80)); + untracked!(threads, 99); + untracked!(time, true); + untracked!(time_llvm_passes, true); + untracked!(time_passes, true); + untracked!(trace_macros, true); + untracked!(ui_testing, true); + untracked!(unpretty, Some("expanded".to_string())); + untracked!(unstable_options, true); + untracked!(validate_mir, true); + untracked!(verbose, true); + + macro_rules! tracked { + ($name: ident, $non_default_value: expr) => { + opts = reference.clone(); + opts.debugging_opts.$name = $non_default_value; + assert_ne!(reference.dep_tracking_hash(), opts.dep_tracking_hash()); + }; + } + + // Make sure that changing a [TRACKED] option changes the hash. + // This list is in alphabetical order. + tracked!(allow_features, Some(vec![String::from("lang_items")])); + tracked!(always_encode_mir, true); + tracked!(asm_comments, true); + tracked!(binary_dep_depinfo, true); + tracked!(chalk, true); + tracked!(codegen_backend, Some("abc".to_string())); + tracked!(crate_attr, vec!["abc".to_string()]); + tracked!(debug_macros, true); + tracked!(dep_info_omit_d_target, true); + tracked!(dual_proc_macros, true); + tracked!(fewer_names, true); + tracked!(force_overflow_checks, Some(true)); + tracked!(force_unstable_if_unmarked, true); + tracked!(fuel, Some(("abc".to_string(), 99))); + tracked!(human_readable_cgu_names, true); + tracked!(inline_in_all_cgus, Some(true)); + tracked!(insert_sideeffect, true); + tracked!(instrument_coverage, true); + tracked!(instrument_mcount, true); + tracked!(link_only, true); + tracked!(merge_functions, Some(MergeFunctions::Disabled)); + tracked!(mir_emit_retag, true); + tracked!(mir_opt_level, 3); + tracked!(mutable_noalias, true); + tracked!(new_llvm_pass_manager, true); + tracked!(no_codegen, true); + tracked!(no_generate_arange_section, true); + tracked!(no_link, true); + tracked!(no_profiler_runtime, true); + tracked!(osx_rpath_install_name, true); + tracked!(panic_abort_tests, true); + tracked!(plt, Some(true)); + tracked!(print_fuel, Some("abc".to_string())); + tracked!(profile, true); + tracked!(profile_emit, Some(PathBuf::from("abc"))); + tracked!(relro_level, Some(RelroLevel::Full)); + tracked!(report_delayed_bugs, true); + tracked!(run_dsymutil, false); + tracked!(sanitizer, SanitizerSet::ADDRESS); + tracked!(sanitizer_memory_track_origins, 2); + tracked!(sanitizer_recover, SanitizerSet::ADDRESS); + tracked!(saturating_float_casts, Some(true)); + tracked!(share_generics, Some(true)); + tracked!(show_span, Some(String::from("abc"))); + tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1)); + tracked!(symbol_mangling_version, SymbolManglingVersion::V0); + tracked!(teach, true); + tracked!(thinlto, Some(true)); + tracked!(tls_model, Some(TlsModel::GeneralDynamic)); + tracked!(treat_err_as_bug, Some(1)); + tracked!(unleash_the_miri_inside_of_you, true); + tracked!(use_ctors_section, Some(true)); + tracked!(verify_llvm_ir, true); +} + +#[test] +fn test_edition_parsing() { + // test default edition + let options = Options::default(); + assert!(options.edition == DEFAULT_EDITION); + + let matches = optgroups().parse(&["--edition=2018".to_string()]).unwrap(); + let (sessopts, _) = build_session_options_and_crate_config(matches); + assert!(sessopts.edition == Edition::Edition2018) +} diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs new file mode 100644 index 00000000000..8816ba198cf --- /dev/null +++ b/compiler/rustc_interface/src/util.rs @@ -0,0 +1,770 @@ +use rustc_ast::mut_visit::{visit_clobber, MutVisitor, *}; +use rustc_ast::ptr::P; +use rustc_ast::util::lev_distance::find_best_match_for_name; +use rustc_ast::{self as ast, AttrVec, BlockCheckMode}; +use rustc_codegen_ssa::traits::CodegenBackend; +use rustc_data_structures::fingerprint::Fingerprint; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +#[cfg(parallel_compiler)] +use rustc_data_structures::jobserver; +use rustc_data_structures::stable_hasher::StableHasher; +use rustc_data_structures::sync::Lrc; +use rustc_errors::registry::Registry; +use rustc_metadata::dynamic_lib::DynamicLibrary; +use rustc_resolve::{self, Resolver}; +use rustc_session as session; +use rustc_session::config::{self, CrateType}; +use rustc_session::config::{ErrorOutputType, Input, OutputFilenames}; +use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer}; +use rustc_session::parse::CrateConfig; +use rustc_session::CrateDisambiguator; +use rustc_session::{early_error, filesearch, output, DiagnosticOutput, Session}; +use rustc_span::edition::Edition; +use rustc_span::source_map::FileLoader; +use rustc_span::symbol::{sym, Symbol}; +use smallvec::SmallVec; +use std::env; +use std::io::{self, Write}; +use std::mem; +use std::ops::DerefMut; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex, Once}; +#[cfg(not(parallel_compiler))] +use std::{panic, thread}; +use tracing::info; + +/// Adds `target_feature = "..."` cfgs for a variety of platform +/// specific features (SSE, NEON etc.). +/// +/// This is performed by checking whether a set of permitted features +/// is available on the target machine, by querying LLVM. +pub fn add_configuration( + cfg: &mut CrateConfig, + sess: &mut Session, + codegen_backend: &dyn CodegenBackend, +) { + let tf = sym::target_feature; + + let target_features = codegen_backend.target_features(sess); + sess.target_features.extend(target_features.iter().cloned()); + + cfg.extend(target_features.into_iter().map(|feat| (tf, Some(feat)))); + + if sess.crt_static(None) { + cfg.insert((tf, Some(sym::crt_dash_static))); + } +} + +pub fn create_session( + sopts: config::Options, + cfg: FxHashSet<(String, Option<String>)>, + diagnostic_output: DiagnosticOutput, + file_loader: Option<Box<dyn FileLoader + Send + Sync + 'static>>, + input_path: Option<PathBuf>, + lint_caps: FxHashMap<lint::LintId, lint::Level>, + descriptions: Registry, +) -> (Lrc<Session>, Lrc<Box<dyn CodegenBackend>>) { + let mut sess = session::build_session( + sopts, + input_path, + descriptions, + diagnostic_output, + lint_caps, + file_loader, + ); + + let codegen_backend = get_codegen_backend(&sess); + + let mut cfg = config::build_configuration(&sess, config::to_crate_config(cfg)); + add_configuration(&mut cfg, &mut sess, &*codegen_backend); + sess.parse_sess.config = cfg; + + (Lrc::new(sess), Lrc::new(codegen_backend)) +} + +const STACK_SIZE: usize = 8 * 1024 * 1024; + +fn get_stack_size() -> Option<usize> { + // FIXME: Hacks on hacks. If the env is trying to override the stack size + // then *don't* set it explicitly. + env::var_os("RUST_MIN_STACK").is_none().then_some(STACK_SIZE) +} + +struct Sink(Arc<Mutex<Vec<u8>>>); +impl Write for Sink { + fn write(&mut self, data: &[u8]) -> io::Result<usize> { + Write::write(&mut *self.0.lock().unwrap(), data) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// Like a `thread::Builder::spawn` followed by a `join()`, but avoids the need +/// for `'static` bounds. +#[cfg(not(parallel_compiler))] +pub fn scoped_thread<F: FnOnce() -> R + Send, R: Send>(cfg: thread::Builder, f: F) -> R { + struct Ptr(*mut ()); + unsafe impl Send for Ptr {} + unsafe impl Sync for Ptr {} + + let mut f = Some(f); + let run = Ptr(&mut f as *mut _ as *mut ()); + let mut result = None; + let result_ptr = Ptr(&mut result as *mut _ as *mut ()); + + let thread = cfg.spawn(move || { + let run = unsafe { (*(run.0 as *mut Option<F>)).take().unwrap() }; + let result = unsafe { &mut *(result_ptr.0 as *mut Option<R>) }; + *result = Some(run()); + }); + + match thread.unwrap().join() { + Ok(()) => result.unwrap(), + Err(p) => panic::resume_unwind(p), + } +} + +#[cfg(not(parallel_compiler))] +pub fn setup_callbacks_and_run_in_thread_pool_with_globals<F: FnOnce() -> R + Send, R: Send>( + edition: Edition, + _threads: usize, + stderr: &Option<Arc<Mutex<Vec<u8>>>>, + f: F, +) -> R { + let mut cfg = thread::Builder::new().name("rustc".to_string()); + + if let Some(size) = get_stack_size() { + cfg = cfg.stack_size(size); + } + + crate::callbacks::setup_callbacks(); + + let main_handler = move || { + rustc_span::with_session_globals(edition, || { + if let Some(stderr) = stderr { + io::set_panic(Some(box Sink(stderr.clone()))); + } + f() + }) + }; + + scoped_thread(cfg, main_handler) +} + +#[cfg(parallel_compiler)] +pub fn setup_callbacks_and_run_in_thread_pool_with_globals<F: FnOnce() -> R + Send, R: Send>( + edition: Edition, + threads: usize, + stderr: &Option<Arc<Mutex<Vec<u8>>>>, + f: F, +) -> R { + use rustc_middle::ty; + crate::callbacks::setup_callbacks(); + + let mut config = rayon::ThreadPoolBuilder::new() + .thread_name(|_| "rustc".to_string()) + .acquire_thread_handler(jobserver::acquire_thread) + .release_thread_handler(jobserver::release_thread) + .num_threads(threads) + .deadlock_handler(|| unsafe { ty::query::handle_deadlock() }); + + if let Some(size) = get_stack_size() { + config = config.stack_size(size); + } + + let with_pool = move |pool: &rayon::ThreadPool| pool.install(move || f()); + + rustc_span::with_session_globals(edition, || { + rustc_span::SESSION_GLOBALS.with(|session_globals| { + // The main handler runs for each Rayon worker thread and sets up + // the thread local rustc uses. `session_globals` is captured and set + // on the new threads. + let main_handler = move |thread: rayon::ThreadBuilder| { + rustc_span::SESSION_GLOBALS.set(session_globals, || { + if let Some(stderr) = stderr { + io::set_panic(Some(box Sink(stderr.clone()))); + } + thread.run() + }) + }; + + config.build_scoped(main_handler, with_pool).unwrap() + }) + }) +} + +fn load_backend_from_dylib(path: &Path) -> fn() -> Box<dyn CodegenBackend> { + let lib = DynamicLibrary::open(path).unwrap_or_else(|err| { + let err = format!("couldn't load codegen backend {:?}: {:?}", path, err); + early_error(ErrorOutputType::default(), &err); + }); + unsafe { + match lib.symbol("__rustc_codegen_backend") { + Ok(f) => { + mem::forget(lib); + mem::transmute::<*mut u8, _>(f) + } + Err(e) => { + let err = format!( + "couldn't load codegen backend as it \ + doesn't export the `__rustc_codegen_backend` \ + symbol: {:?}", + e + ); + early_error(ErrorOutputType::default(), &err); + } + } + } +} + +pub fn get_codegen_backend(sess: &Session) -> Box<dyn CodegenBackend> { + static INIT: Once = Once::new(); + + static mut LOAD: fn() -> Box<dyn CodegenBackend> = || unreachable!(); + + INIT.call_once(|| { + let codegen_name = sess.opts.debugging_opts.codegen_backend.as_deref().unwrap_or("llvm"); + let backend = match codegen_name { + filename if filename.contains('.') => load_backend_from_dylib(filename.as_ref()), + codegen_name => get_builtin_codegen_backend(codegen_name), + }; + + unsafe { + LOAD = backend; + } + }); + let backend = unsafe { LOAD() }; + backend.init(sess); + backend +} + +// This is used for rustdoc, but it uses similar machinery to codegen backend +// loading, so we leave the code here. It is potentially useful for other tools +// that want to invoke the rustc binary while linking to rustc as well. +pub fn rustc_path<'a>() -> Option<&'a Path> { + static RUSTC_PATH: once_cell::sync::OnceCell<Option<PathBuf>> = + once_cell::sync::OnceCell::new(); + + const BIN_PATH: &str = env!("RUSTC_INSTALL_BINDIR"); + + RUSTC_PATH.get_or_init(|| get_rustc_path_inner(BIN_PATH)).as_ref().map(|v| &**v) +} + +fn get_rustc_path_inner(bin_path: &str) -> Option<PathBuf> { + sysroot_candidates().iter().find_map(|sysroot| { + let candidate = sysroot.join(bin_path).join(if cfg!(target_os = "windows") { + "rustc.exe" + } else { + "rustc" + }); + candidate.exists().then_some(candidate) + }) +} + +fn sysroot_candidates() -> Vec<PathBuf> { + let target = session::config::host_triple(); + let mut sysroot_candidates = vec![filesearch::get_or_default_sysroot()]; + let path = current_dll_path().and_then(|s| s.canonicalize().ok()); + if let Some(dll) = path { + // use `parent` twice to chop off the file name and then also the + // directory containing the dll which should be either `lib` or `bin`. + if let Some(path) = dll.parent().and_then(|p| p.parent()) { + // The original `path` pointed at the `rustc_driver` crate's dll. + // Now that dll should only be in one of two locations. The first is + // in the compiler's libdir, for example `$sysroot/lib/*.dll`. The + // other is the target's libdir, for example + // `$sysroot/lib/rustlib/$target/lib/*.dll`. + // + // We don't know which, so let's assume that if our `path` above + // ends in `$target` we *could* be in the target libdir, and always + // assume that we may be in the main libdir. + sysroot_candidates.push(path.to_owned()); + + if path.ends_with(target) { + sysroot_candidates.extend( + path.parent() // chop off `$target` + .and_then(|p| p.parent()) // chop off `rustlib` + .and_then(|p| p.parent()) // chop off `lib` + .map(|s| s.to_owned()), + ); + } + } + } + + return sysroot_candidates; + + #[cfg(unix)] + fn current_dll_path() -> Option<PathBuf> { + use std::ffi::{CStr, OsStr}; + use std::os::unix::prelude::*; + + unsafe { + let addr = current_dll_path as usize as *mut _; + let mut info = mem::zeroed(); + if libc::dladdr(addr, &mut info) == 0 { + info!("dladdr failed"); + return None; + } + if info.dli_fname.is_null() { + info!("dladdr returned null pointer"); + return None; + } + let bytes = CStr::from_ptr(info.dli_fname).to_bytes(); + let os = OsStr::from_bytes(bytes); + Some(PathBuf::from(os)) + } + } + + #[cfg(windows)] + fn current_dll_path() -> Option<PathBuf> { + use std::ffi::OsString; + use std::os::windows::prelude::*; + use std::ptr; + + use winapi::um::libloaderapi::{ + GetModuleFileNameW, GetModuleHandleExW, GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + }; + + unsafe { + let mut module = ptr::null_mut(); + let r = GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + current_dll_path as usize as *mut _, + &mut module, + ); + if r == 0 { + info!("GetModuleHandleExW failed: {}", io::Error::last_os_error()); + return None; + } + let mut space = Vec::with_capacity(1024); + let r = GetModuleFileNameW(module, space.as_mut_ptr(), space.capacity() as u32); + if r == 0 { + info!("GetModuleFileNameW failed: {}", io::Error::last_os_error()); + return None; + } + let r = r as usize; + if r >= space.capacity() { + info!("our buffer was too small? {}", io::Error::last_os_error()); + return None; + } + space.set_len(r); + let os = OsString::from_wide(&space); + Some(PathBuf::from(os)) + } + } +} + +pub fn get_builtin_codegen_backend(backend_name: &str) -> fn() -> Box<dyn CodegenBackend> { + #[cfg(feature = "llvm")] + { + if backend_name == "llvm" { + return rustc_codegen_llvm::LlvmCodegenBackend::new; + } + } + + let err = format!("unsupported builtin codegen backend `{}`", backend_name); + early_error(ErrorOutputType::default(), &err); +} + +pub(crate) fn compute_crate_disambiguator(session: &Session) -> CrateDisambiguator { + use std::hash::Hasher; + + // The crate_disambiguator is a 128 bit hash. The disambiguator is fed + // into various other hashes quite a bit (symbol hashes, incr. comp. hashes, + // debuginfo type IDs, etc), so we don't want it to be too wide. 128 bits + // should still be safe enough to avoid collisions in practice. + let mut hasher = StableHasher::new(); + + let mut metadata = session.opts.cg.metadata.clone(); + // We don't want the crate_disambiguator to dependent on the order + // -C metadata arguments, so sort them: + metadata.sort(); + // Every distinct -C metadata value is only incorporated once: + metadata.dedup(); + + hasher.write(b"metadata"); + for s in &metadata { + // Also incorporate the length of a metadata string, so that we generate + // different values for `-Cmetadata=ab -Cmetadata=c` and + // `-Cmetadata=a -Cmetadata=bc` + hasher.write_usize(s.len()); + hasher.write(s.as_bytes()); + } + + // Also incorporate crate type, so that we don't get symbol conflicts when + // linking against a library of the same name, if this is an executable. + let is_exe = session.crate_types().contains(&CrateType::Executable); + hasher.write(if is_exe { b"exe" } else { b"lib" }); + + CrateDisambiguator::from(hasher.finish::<Fingerprint>()) +} + +pub(crate) fn check_attr_crate_type( + sess: &Session, + attrs: &[ast::Attribute], + lint_buffer: &mut LintBuffer, +) { + // Unconditionally collect crate types from attributes to make them used + for a in attrs.iter() { + if sess.check_name(a, sym::crate_type) { + if let Some(n) = a.value_str() { + if categorize_crate_type(n).is_some() { + return; + } + + if let ast::MetaItemKind::NameValue(spanned) = a.meta().unwrap().kind { + let span = spanned.span; + let lev_candidate = + find_best_match_for_name(CRATE_TYPES.iter().map(|(k, _)| k), n, None); + if let Some(candidate) = lev_candidate { + lint_buffer.buffer_lint_with_diagnostic( + lint::builtin::UNKNOWN_CRATE_TYPES, + ast::CRATE_NODE_ID, + span, + "invalid `crate_type` value", + BuiltinLintDiagnostics::UnknownCrateTypes( + span, + "did you mean".to_string(), + format!("\"{}\"", candidate), + ), + ); + } else { + lint_buffer.buffer_lint( + lint::builtin::UNKNOWN_CRATE_TYPES, + ast::CRATE_NODE_ID, + span, + "invalid `crate_type` value", + ); + } + } + } + } + } +} + +const CRATE_TYPES: &[(Symbol, CrateType)] = &[ + (sym::rlib, CrateType::Rlib), + (sym::dylib, CrateType::Dylib), + (sym::cdylib, CrateType::Cdylib), + (sym::lib, config::default_lib_output()), + (sym::staticlib, CrateType::Staticlib), + (sym::proc_dash_macro, CrateType::ProcMacro), + (sym::bin, CrateType::Executable), +]; + +fn categorize_crate_type(s: Symbol) -> Option<CrateType> { + Some(CRATE_TYPES.iter().find(|(key, _)| *key == s)?.1) +} + +pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<CrateType> { + // Unconditionally collect crate types from attributes to make them used + let attr_types: Vec<CrateType> = attrs + .iter() + .filter_map(|a| { + if session.check_name(a, sym::crate_type) { + match a.value_str() { + Some(s) => categorize_crate_type(s), + _ => None, + } + } else { + None + } + }) + .collect(); + + // If we're generating a test executable, then ignore all other output + // styles at all other locations + if session.opts.test { + return vec![CrateType::Executable]; + } + + // Only check command line flags if present. If no types are specified by + // command line, then reuse the empty `base` Vec to hold the types that + // will be found in crate attributes. + let mut base = session.opts.crate_types.clone(); + if base.is_empty() { + base.extend(attr_types); + if base.is_empty() { + base.push(output::default_output_for_target(session)); + } else { + base.sort(); + base.dedup(); + } + } + + base.retain(|crate_type| { + let res = !output::invalid_output_for_target(session, *crate_type); + + if !res { + session.warn(&format!( + "dropping unsupported crate type `{}` for target `{}`", + *crate_type, session.opts.target_triple + )); + } + + res + }); + + base +} + +pub fn build_output_filenames( + input: &Input, + odir: &Option<PathBuf>, + ofile: &Option<PathBuf>, + attrs: &[ast::Attribute], + sess: &Session, +) -> OutputFilenames { + match *ofile { + None => { + // "-" as input file will cause the parser to read from stdin so we + // have to make up a name + // We want to toss everything after the final '.' + let dirpath = (*odir).as_ref().cloned().unwrap_or_default(); + + // If a crate name is present, we use it as the link name + let stem = sess + .opts + .crate_name + .clone() + .or_else(|| rustc_attr::find_crate_name(&sess, attrs).map(|n| n.to_string())) + .unwrap_or_else(|| input.filestem().to_owned()); + + OutputFilenames::new( + dirpath, + stem, + None, + sess.opts.cg.extra_filename.clone(), + sess.opts.output_types.clone(), + ) + } + + Some(ref out_file) => { + let unnamed_output_types = + sess.opts.output_types.values().filter(|a| a.is_none()).count(); + let ofile = if unnamed_output_types > 1 { + sess.warn( + "due to multiple output types requested, the explicitly specified \ + output file name will be adapted for each output type", + ); + None + } else { + if !sess.opts.cg.extra_filename.is_empty() { + sess.warn("ignoring -C extra-filename flag due to -o flag"); + } + Some(out_file.clone()) + }; + if *odir != None { + sess.warn("ignoring --out-dir flag due to -o flag"); + } + + OutputFilenames::new( + out_file.parent().unwrap_or_else(|| Path::new("")).to_path_buf(), + out_file.file_stem().unwrap_or_default().to_str().unwrap().to_string(), + ofile, + sess.opts.cg.extra_filename.clone(), + sess.opts.output_types.clone(), + ) + } + } +} + +// Note: Also used by librustdoc, see PR #43348. Consider moving this struct elsewhere. +// +// FIXME: Currently the `everybody_loops` transformation is not applied to: +// * `const fn`, due to issue #43636 that `loop` is not supported for const evaluation. We are +// waiting for miri to fix that. +// * `impl Trait`, due to issue #43869 that functions returning impl Trait cannot be diverging. +// Solving this may require `!` to implement every trait, which relies on the an even more +// ambitious form of the closed RFC #1637. See also [#34511]. +// +// [#34511]: https://github.com/rust-lang/rust/issues/34511#issuecomment-322340401 +pub struct ReplaceBodyWithLoop<'a, 'b> { + within_static_or_const: bool, + nested_blocks: Option<Vec<ast::Block>>, + resolver: &'a mut Resolver<'b>, +} + +impl<'a, 'b> ReplaceBodyWithLoop<'a, 'b> { + pub fn new(resolver: &'a mut Resolver<'b>) -> ReplaceBodyWithLoop<'a, 'b> { + ReplaceBodyWithLoop { within_static_or_const: false, nested_blocks: None, resolver } + } + + fn run<R, F: FnOnce(&mut Self) -> R>(&mut self, is_const: bool, action: F) -> R { + let old_const = mem::replace(&mut self.within_static_or_const, is_const); + let old_blocks = self.nested_blocks.take(); + let ret = action(self); + self.within_static_or_const = old_const; + self.nested_blocks = old_blocks; + ret + } + + fn should_ignore_fn(ret_ty: &ast::FnRetTy) -> bool { + if let ast::FnRetTy::Ty(ref ty) = ret_ty { + fn involves_impl_trait(ty: &ast::Ty) -> bool { + match ty.kind { + ast::TyKind::ImplTrait(..) => true, + ast::TyKind::Slice(ref subty) + | ast::TyKind::Array(ref subty, _) + | ast::TyKind::Ptr(ast::MutTy { ty: ref subty, .. }) + | ast::TyKind::Rptr(_, ast::MutTy { ty: ref subty, .. }) + | ast::TyKind::Paren(ref subty) => involves_impl_trait(subty), + ast::TyKind::Tup(ref tys) => any_involves_impl_trait(tys.iter()), + ast::TyKind::Path(_, ref path) => { + path.segments.iter().any(|seg| match seg.args.as_deref() { + None => false, + Some(&ast::GenericArgs::AngleBracketed(ref data)) => { + data.args.iter().any(|arg| match arg { + ast::AngleBracketedArg::Arg(arg) => match arg { + ast::GenericArg::Type(ty) => involves_impl_trait(ty), + ast::GenericArg::Lifetime(_) + | ast::GenericArg::Const(_) => false, + }, + ast::AngleBracketedArg::Constraint(c) => match c.kind { + ast::AssocTyConstraintKind::Bound { .. } => true, + ast::AssocTyConstraintKind::Equality { ref ty } => { + involves_impl_trait(ty) + } + }, + }) + } + Some(&ast::GenericArgs::Parenthesized(ref data)) => { + any_involves_impl_trait(data.inputs.iter()) + || ReplaceBodyWithLoop::should_ignore_fn(&data.output) + } + }) + } + _ => false, + } + } + + fn any_involves_impl_trait<'a, I: Iterator<Item = &'a P<ast::Ty>>>(mut it: I) -> bool { + it.any(|subty| involves_impl_trait(subty)) + } + + involves_impl_trait(ty) + } else { + false + } + } + + fn is_sig_const(sig: &ast::FnSig) -> bool { + matches!(sig.header.constness, ast::Const::Yes(_)) + || ReplaceBodyWithLoop::should_ignore_fn(&sig.decl.output) + } +} + +impl<'a> MutVisitor for ReplaceBodyWithLoop<'a, '_> { + fn visit_item_kind(&mut self, i: &mut ast::ItemKind) { + let is_const = match i { + ast::ItemKind::Static(..) | ast::ItemKind::Const(..) => true, + ast::ItemKind::Fn(_, ref sig, _, _) => Self::is_sig_const(sig), + _ => false, + }; + self.run(is_const, |s| noop_visit_item_kind(i, s)) + } + + fn flat_map_trait_item(&mut self, i: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> { + let is_const = match i.kind { + ast::AssocItemKind::Const(..) => true, + ast::AssocItemKind::Fn(_, ref sig, _, _) => Self::is_sig_const(sig), + _ => false, + }; + self.run(is_const, |s| noop_flat_map_assoc_item(i, s)) + } + + fn flat_map_impl_item(&mut self, i: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> { + self.flat_map_trait_item(i) + } + + fn visit_anon_const(&mut self, c: &mut ast::AnonConst) { + self.run(true, |s| noop_visit_anon_const(c, s)) + } + + fn visit_block(&mut self, b: &mut P<ast::Block>) { + fn stmt_to_block( + rules: ast::BlockCheckMode, + s: Option<ast::Stmt>, + resolver: &mut Resolver<'_>, + ) -> ast::Block { + ast::Block { + stmts: s.into_iter().collect(), + rules, + id: resolver.next_node_id(), + span: rustc_span::DUMMY_SP, + } + } + + fn block_to_stmt(b: ast::Block, resolver: &mut Resolver<'_>) -> ast::Stmt { + let expr = P(ast::Expr { + id: resolver.next_node_id(), + kind: ast::ExprKind::Block(P(b), None), + span: rustc_span::DUMMY_SP, + attrs: AttrVec::new(), + tokens: None, + }); + + ast::Stmt { + id: resolver.next_node_id(), + kind: ast::StmtKind::Expr(expr), + span: rustc_span::DUMMY_SP, + } + } + + let empty_block = stmt_to_block(BlockCheckMode::Default, None, self.resolver); + let loop_expr = P(ast::Expr { + kind: ast::ExprKind::Loop(P(empty_block), None), + id: self.resolver.next_node_id(), + span: rustc_span::DUMMY_SP, + attrs: AttrVec::new(), + tokens: None, + }); + + let loop_stmt = ast::Stmt { + id: self.resolver.next_node_id(), + span: rustc_span::DUMMY_SP, + kind: ast::StmtKind::Expr(loop_expr), + }; + + if self.within_static_or_const { + noop_visit_block(b, self) + } else { + visit_clobber(b.deref_mut(), |b| { + let mut stmts = vec![]; + for s in b.stmts { + let old_blocks = self.nested_blocks.replace(vec![]); + + stmts.extend(self.flat_map_stmt(s).into_iter().filter(|s| s.is_item())); + + // we put a Some in there earlier with that replace(), so this is valid + let new_blocks = self.nested_blocks.take().unwrap(); + self.nested_blocks = old_blocks; + stmts.extend(new_blocks.into_iter().map(|b| block_to_stmt(b, self.resolver))); + } + + let mut new_block = ast::Block { stmts, ..b }; + + if let Some(old_blocks) = self.nested_blocks.as_mut() { + //push our fresh block onto the cache and yield an empty block with `loop {}` + if !new_block.stmts.is_empty() { + old_blocks.push(new_block); + } + + stmt_to_block(b.rules, Some(loop_stmt), &mut self.resolver) + } else { + //push `loop {}` onto the end of our fresh block and yield that + new_block.stmts.push(loop_stmt); + + new_block + } + }) + } + } + + // in general the pretty printer processes unexpanded code, so + // we override the default `visit_mac` method which panics. + fn visit_mac(&mut self, mac: &mut ast::MacCall) { + noop_visit_mac(mac, self) + } +} |
