about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_codegen_cranelift/src/base.rs12
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/block.rs6
-rw-r--r--compiler/rustc_const_eval/src/const_eval/machine.rs1
-rw-r--r--compiler/rustc_const_eval/src/util/caller_location.rs12
-rw-r--r--compiler/rustc_hir/src/lang_items.rs1
-rw-r--r--compiler/rustc_macros/src/symbols.rs12
-rw-r--r--compiler/rustc_macros/src/symbols/tests.rs15
-rw-r--r--compiler/rustc_middle/messages.ftl3
-rw-r--r--compiler/rustc_middle/src/mir/syntax.rs1
-rw-r--r--compiler/rustc_middle/src/mir/terminator.rs8
-rw-r--r--compiler/rustc_middle/src/mir/visit.rs2
-rw-r--r--compiler/rustc_middle/src/query/mod.rs12
-rw-r--r--compiler/rustc_mir_transform/src/check_enums.rs501
-rw-r--r--compiler/rustc_mir_transform/src/inline.rs5
-rw-r--r--compiler/rustc_mir_transform/src/inline/cycle.rs273
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs4
-rw-r--r--compiler/rustc_monomorphize/src/collector.rs3
-rw-r--r--compiler/rustc_smir/src/rustc_smir/convert/mir.rs3
-rw-r--r--compiler/rustc_smir/src/stable_mir/mir/body.rs4
-rw-r--r--compiler/rustc_smir/src/stable_mir/mir/pretty.rs4
-rw-r--r--compiler/rustc_smir/src/stable_mir/mir/visit.rs3
-rw-r--r--compiler/rustc_span/src/symbol.rs132
-rw-r--r--compiler/rustc_type_ir/src/binder.rs11
-rw-r--r--library/alloc/src/collections/btree/set.rs4
-rw-r--r--library/core/src/panic/location.rs38
-rw-r--r--library/core/src/panicking.rs16
-rw-r--r--library/std/src/os/illumos/mod.rs1
-rw-r--r--library/std/src/os/illumos/net.rs50
-rw-r--r--library/std/src/os/solaris/mod.rs1
-rw-r--r--library/std/src/os/solaris/net.rs50
-rw-r--r--library/std/src/sys/net/connection/socket/unix.rs15
-rw-r--r--library/std/src/sys/process/unix/unix.rs4
-rw-r--r--library/std/src/sys/process/unix/vxworks.rs4
-rwxr-xr-xsrc/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh5
-rw-r--r--src/librustdoc/html/render/print_item.rs1
-rw-r--r--src/tools/build-manifest/src/main.rs4
m---------src/tools/cargo0
-rw-r--r--src/tools/miri/README.md2
-rw-r--r--src/tools/miri/src/lib.rs2
-rw-r--r--src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs2
-rw-r--r--src/tools/opt-dist/src/exec.rs22
-rw-r--r--src/tools/opt-dist/src/main.rs5
-rw-r--r--src/tools/test-float-parse/src/ui.rs2
-rw-r--r--tests/rustdoc/enum/enum-variant-non_exhaustive.rs17
-rw-r--r--tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html4
-rw-r--r--tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html4
-rw-r--r--tests/ui/mir/enum/convert_non_enum_break.rs20
-rw-r--r--tests/ui/mir/enum/convert_non_enum_niche_break.rs27
-rw-r--r--tests/ui/mir/enum/convert_non_enum_niche_ok.rs29
-rw-r--r--tests/ui/mir/enum/convert_non_enum_ok.rs20
-rw-r--r--tests/ui/mir/enum/niche_option_tuple_break.rs20
-rw-r--r--tests/ui/mir/enum/niche_option_tuple_ok.rs21
-rw-r--r--tests/ui/mir/enum/numbered_variants_break.rs13
-rw-r--r--tests/ui/mir/enum/numbered_variants_ok.rs13
-rw-r--r--tests/ui/mir/enum/option_with_bigger_niche_break.rs14
-rw-r--r--tests/ui/mir/enum/option_with_bigger_niche_ok.rs14
-rw-r--r--tests/ui/mir/enum/plain_no_data_break.rs14
-rw-r--r--tests/ui/mir/enum/plain_no_data_ok.rs14
-rw-r--r--tests/ui/mir/enum/single_ok.rs11
-rw-r--r--tests/ui/mir/enum/single_with_repr_break.rs13
-rw-r--r--tests/ui/mir/enum/single_with_repr_ok.rs12
-rw-r--r--tests/ui/mir/enum/with_niche_int_break.rs21
-rw-r--r--tests/ui/mir/enum/with_niche_int_ok.rs23
-rw-r--r--tests/ui/mir/enum/with_niche_ptr_ok.rs14
-rw-r--r--tests/ui/mir/enum/wrap_break.rs14
-rw-r--r--tests/ui/mir/enum/wrap_ok.rs12
66 files changed, 1355 insertions, 265 deletions
diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs
index 0b641ba64b7..1b68c6535da 100644
--- a/compiler/rustc_codegen_cranelift/src/base.rs
+++ b/compiler/rustc_codegen_cranelift/src/base.rs
@@ -407,6 +407,18 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
                             source_info.span,
                         )
                     }
+                    AssertKind::InvalidEnumConstruction(source) => {
+                        let source = codegen_operand(fx, source).load_scalar(fx);
+                        let location = fx.get_caller_location(source_info).load_scalar(fx);
+
+                        codegen_panic_inner(
+                            fx,
+                            rustc_hir::LangItem::PanicInvalidEnumConstruction,
+                            &[source, location],
+                            *unwind,
+                            source_info.span,
+                        )
+                    }
                     _ => {
                         let location = fx.get_caller_location(source_info).load_scalar(fx);
 
diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs
index 3df97429e09..1d5fbfc0896 100644
--- a/compiler/rustc_codegen_ssa/src/mir/block.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/block.rs
@@ -776,6 +776,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                 // `#[track_caller]` adds an implicit argument.
                 (LangItem::PanicNullPointerDereference, vec![location])
             }
+            AssertKind::InvalidEnumConstruction(source) => {
+                let source = self.codegen_operand(bx, source).immediate();
+                // It's `fn panic_invalid_enum_construction(source: u128)`,
+                // `#[track_caller]` adds an implicit argument.
+                (LangItem::PanicInvalidEnumConstruction, vec![source, location])
+            }
             _ => {
                 // It's `pub fn panic_...()` and `#[track_caller]` adds an implicit argument.
                 (msg.panic_function(), vec![location])
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index 2ec3f8432c7..317b1229a90 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -508,6 +508,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
                 found: eval_to_int(found)?,
             },
             NullPointerDereference => NullPointerDereference,
+            InvalidEnumConstruction(source) => InvalidEnumConstruction(eval_to_int(source)?),
         };
         Err(ConstEvalErrKind::AssertFailure(err)).into()
     }
diff --git a/compiler/rustc_const_eval/src/util/caller_location.rs b/compiler/rustc_const_eval/src/util/caller_location.rs
index ab2de279ed8..f489b05fbbd 100644
--- a/compiler/rustc_const_eval/src/util/caller_location.rs
+++ b/compiler/rustc_const_eval/src/util/caller_location.rs
@@ -21,13 +21,14 @@ fn alloc_caller_location<'tcx>(
     assert!(!filename.as_str().as_bytes().contains(&0));
 
     let loc_details = ecx.tcx.sess.opts.unstable_opts.location_detail;
-    let file_wide_ptr = {
+    let filename = {
         let filename = if loc_details.file { filename.as_str() } else { "<redacted>" };
         let filename_with_nul = filename.to_owned() + "\0";
         // This can fail if rustc runs out of memory right here. Trying to emit an error would be
         // pointless, since that would require allocating more memory than these short strings.
         let file_ptr = ecx.allocate_bytes_dedup(filename_with_nul.as_bytes()).unwrap();
-        Immediate::new_slice(file_ptr.into(), filename_with_nul.len().try_into().unwrap(), ecx)
+        let file_len = u64::try_from(filename.len()).unwrap();
+        Immediate::new_slice(file_ptr.into(), file_len, ecx)
     };
     let line = if loc_details.line { Scalar::from_u32(line) } else { Scalar::from_u32(0) };
     let col = if loc_details.column { Scalar::from_u32(col) } else { Scalar::from_u32(0) };
@@ -41,11 +42,8 @@ fn alloc_caller_location<'tcx>(
     let location = ecx.allocate(loc_layout, MemoryKind::CallerLocation).unwrap();
 
     // Initialize fields.
-    ecx.write_immediate(
-        file_wide_ptr,
-        &ecx.project_field(&location, FieldIdx::from_u32(0)).unwrap(),
-    )
-    .expect("writing to memory we just allocated cannot fail");
+    ecx.write_immediate(filename, &ecx.project_field(&location, FieldIdx::from_u32(0)).unwrap())
+        .expect("writing to memory we just allocated cannot fail");
     ecx.write_scalar(line, &ecx.project_field(&location, FieldIdx::from_u32(1)).unwrap())
         .expect("writing to memory we just allocated cannot fail");
     ecx.write_scalar(col, &ecx.project_field(&location, FieldIdx::from_u32(2)).unwrap())
diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs
index 3a08e5ae336..10dd5ff9aa7 100644
--- a/compiler/rustc_hir/src/lang_items.rs
+++ b/compiler/rustc_hir/src/lang_items.rs
@@ -312,6 +312,7 @@ language_item_table! {
     PanicAsyncGenFnResumedPanic, sym::panic_const_async_gen_fn_resumed_panic, panic_const_async_gen_fn_resumed_panic, Target::Fn, GenericRequirement::None;
     PanicGenFnNonePanic, sym::panic_const_gen_fn_none_panic, panic_const_gen_fn_none_panic, Target::Fn, GenericRequirement::None;
     PanicNullPointerDereference, sym::panic_null_pointer_dereference, panic_null_pointer_dereference, Target::Fn, GenericRequirement::None;
+    PanicInvalidEnumConstruction, sym::panic_invalid_enum_construction, panic_invalid_enum_construction, Target::Fn, GenericRequirement::None;
     PanicCoroutineResumedDrop, sym::panic_const_coroutine_resumed_drop, panic_const_coroutine_resumed_drop, Target::Fn, GenericRequirement::None;
     PanicAsyncFnResumedDrop, sym::panic_const_async_fn_resumed_drop, panic_const_async_fn_resumed_drop, Target::Fn, GenericRequirement::None;
     PanicAsyncGenFnResumedDrop, sym::panic_const_async_gen_fn_resumed_drop, panic_const_async_gen_fn_resumed_drop, Target::Fn, GenericRequirement::None;
diff --git a/compiler/rustc_macros/src/symbols.rs b/compiler/rustc_macros/src/symbols.rs
index 2b00b7dd27a..78a4d47ca33 100644
--- a/compiler/rustc_macros/src/symbols.rs
+++ b/compiler/rustc_macros/src/symbols.rs
@@ -190,17 +190,6 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
     let mut symbols_stream = quote! {};
     let mut prefill_stream = quote! {};
     let mut entries = Entries::with_capacity(input.keywords.len() + input.symbols.len() + 10);
-    let mut prev_key: Option<(Span, String)> = None;
-
-    let mut check_order = |span: Span, s: &str, errors: &mut Errors| {
-        if let Some((prev_span, ref prev_str)) = prev_key {
-            if s < prev_str {
-                errors.error(span, format!("Symbol `{s}` must precede `{prev_str}`"));
-                errors.error(prev_span, format!("location of previous symbol `{prev_str}`"));
-            }
-        }
-        prev_key = Some((span, s.to_string()));
-    };
 
     // Generate the listed keywords.
     for keyword in input.keywords.iter() {
@@ -219,7 +208,6 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) {
     // Generate the listed symbols.
     for symbol in input.symbols.iter() {
         let name = &symbol.name;
-        check_order(symbol.name.span(), &name.to_string(), &mut errors);
 
         let value = match &symbol.value {
             Value::SameAsName => name.to_string(),
diff --git a/compiler/rustc_macros/src/symbols/tests.rs b/compiler/rustc_macros/src/symbols/tests.rs
index 9c53453df5b..f0a7a2106be 100644
--- a/compiler/rustc_macros/src/symbols/tests.rs
+++ b/compiler/rustc_macros/src/symbols/tests.rs
@@ -84,18 +84,3 @@ fn check_dup_symbol_and_keyword() {
     };
     test_symbols_macro(input, &["Symbol `splat` is duplicated", "location of previous definition"]);
 }
-
-#[test]
-fn check_symbol_order() {
-    let input = quote! {
-        Keywords {}
-        Symbols {
-            zebra,
-            aardvark,
-        }
-    };
-    test_symbols_macro(
-        input,
-        &["Symbol `aardvark` must precede `zebra`", "location of previous symbol `zebra`"],
-    );
-}
diff --git a/compiler/rustc_middle/messages.ftl b/compiler/rustc_middle/messages.ftl
index 0073deb18da..69aa4383f13 100644
--- a/compiler/rustc_middle/messages.ftl
+++ b/compiler/rustc_middle/messages.ftl
@@ -17,6 +17,9 @@ middle_assert_gen_resume_after_drop = `gen` fn or block cannot be further iterat
 
 middle_assert_gen_resume_after_panic = `gen` fn or block cannot be further iterated on after it panicked
 
+middle_assert_invalid_enum_construction =
+    trying to construct an enum from an invalid value `{$source}`
+
 middle_assert_misaligned_ptr_deref =
     misaligned pointer dereference: address must be a multiple of {$required} but is {$found}
 
diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs
index 56f19d7929d..c50a30cf7f3 100644
--- a/compiler/rustc_middle/src/mir/syntax.rs
+++ b/compiler/rustc_middle/src/mir/syntax.rs
@@ -1075,6 +1075,7 @@ pub enum AssertKind<O> {
     ResumedAfterDrop(CoroutineKind),
     MisalignedPointerDereference { required: O, found: O },
     NullPointerDereference,
+    InvalidEnumConstruction(O),
 }
 
 #[derive(Clone, Debug, PartialEq, TyEncodable, TyDecodable, Hash, HashStable)]
diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs
index 0834fa8844c..4034a3a06e9 100644
--- a/compiler/rustc_middle/src/mir/terminator.rs
+++ b/compiler/rustc_middle/src/mir/terminator.rs
@@ -208,6 +208,7 @@ impl<O> AssertKind<O> {
                 LangItem::PanicGenFnNonePanic
             }
             NullPointerDereference => LangItem::PanicNullPointerDereference,
+            InvalidEnumConstruction(_) => LangItem::PanicInvalidEnumConstruction,
             ResumedAfterDrop(CoroutineKind::Coroutine(_)) => LangItem::PanicCoroutineResumedDrop,
             ResumedAfterDrop(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => {
                 LangItem::PanicAsyncFnResumedDrop
@@ -284,6 +285,9 @@ impl<O> AssertKind<O> {
                 )
             }
             NullPointerDereference => write!(f, "\"null pointer dereference occurred\""),
+            InvalidEnumConstruction(source) => {
+                write!(f, "\"trying to construct an enum from an invalid value {{}}\", {source:?}")
+            }
             ResumedAfterReturn(CoroutineKind::Coroutine(_)) => {
                 write!(f, "\"coroutine resumed after completion\"")
             }
@@ -367,6 +371,7 @@ impl<O> AssertKind<O> {
                 middle_assert_coroutine_resume_after_panic
             }
             NullPointerDereference => middle_assert_null_ptr_deref,
+            InvalidEnumConstruction(_) => middle_assert_invalid_enum_construction,
             ResumedAfterDrop(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) => {
                 middle_assert_async_resume_after_drop
             }
@@ -420,6 +425,9 @@ impl<O> AssertKind<O> {
                 add!("required", format!("{required:#?}"));
                 add!("found", format!("{found:#?}"));
             }
+            InvalidEnumConstruction(source) => {
+                add!("source", format!("{source:#?}"));
+            }
         }
     }
 }
diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs
index 1777756174b..929ebe1aee1 100644
--- a/compiler/rustc_middle/src/mir/visit.rs
+++ b/compiler/rustc_middle/src/mir/visit.rs
@@ -642,7 +642,7 @@ macro_rules! make_mir_visitor {
                         self.visit_operand(l, location);
                         self.visit_operand(r, location);
                     }
-                    OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) => {
+                    OverflowNeg(op) | DivisionByZero(op) | RemainderByZero(op) | InvalidEnumConstruction(op) => {
                         self.visit_operand(op, location);
                     }
                     ResumedAfterReturn(_) | ResumedAfterPanic(_) | NullPointerDereference | ResumedAfterDrop(_) => {
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index ddedea32112..8a3d26e1b03 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1283,15 +1283,15 @@ rustc_queries! {
         return_result_from_ensure_ok
     }
 
-    /// Check whether the function has any recursion that could cause the inliner to trigger
-    /// a cycle.
-    query mir_callgraph_reachable(key: (ty::Instance<'tcx>, LocalDefId)) -> bool {
+    /// Return the set of (transitive) callees that may result in a recursive call to `key`.
+    query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx UnordSet<LocalDefId> {
         fatal_cycle
+        arena_cache
         desc { |tcx|
-            "computing if `{}` (transitively) calls `{}`",
-            key.0,
-            tcx.def_path_str(key.1),
+            "computing (transitive) callees of `{}` that may recurse",
+            tcx.def_path_str(key),
         }
+        cache_on_disk_if { true }
     }
 
     /// Obtain all the calls into other local functions
diff --git a/compiler/rustc_mir_transform/src/check_enums.rs b/compiler/rustc_mir_transform/src/check_enums.rs
new file mode 100644
index 00000000000..e06e0c6122e
--- /dev/null
+++ b/compiler/rustc_mir_transform/src/check_enums.rs
@@ -0,0 +1,501 @@
+use rustc_abi::{Scalar, Size, TagEncoding, Variants, WrappingRange};
+use rustc_hir::LangItem;
+use rustc_index::IndexVec;
+use rustc_middle::bug;
+use rustc_middle::mir::visit::Visitor;
+use rustc_middle::mir::*;
+use rustc_middle::ty::layout::PrimitiveExt;
+use rustc_middle::ty::{self, Ty, TyCtxt, TypingEnv};
+use rustc_session::Session;
+use tracing::debug;
+
+/// This pass inserts checks for a valid enum discriminant where they are most
+/// likely to find UB, because checking everywhere like Miri would generate too
+/// much MIR.
+pub(super) struct CheckEnums;
+
+impl<'tcx> crate::MirPass<'tcx> for CheckEnums {
+    fn is_enabled(&self, sess: &Session) -> bool {
+        sess.ub_checks()
+    }
+
+    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
+        // This pass emits new panics. If for whatever reason we do not have a panic
+        // implementation, running this pass may cause otherwise-valid code to not compile.
+        if tcx.lang_items().get(LangItem::PanicImpl).is_none() {
+            return;
+        }
+
+        let typing_env = body.typing_env(tcx);
+        let basic_blocks = body.basic_blocks.as_mut();
+        let local_decls = &mut body.local_decls;
+
+        // This operation inserts new blocks. Each insertion changes the Location for all
+        // statements/blocks after. Iterating or visiting the MIR in order would require updating
+        // our current location after every insertion. By iterating backwards, we dodge this issue:
+        // The only Locations that an insertion changes have already been handled.
+        for block in basic_blocks.indices().rev() {
+            for statement_index in (0..basic_blocks[block].statements.len()).rev() {
+                let location = Location { block, statement_index };
+                let statement = &basic_blocks[block].statements[statement_index];
+                let source_info = statement.source_info;
+
+                let mut finder = EnumFinder::new(tcx, local_decls, typing_env);
+                finder.visit_statement(statement, location);
+
+                for check in finder.into_found_enums() {
+                    debug!("Inserting enum check");
+                    let new_block = split_block(basic_blocks, location);
+
+                    match check {
+                        EnumCheckType::Direct { source_op, discr, op_size, valid_discrs } => {
+                            insert_direct_enum_check(
+                                tcx,
+                                local_decls,
+                                basic_blocks,
+                                block,
+                                source_op,
+                                discr,
+                                op_size,
+                                valid_discrs,
+                                source_info,
+                                new_block,
+                            )
+                        }
+                        EnumCheckType::Uninhabited => insert_uninhabited_enum_check(
+                            tcx,
+                            local_decls,
+                            &mut basic_blocks[block],
+                            source_info,
+                            new_block,
+                        ),
+                        EnumCheckType::WithNiche {
+                            source_op,
+                            discr,
+                            op_size,
+                            offset,
+                            valid_range,
+                        } => insert_niche_check(
+                            tcx,
+                            local_decls,
+                            &mut basic_blocks[block],
+                            source_op,
+                            valid_range,
+                            discr,
+                            op_size,
+                            offset,
+                            source_info,
+                            new_block,
+                        ),
+                    }
+                }
+            }
+        }
+    }
+
+    fn is_required(&self) -> bool {
+        true
+    }
+}
+
+/// Represent the different kind of enum checks we can insert.
+enum EnumCheckType<'tcx> {
+    /// We know we try to create an uninhabited enum from an inhabited variant.
+    Uninhabited,
+    /// We know the enum does no niche optimizations and can thus easily compute
+    /// the valid discriminants.
+    Direct {
+        source_op: Operand<'tcx>,
+        discr: TyAndSize<'tcx>,
+        op_size: Size,
+        valid_discrs: Vec<u128>,
+    },
+    /// We try to construct an enum that has a niche.
+    WithNiche {
+        source_op: Operand<'tcx>,
+        discr: TyAndSize<'tcx>,
+        op_size: Size,
+        offset: Size,
+        valid_range: WrappingRange,
+    },
+}
+
+struct TyAndSize<'tcx> {
+    pub ty: Ty<'tcx>,
+    pub size: Size,
+}
+
+/// A [Visitor] that finds the construction of enums and evaluates which checks
+/// we should apply.
+struct EnumFinder<'a, 'tcx> {
+    tcx: TyCtxt<'tcx>,
+    local_decls: &'a mut LocalDecls<'tcx>,
+    typing_env: TypingEnv<'tcx>,
+    enums: Vec<EnumCheckType<'tcx>>,
+}
+
+impl<'a, 'tcx> EnumFinder<'a, 'tcx> {
+    fn new(
+        tcx: TyCtxt<'tcx>,
+        local_decls: &'a mut LocalDecls<'tcx>,
+        typing_env: TypingEnv<'tcx>,
+    ) -> Self {
+        EnumFinder { tcx, local_decls, typing_env, enums: Vec::new() }
+    }
+
+    /// Returns the found enum creations and which checks should be inserted.
+    fn into_found_enums(self) -> Vec<EnumCheckType<'tcx>> {
+        self.enums
+    }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for EnumFinder<'a, 'tcx> {
+    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
+        if let Rvalue::Cast(CastKind::Transmute, op, ty) = rvalue {
+            let ty::Adt(adt_def, _) = ty.kind() else {
+                return;
+            };
+            if !adt_def.is_enum() {
+                return;
+            }
+
+            let Ok(enum_layout) = self.tcx.layout_of(self.typing_env.as_query_input(*ty)) else {
+                return;
+            };
+            let Ok(op_layout) = self
+                .tcx
+                .layout_of(self.typing_env.as_query_input(op.ty(self.local_decls, self.tcx)))
+            else {
+                return;
+            };
+
+            match enum_layout.variants {
+                Variants::Empty if op_layout.is_uninhabited() => return,
+                // An empty enum that tries to be constructed from an inhabited value, this
+                // is never correct.
+                Variants::Empty => {
+                    // The enum layout is uninhabited but we construct it from sth inhabited.
+                    // This is always UB.
+                    self.enums.push(EnumCheckType::Uninhabited);
+                }
+                // Construction of Single value enums is always fine.
+                Variants::Single { .. } => {}
+                // Construction of an enum with multiple variants but no niche optimizations.
+                Variants::Multiple {
+                    tag_encoding: TagEncoding::Direct,
+                    tag: Scalar::Initialized { value, .. },
+                    ..
+                } => {
+                    let valid_discrs =
+                        adt_def.discriminants(self.tcx).map(|(_, discr)| discr.val).collect();
+
+                    let discr =
+                        TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) };
+                    self.enums.push(EnumCheckType::Direct {
+                        source_op: op.to_copy(),
+                        discr,
+                        op_size: op_layout.size,
+                        valid_discrs,
+                    });
+                }
+                // Construction of an enum with multiple variants and niche optimizations.
+                Variants::Multiple {
+                    tag_encoding: TagEncoding::Niche { .. },
+                    tag: Scalar::Initialized { value, valid_range, .. },
+                    tag_field,
+                    ..
+                } => {
+                    let discr =
+                        TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) };
+                    self.enums.push(EnumCheckType::WithNiche {
+                        source_op: op.to_copy(),
+                        discr,
+                        op_size: op_layout.size,
+                        offset: enum_layout.fields.offset(tag_field.as_usize()),
+                        valid_range,
+                    });
+                }
+                _ => return,
+            }
+
+            self.super_rvalue(rvalue, location);
+        }
+    }
+}
+
+fn split_block(
+    basic_blocks: &mut IndexVec<BasicBlock, BasicBlockData<'_>>,
+    location: Location,
+) -> BasicBlock {
+    let block_data = &mut basic_blocks[location.block];
+
+    // Drain every statement after this one and move the current terminator to a new basic block.
+    let new_block = BasicBlockData {
+        statements: block_data.statements.split_off(location.statement_index),
+        terminator: block_data.terminator.take(),
+        is_cleanup: block_data.is_cleanup,
+    };
+
+    basic_blocks.push(new_block)
+}
+
+/// Inserts the cast of an operand (any type) to a u128 value that holds the discriminant value.
+fn insert_discr_cast_to_u128<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
+    block_data: &mut BasicBlockData<'tcx>,
+    source_op: Operand<'tcx>,
+    discr: TyAndSize<'tcx>,
+    op_size: Size,
+    offset: Option<Size>,
+    source_info: SourceInfo,
+) -> Place<'tcx> {
+    let get_ty_for_size = |tcx: TyCtxt<'tcx>, size: Size| -> Ty<'tcx> {
+        match size.bytes() {
+            1 => tcx.types.u8,
+            2 => tcx.types.u16,
+            4 => tcx.types.u32,
+            8 => tcx.types.u64,
+            16 => tcx.types.u128,
+            invalid => bug!("Found discriminant with invalid size, has {} bytes", invalid),
+        }
+    };
+
+    let (cast_kind, discr_ty_bits) = if discr.size.bytes() < op_size.bytes() {
+        // The discriminant is less wide than the operand, cast the operand into
+        // [MaybeUninit; N] and then index into it.
+        let mu = Ty::new_maybe_uninit(tcx, tcx.types.u8);
+        let array_len = op_size.bytes();
+        let mu_array_ty = Ty::new_array(tcx, mu, array_len);
+        let mu_array =
+            local_decls.push(LocalDecl::with_source_info(mu_array_ty, source_info)).into();
+        let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, mu_array_ty);
+        block_data.statements.push(Statement {
+            source_info,
+            kind: StatementKind::Assign(Box::new((mu_array, rvalue))),
+        });
+
+        // Index into the array of MaybeUninit to get something that is actually
+        // as wide as the discriminant.
+        let offset = offset.unwrap_or(Size::ZERO);
+        let smaller_mu_array = mu_array.project_deeper(
+            &[ProjectionElem::Subslice {
+                from: offset.bytes(),
+                to: offset.bytes() + discr.size.bytes(),
+                from_end: false,
+            }],
+            tcx,
+        );
+
+        (CastKind::Transmute, Operand::Copy(smaller_mu_array))
+    } else {
+        let operand_int_ty = get_ty_for_size(tcx, op_size);
+
+        let op_as_int =
+            local_decls.push(LocalDecl::with_source_info(operand_int_ty, source_info)).into();
+        let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, operand_int_ty);
+        block_data.statements.push(Statement {
+            source_info,
+            kind: StatementKind::Assign(Box::new((op_as_int, rvalue))),
+        });
+
+        (CastKind::IntToInt, Operand::Copy(op_as_int))
+    };
+
+    // Cast the resulting value to the actual discriminant integer type.
+    let rvalue = Rvalue::Cast(cast_kind, discr_ty_bits, discr.ty);
+    let discr_in_discr_ty =
+        local_decls.push(LocalDecl::with_source_info(discr.ty, source_info)).into();
+    block_data.statements.push(Statement {
+        source_info,
+        kind: StatementKind::Assign(Box::new((discr_in_discr_ty, rvalue))),
+    });
+
+    // Cast the discriminant to a u128 (base for comparisions of enum discriminants).
+    let const_u128 = Ty::new_uint(tcx, ty::UintTy::U128);
+    let rvalue = Rvalue::Cast(CastKind::IntToInt, Operand::Copy(discr_in_discr_ty), const_u128);
+    let discr = local_decls.push(LocalDecl::with_source_info(const_u128, source_info)).into();
+    block_data
+        .statements
+        .push(Statement { source_info, kind: StatementKind::Assign(Box::new((discr, rvalue))) });
+
+    discr
+}
+
+fn insert_direct_enum_check<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
+    basic_blocks: &mut IndexVec<BasicBlock, BasicBlockData<'tcx>>,
+    current_block: BasicBlock,
+    source_op: Operand<'tcx>,
+    discr: TyAndSize<'tcx>,
+    op_size: Size,
+    discriminants: Vec<u128>,
+    source_info: SourceInfo,
+    new_block: BasicBlock,
+) {
+    // Insert a new target block that is branched to in case of an invalid discriminant.
+    let invalid_discr_block_data = BasicBlockData::new(None, false);
+    let invalid_discr_block = basic_blocks.push(invalid_discr_block_data);
+    let block_data = &mut basic_blocks[current_block];
+    let discr = insert_discr_cast_to_u128(
+        tcx,
+        local_decls,
+        block_data,
+        source_op,
+        discr,
+        op_size,
+        None,
+        source_info,
+    );
+
+    // Branch based on the discriminant value.
+    block_data.terminator = Some(Terminator {
+        source_info,
+        kind: TerminatorKind::SwitchInt {
+            discr: Operand::Copy(discr),
+            targets: SwitchTargets::new(
+                discriminants.into_iter().map(|discr| (discr, new_block)),
+                invalid_discr_block,
+            ),
+        },
+    });
+
+    // Abort in case of an invalid enum discriminant.
+    basic_blocks[invalid_discr_block].terminator = Some(Terminator {
+        source_info,
+        kind: TerminatorKind::Assert {
+            cond: Operand::Constant(Box::new(ConstOperand {
+                span: source_info.span,
+                user_ty: None,
+                const_: Const::Val(ConstValue::from_bool(false), tcx.types.bool),
+            })),
+            expected: true,
+            target: new_block,
+            msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr))),
+            // This calls panic_invalid_enum_construction, which is #[rustc_nounwind].
+            // We never want to insert an unwind into unsafe code, because unwinding could
+            // make a failing UB check turn into much worse UB when we start unwinding.
+            unwind: UnwindAction::Unreachable,
+        },
+    });
+}
+
+fn insert_uninhabited_enum_check<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
+    block_data: &mut BasicBlockData<'tcx>,
+    source_info: SourceInfo,
+    new_block: BasicBlock,
+) {
+    let is_ok: Place<'_> =
+        local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
+    block_data.statements.push(Statement {
+        source_info,
+        kind: StatementKind::Assign(Box::new((
+            is_ok,
+            Rvalue::Use(Operand::Constant(Box::new(ConstOperand {
+                span: source_info.span,
+                user_ty: None,
+                const_: Const::Val(ConstValue::from_bool(false), tcx.types.bool),
+            }))),
+        ))),
+    });
+
+    block_data.terminator = Some(Terminator {
+        source_info,
+        kind: TerminatorKind::Assert {
+            cond: Operand::Copy(is_ok),
+            expected: true,
+            target: new_block,
+            msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Constant(Box::new(
+                ConstOperand {
+                    span: source_info.span,
+                    user_ty: None,
+                    const_: Const::Val(ConstValue::from_u128(0), tcx.types.u128),
+                },
+            )))),
+            // This calls panic_invalid_enum_construction, which is #[rustc_nounwind].
+            // We never want to insert an unwind into unsafe code, because unwinding could
+            // make a failing UB check turn into much worse UB when we start unwinding.
+            unwind: UnwindAction::Unreachable,
+        },
+    });
+}
+
+fn insert_niche_check<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
+    block_data: &mut BasicBlockData<'tcx>,
+    source_op: Operand<'tcx>,
+    valid_range: WrappingRange,
+    discr: TyAndSize<'tcx>,
+    op_size: Size,
+    offset: Size,
+    source_info: SourceInfo,
+    new_block: BasicBlock,
+) {
+    let discr = insert_discr_cast_to_u128(
+        tcx,
+        local_decls,
+        block_data,
+        source_op,
+        discr,
+        op_size,
+        Some(offset),
+        source_info,
+    );
+
+    // Compare the discriminant agains the valid_range.
+    let start_const = Operand::Constant(Box::new(ConstOperand {
+        span: source_info.span,
+        user_ty: None,
+        const_: Const::Val(ConstValue::from_u128(valid_range.start), tcx.types.u128),
+    }));
+    let end_start_diff_const = Operand::Constant(Box::new(ConstOperand {
+        span: source_info.span,
+        user_ty: None,
+        const_: Const::Val(
+            ConstValue::from_u128(u128::wrapping_sub(valid_range.end, valid_range.start)),
+            tcx.types.u128,
+        ),
+    }));
+
+    let discr_diff: Place<'_> =
+        local_decls.push(LocalDecl::with_source_info(tcx.types.u128, source_info)).into();
+    block_data.statements.push(Statement {
+        source_info,
+        kind: StatementKind::Assign(Box::new((
+            discr_diff,
+            Rvalue::BinaryOp(BinOp::Sub, Box::new((Operand::Copy(discr), start_const))),
+        ))),
+    });
+
+    let is_ok: Place<'_> =
+        local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
+    block_data.statements.push(Statement {
+        source_info,
+        kind: StatementKind::Assign(Box::new((
+            is_ok,
+            Rvalue::BinaryOp(
+                // This is a `WrappingRange`, so make sure to get the wrapping right.
+                BinOp::Le,
+                Box::new((Operand::Copy(discr_diff), end_start_diff_const)),
+            ),
+        ))),
+    });
+
+    block_data.terminator = Some(Terminator {
+        source_info,
+        kind: TerminatorKind::Assert {
+            cond: Operand::Copy(is_ok),
+            expected: true,
+            target: new_block,
+            msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr))),
+            // This calls panic_invalid_enum_construction, which is #[rustc_nounwind].
+            // We never want to insert an unwind into unsafe code, because unwinding could
+            // make a failing UB check turn into much worse UB when we start unwinding.
+            unwind: UnwindAction::Unreachable,
+        },
+    });
+}
diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs
index f48dba9663a..c27087fea11 100644
--- a/compiler/rustc_mir_transform/src/inline.rs
+++ b/compiler/rustc_mir_transform/src/inline.rs
@@ -770,14 +770,15 @@ fn check_mir_is_available<'tcx, I: Inliner<'tcx>>(
         return Ok(());
     }
 
-    if callee_def_id.is_local()
+    if let Some(callee_def_id) = callee_def_id.as_local()
         && !inliner
             .tcx()
             .is_lang_item(inliner.tcx().parent(caller_def_id), rustc_hir::LangItem::FnOnce)
     {
         // If we know for sure that the function we're calling will itself try to
         // call us, then we avoid inlining that function.
-        if inliner.tcx().mir_callgraph_reachable((callee, caller_def_id.expect_local())) {
+        if inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local()).contains(&callee_def_id)
+        {
             debug!("query cycle avoidance");
             return Err("caller might be reachable from callee");
         }
diff --git a/compiler/rustc_mir_transform/src/inline/cycle.rs b/compiler/rustc_mir_transform/src/inline/cycle.rs
index a944960ce4a..08f3ce5fd67 100644
--- a/compiler/rustc_mir_transform/src/inline/cycle.rs
+++ b/compiler/rustc_mir_transform/src/inline/cycle.rs
@@ -1,5 +1,6 @@
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
 use rustc_data_structures::stack::ensure_sufficient_stack;
+use rustc_data_structures::unord::UnordSet;
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_middle::mir::TerminatorKind;
 use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, TyCtxt, TypeVisitableExt};
@@ -7,137 +8,143 @@ use rustc_session::Limit;
 use rustc_span::sym;
 use tracing::{instrument, trace};
 
-// FIXME: check whether it is cheaper to precompute the entire call graph instead of invoking
-// this query ridiculously often.
-#[instrument(level = "debug", skip(tcx, root, target))]
-pub(crate) fn mir_callgraph_reachable<'tcx>(
+#[instrument(level = "debug", skip(tcx), ret)]
+fn should_recurse<'tcx>(tcx: TyCtxt<'tcx>, callee: ty::Instance<'tcx>) -> bool {
+    match callee.def {
+        // If there is no MIR available (either because it was not in metadata or
+        // because it has no MIR because it's an extern function), then the inliner
+        // won't cause cycles on this.
+        InstanceKind::Item(_) => {
+            if !tcx.is_mir_available(callee.def_id()) {
+                return false;
+            }
+        }
+
+        // These have no own callable MIR.
+        InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => return false,
+
+        // These have MIR and if that MIR is inlined, instantiated and then inlining is run
+        // again, a function item can end up getting inlined. Thus we'll be able to cause
+        // a cycle that way
+        InstanceKind::VTableShim(_)
+        | InstanceKind::ReifyShim(..)
+        | InstanceKind::FnPtrShim(..)
+        | InstanceKind::ClosureOnceShim { .. }
+        | InstanceKind::ConstructCoroutineInClosureShim { .. }
+        | InstanceKind::ThreadLocalShim { .. }
+        | InstanceKind::CloneShim(..) => {}
+
+        // This shim does not call any other functions, thus there can be no recursion.
+        InstanceKind::FnPtrAddrShim(..) => return false,
+
+        // FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to
+        // have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
+        // needs some more analysis.
+        InstanceKind::DropGlue(..)
+        | InstanceKind::FutureDropPollShim(..)
+        | InstanceKind::AsyncDropGlue(..)
+        | InstanceKind::AsyncDropGlueCtorShim(..) => {
+            if callee.has_param() {
+                return false;
+            }
+        }
+    }
+
+    crate::pm::should_run_pass(tcx, &crate::inline::Inline, crate::pm::Optimizations::Allowed)
+        || crate::inline::ForceInline::should_run_pass_for_callee(tcx, callee.def.def_id())
+}
+
+#[instrument(
+    level = "debug",
+    skip(tcx, typing_env, seen, involved, recursion_limiter, recursion_limit),
+    ret
+)]
+fn process<'tcx>(
     tcx: TyCtxt<'tcx>,
-    (root, target): (ty::Instance<'tcx>, LocalDefId),
+    typing_env: ty::TypingEnv<'tcx>,
+    caller: ty::Instance<'tcx>,
+    target: LocalDefId,
+    seen: &mut FxHashSet<ty::Instance<'tcx>>,
+    involved: &mut FxHashSet<LocalDefId>,
+    recursion_limiter: &mut FxHashMap<DefId, usize>,
+    recursion_limit: Limit,
 ) -> bool {
-    trace!(%root, target = %tcx.def_path_str(target));
-    assert_ne!(
-        root.def_id().expect_local(),
-        target,
-        "you should not call `mir_callgraph_reachable` on immediate self recursion"
-    );
-    assert!(
-        matches!(root.def, InstanceKind::Item(_)),
-        "you should not call `mir_callgraph_reachable` on shims"
-    );
-    assert!(
-        !tcx.is_constructor(root.def_id()),
-        "you should not call `mir_callgraph_reachable` on enum/struct constructor functions"
-    );
-    #[instrument(
-        level = "debug",
-        skip(tcx, typing_env, target, stack, seen, recursion_limiter, caller, recursion_limit)
-    )]
-    fn process<'tcx>(
-        tcx: TyCtxt<'tcx>,
-        typing_env: ty::TypingEnv<'tcx>,
-        caller: ty::Instance<'tcx>,
-        target: LocalDefId,
-        stack: &mut Vec<ty::Instance<'tcx>>,
-        seen: &mut FxHashSet<ty::Instance<'tcx>>,
-        recursion_limiter: &mut FxHashMap<DefId, usize>,
-        recursion_limit: Limit,
-    ) -> bool {
-        trace!(%caller);
-        for &(callee, args) in tcx.mir_inliner_callees(caller.def) {
-            let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions(
-                tcx,
-                typing_env,
-                ty::EarlyBinder::bind(args),
-            ) else {
-                trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping");
-                continue;
-            };
-            let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else {
-                trace!(?callee, "cannot resolve, skipping");
-                continue;
-            };
+    trace!(%caller);
+    let mut cycle_found = false;
 
-            // Found a path.
-            if callee.def_id() == target.to_def_id() {
-                return true;
-            }
+    for &(callee, args) in tcx.mir_inliner_callees(caller.def) {
+        let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions(
+            tcx,
+            typing_env,
+            ty::EarlyBinder::bind(args),
+        ) else {
+            trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping");
+            continue;
+        };
+        let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else {
+            trace!(?callee, "cannot resolve, skipping");
+            continue;
+        };
 
-            if tcx.is_constructor(callee.def_id()) {
-                trace!("constructors always have MIR");
-                // Constructor functions cannot cause a query cycle.
-                continue;
-            }
+        // Found a path.
+        if callee.def_id() == target.to_def_id() {
+            cycle_found = true;
+        }
 
-            match callee.def {
-                InstanceKind::Item(_) => {
-                    // If there is no MIR available (either because it was not in metadata or
-                    // because it has no MIR because it's an extern function), then the inliner
-                    // won't cause cycles on this.
-                    if !tcx.is_mir_available(callee.def_id()) {
-                        trace!(?callee, "no mir available, skipping");
-                        continue;
-                    }
-                }
-                // These have no own callable MIR.
-                InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => continue,
-                // These have MIR and if that MIR is inlined, instantiated and then inlining is run
-                // again, a function item can end up getting inlined. Thus we'll be able to cause
-                // a cycle that way
-                InstanceKind::VTableShim(_)
-                | InstanceKind::ReifyShim(..)
-                | InstanceKind::FnPtrShim(..)
-                | InstanceKind::ClosureOnceShim { .. }
-                | InstanceKind::ConstructCoroutineInClosureShim { .. }
-                | InstanceKind::ThreadLocalShim { .. }
-                | InstanceKind::CloneShim(..) => {}
-
-                // This shim does not call any other functions, thus there can be no recursion.
-                InstanceKind::FnPtrAddrShim(..) => {
-                    continue;
-                }
-                InstanceKind::DropGlue(..)
-                | InstanceKind::FutureDropPollShim(..)
-                | InstanceKind::AsyncDropGlue(..)
-                | InstanceKind::AsyncDropGlueCtorShim(..) => {
-                    // FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to
-                    // have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
-                    // needs some more analysis.
-                    if callee.has_param() {
-                        continue;
-                    }
-                }
-            }
+        if tcx.is_constructor(callee.def_id()) {
+            trace!("constructors always have MIR");
+            // Constructor functions cannot cause a query cycle.
+            continue;
+        }
+
+        if !should_recurse(tcx, callee) {
+            continue;
+        }
 
-            if seen.insert(callee) {
-                let recursion = recursion_limiter.entry(callee.def_id()).or_default();
-                trace!(?callee, recursion = *recursion);
-                if recursion_limit.value_within_limit(*recursion) {
-                    *recursion += 1;
-                    stack.push(callee);
-                    let found_recursion = ensure_sufficient_stack(|| {
-                        process(
-                            tcx,
-                            typing_env,
-                            callee,
-                            target,
-                            stack,
-                            seen,
-                            recursion_limiter,
-                            recursion_limit,
-                        )
-                    });
-                    if found_recursion {
-                        return true;
-                    }
-                    stack.pop();
-                } else {
-                    // Pessimistically assume that there could be recursion.
-                    return true;
+        if seen.insert(callee) {
+            let recursion = recursion_limiter.entry(callee.def_id()).or_default();
+            trace!(?callee, recursion = *recursion);
+            let found_recursion = if recursion_limit.value_within_limit(*recursion) {
+                *recursion += 1;
+                ensure_sufficient_stack(|| {
+                    process(
+                        tcx,
+                        typing_env,
+                        callee,
+                        target,
+                        seen,
+                        involved,
+                        recursion_limiter,
+                        recursion_limit,
+                    )
+                })
+            } else {
+                // Pessimistically assume that there could be recursion.
+                true
+            };
+            if found_recursion {
+                if let Some(callee) = callee.def_id().as_local() {
+                    // Calling `optimized_mir` of a non-local definition cannot cycle.
+                    involved.insert(callee);
                 }
+                cycle_found = true;
             }
         }
-        false
     }
+
+    cycle_found
+}
+
+#[instrument(level = "debug", skip(tcx), ret)]
+pub(crate) fn mir_callgraph_cyclic<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    root: LocalDefId,
+) -> UnordSet<LocalDefId> {
+    assert!(
+        !tcx.is_constructor(root.to_def_id()),
+        "you should not call `mir_callgraph_reachable` on enum/struct constructor functions"
+    );
+
     // FIXME(-Znext-solver=no): Remove this hack when trait solver overflow can return an error.
     // In code like that pointed out in #128887, the type complexity we ask the solver to deal with
     // grows as we recurse into the call graph. If we use the same recursion limit here and in the
@@ -146,16 +153,32 @@ pub(crate) fn mir_callgraph_reachable<'tcx>(
     // the default recursion limits are quite generous for us. If we need to recurse 64 times
     // into the call graph, we're probably not going to find any useful MIR inlining.
     let recursion_limit = tcx.recursion_limit() / 2;
+    let mut involved = FxHashSet::default();
+    let typing_env = ty::TypingEnv::post_analysis(tcx, root);
+    let Ok(Some(root_instance)) = ty::Instance::try_resolve(
+        tcx,
+        typing_env,
+        root.to_def_id(),
+        ty::GenericArgs::identity_for_item(tcx, root.to_def_id()),
+    ) else {
+        trace!("cannot resolve, skipping");
+        return involved.into();
+    };
+    if !should_recurse(tcx, root_instance) {
+        trace!("cannot walk, skipping");
+        return involved.into();
+    }
     process(
         tcx,
-        ty::TypingEnv::post_analysis(tcx, target),
+        typing_env,
+        root_instance,
         root,
-        target,
-        &mut Vec::new(),
         &mut FxHashSet::default(),
+        &mut involved,
         &mut FxHashMap::default(),
         recursion_limit,
-    )
+    );
+    involved.into()
 }
 
 pub(crate) fn mir_inliner_callees<'tcx>(
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index 572ad585c8c..c4415294264 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -117,6 +117,7 @@ declare_passes! {
     mod check_inline : CheckForceInline;
     mod check_call_recursion : CheckCallRecursion, CheckDropRecursion;
     mod check_alignment : CheckAlignment;
+    mod check_enums : CheckEnums;
     mod check_const_item_mutation : CheckConstItemMutation;
     mod check_null : CheckNull;
     mod check_packed_ref : CheckPackedRef;
@@ -215,7 +216,7 @@ pub fn provide(providers: &mut Providers) {
         optimized_mir,
         is_mir_available,
         is_ctfe_mir_available: is_mir_available,
-        mir_callgraph_reachable: inline::cycle::mir_callgraph_reachable,
+        mir_callgraph_cyclic: inline::cycle::mir_callgraph_cyclic,
         mir_inliner_callees: inline::cycle::mir_inliner_callees,
         promoted_mir,
         deduced_param_attrs: deduce_param_attrs::deduced_param_attrs,
@@ -666,6 +667,7 @@ pub(crate) fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'
             // Add some UB checks before any UB gets optimized away.
             &check_alignment::CheckAlignment,
             &check_null::CheckNull,
+            &check_enums::CheckEnums,
             // Before inlining: trim down MIR with passes to reduce inlining work.
 
             // Has to be done before inlining, otherwise actual call will be almost always inlined.
diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs
index 38da5c390ef..798f4da9b3b 100644
--- a/compiler/rustc_monomorphize/src/collector.rs
+++ b/compiler/rustc_monomorphize/src/collector.rs
@@ -834,6 +834,9 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
                 mir::AssertKind::NullPointerDereference => {
                     push_mono_lang_item(self, LangItem::PanicNullPointerDereference);
                 }
+                mir::AssertKind::InvalidEnumConstruction(_) => {
+                    push_mono_lang_item(self, LangItem::PanicInvalidEnumConstruction);
+                }
                 _ => {
                     push_mono_lang_item(self, msg.panic_function());
                 }
diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs
index 42b3e59b73a..85e71ed2c25 100644
--- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs
+++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs
@@ -506,6 +506,9 @@ impl<'tcx> Stable<'tcx> for mir::AssertMessage<'tcx> {
             AssertKind::NullPointerDereference => {
                 stable_mir::mir::AssertMessage::NullPointerDereference
             }
+            AssertKind::InvalidEnumConstruction(source) => {
+                stable_mir::mir::AssertMessage::InvalidEnumConstruction(source.stable(tables))
+            }
         }
     }
 }
diff --git a/compiler/rustc_smir/src/stable_mir/mir/body.rs b/compiler/rustc_smir/src/stable_mir/mir/body.rs
index 660cd7db080..e4b7659ce7f 100644
--- a/compiler/rustc_smir/src/stable_mir/mir/body.rs
+++ b/compiler/rustc_smir/src/stable_mir/mir/body.rs
@@ -270,6 +270,7 @@ pub enum AssertMessage {
     ResumedAfterDrop(CoroutineKind),
     MisalignedPointerDereference { required: Operand, found: Operand },
     NullPointerDereference,
+    InvalidEnumConstruction(Operand),
 }
 
 impl AssertMessage {
@@ -342,6 +343,9 @@ impl AssertMessage {
                 Ok("misaligned pointer dereference")
             }
             AssertMessage::NullPointerDereference => Ok("null pointer dereference occurred"),
+            AssertMessage::InvalidEnumConstruction(_) => {
+                Ok("trying to construct an enum from an invalid value")
+            }
         }
     }
 }
diff --git a/compiler/rustc_smir/src/stable_mir/mir/pretty.rs b/compiler/rustc_smir/src/stable_mir/mir/pretty.rs
index ba20651f993..b068a9a1081 100644
--- a/compiler/rustc_smir/src/stable_mir/mir/pretty.rs
+++ b/compiler/rustc_smir/src/stable_mir/mir/pretty.rs
@@ -313,6 +313,10 @@ fn pretty_assert_message<W: Write>(writer: &mut W, msg: &AssertMessage) -> io::R
         AssertMessage::NullPointerDereference => {
             write!(writer, "\"null pointer dereference occurred\"")
         }
+        AssertMessage::InvalidEnumConstruction(op) => {
+            let pretty_op = pretty_operand(op);
+            write!(writer, "\"trying to construct an enum from an invalid value {{}}\",{pretty_op}")
+        }
         AssertMessage::ResumedAfterReturn(_)
         | AssertMessage::ResumedAfterPanic(_)
         | AssertMessage::ResumedAfterDrop(_) => {
diff --git a/compiler/rustc_smir/src/stable_mir/mir/visit.rs b/compiler/rustc_smir/src/stable_mir/mir/visit.rs
index e21dc11eea9..b7dd433eb09 100644
--- a/compiler/rustc_smir/src/stable_mir/mir/visit.rs
+++ b/compiler/rustc_smir/src/stable_mir/mir/visit.rs
@@ -367,7 +367,8 @@ macro_rules! make_mir_visitor {
                     }
                     AssertMessage::OverflowNeg(op)
                     | AssertMessage::DivisionByZero(op)
-                    | AssertMessage::RemainderByZero(op) => {
+                    | AssertMessage::RemainderByZero(op)
+                    | AssertMessage::InvalidEnumConstruction(op) => {
                         self.visit_operand(op, location);
                     }
                     AssertMessage::ResumedAfterReturn(_)
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 4be7c7ba510..a51f4e3dc07 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -150,14 +150,10 @@ symbols! {
     // As well as the symbols listed, there are symbols for the strings
     // "0", "1", ..., "9", which are accessible via `sym::integer`.
     //
-    // The proc macro will abort if symbols are not in alphabetical order (as
-    // defined by `impl Ord for str`) or if any symbols are duplicated. Vim
-    // users can sort the list by selecting it and executing the command
-    // `:'<,'>!LC_ALL=C sort`.
-    //
     // There is currently no checking that all symbols are used; that would be
     // nice to have.
     Symbols {
+        // tidy-alphabetical-start
         Abi,
         AcqRel,
         Acquire,
@@ -175,18 +171,18 @@ symbols! {
         AsyncGenPending,
         AsyncGenReady,
         AtomicBool,
-        AtomicI128,
+        AtomicI8,
         AtomicI16,
         AtomicI32,
         AtomicI64,
-        AtomicI8,
+        AtomicI128,
         AtomicIsize,
         AtomicPtr,
-        AtomicU128,
+        AtomicU8,
         AtomicU16,
         AtomicU32,
         AtomicU64,
-        AtomicU8,
+        AtomicU128,
         AtomicUsize,
         BTreeEntry,
         BTreeMap,
@@ -607,10 +603,10 @@ symbols! {
         catch_unwind,
         cause,
         cdylib,
-        ceilf128,
         ceilf16,
         ceilf32,
         ceilf64,
+        ceilf128,
         cfg,
         cfg_accessible,
         cfg_attr,
@@ -747,10 +743,10 @@ symbols! {
         copy,
         copy_closures,
         copy_nonoverlapping,
-        copysignf128,
         copysignf16,
         copysignf32,
         copysignf64,
+        copysignf128,
         core,
         core_panic,
         core_panic_2015_macro,
@@ -763,10 +759,10 @@ symbols! {
         coroutine_state,
         coroutine_yield,
         coroutines,
-        cosf128,
         cosf16,
         cosf32,
         cosf64,
+        cosf128,
         count,
         coverage,
         coverage_attribute,
@@ -874,8 +870,8 @@ symbols! {
         dotdot_in_tuple_patterns,
         dotdoteq_in_patterns,
         dreg,
-        dreg_low16,
         dreg_low8,
+        dreg_low16,
         drop,
         drop_in_place,
         drop_types_in_const,
@@ -928,16 +924,16 @@ symbols! {
         exhaustive_integer_patterns,
         exhaustive_patterns,
         existential_type,
-        exp2f128,
         exp2f16,
         exp2f32,
         exp2f64,
+        exp2f128,
         expect,
         expected,
-        expf128,
         expf16,
         expf32,
         expf64,
+        expf128,
         explicit_extern_abis,
         explicit_generic_args_with_impl_trait,
         explicit_tail_calls,
@@ -958,9 +954,6 @@ symbols! {
         external,
         external_doc,
         f,
-        f128,
-        f128_epsilon,
-        f128_nan,
         f16,
         f16_epsilon,
         f16_nan,
@@ -999,10 +992,13 @@ symbols! {
         f64_legacy_const_neg_infinity,
         f64_legacy_const_radix,
         f64_nan,
-        fabsf128,
+        f128,
+        f128_epsilon,
+        f128_nan,
         fabsf16,
         fabsf32,
         fabsf64,
+        fabsf128,
         fadd_algebraic,
         fadd_fast,
         fake_variadic,
@@ -1024,22 +1020,22 @@ symbols! {
         flags,
         float,
         float_to_int_unchecked,
-        floorf128,
         floorf16,
         floorf32,
         floorf64,
-        fmaf128,
+        floorf128,
         fmaf16,
         fmaf32,
         fmaf64,
+        fmaf128,
         fmt,
         fmt_debug,
         fmul_algebraic,
         fmul_fast,
-        fmuladdf128,
         fmuladdf16,
         fmuladdf32,
         fmuladdf64,
+        fmuladdf128,
         fn_align,
         fn_body,
         fn_delegation,
@@ -1140,13 +1136,12 @@ symbols! {
         html_root_url,
         hwaddress,
         i,
-        i128,
-        i128_legacy_const_max,
-        i128_legacy_const_min,
-        i128_legacy_fn_max_value,
-        i128_legacy_fn_min_value,
-        i128_legacy_mod,
-        i128_type,
+        i8,
+        i8_legacy_const_max,
+        i8_legacy_const_min,
+        i8_legacy_fn_max_value,
+        i8_legacy_fn_min_value,
+        i8_legacy_mod,
         i16,
         i16_legacy_const_max,
         i16_legacy_const_min,
@@ -1165,12 +1160,13 @@ symbols! {
         i64_legacy_fn_max_value,
         i64_legacy_fn_min_value,
         i64_legacy_mod,
-        i8,
-        i8_legacy_const_max,
-        i8_legacy_const_min,
-        i8_legacy_fn_max_value,
-        i8_legacy_fn_min_value,
-        i8_legacy_mod,
+        i128,
+        i128_legacy_const_max,
+        i128_legacy_const_min,
+        i128_legacy_fn_max_value,
+        i128_legacy_fn_min_value,
+        i128_legacy_mod,
+        i128_type,
         ident,
         if_let,
         if_let_guard,
@@ -1292,19 +1288,19 @@ symbols! {
         loaded_from_disk,
         local,
         local_inner_macros,
-        log10f128,
-        log10f16,
-        log10f32,
-        log10f64,
-        log2f128,
         log2f16,
         log2f32,
         log2f64,
+        log2f128,
+        log10f16,
+        log10f32,
+        log10f64,
+        log10f128,
         log_syntax,
-        logf128,
         logf16,
         logf32,
         logf64,
+        logf128,
         loongarch_target_feature,
         loop_break_value,
         loop_match,
@@ -1334,14 +1330,14 @@ symbols! {
         match_beginning_vert,
         match_default_bindings,
         matches_macro,
-        maximumf128,
         maximumf16,
         maximumf32,
         maximumf64,
-        maxnumf128,
+        maximumf128,
         maxnumf16,
         maxnumf32,
         maxnumf64,
+        maxnumf128,
         may_dangle,
         may_unwind,
         maybe_uninit,
@@ -1372,14 +1368,14 @@ symbols! {
         min_generic_const_args,
         min_specialization,
         min_type_alias_impl_trait,
-        minimumf128,
         minimumf16,
         minimumf32,
         minimumf64,
-        minnumf128,
+        minimumf128,
         minnumf16,
         minnumf32,
         minnumf64,
+        minnumf128,
         mips_target_feature,
         mir_assume,
         mir_basic_block,
@@ -1586,6 +1582,7 @@ symbols! {
         panic_implementation,
         panic_in_cleanup,
         panic_info,
+        panic_invalid_enum_construction,
         panic_location,
         panic_misaligned_pointer_dereference,
         panic_nounwind,
@@ -1633,14 +1630,14 @@ symbols! {
         post_dash_lto: "post-lto",
         postfix_match,
         powerpc_target_feature,
-        powf128,
         powf16,
         powf32,
         powf64,
-        powif128,
+        powf128,
         powif16,
         powif32,
         powif64,
+        powif128,
         pre_dash_lto: "pre-lto",
         precise_capturing,
         precise_capturing_in_traits,
@@ -1785,14 +1782,14 @@ symbols! {
         ropi_rwpi: "ropi-rwpi",
         rotate_left,
         rotate_right,
-        round_ties_even_f128,
         round_ties_even_f16,
         round_ties_even_f32,
         round_ties_even_f64,
-        roundf128,
+        round_ties_even_f128,
         roundf16,
         roundf32,
         roundf64,
+        roundf128,
         rt,
         rtm_target_feature,
         rust,
@@ -1972,8 +1969,8 @@ symbols! {
         simd_fexp2,
         simd_ffi,
         simd_flog,
-        simd_flog10,
         simd_flog2,
+        simd_flog10,
         simd_floor,
         simd_fma,
         simd_fmax,
@@ -2021,10 +2018,10 @@ symbols! {
         simd_with_exposed_provenance,
         simd_xor,
         since,
-        sinf128,
         sinf16,
         sinf32,
         sinf64,
+        sinf128,
         size,
         size_of,
         size_of_val,
@@ -2046,10 +2043,10 @@ symbols! {
         specialization,
         speed,
         spotlight,
-        sqrtf128,
         sqrtf16,
         sqrtf32,
         sqrtf64,
+        sqrtf128,
         sreg,
         sreg_low16,
         sse,
@@ -2127,10 +2124,10 @@ symbols! {
         target_has_atomic,
         target_has_atomic_equal_alignment,
         target_has_atomic_load_store,
-        target_has_reliable_f128,
-        target_has_reliable_f128_math,
         target_has_reliable_f16,
         target_has_reliable_f16_math,
+        target_has_reliable_f128,
+        target_has_reliable_f128_math,
         target_os,
         target_pointer_width,
         target_thread_local,
@@ -2173,10 +2170,10 @@ symbols! {
         transparent_enums,
         transparent_unions,
         trivial_bounds,
-        truncf128,
         truncf16,
         truncf32,
         truncf64,
+        truncf128,
         try_blocks,
         try_capture,
         try_from,
@@ -2205,12 +2202,12 @@ symbols! {
         type_name,
         type_privacy_lints,
         typed_swap_nonoverlapping,
-        u128,
-        u128_legacy_const_max,
-        u128_legacy_const_min,
-        u128_legacy_fn_max_value,
-        u128_legacy_fn_min_value,
-        u128_legacy_mod,
+        u8,
+        u8_legacy_const_max,
+        u8_legacy_const_min,
+        u8_legacy_fn_max_value,
+        u8_legacy_fn_min_value,
+        u8_legacy_mod,
         u16,
         u16_legacy_const_max,
         u16_legacy_const_min,
@@ -2229,12 +2226,12 @@ symbols! {
         u64_legacy_fn_max_value,
         u64_legacy_fn_min_value,
         u64_legacy_mod,
-        u8,
-        u8_legacy_const_max,
-        u8_legacy_const_min,
-        u8_legacy_fn_max_value,
-        u8_legacy_fn_min_value,
-        u8_legacy_mod,
+        u128,
+        u128_legacy_const_max,
+        u128_legacy_const_min,
+        u128_legacy_fn_max_value,
+        u128_legacy_fn_min_value,
+        u128_legacy_mod,
         ub_checks,
         unaligned_volatile_load,
         unaligned_volatile_store,
@@ -2387,6 +2384,7 @@ symbols! {
         zfh,
         zfhmin,
         zmm_reg,
+        // tidy-alphabetical-end
     }
 }
 
diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs
index 927a2ce84ea..a7b915c4845 100644
--- a/compiler/rustc_type_ir/src/binder.rs
+++ b/compiler/rustc_type_ir/src/binder.rs
@@ -622,6 +622,17 @@ impl<I: Interner, T: TypeFoldable<I>> ty::EarlyBinder<I, T> {
     where
         A: SliceLike<Item = I::GenericArg>,
     {
+        // Nothing to fold, so let's avoid visiting things and possibly re-hashing/equating
+        // them when interning. Perf testing found this to be a modest improvement.
+        // See: <https://github.com/rust-lang/rust/pull/142317>
+        if args.is_empty() {
+            assert!(
+                !self.value.has_param(),
+                "{:?} has parameters, but no args were provided in instantiate",
+                self.value,
+            );
+            return self.value;
+        }
         let mut folder = ArgFolder { cx, args: args.as_slice(), binders_passed: 0 };
         self.value.fold_with(&mut folder)
     }
diff --git a/library/alloc/src/collections/btree/set.rs b/library/alloc/src/collections/btree/set.rs
index aa9e5fce1d4..d50ce02bda7 100644
--- a/library/alloc/src/collections/btree/set.rs
+++ b/library/alloc/src/collections/btree/set.rs
@@ -1517,9 +1517,7 @@ impl<T: Ord, const N: usize> From<[T; N]> for BTreeSet<T> {
 
         // use stable sort to preserve the insertion order.
         arr.sort();
-        let iter = IntoIterator::into_iter(arr).map(|k| (k, SetValZST::default()));
-        let map = BTreeMap::bulk_build_from_sorted_iter(iter, Global);
-        BTreeSet { map }
+        BTreeSet::from_sorted_iter(IntoIterator::into_iter(arr), Global)
     }
 }
 
diff --git a/library/core/src/panic/location.rs b/library/core/src/panic/location.rs
index f1eedede8aa..97227020885 100644
--- a/library/core/src/panic/location.rs
+++ b/library/core/src/panic/location.rs
@@ -1,5 +1,7 @@
 use crate::ffi::CStr;
 use crate::fmt;
+use crate::marker::PhantomData;
+use crate::ptr::NonNull;
 
 /// A struct containing information about the location of a panic.
 ///
@@ -33,14 +35,13 @@ use crate::fmt;
 #[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
 #[stable(feature = "panic_hooks", since = "1.10.0")]
 pub struct Location<'a> {
-    // Note: this filename will have exactly one nul byte at its end, but otherwise
-    // it must never contain interior nul bytes. This is relied on for the conversion
-    // to `CStr` below.
-    //
-    // The prefix of the string without the trailing nul byte will be a regular UTF8 `str`.
-    file_bytes_with_nul: &'a [u8],
+    // A raw pointer is used rather than a reference because the pointer is valid for one more byte
+    // than the length stored in this pointer; the additional byte is the NUL-terminator used by
+    // `Location::file_with_nul`.
+    filename: NonNull<str>,
     line: u32,
     col: u32,
+    _filename: PhantomData<&'a str>,
 }
 
 #[stable(feature = "panic_hooks", since = "1.10.0")]
@@ -143,10 +144,8 @@ impl<'a> Location<'a> {
     #[stable(feature = "panic_hooks", since = "1.10.0")]
     #[rustc_const_stable(feature = "const_location_fields", since = "1.79.0")]
     pub const fn file(&self) -> &str {
-        let str_len = self.file_bytes_with_nul.len() - 1;
-        // SAFETY: `file_bytes_with_nul` without the trailing nul byte is guaranteed to be
-        // valid UTF8.
-        unsafe { crate::str::from_raw_parts(self.file_bytes_with_nul.as_ptr(), str_len) }
+        // SAFETY: The filename is valid.
+        unsafe { self.filename.as_ref() }
     }
 
     /// Returns the name of the source file as a nul-terminated `CStr`.
@@ -157,9 +156,17 @@ impl<'a> Location<'a> {
     #[unstable(feature = "file_with_nul", issue = "141727")]
     #[inline]
     pub const fn file_with_nul(&self) -> &CStr {
-        // SAFETY: `file_bytes_with_nul` is guaranteed to have a trailing nul byte and no
-        // interior nul bytes.
-        unsafe { CStr::from_bytes_with_nul_unchecked(self.file_bytes_with_nul) }
+        let filename = self.filename.as_ptr();
+
+        // SAFETY: The filename is valid for `filename_len+1` bytes, so this addition can't
+        // overflow.
+        let cstr_len = unsafe { crate::mem::size_of_val_raw(filename).unchecked_add(1) };
+
+        // SAFETY: The filename is valid for `filename_len+1` bytes.
+        let slice = unsafe { crate::slice::from_raw_parts(filename.cast(), cstr_len) };
+
+        // SAFETY: The filename is guaranteed to have a trailing nul byte and no interior nul bytes.
+        unsafe { CStr::from_bytes_with_nul_unchecked(slice) }
     }
 
     /// Returns the line number from which the panic originated.
@@ -220,3 +227,8 @@ impl fmt::Display for Location<'_> {
         write!(formatter, "{}:{}:{}", self.file(), self.line, self.col)
     }
 }
+
+#[stable(feature = "panic_hooks", since = "1.10.0")]
+unsafe impl Send for Location<'_> {}
+#[stable(feature = "panic_hooks", since = "1.10.0")]
+unsafe impl Sync for Location<'_> {}
diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs
index d87f4814f02..812bc5e6145 100644
--- a/library/core/src/panicking.rs
+++ b/library/core/src/panicking.rs
@@ -314,6 +314,22 @@ fn panic_null_pointer_dereference() -> ! {
     )
 }
 
+#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold, optimize(size))]
+#[cfg_attr(feature = "panic_immediate_abort", inline)]
+#[track_caller]
+#[lang = "panic_invalid_enum_construction"] // needed by codegen for panic on invalid enum construction.
+#[rustc_nounwind] // `CheckEnums` MIR pass requires this function to never unwind
+fn panic_invalid_enum_construction(source: u128) -> ! {
+    if cfg!(feature = "panic_immediate_abort") {
+        super::intrinsics::abort()
+    }
+
+    panic_nounwind_fmt(
+        format_args!("trying to construct an enum from an invalid value {source:#x}"),
+        /* force_no_backtrace */ false,
+    )
+}
+
 /// Panics because we cannot unwind out of a function.
 ///
 /// This is a separate function to avoid the codesize impact of each crate containing the string to
diff --git a/library/std/src/os/illumos/mod.rs b/library/std/src/os/illumos/mod.rs
index e61926f8935..5fbe352e7cf 100644
--- a/library/std/src/os/illumos/mod.rs
+++ b/library/std/src/os/illumos/mod.rs
@@ -3,4 +3,5 @@
 #![stable(feature = "raw_ext", since = "1.1.0")]
 
 pub mod fs;
+pub mod net;
 pub mod raw;
diff --git a/library/std/src/os/illumos/net.rs b/library/std/src/os/illumos/net.rs
new file mode 100644
index 00000000000..5ef4e1ec89e
--- /dev/null
+++ b/library/std/src/os/illumos/net.rs
@@ -0,0 +1,50 @@
+//! illumos-specific networking functionality.
+
+#![unstable(feature = "unix_socket_exclbind", issue = "123481")]
+
+use crate::io;
+use crate::os::unix::net;
+use crate::sealed::Sealed;
+use crate::sys_common::AsInner;
+
+/// illumos-specific functionality for `AF_UNIX` sockets [`UnixDatagram`]
+/// and [`UnixStream`].
+///
+/// [`UnixDatagram`]: net::UnixDatagram
+/// [`UnixStream`]: net::UnixStream
+#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
+pub trait UnixSocketExt: Sealed {
+    /// Enables exclusive binding on the socket.
+    ///
+    /// If true and if the socket had been set with `SO_REUSEADDR`,
+    /// it neutralises its effect.
+    /// See [`man 3 tcp`](https://docs.oracle.com/cd/E88353_01/html/E37843/setsockopt-3c.html)
+    #[unstable(feature = "unix_socket_exclbind", issue = "123481")]
+    fn so_exclbind(&self, excl: bool) -> io::Result<()>;
+
+    /// Get the bind exclusivity bind state of the socket.
+    #[unstable(feature = "unix_socket_exclbind", issue = "123481")]
+    fn exclbind(&self) -> io::Result<bool>;
+}
+
+#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
+impl UnixSocketExt for net::UnixDatagram {
+    fn exclbind(&self) -> io::Result<bool> {
+        self.as_inner().exclbind()
+    }
+
+    fn so_exclbind(&self, excl: bool) -> io::Result<()> {
+        self.as_inner().set_exclbind(excl)
+    }
+}
+
+#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
+impl UnixSocketExt for net::UnixStream {
+    fn exclbind(&self) -> io::Result<bool> {
+        self.as_inner().exclbind()
+    }
+
+    fn so_exclbind(&self, excl: bool) -> io::Result<()> {
+        self.as_inner().set_exclbind(excl)
+    }
+}
diff --git a/library/std/src/os/solaris/mod.rs b/library/std/src/os/solaris/mod.rs
index e4cfd53291a..b4e83620849 100644
--- a/library/std/src/os/solaris/mod.rs
+++ b/library/std/src/os/solaris/mod.rs
@@ -3,4 +3,5 @@
 #![stable(feature = "raw_ext", since = "1.1.0")]
 
 pub mod fs;
+pub mod net;
 pub mod raw;
diff --git a/library/std/src/os/solaris/net.rs b/library/std/src/os/solaris/net.rs
new file mode 100644
index 00000000000..ca841f15b0e
--- /dev/null
+++ b/library/std/src/os/solaris/net.rs
@@ -0,0 +1,50 @@
+//! solaris-specific networking functionality.
+
+#![unstable(feature = "unix_socket_exclbind", issue = "123481")]
+
+use crate::io;
+use crate::os::unix::net;
+use crate::sealed::Sealed;
+use crate::sys_common::AsInner;
+
+/// solaris-specific functionality for `AF_UNIX` sockets [`UnixDatagram`]
+/// and [`UnixStream`].
+///
+/// [`UnixDatagram`]: net::UnixDatagram
+/// [`UnixStream`]: net::UnixStream
+#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
+pub trait UnixSocketExt: Sealed {
+    /// Enables exclusive binding on the socket.
+    ///
+    /// If true and if the socket had been set with `SO_REUSEADDR`,
+    /// it neutralises its effect.
+    /// See [`man 3 tcp`](https://docs.oracle.com/cd/E88353_01/html/E37843/setsockopt-3c.html)
+    #[unstable(feature = "unix_socket_exclbind", issue = "123481")]
+    fn so_exclbind(&self, excl: bool) -> io::Result<()>;
+
+    /// Get the bind exclusivity bind state of the socket.
+    #[unstable(feature = "unix_socket_exclbind", issue = "123481")]
+    fn exclbind(&self) -> io::Result<bool>;
+}
+
+#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
+impl UnixSocketExt for net::UnixDatagram {
+    fn exclbind(&self) -> io::Result<bool> {
+        self.as_inner().exclbind()
+    }
+
+    fn so_exclbind(&self, excl: bool) -> io::Result<()> {
+        self.as_inner().set_exclbind(excl)
+    }
+}
+
+#[unstable(feature = "unix_socket_exclbind", issue = "123481")]
+impl UnixSocketExt for net::UnixStream {
+    fn exclbind(&self) -> io::Result<bool> {
+        self.as_inner().exclbind()
+    }
+
+    fn so_exclbind(&self, excl: bool) -> io::Result<()> {
+        self.as_inner().set_exclbind(excl)
+    }
+}
diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs
index b2a4961c3c5..cc111f3521b 100644
--- a/library/std/src/sys/net/connection/socket/unix.rs
+++ b/library/std/src/sys/net/connection/socket/unix.rs
@@ -522,6 +522,21 @@ impl Socket {
         Ok(name)
     }
 
+    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+    pub fn set_exclbind(&self, excl: bool) -> io::Result<()> {
+        // not yet on libc crate
+        const SO_EXCLBIND: i32 = 0x1015;
+        setsockopt(self, libc::SOL_SOCKET, SO_EXCLBIND, excl)
+    }
+
+    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+    pub fn exclbind(&self) -> io::Result<bool> {
+        // not yet on libc crate
+        const SO_EXCLBIND: i32 = 0x1015;
+        let raw: c_int = getsockopt(self, libc::SOL_SOCKET, SO_EXCLBIND)?;
+        Ok(raw != 0)
+    }
+
     #[cfg(any(target_os = "android", target_os = "linux",))]
     pub fn set_passcred(&self, passcred: bool) -> io::Result<()> {
         setsockopt(self, libc::SOL_SOCKET, libc::SO_PASSCRED, passcred as libc::c_int)
diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs
index 1fe80c13b81..bbd03e2b0c4 100644
--- a/library/std/src/sys/process/unix/unix.rs
+++ b/library/std/src/sys/process/unix/unix.rs
@@ -968,8 +968,8 @@ impl Process {
     }
 
     pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
-        // If we've already waited on this process then the pid can be recycled
-        // and used for another process, and we probably shouldn't be signaling
+        // If we've already waited on this process then the pid can be recycled and
+        // used for another process, and we probably shouldn't be sending signals to
         // random processes, so return Ok because the process has exited already.
         if self.status.is_some() {
             return Ok(());
diff --git a/library/std/src/sys/process/unix/vxworks.rs b/library/std/src/sys/process/unix/vxworks.rs
index 51ae8c56bdb..2275cbb946a 100644
--- a/library/std/src/sys/process/unix/vxworks.rs
+++ b/library/std/src/sys/process/unix/vxworks.rs
@@ -151,8 +151,8 @@ impl Process {
     }
 
     pub fn send_signal(&self, signal: i32) -> io::Result<()> {
-        // If we've already waited on this process then the pid can be recycled
-        // and used for another process, and we probably shouldn't be killing
+        // If we've already waited on this process then the pid can be recycled and
+        // used for another process, and we probably shouldn't be sending signals to
         // random processes, so return Ok because the process has exited already.
         if self.status.is_some() {
             Ok(())
diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh b/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh
index 064ac5b0a5e..924bdbc7615 100755
--- a/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh
+++ b/src/ci/docker/host-x86_64/dist-x86_64-linux/dist.sh
@@ -10,4 +10,7 @@ python3 ../x.py build --set rust.debug=true opt-dist
     build-manifest bootstrap
 
 # Use GCC for building GCC, as it seems to behave badly when built with Clang
-CC=/rustroot/bin/cc CXX=/rustroot/bin/c++ python3 ../x.py dist gcc
+# Only build GCC on full builds, not try builds
+if [ "${DIST_TRY_BUILD:-0}" == "0" ]; then
+    CC=/rustroot/bin/cc CXX=/rustroot/bin/c++ python3 ../x.py dist gcc
+fi
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index e16acc9622f..e33bdc0db32 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1703,6 +1703,7 @@ fn render_enum_fields(
                 if v.is_stripped() {
                     continue;
                 }
+                write!(w, "{}", render_attributes_in_pre(v, TAB, cx))?;
                 w.write_str(TAB)?;
                 match v.kind {
                     clean::VariantItem(ref var) => match var.kind {
diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs
index 4c53ea42793..0520eff0fa2 100644
--- a/src/tools/build-manifest/src/main.rs
+++ b/src/tools/build-manifest/src/main.rs
@@ -14,6 +14,7 @@ use crate::versions::{PkgType, Versions};
 
 static HOSTS: &[&str] = &[
     "aarch64-apple-darwin",
+    "aarch64-pc-windows-gnullvm",
     "aarch64-pc-windows-msvc",
     "aarch64-unknown-linux-gnu",
     "aarch64-unknown-linux-musl",
@@ -44,6 +45,7 @@ static HOSTS: &[&str] = &[
     "x86_64-apple-darwin",
     "x86_64-pc-solaris",
     "x86_64-pc-windows-gnu",
+    "x86_64-pc-windows-gnullvm",
     "x86_64-pc-windows-msvc",
     "x86_64-unknown-freebsd",
     "x86_64-unknown-illumos",
@@ -470,7 +472,7 @@ impl Builder {
                 }
                 // so is rust-mingw if it's available for the target
                 PkgType::RustMingw => {
-                    if host.contains("pc-windows-gnu") {
+                    if host.ends_with("pc-windows-gnu") {
                         components.push(host_component(pkg));
                     }
                 }
diff --git a/src/tools/cargo b/src/tools/cargo
-Subproject 409fed7dc1553d49cb9a8c0637d12d65571346c
+Subproject 930b4f62cfcd1f0eabdb30a56d91bf6844b739b
diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md
index 4452442cd4f..e609fb9b6f9 100644
--- a/src/tools/miri/README.md
+++ b/src/tools/miri/README.md
@@ -595,7 +595,7 @@ Definite bugs found:
 * [Occasional memory leak in `std::mpsc` channels](https://github.com/rust-lang/rust/issues/121582) (original code in [crossbeam](https://github.com/crossbeam-rs/crossbeam/pull/1084))
 * [Weak-memory-induced memory leak in Windows thread-local storage](https://github.com/rust-lang/rust/pull/124281)
 * [A bug in the new `RwLock::downgrade` implementation](https://rust-lang.zulipchat.com/#narrow/channel/269128-miri/topic/Miri.20error.20library.20test) (caught by Miri before it landed in the Rust repo)
-* [Mockall reading unintialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite)
+* [Mockall reading uninitialized memory when mocking `std::io::Read::read`, even if all expectations are satisfied](https://github.com/asomers/mockall/issues/647) (caught by Miri running Tokio's test suite)
 * [`ReentrantLock` not correctly dealing with reuse of addresses for TLS storage of different threads](https://github.com/rust-lang/rust/pull/141248)
 
 Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows is currently just an experiment):
diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs
index 850478b516c..edbc004d058 100644
--- a/src/tools/miri/src/lib.rs
+++ b/src/tools/miri/src/lib.rs
@@ -173,7 +173,7 @@ pub const MIRI_DEFAULT_ARGS: &[&str] = &[
     "-Zmir-emit-retag",
     "-Zmir-preserve-ub",
     "-Zmir-opt-level=0",
-    "-Zmir-enable-passes=-CheckAlignment,-CheckNull",
+    "-Zmir-enable-passes=-CheckAlignment,-CheckNull,-CheckEnums",
     // Deduplicating diagnostics means we miss events when tracking what happens during an
     // execution. Let's not do that.
     "-Zdeduplicate-diagnostics=no",
diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs
index c63e926376d..9646cdb2065 100644
--- a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs
+++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_to_raw_pointer.rs
@@ -6,7 +6,7 @@ fn direct_raw(x: *const (i32, i32)) -> *const i32 {
 
 // Ensure that if a raw pointer is created via an intermediate
 // reference, we catch that. (Just in case someone decides to
-// desugar this differenly or so.)
+// desugar this differently or so.)
 fn via_ref(x: *const (i32, i32)) -> *const i32 {
     unsafe { &(*x).0 as *const i32 } //~ERROR: dangling pointer
 }
diff --git a/src/tools/opt-dist/src/exec.rs b/src/tools/opt-dist/src/exec.rs
index 64ce5cc3775..0dc6e56b9d5 100644
--- a/src/tools/opt-dist/src/exec.rs
+++ b/src/tools/opt-dist/src/exec.rs
@@ -122,7 +122,12 @@ impl Bootstrap {
         let metrics_path = env.build_root().join("build").join("metrics.json");
         let args = dist_args.iter().map(|arg| arg.as_str()).collect::<Vec<_>>();
         let cmd = cmd(&args).env("RUST_BACKTRACE", "full");
-        let cmd = add_shared_x_flags(env, cmd);
+        let mut cmd = add_shared_x_flags(env, cmd);
+        if env.is_fast_try_build() {
+            // We set build.extended=false for fast try builds, but we still need Cargo
+            cmd = cmd.arg("cargo");
+        }
+
         Self { cmd, metrics_path }
     }
 
@@ -189,5 +194,18 @@ impl Bootstrap {
 }
 
 fn add_shared_x_flags(env: &Environment, cmd: CmdBuilder) -> CmdBuilder {
-    if env.is_fast_try_build() { cmd.arg("--set").arg("rust.deny-warnings=false") } else { cmd }
+    if env.is_fast_try_build() {
+        // Skip things that cannot be skipped through `x ... --skip`
+        cmd.arg("--set")
+            .arg("rust.llvm-bitcode-linker=false")
+            // Skip wasm-component-ld. This also skips cargo, which we need to re-enable for dist
+            .arg("--set")
+            .arg("build.extended=false")
+            .arg("--set")
+            .arg("rust.codegen-backends=['llvm']")
+            .arg("--set")
+            .arg("rust.deny-warnings=false")
+    } else {
+        cmd
+    }
 }
diff --git a/src/tools/opt-dist/src/main.rs b/src/tools/opt-dist/src/main.rs
index d2827ec01ca..9c8a6637a3b 100644
--- a/src/tools/opt-dist/src/main.rs
+++ b/src/tools/opt-dist/src/main.rs
@@ -407,13 +407,18 @@ fn main() -> anyhow::Result<()> {
         for target in [
             "rust-docs",
             "rustc-docs",
+            "rustc-dev",
+            "rust-dev",
             "rust-docs-json",
             "rust-analyzer",
             "rustc-src",
+            "extended",
             "clippy",
             "miri",
             "rustfmt",
             "gcc",
+            "generate-copyright",
+            "bootstrap",
         ] {
             build_args.extend(["--skip".to_string(), target.to_string()]);
         }
diff --git a/src/tools/test-float-parse/src/ui.rs b/src/tools/test-float-parse/src/ui.rs
index 73473eef0bf..1a9ba0dc1d9 100644
--- a/src/tools/test-float-parse/src/ui.rs
+++ b/src/tools/test-float-parse/src/ui.rs
@@ -118,7 +118,7 @@ pub fn finish_all(tests: &[TestInfo], total_elapsed: Duration, cfg: &Config) ->
         match result {
             Ok(FinishedAll) => (),
             Err(EarlyExit::Timeout) => {
-                println!("      exited early; exceded {:?} timeout", cfg.timeout)
+                println!("      exited early; exceeded {:?} timeout", cfg.timeout)
             }
             Err(EarlyExit::MaxFailures) => {
                 println!("      exited early; exceeded {:?} max failures", cfg.max_failures)
diff --git a/tests/rustdoc/enum/enum-variant-non_exhaustive.rs b/tests/rustdoc/enum/enum-variant-non_exhaustive.rs
new file mode 100644
index 00000000000..ea0234a49f6
--- /dev/null
+++ b/tests/rustdoc/enum/enum-variant-non_exhaustive.rs
@@ -0,0 +1,17 @@
+// regression test for https://github.com/rust-lang/rust/issues/142599
+
+#![crate_name = "foo"]
+
+//@ snapshot type-code 'foo/enum.Type.html' '//pre[@class="rust item-decl"]/code'
+pub enum Type {
+    #[non_exhaustive]
+    // attribute that should not be shown
+    #[warn(unsafe_code)]
+    Variant,
+}
+
+// we would love to use the `following-sibling::` axis
+// (along with an `h2[@id="aliased-type"]` query),
+// but unfortunately python doesn't implement that.
+//@ snapshot type-alias-code 'foo/type.TypeAlias.html' '//pre[@class="rust item-decl"][2]/code'
+pub type TypeAlias = Type;
diff --git a/tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html
new file mode 100644
index 00000000000..04eea709079
--- /dev/null
+++ b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-alias-code.html
@@ -0,0 +1,4 @@
+<code>pub enum TypeAlias {
+    #[non_exhaustive]
+    Variant,
+}</code>
\ No newline at end of file
diff --git a/tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html
new file mode 100644
index 00000000000..6c8851ea5df
--- /dev/null
+++ b/tests/rustdoc/enum/enum-variant-non_exhaustive.type-code.html
@@ -0,0 +1,4 @@
+<code>pub enum Type {
+    #[non_exhaustive]
+    Variant,
+}</code>
\ No newline at end of file
diff --git a/tests/ui/mir/enum/convert_non_enum_break.rs b/tests/ui/mir/enum/convert_non_enum_break.rs
new file mode 100644
index 00000000000..de062c39907
--- /dev/null
+++ b/tests/ui/mir/enum/convert_non_enum_break.rs
@@ -0,0 +1,20 @@
+//@ run-fail
+//@ compile-flags: -C debug-assertions
+//@ error-pattern: trying to construct an enum from an invalid value 0x10000
+
+#[allow(dead_code)]
+#[repr(u32)]
+enum Foo {
+    A,
+    B,
+}
+
+#[allow(dead_code)]
+struct Bar {
+    a: u16,
+    b: u16,
+}
+
+fn main() {
+    let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 0, b: 1 }) };
+}
diff --git a/tests/ui/mir/enum/convert_non_enum_niche_break.rs b/tests/ui/mir/enum/convert_non_enum_niche_break.rs
new file mode 100644
index 00000000000..9ff4849c5b1
--- /dev/null
+++ b/tests/ui/mir/enum/convert_non_enum_niche_break.rs
@@ -0,0 +1,27 @@
+//@ run-fail
+//@ compile-flags: -C debug-assertions
+//@ error-pattern: trying to construct an enum from an invalid value 0x5
+
+#[allow(dead_code)]
+#[repr(u16)]
+enum Mix {
+    A,
+    B(u16),
+}
+
+#[allow(dead_code)]
+enum Nested {
+    C(Mix),
+    D,
+    E,
+}
+
+#[allow(dead_code)]
+struct Bar {
+    a: u16,
+    b: u16,
+}
+
+fn main() {
+    let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 5, b: 0 }) };
+}
diff --git a/tests/ui/mir/enum/convert_non_enum_niche_ok.rs b/tests/ui/mir/enum/convert_non_enum_niche_ok.rs
new file mode 100644
index 00000000000..24027da5458
--- /dev/null
+++ b/tests/ui/mir/enum/convert_non_enum_niche_ok.rs
@@ -0,0 +1,29 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+#[allow(dead_code)]
+#[repr(u16)]
+enum Mix {
+    A,
+    B(u16),
+}
+
+#[allow(dead_code)]
+enum Nested {
+    C(Mix),
+    D,
+    E,
+}
+
+#[allow(dead_code)]
+struct Bar {
+    a: u16,
+    b: u16,
+}
+
+fn main() {
+    let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 0, b: 0 }) };
+    let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 1, b: 0 }) };
+    let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 2, b: 0 }) };
+    let _val: Nested = unsafe { std::mem::transmute::<_, Nested>(Bar { a: 3, b: 0 }) };
+}
diff --git a/tests/ui/mir/enum/convert_non_enum_ok.rs b/tests/ui/mir/enum/convert_non_enum_ok.rs
new file mode 100644
index 00000000000..37fc64342ca
--- /dev/null
+++ b/tests/ui/mir/enum/convert_non_enum_ok.rs
@@ -0,0 +1,20 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+#[allow(dead_code)]
+#[repr(u32)]
+enum Foo {
+    A,
+    B,
+}
+
+#[allow(dead_code)]
+struct Bar {
+    a: u16,
+    b: u16,
+}
+
+fn main() {
+    let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 0, b: 0 }) };
+    let _val: Foo = unsafe { std::mem::transmute::<_, Foo>(Bar { a: 1, b: 0 }) };
+}
diff --git a/tests/ui/mir/enum/niche_option_tuple_break.rs b/tests/ui/mir/enum/niche_option_tuple_break.rs
new file mode 100644
index 00000000000..43eef3a4cc5
--- /dev/null
+++ b/tests/ui/mir/enum/niche_option_tuple_break.rs
@@ -0,0 +1,20 @@
+//@ run-fail
+//@ compile-flags: -C debug-assertions
+//@ error-pattern: trying to construct an enum from an invalid value 0x3
+
+#[allow(dead_code)]
+enum Foo {
+    A,
+    B,
+}
+
+#[allow(dead_code)]
+struct Bar {
+    a: usize,
+    b: usize,
+}
+
+fn main() {
+    let _val: Option<(usize, Foo)> =
+        unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 3, b: 3 }) };
+}
diff --git a/tests/ui/mir/enum/niche_option_tuple_ok.rs b/tests/ui/mir/enum/niche_option_tuple_ok.rs
new file mode 100644
index 00000000000..71c885c7edb
--- /dev/null
+++ b/tests/ui/mir/enum/niche_option_tuple_ok.rs
@@ -0,0 +1,21 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+#[allow(dead_code)]
+enum Foo {
+    A,
+    B,
+}
+
+#[allow(dead_code)]
+struct Bar {
+    a: usize,
+    b: usize,
+}
+
+fn main() {
+    let _val: Option<(usize, Foo)> =
+        unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 0, b: 0 }) };
+    let _val: Option<(usize, Foo)> =
+        unsafe { std::mem::transmute::<_, Option<(usize, Foo)>>(Bar { a: 1, b: 0 }) };
+}
diff --git a/tests/ui/mir/enum/numbered_variants_break.rs b/tests/ui/mir/enum/numbered_variants_break.rs
new file mode 100644
index 00000000000..e3e71dc8aec
--- /dev/null
+++ b/tests/ui/mir/enum/numbered_variants_break.rs
@@ -0,0 +1,13 @@
+//@ run-fail
+//@ compile-flags: -C debug-assertions
+//@ error-pattern: trying to construct an enum from an invalid value 0x3
+
+#[allow(dead_code)]
+enum Foo {
+    A,
+    B,
+}
+
+fn main() {
+    let _val: Foo = unsafe { std::mem::transmute::<u8, Foo>(3) };
+}
diff --git a/tests/ui/mir/enum/numbered_variants_ok.rs b/tests/ui/mir/enum/numbered_variants_ok.rs
new file mode 100644
index 00000000000..995a2f6511b
--- /dev/null
+++ b/tests/ui/mir/enum/numbered_variants_ok.rs
@@ -0,0 +1,13 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+#[allow(dead_code)]
+enum Foo {
+    A,
+    B,
+}
+
+fn main() {
+    let _val: Foo = unsafe { std::mem::transmute::<u8, Foo>(0) };
+    let _val: Foo = unsafe { std::mem::transmute::<u8, Foo>(1) };
+}
diff --git a/tests/ui/mir/enum/option_with_bigger_niche_break.rs b/tests/ui/mir/enum/option_with_bigger_niche_break.rs
new file mode 100644
index 00000000000..c66614b845b
--- /dev/null
+++ b/tests/ui/mir/enum/option_with_bigger_niche_break.rs
@@ -0,0 +1,14 @@
+//@ run-fail
+//@ compile-flags: -C debug-assertions
+//@ error-pattern: trying to construct an enum from an invalid value 0x0
+
+#[repr(u32)]
+#[allow(dead_code)]
+enum Foo {
+    A = 2,
+    B,
+}
+
+fn main() {
+    let _val: Option<Foo> = unsafe { std::mem::transmute::<u32, Option<Foo>>(0) };
+}
diff --git a/tests/ui/mir/enum/option_with_bigger_niche_ok.rs b/tests/ui/mir/enum/option_with_bigger_niche_ok.rs
new file mode 100644
index 00000000000..1d44ffd28fc
--- /dev/null
+++ b/tests/ui/mir/enum/option_with_bigger_niche_ok.rs
@@ -0,0 +1,14 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+#[repr(u32)]
+#[allow(dead_code)]
+enum Foo {
+    A = 2,
+    B,
+}
+
+fn main() {
+    let _val: Option<Foo> = unsafe { std::mem::transmute::<u32, Option<Foo>>(2) };
+    let _val: Option<Foo> = unsafe { std::mem::transmute::<u32, Option<Foo>>(3) };
+}
diff --git a/tests/ui/mir/enum/plain_no_data_break.rs b/tests/ui/mir/enum/plain_no_data_break.rs
new file mode 100644
index 00000000000..db68e752479
--- /dev/null
+++ b/tests/ui/mir/enum/plain_no_data_break.rs
@@ -0,0 +1,14 @@
+//@ run-fail
+//@ compile-flags: -C debug-assertions
+//@ error-pattern: trying to construct an enum from an invalid value 0x1
+
+#[repr(u32)]
+#[allow(dead_code)]
+enum Foo {
+    A = 2,
+    B,
+}
+
+fn main() {
+    let _val: Foo = unsafe { std::mem::transmute::<u32, Foo>(1) };
+}
diff --git a/tests/ui/mir/enum/plain_no_data_ok.rs b/tests/ui/mir/enum/plain_no_data_ok.rs
new file mode 100644
index 00000000000..bbdc18f96dc
--- /dev/null
+++ b/tests/ui/mir/enum/plain_no_data_ok.rs
@@ -0,0 +1,14 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+#[repr(u32)]
+#[allow(dead_code)]
+enum Foo {
+    A = 2,
+    B,
+}
+
+fn main() {
+    let _val: Foo = unsafe { std::mem::transmute::<u32, Foo>(2) };
+    let _val: Foo = unsafe { std::mem::transmute::<u32, Foo>(3) };
+}
diff --git a/tests/ui/mir/enum/single_ok.rs b/tests/ui/mir/enum/single_ok.rs
new file mode 100644
index 00000000000..06b5a237c68
--- /dev/null
+++ b/tests/ui/mir/enum/single_ok.rs
@@ -0,0 +1,11 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+#[allow(dead_code)]
+enum Single {
+    A
+}
+
+fn main() {
+    let _val: Single = unsafe { std::mem::transmute::<(), Single>(()) };
+}
diff --git a/tests/ui/mir/enum/single_with_repr_break.rs b/tests/ui/mir/enum/single_with_repr_break.rs
new file mode 100644
index 00000000000..5a4ec85a9b5
--- /dev/null
+++ b/tests/ui/mir/enum/single_with_repr_break.rs
@@ -0,0 +1,13 @@
+//@ run-fail
+//@ compile-flags: -C debug-assertions
+//@ error-pattern: trying to construct an enum from an invalid value 0x1
+
+#[allow(dead_code)]
+#[repr(u16)]
+enum Single {
+    A
+}
+
+fn main() {
+    let _val: Single = unsafe { std::mem::transmute::<u16, Single>(1) };
+}
diff --git a/tests/ui/mir/enum/single_with_repr_ok.rs b/tests/ui/mir/enum/single_with_repr_ok.rs
new file mode 100644
index 00000000000..b0ed2bad660
--- /dev/null
+++ b/tests/ui/mir/enum/single_with_repr_ok.rs
@@ -0,0 +1,12 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+#[allow(dead_code)]
+#[repr(u16)]
+enum Single {
+    A
+}
+
+fn main() {
+    let _val: Single = unsafe { std::mem::transmute::<u16, Single>(0) };
+}
diff --git a/tests/ui/mir/enum/with_niche_int_break.rs b/tests/ui/mir/enum/with_niche_int_break.rs
new file mode 100644
index 00000000000..0ec60a33564
--- /dev/null
+++ b/tests/ui/mir/enum/with_niche_int_break.rs
@@ -0,0 +1,21 @@
+//@ run-fail
+//@ compile-flags: -C debug-assertions
+//@ error-pattern: trying to construct an enum from an invalid value 0x4
+
+#[allow(dead_code)]
+#[repr(u16)]
+enum Mix {
+    A,
+    B(u16),
+}
+
+#[allow(dead_code)]
+enum Nested {
+    C(Mix),
+    D,
+    E,
+}
+
+fn main() {
+    let _val: Nested = unsafe { std::mem::transmute::<u32, Nested>(4) };
+}
diff --git a/tests/ui/mir/enum/with_niche_int_ok.rs b/tests/ui/mir/enum/with_niche_int_ok.rs
new file mode 100644
index 00000000000..9a3ff3a73be
--- /dev/null
+++ b/tests/ui/mir/enum/with_niche_int_ok.rs
@@ -0,0 +1,23 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+#[allow(dead_code)]
+#[repr(u16)]
+enum Mix {
+    A,
+    B(u16),
+}
+
+#[allow(dead_code)]
+enum Nested {
+    C(Mix),
+    D,
+    E,
+}
+
+fn main() {
+    let _val: Nested = unsafe { std::mem::transmute::<u32, Nested>(0) };
+    let _val: Nested = unsafe { std::mem::transmute::<u32, Nested>(1) };
+    let _val: Nested = unsafe { std::mem::transmute::<u32, Nested>(2) };
+    let _val: Nested = unsafe { std::mem::transmute::<u32, Nested>(3) };
+}
diff --git a/tests/ui/mir/enum/with_niche_ptr_ok.rs b/tests/ui/mir/enum/with_niche_ptr_ok.rs
new file mode 100644
index 00000000000..969d955f7a4
--- /dev/null
+++ b/tests/ui/mir/enum/with_niche_ptr_ok.rs
@@ -0,0 +1,14 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+fn main() {
+    let _val = unsafe {
+        std::mem::transmute::<*const usize, Option<unsafe extern "C" fn()>>(std::ptr::null())
+    };
+    let _val = unsafe {
+        std::mem::transmute::<*const usize, Option<unsafe extern "C" fn()>>(usize::MAX as *const _)
+    };
+    let _val = unsafe { std::mem::transmute::<usize, Option<unsafe extern "C" fn()>>(0) };
+    let _val = unsafe { std::mem::transmute::<usize, Option<unsafe extern "C" fn()>>(1) };
+    let _val = unsafe { std::mem::transmute::<usize, Option<unsafe extern "C" fn()>>(usize::MAX) };
+}
diff --git a/tests/ui/mir/enum/wrap_break.rs b/tests/ui/mir/enum/wrap_break.rs
new file mode 100644
index 00000000000..4491394ca5a
--- /dev/null
+++ b/tests/ui/mir/enum/wrap_break.rs
@@ -0,0 +1,14 @@
+//@ run-fail
+//@ compile-flags: -C debug-assertions
+//@ error-pattern: trying to construct an enum from an invalid value 0x0
+#![feature(never_type)]
+#![allow(invalid_value)]
+
+#[allow(dead_code)]
+enum Wrap {
+    A(!),
+}
+
+fn main() {
+    let _val: Wrap = unsafe { std::mem::transmute::<(), Wrap>(()) };
+}
diff --git a/tests/ui/mir/enum/wrap_ok.rs b/tests/ui/mir/enum/wrap_ok.rs
new file mode 100644
index 00000000000..2881675c9ce
--- /dev/null
+++ b/tests/ui/mir/enum/wrap_ok.rs
@@ -0,0 +1,12 @@
+//@ run-pass
+//@ compile-flags: -C debug-assertions
+
+#[allow(dead_code)]
+enum Wrap {
+    A(u32),
+}
+
+fn main() {
+    let _val: Wrap = unsafe { std::mem::transmute::<u32, Wrap>(2) };
+    let _val: Wrap = unsafe { std::mem::transmute::<u32, Wrap>(u32::MAX) };
+}