From 075084f772abbc53263fc946c047a01e0dd65f80 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Tue, 23 Aug 2022 00:07:26 +0000 Subject: [PATCH 1/4] Make `const_eval_select` a real intrinsic --- compiler/rustc_codegen_ssa/src/mir/block.rs | 3 +- .../src/const_eval/machine.rs | 16 +- compiler/rustc_hir/src/lang_items.rs | 2 - .../rustc_middle/src/ty/consts/valtree.rs | 5 +- compiler/rustc_middle/src/ty/layout.rs | 175 +++++++++--------- compiler/rustc_mir_transform/src/lib.rs | 68 ++++++- compiler/rustc_monomorphize/src/collector.rs | 8 +- compiler/rustc_span/src/symbol.rs | 1 - .../rustc_typeck/src/check/fn_ctxt/checks.rs | 33 +++- library/core/src/intrinsics.rs | 143 +++++++------- library/core/src/mem/valid_align.rs | 2 +- library/core/src/num/f32.rs | 22 ++- library/core/src/num/f64.rs | 22 ++- library/core/src/num/nonzero.rs | 2 +- library/core/src/ptr/const_ptr.rs | 5 +- library/core/src/ptr/mod.rs | 8 +- library/core/src/slice/index.rs | 20 +- library/core/src/slice/mod.rs | 15 +- library/core/src/slice/raw.rs | 4 +- library/core/src/str/mod.rs | 2 + .../const-float-bits-reject-conv.stderr | 40 ---- .../const-eval-select-backtrace-std.rs | 6 + ...const-eval-select-backtrace-std.run.stderr | 2 + .../intrinsics/const-eval-select-backtrace.rs | 18 ++ .../const-eval-select-backtrace.run.stderr | 2 + .../ui/intrinsics/const-eval-select-bad.rs | 12 +- .../intrinsics/const-eval-select-bad.stderr | 79 +++++--- 27 files changed, 430 insertions(+), 285 deletions(-) create mode 100644 src/test/ui/intrinsics/const-eval-select-backtrace-std.rs create mode 100644 src/test/ui/intrinsics/const-eval-select-backtrace-std.run.stderr create mode 100644 src/test/ui/intrinsics/const-eval-select-backtrace.rs create mode 100644 src/test/ui/intrinsics/const-eval-select-backtrace.run.stderr diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 5c67d3b6431fd..3154f12a77964 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -13,8 +13,7 @@ use rustc_ast as ast; use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_hir::lang_items::LangItem; use rustc_index::vec::Idx; -use rustc_middle::mir::AssertKind; -use rustc_middle::mir::{self, SwitchTargets}; +use rustc_middle::mir::{self, AssertKind, SwitchTargets}; use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf}; use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; use rustc_middle::ty::{self, Instance, Ty, TypeVisitable}; diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 9ea9fbe0e0e54..6e5c840bdfd6a 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -35,21 +35,7 @@ impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> { // All `#[rustc_do_not_const_check]` functions should be hooked here. let def_id = instance.def_id(); - if Some(def_id) == self.tcx.lang_items().const_eval_select() { - // redirect to const_eval_select_ct - if let Some(const_eval_select) = self.tcx.lang_items().const_eval_select_ct() { - return Ok(Some( - ty::Instance::resolve( - *self.tcx, - ty::ParamEnv::reveal_all(), - const_eval_select, - instance.substs, - ) - .unwrap() - .unwrap(), - )); - } - } else if Some(def_id) == self.tcx.lang_items().panic_display() + if Some(def_id) == self.tcx.lang_items().panic_display() || Some(def_id) == self.tcx.lang_items().begin_panic_fn() { // &str or &&str diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index f19d07d98dcb2..bc1ea1c4c736f 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -269,8 +269,6 @@ language_item_table! { DropInPlace, sym::drop_in_place, drop_in_place_fn, Target::Fn, GenericRequirement::Minimum(1); Oom, sym::oom, oom, Target::Fn, GenericRequirement::None; AllocLayout, sym::alloc_layout, alloc_layout, Target::Struct, GenericRequirement::None; - ConstEvalSelect, sym::const_eval_select, const_eval_select, Target::Fn, GenericRequirement::Exact(4); - ConstConstEvalSelect, sym::const_eval_select_ct,const_eval_select_ct, Target::Fn, GenericRequirement::Exact(4); Start, sym::start, start_fn, Target::Fn, GenericRequirement::Exact(1); diff --git a/compiler/rustc_middle/src/ty/consts/valtree.rs b/compiler/rustc_middle/src/ty/consts/valtree.rs index 93707bb18ceec..a803fca0d5b89 100644 --- a/compiler/rustc_middle/src/ty/consts/valtree.rs +++ b/compiler/rustc_middle/src/ty/consts/valtree.rs @@ -18,7 +18,7 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable}; /// `ValTree` does not have this problem with representation, as it only contains integers or /// lists of (nested) `ValTree`. pub enum ValTree<'tcx> { - /// ZSTs, integers, `bool`, `char` are represented as scalars. + /// integers, `bool`, `char` are represented as scalars. /// See the `ScalarInt` documentation for how `ScalarInt` guarantees that equal values /// of these types have the same representation. Leaf(ScalarInt), @@ -27,8 +27,11 @@ pub enum ValTree<'tcx> { // dont use SliceOrStr for now /// The fields of any kind of aggregate. Structs, tuples and arrays are represented by /// listing their fields' values in order. + /// /// Enums are represented by storing their discriminant as a field, followed by all /// the fields of the variant. + /// + /// ZST types are represented as an empty slice. Branch(&'tcx [ValTree<'tcx>]), } diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index 26b60e4f33988..2c587b76f025f 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -263,6 +263,7 @@ fn layout_of<'tcx>( Ok(layout) } +#[derive(Clone, Copy)] pub struct LayoutCx<'tcx, C> { pub tcx: C, pub param_env: ty::ParamEnv<'tcx>, @@ -3063,6 +3064,93 @@ fn fn_abi_of_instance<'tcx>( ) } +// Handle safe Rust thin and fat pointers. +pub fn adjust_for_rust_scalar<'tcx>( + cx: LayoutCx<'tcx, TyCtxt<'tcx>>, + attrs: &mut ArgAttributes, + scalar: Scalar, + layout: TyAndLayout<'tcx>, + offset: Size, + is_return: bool, +) { + // Booleans are always a noundef i1 that needs to be zero-extended. + if scalar.is_bool() { + attrs.ext(ArgExtension::Zext); + attrs.set(ArgAttribute::NoUndef); + return; + } + + // Scalars which have invalid values cannot be undef. + if !scalar.is_always_valid(&cx) { + attrs.set(ArgAttribute::NoUndef); + } + + // Only pointer types handled below. + let Scalar::Initialized { value: Pointer, valid_range} = scalar else { return }; + + if !valid_range.contains(0) { + attrs.set(ArgAttribute::NonNull); + } + + if let Some(pointee) = layout.pointee_info_at(&cx, offset) { + if let Some(kind) = pointee.safe { + attrs.pointee_align = Some(pointee.align); + + // `Box` (`UniqueBorrowed`) are not necessarily dereferenceable + // for the entire duration of the function as they can be deallocated + // at any time. Same for shared mutable references. If LLVM had a + // way to say "dereferenceable on entry" we could use it here. + attrs.pointee_size = match kind { + PointerKind::UniqueBorrowed + | PointerKind::UniqueBorrowedPinned + | PointerKind::Frozen => pointee.size, + PointerKind::SharedMutable | PointerKind::UniqueOwned => Size::ZERO, + }; + + // `Box`, `&T`, and `&mut T` cannot be undef. + // Note that this only applies to the value of the pointer itself; + // this attribute doesn't make it UB for the pointed-to data to be undef. + attrs.set(ArgAttribute::NoUndef); + + // The aliasing rules for `Box` are still not decided, but currently we emit + // `noalias` for it. This can be turned off using an unstable flag. + // See https://github.com/rust-lang/unsafe-code-guidelines/issues/326 + let noalias_for_box = cx.tcx.sess.opts.unstable_opts.box_noalias.unwrap_or(true); + + // `&mut` pointer parameters never alias other parameters, + // or mutable global data + // + // `&T` where `T` contains no `UnsafeCell` is immutable, + // and can be marked as both `readonly` and `noalias`, as + // LLVM's definition of `noalias` is based solely on memory + // dependencies rather than pointer equality + // + // Due to past miscompiles in LLVM, we apply a separate NoAliasMutRef attribute + // for UniqueBorrowed arguments, so that the codegen backend can decide whether + // or not to actually emit the attribute. It can also be controlled with the + // `-Zmutable-noalias` debugging option. + let no_alias = match kind { + PointerKind::SharedMutable + | PointerKind::UniqueBorrowed + | PointerKind::UniqueBorrowedPinned => false, + PointerKind::UniqueOwned => noalias_for_box, + PointerKind::Frozen => !is_return, + }; + if no_alias { + attrs.set(ArgAttribute::NoAlias); + } + + if kind == PointerKind::Frozen && !is_return { + attrs.set(ArgAttribute::ReadOnly); + } + + if kind == PointerKind::UniqueBorrowed && !is_return { + attrs.set(ArgAttribute::NoAliasMutRef); + } + } + } +} + impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> { // FIXME(eddyb) perhaps group the signature/type-containing (or all of them?) // arguments of this method, into a separate `struct`. @@ -3118,91 +3206,6 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> { use SpecAbi::*; let rust_abi = matches!(sig.abi, RustIntrinsic | PlatformIntrinsic | Rust | RustCall); - // Handle safe Rust thin and fat pointers. - let adjust_for_rust_scalar = |attrs: &mut ArgAttributes, - scalar: Scalar, - layout: TyAndLayout<'tcx>, - offset: Size, - is_return: bool| { - // Booleans are always a noundef i1 that needs to be zero-extended. - if scalar.is_bool() { - attrs.ext(ArgExtension::Zext); - attrs.set(ArgAttribute::NoUndef); - return; - } - - // Scalars which have invalid values cannot be undef. - if !scalar.is_always_valid(self) { - attrs.set(ArgAttribute::NoUndef); - } - - // Only pointer types handled below. - let Scalar::Initialized { value: Pointer, valid_range} = scalar else { return }; - - if !valid_range.contains(0) { - attrs.set(ArgAttribute::NonNull); - } - - if let Some(pointee) = layout.pointee_info_at(self, offset) { - if let Some(kind) = pointee.safe { - attrs.pointee_align = Some(pointee.align); - - // `Box` (`UniqueBorrowed`) are not necessarily dereferenceable - // for the entire duration of the function as they can be deallocated - // at any time. Same for shared mutable references. If LLVM had a - // way to say "dereferenceable on entry" we could use it here. - attrs.pointee_size = match kind { - PointerKind::UniqueBorrowed - | PointerKind::UniqueBorrowedPinned - | PointerKind::Frozen => pointee.size, - PointerKind::SharedMutable | PointerKind::UniqueOwned => Size::ZERO, - }; - - // `Box`, `&T`, and `&mut T` cannot be undef. - // Note that this only applies to the value of the pointer itself; - // this attribute doesn't make it UB for the pointed-to data to be undef. - attrs.set(ArgAttribute::NoUndef); - - // The aliasing rules for `Box` are still not decided, but currently we emit - // `noalias` for it. This can be turned off using an unstable flag. - // See https://github.com/rust-lang/unsafe-code-guidelines/issues/326 - let noalias_for_box = - self.tcx().sess.opts.unstable_opts.box_noalias.unwrap_or(true); - - // `&mut` pointer parameters never alias other parameters, - // or mutable global data - // - // `&T` where `T` contains no `UnsafeCell` is immutable, - // and can be marked as both `readonly` and `noalias`, as - // LLVM's definition of `noalias` is based solely on memory - // dependencies rather than pointer equality - // - // Due to past miscompiles in LLVM, we apply a separate NoAliasMutRef attribute - // for UniqueBorrowed arguments, so that the codegen backend can decide whether - // or not to actually emit the attribute. It can also be controlled with the - // `-Zmutable-noalias` debugging option. - let no_alias = match kind { - PointerKind::SharedMutable - | PointerKind::UniqueBorrowed - | PointerKind::UniqueBorrowedPinned => false, - PointerKind::UniqueOwned => noalias_for_box, - PointerKind::Frozen => !is_return, - }; - if no_alias { - attrs.set(ArgAttribute::NoAlias); - } - - if kind == PointerKind::Frozen && !is_return { - attrs.set(ArgAttribute::ReadOnly); - } - - if kind == PointerKind::UniqueBorrowed && !is_return { - attrs.set(ArgAttribute::NoAliasMutRef); - } - } - } - }; - let arg_of = |ty: Ty<'tcx>, arg_idx: Option| -> Result<_, FnAbiError<'tcx>> { let is_return = arg_idx.is_none(); @@ -3218,7 +3221,7 @@ impl<'tcx> LayoutCx<'tcx, TyCtxt<'tcx>> { let mut arg = ArgAbi::new(self, layout, |layout, scalar, offset| { let mut attrs = ArgAttributes::new(); - adjust_for_rust_scalar(&mut attrs, scalar, *layout, offset, is_return); + adjust_for_rust_scalar(*self, &mut attrs, scalar, *layout, offset, is_return); attrs }); diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 0b674b38f3282..69b49604250b4 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -10,6 +10,7 @@ #![feature(trusted_step)] #![feature(try_blocks)] #![feature(yeet_expr)] +#![feature(if_let_guard)] #![recursion_limit = "256"] #[macro_use] @@ -27,10 +28,13 @@ use rustc_hir::intravisit::{self, Visitor}; use rustc_index::vec::IndexVec; use rustc_middle::mir::visit::Visitor as _; use rustc_middle::mir::{ - traversal, AnalysisPhase, Body, ConstQualifs, MirPass, MirPhase, Promoted, RuntimePhase, + traversal, AnalysisPhase, Body, ConstQualifs, Constant, LocalDecl, MirPass, MirPhase, Operand, + Place, ProjectionElem, Promoted, RuntimePhase, Rvalue, SourceInfo, Statement, StatementKind, + TerminatorKind, }; use rustc_middle::ty::query::Providers; use rustc_middle::ty::{self, TyCtxt, TypeVisitable}; +use rustc_span::sym; #[macro_use] mod pass_manager; @@ -140,6 +144,64 @@ pub fn provide(providers: &mut Providers) { }; } +fn remap_mir_for_const_eval_select<'tcx>( + tcx: TyCtxt<'tcx>, + mut body: Body<'tcx>, + context: hir::Constness, +) -> Body<'tcx> { + for bb in body.basic_blocks.as_mut().iter_mut() { + let terminator = bb.terminator.as_mut().expect("invalid terminator"); + match terminator.kind { + TerminatorKind::Call { + func: Operand::Constant(box Constant { ref literal, .. }), + ref mut args, + destination, + target, + cleanup, + fn_span, + .. + } if let ty::FnDef(def_id, _) = *literal.ty().kind() + && tcx.item_name(def_id) == sym::const_eval_select + && tcx.is_intrinsic(def_id) => + { + let [tupled_args, called_in_const, called_at_rt]: [_; 3] = std::mem::take(args).try_into().unwrap(); + let ty = tupled_args.ty(&body.local_decls, tcx); + let fields = ty.tuple_fields(); + let num_args = fields.len(); + let func = if context == hir::Constness::Const { called_in_const } else { called_at_rt }; + let (method, place): (fn(Place<'tcx>) -> Operand<'tcx>, Place<'tcx>) = match tupled_args { + Operand::Constant(_) => { + // there is no good way of extracting a tuple arg from a constant (const generic stuff) + // so we just create a temporary and deconstruct that. + let local = body.local_decls.push(LocalDecl::new(ty, fn_span)); + bb.statements.push(Statement { + source_info: SourceInfo::outermost(fn_span), + kind: StatementKind::Assign(Box::new((local.into(), Rvalue::Use(tupled_args.clone())))), + }); + (Operand::Move, local.into()) + } + Operand::Move(place) => (Operand::Move, place), + Operand::Copy(place) => (Operand::Copy, place), + }; + let place_elems = place.projection; + let arguments = (0..num_args).map(|x| { + let mut place_elems = place_elems.to_vec(); + place_elems.push(ProjectionElem::Field(x.into(), fields[x])); + let projection = tcx.intern_place_elems(&place_elems); + let place = Place { + local: place.local, + projection, + }; + method(place) + }).collect(); + terminator.kind = TerminatorKind::Call { func, args: arguments, destination, target, cleanup, from_hir_call: false, fn_span }; + } + _ => {} + } + } + body +} + fn is_mir_available(tcx: TyCtxt<'_>, def_id: DefId) -> bool { let def_id = def_id.expect_local(); tcx.mir_keys(()).contains(&def_id) @@ -354,7 +416,7 @@ fn inner_mir_for_ctfe(tcx: TyCtxt<'_>, def: ty::WithOptConstParam) - debug_assert!(!body.has_free_regions(), "Free regions in MIR for CTFE"); - body + remap_mir_for_const_eval_select(tcx, body, hir::Constness::Const) } /// Obtain just the main MIR (no promoteds) and run some cleanups on it. This also runs @@ -565,7 +627,7 @@ fn inner_optimized_mir(tcx: TyCtxt<'_>, did: LocalDefId) -> Body<'_> { debug_assert!(!body.has_free_regions(), "Free regions in optimized MIR"); - body + remap_mir_for_const_eval_select(tcx, body, hir::Constness::NotConst) } /// Fetch all the promoteds of an item and prepare their MIR bodies to be ready for diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 4e37da0dedb15..be74a9d11e3c5 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -112,12 +112,6 @@ //! method in operand position, we treat it as a neighbor of the current //! mono item. Calls are just a special case of that. //! -//! #### Closures -//! In a way, closures are a simple case. Since every closure object needs to be -//! constructed somewhere, we can reliably discover them by observing -//! `RValue::Aggregate` expressions with `AggregateKind::Closure`. This is also -//! true for closures inlined from other crates. -//! //! #### Drop glue //! Drop glue mono items are introduced by MIR drop-statements. The //! generated mono item will again have drop-glue item neighbors if the @@ -835,7 +829,7 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> { mir::TerminatorKind::Call { ref func, .. } => { let callee_ty = func.ty(self.body, tcx); let callee_ty = self.monomorphize(callee_ty); - visit_fn_use(self.tcx, callee_ty, true, source, &mut self.output); + visit_fn_use(self.tcx, callee_ty, true, source, &mut self.output) } mir::TerminatorKind::Drop { ref place, .. } | mir::TerminatorKind::DropAndReplace { ref place, .. } => { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index c501fba21c60f..f6ce7675c8fae 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -510,7 +510,6 @@ symbols! { const_deallocate, const_eval_limit, const_eval_select, - const_eval_select_ct, const_evaluatable_checked, const_extern_fn, const_fn, diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs index ce0e59d062266..d1d27db9131fd 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs @@ -31,7 +31,7 @@ use rustc_middle::ty::visit::TypeVisitable; use rustc_middle::ty::{self, DefIdTree, IsSuggestable, Ty, TypeSuperVisitable, TypeVisitor}; use rustc_session::Session; use rustc_span::symbol::Ident; -use rustc_span::{self, Span}; +use rustc_span::{self, sym, Span}; use rustc_trait_selection::traits::{self, ObligationCauseCode, SelectionContext}; use std::iter; @@ -224,6 +224,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let minimum_input_count = expected_input_tys.len(); let provided_arg_count = provided_args.len(); + let is_const_eval_select = matches!(fn_def_id, Some(def_id) if + self.tcx.def_kind(def_id) == hir::def::DefKind::Fn + && self.tcx.is_intrinsic(def_id) + && self.tcx.item_name(def_id) == sym::const_eval_select); + // We introduce a helper function to demand that a given argument satisfy a given input // This is more complicated than just checking type equality, as arguments could be coerced // This version writes those types back so further type checking uses the narrowed types @@ -259,6 +264,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return Compatibility::Incompatible(coerce_error); } + // Check that second and third argument of `const_eval_select` must be `FnDef`, and additionally that + // the second argument must be `const fn`. The first argument must be a tuple, but this is already expressed + // in the function signature (`F: FnOnce`), so I did not bother to add another check here. + // + // This check is here because there is currently no way to express a trait bound for `FnDef` types only. + if is_const_eval_select && (1..=2).contains(&idx) { + if let ty::FnDef(def_id, _) = checked_ty.kind() { + if idx == 1 && !self.tcx.is_const_fn_raw(*def_id) { + self.tcx + .sess + .struct_span_err(provided_arg.span, "this argument must be a `const fn`") + .help("consult the documentation on `const_eval_select` for more information") + .emit(); + } + } else { + self.tcx + .sess + .struct_span_err(provided_arg.span, "this argument must be a function item") + .note(format!("expected a function item, found {checked_ty}")) + .help( + "consult the documentation on `const_eval_select` for more information", + ) + .emit(); + } + } + // 3. Check if the formal type is a supertype of the checked one // and register any such obligations for future type checks let supertype_error = self diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index d610f0a02f403..12e4dfe74fb88 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -54,7 +54,9 @@ )] #![allow(missing_docs)] -use crate::marker::{Destruct, DiscriminantKind}; +#[cfg(bootstrap)] +use crate::marker::Destruct; +use crate::marker::DiscriminantKind; use crate::mem; // These imports are used for simplifying intra-doc links @@ -2085,6 +2087,65 @@ extern "rust-intrinsic" { /// `ptr` must point to a vtable. /// The intrinsic will return the alignment stored in that vtable. pub fn vtable_align(ptr: *const ()) -> usize; + + /// Selects which function to call depending on the context. + /// + /// If this function is evaluated at compile-time, then a call to this + /// intrinsic will be replaced with a call to `called_in_const`. It gets + /// replaced with a call to `called_at_rt` otherwise. + /// + /// # Type Requirements + /// + /// The two functions must be both function items. They cannot be function + /// pointers or closures. The first function must be a `const fn`. + /// + /// `arg` will be the tupled arguments that will be passed to either one of + /// the two functions, therefore, both functions must accept the same type of + /// arguments. Both functions must return RET. + /// + /// # Safety + /// + /// The two functions must behave observably equivalent. Safe code in other + /// crates may assume that calling a `const fn` at compile-time and at run-time + /// produces the same result. A function that produces a different result when + /// evaluated at run-time, or has any other observable side-effects, is + /// *unsound*. + /// + /// Here is an example of how this could cause a problem: + /// ```no_run + /// #![feature(const_eval_select)] + /// #![feature(core_intrinsics)] + /// use std::hint::unreachable_unchecked; + /// use std::intrinsics::const_eval_select; + /// + /// // Crate A + /// pub const fn inconsistent() -> i32 { + /// fn runtime() -> i32 { 1 } + /// const fn compiletime() -> i32 { 2 } + /// + /// unsafe { + // // ⚠ This code violates the required equivalence of `compiletime` + /// // and `runtime`. + /// const_eval_select((), compiletime, runtime) + /// } + /// } + /// + /// // Crate B + /// const X: i32 = inconsistent(); + /// let x = inconsistent(); + /// if x != X { unsafe { unreachable_unchecked(); }} + /// ``` + /// + /// This code causes Undefined Behavior when being run, since the + /// `unreachable_unchecked` is actually being reached. The bug is in *crate A*, + /// which violates the principle that a `const fn` must behave the same at + /// compile-time and at run-time. The unsafe code in crate B is fine. + #[cfg(not(bootstrap))] + #[rustc_const_unstable(feature = "const_eval_select", issue = "none")] + pub fn const_eval_select(arg: ARG, called_in_const: F, called_at_rt: G) -> RET + where + G: FnOnce, + F: FnOnce; } // Some functions are defined here because they accidentally got made @@ -2095,6 +2156,11 @@ extern "rust-intrinsic" { /// Check that the preconditions of an unsafe function are followed, if debug_assertions are on, /// and only at runtime. /// +/// This macro should be called as `assert_unsafe_precondition!([Generics](name: Type) => Expression)` +/// where the names specified will be moved into the macro as captured variables, and defines an item +/// to call `const_eval_select` on. The tokens inside the square brackets are used to denote generics +/// for the function declaractions and can be omitted if there is no generics. +/// /// # Safety /// /// Invoking this macro is only sound if the following code is already UB when the passed @@ -2109,18 +2175,20 @@ extern "rust-intrinsic" { /// the occasional mistake, and this check should help them figure things out. #[allow_internal_unstable(const_eval_select)] // permit this to be called in stably-const fn macro_rules! assert_unsafe_precondition { - ($e:expr) => { + ($([$($tt:tt)*])?($($i:ident:$ty:ty),*$(,)?) => $e:expr) => { if cfg!(debug_assertions) { - // Use a closure so that we can capture arbitrary expressions from the invocation - let runtime = || { + // allow non_snake_case to allow capturing const generics + #[allow(non_snake_case)] + fn runtime$(<$($tt)*>)?($($i:$ty),*) { if !$e { // abort instead of panicking to reduce impact on code size ::core::intrinsics::abort(); } - }; - const fn comptime() {} + } + #[allow(non_snake_case)] + const fn comptime$(<$($tt)*>)?($(_:$ty),*) {} - ::core::intrinsics::const_eval_select((), comptime, runtime); + ::core::intrinsics::const_eval_select(($($i,)*), comptime, runtime); } }; } @@ -2243,7 +2311,7 @@ pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: us // SAFETY: the safety contract for `copy_nonoverlapping` must be // upheld by the caller. unsafe { - assert_unsafe_precondition!( + assert_unsafe_precondition!([T](src: *const T, dst: *mut T, count: usize) => is_aligned_and_not_null(src) && is_aligned_and_not_null(dst) && is_nonoverlapping(src, dst, count) @@ -2329,7 +2397,8 @@ pub const unsafe fn copy(src: *const T, dst: *mut T, count: usize) { // SAFETY: the safety contract for `copy` must be upheld by the caller. unsafe { - assert_unsafe_precondition!(is_aligned_and_not_null(src) && is_aligned_and_not_null(dst)); + assert_unsafe_precondition!([T](src: *const T, dst: *mut T) => + is_aligned_and_not_null(src) && is_aligned_and_not_null(dst)); copy(src, dst, count) } } @@ -2397,63 +2466,12 @@ pub const unsafe fn write_bytes(dst: *mut T, val: u8, count: usize) { // SAFETY: the safety contract for `write_bytes` must be upheld by the caller. unsafe { - assert_unsafe_precondition!(is_aligned_and_not_null(dst)); + assert_unsafe_precondition!([T](dst: *mut T) => is_aligned_and_not_null(dst)); write_bytes(dst, val, count) } } -/// Selects which function to call depending on the context. -/// -/// If this function is evaluated at compile-time, then a call to this -/// intrinsic will be replaced with a call to `called_in_const`. It gets -/// replaced with a call to `called_at_rt` otherwise. -/// -/// # Type Requirements -/// -/// The two functions must be both function items. They cannot be function -/// pointers or closures. -/// -/// `arg` will be the arguments that will be passed to either one of the -/// two functions, therefore, both functions must accept the same type of -/// arguments. Both functions must return RET. -/// -/// # Safety -/// -/// The two functions must behave observably equivalent. Safe code in other -/// crates may assume that calling a `const fn` at compile-time and at run-time -/// produces the same result. A function that produces a different result when -/// evaluated at run-time, or has any other observable side-effects, is -/// *unsound*. -/// -/// Here is an example of how this could cause a problem: -/// ```no_run -/// #![feature(const_eval_select)] -/// #![feature(core_intrinsics)] -/// use std::hint::unreachable_unchecked; -/// use std::intrinsics::const_eval_select; -/// -/// // Crate A -/// pub const fn inconsistent() -> i32 { -/// fn runtime() -> i32 { 1 } -/// const fn compiletime() -> i32 { 2 } -/// -/// unsafe { -// // ⚠ This code violates the required equivalence of `compiletime` -/// // and `runtime`. -/// const_eval_select((), compiletime, runtime) -/// } -/// } -/// -/// // Crate B -/// const X: i32 = inconsistent(); -/// let x = inconsistent(); -/// if x != X { unsafe { unreachable_unchecked(); }} -/// ``` -/// -/// This code causes Undefined Behavior when being run, since the -/// `unreachable_unchecked` is actually being reached. The bug is in *crate A*, -/// which violates the principle that a `const fn` must behave the same at -/// compile-time and at run-time. The unsafe code in crate B is fine. +#[cfg(bootstrap)] #[unstable( feature = "const_eval_select", issue = "none", @@ -2475,6 +2493,7 @@ where called_at_rt.call_once(arg) } +#[cfg(bootstrap)] #[unstable( feature = "const_eval_select", issue = "none", diff --git a/library/core/src/mem/valid_align.rs b/library/core/src/mem/valid_align.rs index b9ccc0b4c799f..32b2afb72b0dc 100644 --- a/library/core/src/mem/valid_align.rs +++ b/library/core/src/mem/valid_align.rs @@ -28,7 +28,7 @@ impl ValidAlign { #[inline] pub(crate) const unsafe fn new_unchecked(align: usize) -> Self { // SAFETY: Precondition passed to the caller. - unsafe { assert_unsafe_precondition!(align.is_power_of_two()) }; + unsafe { assert_unsafe_precondition!((align: usize) => align.is_power_of_two()) }; // SAFETY: By precondition, this must be a power of two, and // our variants encompass all possible powers of two. diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index da41ea536357a..6983d83808db8 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1033,10 +1033,13 @@ impl f32 { } } } - // SAFETY: `u32` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - let rt_f32_to_u32 = |rt| unsafe { mem::transmute::(rt) }; + + fn rt_f32_to_u32(x: f32) -> u32 { + // SAFETY: `u32` is a plain old datatype so we can always... uh... + // ...look, just pretend you forgot what you just read. + // Stability concerns. + unsafe { mem::transmute(x) } + } // SAFETY: We use internal implementations that either always work or fail at compile time. unsafe { intrinsics::const_eval_select((self,), ct_f32_to_u32, rt_f32_to_u32) } } @@ -1121,10 +1124,13 @@ impl f32 { } } } - // SAFETY: `u32` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - let rt_u32_to_f32 = |rt| unsafe { mem::transmute::(rt) }; + + fn rt_u32_to_f32(x: u32) -> f32 { + // SAFETY: `u32` is a plain old datatype so we can always... uh... + // ...look, just pretend you forgot what you just read. + // Stability concerns. + unsafe { mem::transmute(x) } + } // SAFETY: We use internal implementations that either always work or fail at compile time. unsafe { intrinsics::const_eval_select((v,), ct_u32_to_f32, rt_u32_to_f32) } } diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 631d559df5f70..ce64afc0bd892 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1026,10 +1026,13 @@ impl f64 { } } } - // SAFETY: `u64` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - let rt_f64_to_u64 = |rt| unsafe { mem::transmute::(rt) }; + + fn rt_f64_to_u64(rt: f64) -> u64 { + // SAFETY: `u64` is a plain old datatype so we can always... uh... + // ...look, just pretend you forgot what you just read. + // Stability concerns. + unsafe { mem::transmute::(rt) } + } // SAFETY: We use internal implementations that either always work or fail at compile time. unsafe { intrinsics::const_eval_select((self,), ct_f64_to_u64, rt_f64_to_u64) } } @@ -1119,10 +1122,13 @@ impl f64 { } } } - // SAFETY: `u64` is a plain old datatype so we can always... uh... - // ...look, just pretend you forgot what you just read. - // Stability concerns. - let rt_u64_to_f64 = |rt| unsafe { mem::transmute::(rt) }; + + fn rt_u64_to_f64(rt: u64) -> f64 { + // SAFETY: `u64` is a plain old datatype so we can always... uh... + // ...look, just pretend you forgot what you just read. + // Stability concerns. + unsafe { mem::transmute::(rt) } + } // SAFETY: We use internal implementations that either always work or fail at compile time. unsafe { intrinsics::const_eval_select((v,), ct_u64_to_f64, rt_u64_to_f64) } } diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index 1cf306f2103b4..532a09736a7ac 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -56,7 +56,7 @@ macro_rules! nonzero_integers { pub const unsafe fn new_unchecked(n: $Int) -> Self { // SAFETY: this is guaranteed to be safe by the caller. unsafe { - core::intrinsics::assert_unsafe_precondition!(n != 0); + core::intrinsics::assert_unsafe_precondition!((n: $Int) => n != 0); Self(n) } } diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index f5c72d7975534..80bff74f3e9f4 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -755,9 +755,12 @@ impl *const T { where T: Sized, { + let this = self; // SAFETY: The comparison has no side-effects, and the intrinsic // does this check internally in the CTFE implementation. - unsafe { assert_unsafe_precondition!(self >= origin) }; + unsafe { + assert_unsafe_precondition!([T](this: *const T, origin: *const T) => this >= origin) + }; let pointee_size = mem::size_of::(); assert!(0 < pointee_size && pointee_size <= isize::MAX as usize); diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 41a2685d361c8..e976abed774b8 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -886,7 +886,7 @@ pub const unsafe fn swap_nonoverlapping(x: *mut T, y: *mut T, count: usize) { // SAFETY: the caller must guarantee that `x` and `y` are // valid for writes and properly aligned. unsafe { - assert_unsafe_precondition!( + assert_unsafe_precondition!([T](x: *mut T, y: *mut T, count: usize) => is_aligned_and_not_null(x) && is_aligned_and_not_null(y) && is_nonoverlapping(x, y, count) @@ -983,7 +983,7 @@ pub const unsafe fn replace(dst: *mut T, mut src: T) -> T { // and cannot overlap `src` since `dst` must point to a distinct // allocated object. unsafe { - assert_unsafe_precondition!(is_aligned_and_not_null(dst)); + assert_unsafe_precondition!([T](dst: *mut T) => is_aligned_and_not_null(dst)); mem::swap(&mut *dst, &mut src); // cannot overlap } src @@ -1470,7 +1470,7 @@ pub const unsafe fn write_unaligned(dst: *mut T, src: T) { pub unsafe fn read_volatile(src: *const T) -> T { // SAFETY: the caller must uphold the safety contract for `volatile_load`. unsafe { - assert_unsafe_precondition!(is_aligned_and_not_null(src)); + assert_unsafe_precondition!([T](src: *const T) => is_aligned_and_not_null(src)); intrinsics::volatile_load(src) } } @@ -1541,7 +1541,7 @@ pub unsafe fn read_volatile(src: *const T) -> T { pub unsafe fn write_volatile(dst: *mut T, src: T) { // SAFETY: the caller must uphold the safety contract for `volatile_store`. unsafe { - assert_unsafe_precondition!(is_aligned_and_not_null(dst)); + assert_unsafe_precondition!([T](dst: *mut T) => is_aligned_and_not_null(dst)); intrinsics::volatile_store(dst, src); } } diff --git a/library/core/src/slice/index.rs b/library/core/src/slice/index.rs index fd7ecf3daf316..3403a5a86f7cb 100644 --- a/library/core/src/slice/index.rs +++ b/library/core/src/slice/index.rs @@ -48,10 +48,12 @@ const fn slice_start_index_len_fail(index: usize, len: usize) -> ! { } // FIXME const-hack +#[track_caller] fn slice_start_index_len_fail_rt(index: usize, len: usize) -> ! { panic!("range start index {index} out of range for slice of length {len}"); } +#[track_caller] const fn slice_start_index_len_fail_ct(_: usize, _: usize) -> ! { panic!("slice start index is out of range for slice"); } @@ -69,10 +71,12 @@ const fn slice_end_index_len_fail(index: usize, len: usize) -> ! { } // FIXME const-hack +#[track_caller] fn slice_end_index_len_fail_rt(index: usize, len: usize) -> ! { panic!("range end index {index} out of range for slice of length {len}"); } +#[track_caller] const fn slice_end_index_len_fail_ct(_: usize, _: usize) -> ! { panic!("slice end index is out of range for slice"); } @@ -88,10 +92,12 @@ const fn slice_index_order_fail(index: usize, end: usize) -> ! { } // FIXME const-hack +#[track_caller] fn slice_index_order_fail_rt(index: usize, end: usize) -> ! { panic!("slice index starts at {index} but ends at {end}"); } +#[track_caller] const fn slice_index_order_fail_ct(_: usize, _: usize) -> ! { panic!("slice index start is larger than end"); } @@ -217,21 +223,23 @@ unsafe impl const SliceIndex<[T]> for usize { #[inline] unsafe fn get_unchecked(self, slice: *const [T]) -> *const T { + let this = self; // SAFETY: the caller guarantees that `slice` is not dangling, so it // cannot be longer than `isize::MAX`. They also guarantee that // `self` is in bounds of `slice` so `self` cannot overflow an `isize`, // so the call to `add` is safe. unsafe { - assert_unsafe_precondition!(self < slice.len()); + assert_unsafe_precondition!([T](this: usize, slice: *const [T]) => this < slice.len()); slice.as_ptr().add(self) } } #[inline] unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut T { + let this = self; // SAFETY: see comments for `get_unchecked` above. unsafe { - assert_unsafe_precondition!(self < slice.len()); + assert_unsafe_precondition!([T](this: usize, slice: *mut [T]) => this < slice.len()); slice.as_mut_ptr().add(self) } } @@ -276,22 +284,26 @@ unsafe impl const SliceIndex<[T]> for ops::Range { #[inline] unsafe fn get_unchecked(self, slice: *const [T]) -> *const [T] { + let this = ops::Range { start: self.start, end: self.end }; // SAFETY: the caller guarantees that `slice` is not dangling, so it // cannot be longer than `isize::MAX`. They also guarantee that // `self` is in bounds of `slice` so `self` cannot overflow an `isize`, // so the call to `add` is safe. unsafe { - assert_unsafe_precondition!(self.end >= self.start && self.end <= slice.len()); + assert_unsafe_precondition!([T](this: ops::Range, slice: *const [T]) => + this.end >= this.start && this.end <= slice.len()); ptr::slice_from_raw_parts(slice.as_ptr().add(self.start), self.end - self.start) } } #[inline] unsafe fn get_unchecked_mut(self, slice: *mut [T]) -> *mut [T] { + let this = ops::Range { start: self.start, end: self.end }; // SAFETY: see comments for `get_unchecked` above. unsafe { - assert_unsafe_precondition!(self.end >= self.start && self.end <= slice.len()); + assert_unsafe_precondition!([T](this: ops::Range, slice: *mut [T]) => + this.end >= this.start && this.end <= slice.len()); ptr::slice_from_raw_parts_mut(slice.as_mut_ptr().add(self.start), self.end - self.start) } } diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 1958745b5868c..6a7150d2986ed 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -656,10 +656,11 @@ impl [T] { #[unstable(feature = "slice_swap_unchecked", issue = "88539")] #[rustc_const_unstable(feature = "const_swap", issue = "83163")] pub const unsafe fn swap_unchecked(&mut self, a: usize, b: usize) { - let ptr = self.as_mut_ptr(); + let this = self; + let ptr = this.as_mut_ptr(); // SAFETY: caller has to guarantee that `a < self.len()` and `b < self.len()` unsafe { - assert_unsafe_precondition!(a < self.len() && b < self.len()); + assert_unsafe_precondition!([T](a: usize, b: usize, this: &mut [T]) => a < this.len() && b < this.len()); ptr::swap(ptr.add(a), ptr.add(b)); } } @@ -972,9 +973,10 @@ impl [T] { #[inline] #[must_use] pub unsafe fn as_chunks_unchecked(&self) -> &[[T; N]] { + let this = self; // SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length let new_len = unsafe { - assert_unsafe_precondition!(N != 0 && self.len() % N == 0); + assert_unsafe_precondition!([T](this: &[T], N: usize) => N != 0 && this.len() % N == 0); exact_div(self.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into @@ -1111,10 +1113,11 @@ impl [T] { #[inline] #[must_use] pub unsafe fn as_chunks_unchecked_mut(&mut self) -> &mut [[T; N]] { + let this = &*self; // SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length let new_len = unsafe { - assert_unsafe_precondition!(N != 0 && self.len() % N == 0); - exact_div(self.len(), N) + assert_unsafe_precondition!([T](this: &[T], N: usize) => N != 0 && this.len() % N == 0); + exact_div(this.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. @@ -1687,7 +1690,7 @@ impl [T] { // `[ptr; mid]` and `[mid; len]` are not overlapping, so returning a mutable reference // is fine. unsafe { - assert_unsafe_precondition!(mid <= len); + assert_unsafe_precondition!((mid: usize, len: usize) => mid <= len); (from_raw_parts_mut(ptr, mid), from_raw_parts_mut(ptr.add(mid), len - mid)) } } diff --git a/library/core/src/slice/raw.rs b/library/core/src/slice/raw.rs index 107e71ab68b01..f1e8bc79bf4a2 100644 --- a/library/core/src/slice/raw.rs +++ b/library/core/src/slice/raw.rs @@ -90,7 +90,7 @@ use crate::ptr; pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] { // SAFETY: the caller must uphold the safety contract for `from_raw_parts`. unsafe { - assert_unsafe_precondition!( + assert_unsafe_precondition!([T](data: *const T, len: usize) => is_aligned_and_not_null(data) && crate::mem::size_of::().saturating_mul(len) <= isize::MAX as usize ); @@ -134,7 +134,7 @@ pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] pub const unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a mut [T] { // SAFETY: the caller must uphold the safety contract for `from_raw_parts_mut`. unsafe { - assert_unsafe_precondition!( + assert_unsafe_precondition!([T](data: *mut T, len: usize) => is_aligned_and_not_null(data) && crate::mem::size_of::().saturating_mul(len) <= isize::MAX as usize ); diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index 2120bf61d759d..f673aa2a44b10 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -91,10 +91,12 @@ const fn slice_error_fail(s: &str, begin: usize, end: usize) -> ! { } } +#[track_caller] const fn slice_error_fail_ct(_: &str, _: usize, _: usize) -> ! { panic!("failed to slice string"); } +#[track_caller] fn slice_error_fail_rt(s: &str, begin: usize, end: usize) -> ! { const MAX_DISPLAY_LENGTH: usize = 256; let trunc_len = s.floor_char_boundary(MAX_DISPLAY_LENGTH); diff --git a/src/test/ui/consts/const-float-bits-reject-conv.stderr b/src/test/ui/consts/const-float-bits-reject-conv.stderr index d6e993a10101d..01f2f48956458 100644 --- a/src/test/ui/consts/const-float-bits-reject-conv.stderr +++ b/src/test/ui/consts/const-float-bits-reject-conv.stderr @@ -10,16 +10,6 @@ LL | panic!("const-eval error: cannot use f32::to_bits on a LL | unsafe { intrinsics::const_eval_select((self,), ct_f32_to_u32, rt_f32_to_u32) } | -------------------------------------------------------------------- inside `core::f32::::to_bits` at $SRC_DIR/core/src/num/f32.rs:LL:COL | - ::: $SRC_DIR/core/src/ops/function.rs:LL:COL - | -LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; - | ------------------------------------------------------------------ inside ` u32 {core::f32::::to_bits::ct_f32_to_u32} as FnOnce<(f32,)>>::call_once - shim(fn(f32) -> u32 {core::f32::::to_bits::ct_f32_to_u32})` at $SRC_DIR/core/src/ops/function.rs:LL:COL - | - ::: $SRC_DIR/core/src/intrinsics.rs:LL:COL - | -LL | called_in_const.call_once(arg) - | ------------------------------ inside `const_eval_select::<(f32,), fn(f32) -> u32 {core::f32::::to_bits::ct_f32_to_u32}, [closure@core::f32::::to_bits::{closure#0}], u32>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL - | ::: $DIR/const-float-bits-reject-conv.rs:27:30 | LL | const MASKED_NAN1: u32 = f32::NAN.to_bits() ^ 0x002A_AAAA; @@ -39,16 +29,6 @@ LL | panic!("const-eval error: cannot use f32::to_bits on a LL | unsafe { intrinsics::const_eval_select((self,), ct_f32_to_u32, rt_f32_to_u32) } | -------------------------------------------------------------------- inside `core::f32::::to_bits` at $SRC_DIR/core/src/num/f32.rs:LL:COL | - ::: $SRC_DIR/core/src/ops/function.rs:LL:COL - | -LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; - | ------------------------------------------------------------------ inside ` u32 {core::f32::::to_bits::ct_f32_to_u32} as FnOnce<(f32,)>>::call_once - shim(fn(f32) -> u32 {core::f32::::to_bits::ct_f32_to_u32})` at $SRC_DIR/core/src/ops/function.rs:LL:COL - | - ::: $SRC_DIR/core/src/intrinsics.rs:LL:COL - | -LL | called_in_const.call_once(arg) - | ------------------------------ inside `const_eval_select::<(f32,), fn(f32) -> u32 {core::f32::::to_bits::ct_f32_to_u32}, [closure@core::f32::::to_bits::{closure#0}], u32>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL - | ::: $DIR/const-float-bits-reject-conv.rs:28:30 | LL | const MASKED_NAN2: u32 = f32::NAN.to_bits() ^ 0x0055_5555; @@ -117,16 +97,6 @@ LL | panic!("const-eval error: cannot use f64::to_bits on a LL | unsafe { intrinsics::const_eval_select((self,), ct_f64_to_u64, rt_f64_to_u64) } | -------------------------------------------------------------------- inside `core::f64::::to_bits` at $SRC_DIR/core/src/num/f64.rs:LL:COL | - ::: $SRC_DIR/core/src/ops/function.rs:LL:COL - | -LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; - | ------------------------------------------------------------------ inside ` u64 {core::f64::::to_bits::ct_f64_to_u64} as FnOnce<(f64,)>>::call_once - shim(fn(f64) -> u64 {core::f64::::to_bits::ct_f64_to_u64})` at $SRC_DIR/core/src/ops/function.rs:LL:COL - | - ::: $SRC_DIR/core/src/intrinsics.rs:LL:COL - | -LL | called_in_const.call_once(arg) - | ------------------------------ inside `const_eval_select::<(f64,), fn(f64) -> u64 {core::f64::::to_bits::ct_f64_to_u64}, [closure@core::f64::::to_bits::{closure#0}], u64>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL - | ::: $DIR/const-float-bits-reject-conv.rs:54:30 | LL | const MASKED_NAN1: u64 = f64::NAN.to_bits() ^ 0x000A_AAAA_AAAA_AAAA; @@ -146,16 +116,6 @@ LL | panic!("const-eval error: cannot use f64::to_bits on a LL | unsafe { intrinsics::const_eval_select((self,), ct_f64_to_u64, rt_f64_to_u64) } | -------------------------------------------------------------------- inside `core::f64::::to_bits` at $SRC_DIR/core/src/num/f64.rs:LL:COL | - ::: $SRC_DIR/core/src/ops/function.rs:LL:COL - | -LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; - | ------------------------------------------------------------------ inside ` u64 {core::f64::::to_bits::ct_f64_to_u64} as FnOnce<(f64,)>>::call_once - shim(fn(f64) -> u64 {core::f64::::to_bits::ct_f64_to_u64})` at $SRC_DIR/core/src/ops/function.rs:LL:COL - | - ::: $SRC_DIR/core/src/intrinsics.rs:LL:COL - | -LL | called_in_const.call_once(arg) - | ------------------------------ inside `const_eval_select::<(f64,), fn(f64) -> u64 {core::f64::::to_bits::ct_f64_to_u64}, [closure@core::f64::::to_bits::{closure#0}], u64>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL - | ::: $DIR/const-float-bits-reject-conv.rs:55:30 | LL | const MASKED_NAN2: u64 = f64::NAN.to_bits() ^ 0x0005_5555_5555_5555; diff --git a/src/test/ui/intrinsics/const-eval-select-backtrace-std.rs b/src/test/ui/intrinsics/const-eval-select-backtrace-std.rs new file mode 100644 index 0000000000000..29aefe07162b3 --- /dev/null +++ b/src/test/ui/intrinsics/const-eval-select-backtrace-std.rs @@ -0,0 +1,6 @@ +// See issue #100696. +// run-fail +// check-run-results +fn main() { + &""[1..]; +} diff --git a/src/test/ui/intrinsics/const-eval-select-backtrace-std.run.stderr b/src/test/ui/intrinsics/const-eval-select-backtrace-std.run.stderr new file mode 100644 index 0000000000000..e53e6034620ca --- /dev/null +++ b/src/test/ui/intrinsics/const-eval-select-backtrace-std.run.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'byte index 1 is out of bounds of ``', $DIR/const-eval-select-backtrace-std.rs:5:6 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/test/ui/intrinsics/const-eval-select-backtrace.rs b/src/test/ui/intrinsics/const-eval-select-backtrace.rs new file mode 100644 index 0000000000000..99f0725200c1d --- /dev/null +++ b/src/test/ui/intrinsics/const-eval-select-backtrace.rs @@ -0,0 +1,18 @@ +#![feature(core_intrinsics)] +// See issue #100696. +// run-fail +// check-run-results + +#[track_caller] +fn uhoh() { + panic!("Aaah!") +} + +const fn c() {} + +fn main() { + // safety: this is unsound and just used to test + unsafe { + std::intrinsics::const_eval_select((), c, uhoh); + } +} diff --git a/src/test/ui/intrinsics/const-eval-select-backtrace.run.stderr b/src/test/ui/intrinsics/const-eval-select-backtrace.run.stderr new file mode 100644 index 0000000000000..2fd730ac7a68a --- /dev/null +++ b/src/test/ui/intrinsics/const-eval-select-backtrace.run.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'Aaah!', $DIR/const-eval-select-backtrace.rs:16:9 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/test/ui/intrinsics/const-eval-select-bad.rs b/src/test/ui/intrinsics/const-eval-select-bad.rs index e5640f5ab53b1..fa14efad7b4a4 100644 --- a/src/test/ui/intrinsics/const-eval-select-bad.rs +++ b/src/test/ui/intrinsics/const-eval-select-bad.rs @@ -5,10 +5,13 @@ use std::intrinsics::const_eval_select; const fn not_fn_items() { const_eval_select((), || {}, || {}); - //~^ ERROR the trait bound + //~^ ERROR this argument must be a function item + //~| ERROR this argument must be a function item const_eval_select((), 42, 0xDEADBEEF); - //~^ ERROR the trait bound + //~^ ERROR expected a `FnOnce<()>` closure //~| ERROR expected a `FnOnce<()>` closure + //~| ERROR this argument must be a function item + //~| ERROR this argument must be a function item } const fn foo(n: i32) -> i32 { @@ -35,4 +38,9 @@ const fn args_ty_mismatch() { //~^ ERROR type mismatch } +const fn non_const_fn() { + const_eval_select((1,), bar, bar); + //~^ ERROR this argument must be a `const fn` +} + fn main() {} diff --git a/src/test/ui/intrinsics/const-eval-select-bad.stderr b/src/test/ui/intrinsics/const-eval-select-bad.stderr index 904e83624b3f2..3720528ad4e40 100644 --- a/src/test/ui/intrinsics/const-eval-select-bad.stderr +++ b/src/test/ui/intrinsics/const-eval-select-bad.stderr @@ -1,42 +1,57 @@ -error[E0277]: the trait bound `[closure@$DIR/const-eval-select-bad.rs:7:27: 7:29]: FnOnce<()>` is not satisfied +error: this argument must be a function item --> $DIR/const-eval-select-bad.rs:7:27 | LL | const_eval_select((), || {}, || {}); - | ----------------- ^^^^^ expected an `FnOnce<()>` closure, found `[closure@$DIR/const-eval-select-bad.rs:7:27: 7:29]` - | | - | required by a bound introduced by this call + | ^^^^^ | - = help: the trait `~const FnOnce<()>` is not implemented for closure `[closure@$DIR/const-eval-select-bad.rs:7:27: 7:29]` -note: the trait `FnOnce<()>` is implemented for `[closure@$DIR/const-eval-select-bad.rs:7:27: 7:29]`, but that implementation is not `const` - --> $DIR/const-eval-select-bad.rs:7:27 + = note: expected a function item, found [closure@$DIR/const-eval-select-bad.rs:7:27: 7:29] + = help: consult the documentation on `const_eval_select` for more information + +error: this argument must be a function item + --> $DIR/const-eval-select-bad.rs:7:34 | LL | const_eval_select((), || {}, || {}); - | ^^^^^ - = note: wrap the `[closure@$DIR/const-eval-select-bad.rs:7:27: 7:29]` in a closure with no arguments: `|| { /* code */ }` -note: required by a bound in `const_eval_select` - --> $SRC_DIR/core/src/intrinsics.rs:LL:COL + | ^^^^^ | -LL | F: ~const FnOnce, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `const_eval_select` + = note: expected a function item, found [closure@$DIR/const-eval-select-bad.rs:7:34: 7:36] + = help: consult the documentation on `const_eval_select` for more information -error[E0277]: the trait bound `{integer}: FnOnce<()>` is not satisfied - --> $DIR/const-eval-select-bad.rs:9:27 +error: this argument must be a function item + --> $DIR/const-eval-select-bad.rs:10:27 + | +LL | const_eval_select((), 42, 0xDEADBEEF); + | ^^ + | + = note: expected a function item, found {integer} + = help: consult the documentation on `const_eval_select` for more information + +error[E0277]: expected a `FnOnce<()>` closure, found `{integer}` + --> $DIR/const-eval-select-bad.rs:10:27 | LL | const_eval_select((), 42, 0xDEADBEEF); | ----------------- ^^ expected an `FnOnce<()>` closure, found `{integer}` | | | required by a bound introduced by this call | - = help: the trait `~const FnOnce<()>` is not implemented for `{integer}` + = help: the trait `FnOnce<()>` is not implemented for `{integer}` = note: wrap the `{integer}` in a closure with no arguments: `|| { /* code */ }` note: required by a bound in `const_eval_select` --> $SRC_DIR/core/src/intrinsics.rs:LL:COL | -LL | F: ~const FnOnce, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `const_eval_select` +LL | F: FnOnce; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `const_eval_select` + +error: this argument must be a function item + --> $DIR/const-eval-select-bad.rs:10:31 + | +LL | const_eval_select((), 42, 0xDEADBEEF); + | ^^^^^^^^^^ + | + = note: expected a function item, found {integer} + = help: consult the documentation on `const_eval_select` for more information error[E0277]: expected a `FnOnce<()>` closure, found `{integer}` - --> $DIR/const-eval-select-bad.rs:9:31 + --> $DIR/const-eval-select-bad.rs:10:31 | LL | const_eval_select((), 42, 0xDEADBEEF); | ----------------- ^^^^^^^^^^ expected an `FnOnce<()>` closure, found `{integer}` @@ -48,11 +63,11 @@ LL | const_eval_select((), 42, 0xDEADBEEF); note: required by a bound in `const_eval_select` --> $SRC_DIR/core/src/intrinsics.rs:LL:COL | -LL | G: FnOnce + ~const Destruct, - | ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `const_eval_select` +LL | G: FnOnce, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `const_eval_select` error[E0271]: expected `fn(i32) -> bool {bar}` to be a fn item that returns `i32`, but it returns `bool` - --> $DIR/const-eval-select-bad.rs:29:34 + --> $DIR/const-eval-select-bad.rs:32:34 | LL | const_eval_select((1,), foo, bar); | ----------------- ^^^ expected `i32`, found `bool` @@ -62,11 +77,11 @@ LL | const_eval_select((1,), foo, bar); note: required by a bound in `const_eval_select` --> $SRC_DIR/core/src/intrinsics.rs:LL:COL | -LL | G: FnOnce + ~const Destruct, - | ^^^^^^^^^^^^ required by this bound in `const_eval_select` +LL | G: FnOnce, + | ^^^^^^^^^^^^ required by this bound in `const_eval_select` error[E0631]: type mismatch in function arguments - --> $DIR/const-eval-select-bad.rs:34:32 + --> $DIR/const-eval-select-bad.rs:37:32 | LL | const fn foo(n: i32) -> i32 { | --------------------------- found signature defined here @@ -81,10 +96,18 @@ LL | const_eval_select((true,), foo, baz); note: required by a bound in `const_eval_select` --> $SRC_DIR/core/src/intrinsics.rs:LL:COL | -LL | F: ~const FnOnce, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `const_eval_select` +LL | F: FnOnce; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `const_eval_select` + +error: this argument must be a `const fn` + --> $DIR/const-eval-select-bad.rs:42:29 + | +LL | const_eval_select((1,), bar, bar); + | ^^^ + | + = help: consult the documentation on `const_eval_select` for more information -error: aborting due to 5 previous errors +error: aborting due to 9 previous errors Some errors have detailed explanations: E0271, E0277, E0631. For more information about an error, try `rustc --explain E0271`. From bd61b8fb3ff04de61f40ea62043b17dc1e418c4c Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Fri, 26 Aug 2022 16:44:07 +0000 Subject: [PATCH 2/4] Add `inline(always)` to function generated by macro --- library/core/src/intrinsics.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 12e4dfe74fb88..56f5824efd4ca 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -2179,6 +2179,7 @@ macro_rules! assert_unsafe_precondition { if cfg!(debug_assertions) { // allow non_snake_case to allow capturing const generics #[allow(non_snake_case)] + #[inline(always)] fn runtime$(<$($tt)*>)?($($i:$ty),*) { if !$e { // abort instead of panicking to reduce impact on code size From 58c8823e3183611160207a47c9914bcbf84f9eb1 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Wed, 31 Aug 2022 13:28:55 +0000 Subject: [PATCH 3/4] remap mir before running optimization passes --- compiler/rustc_mir_transform/src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 69b49604250b4..162f7d969b181 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -387,7 +387,9 @@ fn inner_mir_for_ctfe(tcx: TyCtxt<'_>, def: ty::WithOptConstParam) - .body_const_context(def.did) .expect("mir_for_ctfe should not be used for runtime functions"); - let mut body = tcx.mir_drops_elaborated_and_const_checked(def).borrow().clone(); + let body = tcx.mir_drops_elaborated_and_const_checked(def).borrow().clone(); + + let mut body = remap_mir_for_const_eval_select(tcx, body, hir::Constness::Const); match context { // Do not const prop functions, either they get executed at runtime or exported to metadata, @@ -416,7 +418,7 @@ fn inner_mir_for_ctfe(tcx: TyCtxt<'_>, def: ty::WithOptConstParam) - debug_assert!(!body.has_free_regions(), "Free regions in MIR for CTFE"); - remap_mir_for_const_eval_select(tcx, body, hir::Constness::Const) + body } /// Obtain just the main MIR (no promoteds) and run some cleanups on it. This also runs @@ -620,14 +622,15 @@ fn inner_optimized_mir(tcx: TyCtxt<'_>, did: LocalDefId) -> Body<'_> { Some(other) => panic!("do not use `optimized_mir` for constants: {:?}", other), } debug!("about to call mir_drops_elaborated..."); - let mut body = + let body = tcx.mir_drops_elaborated_and_const_checked(ty::WithOptConstParam::unknown(did)).steal(); + let mut body = remap_mir_for_const_eval_select(tcx, body, hir::Constness::NotConst); debug!("body: {:#?}", body); run_optimization_passes(tcx, &mut body); debug_assert!(!body.has_free_regions(), "Free regions in optimized MIR"); - remap_mir_for_const_eval_select(tcx, body, hir::Constness::NotConst) + body } /// Fetch all the promoteds of an item and prepare their MIR bodies to be ready for From 65b685e82d26600ac8b468ef58954526163dfdf2 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Thu, 1 Sep 2022 18:53:17 +0800 Subject: [PATCH 4/4] Add `inline(always)` to rt functions --- library/core/src/num/f32.rs | 2 ++ library/core/src/num/f64.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 6983d83808db8..c83b928efc18d 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1034,6 +1034,7 @@ impl f32 { } } + #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 fn rt_f32_to_u32(x: f32) -> u32 { // SAFETY: `u32` is a plain old datatype so we can always... uh... // ...look, just pretend you forgot what you just read. @@ -1125,6 +1126,7 @@ impl f32 { } } + #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 fn rt_u32_to_f32(x: u32) -> f32 { // SAFETY: `u32` is a plain old datatype so we can always... uh... // ...look, just pretend you forgot what you just read. diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index ce64afc0bd892..28d23f733debd 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1027,6 +1027,7 @@ impl f64 { } } + #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 fn rt_f64_to_u64(rt: f64) -> u64 { // SAFETY: `u64` is a plain old datatype so we can always... uh... // ...look, just pretend you forgot what you just read. @@ -1123,6 +1124,7 @@ impl f64 { } } + #[inline(always)] // See https://github.com/rust-lang/compiler-builtins/issues/491 fn rt_u64_to_f64(rt: u64) -> f64 { // SAFETY: `u64` is a plain old datatype so we can always... uh... // ...look, just pretend you forgot what you just read.