From 3dfd9dfe7ce9bf4bcbb4d1d880cd2c75a85b310c Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 16 Sep 2023 14:36:49 +0000 Subject: Remove an LTO dependent cgu_reuse_tracker.set_actual_reuse call --- compiler/rustc_codegen_ssa/src/back/write.rs | 4 ---- 1 file changed, 4 deletions(-) (limited to 'compiler/rustc_codegen_ssa/src/back') diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index f192747c8ab..bc4578719e5 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -26,7 +26,6 @@ use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::middle::exported_symbols::SymbolExportInfo; use rustc_middle::ty::TyCtxt; -use rustc_session::cgu_reuse_tracker::CguReuseTracker; use rustc_session::config::{self, CrateType, Lto, OutFileName, OutputFilenames, OutputType}; use rustc_session::config::{Passes, SwitchWithOptPath}; use rustc_session::Session; @@ -366,8 +365,6 @@ pub struct CodegenContext { /// The incremental compilation session directory, or None if we are not /// compiling incrementally pub incr_comp_session_dir: Option, - /// Used to update CGU re-use information during the thinlto phase. - pub cgu_reuse_tracker: CguReuseTracker, /// Channel back to the main control thread to send messages to pub coordinator_send: Sender>, } @@ -1119,7 +1116,6 @@ fn start_executing_work( remark: sess.opts.cg.remark.clone(), remark_dir, incr_comp_session_dir: sess.incr_comp_session_dir_opt().map(|r| r.clone()), - cgu_reuse_tracker: sess.cgu_reuse_tracker.clone(), coordinator_send, expanded_args: tcx.sess.expanded_args.clone(), diag_emitter: shared_emitter.clone(), -- cgit 1.4.1-3-g733a5 From e9fa2ca6add1db7f77c90bdc06210be694ed2571 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:23:35 +0000 Subject: Remove cgu_reuse_tracker from Session This removes a bit of global mutable state --- Cargo.lock | 1 + compiler/rustc_codegen_cranelift/src/driver/aot.rs | 68 ++--- compiler/rustc_codegen_ssa/Cargo.toml | 1 + compiler/rustc_codegen_ssa/messages.ftl | 24 ++ .../rustc_codegen_ssa/src/assert_module_sources.rs | 308 +++++++++++++++++++++ compiler/rustc_codegen_ssa/src/back/write.rs | 2 - compiler/rustc_codegen_ssa/src/base.rs | 10 +- compiler/rustc_codegen_ssa/src/errors.rs | 69 +++++ compiler/rustc_codegen_ssa/src/lib.rs | 1 + compiler/rustc_incremental/messages.ftl | 14 - .../rustc_incremental/src/assert_module_sources.rs | 171 ------------ compiler/rustc_incremental/src/errors.rs | 50 ---- compiler/rustc_incremental/src/lib.rs | 1 - compiler/rustc_interface/src/passes.rs | 3 +- compiler/rustc_session/messages.ftl | 9 - compiler/rustc_session/src/cgu_reuse_tracker.rs | 130 --------- compiler/rustc_session/src/errors.rs | 19 -- compiler/rustc_session/src/lib.rs | 1 - compiler/rustc_session/src/session.rs | 11 - 19 files changed, 449 insertions(+), 444 deletions(-) create mode 100644 compiler/rustc_codegen_ssa/src/assert_module_sources.rs delete mode 100644 compiler/rustc_incremental/src/assert_module_sources.rs delete mode 100644 compiler/rustc_session/src/cgu_reuse_tracker.rs (limited to 'compiler/rustc_codegen_ssa/src/back') diff --git a/Cargo.lock b/Cargo.lock index 082bb4be93c..1861f986d20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3650,6 +3650,7 @@ dependencies = [ "serde_json", "smallvec", "tempfile", + "thin-vec", "thorin-dwp", "tracing", "windows", diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index 7a94c3aaef6..aaead1ffc5f 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use std::thread::JoinHandle; use cranelift_object::{ObjectBuilder, ObjectModule}; +use rustc_codegen_ssa::assert_module_sources::CguReuse; use rustc_codegen_ssa::back::metadata::create_compressed_metadata_file; use rustc_codegen_ssa::base::determine_cgu_reuse; use rustc_codegen_ssa::{CodegenResults, CompiledModule, CrateInfo, ModuleKind}; @@ -15,7 +16,6 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::mir::mono::{CodegenUnit, MonoItem}; -use rustc_session::cgu_reuse_tracker::CguReuse; use rustc_session::config::{DebugInfo, OutputFilenames, OutputType}; use rustc_session::Session; @@ -375,43 +375,47 @@ pub(crate) fn run_aot( } } + // Calculate the CGU reuse + let cgu_reuse = tcx.sess.time("find_cgu_reuse", || { + cgus.iter().map(|cgu| determine_cgu_reuse(tcx, &cgu)).collect::>() + }); + + rustc_codegen_ssa::assert_module_sources::assert_module_sources(tcx, &|cgu_reuse_tracker| { + for (i, cgu) in cgus.iter().enumerate() { + let cgu_reuse = cgu_reuse[i]; + cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse); + } + }); + let global_asm_config = Arc::new(crate::global_asm::GlobalAsmConfig::new(tcx)); let mut concurrency_limiter = ConcurrencyLimiter::new(tcx.sess, cgus.len()); let modules = tcx.sess.time("codegen mono items", || { cgus.iter() - .map(|cgu| { - let cgu_reuse = if backend_config.disable_incr_cache { - CguReuse::No - } else { - determine_cgu_reuse(tcx, cgu) - }; - tcx.sess.cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse); - - match cgu_reuse { - CguReuse::No => { - let dep_node = cgu.codegen_dep_node(tcx); - tcx.dep_graph - .with_task( - dep_node, - tcx, - ( - backend_config.clone(), - global_asm_config.clone(), - cgu.name(), - concurrency_limiter.acquire(tcx.sess.diagnostic()), - ), - module_codegen, - Some(rustc_middle::dep_graph::hash_result), - ) - .0 - } - CguReuse::PreLto => unreachable!(), - CguReuse::PostLto => { - concurrency_limiter.job_already_done(); - OngoingModuleCodegen::Sync(reuse_workproduct_for_cgu(tcx, cgu)) - } + .enumerate() + .map(|(i, cgu)| match cgu_reuse[i] { + CguReuse::No => { + let dep_node = cgu.codegen_dep_node(tcx); + tcx.dep_graph + .with_task( + dep_node, + tcx, + ( + backend_config.clone(), + global_asm_config.clone(), + cgu.name(), + concurrency_limiter.acquire(tcx.sess.diagnostic()), + ), + module_codegen, + Some(rustc_middle::dep_graph::hash_result), + ) + .0 + } + CguReuse::PreLto => unreachable!(), + CguReuse::PostLto => { + concurrency_limiter.job_already_done(); + OngoingModuleCodegen::Sync(reuse_workproduct_for_cgu(tcx, cgu)) } }) .collect::>() diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml index 34d0e2d1df6..454e2f80676 100644 --- a/compiler/rustc_codegen_ssa/Cargo.toml +++ b/compiler/rustc_codegen_ssa/Cargo.toml @@ -16,6 +16,7 @@ pathdiff = "0.2.0" serde_json = "1.0.59" smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } regex = "1.4" +thin-vec = "0.2.12" rustc_serialize = { path = "../rustc_serialize" } rustc_arena = { path = "../rustc_arena" } diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 66482667336..5881c6236ec 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -11,6 +11,9 @@ codegen_ssa_atomic_compare_exchange = Atomic compare-exchange intrinsic missing codegen_ssa_binary_output_to_tty = option `-o` or `--emit` is used to write binary output type `{$shorthand}` to stdout, but stdout is a tty +codegen_ssa_cgu_not_recorded = + CGU-reuse for `{$cgu_user_name}` is (mangled: `{$cgu_name}`) was not recorded + codegen_ssa_check_installed_visual_studio = please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option. codegen_ssa_copy_path = could not copy {$from} to {$to}: {$error} @@ -39,6 +42,8 @@ codegen_ssa_failed_to_get_layout = failed to get layout for {$ty}: {$err} codegen_ssa_failed_to_write = failed to write {$path}: {$error} +codegen_ssa_field_associated_value_expected = associated value expected for `{$name}` + codegen_ssa_ignoring_emit_path = ignoring emit path because multiple .{$extension} files were produced codegen_ssa_ignoring_output = ignoring -o because multiple .{$extension} files were produced @@ -46,6 +51,12 @@ codegen_ssa_ignoring_output = ignoring -o because multiple .{$extension} files w codegen_ssa_illegal_link_ordinal_format = illegal ordinal format in `link_ordinal` .note = an unsuffixed integer value, e.g., `1`, is expected +codegen_ssa_incorrect_cgu_reuse_type = + CGU-reuse for `{$cgu_user_name}` is `{$actual_reuse}` but should be {$at_least -> + [one] {"at least "} + *[other] {""} + }`{$expected_reuse}` + codegen_ssa_insufficient_vs_code_product = VS Code is a different product, and is not sufficient. codegen_ssa_invalid_link_ordinal_nargs = incorrect number of arguments to `#[link_ordinal]` @@ -153,12 +164,18 @@ codegen_ssa_linker_unsupported_modifier = `as-needed` modifier not supported for codegen_ssa_linking_failed = linking with `{$linker_path}` failed: {$exit_status} +codegen_ssa_malformed_cgu_name = + found malformed codegen unit name `{$user_path}`. codegen units names must always start with the name of the crate (`{$crate_name}` in this case). + codegen_ssa_metadata_object_file_write = error writing metadata object file: {$error} codegen_ssa_missing_cpp_build_tool_component = or a necessary component may be missing from the "C++ build tools" workload codegen_ssa_missing_memory_ordering = Atomic intrinsic missing memory ordering +codegen_ssa_missing_query_depgraph = + found CGU-reuse attribute but `-Zquery-dep-graph` was not specified + codegen_ssa_msvc_missing_linker = the msvc targets depend on the msvc linker but `link.exe` was not found codegen_ssa_multiple_external_func_decl = multiple declarations of external function `{$function}` from library `{$library_name}` have different calling conventions @@ -166,6 +183,11 @@ codegen_ssa_multiple_external_func_decl = multiple declarations of external func codegen_ssa_multiple_main_functions = entry symbol `main` declared multiple times .help = did you use `#[no_mangle]` on `fn main`? Use `#[start]` instead +codegen_ssa_no_field = no field `{$name}` + +codegen_ssa_no_module_named = + no module named `{$user_path}` (mangled: {$cgu_name}). available modules: {$cgu_names} + codegen_ssa_no_natvis_directory = error enumerating natvis directory: {$error} codegen_ssa_processing_dymutil_failed = processing debug info with `dsymutil` failed: {$status} @@ -297,6 +319,8 @@ codegen_ssa_unknown_atomic_operation = unknown atomic operation codegen_ssa_unknown_atomic_ordering = unknown ordering in atomic intrinsic +codegen_ssa_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified + codegen_ssa_unsupported_arch = unsupported arch `{$arch}` for os `{$os}` codegen_ssa_unsupported_link_self_contained = option `-C link-self-contained` is not supported on this target diff --git a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs new file mode 100644 index 00000000000..a5a16a929ae --- /dev/null +++ b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs @@ -0,0 +1,308 @@ +//! This pass is only used for UNIT TESTS related to incremental +//! compilation. It tests whether a particular `.o` file will be re-used +//! from a previous compilation or whether it must be regenerated. +//! +//! The user adds annotations to the crate of the following form: +//! +//! ``` +//! # #![feature(rustc_attrs)] +//! # #![allow(internal_features)] +//! #![rustc_partition_reused(module="spike", cfg="rpass2")] +//! #![rustc_partition_codegened(module="spike-x", cfg="rpass2")] +//! ``` +//! +//! The first indicates (in the cfg `rpass2`) that `spike.o` will be +//! reused, the second that `spike-x.o` will be recreated. If these +//! annotations are inaccurate, errors are reported. +//! +//! The reason that we use `cfg=...` and not `#[cfg_attr]` is so that +//! the HIR doesn't change as a result of the annotations, which might +//! perturb the reuse results. +//! +//! `#![rustc_expected_cgu_reuse(module="spike", cfg="rpass2", kind="post-lto")]` +//! allows for doing a more fine-grained check to see if pre- or post-lto data +//! was re-used. + +use crate::errors; +use rustc_ast as ast; +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::unord::UnordSet; +use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; +use rustc_hir::def_id::LOCAL_CRATE; +use rustc_middle::mir::mono::CodegenUnitNameBuilder; +use rustc_middle::ty::TyCtxt; +use rustc_session::Session; +use rustc_span::symbol::sym; +use rustc_span::{Span, Symbol}; +use std::borrow::Cow; +use std::fmt::{self}; +use std::sync::{Arc, Mutex}; +use thin_vec::ThinVec; + +#[allow(missing_docs)] +pub fn assert_module_sources(tcx: TyCtxt<'_>, set_reuse: &dyn Fn(&CguReuseTracker)) { + tcx.dep_graph.with_ignore(|| { + if tcx.sess.opts.incremental.is_none() { + return; + } + + let available_cgus = + tcx.collect_and_partition_mono_items(()).1.iter().map(|cgu| cgu.name()).collect(); + + let ams = AssertModuleSource { + tcx, + available_cgus, + cgu_reuse_tracker: if tcx.sess.opts.unstable_opts.query_dep_graph { + CguReuseTracker::new() + } else { + CguReuseTracker::new_disabled() + }, + }; + + for attr in tcx.hir().attrs(rustc_hir::CRATE_HIR_ID) { + ams.check_attr(attr); + } + + set_reuse(&ams.cgu_reuse_tracker); + + ams.cgu_reuse_tracker.check_expected_reuse(tcx.sess); + }); +} + +struct AssertModuleSource<'tcx> { + tcx: TyCtxt<'tcx>, + available_cgus: UnordSet, + cgu_reuse_tracker: CguReuseTracker, +} + +impl<'tcx> AssertModuleSource<'tcx> { + fn check_attr(&self, attr: &ast::Attribute) { + let (expected_reuse, comp_kind) = if attr.has_name(sym::rustc_partition_reused) { + (CguReuse::PreLto, ComparisonKind::AtLeast) + } else if attr.has_name(sym::rustc_partition_codegened) { + (CguReuse::No, ComparisonKind::Exact) + } else if attr.has_name(sym::rustc_expected_cgu_reuse) { + match self.field(attr, sym::kind) { + sym::no => (CguReuse::No, ComparisonKind::Exact), + sym::pre_dash_lto => (CguReuse::PreLto, ComparisonKind::Exact), + sym::post_dash_lto => (CguReuse::PostLto, ComparisonKind::Exact), + sym::any => (CguReuse::PreLto, ComparisonKind::AtLeast), + other => { + self.tcx + .sess + .emit_fatal(errors::UnknownReuseKind { span: attr.span, kind: other }); + } + } + } else { + return; + }; + + if !self.tcx.sess.opts.unstable_opts.query_dep_graph { + self.tcx.sess.emit_fatal(errors::MissingQueryDepGraph { span: attr.span }); + } + + if !self.check_config(attr) { + debug!("check_attr: config does not match, ignoring attr"); + return; + } + + let user_path = self.field(attr, sym::module).to_string(); + let crate_name = self.tcx.crate_name(LOCAL_CRATE).to_string(); + + if !user_path.starts_with(&crate_name) { + self.tcx.sess.emit_fatal(errors::MalformedCguName { + span: attr.span, + user_path, + crate_name, + }); + } + + // Split of the "special suffix" if there is one. + let (user_path, cgu_special_suffix) = if let Some(index) = user_path.rfind('.') { + (&user_path[..index], Some(&user_path[index + 1..])) + } else { + (&user_path[..], None) + }; + + let mut iter = user_path.split('-'); + + // Remove the crate name + assert_eq!(iter.next().unwrap(), crate_name); + + let cgu_path_components = iter.collect::>(); + + let cgu_name_builder = &mut CodegenUnitNameBuilder::new(self.tcx); + let cgu_name = + cgu_name_builder.build_cgu_name(LOCAL_CRATE, cgu_path_components, cgu_special_suffix); + + debug!("mapping '{}' to cgu name '{}'", self.field(attr, sym::module), cgu_name); + + if !self.available_cgus.contains(&cgu_name) { + let cgu_names: Vec<&str> = + self.available_cgus.items().map(|cgu| cgu.as_str()).into_sorted_stable_ord(); + self.tcx.sess.emit_err(errors::NoModuleNamed { + span: attr.span, + user_path, + cgu_name, + cgu_names: cgu_names.join(", "), + }); + } + + self.cgu_reuse_tracker.set_expectation( + cgu_name, + &user_path, + attr.span, + expected_reuse, + comp_kind, + ); + } + + fn field(&self, attr: &ast::Attribute, name: Symbol) -> Symbol { + for item in attr.meta_item_list().unwrap_or_else(ThinVec::new) { + if item.has_name(name) { + if let Some(value) = item.value_str() { + return value; + } else { + self.tcx.sess.emit_fatal(errors::FieldAssociatedValueExpected { + span: item.span(), + name, + }); + } + } + } + + self.tcx.sess.emit_fatal(errors::NoField { span: attr.span, name }); + } + + /// Scan for a `cfg="foo"` attribute and check whether we have a + /// cfg flag called `foo`. + fn check_config(&self, attr: &ast::Attribute) -> bool { + let config = &self.tcx.sess.parse_sess.config; + let value = self.field(attr, sym::cfg); + debug!("check_config(config={:?}, value={:?})", config, value); + if config.iter().any(|&(name, _)| name == value) { + debug!("check_config: matched"); + return true; + } + debug!("check_config: no match found"); + false + } +} + +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub enum CguReuse { + No, + PreLto, + PostLto, +} + +impl fmt::Display for CguReuse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + CguReuse::No => write!(f, "No"), + CguReuse::PreLto => write!(f, "PreLto "), + CguReuse::PostLto => write!(f, "PostLto "), + } + } +} + +impl IntoDiagnosticArg for CguReuse { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::Owned(self.to_string())) + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ComparisonKind { + Exact, + AtLeast, +} + +struct TrackerData { + actual_reuse: FxHashMap, + expected_reuse: FxHashMap, +} + +// Span does not implement `Send`, so we can't just store it in the shared +// `TrackerData` object. Instead of splitting up `TrackerData` into shared and +// non-shared parts (which would be complicated), we just mark the `Span` here +// explicitly as `Send`. That's safe because the span data here is only ever +// accessed from the main thread. +struct SendSpan(Span); +unsafe impl Send for SendSpan {} + +#[derive(Clone)] +pub struct CguReuseTracker { + data: Option>>, +} + +impl CguReuseTracker { + pub fn new() -> CguReuseTracker { + let data = + TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() }; + + CguReuseTracker { data: Some(Arc::new(Mutex::new(data))) } + } + + pub fn new_disabled() -> CguReuseTracker { + CguReuseTracker { data: None } + } + + pub fn set_actual_reuse(&self, cgu_name: &str, kind: CguReuse) { + if let Some(ref data) = self.data { + debug!("set_actual_reuse({cgu_name:?}, {kind:?})"); + + let prev_reuse = data.lock().unwrap().actual_reuse.insert(cgu_name.to_string(), kind); + assert!(prev_reuse.is_none()); + } + } + + pub fn set_expectation( + &self, + cgu_name: Symbol, + cgu_user_name: &str, + error_span: Span, + expected_reuse: CguReuse, + comparison_kind: ComparisonKind, + ) { + if let Some(ref data) = self.data { + debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})"); + let mut data = data.lock().unwrap(); + + data.expected_reuse.insert( + cgu_name.to_string(), + (cgu_user_name.to_string(), SendSpan(error_span), expected_reuse, comparison_kind), + ); + } + } + + pub fn check_expected_reuse(&self, sess: &Session) { + if let Some(ref data) = self.data { + let data = data.lock().unwrap(); + + for (cgu_name, &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind)) in + &data.expected_reuse + { + if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) { + let (error, at_least) = match comparison_kind { + ComparisonKind::Exact => (expected_reuse != actual_reuse, false), + ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true), + }; + + if error { + let at_least = if at_least { 1 } else { 0 }; + errors::IncorrectCguReuseType { + span: error_span.0, + cgu_user_name, + actual_reuse, + expected_reuse, + at_least, + }; + } + } else { + sess.emit_fatal(errors::CguNotRecorded { cgu_user_name, cgu_name }); + } + } + } + } +} diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index bc4578719e5..3d6a2124334 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -1965,8 +1965,6 @@ impl OngoingCodegen { } }); - sess.cgu_reuse_tracker.check_expected_reuse(sess); - sess.abort_if_errors(); let work_products = diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index ede1dc3dfcd..198e5696357 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -1,3 +1,4 @@ +use crate::assert_module_sources::CguReuse; use crate::back::link::are_upstream_rust_objects_already_included; use crate::back::metadata::create_compressed_metadata_file; use crate::back::write::{ @@ -31,7 +32,6 @@ use rustc_middle::mir::mono::{CodegenUnit, CodegenUnitNameBuilder, MonoItem}; use rustc_middle::query::Providers; use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; -use rustc_session::cgu_reuse_tracker::CguReuse; use rustc_session::config::{self, CrateType, EntryFnType, OutputType}; use rustc_session::Session; use rustc_span::symbol::sym; @@ -683,6 +683,13 @@ pub fn codegen_crate( codegen_units.iter().map(|cgu| determine_cgu_reuse(tcx, &cgu)).collect::>() }); + crate::assert_module_sources::assert_module_sources(tcx, &|cgu_reuse_tracker| { + for (i, cgu) in codegen_units.iter().enumerate() { + let cgu_reuse = cgu_reuse[i]; + cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse); + } + }); + let mut total_codegen_time = Duration::new(0, 0); let start_rss = tcx.sess.opts.unstable_opts.time_passes.then(|| get_resident_set_size()); @@ -727,7 +734,6 @@ pub fn codegen_crate( ongoing_codegen.check_for_errors(tcx.sess); let cgu_reuse = cgu_reuse[i]; - tcx.sess.cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse); match cgu_reuse { CguReuse::No => { diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index 39b2fa37fbf..ed6ac9f9c5d 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -1,5 +1,6 @@ //! Errors emitted by codegen_ssa +use crate::assert_module_sources::CguReuse; use crate::back::command::Command; use crate::fluent_generated as fluent; use rustc_errors::{ @@ -16,6 +17,74 @@ use std::io::Error; use std::path::{Path, PathBuf}; use std::process::ExitStatus; +#[derive(Diagnostic)] +#[diag(codegen_ssa_incorrect_cgu_reuse_type)] +pub struct IncorrectCguReuseType<'a> { + #[primary_span] + pub span: Span, + pub cgu_user_name: &'a str, + pub actual_reuse: CguReuse, + pub expected_reuse: CguReuse, + pub at_least: u8, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_cgu_not_recorded)] +pub struct CguNotRecorded<'a> { + pub cgu_user_name: &'a str, + pub cgu_name: &'a str, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_unknown_reuse_kind)] +pub struct UnknownReuseKind { + #[primary_span] + pub span: Span, + pub kind: Symbol, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_missing_query_depgraph)] +pub struct MissingQueryDepGraph { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_malformed_cgu_name)] +pub struct MalformedCguName { + #[primary_span] + pub span: Span, + pub user_path: String, + pub crate_name: String, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_no_module_named)] +pub struct NoModuleNamed<'a> { + #[primary_span] + pub span: Span, + pub user_path: &'a str, + pub cgu_name: Symbol, + pub cgu_names: String, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_field_associated_value_expected)] +pub struct FieldAssociatedValueExpected { + #[primary_span] + pub span: Span, + pub name: Symbol, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_no_field)] +pub struct NoField { + #[primary_span] + pub span: Span, + pub name: Symbol, +} + #[derive(Diagnostic)] #[diag(codegen_ssa_lib_def_write_failure)] pub struct LibDefWriteFailure { diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index f6186a290f8..008da2a7ce0 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -43,6 +43,7 @@ use std::collections::BTreeSet; use std::io; use std::path::{Path, PathBuf}; +pub mod assert_module_sources; pub mod back; pub mod base; pub mod codegen_attrs; diff --git a/compiler/rustc_incremental/messages.ftl b/compiler/rustc_incremental/messages.ftl index 9fa4e0fb27c..5d885e07192 100644 --- a/compiler/rustc_incremental/messages.ftl +++ b/compiler/rustc_incremental/messages.ftl @@ -46,8 +46,6 @@ incremental_delete_partial = failed to delete partly initialized session dir `{$ incremental_delete_workproduct = file-system error deleting outdated file `{$path}`: {$err} -incremental_field_associated_value_expected = associated value expected for `{$name}` - incremental_finalize = error finalizing incremental compilation session directory `{$path}`: {$err} incremental_finalized_gc_failed = @@ -63,25 +61,15 @@ incremental_load_dep_graph = could not load dep-graph from `{$path}`: {$err} incremental_lock_unsupported = the filesystem for the incremental path at {$session_dir} does not appear to support locking, consider changing the incremental path to a filesystem that supports locking or disable incremental compilation -incremental_malformed_cgu_name = - found malformed codegen unit name `{$user_path}`. codegen units names must always start with the name of the crate (`{$crate_name}` in this case). incremental_missing_depnode = missing `DepNode` variant incremental_missing_if_this_changed = no `#[rustc_if_this_changed]` annotation detected -incremental_missing_query_depgraph = - found CGU-reuse attribute but `-Zquery-dep-graph` was not specified - incremental_move_dep_graph = failed to move dependency graph from `{$from}` to `{$to}`: {$err} incremental_no_cfg = no cfg attribute -incremental_no_field = no field `{$name}` - -incremental_no_module_named = - no module named `{$user_path}` (mangled: {$cgu_name}). available modules: {$cgu_names} - incremental_no_path = no path from `{$source}` to `{$target}` incremental_not_clean = `{$dep_node_str}` should be clean but is not @@ -107,8 +95,6 @@ incremental_undefined_clean_dirty_assertions_item = incremental_unknown_item = unknown item `{$name}` -incremental_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified - incremental_unrecognized_depnode = unrecognized `DepNode` variant: {$name} incremental_unrecognized_depnode_label = dep-node label `{$label}` not recognized diff --git a/compiler/rustc_incremental/src/assert_module_sources.rs b/compiler/rustc_incremental/src/assert_module_sources.rs deleted file mode 100644 index 8e22ab4083e..00000000000 --- a/compiler/rustc_incremental/src/assert_module_sources.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! This pass is only used for UNIT TESTS related to incremental -//! compilation. It tests whether a particular `.o` file will be re-used -//! from a previous compilation or whether it must be regenerated. -//! -//! The user adds annotations to the crate of the following form: -//! -//! ``` -//! # #![feature(rustc_attrs)] -//! # #![allow(internal_features)] -//! #![rustc_partition_reused(module="spike", cfg="rpass2")] -//! #![rustc_partition_codegened(module="spike-x", cfg="rpass2")] -//! ``` -//! -//! The first indicates (in the cfg `rpass2`) that `spike.o` will be -//! reused, the second that `spike-x.o` will be recreated. If these -//! annotations are inaccurate, errors are reported. -//! -//! The reason that we use `cfg=...` and not `#[cfg_attr]` is so that -//! the HIR doesn't change as a result of the annotations, which might -//! perturb the reuse results. -//! -//! `#![rustc_expected_cgu_reuse(module="spike", cfg="rpass2", kind="post-lto")]` -//! allows for doing a more fine-grained check to see if pre- or post-lto data -//! was re-used. - -use crate::errors; -use rustc_ast as ast; -use rustc_data_structures::unord::UnordSet; -use rustc_hir::def_id::LOCAL_CRATE; -use rustc_middle::mir::mono::CodegenUnitNameBuilder; -use rustc_middle::ty::TyCtxt; -use rustc_session::cgu_reuse_tracker::*; -use rustc_span::symbol::{sym, Symbol}; -use thin_vec::ThinVec; - -#[allow(missing_docs)] -pub fn assert_module_sources(tcx: TyCtxt<'_>) { - tcx.dep_graph.with_ignore(|| { - if tcx.sess.opts.incremental.is_none() { - return; - } - - let available_cgus = - tcx.collect_and_partition_mono_items(()).1.iter().map(|cgu| cgu.name()).collect(); - - let ams = AssertModuleSource { tcx, available_cgus }; - - for attr in tcx.hir().attrs(rustc_hir::CRATE_HIR_ID) { - ams.check_attr(attr); - } - }) -} - -struct AssertModuleSource<'tcx> { - tcx: TyCtxt<'tcx>, - available_cgus: UnordSet, -} - -impl<'tcx> AssertModuleSource<'tcx> { - fn check_attr(&self, attr: &ast::Attribute) { - let (expected_reuse, comp_kind) = if attr.has_name(sym::rustc_partition_reused) { - (CguReuse::PreLto, ComparisonKind::AtLeast) - } else if attr.has_name(sym::rustc_partition_codegened) { - (CguReuse::No, ComparisonKind::Exact) - } else if attr.has_name(sym::rustc_expected_cgu_reuse) { - match self.field(attr, sym::kind) { - sym::no => (CguReuse::No, ComparisonKind::Exact), - sym::pre_dash_lto => (CguReuse::PreLto, ComparisonKind::Exact), - sym::post_dash_lto => (CguReuse::PostLto, ComparisonKind::Exact), - sym::any => (CguReuse::PreLto, ComparisonKind::AtLeast), - other => { - self.tcx - .sess - .emit_fatal(errors::UnknownReuseKind { span: attr.span, kind: other }); - } - } - } else { - return; - }; - - if !self.tcx.sess.opts.unstable_opts.query_dep_graph { - self.tcx.sess.emit_fatal(errors::MissingQueryDepGraph { span: attr.span }); - } - - if !self.check_config(attr) { - debug!("check_attr: config does not match, ignoring attr"); - return; - } - - let user_path = self.field(attr, sym::module).to_string(); - let crate_name = self.tcx.crate_name(LOCAL_CRATE).to_string(); - - if !user_path.starts_with(&crate_name) { - self.tcx.sess.emit_fatal(errors::MalformedCguName { - span: attr.span, - user_path, - crate_name, - }); - } - - // Split of the "special suffix" if there is one. - let (user_path, cgu_special_suffix) = if let Some(index) = user_path.rfind('.') { - (&user_path[..index], Some(&user_path[index + 1..])) - } else { - (&user_path[..], None) - }; - - let mut iter = user_path.split('-'); - - // Remove the crate name - assert_eq!(iter.next().unwrap(), crate_name); - - let cgu_path_components = iter.collect::>(); - - let cgu_name_builder = &mut CodegenUnitNameBuilder::new(self.tcx); - let cgu_name = - cgu_name_builder.build_cgu_name(LOCAL_CRATE, cgu_path_components, cgu_special_suffix); - - debug!("mapping '{}' to cgu name '{}'", self.field(attr, sym::module), cgu_name); - - if !self.available_cgus.contains(&cgu_name) { - let cgu_names: Vec<&str> = - self.available_cgus.items().map(|cgu| cgu.as_str()).into_sorted_stable_ord(); - self.tcx.sess.emit_err(errors::NoModuleNamed { - span: attr.span, - user_path, - cgu_name, - cgu_names: cgu_names.join(", "), - }); - } - - self.tcx.sess.cgu_reuse_tracker.set_expectation( - cgu_name, - &user_path, - attr.span, - expected_reuse, - comp_kind, - ); - } - - fn field(&self, attr: &ast::Attribute, name: Symbol) -> Symbol { - for item in attr.meta_item_list().unwrap_or_else(ThinVec::new) { - if item.has_name(name) { - if let Some(value) = item.value_str() { - return value; - } else { - self.tcx.sess.emit_fatal(errors::FieldAssociatedValueExpected { - span: item.span(), - name, - }); - } - } - } - - self.tcx.sess.emit_fatal(errors::NoField { span: attr.span, name }); - } - - /// Scan for a `cfg="foo"` attribute and check whether we have a - /// cfg flag called `foo`. - fn check_config(&self, attr: &ast::Attribute) -> bool { - let config = &self.tcx.sess.parse_sess.config; - let value = self.field(attr, sym::cfg); - debug!("check_config(config={:?}, value={:?})", config, value); - if config.iter().any(|&(name, _)| name == value) { - debug!("check_config: matched"); - return true; - } - debug!("check_config: no match found"); - false - } -} diff --git a/compiler/rustc_incremental/src/errors.rs b/compiler/rustc_incremental/src/errors.rs index deb87678365..05ed4f7598d 100644 --- a/compiler/rustc_incremental/src/errors.rs +++ b/compiler/rustc_incremental/src/errors.rs @@ -40,56 +40,6 @@ pub struct NoPath { pub source: String, } -#[derive(Diagnostic)] -#[diag(incremental_unknown_reuse_kind)] -pub struct UnknownReuseKind { - #[primary_span] - pub span: Span, - pub kind: Symbol, -} - -#[derive(Diagnostic)] -#[diag(incremental_missing_query_depgraph)] -pub struct MissingQueryDepGraph { - #[primary_span] - pub span: Span, -} - -#[derive(Diagnostic)] -#[diag(incremental_malformed_cgu_name)] -pub struct MalformedCguName { - #[primary_span] - pub span: Span, - pub user_path: String, - pub crate_name: String, -} - -#[derive(Diagnostic)] -#[diag(incremental_no_module_named)] -pub struct NoModuleNamed<'a> { - #[primary_span] - pub span: Span, - pub user_path: &'a str, - pub cgu_name: Symbol, - pub cgu_names: String, -} - -#[derive(Diagnostic)] -#[diag(incremental_field_associated_value_expected)] -pub struct FieldAssociatedValueExpected { - #[primary_span] - pub span: Span, - pub name: Symbol, -} - -#[derive(Diagnostic)] -#[diag(incremental_no_field)] -pub struct NoField { - #[primary_span] - pub span: Span, - pub name: Symbol, -} - #[derive(Diagnostic)] #[diag(incremental_assertion_auto)] pub struct AssertionAuto<'a> { diff --git a/compiler/rustc_incremental/src/lib.rs b/compiler/rustc_incremental/src/lib.rs index 220ea194a6d..e4050099fc1 100644 --- a/compiler/rustc_incremental/src/lib.rs +++ b/compiler/rustc_incremental/src/lib.rs @@ -13,7 +13,6 @@ extern crate rustc_middle; extern crate tracing; mod assert_dep_graph; -pub mod assert_module_sources; mod errors; mod persist; diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 0e8f93cef17..718dbaaafcc 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -957,10 +957,9 @@ pub fn start_codegen<'tcx>( codegen_backend.codegen_crate(tcx, metadata, need_metadata_module) }); - // Don't run these test assertions when not doing codegen. Compiletest tries to build + // Don't run this test assertions when not doing codegen. Compiletest tries to build // build-fail tests in check mode first and expects it to not give an error in that case. if tcx.sess.opts.output_types.should_codegen() { - rustc_incremental::assert_module_sources::assert_module_sources(tcx); rustc_symbol_mangling::test::report_symbol_names(tcx); } diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl index b356b503aa5..fa1b6f9f13d 100644 --- a/compiler/rustc_session/messages.ftl +++ b/compiler/rustc_session/messages.ftl @@ -5,9 +5,6 @@ session_cannot_enable_crt_static_linux = sanitizer is incompatible with statical session_cannot_mix_and_match_sanitizers = `-Zsanitizer={$first}` is incompatible with `-Zsanitizer={$second}` -session_cgu_not_recorded = - CGU-reuse for `{$cgu_user_name}` is (mangled: `{$cgu_name}`) was not recorded - session_cli_feature_diagnostic_help = add `-Zcrate-attr="feature({$feature})"` to the command-line options to enable @@ -34,12 +31,6 @@ session_hexadecimal_float_literal_not_supported = hexadecimal float literal is n session_incompatible_linker_flavor = linker flavor `{$flavor}` is incompatible with the current target .note = compatible flavors are: {$compatible_list} -session_incorrect_cgu_reuse_type = - CGU-reuse for `{$cgu_user_name}` is `{$actual_reuse}` but should be {$at_least -> - [one] {"at least "} - *[other] {""} - }`{$expected_reuse}` - session_instrumentation_not_supported = {$us} instrumentation is not supported for this target session_int_literal_too_large = integer literal is too large diff --git a/compiler/rustc_session/src/cgu_reuse_tracker.rs b/compiler/rustc_session/src/cgu_reuse_tracker.rs deleted file mode 100644 index 74b68fa7d35..00000000000 --- a/compiler/rustc_session/src/cgu_reuse_tracker.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! Some facilities for tracking how codegen-units are reused during incremental -//! compilation. This is used for incremental compilation tests and debug -//! output. - -use crate::errors::{CguNotRecorded, IncorrectCguReuseType}; -use crate::Session; -use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; -use rustc_span::{Span, Symbol}; -use std::borrow::Cow; -use std::fmt::{self}; -use std::sync::{Arc, Mutex}; - -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub enum CguReuse { - No, - PreLto, - PostLto, -} - -impl fmt::Display for CguReuse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - CguReuse::No => write!(f, "No"), - CguReuse::PreLto => write!(f, "PreLto "), - CguReuse::PostLto => write!(f, "PostLto "), - } - } -} - -impl IntoDiagnosticArg for CguReuse { - fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { - DiagnosticArgValue::Str(Cow::Owned(self.to_string())) - } -} - -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum ComparisonKind { - Exact, - AtLeast, -} - -struct TrackerData { - actual_reuse: FxHashMap, - expected_reuse: FxHashMap, -} - -// Span does not implement `Send`, so we can't just store it in the shared -// `TrackerData` object. Instead of splitting up `TrackerData` into shared and -// non-shared parts (which would be complicated), we just mark the `Span` here -// explicitly as `Send`. That's safe because the span data here is only ever -// accessed from the main thread. -struct SendSpan(Span); -unsafe impl Send for SendSpan {} - -#[derive(Clone)] -pub struct CguReuseTracker { - data: Option>>, -} - -impl CguReuseTracker { - pub fn new() -> CguReuseTracker { - let data = - TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() }; - - CguReuseTracker { data: Some(Arc::new(Mutex::new(data))) } - } - - pub fn new_disabled() -> CguReuseTracker { - CguReuseTracker { data: None } - } - - pub fn set_actual_reuse(&self, cgu_name: &str, kind: CguReuse) { - if let Some(ref data) = self.data { - debug!("set_actual_reuse({cgu_name:?}, {kind:?})"); - - let prev_reuse = data.lock().unwrap().actual_reuse.insert(cgu_name.to_string(), kind); - assert!(prev_reuse.is_none()); - } - } - - pub fn set_expectation( - &self, - cgu_name: Symbol, - cgu_user_name: &str, - error_span: Span, - expected_reuse: CguReuse, - comparison_kind: ComparisonKind, - ) { - if let Some(ref data) = self.data { - debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})"); - let mut data = data.lock().unwrap(); - - data.expected_reuse.insert( - cgu_name.to_string(), - (cgu_user_name.to_string(), SendSpan(error_span), expected_reuse, comparison_kind), - ); - } - } - - pub fn check_expected_reuse(&self, sess: &Session) { - if let Some(ref data) = self.data { - let data = data.lock().unwrap(); - - for (cgu_name, &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind)) in - &data.expected_reuse - { - if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) { - let (error, at_least) = match comparison_kind { - ComparisonKind::Exact => (expected_reuse != actual_reuse, false), - ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true), - }; - - if error { - let at_least = if at_least { 1 } else { 0 }; - IncorrectCguReuseType { - span: error_span.0, - cgu_user_name, - actual_reuse, - expected_reuse, - at_least, - }; - } - } else { - sess.emit_fatal(CguNotRecorded { cgu_user_name, cgu_name }); - } - } - } - } -} diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index 5f8bbfca890..31094e0d266 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -1,6 +1,5 @@ use std::num::NonZeroU32; -use crate::cgu_reuse_tracker::CguReuse; use crate::parse::ParseSess; use rustc_ast::token; use rustc_ast::util::literal::LitError; @@ -9,24 +8,6 @@ use rustc_macros::Diagnostic; use rustc_span::{BytePos, Span, Symbol}; use rustc_target::spec::{SplitDebuginfo, StackProtector, TargetTriple}; -#[derive(Diagnostic)] -#[diag(session_incorrect_cgu_reuse_type)] -pub struct IncorrectCguReuseType<'a> { - #[primary_span] - pub span: Span, - pub cgu_user_name: &'a str, - pub actual_reuse: CguReuse, - pub expected_reuse: CguReuse, - pub at_least: u8, -} - -#[derive(Diagnostic)] -#[diag(session_cgu_not_recorded)] -pub struct CguNotRecorded<'a> { - pub cgu_user_name: &'a str, - pub cgu_name: &'a str, -} - pub struct FeatureGateError { pub span: MultiSpan, pub explain: DiagnosticMessage, diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs index d6c746a7bd8..66a5092ccd4 100644 --- a/compiler/rustc_session/src/lib.rs +++ b/compiler/rustc_session/src/lib.rs @@ -22,7 +22,6 @@ extern crate tracing; use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage}; use rustc_fluent_macro::fluent_messages; -pub mod cgu_reuse_tracker; pub mod utils; pub use lint::{declare_lint, declare_lint_pass, declare_tool_lint, impl_lint_pass}; pub use rustc_lint_defs as lint; diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index b484978eed2..5cac11cc8f7 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1,4 +1,3 @@ -use crate::cgu_reuse_tracker::CguReuseTracker; use crate::code_stats::CodeStats; pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; use crate::config::{ @@ -153,9 +152,6 @@ pub struct Session { pub io: CompilerIO, incr_comp_session: OneThread>, - /// Used for incremental compilation tests. Will only be populated if - /// `-Zquery-dep-graph` is specified. - pub cgu_reuse_tracker: CguReuseTracker, /// Used by `-Z self-profile`. pub prof: SelfProfilerRef, @@ -1431,12 +1427,6 @@ pub fn build_session( }); let print_fuel = AtomicU64::new(0); - let cgu_reuse_tracker = if sopts.unstable_opts.query_dep_graph { - CguReuseTracker::new() - } else { - CguReuseTracker::new_disabled() - }; - let prof = SelfProfilerRef::new( self_profiler, sopts.unstable_opts.time_passes.then(|| sopts.unstable_opts.time_passes_format), @@ -1461,7 +1451,6 @@ pub fn build_session( sysroot, io, incr_comp_session: OneThread::new(RefCell::new(IncrCompSession::NotInitialized)), - cgu_reuse_tracker, prof, perf_stats: PerfStats { symbol_hash_time: Lock::new(Duration::from_secs(0)), -- cgit 1.4.1-3-g733a5