diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs
index 9233287cf3a75..2440f20502ab1 100644
--- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs
@@ -187,6 +187,12 @@ impl Display for RegionName {
     }
 }
 
+impl rustc_errors::IntoDiagnosticArg for RegionName {
+    fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> {
+        self.to_string().into_diagnostic_arg()
+    }
+}
+
 impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
     pub(crate) fn mir_def_id(&self) -> hir::def_id::LocalDefId {
         self.body.source.def_id().expect_local()
diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs
index b2d92d0dba7a4..c71413e8e7c04 100644
--- a/compiler/rustc_borrowck/src/nll.rs
+++ b/compiler/rustc_borrowck/src/nll.rs
@@ -369,6 +369,8 @@ pub(super) fn dump_mir_results<'tcx>(
     };
 }
 
+#[allow(rustc::diagnostic_outside_of_impl)]
+#[allow(rustc::untranslatable_diagnostic)]
 pub(super) fn dump_annotation<'tcx>(
     infcx: &InferCtxt<'tcx>,
     body: &Body<'tcx>,
diff --git a/compiler/rustc_borrowck/src/session_diagnostics.rs b/compiler/rustc_borrowck/src/session_diagnostics.rs
index 23acf159240fa..13199d0385255 100644
--- a/compiler/rustc_borrowck/src/session_diagnostics.rs
+++ b/compiler/rustc_borrowck/src/session_diagnostics.rs
@@ -1,4 +1,4 @@
-use rustc_errors::{IntoDiagnosticArg, MultiSpan};
+use rustc_errors::MultiSpan;
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
 use rustc_middle::ty::{GenericArg, Ty};
 use rustc_span::Span;
@@ -128,18 +128,6 @@ pub(crate) enum LifetimeReturnCategoryErr<'a> {
     },
 }
 
-impl IntoDiagnosticArg for &RegionName {
-    fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> {
-        format!("{}", self).into_diagnostic_arg()
-    }
-}
-
-impl IntoDiagnosticArg for RegionName {
-    fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> {
-        format!("{}", self).into_diagnostic_arg()
-    }
-}
-
 #[derive(Subdiagnostic)]
 pub(crate) enum RequireStaticErr {
     #[note(borrowck_used_impl_require_static)]
diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs
index bf1da38312f7d..5ab87feb98b11 100644
--- a/compiler/rustc_codegen_gcc/src/lib.rs
+++ b/compiler/rustc_codegen_gcc/src/lib.rs
@@ -200,6 +200,7 @@ unsafe impl Sync for GccContext {}
 impl WriteBackendMethods for GccCodegenBackend {
     type Module = GccContext;
     type TargetMachine = ();
+    type TargetMachineError = ();
     type ModuleBuffer = ModuleBuffer;
     type ThinData = ();
     type ThinBuffer = ThinBuffer;
diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs
index 6c0faf37a63ce..d2e01708a37bc 100644
--- a/compiler/rustc_codegen_llvm/src/back/lto.rs
+++ b/compiler/rustc_codegen_llvm/src/back/lto.rs
@@ -1,5 +1,7 @@
 use crate::back::write::{self, save_temp_bitcode, DiagnosticHandlers};
-use crate::errors::DynamicLinkingWithLTO;
+use crate::errors::{
+    DynamicLinkingWithLTO, LlvmError, LtoBitcodeFromRlib, LtoDisallowed, LtoDylib,
+};
 use crate::llvm::{self, build_string};
 use crate::{LlvmCodegenBackend, ModuleLlvm};
 use object::read::archive::ArchiveFile;
@@ -77,15 +79,12 @@ fn prepare_lto(
         // Make sure we actually can run LTO
         for crate_type in cgcx.crate_types.iter() {
             if !crate_type_allows_lto(*crate_type) {
-                let e = diag_handler.fatal(
-                    "lto can only be run for executables, cdylibs and \
-                                            static library outputs",
-                );
-                return Err(e);
+                diag_handler.emit_err(LtoDisallowed);
+                return Err(FatalError);
             } else if *crate_type == CrateType::Dylib {
                 if !cgcx.opts.unstable_opts.dylib_lto {
-                    return Err(diag_handler
-                        .fatal("lto cannot be used for `dylib` crate type without `-Zdylib-lto`"));
+                    diag_handler.emit_err(LtoDylib);
+                    return Err(FatalError);
                 }
             }
         }
@@ -127,7 +126,10 @@ fn prepare_lto(
                         let module = SerializedModule::FromRlib(data.to_vec());
                         upstream_modules.push((module, CString::new(name).unwrap()));
                     }
-                    Err(msg) => return Err(diag_handler.fatal(&msg)),
+                    Err(e) => {
+                        diag_handler.emit_err(e);
+                        return Err(FatalError);
+                    }
                 }
             }
         }
@@ -140,7 +142,7 @@ fn prepare_lto(
     Ok((symbols_below_threshold, upstream_modules))
 }
 
-fn get_bitcode_slice_from_object_data(obj: &[u8]) -> Result<&[u8], String> {
+fn get_bitcode_slice_from_object_data(obj: &[u8]) -> Result<&[u8], LtoBitcodeFromRlib> {
     let mut len = 0;
     let data =
         unsafe { llvm::LLVMRustGetBitcodeSliceFromObjectData(obj.as_ptr(), obj.len(), &mut len) };
@@ -155,8 +157,9 @@ fn get_bitcode_slice_from_object_data(obj: &[u8]) -> Result<&[u8], String> {
         Ok(bc)
     } else {
         assert!(len == 0);
-        let msg = llvm::last_error().unwrap_or_else(|| "unknown LLVM error".to_string());
-        Err(format!("failed to get bitcode from object file for LTO ({})", msg))
+        Err(LtoBitcodeFromRlib {
+            llvm_err: llvm::last_error().unwrap_or_else(|| "unknown LLVM error".to_string()),
+        })
     }
 }
 
@@ -328,10 +331,9 @@ fn fat_lto(
                 });
             info!("linking {:?}", name);
             let data = bc_decoded.data();
-            linker.add(data).map_err(|()| {
-                let msg = format!("failed to load bitcode of module {:?}", name);
-                write::llvm_err(diag_handler, &msg)
-            })?;
+            linker
+                .add(data)
+                .map_err(|()| write::llvm_err(diag_handler, LlvmError::LoadBitcode { name }))?;
             serialized_bitcode.push(bc_decoded);
         }
         drop(linker);
@@ -489,7 +491,7 @@ fn thin_lto(
             symbols_below_threshold.as_ptr(),
             symbols_below_threshold.len() as u32,
         )
-        .ok_or_else(|| write::llvm_err(diag_handler, "failed to prepare thin LTO context"))?;
+        .ok_or_else(|| write::llvm_err(diag_handler, LlvmError::PrepareThinLtoContext))?;
 
         let data = ThinData(data);
 
@@ -562,8 +564,7 @@ fn thin_lto(
         // session, overwriting the previous serialized data (if any).
         if let Some(path) = key_map_path {
             if let Err(err) = curr_key_map.save_to_file(&path) {
-                let msg = format!("Error while writing ThinLTO key data: {}", err);
-                return Err(write::llvm_err(diag_handler, &msg));
+                return Err(write::llvm_err(diag_handler, LlvmError::WriteThinLtoKey { err }));
             }
         }
 
@@ -689,8 +690,7 @@ pub unsafe fn optimize_thin_module(
 
     let module_name = &thin_module.shared.module_names[thin_module.idx];
     let tm_factory_config = TargetMachineFactoryConfig::new(cgcx, module_name.to_str().unwrap());
-    let tm =
-        (cgcx.tm_factory)(tm_factory_config).map_err(|e| write::llvm_err(&diag_handler, &e))?;
+    let tm = (cgcx.tm_factory)(tm_factory_config).map_err(|e| write::llvm_err(&diag_handler, e))?;
 
     // Right now the implementation we've got only works over serialized
     // modules, so we create a fresh new LLVM context and parse the module
@@ -717,8 +717,7 @@ pub unsafe fn optimize_thin_module(
         let mut cu2 = ptr::null_mut();
         llvm::LLVMRustThinLTOGetDICompileUnit(llmod, &mut cu1, &mut cu2);
         if !cu2.is_null() {
-            let msg = "multiple source DICompileUnits found";
-            return Err(write::llvm_err(&diag_handler, msg));
+            return Err(write::llvm_err(&diag_handler, LlvmError::MultipleSourceDiCompileUnit));
         }
 
         // Up next comes the per-module local analyses that we do for Thin LTO.
@@ -733,8 +732,7 @@ pub unsafe fn optimize_thin_module(
             let _timer =
                 cgcx.prof.generic_activity_with_arg("LLVM_thin_lto_rename", thin_module.name());
             if !llvm::LLVMRustPrepareThinLTORename(thin_module.shared.data.0, llmod, target) {
-                let msg = "failed to prepare thin LTO module";
-                return Err(write::llvm_err(&diag_handler, msg));
+                return Err(write::llvm_err(&diag_handler, LlvmError::PrepareThinLtoModule));
             }
             save_temp_bitcode(cgcx, &module, "thin-lto-after-rename");
         }
@@ -744,8 +742,7 @@ pub unsafe fn optimize_thin_module(
                 .prof
                 .generic_activity_with_arg("LLVM_thin_lto_resolve_weak", thin_module.name());
             if !llvm::LLVMRustPrepareThinLTOResolveWeak(thin_module.shared.data.0, llmod) {
-                let msg = "failed to prepare thin LTO module";
-                return Err(write::llvm_err(&diag_handler, msg));
+                return Err(write::llvm_err(&diag_handler, LlvmError::PrepareThinLtoModule));
             }
             save_temp_bitcode(cgcx, &module, "thin-lto-after-resolve");
         }
@@ -755,8 +752,7 @@ pub unsafe fn optimize_thin_module(
                 .prof
                 .generic_activity_with_arg("LLVM_thin_lto_internalize", thin_module.name());
             if !llvm::LLVMRustPrepareThinLTOInternalize(thin_module.shared.data.0, llmod) {
-                let msg = "failed to prepare thin LTO module";
-                return Err(write::llvm_err(&diag_handler, msg));
+                return Err(write::llvm_err(&diag_handler, LlvmError::PrepareThinLtoModule));
             }
             save_temp_bitcode(cgcx, &module, "thin-lto-after-internalize");
         }
@@ -765,8 +761,7 @@ pub unsafe fn optimize_thin_module(
             let _timer =
                 cgcx.prof.generic_activity_with_arg("LLVM_thin_lto_import", thin_module.name());
             if !llvm::LLVMRustPrepareThinLTOImport(thin_module.shared.data.0, llmod, target) {
-                let msg = "failed to prepare thin LTO module";
-                return Err(write::llvm_err(&diag_handler, msg));
+                return Err(write::llvm_err(&diag_handler, LlvmError::PrepareThinLtoModule));
             }
             save_temp_bitcode(cgcx, &module, "thin-lto-after-import");
         }
@@ -886,11 +881,7 @@ pub fn parse_module<'a>(
     diag_handler: &Handler,
 ) -> Result<&'a llvm::Module, FatalError> {
     unsafe {
-        llvm::LLVMRustParseBitcodeForLTO(cx, data.as_ptr(), data.len(), name.as_ptr()).ok_or_else(
-            || {
-                let msg = "failed to parse bitcode for LTO module";
-                write::llvm_err(diag_handler, msg)
-            },
-        )
+        llvm::LLVMRustParseBitcodeForLTO(cx, data.as_ptr(), data.len(), name.as_ptr())
+            .ok_or_else(|| write::llvm_err(diag_handler, LlvmError::ParseBitcode))
     }
 }
diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs
index b2af9f31e4494..38f8733763dfa 100644
--- a/compiler/rustc_codegen_llvm/src/back/write.rs
+++ b/compiler/rustc_codegen_llvm/src/back/write.rs
@@ -5,6 +5,9 @@ use crate::back::profiling::{
 use crate::base;
 use crate::common;
 use crate::consts;
+use crate::errors::{
+    CopyBitcode, FromLlvmDiag, FromLlvmOptimizationDiag, LlvmError, WithLlvmError, WriteBytecode,
+};
 use crate::llvm::{self, DiagnosticInfo, PassManager};
 use crate::llvm_util;
 use crate::type_::Type;
@@ -37,10 +40,10 @@ use std::slice;
 use std::str;
 use std::sync::Arc;
 
-pub fn llvm_err(handler: &rustc_errors::Handler, msg: &str) -> FatalError {
+pub fn llvm_err<'a>(handler: &rustc_errors::Handler, err: LlvmError<'a>) -> FatalError {
     match llvm::last_error() {
-        Some(err) => handler.fatal(&format!("{}: {}", msg, err)),
-        None => handler.fatal(msg),
+        Some(llvm_err) => handler.emit_almost_fatal(WithLlvmError(err, llvm_err)),
+        None => handler.emit_almost_fatal(err),
     }
 }
 
@@ -85,10 +88,9 @@ pub fn write_output_file<'ll>(
             }
         }
 
-        result.into_result().map_err(|()| {
-            let msg = format!("could not write output to {}", output.display());
-            llvm_err(handler, &msg)
-        })
+        result
+            .into_result()
+            .map_err(|()| llvm_err(handler, LlvmError::WriteOutput { path: output }))
     }
 }
 
@@ -98,7 +100,7 @@ pub fn create_informational_target_machine(sess: &Session) -> &'static mut llvm:
     // system/tcx is set up.
     let features = llvm_util::global_llvm_features(sess, false);
     target_machine_factory(sess, config::OptLevel::No, &features)(config)
-        .unwrap_or_else(|err| llvm_err(sess.diagnostic(), &err).raise())
+        .unwrap_or_else(|err| llvm_err(sess.diagnostic(), err).raise())
 }
 
 pub fn create_target_machine(tcx: TyCtxt<'_>, mod_name: &str) -> &'static mut llvm::TargetMachine {
@@ -117,7 +119,7 @@ pub fn create_target_machine(tcx: TyCtxt<'_>, mod_name: &str) -> &'static mut ll
         tcx.backend_optimization_level(()),
         tcx.global_backend_features(()),
     )(config)
-    .unwrap_or_else(|err| llvm_err(tcx.sess.diagnostic(), &err).raise())
+    .unwrap_or_else(|err| llvm_err(tcx.sess.diagnostic(), err).raise())
 }
 
 pub fn to_llvm_opt_settings(
@@ -240,9 +242,7 @@ pub fn target_machine_factory(
             )
         };
 
-        tm.ok_or_else(|| {
-            format!("Could not create LLVM TargetMachine for triple: {}", triple.to_str().unwrap())
-        })
+        tm.ok_or_else(|| LlvmError::CreateTargetMachine { triple: triple.clone() })
     })
 }
 
@@ -355,25 +355,28 @@ unsafe extern "C" fn diagnostic_handler(info: &DiagnosticInfo, user: *mut c_void
             };
 
             if enabled {
-                diag_handler.note_without_error(&format!(
-                    "{}:{}:{}: {}: {}",
-                    opt.filename, opt.line, opt.column, opt.pass_name, opt.message,
-                ));
+                diag_handler.emit_note(FromLlvmOptimizationDiag {
+                    filename: &opt.filename,
+                    line: opt.line,
+                    column: opt.column,
+                    pass_name: &opt.pass_name,
+                    message: &opt.message,
+                });
             }
         }
         llvm::diagnostic::PGO(diagnostic_ref) | llvm::diagnostic::Linker(diagnostic_ref) => {
-            let msg = llvm::build_string(|s| {
+            let message = llvm::build_string(|s| {
                 llvm::LLVMRustWriteDiagnosticInfoToString(diagnostic_ref, s)
             })
             .expect("non-UTF8 diagnostic");
-            diag_handler.warn(&msg);
+            diag_handler.emit_warning(FromLlvmDiag { message });
         }
         llvm::diagnostic::Unsupported(diagnostic_ref) => {
-            let msg = llvm::build_string(|s| {
+            let message = llvm::build_string(|s| {
                 llvm::LLVMRustWriteDiagnosticInfoToString(diagnostic_ref, s)
             })
             .expect("non-UTF8 diagnostic");
-            diag_handler.err(&msg);
+            diag_handler.emit_err(FromLlvmDiag { message });
         }
         llvm::diagnostic::UnknownDiagnostic(..) => {}
     }
@@ -494,7 +497,7 @@ pub(crate) unsafe fn llvm_optimize(
         llvm_plugins.as_ptr().cast(),
         llvm_plugins.len(),
     );
-    result.into_result().map_err(|()| llvm_err(diag_handler, "failed to run LLVM passes"))
+    result.into_result().map_err(|()| llvm_err(diag_handler, LlvmError::RunLlvmPasses))
 }
 
 // Unsafe due to LLVM calls.
@@ -547,8 +550,7 @@ pub(crate) fn link(
         let _timer = cgcx.prof.generic_activity_with_arg("LLVM_link_module", &*module.name);
         let buffer = ModuleBuffer::new(module.module_llvm.llmod());
         linker.add(buffer.data()).map_err(|()| {
-            let msg = format!("failed to serialize module {:?}", module.name);
-            llvm_err(diag_handler, &msg)
+            llvm_err(diag_handler, LlvmError::SerializeModule { name: &module.name })
         })?;
     }
     drop(linker);
@@ -626,9 +628,8 @@ pub(crate) unsafe fn codegen(
                 let _timer = cgcx
                     .prof
                     .generic_activity_with_arg("LLVM_module_codegen_emit_bitcode", &*module.name);
-                if let Err(e) = fs::write(&bc_out, data) {
-                    let msg = format!("failed to write bytecode to {}: {}", bc_out.display(), e);
-                    diag_handler.err(&msg);
+                if let Err(err) = fs::write(&bc_out, data) {
+                    diag_handler.emit_err(WriteBytecode { path: &bc_out, err });
                 }
             }
 
@@ -678,10 +679,9 @@ pub(crate) unsafe fn codegen(
                 record_artifact_size(&cgcx.prof, "llvm_ir", &out);
             }
 
-            result.into_result().map_err(|()| {
-                let msg = format!("failed to write LLVM IR to {}", out.display());
-                llvm_err(diag_handler, &msg)
-            })?;
+            result
+                .into_result()
+                .map_err(|()| llvm_err(diag_handler, LlvmError::WriteIr { path: &out }))?;
         }
 
         if config.emit_asm {
@@ -749,8 +749,8 @@ pub(crate) unsafe fn codegen(
 
             EmitObj::Bitcode => {
                 debug!("copying bitcode {:?} to obj {:?}", bc_out, obj_out);
-                if let Err(e) = link_or_copy(&bc_out, &obj_out) {
-                    diag_handler.err(&format!("failed to copy bitcode to object file: {}", e));
+                if let Err(err) = link_or_copy(&bc_out, &obj_out) {
+                    diag_handler.emit_err(CopyBitcode { err });
                 }
 
                 if !config.emit_bc {
diff --git a/compiler/rustc_codegen_llvm/src/errors.rs b/compiler/rustc_codegen_llvm/src/errors.rs
index 001d1ce93d8b4..81072edc475c4 100644
--- a/compiler/rustc_codegen_llvm/src/errors.rs
+++ b/compiler/rustc_codegen_llvm/src/errors.rs
@@ -1,10 +1,11 @@
 use std::borrow::Cow;
+use std::ffi::CString;
+use std::path::Path;
 
-use rustc_errors::fluent;
-use rustc_errors::DiagnosticBuilder;
-use rustc_errors::ErrorGuaranteed;
-use rustc_errors::Handler;
-use rustc_errors::IntoDiagnostic;
+use rustc_data_structures::small_c_str::SmallCStr;
+use rustc_errors::{
+    fluent, DiagnosticBuilder, EmissionGuarantee, ErrorGuaranteed, Handler, IntoDiagnostic,
+};
 use rustc_macros::{Diagnostic, Subdiagnostic};
 use rustc_span::Span;
 
@@ -81,10 +82,18 @@ pub(crate) struct DlltoolFailImportLibrary<'a> {
 #[note]
 pub(crate) struct DynamicLinkingWithLTO;
 
-#[derive(Diagnostic)]
-#[diag(codegen_llvm_fail_parsing_target_machine_config_to_target_machine)]
-pub(crate) struct FailParsingTargetMachineConfigToTargetMachine {
-    pub error: String,
+pub(crate) struct ParseTargetMachineConfig<'a>(pub LlvmError<'a>);
+
+impl<EM: EmissionGuarantee> IntoDiagnostic<'_, EM> for ParseTargetMachineConfig<'_> {
+    fn into_diagnostic(self, sess: &'_ Handler) -> DiagnosticBuilder<'_, EM> {
+        let diag: DiagnosticBuilder<'_, EM> = self.0.into_diagnostic(sess);
+        let (message, _) = diag.styled_message().first().expect("`LlvmError` with no message");
+        let message = sess.eagerly_translate_to_string(message.clone(), diag.args());
+
+        let mut diag = sess.struct_diagnostic(fluent::codegen_llvm_parse_target_machine_config);
+        diag.set_arg("error", message);
+        diag
+    }
 }
 
 pub(crate) struct TargetFeatureDisableOrEnable<'a> {
@@ -110,3 +119,99 @@ impl IntoDiagnostic<'_, ErrorGuaranteed> for TargetFeatureDisableOrEnable<'_> {
         diag
     }
 }
+
+#[derive(Diagnostic)]
+#[diag(codegen_llvm_lto_disallowed)]
+pub(crate) struct LtoDisallowed;
+
+#[derive(Diagnostic)]
+#[diag(codegen_llvm_lto_dylib)]
+pub(crate) struct LtoDylib;
+
+#[derive(Diagnostic)]
+#[diag(codegen_llvm_lto_bitcode_from_rlib)]
+pub(crate) struct LtoBitcodeFromRlib {
+    pub llvm_err: String,
+}
+
+#[derive(Diagnostic)]
+pub enum LlvmError<'a> {
+    #[diag(codegen_llvm_write_output)]
+    WriteOutput { path: &'a Path },
+    #[diag(codegen_llvm_target_machine)]
+    CreateTargetMachine { triple: SmallCStr },
+    #[diag(codegen_llvm_run_passes)]
+    RunLlvmPasses,
+    #[diag(codegen_llvm_serialize_module)]
+    SerializeModule { name: &'a str },
+    #[diag(codegen_llvm_write_ir)]
+    WriteIr { path: &'a Path },
+    #[diag(codegen_llvm_prepare_thin_lto_context)]
+    PrepareThinLtoContext,
+    #[diag(codegen_llvm_load_bitcode)]
+    LoadBitcode { name: CString },
+    #[diag(codegen_llvm_write_thinlto_key)]
+    WriteThinLtoKey { err: std::io::Error },
+    #[diag(codegen_llvm_multiple_source_dicompileunit)]
+    MultipleSourceDiCompileUnit,
+    #[diag(codegen_llvm_prepare_thin_lto_module)]
+    PrepareThinLtoModule,
+    #[diag(codegen_llvm_parse_bitcode)]
+    ParseBitcode,
+}
+
+pub(crate) struct WithLlvmError<'a>(pub LlvmError<'a>, pub String);
+
+impl<EM: EmissionGuarantee> IntoDiagnostic<'_, EM> for WithLlvmError<'_> {
+    fn into_diagnostic(self, sess: &'_ Handler) -> DiagnosticBuilder<'_, EM> {
+        use LlvmError::*;
+        let msg_with_llvm_err = match &self.0 {
+            WriteOutput { .. } => fluent::codegen_llvm_write_output_with_llvm_err,
+            CreateTargetMachine { .. } => fluent::codegen_llvm_target_machine_with_llvm_err,
+            RunLlvmPasses => fluent::codegen_llvm_run_passes_with_llvm_err,
+            SerializeModule { .. } => fluent::codegen_llvm_serialize_module_with_llvm_err,
+            WriteIr { .. } => fluent::codegen_llvm_write_ir_with_llvm_err,
+            PrepareThinLtoContext => fluent::codegen_llvm_prepare_thin_lto_context_with_llvm_err,
+            LoadBitcode { .. } => fluent::codegen_llvm_load_bitcode_with_llvm_err,
+            WriteThinLtoKey { .. } => fluent::codegen_llvm_write_thinlto_key_with_llvm_err,
+            MultipleSourceDiCompileUnit => {
+                fluent::codegen_llvm_multiple_source_dicompileunit_with_llvm_err
+            }
+            PrepareThinLtoModule => fluent::codegen_llvm_prepare_thin_lto_module_with_llvm_err,
+            ParseBitcode => fluent::codegen_llvm_parse_bitcode_with_llvm_err,
+        };
+        let mut diag = self.0.into_diagnostic(sess);
+        diag.set_primary_message(msg_with_llvm_err);
+        diag.set_arg("llvm_err", self.1);
+        diag
+    }
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_llvm_from_llvm_optimization_diag)]
+pub(crate) struct FromLlvmOptimizationDiag<'a> {
+    pub filename: &'a str,
+    pub line: std::ffi::c_uint,
+    pub column: std::ffi::c_uint,
+    pub pass_name: &'a str,
+    pub message: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_llvm_from_llvm_diag)]
+pub(crate) struct FromLlvmDiag {
+    pub message: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_llvm_write_bytecode)]
+pub(crate) struct WriteBytecode<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_llvm_copy_bitcode)]
+pub(crate) struct CopyBitcode {
+    pub err: std::io::Error,
+}
diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs
index 246e82545c874..111d14b265cde 100644
--- a/compiler/rustc_codegen_llvm/src/lib.rs
+++ b/compiler/rustc_codegen_llvm/src/lib.rs
@@ -5,11 +5,12 @@
 //! This API is completely unstable and subject to change.
 
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![feature(extern_types)]
 #![feature(hash_raw_entry)]
+#![feature(iter_intersperse)]
 #![feature(let_chains)]
-#![feature(extern_types)]
+#![feature(never_type)]
 #![feature(once_cell)]
-#![feature(iter_intersperse)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
 #![deny(rustc::untranslatable_diagnostic)]
@@ -22,7 +23,7 @@ extern crate tracing;
 
 use back::write::{create_informational_target_machine, create_target_machine};
 
-use errors::FailParsingTargetMachineConfigToTargetMachine;
+use errors::ParseTargetMachineConfig;
 pub use llvm_util::target_features;
 use rustc_ast::expand::allocator::AllocatorKind;
 use rustc_codegen_ssa::back::lto::{LtoModuleCodegen, SerializedModule, ThinModule};
@@ -169,6 +170,7 @@ impl WriteBackendMethods for LlvmCodegenBackend {
     type Module = ModuleLlvm;
     type ModuleBuffer = back::lto::ModuleBuffer;
     type TargetMachine = &'static mut llvm::TargetMachine;
+    type TargetMachineError = crate::errors::LlvmError<'static>;
     type ThinData = back::lto::ThinData;
     type ThinBuffer = back::lto::ThinBuffer;
     fn print_pass_timings(&self) {
@@ -416,8 +418,7 @@ impl ModuleLlvm {
             let tm = match (cgcx.tm_factory)(tm_factory_config) {
                 Ok(m) => m,
                 Err(e) => {
-                    handler.emit_err(FailParsingTargetMachineConfigToTargetMachine { error: e });
-                    return Err(FatalError);
+                    return Err(handler.emit_almost_fatal(ParseTargetMachineConfig(e)));
                 }
             };
 
diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs
index 9f1614af7b16c..8508ab87532c2 100644
--- a/compiler/rustc_codegen_ssa/src/back/write.rs
+++ b/compiler/rustc_codegen_ssa/src/back/write.rs
@@ -305,8 +305,12 @@ impl TargetMachineFactoryConfig {
 }
 
 pub type TargetMachineFactoryFn<B> = Arc<
-    dyn Fn(TargetMachineFactoryConfig) -> Result<<B as WriteBackendMethods>::TargetMachine, String>
-        + Send
+    dyn Fn(
+            TargetMachineFactoryConfig,
+        ) -> Result<
+            <B as WriteBackendMethods>::TargetMachine,
+            <B as WriteBackendMethods>::TargetMachineError,
+        > + Send
         + Sync,
 >;
 
diff --git a/compiler/rustc_codegen_ssa/src/traits/write.rs b/compiler/rustc_codegen_ssa/src/traits/write.rs
index e0e8ffa89ed15..9826256a4c5d5 100644
--- a/compiler/rustc_codegen_ssa/src/traits/write.rs
+++ b/compiler/rustc_codegen_ssa/src/traits/write.rs
@@ -8,6 +8,7 @@ use rustc_middle::dep_graph::WorkProduct;
 pub trait WriteBackendMethods: 'static + Sized + Clone {
     type Module: Send + Sync;
     type TargetMachine;
+    type TargetMachineError;
     type ModuleBuffer: ModuleBufferMethods;
     type ThinData: Send + Sync;
     type ThinBuffer: ThinBufferMethods;
diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs
index ccefd6adaf14b..02e0b042ad263 100644
--- a/compiler/rustc_driver/src/lib.rs
+++ b/compiler/rustc_driver/src/lib.rs
@@ -43,7 +43,6 @@ use rustc_span::source_map::{FileLoader, FileName};
 use rustc_span::symbol::sym;
 use rustc_target::json::ToJson;
 
-use std::borrow::Cow;
 use std::cmp::max;
 use std::env;
 use std::ffi::OsString;
@@ -1205,29 +1204,20 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
         handler.emit_diagnostic(&mut d);
     }
 
-    let mut xs: Vec<Cow<'static, str>> = vec![
-        "the compiler unexpectedly panicked. this is a bug.".into(),
-        format!("we would appreciate a bug report: {bug_report_url}").into(),
-        format!(
-            "rustc {} running on {}",
-            util::version_str!().unwrap_or("unknown_version"),
-            config::host_triple()
-        )
-        .into(),
-    ];
+    handler.emit_note(session_diagnostics::Ice);
+    handler.emit_note(session_diagnostics::IceBugReport { bug_report_url });
+    handler.emit_note(session_diagnostics::IceVersion {
+        version: util::version_str!().unwrap_or("unknown_version"),
+        triple: config::host_triple(),
+    });
 
     if let Some((flags, excluded_cargo_defaults)) = extra_compiler_flags() {
-        xs.push(format!("compiler flags: {}", flags.join(" ")).into());
-
+        handler.emit_note(session_diagnostics::IceFlags { flags: flags.join(" ") });
         if excluded_cargo_defaults {
-            xs.push("some of the compiler flags provided by cargo are hidden".into());
+            handler.emit_note(session_diagnostics::IceExcludeCargoDefaults);
         }
     }
 
-    for note in &xs {
-        handler.note_without_error(note.as_ref());
-    }
-
     // If backtraces are enabled, also print the query stack
     let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0");
 
diff --git a/compiler/rustc_driver/src/session_diagnostics.rs b/compiler/rustc_driver/src/session_diagnostics.rs
index c1bc10891144c..a7aef9cbc2c8a 100644
--- a/compiler/rustc_driver/src/session_diagnostics.rs
+++ b/compiler/rustc_driver/src/session_diagnostics.rs
@@ -38,3 +38,30 @@ pub(crate) struct UnprettyDumpFail {
     pub path: String,
     pub err: String,
 }
+
+#[derive(Diagnostic)]
+#[diag(driver_ice)]
+pub(crate) struct Ice;
+
+#[derive(Diagnostic)]
+#[diag(driver_ice_bug_report)]
+pub(crate) struct IceBugReport<'a> {
+    pub bug_report_url: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(driver_ice_version)]
+pub(crate) struct IceVersion<'a> {
+    pub version: &'a str,
+    pub triple: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(driver_ice_flags)]
+pub(crate) struct IceFlags {
+    pub flags: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(driver_ice_exclude_cargo_defaults)]
+pub(crate) struct IceExcludeCargoDefaults;
diff --git a/compiler/rustc_error_messages/locales/en-US/codegen_llvm.ftl b/compiler/rustc_error_messages/locales/en-US/codegen_llvm.ftl
index b82c903290b9a..e5df417370bb9 100644
--- a/compiler/rustc_error_messages/locales/en-US/codegen_llvm.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/codegen_llvm.ftl
@@ -39,5 +39,51 @@ codegen_llvm_dynamic_linking_with_lto =
     cannot prefer dynamic linking when performing LTO
     .note = only 'staticlib', 'bin', and 'cdylib' outputs are supported with LTO
 
-codegen_llvm_fail_parsing_target_machine_config_to_target_machine =
+codegen_llvm_parse_target_machine_config =
     failed to parse target machine config to target machine: {$error}
+
+codegen_llvm_lto_disallowed = lto can only be run for executables, cdylibs and static library outputs
+
+codegen_llvm_lto_dylib = lto cannot be used for `dylib` crate type without `-Zdylib-lto`
+
+codegen_llvm_lto_bitcode_from_rlib = failed to get bitcode from object file for LTO ({$llvm_err})
+
+codegen_llvm_write_output = could not write output to {$path}
+codegen_llvm_write_output_with_llvm_err = could not write output to {$path}: {$llvm_err}
+
+codegen_llvm_target_machine = could not create LLVM TargetMachine for triple: {$triple}
+codegen_llvm_target_machine_with_llvm_err = could not create LLVM TargetMachine for triple: {$triple}: {$llvm_err}
+
+codegen_llvm_run_passes = failed to run LLVM passes
+codegen_llvm_run_passes_with_llvm_err = failed to run LLVM passes: {$llvm_err}
+
+codegen_llvm_serialize_module = failed to serialize module {$name}
+codegen_llvm_serialize_module_with_llvm_err = failed to serialize module {$name}: {$llvm_err}
+
+codegen_llvm_write_ir = failed to write LLVM IR to {$path}
+codegen_llvm_write_ir_with_llvm_err = failed to write LLVM IR to {$path}: {$llvm_err}
+
+codegen_llvm_prepare_thin_lto_context = failed to prepare thin LTO context
+codegen_llvm_prepare_thin_lto_context_with_llvm_err = failed to prepare thin LTO context: {$llvm_err}
+
+codegen_llvm_load_bitcode = failed to load bitcode of module "{$name}"
+codegen_llvm_load_bitcode_with_llvm_err = failed to load bitcode of module "{$name}": {$llvm_err}
+
+codegen_llvm_write_thinlto_key = error while writing ThinLTO key data: {$err}
+codegen_llvm_write_thinlto_key_with_llvm_err = error while writing ThinLTO key data: {$err}: {$llvm_err}
+
+codegen_llvm_multiple_source_dicompileunit = multiple source DICompileUnits found
+codegen_llvm_multiple_source_dicompileunit_with_llvm_err = multiple source DICompileUnits found: {$llvm_err}
+
+codegen_llvm_prepare_thin_lto_module = failed to prepare thin LTO module
+codegen_llvm_prepare_thin_lto_module_with_llvm_err = failed to prepare thin LTO module: {$llvm_err}
+
+codegen_llvm_parse_bitcode = failed to parse bitcode for LTO module
+codegen_llvm_parse_bitcode_with_llvm_err = failed to parse bitcode for LTO module: {$llvm_err}
+
+codegen_llvm_from_llvm_optimization_diag = {$filename}:{$line}:{$column} {$pass_name}: {$message}
+codegen_llvm_from_llvm_diag = {$message}
+
+codegen_llvm_write_bytecode = failed to write bytecode to {$path}: {$err}
+
+codegen_llvm_copy_bitcode = failed to copy bitcode to object file: {$err}
diff --git a/compiler/rustc_error_messages/locales/en-US/driver.ftl b/compiler/rustc_error_messages/locales/en-US/driver.ftl
index 8ad198c86c933..79ffc82c6c67d 100644
--- a/compiler/rustc_error_messages/locales/en-US/driver.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/driver.ftl
@@ -11,3 +11,9 @@ driver_rlink_rustc_version_mismatch = .rlink file was produced by rustc version
 driver_rlink_no_a_file = rlink must be a file
 
 driver_unpretty_dump_fail = pretty-print failed to write `{$path}` due to error `{$err}`
+
+driver_ice = the compiler unexpectedly panicked. this is a bug.
+driver_ice_bug_report = we would appreciate a bug report: {$bug_report_url}
+driver_ice_version = rustc {$version} running on {$triple}
+driver_ice_flags = compiler flags: {$flags}
+driver_ice_exclude_cargo_defaults = some of the compiler flags provided by cargo are hidden
diff --git a/compiler/rustc_error_messages/locales/en-US/expand.ftl b/compiler/rustc_error_messages/locales/en-US/expand.ftl
index df0e8ae5dd8f5..dbd80954382db 100644
--- a/compiler/rustc_error_messages/locales/en-US/expand.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/expand.ftl
@@ -127,3 +127,5 @@ expand_module_file_not_found =
 expand_module_multiple_candidates =
     file for module `{$name}` found at both "{$default_path}" and "{$secondary_path}"
     .help = delete or rename one of them to remove the ambiguity
+
+expand_trace_macro = trace_macro
diff --git a/compiler/rustc_error_messages/locales/en-US/incremental.ftl b/compiler/rustc_error_messages/locales/en-US/incremental.ftl
new file mode 100644
index 0000000000000..4852ee0d9595c
--- /dev/null
+++ b/compiler/rustc_error_messages/locales/en-US/incremental.ftl
@@ -0,0 +1,118 @@
+incremental_unrecognized_depnode = unrecognized `DepNode` variant: {$name}
+
+incremental_missing_depnode = missing `DepNode` variant
+
+incremental_missing_if_this_changed = no `#[rustc_if_this_changed]` annotation detected
+
+incremental_no_path = no path from `{$source}` to `{$target}`
+
+incremental_ok = OK
+
+incremental_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified
+
+incremental_missing_query_depgraph =
+    found CGU-reuse attribute but `-Zquery-dep-graph` was not specified
+
+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_no_module_named =
+    no module named `{$user_path}` (mangled: {$cgu_name}). available modules: {$cgu_names}
+
+incremental_field_associated_value_expected = associated value expected for `{$name}`
+
+incremental_no_field = no field `{$name}`
+
+incremental_assertion_auto =
+    `except` specified DepNodes that can not be affected for \"{$name}\": \"{$e}\"
+
+incremental_undefined_clean_dirty_assertions_item =
+    clean/dirty auto-assertions not yet defined for Node::Item.node={$kind}
+
+incremental_undefined_clean_dirty_assertions =
+    clean/dirty auto-assertions not yet defined for {$kind}
+
+incremental_repeated_depnode_label = dep-node label `{$label}` is repeated
+
+incremental_unrecognized_depnode_label = dep-node label `{$label}` not recognized
+
+incremental_not_dirty = `{$dep_node_str}` should be dirty but is not
+
+incremental_not_clean = `{$dep_node_str}` should be clean but is not
+
+incremental_not_loaded = `{$dep_node_str}` should have been loaded from disk but it was not
+
+incremental_unknown_item = unknown item `{$name}`
+
+incremental_no_cfg = no cfg attribute
+
+incremental_associated_value_expected_for = associated value expected for `{$ident}`
+
+incremental_associated_value_expected = expected an associated value
+
+incremental_unchecked_clean = found unchecked `#[rustc_clean]` attribute
+
+incremental_delete_old = unable to delete old {$name} at `{$path}`: {$err}
+
+incremental_create_new = failed to create {$name} at `{$path}`: {$err}
+
+incremental_write_new = failed to write {$name} to `{$path}`: {$err}
+
+incremental_canonicalize_path = incremental compilation: error canonicalizing path `{$path}`: {$err}
+
+incremental_create_incr_comp_dir =
+    could not create incremental compilation {$tag} directory `{$path}`: {$err}
+
+incremental_create_lock =
+    incremental compilation: could not create session directory lock file: {$lock_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_cargo_help_1 =
+    incremental compilation can be disabled by setting the environment variable CARGO_INCREMENTAL=0 (see https://doc.rust-lang.org/cargo/reference/profiles.html#incremental)
+incremental_cargo_help_2 =
+    the entire build directory can be changed to a different filesystem by setting the environment variable CARGO_TARGET_DIR to a different path (see https://doc.rust-lang.org/cargo/reference/config.html#buildtarget-dir)
+
+incremental_delete_lock =
+    error deleting lock file for incremental compilation session directory `{$path}`: {$err}
+
+incremental_hard_link_failed =
+    hard linking files in the incremental compilation cache failed. copying files instead. consider moving the cache directory to a file system which supports hard linking in session dir `{$path}`
+
+incremental_delete_partial = failed to delete partly initialized session dir `{$path}`: {$err}
+
+incremental_delete_full = error deleting incremental compilation session directory `{$path}`: {$err}
+
+incremental_finalize = error finalizing incremental compilation session directory `{$path}`: {$err}
+
+incremental_invalid_gc_failed =
+    failed to garbage collect invalid incremental compilation session directory `{$path}`: {$err}
+
+incremental_finalized_gc_failed =
+    failed to garbage collect finalized incremental compilation session directory `{$path}`: {$err}
+
+incremental_session_gc_failed =
+    failed to garbage collect incremental compilation session directory `{$path}`: {$err}
+
+incremental_assert_not_loaded =
+    we asserted that the incremental cache should not be loaded, but it was loaded
+
+incremental_assert_loaded =
+    we asserted that an existing incremental cache directory should be successfully loaded, but it was not
+
+incremental_delete_incompatible =
+    failed to delete invalidated or incompatible incremental compilation session directory contents `{$path}`: {$err}
+
+incremental_load_dep_graph = could not load dep-graph from `{$path}`: {$err}
+
+incremental_decode_incr_cache = could not decode incremental cache: {$err}
+
+incremental_write_dep_graph = failed to write dependency graph to `{$path}`: {$err}
+
+incremental_move_dep_graph = failed to move dependency graph from `{$from}` to `{$to}`: {$err}
+
+incremental_create_dep_graph = failed to create dependency graph at `{$path}`: {$err}
+
+incremental_copy_workproduct_to_cache =
+    error copying object file `{$from}` to incremental directory as `{$to}`: {$err}
+
+incremental_delete_workproduct = file-system error deleting outdated file `{$path}`: {$err}
diff --git a/compiler/rustc_error_messages/locales/en-US/interface.ftl b/compiler/rustc_error_messages/locales/en-US/interface.ftl
index 688b044722260..a7bc0e7af1fe9 100644
--- a/compiler/rustc_error_messages/locales/en-US/interface.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/interface.ftl
@@ -44,3 +44,13 @@ interface_failed_writing_file =
 
 interface_proc_macro_crate_panic_abort =
     building proc macro crate with `panic=abort` may crash the compiler should the proc-macro panic
+
+interface_unsupported_crate_type_for_target =
+    dropping unsupported crate type `{$crate_type}` for target `{$target_triple}`
+
+interface_multiple_output_types_adaption =
+    due to multiple output types requested, the explicitly specified output file name will be adapted for each output type
+
+interface_ignoring_extra_filename = ignoring -C extra-filename flag due to -o flag
+
+interface_ignoring_out_dir = ignoring --out-dir flag due to -o flag
diff --git a/compiler/rustc_error_messages/locales/en-US/monomorphize.ftl b/compiler/rustc_error_messages/locales/en-US/monomorphize.ftl
index 243d10bfa0621..6cea6a603f336 100644
--- a/compiler/rustc_error_messages/locales/en-US/monomorphize.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/monomorphize.ftl
@@ -24,3 +24,9 @@ monomorphize_large_assignments =
 
 monomorphize_couldnt_dump_mono_stats =
     unexpected error occurred while dumping monomorphization stats: {$error}
+
+monomorphize_encountered_error_while_instantiating =
+    the above error was encountered while instantiating `{$formatted_item}`
+
+monomorphize_unknown_cgu_collection_mode =
+    unknown codegen-item collection mode '{$mode}', falling back to 'lazy' mode
diff --git a/compiler/rustc_error_messages/locales/en-US/passes.ftl b/compiler/rustc_error_messages/locales/en-US/passes.ftl
index 0c2ab3d08f9de..6ebb188288f00 100644
--- a/compiler/rustc_error_messages/locales/en-US/passes.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/passes.ftl
@@ -731,3 +731,5 @@ passes_proc_macro_missing_args = mismatched {$kind} signature
 passes_proc_macro_invalid_abi = proc macro functions may not be `extern "{$abi}"`
 
 passes_proc_macro_unsafe = proc macro functions may not be `unsafe`
+
+passes_skipping_const_checks = skipping const checks
diff --git a/compiler/rustc_error_messages/locales/en-US/session.ftl b/compiler/rustc_error_messages/locales/en-US/session.ftl
index bc37d91a7c6af..5984c201af0d0 100644
--- a/compiler/rustc_error_messages/locales/en-US/session.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/session.ftl
@@ -89,3 +89,5 @@ session_int_literal_too_large = integer literal is too large
 
 session_invalid_int_literal_width = invalid width `{$width}` for integer literal
     .help = valid widths are 8, 16, 32, 64 and 128
+
+session_optimization_fuel_exhausted = optimization-fuel-exhausted: {$msg}
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index f053bdc3809be..1882d4b698e61 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -52,6 +52,7 @@ fluent_messages! {
     expand => "../locales/en-US/expand.ftl",
     hir_analysis => "../locales/en-US/hir_analysis.ftl",
     hir_typeck => "../locales/en-US/hir_typeck.ftl",
+    incremental => "../locales/en-US/incremental.ftl",
     infer => "../locales/en-US/infer.ftl",
     interface => "../locales/en-US/interface.ftl",
     lint => "../locales/en-US/lint.ftl",
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 4ad24c1400d69..df949e46fbde5 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -1051,6 +1051,7 @@ impl Diagnostic {
     ) -> (
         &Level,
         &[(DiagnosticMessage, Style)],
+        Vec<(&Cow<'static, str>, &DiagnosticArgValue<'static>)>,
         &Option<DiagnosticId>,
         &MultiSpan,
         &Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
@@ -1059,6 +1060,7 @@ impl Diagnostic {
         (
             &self.level,
             &self.message,
+            self.args().collect(),
             &self.code,
             &self.span,
             &self.suggestions,
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index cbfee582d871f..c9d662ad43fe5 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -408,6 +408,59 @@ impl EmissionGuarantee for ! {
     }
 }
 
+impl<'a> DiagnosticBuilder<'a, rustc_span::fatal_error::FatalError> {
+    /// Convenience function for internal use, clients should use one of the
+    /// `struct_*` methods on [`Handler`].
+    #[track_caller]
+    pub(crate) fn new_almost_fatal(
+        handler: &'a Handler,
+        message: impl Into<DiagnosticMessage>,
+    ) -> Self {
+        let diagnostic = Diagnostic::new_with_code(Level::Fatal, None, message);
+        Self::new_diagnostic_almost_fatal(handler, diagnostic)
+    }
+
+    /// Creates a new `DiagnosticBuilder` with an already constructed
+    /// diagnostic.
+    pub(crate) fn new_diagnostic_almost_fatal(
+        handler: &'a Handler,
+        diagnostic: Diagnostic,
+    ) -> Self {
+        debug!("Created new diagnostic");
+        Self {
+            inner: DiagnosticBuilderInner {
+                state: DiagnosticBuilderState::Emittable(handler),
+                diagnostic: Box::new(diagnostic),
+            },
+            _marker: PhantomData,
+        }
+    }
+}
+
+impl EmissionGuarantee for rustc_span::fatal_error::FatalError {
+    fn diagnostic_builder_emit_producing_guarantee(db: &mut DiagnosticBuilder<'_, Self>) -> Self {
+        match db.inner.state {
+            // First `.emit()` call, the `&Handler` is still available.
+            DiagnosticBuilderState::Emittable(handler) => {
+                db.inner.state = DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation;
+
+                handler.emit_diagnostic(&mut db.inner.diagnostic);
+            }
+            // `.emit()` was previously called, disallowed from repeating it.
+            DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => {}
+        }
+        // Then fatally error..
+        rustc_span::fatal_error::FatalError
+    }
+
+    fn make_diagnostic_builder(
+        handler: &Handler,
+        msg: impl Into<DiagnosticMessage>,
+    ) -> DiagnosticBuilder<'_, Self> {
+        DiagnosticBuilder::new_almost_fatal(handler, msg)
+    }
+}
+
 /// In general, the `DiagnosticBuilder` uses deref to allow access to
 /// the fields and methods of the embedded `diagnostic` in a
 /// transparent way. *However,* many of the methods are intended to
diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs
index dad5e98aac021..7a94ce3777a29 100644
--- a/compiler/rustc_errors/src/diagnostic_impls.rs
+++ b/compiler/rustc_errors/src/diagnostic_impls.rs
@@ -36,6 +36,12 @@ impl<'a, T: fmt::Display> From<&'a T> for DiagnosticArgFromDisplay<'a> {
     }
 }
 
+impl<'a, T: Clone + IntoDiagnosticArg> IntoDiagnosticArg for &'a T {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        self.clone().into_diagnostic_arg()
+    }
+}
+
 macro_rules! into_diagnostic_arg_using_display {
     ($( $ty:ty ),+ $(,)?) => {
         $(
@@ -153,12 +159,6 @@ impl IntoDiagnosticArg for ast::Path {
     }
 }
 
-impl IntoDiagnosticArg for &ast::Path {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(pprust::path_to_string(self)))
-    }
-}
-
 impl IntoDiagnosticArg for ast::token::Token {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         DiagnosticArgValue::Str(pprust::token_to_string(&self))
@@ -177,6 +177,18 @@ impl IntoDiagnosticArg for type_ir::FloatTy {
     }
 }
 
+impl IntoDiagnosticArg for std::ffi::CString {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string_lossy().into_owned()))
+    }
+}
+
+impl IntoDiagnosticArg for rustc_data_structures::small_c_str::SmallCStr {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        DiagnosticArgValue::Str(Cow::Owned(self.to_string_lossy().into_owned()))
+    }
+}
+
 impl IntoDiagnosticArg for Level {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         DiagnosticArgValue::Str(Cow::Borrowed(self.to_cmd_flag()))
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index d076fc08b0e2f..ec04e865d53b1 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -617,22 +617,24 @@ impl Handler {
         }
     }
 
-    /// Translate `message` eagerly with `args`.
+    /// Translate `message` eagerly with `args` to `SubdiagnosticMessage::Eager`.
     pub fn eagerly_translate<'a>(
         &self,
         message: DiagnosticMessage,
         args: impl Iterator<Item = DiagnosticArg<'a, 'static>>,
     ) -> SubdiagnosticMessage {
+        SubdiagnosticMessage::Eager(self.eagerly_translate_to_string(message, args))
+    }
+
+    /// Translate `message` eagerly with `args` to `String`.
+    pub fn eagerly_translate_to_string<'a>(
+        &self,
+        message: DiagnosticMessage,
+        args: impl Iterator<Item = DiagnosticArg<'a, 'static>>,
+    ) -> String {
         let inner = self.inner.borrow();
         let args = crate::translation::to_fluent_args(args);
-        SubdiagnosticMessage::Eager(
-            inner
-                .emitter
-                .translate_message(&message, &args)
-                .map_err(Report::new)
-                .unwrap()
-                .to_string(),
-        )
+        inner.emitter.translate_message(&message, &args).map_err(Report::new).unwrap().to_string()
     }
 
     // This is here to not allow mutation of flags;
@@ -1010,6 +1012,7 @@ impl Handler {
     }
 
     #[track_caller]
+    #[rustc_lint_diagnostics]
     pub fn span_note_without_error(
         &self,
         span: impl Into<MultiSpan>,
@@ -1019,6 +1022,7 @@ impl Handler {
     }
 
     #[track_caller]
+    #[rustc_lint_diagnostics]
     pub fn span_note_diag(
         &self,
         span: Span,
@@ -1030,19 +1034,23 @@ impl Handler {
     }
 
     // NOTE: intentionally doesn't raise an error so rustc_codegen_ssa only reports fatal errors in the main thread
+    #[rustc_lint_diagnostics]
     pub fn fatal(&self, msg: impl Into<DiagnosticMessage>) -> FatalError {
         self.inner.borrow_mut().fatal(msg)
     }
 
+    #[rustc_lint_diagnostics]
     pub fn err(&self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
         self.inner.borrow_mut().err(msg)
     }
 
+    #[rustc_lint_diagnostics]
     pub fn warn(&self, msg: impl Into<DiagnosticMessage>) {
         let mut db = DiagnosticBuilder::new(self, Warning(None), msg);
         db.emit();
     }
 
+    #[rustc_lint_diagnostics]
     pub fn note_without_error(&self, msg: impl Into<DiagnosticMessage>) {
         DiagnosticBuilder::new(self, Note, msg).emit();
     }
@@ -1059,6 +1067,7 @@ impl Handler {
     pub fn has_errors(&self) -> Option<ErrorGuaranteed> {
         if self.inner.borrow().has_errors() { Some(ErrorGuaranteed(())) } else { None }
     }
+
     pub fn has_errors_or_lint_errors(&self) -> Option<ErrorGuaranteed> {
         if self.inner.borrow().has_errors_or_lint_errors() {
             Some(ErrorGuaranteed::unchecked_claim_error_was_emitted())
@@ -1132,6 +1141,20 @@ impl Handler {
         self.create_warning(warning).emit()
     }
 
+    pub fn create_almost_fatal<'a>(
+        &'a self,
+        fatal: impl IntoDiagnostic<'a, FatalError>,
+    ) -> DiagnosticBuilder<'a, FatalError> {
+        fatal.into_diagnostic(self)
+    }
+
+    pub fn emit_almost_fatal<'a>(
+        &'a self,
+        fatal: impl IntoDiagnostic<'a, FatalError>,
+    ) -> FatalError {
+        self.create_almost_fatal(fatal).emit()
+    }
+
     pub fn create_fatal<'a>(
         &'a self,
         fatal: impl IntoDiagnostic<'a, !>,
@@ -1157,6 +1180,17 @@ impl Handler {
         self.create_bug(bug).emit()
     }
 
+    pub fn emit_note<'a>(&'a self, note: impl IntoDiagnostic<'a, Noted>) -> Noted {
+        self.create_note(note).emit()
+    }
+
+    pub fn create_note<'a>(
+        &'a self,
+        note: impl IntoDiagnostic<'a, Noted>,
+    ) -> DiagnosticBuilder<'a, Noted> {
+        note.into_diagnostic(self)
+    }
+
     fn emit_diag_at_span(
         &self,
         mut diag: Diagnostic,
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 951d59246785d..5a48473d5b07c 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -4,7 +4,7 @@ use crate::errors::{
     ArgumentNotAttributes, AttrNoArguments, AttributeMetaItem, AttributeSingleWord,
     AttributesWrongForm, CannotBeNameOfMacro, ExpectedCommaInList, HelperAttributeNameInvalid,
     MacroBodyStability, MacroConstStability, NotAMetaItem, OnlyOneArgument, OnlyOneWord,
-    ResolveRelativePath, TakesNoArguments,
+    ResolveRelativePath, TakesNoArguments, TraceMacro,
 };
 use crate::expand::{self, AstFragment, Invocation};
 use crate::module::DirOwnership;
@@ -1142,8 +1142,8 @@ impl<'a> ExtCtxt<'a> {
         self.sess.parse_sess.span_diagnostic.span_bug(sp, msg);
     }
     pub fn trace_macros_diag(&mut self) {
-        for (sp, notes) in self.expansions.iter() {
-            let mut db = self.sess.parse_sess.span_diagnostic.span_note_diag(*sp, "trace_macro");
+        for (span, notes) in self.expansions.iter() {
+            let mut db = self.sess.parse_sess.create_note(TraceMacro { span: *span });
             for note in notes {
                 db.note(note);
             }
diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs
index afe5169d3f5c0..9b9697ab13d26 100644
--- a/compiler/rustc_expand/src/errors.rs
+++ b/compiler/rustc_expand/src/errors.rs
@@ -368,3 +368,10 @@ pub(crate) struct ModuleMultipleCandidates {
     pub default_path: String,
     pub secondary_path: String,
 }
+
+#[derive(Diagnostic)]
+#[diag(expand_trace_macro)]
+pub struct TraceMacro {
+    #[primary_span]
+    pub span: Span,
+}
diff --git a/compiler/rustc_incremental/src/assert_dep_graph.rs b/compiler/rustc_incremental/src/assert_dep_graph.rs
index 6703d53f3805c..22bd12f2e6361 100644
--- a/compiler/rustc_incremental/src/assert_dep_graph.rs
+++ b/compiler/rustc_incremental/src/assert_dep_graph.rs
@@ -33,6 +33,7 @@
 //! fn baz() { foo(); }
 //! ```
 
+use crate::errors;
 use rustc_ast as ast;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::graph::implementation::{Direction, NodeIndex, INCOMING, OUTGOING};
@@ -133,12 +134,10 @@ impl<'tcx> IfThisChanged<'tcx> {
                     Some(n) => {
                         match DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash) {
                             Ok(n) => n,
-                            Err(()) => {
-                                self.tcx.sess.span_fatal(
-                                    attr.span,
-                                    &format!("unrecognized DepNode variant {:?}", n),
-                                );
-                            }
+                            Err(()) => self.tcx.sess.emit_fatal(errors::UnrecognizedDepNode {
+                                span: attr.span,
+                                name: n,
+                            }),
                         }
                     }
                 };
@@ -149,16 +148,14 @@ impl<'tcx> IfThisChanged<'tcx> {
                     Some(n) => {
                         match DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash) {
                             Ok(n) => n,
-                            Err(()) => {
-                                self.tcx.sess.span_fatal(
-                                    attr.span,
-                                    &format!("unrecognized DepNode variant {:?}", n),
-                                );
-                            }
+                            Err(()) => self.tcx.sess.emit_fatal(errors::UnrecognizedDepNode {
+                                span: attr.span,
+                                name: n,
+                            }),
                         }
                     }
                     None => {
-                        self.tcx.sess.span_fatal(attr.span, "missing DepNode variant");
+                        self.tcx.sess.emit_fatal(errors::MissingDepNode { span: attr.span });
                     }
                 };
                 self.then_this_would_need.push((
@@ -204,7 +201,7 @@ fn check_paths<'tcx>(tcx: TyCtxt<'tcx>, if_this_changed: &Sources, then_this_wou
     // Return early here so as not to construct the query, which is not cheap.
     if if_this_changed.is_empty() {
         for &(target_span, _, _, _) in then_this_would_need {
-            tcx.sess.span_err(target_span, "no `#[rustc_if_this_changed]` annotation detected");
+            tcx.sess.emit_err(errors::MissingIfThisChanged { span: target_span });
         }
         return;
     }
@@ -213,16 +210,13 @@ fn check_paths<'tcx>(tcx: TyCtxt<'tcx>, if_this_changed: &Sources, then_this_wou
             let dependents = query.transitive_predecessors(source_dep_node);
             for &(target_span, ref target_pass, _, ref target_dep_node) in then_this_would_need {
                 if !dependents.contains(&target_dep_node) {
-                    tcx.sess.span_err(
-                        target_span,
-                        &format!(
-                            "no path from `{}` to `{}`",
-                            tcx.def_path_str(source_def_id),
-                            target_pass
-                        ),
-                    );
+                    tcx.sess.emit_err(errors::NoPath {
+                        span: target_span,
+                        source: tcx.def_path_str(source_def_id),
+                        target: *target_pass,
+                    });
                 } else {
-                    tcx.sess.span_err(target_span, "OK");
+                    tcx.sess.emit_err(errors::Ok { span: target_span });
                 }
             }
         }
diff --git a/compiler/rustc_incremental/src/assert_module_sources.rs b/compiler/rustc_incremental/src/assert_module_sources.rs
index 89d419bc8e90f..2968a0e1203a9 100644
--- a/compiler/rustc_incremental/src/assert_module_sources.rs
+++ b/compiler/rustc_incremental/src/assert_module_sources.rs
@@ -22,6 +22,7 @@
 //! 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::FxHashSet;
 use rustc_hir::def_id::LOCAL_CRATE;
@@ -66,10 +67,9 @@ impl<'tcx> AssertModuleSource<'tcx> {
                 sym::post_dash_lto => (CguReuse::PostLto, ComparisonKind::Exact),
                 sym::any => (CguReuse::PreLto, ComparisonKind::AtLeast),
                 other => {
-                    self.tcx.sess.span_fatal(
-                        attr.span,
-                        &format!("unknown cgu-reuse-kind `{}` specified", other),
-                    );
+                    self.tcx
+                        .sess
+                        .emit_fatal(errors::UnknownReuseKind { span: attr.span, kind: other });
                 }
             }
         } else {
@@ -77,10 +77,7 @@ impl<'tcx> AssertModuleSource<'tcx> {
         };
 
         if !self.tcx.sess.opts.unstable_opts.query_dep_graph {
-            self.tcx.sess.span_fatal(
-                attr.span,
-                "found CGU-reuse attribute but `-Zquery-dep-graph` was not specified",
-            );
+            self.tcx.sess.emit_fatal(errors::MissingQueryDepGraph { span: attr.span });
         }
 
         if !self.check_config(attr) {
@@ -92,13 +89,11 @@ impl<'tcx> AssertModuleSource<'tcx> {
         let crate_name = self.tcx.crate_name(LOCAL_CRATE).to_string();
 
         if !user_path.starts_with(&crate_name) {
-            let msg = format!(
-                "Found malformed codegen unit name `{}`. \
-                Codegen units names must always start with the name of the \
-                crate (`{}` in this case).",
-                user_path, crate_name
-            );
-            self.tcx.sess.span_fatal(attr.span, &msg);
+            self.tcx.sess.emit_fatal(errors::MalformedCguName {
+                span: attr.span,
+                user_path,
+                crate_name,
+            });
         }
 
         // Split of the "special suffix" if there is one.
@@ -125,15 +120,12 @@ impl<'tcx> AssertModuleSource<'tcx> {
             let mut cgu_names: Vec<&str> =
                 self.available_cgus.iter().map(|cgu| cgu.as_str()).collect();
             cgu_names.sort();
-            self.tcx.sess.span_err(
-                attr.span,
-                &format!(
-                    "no module named `{}` (mangled: {}). Available modules: {}",
-                    user_path,
-                    cgu_name,
-                    cgu_names.join(", ")
-                ),
-            );
+            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(
@@ -151,15 +143,15 @@ impl<'tcx> AssertModuleSource<'tcx> {
                 if let Some(value) = item.value_str() {
                     return value;
                 } else {
-                    self.tcx.sess.span_fatal(
-                        item.span(),
-                        &format!("associated value expected for `{}`", name),
-                    );
+                    self.tcx.sess.emit_fatal(errors::FieldAssociatedValueExpected {
+                        span: item.span(),
+                        name,
+                    });
                 }
             }
         }
 
-        self.tcx.sess.span_fatal(attr.span, &format!("no field `{}`", name));
+        self.tcx.sess.emit_fatal(errors::NoField { span: attr.span, name });
     }
 
     /// Scan for a `cfg="foo"` attribute and check whether we have a
diff --git a/compiler/rustc_incremental/src/errors.rs b/compiler/rustc_incremental/src/errors.rs
new file mode 100644
index 0000000000000..deb8767836543
--- /dev/null
+++ b/compiler/rustc_incremental/src/errors.rs
@@ -0,0 +1,364 @@
+use rustc_macros::Diagnostic;
+use rustc_span::{symbol::Ident, Span, Symbol};
+use std::path::{Path, PathBuf};
+
+#[derive(Diagnostic)]
+#[diag(incremental_unrecognized_depnode)]
+pub struct UnrecognizedDepNode {
+    #[primary_span]
+    pub span: Span,
+    pub name: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_missing_depnode)]
+pub struct MissingDepNode {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_missing_if_this_changed)]
+pub struct MissingIfThisChanged {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_ok)]
+pub struct Ok {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_no_path)]
+pub struct NoPath {
+    #[primary_span]
+    pub span: Span,
+    pub target: Symbol,
+    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> {
+    #[primary_span]
+    pub span: Span,
+    pub name: &'a str,
+    pub e: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_undefined_clean_dirty_assertions_item)]
+pub struct UndefinedCleanDirtyItem {
+    #[primary_span]
+    pub span: Span,
+    pub kind: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_undefined_clean_dirty_assertions)]
+pub struct UndefinedCleanDirty {
+    #[primary_span]
+    pub span: Span,
+    pub kind: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_repeated_depnode_label)]
+pub struct RepeatedDepNodeLabel<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub label: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_unrecognized_depnode_label)]
+pub struct UnrecognizedDepNodeLabel<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub label: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_not_dirty)]
+pub struct NotDirty<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub dep_node_str: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_not_clean)]
+pub struct NotClean<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub dep_node_str: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_not_loaded)]
+pub struct NotLoaded<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub dep_node_str: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_unknown_item)]
+pub struct UnknownItem {
+    #[primary_span]
+    pub span: Span,
+    pub name: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_no_cfg)]
+pub struct NoCfg {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_associated_value_expected_for)]
+pub struct AssociatedValueExpectedFor {
+    #[primary_span]
+    pub span: Span,
+    pub ident: Ident,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_associated_value_expected)]
+pub struct AssociatedValueExpected {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_unchecked_clean)]
+pub struct UncheckedClean {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_delete_old)]
+pub struct DeleteOld<'a> {
+    pub name: &'a str,
+    pub path: PathBuf,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_create_new)]
+pub struct CreateNew<'a> {
+    pub name: &'a str,
+    pub path: PathBuf,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_write_new)]
+pub struct WriteNew<'a> {
+    pub name: &'a str,
+    pub path: PathBuf,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_canonicalize_path)]
+pub struct CanonicalizePath {
+    pub path: PathBuf,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_create_incr_comp_dir)]
+pub struct CreateIncrCompDir<'a> {
+    pub tag: &'a str,
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_create_lock)]
+pub struct CreateLock<'a> {
+    pub lock_err: std::io::Error,
+    pub session_dir: &'a Path,
+    #[note(incremental_lock_unsupported)]
+    pub is_unsupported_lock: Option<()>,
+    #[help(incremental_cargo_help_1)]
+    #[help(incremental_cargo_help_2)]
+    pub is_cargo: Option<()>,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_delete_lock)]
+pub struct DeleteLock<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_hard_link_failed)]
+pub struct HardLinkFailed<'a> {
+    pub path: &'a Path,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_delete_partial)]
+pub struct DeletePartial<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_delete_full)]
+pub struct DeleteFull<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_finalize)]
+pub struct Finalize<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_invalid_gc_failed)]
+pub struct InvalidGcFailed<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_finalized_gc_failed)]
+pub struct FinalizedGcFailed<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_session_gc_failed)]
+pub struct SessionGcFailed<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_assert_not_loaded)]
+pub struct AssertNotLoaded;
+
+#[derive(Diagnostic)]
+#[diag(incremental_assert_loaded)]
+pub struct AssertLoaded;
+
+#[derive(Diagnostic)]
+#[diag(incremental_delete_incompatible)]
+pub struct DeleteIncompatible {
+    pub path: PathBuf,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_load_dep_graph)]
+pub struct LoadDepGraph {
+    pub path: PathBuf,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_decode_incr_cache)]
+pub struct DecodeIncrCache {
+    pub err: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_write_dep_graph)]
+pub struct WriteDepGraph<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_move_dep_graph)]
+pub struct MoveDepGraph<'a> {
+    pub from: &'a Path,
+    pub to: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_create_dep_graph)]
+pub struct CreateDepGraph<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_copy_workproduct_to_cache)]
+pub struct CopyWorkProductToCache<'a> {
+    pub from: &'a Path,
+    pub to: &'a Path,
+    pub err: std::io::Error,
+}
+
+#[derive(Diagnostic)]
+#[diag(incremental_delete_workproduct)]
+pub struct DeleteWorkProduct<'a> {
+    pub path: &'a Path,
+    pub err: std::io::Error,
+}
diff --git a/compiler/rustc_incremental/src/lib.rs b/compiler/rustc_incremental/src/lib.rs
index 83dd9a67e61bc..3c58cfa38f280 100644
--- a/compiler/rustc_incremental/src/lib.rs
+++ b/compiler/rustc_incremental/src/lib.rs
@@ -2,8 +2,11 @@
 
 #![deny(missing_docs)]
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![feature(never_type)]
 #![recursion_limit = "256"]
 #![allow(rustc::potential_query_instability)]
+#![deny(rustc::untranslatable_diagnostic)]
+#![deny(rustc::diagnostic_outside_of_impl)]
 
 #[macro_use]
 extern crate rustc_middle;
@@ -12,6 +15,7 @@ extern crate tracing;
 
 mod assert_dep_graph;
 pub mod assert_module_sources;
+mod errors;
 mod persist;
 
 use assert_dep_graph::assert_dep_graph;
diff --git a/compiler/rustc_incremental/src/persist/dirty_clean.rs b/compiler/rustc_incremental/src/persist/dirty_clean.rs
index ed7b272b13d17..c6e63998c7935 100644
--- a/compiler/rustc_incremental/src/persist/dirty_clean.rs
+++ b/compiler/rustc_incremental/src/persist/dirty_clean.rs
@@ -19,6 +19,7 @@
 //! Errors are reported if we are in the suitable configuration but
 //! the required condition is not met.
 
+use crate::errors;
 use rustc_ast::{self as ast, Attribute, NestedMetaItem};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::def_id::LocalDefId;
@@ -196,11 +197,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
         let loaded_from_disk = self.loaded_from_disk(attr);
         for e in except.iter() {
             if !auto.remove(e) {
-                let msg = format!(
-                    "`except` specified DepNodes that can not be affected for \"{}\": \"{}\"",
-                    name, e
-                );
-                self.tcx.sess.span_fatal(attr.span, &msg);
+                self.tcx.sess.emit_fatal(errors::AssertionAuto { span: attr.span, name, e });
             }
         }
         Assertion { clean: auto, dirty: except, loaded_from_disk }
@@ -282,14 +279,10 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
                     // An implementation, eg `impl<A> Trait for Foo { .. }`
                     HirItem::Impl { .. } => ("ItemKind::Impl", LABELS_IMPL),
 
-                    _ => self.tcx.sess.span_fatal(
-                        attr.span,
-                        &format!(
-                            "clean/dirty auto-assertions not yet defined \
-                             for Node::Item.node={:?}",
-                            item.kind
-                        ),
-                    ),
+                    _ => self.tcx.sess.emit_fatal(errors::UndefinedCleanDirtyItem {
+                        span: attr.span,
+                        kind: format!("{:?}", item.kind),
+                    }),
                 }
             }
             HirNode::TraitItem(item) => match item.kind {
@@ -302,10 +295,10 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
                 ImplItemKind::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL),
                 ImplItemKind::Type(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
             },
-            _ => self.tcx.sess.span_fatal(
-                attr.span,
-                &format!("clean/dirty auto-assertions not yet defined for {:?}", node),
-            ),
+            _ => self.tcx.sess.emit_fatal(errors::UndefinedCleanDirty {
+                span: attr.span,
+                kind: format!("{:?}", node),
+            }),
         };
         let labels =
             Labels::from_iter(labels.iter().flat_map(|s| s.iter().map(|l| (*l).to_string())));
@@ -318,16 +311,15 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
             let label = label.trim();
             if DepNode::has_label_string(label) {
                 if out.contains(label) {
-                    self.tcx.sess.span_fatal(
-                        item.span(),
-                        &format!("dep-node label `{}` is repeated", label),
-                    );
+                    self.tcx
+                        .sess
+                        .emit_fatal(errors::RepeatedDepNodeLabel { span: item.span(), label });
                 }
                 out.insert(label.to_string());
             } else {
                 self.tcx
                     .sess
-                    .span_fatal(item.span(), &format!("dep-node label `{}` not recognized", label));
+                    .emit_fatal(errors::UnrecognizedDepNodeLabel { span: item.span(), label });
             }
         }
         out
@@ -348,7 +340,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
             let dep_node_str = self.dep_node_str(&dep_node);
             self.tcx
                 .sess
-                .span_err(item_span, &format!("`{}` should be dirty but is not", dep_node_str));
+                .emit_err(errors::NotDirty { span: item_span, dep_node_str: &dep_node_str });
         }
     }
 
@@ -359,7 +351,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
             let dep_node_str = self.dep_node_str(&dep_node);
             self.tcx
                 .sess
-                .span_err(item_span, &format!("`{}` should be clean but is not", dep_node_str));
+                .emit_err(errors::NotClean { span: item_span, dep_node_str: &dep_node_str });
         }
     }
 
@@ -368,10 +360,9 @@ impl<'tcx> DirtyCleanVisitor<'tcx> {
 
         if !self.tcx.dep_graph.debug_was_loaded_from_disk(dep_node) {
             let dep_node_str = self.dep_node_str(&dep_node);
-            self.tcx.sess.span_err(
-                item_span,
-                &format!("`{}` should have been loaded from disk but it was not", dep_node_str),
-            );
+            self.tcx
+                .sess
+                .emit_err(errors::NotLoaded { span: item_span, dep_node_str: &dep_node_str });
         }
     }
 
@@ -412,12 +403,12 @@ fn check_config(tcx: TyCtxt<'_>, attr: &Attribute) -> bool {
             debug!("check_config: searching for cfg {:?}", value);
             cfg = Some(config.contains(&(value, None)));
         } else if !(item.has_name(EXCEPT) || item.has_name(LOADED_FROM_DISK)) {
-            tcx.sess.span_err(attr.span, &format!("unknown item `{}`", item.name_or_empty()));
+            tcx.sess.emit_err(errors::UnknownItem { span: attr.span, name: item.name_or_empty() });
         }
     }
 
     match cfg {
-        None => tcx.sess.span_fatal(attr.span, "no cfg attribute"),
+        None => tcx.sess.emit_fatal(errors::NoCfg { span: attr.span }),
         Some(c) => c,
     }
 }
@@ -426,13 +417,11 @@ fn expect_associated_value(tcx: TyCtxt<'_>, item: &NestedMetaItem) -> Symbol {
     if let Some(value) = item.value_str() {
         value
     } else {
-        let msg = if let Some(ident) = item.ident() {
-            format!("associated value expected for `{}`", ident)
+        if let Some(ident) = item.ident() {
+            tcx.sess.emit_fatal(errors::AssociatedValueExpectedFor { span: item.span(), ident });
         } else {
-            "expected an associated value".to_string()
-        };
-
-        tcx.sess.span_fatal(item.span(), &msg);
+            tcx.sess.emit_fatal(errors::AssociatedValueExpected { span: item.span() });
+        }
     }
 }
 
@@ -456,7 +445,7 @@ impl<'tcx> FindAllAttrs<'tcx> {
     fn report_unchecked_attrs(&self, mut checked_attrs: FxHashSet<ast::AttrId>) {
         for attr in &self.found_attrs {
             if !checked_attrs.contains(&attr.id) {
-                self.tcx.sess.span_err(attr.span, "found unchecked `#[rustc_clean]` attribute");
+                self.tcx.sess.emit_err(errors::UncheckedClean { span: attr.span });
                 checked_attrs.insert(attr.id);
             }
         }
diff --git a/compiler/rustc_incremental/src/persist/file_format.rs b/compiler/rustc_incremental/src/persist/file_format.rs
index 2dbd4b6bce85a..dc981c6179eeb 100644
--- a/compiler/rustc_incremental/src/persist/file_format.rs
+++ b/compiler/rustc_incremental/src/persist/file_format.rs
@@ -9,15 +9,15 @@
 //! compiler versions don't change frequently for the typical user, being
 //! conservative here practically has no downside.
 
-use std::env;
-use std::fs;
-use std::io::{self, Read};
-use std::path::{Path, PathBuf};
-
+use crate::errors;
 use rustc_data_structures::memmap::Mmap;
 use rustc_serialize::opaque::{FileEncodeResult, FileEncoder};
 use rustc_serialize::Encoder;
 use rustc_session::Session;
+use std::env;
+use std::fs;
+use std::io::{self, Read};
+use std::path::{Path, PathBuf};
 
 /// The first few bytes of files generated by incremental compilation.
 const FILE_MAGIC: &[u8] = b"RSIC";
@@ -60,12 +60,7 @@ where
         }
         Err(err) if err.kind() == io::ErrorKind::NotFound => (),
         Err(err) => {
-            sess.err(&format!(
-                "unable to delete old {} at `{}`: {}",
-                name,
-                path_buf.display(),
-                err
-            ));
+            sess.emit_err(errors::DeleteOld { name, path: path_buf, err });
             return;
         }
     }
@@ -73,7 +68,7 @@ where
     let mut encoder = match FileEncoder::new(&path_buf) {
         Ok(encoder) => encoder,
         Err(err) => {
-            sess.err(&format!("failed to create {} at `{}`: {}", name, path_buf.display(), err));
+            sess.emit_err(errors::CreateNew { name, path: path_buf, err });
             return;
         }
     };
@@ -90,7 +85,7 @@ where
             debug!("save: data written to disk successfully");
         }
         Err(err) => {
-            sess.err(&format!("failed to write {} to `{}`: {}", name, path_buf.display(), err));
+            sess.emit_err(errors::WriteNew { name, path: path_buf, err });
         }
     }
 }
diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs
index 1fd2b9b0d7b7b..73d7e3becab48 100644
--- a/compiler/rustc_incremental/src/persist/fs.rs
+++ b/compiler/rustc_incremental/src/persist/fs.rs
@@ -103,6 +103,7 @@
 //! unsupported file system and emit a warning in that case. This is not yet
 //! implemented.
 
+use crate::errors;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::svh::Svh;
 use rustc_data_structures::{base_n, flock};
@@ -225,12 +226,7 @@ pub fn prepare_session_directory(
     let crate_dir = match crate_dir.canonicalize() {
         Ok(v) => v,
         Err(err) => {
-            let reported = sess.err(&format!(
-                "incremental compilation: error canonicalizing path `{}`: {}",
-                crate_dir.display(),
-                err
-            ));
-            return Err(reported);
+            return Err(sess.emit_err(errors::CanonicalizePath { path: crate_dir, err }));
         }
     };
 
@@ -273,14 +269,7 @@ pub fn prepare_session_directory(
             debug!("successfully copied data from: {}", source_directory.display());
 
             if !allows_links {
-                sess.warn(&format!(
-                    "Hard linking files in the incremental \
-                                        compilation cache failed. Copying files \
-                                        instead. Consider moving the cache \
-                                        directory to a file system which supports \
-                                        hard linking in session dir `{}`",
-                    session_dir.display()
-                ));
+                sess.emit_warning(errors::HardLinkFailed { path: &session_dir });
             }
 
             sess.init_incr_comp_session(session_dir, directory_lock, true);
@@ -295,12 +284,7 @@ pub fn prepare_session_directory(
             // Try to remove the session directory we just allocated. We don't
             // know if there's any garbage in it from the failed copy action.
             if let Err(err) = safe_remove_dir_all(&session_dir) {
-                sess.warn(&format!(
-                    "Failed to delete partly initialized \
-                                    session dir `{}`: {}",
-                    session_dir.display(),
-                    err
-                ));
+                sess.emit_warning(errors::DeletePartial { path: &session_dir, err });
             }
 
             delete_session_dir_lock_file(sess, &lock_file_path);
@@ -332,12 +316,7 @@ pub fn finalize_session_directory(sess: &Session, svh: Svh) {
         );
 
         if let Err(err) = safe_remove_dir_all(&*incr_comp_session_dir) {
-            sess.warn(&format!(
-                "Error deleting incremental compilation \
-                                session directory `{}`: {}",
-                incr_comp_session_dir.display(),
-                err
-            ));
+            sess.emit_warning(errors::DeleteFull { path: &incr_comp_session_dir, err });
         }
 
         let lock_file_path = lock_file_path(&*incr_comp_session_dir);
@@ -380,12 +359,7 @@ pub fn finalize_session_directory(sess: &Session, svh: Svh) {
         }
         Err(e) => {
             // Warn about the error. However, no need to abort compilation now.
-            sess.warn(&format!(
-                "Error finalizing incremental compilation \
-                               session directory `{}`: {}",
-                incr_comp_session_dir.display(),
-                e
-            ));
+            sess.emit_warning(errors::Finalize { path: &incr_comp_session_dir, err: e });
 
             debug!("finalize_session_directory() - error, marking as invalid");
             // Drop the file lock, so we can garage collect
@@ -488,16 +462,7 @@ fn create_dir(sess: &Session, path: &Path, dir_tag: &str) -> Result<(), ErrorGua
             debug!("{} directory created successfully", dir_tag);
             Ok(())
         }
-        Err(err) => {
-            let reported = sess.err(&format!(
-                "Could not create incremental compilation {} \
-                               directory `{}`: {}",
-                dir_tag,
-                path.display(),
-                err
-            ));
-            Err(reported)
-        }
+        Err(err) => Err(sess.emit_err(errors::CreateIncrCompDir { tag: dir_tag, path, err })),
     }
 }
 
@@ -518,46 +483,20 @@ fn lock_directory(
         // the lock should be exclusive
         Ok(lock) => Ok((lock, lock_file_path)),
         Err(lock_err) => {
-            let mut err = sess.struct_err(&format!(
-                "incremental compilation: could not create \
-                 session directory lock file: {}",
-                lock_err
-            ));
-            if flock::Lock::error_unsupported(&lock_err) {
-                err.note(&format!(
-                    "the filesystem for the incremental path at {} \
-                     does not appear to support locking, consider changing the \
-                     incremental path to a filesystem that supports locking \
-                     or disable incremental compilation",
-                    session_dir.display()
-                ));
-                if std::env::var_os("CARGO").is_some() {
-                    err.help(
-                        "incremental compilation can be disabled by setting the \
-                         environment variable CARGO_INCREMENTAL=0 (see \
-                         https://doc.rust-lang.org/cargo/reference/profiles.html#incremental)",
-                    );
-                    err.help(
-                        "the entire build directory can be changed to a different \
-                        filesystem by setting the environment variable CARGO_TARGET_DIR \
-                        to a different path (see \
-                        https://doc.rust-lang.org/cargo/reference/config.html#buildtarget-dir)",
-                    );
-                }
-            }
-            Err(err.emit())
+            let is_unsupported_lock = flock::Lock::error_unsupported(&lock_err).then_some(());
+            Err(sess.emit_err(errors::CreateLock {
+                lock_err,
+                session_dir,
+                is_unsupported_lock,
+                is_cargo: std::env::var_os("CARGO").map(|_| ()),
+            }))
         }
     }
 }
 
 fn delete_session_dir_lock_file(sess: &Session, lock_file_path: &Path) {
     if let Err(err) = safe_remove_file(&lock_file_path) {
-        sess.warn(&format!(
-            "Error deleting lock file for incremental \
-                            compilation session directory `{}`: {}",
-            lock_file_path.display(),
-            err
-        ));
+        sess.emit_warning(errors::DeleteLock { path: lock_file_path, err });
     }
 }
 
@@ -774,12 +713,7 @@ pub fn garbage_collect_session_directories(sess: &Session) -> io::Result<()> {
         if !lock_file_to_session_dir.values().any(|dir| *dir == directory_name) {
             let path = crate_directory.join(directory_name);
             if let Err(err) = safe_remove_dir_all(&path) {
-                sess.warn(&format!(
-                    "Failed to garbage collect invalid incremental \
-                                    compilation session directory `{}`: {}",
-                    path.display(),
-                    err
-                ));
+                sess.emit_warning(errors::InvalidGcFailed { path: &path, err });
             }
         }
     }
@@ -885,12 +819,7 @@ pub fn garbage_collect_session_directories(sess: &Session) -> io::Result<()> {
         debug!("garbage_collect_session_directories() - deleting `{}`", path.display());
 
         if let Err(err) = safe_remove_dir_all(&path) {
-            sess.warn(&format!(
-                "Failed to garbage collect finalized incremental \
-                                compilation session directory `{}`: {}",
-                path.display(),
-                err
-            ));
+            sess.emit_warning(errors::FinalizedGcFailed { path: &path, err });
         } else {
             delete_session_dir_lock_file(sess, &lock_file_path(&path));
         }
@@ -907,11 +836,7 @@ fn delete_old(sess: &Session, path: &Path) {
     debug!("garbage_collect_session_directories() - deleting `{}`", path.display());
 
     if let Err(err) = safe_remove_dir_all(&path) {
-        sess.warn(&format!(
-            "Failed to garbage collect incremental compilation session directory `{}`: {}",
-            path.display(),
-            err
-        ));
+        sess.emit_warning(errors::SessionGcFailed { path: &path, err });
     } else {
         delete_session_dir_lock_file(sess, &lock_file_path(&path));
     }
diff --git a/compiler/rustc_incremental/src/persist/load.rs b/compiler/rustc_incremental/src/persist/load.rs
index 1c5fd91690230..d5097065dda2e 100644
--- a/compiler/rustc_incremental/src/persist/load.rs
+++ b/compiler/rustc_incremental/src/persist/load.rs
@@ -1,5 +1,6 @@
 //! Code to save/load the dep-graph from files.
 
+use crate::errors;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::memmap::Mmap;
 use rustc_middle::dep_graph::{SerializedDepGraph, WorkProduct, WorkProductId};
@@ -8,7 +9,7 @@ use rustc_serialize::opaque::MemDecoder;
 use rustc_serialize::Decodable;
 use rustc_session::config::IncrementalStateAssertion;
 use rustc_session::Session;
-use std::path::Path;
+use std::path::{Path, PathBuf};
 
 use super::data::*;
 use super::file_format;
@@ -27,11 +28,10 @@ pub enum LoadResult<T> {
     },
     /// The file either didn't exist or was produced by an incompatible compiler version.
     DataOutOfDate,
-    /// An error occurred.
-    Error {
-        #[allow(missing_docs)]
-        message: String,
-    },
+    /// Loading the dep graph failed.
+    LoadDepGraph(PathBuf, std::io::Error),
+    /// Decoding loaded incremental cache failed.
+    DecodeIncrCache(Box<dyn std::any::Any + Send>),
 }
 
 impl<T: Default> LoadResult<T> {
@@ -40,36 +40,31 @@ impl<T: Default> LoadResult<T> {
         // Check for errors when using `-Zassert-incremental-state`
         match (sess.opts.assert_incr_state, &self) {
             (Some(IncrementalStateAssertion::NotLoaded), LoadResult::Ok { .. }) => {
-                sess.fatal(
-                    "We asserted that the incremental cache should not be loaded, \
-                         but it was loaded.",
-                );
+                sess.emit_fatal(errors::AssertNotLoaded);
             }
             (
                 Some(IncrementalStateAssertion::Loaded),
-                LoadResult::Error { .. } | LoadResult::DataOutOfDate,
+                LoadResult::LoadDepGraph(..)
+                | LoadResult::DecodeIncrCache(..)
+                | LoadResult::DataOutOfDate,
             ) => {
-                sess.fatal(
-                    "We asserted that an existing incremental cache directory should \
-                         be successfully loaded, but it was not.",
-                );
+                sess.emit_fatal(errors::AssertLoaded);
             }
             _ => {}
         };
 
         match self {
-            LoadResult::Error { message } => {
-                sess.warn(&message);
+            LoadResult::LoadDepGraph(path, err) => {
+                sess.emit_warning(errors::LoadDepGraph { path, err });
+                Default::default()
+            }
+            LoadResult::DecodeIncrCache(err) => {
+                sess.emit_warning(errors::DecodeIncrCache { err: format!("{err:?}") });
                 Default::default()
             }
             LoadResult::DataOutOfDate => {
                 if let Err(err) = delete_all_session_dir_contents(sess) {
-                    sess.err(&format!(
-                        "Failed to delete invalidated or incompatible \
-                         incremental compilation session directory contents `{}`: {}.",
-                        dep_graph_path(sess).display(),
-                        err
-                    ));
+                    sess.emit_err(errors::DeleteIncompatible { path: dep_graph_path(sess), err });
                 }
                 Default::default()
             }
@@ -90,9 +85,7 @@ fn load_data(
             // compiler version. Neither is an error.
             LoadResult::DataOutOfDate
         }
-        Err(err) => LoadResult::Error {
-            message: format!("could not load dep-graph from `{}`: {}", path.display(), err),
-        },
+        Err(err) => LoadResult::LoadDepGraph(path.to_path_buf(), err),
     }
 }
 
@@ -114,9 +107,9 @@ impl<T> MaybeAsync<LoadResult<T>> {
     pub fn open(self) -> LoadResult<T> {
         match self {
             MaybeAsync::Sync(result) => result,
-            MaybeAsync::Async(handle) => handle.join().unwrap_or_else(|e| LoadResult::Error {
-                message: format!("could not decode incremental cache: {:?}", e),
-            }),
+            MaybeAsync::Async(handle) => {
+                handle.join().unwrap_or_else(|e| LoadResult::DecodeIncrCache(e))
+            }
         }
     }
 }
@@ -185,7 +178,8 @@ pub fn load_dep_graph(sess: &Session) -> DepGraphFuture {
 
         match load_data(report_incremental_info, &path, nightly_build) {
             LoadResult::DataOutOfDate => LoadResult::DataOutOfDate,
-            LoadResult::Error { message } => LoadResult::Error { message },
+            LoadResult::LoadDepGraph(path, err) => LoadResult::LoadDepGraph(path, err),
+            LoadResult::DecodeIncrCache(err) => LoadResult::DecodeIncrCache(err),
             LoadResult::Ok { data: (bytes, start_pos) } => {
                 let mut decoder = MemDecoder::new(&bytes, start_pos);
                 let prev_commandline_args_hash = u64::decode(&mut decoder);
diff --git a/compiler/rustc_incremental/src/persist/save.rs b/compiler/rustc_incremental/src/persist/save.rs
index 6e9dcdd981ec4..27be56eac6f99 100644
--- a/compiler/rustc_incremental/src/persist/save.rs
+++ b/compiler/rustc_incremental/src/persist/save.rs
@@ -1,3 +1,4 @@
+use crate::errors;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync::join;
 use rustc_middle::dep_graph::{DepGraph, SerializedDepGraph, WorkProduct, WorkProductId};
@@ -59,19 +60,14 @@ pub fn save_dep_graph(tcx: TyCtxt<'_>) {
             move || {
                 sess.time("incr_comp_persist_dep_graph", || {
                     if let Err(err) = tcx.dep_graph.encode(&tcx.sess.prof) {
-                        sess.err(&format!(
-                            "failed to write dependency graph to `{}`: {}",
-                            staging_dep_graph_path.display(),
-                            err
-                        ));
+                        sess.emit_err(errors::WriteDepGraph { path: &staging_dep_graph_path, err });
                     }
                     if let Err(err) = fs::rename(&staging_dep_graph_path, &dep_graph_path) {
-                        sess.err(&format!(
-                            "failed to move dependency graph from `{}` to `{}`: {}",
-                            staging_dep_graph_path.display(),
-                            dep_graph_path.display(),
-                            err
-                        ));
+                        sess.emit_err(errors::MoveDepGraph {
+                            from: &staging_dep_graph_path,
+                            to: &dep_graph_path,
+                            err,
+                        });
                     }
                 });
             },
@@ -163,11 +159,7 @@ pub fn build_dep_graph(
     let mut encoder = match FileEncoder::new(&path_buf) {
         Ok(encoder) => encoder,
         Err(err) => {
-            sess.err(&format!(
-                "failed to create dependency graph at `{}`: {}",
-                path_buf.display(),
-                err
-            ));
+            sess.emit_err(errors::CreateDepGraph { path: &path_buf, err });
             return None;
         }
     };
diff --git a/compiler/rustc_incremental/src/persist/work_product.rs b/compiler/rustc_incremental/src/persist/work_product.rs
index 2f1853c441eee..dc98fbeb0d166 100644
--- a/compiler/rustc_incremental/src/persist/work_product.rs
+++ b/compiler/rustc_incremental/src/persist/work_product.rs
@@ -2,6 +2,7 @@
 //!
 //! [work products]: WorkProduct
 
+use crate::errors;
 use crate::persist::fs::*;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_fs_util::link_or_copy;
@@ -28,12 +29,11 @@ pub fn copy_cgu_workproduct_to_incr_comp_cache_dir(
                 let _ = saved_files.insert(ext.to_string(), file_name);
             }
             Err(err) => {
-                sess.warn(&format!(
-                    "error copying object file `{}` to incremental directory as `{}`: {}",
-                    path.display(),
-                    path_in_incr_dir.display(),
-                    err
-                ));
+                sess.emit_warning(errors::CopyWorkProductToCache {
+                    from: &path,
+                    to: &path_in_incr_dir,
+                    err,
+                });
             }
         }
     }
@@ -49,11 +49,7 @@ pub fn delete_workproduct_files(sess: &Session, work_product: &WorkProduct) {
     for (_, path) in &work_product.saved_files {
         let path = in_incr_comp_dir_sess(sess, path);
         if let Err(err) = std_fs::remove_file(&path) {
-            sess.warn(&format!(
-                "file-system error deleting outdated file `{}`: {}",
-                path.display(),
-                err
-            ));
+            sess.emit_warning(errors::DeleteWorkProduct { path: &path, err });
         }
     }
 }
diff --git a/compiler/rustc_interface/Cargo.toml b/compiler/rustc_interface/Cargo.toml
index f817c5bc1cd73..1199ff287c430 100644
--- a/compiler/rustc_interface/Cargo.toml
+++ b/compiler/rustc_interface/Cargo.toml
@@ -49,9 +49,6 @@ rustc_target = { path = "../rustc_target" }
 rustc_trait_selection = { path = "../rustc_trait_selection" }
 rustc_ty_utils = { path = "../rustc_ty_utils" }
 
-[dev-dependencies]
-rustc_target = { path = "../rustc_target" }
-
 [features]
 llvm = ['rustc_codegen_llvm']
 rustc_use_parallel_compiler = ['rayon', 'rustc-rayon-core', 'rustc_query_impl/rustc_use_parallel_compiler', 'rustc_errors/rustc_use_parallel_compiler']
diff --git a/compiler/rustc_interface/src/errors.rs b/compiler/rustc_interface/src/errors.rs
index 15d7e977bbe88..29543fe2f932c 100644
--- a/compiler/rustc_interface/src/errors.rs
+++ b/compiler/rustc_interface/src/errors.rs
@@ -1,5 +1,7 @@
 use rustc_macros::Diagnostic;
+use rustc_session::config::CrateType;
 use rustc_span::{Span, Symbol};
+use rustc_target::spec::TargetTriple;
 
 use std::io;
 use std::path::Path;
@@ -91,3 +93,22 @@ pub struct FailedWritingFile<'a> {
 #[derive(Diagnostic)]
 #[diag(interface_proc_macro_crate_panic_abort)]
 pub struct ProcMacroCratePanicAbort;
+
+#[derive(Diagnostic)]
+#[diag(interface_unsupported_crate_type_for_target)]
+pub struct UnsupportedCrateTypeForTarget<'a> {
+    pub crate_type: CrateType,
+    pub target_triple: &'a TargetTriple,
+}
+
+#[derive(Diagnostic)]
+#[diag(interface_multiple_output_types_adaption)]
+pub struct MultipleOutputTypesAdaption;
+
+#[derive(Diagnostic)]
+#[diag(interface_ignoring_extra_filename)]
+pub struct IgnoringExtraFilename;
+
+#[derive(Diagnostic)]
+#[diag(interface_ignoring_out_dir)]
+pub struct IgnoringOutDir;
diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs
index 54363e07b971a..e4b4d5375e64a 100644
--- a/compiler/rustc_interface/src/util.rs
+++ b/compiler/rustc_interface/src/util.rs
@@ -1,3 +1,4 @@
+use crate::errors;
 use info;
 use libloading::Library;
 use rustc_ast as ast;
@@ -472,16 +473,15 @@ pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<C
     }
 
     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
-            ));
+        if output::invalid_output_for_target(session, *crate_type) {
+            session.emit_warning(errors::UnsupportedCrateTypeForTarget {
+                crate_type: *crate_type,
+                target_triple: &session.opts.target_triple,
+            });
+            false
+        } else {
+            true
         }
-
-        res
     });
 
     base
@@ -517,19 +517,16 @@ pub fn build_output_filenames(attrs: &[ast::Attribute], sess: &Session) -> Outpu
             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",
-                );
+                sess.emit_warning(errors::MultipleOutputTypesAdaption);
                 None
             } else {
                 if !sess.opts.cg.extra_filename.is_empty() {
-                    sess.warn("ignoring -C extra-filename flag due to -o flag");
+                    sess.emit_warning(errors::IgnoringExtraFilename);
                 }
                 Some(out_file.clone())
             };
             if sess.io.output_dir != None {
-                sess.warn("ignoring --out-dir flag due to -o flag");
+                sess.emit_warning(errors::IgnoringOutDir);
             }
 
             OutputFilenames::new(
diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs
index 305f0427e501b..31a3ffbb1d891 100644
--- a/compiler/rustc_monomorphize/src/collector.rs
+++ b/compiler/rustc_monomorphize/src/collector.rs
@@ -201,7 +201,9 @@ use rustc_target::abi::Size;
 use std::ops::Range;
 use std::path::PathBuf;
 
-use crate::errors::{LargeAssignmentsLint, RecursionLimit, TypeLengthLimit};
+use crate::errors::{
+    EncounteredErrorWhileInstantiating, LargeAssignmentsLint, RecursionLimit, TypeLengthLimit,
+};
 
 #[derive(PartialEq)]
 pub enum MonoItemCollectionMode {
@@ -524,10 +526,10 @@ fn collect_items_rec<'tcx>(
         && starting_point.node.is_user_defined()
     {
         let formatted_item = with_no_trimmed_paths!(starting_point.node.to_string());
-        tcx.sess.span_note_without_error(
-            starting_point.span,
-            &format!("the above error was encountered while instantiating `{formatted_item}`"),
-        );
+        tcx.sess.emit_note(EncounteredErrorWhileInstantiating {
+            span: starting_point.span,
+            formatted_item,
+        });
     }
     inlining_map.lock_mut().record_accesses(starting_point.node, &neighbors.items);
 
diff --git a/compiler/rustc_monomorphize/src/errors.rs b/compiler/rustc_monomorphize/src/errors.rs
index 5233cfb21203b..a53bd7e1fef5e 100644
--- a/compiler/rustc_monomorphize/src/errors.rs
+++ b/compiler/rustc_monomorphize/src/errors.rs
@@ -83,3 +83,17 @@ pub struct SymbolAlreadyDefined {
 pub struct CouldntDumpMonoStats {
     pub error: String,
 }
+
+#[derive(Diagnostic)]
+#[diag(monomorphize_encountered_error_while_instantiating)]
+pub struct EncounteredErrorWhileInstantiating {
+    #[primary_span]
+    pub span: Span,
+    pub formatted_item: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(monomorphize_unknown_cgu_collection_mode)]
+pub struct UnknownCguCollectionMode<'a> {
+    pub mode: &'a str,
+}
diff --git a/compiler/rustc_monomorphize/src/partitioning/mod.rs b/compiler/rustc_monomorphize/src/partitioning/mod.rs
index fd6bcad18983a..af3f60bb9326d 100644
--- a/compiler/rustc_monomorphize/src/partitioning/mod.rs
+++ b/compiler/rustc_monomorphize/src/partitioning/mod.rs
@@ -114,7 +114,9 @@ use rustc_span::symbol::Symbol;
 
 use crate::collector::InliningMap;
 use crate::collector::{self, MonoItemCollectionMode};
-use crate::errors::{CouldntDumpMonoStats, SymbolAlreadyDefined, UnknownPartitionStrategy};
+use crate::errors::{
+    CouldntDumpMonoStats, SymbolAlreadyDefined, UnknownCguCollectionMode, UnknownPartitionStrategy,
+};
 
 pub struct PartitioningCx<'a, 'tcx> {
     tcx: TyCtxt<'tcx>,
@@ -348,17 +350,13 @@ where
 fn collect_and_partition_mono_items(tcx: TyCtxt<'_>, (): ()) -> (&DefIdSet, &[CodegenUnit<'_>]) {
     let collection_mode = match tcx.sess.opts.unstable_opts.print_mono_items {
         Some(ref s) => {
-            let mode_string = s.to_lowercase();
-            let mode_string = mode_string.trim();
-            if mode_string == "eager" {
+            let mode = s.to_lowercase();
+            let mode = mode.trim();
+            if mode == "eager" {
                 MonoItemCollectionMode::Eager
             } else {
-                if mode_string != "lazy" {
-                    let message = format!(
-                        "Unknown codegen-item collection mode '{mode_string}'. \
-                                           Falling back to 'lazy' mode."
-                    );
-                    tcx.sess.warn(&message);
+                if mode != "lazy" {
+                    tcx.sess.emit_warning(UnknownCguCollectionMode { mode });
                 }
 
                 MonoItemCollectionMode::Lazy
diff --git a/compiler/rustc_passes/src/check_const.rs b/compiler/rustc_passes/src/check_const.rs
index dd8c646a43c82..526b829bf676a 100644
--- a/compiler/rustc_passes/src/check_const.rs
+++ b/compiler/rustc_passes/src/check_const.rs
@@ -17,7 +17,7 @@ use rustc_middle::ty::TyCtxt;
 use rustc_session::parse::feature_err;
 use rustc_span::{sym, Span, Symbol};
 
-use crate::errors::ExprNotAllowedInContext;
+use crate::errors::{ExprNotAllowedInContext, SkippingConstChecks};
 
 /// An expression that is not *always* legal in a const context.
 #[derive(Clone, Copy)]
@@ -124,7 +124,7 @@ impl<'tcx> CheckConstVisitor<'tcx> {
             // corresponding feature gate. This encourages nightly users to use feature gates when
             // possible.
             None if tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you => {
-                tcx.sess.span_warn(span, "skipping const checks");
+                tcx.sess.emit_warning(SkippingConstChecks { span });
                 return;
             }
 
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 9e05ad22e6241..97169a6367c3d 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1565,3 +1565,10 @@ pub(crate) struct ProcMacroUnsafe {
     #[primary_span]
     pub span: Span,
 }
+
+#[derive(Diagnostic)]
+#[diag(passes_skipping_const_checks)]
+pub struct SkippingConstChecks {
+    #[primary_span]
+    pub span: Span,
+}
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index c49c5fa990413..973d860118ef1 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -24,7 +24,7 @@ use rustc_span::RealFileName;
 use rustc_span::SourceFileHashAlgorithm;
 
 use rustc_errors::emitter::HumanReadableErrorType;
-use rustc_errors::{ColorConfig, HandlerFlags};
+use rustc_errors::{ColorConfig, DiagnosticArgValue, HandlerFlags, IntoDiagnosticArg};
 
 use std::collections::btree_map::{
     Iter as BTreeMapIter, Keys as BTreeMapKeysIter, Values as BTreeMapValuesIter,
@@ -2694,6 +2694,12 @@ impl fmt::Display for CrateType {
     }
 }
 
+impl IntoDiagnosticArg for CrateType {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        self.to_string().into_diagnostic_arg()
+    }
+}
+
 #[derive(Copy, Clone, PartialEq, Debug)]
 pub enum PpSourceMode {
     /// `-Zunpretty=normal`
diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs
index f5a72573d58cd..8e8fba5e236f5 100644
--- a/compiler/rustc_session/src/errors.rs
+++ b/compiler/rustc_session/src/errors.rs
@@ -375,3 +375,9 @@ pub fn report_lit_error(sess: &ParseSess, err: LitError, lit: token::Lit, span:
         }
     }
 }
+
+#[derive(Diagnostic)]
+#[diag(session_optimization_fuel_exhausted)]
+pub struct OptimisationFuelExhausted {
+    pub msg: String,
+}
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index 95f199de6ff6f..91d23f1041fe9 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -5,9 +5,10 @@ use crate::config::Input;
 use crate::config::{self, CrateType, InstrumentCoverage, OptLevel, OutputType, SwitchWithOptPath};
 use crate::errors::{
     BranchProtectionRequiresAArch64, CannotEnableCrtStaticLinux, CannotMixAndMatchSanitizers,
-    LinkerPluginToWindowsNotSupported, NotCircumventFeature, ProfileSampleUseFileDoesNotExist,
-    ProfileUseFileDoesNotExist, SanitizerCfiEnabled, SanitizerNotSupported, SanitizersNotSupported,
-    SkippingConstChecks, SplitDebugInfoUnstablePlatform, StackProtectorNotSupportedForTarget,
+    LinkerPluginToWindowsNotSupported, NotCircumventFeature, OptimisationFuelExhausted,
+    ProfileSampleUseFileDoesNotExist, ProfileUseFileDoesNotExist, SanitizerCfiEnabled,
+    SanitizerNotSupported, SanitizersNotSupported, SkippingConstChecks,
+    SplitDebugInfoUnstablePlatform, StackProtectorNotSupportedForTarget,
     TargetRequiresUnwindTables, UnleashedFeatureHelp, UnstableVirtualFunctionElimination,
     UnsupportedDwarfVersion,
 };
@@ -483,6 +484,8 @@ impl Session {
         self.diagnostic().span_err_with_code(sp, msg, code)
     }
     #[rustc_lint_diagnostics]
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
     pub fn err(&self, msg: impl Into<DiagnosticMessage>) -> ErrorGuaranteed {
         self.diagnostic().err(msg)
     }
@@ -583,12 +586,16 @@ impl Session {
             ))
         }
     }
+
+    #[rustc_lint_diagnostics]
     #[allow(rustc::untranslatable_diagnostic)]
     #[allow(rustc::diagnostic_outside_of_impl)]
     #[track_caller]
     pub fn span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: impl Into<DiagnosticMessage>) {
         self.diagnostic().span_warn(sp, msg)
     }
+
+    #[rustc_lint_diagnostics]
     #[allow(rustc::untranslatable_diagnostic)]
     #[allow(rustc::diagnostic_outside_of_impl)]
     pub fn span_warn_with_code<S: Into<MultiSpan>>(
@@ -599,6 +606,10 @@ impl Session {
     ) {
         self.diagnostic().span_warn_with_code(sp, msg, code)
     }
+
+    #[rustc_lint_diagnostics]
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
     pub fn warn(&self, msg: impl Into<DiagnosticMessage>) {
         self.diagnostic().warn(msg)
     }
@@ -641,11 +652,17 @@ impl Session {
         self.diagnostic().delay_good_path_bug(msg)
     }
 
+    #[rustc_lint_diagnostics]
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
     pub fn note_without_error(&self, msg: impl Into<DiagnosticMessage>) {
         self.diagnostic().note_without_error(msg)
     }
 
     #[track_caller]
+    #[rustc_lint_diagnostics]
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
     pub fn span_note_without_error<S: Into<MultiSpan>>(
         &self,
         sp: S,
@@ -653,6 +670,8 @@ impl Session {
     ) {
         self.diagnostic().span_note_without_error(sp, msg)
     }
+
+    #[rustc_lint_diagnostics]
     #[allow(rustc::untranslatable_diagnostic)]
     #[allow(rustc::diagnostic_outside_of_impl)]
     pub fn struct_note_without_error(
@@ -882,7 +901,7 @@ impl Session {
                         // We only call `msg` in case we can actually emit warnings.
                         // Otherwise, this could cause a `delay_good_path_bug` to
                         // trigger (issue #79546).
-                        self.warn(&format!("optimization-fuel-exhausted: {}", msg()));
+                        self.emit_warning(OptimisationFuelExhausted { msg: msg() });
                     }
                     fuel.out_of_fuel = true;
                 } else if fuel.remaining > 0 {
diff --git a/tests/run-make/incremental-session-fail/Makefile b/tests/run-make/incremental-session-fail/Makefile
index 0461bb926e76e..6ce1370927bc1 100644
--- a/tests/run-make/incremental-session-fail/Makefile
+++ b/tests/run-make/incremental-session-fail/Makefile
@@ -9,6 +9,6 @@ all:
 	touch $(SESSION_DIR)
 	# Check exit code is 1 for an error, and not 101 for ICE.
 	$(RUSTC) foo.rs --crate-type=rlib -C incremental=$(SESSION_DIR) > $(OUTPUT_FILE) 2>&1; [ $$? -eq 1 ]
-	$(CGREP) "Could not create incremental compilation crate directory" < $(OUTPUT_FILE)
+	$(CGREP) "could not create incremental compilation crate directory" < $(OUTPUT_FILE)
 	# -v tests are fragile, hopefully this text won't change
 	$(CGREP) -v "internal compiler error" < $(OUTPUT_FILE)