about summary refs log tree commit diff
path: root/compiler/rustc_errors
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_errors')
-rw-r--r--compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs36
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs32
-rw-r--r--compiler/rustc_errors/src/emitter.rs100
-rw-r--r--compiler/rustc_errors/src/json.rs70
-rw-r--r--compiler/rustc_errors/src/json/tests.rs6
-rw-r--r--compiler/rustc_errors/src/lib.rs55
-rw-r--r--compiler/rustc_errors/src/tests.rs43
-rw-r--r--compiler/rustc_errors/src/timings.rs121
-rw-r--r--compiler/rustc_errors/src/translation.rs43
9 files changed, 336 insertions, 170 deletions
diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
index f3aeb8d224b..2eb3c23259f 100644
--- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
+++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
@@ -15,17 +15,15 @@ use rustc_span::source_map::SourceMap;
 use crate::emitter::FileWithAnnotatedLines;
 use crate::registry::Registry;
 use crate::snippet::Line;
-use crate::translation::{Translate, to_fluent_args};
+use crate::translation::{Translator, to_fluent_args};
 use crate::{
-    CodeSuggestion, DiagInner, DiagMessage, Emitter, ErrCode, FluentBundle, LazyFallbackBundle,
-    Level, MultiSpan, Style, Subdiag,
+    CodeSuggestion, DiagInner, DiagMessage, Emitter, ErrCode, Level, MultiSpan, Style, Subdiag,
 };
 
 /// Generates diagnostics using annotate-snippet
 pub struct AnnotateSnippetEmitter {
     source_map: Option<Arc<SourceMap>>,
-    fluent_bundle: Option<Arc<FluentBundle>>,
-    fallback_bundle: LazyFallbackBundle,
+    translator: Translator,
 
     /// If true, hides the longer explanation text
     short_message: bool,
@@ -35,16 +33,6 @@ pub struct AnnotateSnippetEmitter {
     macro_backtrace: bool,
 }
 
-impl Translate for AnnotateSnippetEmitter {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
-        self.fluent_bundle.as_deref()
-    }
-
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        &self.fallback_bundle
-    }
-}
-
 impl Emitter for AnnotateSnippetEmitter {
     /// The entry point for the diagnostics generation
     fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
@@ -78,6 +66,10 @@ impl Emitter for AnnotateSnippetEmitter {
     fn should_show_explain(&self) -> bool {
         !self.short_message
     }
+
+    fn translator(&self) -> &Translator {
+        &self.translator
+    }
 }
 
 /// Provides the source string for the given `line` of `file`
@@ -104,19 +96,11 @@ fn annotation_level_for_level(level: Level) -> annotate_snippets::Level {
 impl AnnotateSnippetEmitter {
     pub fn new(
         source_map: Option<Arc<SourceMap>>,
-        fluent_bundle: Option<Arc<FluentBundle>>,
-        fallback_bundle: LazyFallbackBundle,
+        translator: Translator,
         short_message: bool,
         macro_backtrace: bool,
     ) -> Self {
-        Self {
-            source_map,
-            fluent_bundle,
-            fallback_bundle,
-            short_message,
-            ui_testing: false,
-            macro_backtrace,
-        }
+        Self { source_map, translator, short_message, ui_testing: false, macro_backtrace }
     }
 
     /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
@@ -137,7 +121,7 @@ impl AnnotateSnippetEmitter {
         _children: &[Subdiag],
         _suggestions: &[CodeSuggestion],
     ) {
-        let message = self.translate_messages(messages, args);
+        let message = self.translator.translate_messages(messages, args);
         if let Some(source_map) = &self.source_map {
             // Make sure our primary file comes first
             let primary_lo = if let Some(primary_span) = msp.primary_span().as_ref() {
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index a11f81b55bb..8da7cdd9358 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -289,6 +289,9 @@ pub struct DiagInner {
     pub suggestions: Suggestions,
     pub args: DiagArgMap,
 
+    // This is used to store args and restore them after a subdiagnostic is rendered.
+    pub reserved_args: DiagArgMap,
+
     /// This is not used for highlighting or rendering any error message. Rather, it can be used
     /// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of
     /// `span` if there is one. Otherwise, it is `DUMMY_SP`.
@@ -319,6 +322,7 @@ impl DiagInner {
             children: vec![],
             suggestions: Suggestions::Enabled(vec![]),
             args: Default::default(),
+            reserved_args: Default::default(),
             sort_span: DUMMY_SP,
             is_lint: None,
             long_ty_path: None,
@@ -390,7 +394,27 @@ impl DiagInner {
     }
 
     pub(crate) fn arg(&mut self, name: impl Into<DiagArgName>, arg: impl IntoDiagArg) {
-        self.args.insert(name.into(), arg.into_diag_arg(&mut self.long_ty_path));
+        let name = name.into();
+        let value = arg.into_diag_arg(&mut self.long_ty_path);
+        // This assertion is to avoid subdiagnostics overwriting an existing diagnostic arg.
+        debug_assert!(
+            !self.args.contains_key(&name) || self.args.get(&name) == Some(&value),
+            "arg {} already exists",
+            name
+        );
+        self.args.insert(name, value);
+    }
+
+    pub fn remove_arg(&mut self, name: &str) {
+        self.args.swap_remove(name);
+    }
+
+    pub fn store_args(&mut self) {
+        self.reserved_args = self.args.clone();
+    }
+
+    pub fn restore_args(&mut self) {
+        self.args = std::mem::take(&mut self.reserved_args);
     }
 
     /// Fields used for Hash, and PartialEq trait.
@@ -1423,6 +1447,12 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
         self.downgrade_to_delayed_bug();
         self.emit()
     }
+
+    pub fn remove_arg(&mut self, name: &str) {
+        if let Some(diag) = self.diag.as_mut() {
+            diag.remove_arg(name);
+        }
+    }
 }
 
 /// Destructor bomb: every `Diag` must be consumed (emitted, cancelled, etc.)
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index fe01e289334..e333de4b660 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -34,10 +34,11 @@ use crate::snippet::{
     Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString,
 };
 use crate::styled_buffer::StyledBuffer;
-use crate::translation::{Translate, to_fluent_args};
+use crate::timings::TimingRecord;
+use crate::translation::{Translator, to_fluent_args};
 use crate::{
-    CodeSuggestion, DiagInner, DiagMessage, ErrCode, FluentBundle, LazyFallbackBundle, Level,
-    MultiSpan, Subdiag, SubstitutionHighlight, SuggestionStyle, TerminalUrl,
+    CodeSuggestion, DiagInner, DiagMessage, ErrCode, Level, MultiSpan, Subdiag,
+    SubstitutionHighlight, SuggestionStyle, TerminalUrl,
 };
 
 /// Default column width, used in tests and when terminal dimensions cannot be determined.
@@ -164,12 +165,17 @@ impl Margin {
     }
 }
 
+pub enum TimingEvent {
+    Start,
+    End,
+}
+
 const ANONYMIZED_LINE_NUM: &str = "LL";
 
 pub type DynEmitter = dyn Emitter + DynSend;
 
-/// Emitter trait for emitting errors.
-pub trait Emitter: Translate {
+/// Emitter trait for emitting errors and other structured information.
+pub trait Emitter {
     /// Emit a structured diagnostic.
     fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
 
@@ -177,6 +183,10 @@ pub trait Emitter: Translate {
     /// Currently only supported for the JSON format.
     fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
 
+    /// Emit a timestamp with start/end of a timing section.
+    /// Currently only supported for the JSON format.
+    fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
+
     /// Emit a report about future breakage.
     /// Currently only supported for the JSON format.
     fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
@@ -202,6 +212,8 @@ pub trait Emitter: Translate {
 
     fn source_map(&self) -> Option<&SourceMap>;
 
+    fn translator(&self) -> &Translator;
+
     /// Formats the substitutions of the primary_span
     ///
     /// There are a lot of conditions to this method, but in short:
@@ -214,13 +226,17 @@ pub trait Emitter: Translate {
     /// * If the current `DiagInner` has multiple suggestions,
     ///   we leave `primary_span` and the suggestions untouched.
     fn primary_span_formatted(
-        &mut self,
+        &self,
         primary_span: &mut MultiSpan,
         suggestions: &mut Vec<CodeSuggestion>,
         fluent_args: &FluentArgs<'_>,
     ) {
         if let Some((sugg, rest)) = suggestions.split_first() {
-            let msg = self.translate_message(&sugg.msg, fluent_args).map_err(Report::new).unwrap();
+            let msg = self
+                .translator()
+                .translate_message(&sugg.msg, fluent_args)
+                .map_err(Report::new)
+                .unwrap();
             if rest.is_empty()
                // ^ if there is only one suggestion
                // don't display multi-suggestions as labels
@@ -481,16 +497,6 @@ pub trait Emitter: Translate {
     }
 }
 
-impl Translate for HumanEmitter {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
-        self.fluent_bundle.as_deref()
-    }
-
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        &self.fallback_bundle
-    }
-}
-
 impl Emitter for HumanEmitter {
     fn source_map(&self) -> Option<&SourceMap> {
         self.sm.as_deref()
@@ -528,39 +534,52 @@ impl Emitter for HumanEmitter {
     fn supports_color(&self) -> bool {
         self.dst.supports_color()
     }
+
+    fn translator(&self) -> &Translator {
+        &self.translator
+    }
 }
 
 /// An emitter that does nothing when emitting a non-fatal diagnostic.
 /// Fatal diagnostics are forwarded to `fatal_emitter` to avoid silent
 /// failures of rustc, as witnessed e.g. in issue #89358.
-pub struct SilentEmitter {
+pub struct FatalOnlyEmitter {
     pub fatal_emitter: Box<dyn Emitter + DynSend>,
     pub fatal_note: Option<String>,
-    pub emit_fatal_diagnostic: bool,
 }
 
-impl Translate for SilentEmitter {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
+impl Emitter for FatalOnlyEmitter {
+    fn source_map(&self) -> Option<&SourceMap> {
         None
     }
 
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        self.fatal_emitter.fallback_fluent_bundle()
+    fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
+        if diag.level == Level::Fatal {
+            if let Some(fatal_note) = &self.fatal_note {
+                diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
+            }
+            self.fatal_emitter.emit_diagnostic(diag, registry);
+        }
+    }
+
+    fn translator(&self) -> &Translator {
+        self.fatal_emitter.translator()
     }
 }
 
+pub struct SilentEmitter {
+    pub translator: Translator,
+}
+
 impl Emitter for SilentEmitter {
     fn source_map(&self) -> Option<&SourceMap> {
         None
     }
 
-    fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
-        if self.emit_fatal_diagnostic && diag.level == Level::Fatal {
-            if let Some(fatal_note) = &self.fatal_note {
-                diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
-            }
-            self.fatal_emitter.emit_diagnostic(diag, registry);
-        }
+    fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {}
+
+    fn translator(&self) -> &Translator {
+        &self.translator
     }
 }
 
@@ -605,9 +624,8 @@ pub struct HumanEmitter {
     #[setters(skip)]
     dst: IntoDynSyncSend<Destination>,
     sm: Option<Arc<SourceMap>>,
-    fluent_bundle: Option<Arc<FluentBundle>>,
     #[setters(skip)]
-    fallback_bundle: LazyFallbackBundle,
+    translator: Translator,
     short_message: bool,
     ui_testing: bool,
     ignored_directories_in_source_blocks: Vec<String>,
@@ -627,12 +645,11 @@ pub(crate) struct FileWithAnnotatedLines {
 }
 
 impl HumanEmitter {
-    pub fn new(dst: Destination, fallback_bundle: LazyFallbackBundle) -> HumanEmitter {
+    pub fn new(dst: Destination, translator: Translator) -> HumanEmitter {
         HumanEmitter {
             dst: IntoDynSyncSend(dst),
             sm: None,
-            fluent_bundle: None,
-            fallback_bundle,
+            translator,
             short_message: false,
             ui_testing: false,
             ignored_directories_in_source_blocks: Vec::new(),
@@ -1423,7 +1440,7 @@ impl HumanEmitter {
         //                very *weird* formats
         //                see?
         for (text, style) in msgs.iter() {
-            let text = self.translate_message(text, args).map_err(Report::new).unwrap();
+            let text = self.translator.translate_message(text, args).map_err(Report::new).unwrap();
             let text = &normalize_whitespace(&text);
             let lines = text.split('\n').collect::<Vec<_>>();
             if lines.len() > 1 {
@@ -1518,7 +1535,8 @@ impl HumanEmitter {
             }
             let mut line = 0;
             for (text, style) in msgs.iter() {
-                let text = self.translate_message(text, args).map_err(Report::new).unwrap();
+                let text =
+                    self.translator.translate_message(text, args).map_err(Report::new).unwrap();
                 // Account for newlines to align output to its label.
                 for text in normalize_whitespace(&text).lines() {
                     buffer.append(
@@ -1550,7 +1568,7 @@ impl HumanEmitter {
                     .into_iter()
                     .filter_map(|label| match label.label {
                         Some(msg) if label.is_primary => {
-                            let text = self.translate_message(&msg, args).ok()?;
+                            let text = self.translator.translate_message(&msg, args).ok()?;
                             if !text.trim().is_empty() { Some(text.to_string()) } else { None }
                         }
                         _ => None,
@@ -3094,7 +3112,11 @@ impl FileWithAnnotatedLines {
 
                 let label = label.as_ref().map(|m| {
                     normalize_whitespace(
-                        &emitter.translate_message(m, args).map_err(Report::new).unwrap(),
+                        &emitter
+                            .translator()
+                            .translate_message(m, args)
+                            .map_err(Report::new)
+                            .unwrap(),
                     )
                 });
 
diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
index a6583407b7e..4348610be0a 100644
--- a/compiler/rustc_errors/src/json.rs
+++ b/compiler/rustc_errors/src/json.rs
@@ -28,14 +28,12 @@ use termcolor::{ColorSpec, WriteColor};
 use crate::diagnostic::IsLint;
 use crate::emitter::{
     ColorConfig, Destination, Emitter, HumanEmitter, HumanReadableErrorType, OutputTheme,
-    should_show_source_code,
+    TimingEvent, should_show_source_code,
 };
 use crate::registry::Registry;
-use crate::translation::{Translate, to_fluent_args};
-use crate::{
-    CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, Subdiag, Suggestions,
-    TerminalUrl,
-};
+use crate::timings::{TimingRecord, TimingSection};
+use crate::translation::{Translator, to_fluent_args};
+use crate::{CodeSuggestion, MultiSpan, SpanLabel, Subdiag, Suggestions, TerminalUrl};
 
 #[cfg(test)]
 mod tests;
@@ -46,9 +44,8 @@ pub struct JsonEmitter {
     dst: IntoDynSyncSend<Box<dyn Write + Send>>,
     #[setters(skip)]
     sm: Option<Arc<SourceMap>>,
-    fluent_bundle: Option<Arc<FluentBundle>>,
     #[setters(skip)]
-    fallback_bundle: LazyFallbackBundle,
+    translator: Translator,
     #[setters(skip)]
     pretty: bool,
     ui_testing: bool,
@@ -66,7 +63,7 @@ impl JsonEmitter {
     pub fn new(
         dst: Box<dyn Write + Send>,
         sm: Option<Arc<SourceMap>>,
-        fallback_bundle: LazyFallbackBundle,
+        translator: Translator,
         pretty: bool,
         json_rendered: HumanReadableErrorType,
         color_config: ColorConfig,
@@ -74,8 +71,7 @@ impl JsonEmitter {
         JsonEmitter {
             dst: IntoDynSyncSend(dst),
             sm,
-            fluent_bundle: None,
-            fallback_bundle,
+            translator,
             pretty,
             ui_testing: false,
             ignored_directories_in_source_blocks: Vec::new(),
@@ -104,20 +100,11 @@ impl JsonEmitter {
 enum EmitTyped<'a> {
     Diagnostic(Diagnostic),
     Artifact(ArtifactNotification<'a>),
+    SectionTiming(SectionTimestamp<'a>),
     FutureIncompat(FutureIncompatReport<'a>),
     UnusedExtern(UnusedExterns<'a>),
 }
 
-impl Translate for JsonEmitter {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
-        self.fluent_bundle.as_deref()
-    }
-
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        &self.fallback_bundle
-    }
-}
-
 impl Emitter for JsonEmitter {
     fn emit_diagnostic(&mut self, diag: crate::DiagInner, registry: &Registry) {
         let data = Diagnostic::from_errors_diagnostic(diag, self, registry);
@@ -135,6 +122,22 @@ impl Emitter for JsonEmitter {
         }
     }
 
+    fn emit_timing_section(&mut self, record: TimingRecord, event: TimingEvent) {
+        let event = match event {
+            TimingEvent::Start => "start",
+            TimingEvent::End => "end",
+        };
+        let name = match record.section {
+            TimingSection::Linking => "link",
+            TimingSection::Codegen => "codegen",
+        };
+        let data = SectionTimestamp { name, event, timestamp: record.timestamp };
+        let result = self.emit(EmitTyped::SectionTiming(data));
+        if let Err(e) = result {
+            panic!("failed to print timing section: {e:?}");
+        }
+    }
+
     fn emit_future_breakage_report(&mut self, diags: Vec<crate::DiagInner>, registry: &Registry) {
         let data: Vec<FutureBreakageItem<'_>> = diags
             .into_iter()
@@ -177,6 +180,10 @@ impl Emitter for JsonEmitter {
     fn should_show_explain(&self) -> bool {
         !self.json_rendered.short()
     }
+
+    fn translator(&self) -> &Translator {
+        &self.translator
+    }
 }
 
 // The following data types are provided just for serialisation.
@@ -264,6 +271,16 @@ struct ArtifactNotification<'a> {
 }
 
 #[derive(Serialize)]
+struct SectionTimestamp<'a> {
+    /// Name of the section
+    name: &'a str,
+    /// Start/end of the section
+    event: &'a str,
+    /// Opaque timestamp.
+    timestamp: u128,
+}
+
+#[derive(Serialize)]
 struct FutureBreakageItem<'a> {
     // Always EmitTyped::Diagnostic, but we want to make sure it gets serialized
     // with "$message_type".
@@ -297,7 +314,7 @@ impl Diagnostic {
         let args = to_fluent_args(diag.args.iter());
         let sugg_to_diag = |sugg: &CodeSuggestion| {
             let translated_message =
-                je.translate_message(&sugg.msg, &args).map_err(Report::new).unwrap();
+                je.translator.translate_message(&sugg.msg, &args).map_err(Report::new).unwrap();
             Diagnostic {
                 message: translated_message.to_string(),
                 code: None,
@@ -341,7 +358,7 @@ impl Diagnostic {
             }
         }
 
-        let translated_message = je.translate_messages(&diag.messages, &args);
+        let translated_message = je.translator.translate_messages(&diag.messages, &args);
 
         let code = if let Some(code) = diag.code {
             Some(DiagnosticCode {
@@ -369,10 +386,9 @@ impl Diagnostic {
             ColorConfig::Always | ColorConfig::Auto => dst = Box::new(termcolor::Ansi::new(dst)),
             ColorConfig::Never => {}
         }
-        HumanEmitter::new(dst, Arc::clone(&je.fallback_bundle))
+        HumanEmitter::new(dst, je.translator.clone())
             .short_message(short)
             .sm(je.sm.clone())
-            .fluent_bundle(je.fluent_bundle.clone())
             .diagnostic_width(je.diagnostic_width)
             .macro_backtrace(je.macro_backtrace)
             .track_diagnostics(je.track_diagnostics)
@@ -403,7 +419,7 @@ impl Diagnostic {
         args: &FluentArgs<'_>,
         je: &JsonEmitter,
     ) -> Diagnostic {
-        let translated_message = je.translate_messages(&subdiag.messages, args);
+        let translated_message = je.translator.translate_messages(&subdiag.messages, args);
         Diagnostic {
             message: translated_message.to_string(),
             code: None,
@@ -427,7 +443,7 @@ impl DiagnosticSpan {
             span.is_primary,
             span.label
                 .as_ref()
-                .map(|m| je.translate_message(m, args).unwrap())
+                .map(|m| je.translator.translate_message(m, args).unwrap())
                 .map(|m| m.to_string()),
             suggestion,
             je,
diff --git a/compiler/rustc_errors/src/json/tests.rs b/compiler/rustc_errors/src/json/tests.rs
index 40973e8e5d8..8cf81f467d8 100644
--- a/compiler/rustc_errors/src/json/tests.rs
+++ b/compiler/rustc_errors/src/json/tests.rs
@@ -41,14 +41,14 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
     rustc_span::create_default_session_globals_then(|| {
         let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
         sm.new_source_file(Path::new("test.rs").to_owned().into(), code.to_owned());
-        let fallback_bundle =
-            crate::fallback_fluent_bundle(vec![crate::DEFAULT_LOCALE_RESOURCE], false);
+        let translator =
+            Translator::with_fallback_bundle(vec![crate::DEFAULT_LOCALE_RESOURCE], false);
 
         let output = Arc::new(Mutex::new(Vec::new()));
         let je = JsonEmitter::new(
             Box::new(Shared { data: output.clone() }),
             Some(sm),
-            fallback_bundle,
+            translator,
             true, // pretty
             HumanReadableErrorType::Short,
             ColorConfig::Never,
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 9f72fc4705a..207aed8c755 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -7,6 +7,7 @@
 #![allow(internal_features)]
 #![allow(rustc::diagnostic_outside_of_impl)]
 #![allow(rustc::untranslatable_diagnostic)]
+#![cfg_attr(not(bootstrap), allow(rustc::direct_use_of_rustc_type_ir))]
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
 #![doc(rust_logo)]
 #![feature(array_windows)]
@@ -74,7 +75,9 @@ pub use snippet::Style;
 pub use termcolor::{Color, ColorSpec, WriteColor};
 use tracing::debug;
 
+use crate::emitter::TimingEvent;
 use crate::registry::Registry;
+use crate::timings::TimingRecord;
 
 pub mod annotate_snippet_emitter_writer;
 pub mod codes;
@@ -90,6 +93,7 @@ mod snippet;
 mod styled_buffer;
 #[cfg(test)]
 mod tests;
+pub mod timings;
 pub mod translation;
 
 pub type PResult<'a, T> = Result<T, Diag<'a>>;
@@ -744,40 +748,10 @@ impl DiagCtxt {
         Self { inner: Lock::new(DiagCtxtInner::new(emitter)) }
     }
 
-    pub fn make_silent(&self, fatal_note: Option<String>, emit_fatal_diagnostic: bool) {
-        // An empty type that implements `Emitter` to temporarily swap in place of the real one,
-        // which will be used in constructing its replacement.
-        struct FalseEmitter;
-
-        impl Emitter for FalseEmitter {
-            fn emit_diagnostic(&mut self, _: DiagInner, _: &Registry) {
-                unimplemented!("false emitter must only used during `make_silent`")
-            }
-
-            fn source_map(&self) -> Option<&SourceMap> {
-                unimplemented!("false emitter must only used during `make_silent`")
-            }
-        }
-
-        impl translation::Translate for FalseEmitter {
-            fn fluent_bundle(&self) -> Option<&FluentBundle> {
-                unimplemented!("false emitter must only used during `make_silent`")
-            }
-
-            fn fallback_fluent_bundle(&self) -> &FluentBundle {
-                unimplemented!("false emitter must only used during `make_silent`")
-            }
-        }
-
+    pub fn make_silent(&self) {
         let mut inner = self.inner.borrow_mut();
-        let mut prev_emitter = Box::new(FalseEmitter) as Box<dyn Emitter + DynSend>;
-        std::mem::swap(&mut inner.emitter, &mut prev_emitter);
-        let new_emitter = Box::new(emitter::SilentEmitter {
-            fatal_emitter: prev_emitter,
-            fatal_note,
-            emit_fatal_diagnostic,
-        });
-        inner.emitter = new_emitter;
+        let translator = inner.emitter.translator().clone();
+        inner.emitter = Box::new(emitter::SilentEmitter { translator });
     }
 
     pub fn set_emitter(&self, emitter: Box<dyn Emitter + DynSend>) {
@@ -1156,6 +1130,14 @@ impl<'a> DiagCtxtHandle<'a> {
         self.inner.borrow_mut().emitter.emit_artifact_notification(path, artifact_type);
     }
 
+    pub fn emit_timing_section_start(&self, record: TimingRecord) {
+        self.inner.borrow_mut().emitter.emit_timing_section(record, TimingEvent::Start);
+    }
+
+    pub fn emit_timing_section_end(&self, record: TimingRecord) {
+        self.inner.borrow_mut().emitter.emit_timing_section(record, TimingEvent::End);
+    }
+
     pub fn emit_future_breakage_report(&self) {
         let inner = &mut *self.inner.borrow_mut();
         let diags = std::mem::take(&mut inner.future_breakage_diagnostics);
@@ -1759,7 +1741,12 @@ impl DiagCtxtInner {
         args: impl Iterator<Item = DiagArg<'a>>,
     ) -> String {
         let args = crate::translation::to_fluent_args(args);
-        self.emitter.translate_message(&message, &args).map_err(Report::new).unwrap().to_string()
+        self.emitter
+            .translator()
+            .translate_message(&message, &args)
+            .map_err(Report::new)
+            .unwrap()
+            .to_string()
     }
 
     fn eagerly_translate_for_subdiag(
diff --git a/compiler/rustc_errors/src/tests.rs b/compiler/rustc_errors/src/tests.rs
index 376fd24d57b..34ebac0fde1 100644
--- a/compiler/rustc_errors/src/tests.rs
+++ b/compiler/rustc_errors/src/tests.rs
@@ -1,3 +1,5 @@
+use std::sync::{Arc, LazyLock};
+
 use rustc_data_structures::sync::IntoDynSyncSend;
 use rustc_error_messages::fluent_bundle::resolver::errors::{ReferenceKind, ResolverError};
 use rustc_error_messages::{DiagMessage, langid};
@@ -5,23 +7,9 @@ use rustc_error_messages::{DiagMessage, langid};
 use crate::FluentBundle;
 use crate::error::{TranslateError, TranslateErrorKind};
 use crate::fluent_bundle::*;
-use crate::translation::Translate;
-
-struct Dummy {
-    bundle: FluentBundle,
-}
-
-impl Translate for Dummy {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
-        None
-    }
+use crate::translation::Translator;
 
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        &self.bundle
-    }
-}
-
-fn make_dummy(ftl: &'static str) -> Dummy {
+fn make_translator(ftl: &'static str) -> Translator {
     let resource = FluentResource::try_new(ftl.into()).expect("Failed to parse an FTL string.");
 
     let langid_en = langid!("en-US");
@@ -33,12 +21,15 @@ fn make_dummy(ftl: &'static str) -> Dummy {
 
     bundle.add_resource(resource).expect("Failed to add FTL resources to the bundle.");
 
-    Dummy { bundle }
+    Translator {
+        fluent_bundle: None,
+        fallback_fluent_bundle: Arc::new(LazyLock::new(Box::new(|| bundle))),
+    }
 }
 
 #[test]
 fn wellformed_fluent() {
-    let dummy = make_dummy("mir_build_borrow_of_moved_value = borrow of moved value
+    let translator = make_translator("mir_build_borrow_of_moved_value = borrow of moved value
     .label = value moved into `{$name}` here
     .occurs_because_label = move occurs because `{$name}` has type `{$ty}` which does not implement the `Copy` trait
     .value_borrowed_label = value borrowed here after move
@@ -54,7 +45,7 @@ fn wellformed_fluent() {
         );
 
         assert_eq!(
-            dummy.translate_message(&message, &args).unwrap(),
+            translator.translate_message(&message, &args).unwrap(),
             "borrow this binding in the pattern to avoid moving the value"
         );
     }
@@ -66,7 +57,7 @@ fn wellformed_fluent() {
         );
 
         assert_eq!(
-            dummy.translate_message(&message, &args).unwrap(),
+            translator.translate_message(&message, &args).unwrap(),
             "value borrowed here after move"
         );
     }
@@ -78,7 +69,7 @@ fn wellformed_fluent() {
         );
 
         assert_eq!(
-            dummy.translate_message(&message, &args).unwrap(),
+            translator.translate_message(&message, &args).unwrap(),
             "move occurs because `\u{2068}Foo\u{2069}` has type `\u{2068}std::string::String\u{2069}` which does not implement the `Copy` trait"
         );
 
@@ -89,7 +80,7 @@ fn wellformed_fluent() {
             );
 
             assert_eq!(
-                dummy.translate_message(&message, &args).unwrap(),
+                translator.translate_message(&message, &args).unwrap(),
                 "value moved into `\u{2068}Foo\u{2069}` here"
             );
         }
@@ -98,7 +89,7 @@ fn wellformed_fluent() {
 
 #[test]
 fn misformed_fluent() {
-    let dummy = make_dummy("mir_build_borrow_of_moved_value = borrow of moved value
+    let translator = make_translator("mir_build_borrow_of_moved_value = borrow of moved value
     .label = value moved into `{name}` here
     .occurs_because_label = move occurs because `{$oops}` has type `{$ty}` which does not implement the `Copy` trait
     .suggestion = borrow this binding in the pattern to avoid moving the value");
@@ -112,7 +103,7 @@ fn misformed_fluent() {
             Some("value_borrowed_label".into()),
         );
 
-        let err = dummy.translate_message(&message, &args).unwrap_err();
+        let err = translator.translate_message(&message, &args).unwrap_err();
         assert!(
             matches!(
                 &err,
@@ -141,7 +132,7 @@ fn misformed_fluent() {
             Some("label".into()),
         );
 
-        let err = dummy.translate_message(&message, &args).unwrap_err();
+        let err = translator.translate_message(&message, &args).unwrap_err();
         if let TranslateError::Two {
             primary: box TranslateError::One { kind: TranslateErrorKind::PrimaryBundleMissing, .. },
             fallback: box TranslateError::One { kind: TranslateErrorKind::Fluent { errs }, .. },
@@ -168,7 +159,7 @@ fn misformed_fluent() {
             Some("occurs_because_label".into()),
         );
 
-        let err = dummy.translate_message(&message, &args).unwrap_err();
+        let err = translator.translate_message(&message, &args).unwrap_err();
         if let TranslateError::Two {
             primary: box TranslateError::One { kind: TranslateErrorKind::PrimaryBundleMissing, .. },
             fallback: box TranslateError::One { kind: TranslateErrorKind::Fluent { errs }, .. },
diff --git a/compiler/rustc_errors/src/timings.rs b/compiler/rustc_errors/src/timings.rs
new file mode 100644
index 00000000000..0d82f3e8db8
--- /dev/null
+++ b/compiler/rustc_errors/src/timings.rs
@@ -0,0 +1,121 @@
+use std::time::Instant;
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::sync::Lock;
+
+use crate::DiagCtxtHandle;
+
+/// A high-level section of the compilation process.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub enum TimingSection {
+    /// Time spent doing codegen.
+    Codegen,
+    /// Time spent linking.
+    Linking,
+}
+
+/// Section with attached timestamp
+#[derive(Copy, Clone, Debug)]
+pub struct TimingRecord {
+    pub section: TimingSection,
+    /// Microseconds elapsed since some predetermined point in time (~start of the rustc process).
+    pub timestamp: u128,
+}
+
+impl TimingRecord {
+    fn from_origin(origin: Instant, section: TimingSection) -> Self {
+        Self { section, timestamp: Instant::now().duration_since(origin).as_micros() }
+    }
+
+    pub fn section(&self) -> TimingSection {
+        self.section
+    }
+
+    pub fn timestamp(&self) -> u128 {
+        self.timestamp
+    }
+}
+
+/// Manages emission of start/end section timings, enabled through `--json=timings`.
+pub struct TimingSectionHandler {
+    /// Time when the compilation session started.
+    /// If `None`, timing is disabled.
+    origin: Option<Instant>,
+    /// Sanity check to ensure that we open and close sections correctly.
+    opened_sections: Lock<FxHashSet<TimingSection>>,
+}
+
+impl TimingSectionHandler {
+    pub fn new(enabled: bool) -> Self {
+        let origin = if enabled { Some(Instant::now()) } else { None };
+        Self { origin, opened_sections: Lock::new(FxHashSet::default()) }
+    }
+
+    /// Returns a RAII guard that will immediately emit a start the provided section, and then emit
+    /// its end when it is dropped.
+    pub fn section_guard<'a>(
+        &self,
+        diag_ctxt: DiagCtxtHandle<'a>,
+        section: TimingSection,
+    ) -> TimingSectionGuard<'a> {
+        if self.is_enabled() && self.opened_sections.borrow().contains(&section) {
+            diag_ctxt
+                .bug(format!("Section `{section:?}` was started again before it was finished"));
+        }
+
+        TimingSectionGuard::create(diag_ctxt, section, self.origin)
+    }
+
+    /// Start the provided section.
+    pub fn start_section(&self, diag_ctxt: DiagCtxtHandle<'_>, section: TimingSection) {
+        if let Some(origin) = self.origin {
+            let mut opened = self.opened_sections.borrow_mut();
+            if !opened.insert(section) {
+                diag_ctxt
+                    .bug(format!("Section `{section:?}` was started again before it was finished"));
+            }
+
+            diag_ctxt.emit_timing_section_start(TimingRecord::from_origin(origin, section));
+        }
+    }
+
+    /// End the provided section.
+    pub fn end_section(&self, diag_ctxt: DiagCtxtHandle<'_>, section: TimingSection) {
+        if let Some(origin) = self.origin {
+            let mut opened = self.opened_sections.borrow_mut();
+            if !opened.remove(&section) {
+                diag_ctxt.bug(format!("Section `{section:?}` was ended before being started"));
+            }
+
+            diag_ctxt.emit_timing_section_end(TimingRecord::from_origin(origin, section));
+        }
+    }
+
+    fn is_enabled(&self) -> bool {
+        self.origin.is_some()
+    }
+}
+
+/// RAII wrapper for starting and ending section timings.
+pub struct TimingSectionGuard<'a> {
+    dcx: DiagCtxtHandle<'a>,
+    section: TimingSection,
+    origin: Option<Instant>,
+}
+
+impl<'a> TimingSectionGuard<'a> {
+    fn create(dcx: DiagCtxtHandle<'a>, section: TimingSection, origin: Option<Instant>) -> Self {
+        if let Some(origin) = origin {
+            dcx.emit_timing_section_start(TimingRecord::from_origin(origin, section));
+        }
+        Self { dcx, section, origin }
+    }
+}
+
+impl<'a> Drop for TimingSectionGuard<'a> {
+    fn drop(&mut self) {
+        if let Some(origin) = self.origin {
+            self.dcx.emit_timing_section_end(TimingRecord::from_origin(origin, self.section));
+        }
+    }
+}
diff --git a/compiler/rustc_errors/src/translation.rs b/compiler/rustc_errors/src/translation.rs
index 156f5e5d26e..c0bcec093c7 100644
--- a/compiler/rustc_errors/src/translation.rs
+++ b/compiler/rustc_errors/src/translation.rs
@@ -1,8 +1,9 @@
 use std::borrow::Cow;
 use std::env;
 use std::error::Report;
+use std::sync::Arc;
 
-pub use rustc_error_messages::FluentArgs;
+pub use rustc_error_messages::{FluentArgs, LazyFallbackBundle};
 use tracing::{debug, trace};
 
 use crate::error::{TranslateError, TranslateErrorKind};
@@ -28,19 +29,33 @@ pub fn to_fluent_args<'iter>(iter: impl Iterator<Item = DiagArg<'iter>>) -> Flue
     args
 }
 
-pub trait Translate {
-    /// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
-    /// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
-    /// should be used.
-    fn fluent_bundle(&self) -> Option<&FluentBundle>;
-
+#[derive(Clone)]
+pub struct Translator {
+    /// Localized diagnostics for the locale requested by the user. If no language was requested by
+    /// the user then this will be `None` and `fallback_fluent_bundle` should be used.
+    pub fluent_bundle: Option<Arc<FluentBundle>>,
     /// Return `FluentBundle` with localized diagnostics for the default locale of the compiler.
     /// Used when the user has not requested a specific language or when a localized diagnostic is
     /// unavailable for the requested locale.
-    fn fallback_fluent_bundle(&self) -> &FluentBundle;
+    pub fallback_fluent_bundle: LazyFallbackBundle,
+}
+
+impl Translator {
+    pub fn with_fallback_bundle(
+        resources: Vec<&'static str>,
+        with_directionality_markers: bool,
+    ) -> Translator {
+        Translator {
+            fluent_bundle: None,
+            fallback_fluent_bundle: crate::fallback_fluent_bundle(
+                resources,
+                with_directionality_markers,
+            ),
+        }
+    }
 
     /// Convert `DiagMessage`s to a string, performing translation if necessary.
-    fn translate_messages(
+    pub fn translate_messages(
         &self,
         messages: &[(DiagMessage, Style)],
         args: &FluentArgs<'_>,
@@ -54,7 +69,7 @@ pub trait Translate {
     }
 
     /// Convert a `DiagMessage` to a string, performing translation if necessary.
-    fn translate_message<'a>(
+    pub fn translate_message<'a>(
         &'a self,
         message: &'a DiagMessage,
         args: &'a FluentArgs<'_>,
@@ -91,7 +106,7 @@ pub trait Translate {
             };
 
         try {
-            match self.fluent_bundle().map(|b| translate_with_bundle(b)) {
+            match self.fluent_bundle.as_ref().map(|b| translate_with_bundle(b)) {
                 // The primary bundle was present and translation succeeded
                 Some(Ok(t)) => t,
 
@@ -102,7 +117,7 @@ pub trait Translate {
                     primary @ TranslateError::One {
                         kind: TranslateErrorKind::MessageMissing, ..
                     },
-                )) => translate_with_bundle(self.fallback_fluent_bundle())
+                )) => translate_with_bundle(&self.fallback_fluent_bundle)
                     .map_err(|fallback| primary.and(fallback))?,
 
                 // Always yeet out for errors on debug (unless
@@ -118,11 +133,11 @@ pub trait Translate {
 
                 // ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
                 // just hide it and try with the fallback bundle.
-                Some(Err(primary)) => translate_with_bundle(self.fallback_fluent_bundle())
+                Some(Err(primary)) => translate_with_bundle(&self.fallback_fluent_bundle)
                     .map_err(|fallback| primary.and(fallback))?,
 
                 // The primary bundle is missing, proceed to the fallback bundle
-                None => translate_with_bundle(self.fallback_fluent_bundle())
+                None => translate_with_bundle(&self.fallback_fluent_bundle)
                     .map_err(|fallback| TranslateError::primary(identifier, args).and(fallback))?,
             }
         }