diff --git a/src/doc/unstable-book/src/language-features/const-eval-limit.md b/src/doc/unstable-book/src/language-features/const-eval-limit.md new file mode 100644 index 0000000000000..df68e83bcac74 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/const-eval-limit.md @@ -0,0 +1,7 @@ +# `const_eval_limit` + +The tracking issue for this feature is: [#67217] + +[#67217]: https://github.com/rust-lang/rust/issues/67217 + +The `const_eval_limit` allows someone to limit the evaluation steps the CTFE undertakes to evaluate a `const fn`. diff --git a/src/librustc/middle/recursion_limit.rs b/src/librustc/middle/limits.rs similarity index 64% rename from src/librustc/middle/recursion_limit.rs rename to src/librustc/middle/limits.rs index ae31a2cc63da6..22e4f5ea22261 100644 --- a/src/librustc/middle/recursion_limit.rs +++ b/src/librustc/middle/limits.rs @@ -1,9 +1,9 @@ -// Recursion limit. -// -// There are various parts of the compiler that must impose arbitrary limits -// on how deeply they recurse to prevent stack overflow. Users can override -// this via an attribute on the crate like `#![recursion_limit="22"]`. This pass -// just peeks and looks for that attribute. +//! Registering limits, recursion_limit, type_length_limit and const_eval_limit +//! +//! There are various parts of the compiler that must impose arbitrary limits +//! on how deeply they recurse to prevent stack overflow. Users can override +//! this via an attribute on the crate like `#![recursion_limit="22"]`. This pass +//! just peeks and looks for that attribute. use crate::session::Session; use core::num::IntErrorKind; @@ -16,6 +16,7 @@ use rustc_data_structures::sync::Once; pub fn update_limits(sess: &Session, krate: &ast::Crate) { update_limit(sess, krate, &sess.recursion_limit, sym::recursion_limit, 128); update_limit(sess, krate, &sess.type_length_limit, sym::type_length_limit, 1048576); + update_limit(sess, krate, &sess.const_eval_limit, sym::const_eval_limit, 1_000_000); } fn update_limit( @@ -37,10 +38,8 @@ fn update_limit( return; } Err(e) => { - let mut err = sess.struct_span_err( - attr.span, - "`recursion_limit` must be a non-negative integer", - ); + let mut err = + sess.struct_span_err(attr.span, "`limit` must be a non-negative integer"); let value_span = attr .meta() @@ -49,11 +48,11 @@ fn update_limit( .unwrap_or(attr.span); let error_str = match e.kind() { - IntErrorKind::Overflow => "`recursion_limit` is too large", - IntErrorKind::Empty => "`recursion_limit` must be a non-negative integer", + IntErrorKind::Overflow => "`limit` is too large", + IntErrorKind::Empty => "`limit` must be a non-negative integer", IntErrorKind::InvalidDigit => "not a valid integer", - IntErrorKind::Underflow => bug!("`recursion_limit` should never underflow"), - IntErrorKind::Zero => bug!("zero is a valid `recursion_limit`"), + IntErrorKind::Underflow => bug!("`limit` should never underflow"), + IntErrorKind::Zero => bug!("zero is a valid `limit`"), kind => bug!("unimplemented IntErrorKind variant: {:?}", kind), }; diff --git a/src/librustc/middle/mod.rs b/src/librustc/middle/mod.rs index b20f2cf3a85c1..464488964afb7 100644 --- a/src/librustc/middle/mod.rs +++ b/src/librustc/middle/mod.rs @@ -28,8 +28,8 @@ pub mod lib_features { } } } +pub mod limits; pub mod privacy; -pub mod recursion_limit; pub mod region; pub mod resolve_lifetime; pub mod stability; diff --git a/src/librustc_feature/active.rs b/src/librustc_feature/active.rs index 0082f4f1a6e89..6517a22701b0e 100644 --- a/src/librustc_feature/active.rs +++ b/src/librustc_feature/active.rs @@ -552,6 +552,9 @@ declare_features! ( /// Allows the use of `no_sanitize` attribute. (active, no_sanitize, "1.42.0", Some(39699), None), + // Allows limiting the evaluation steps of const expressions + (active, const_eval_limit, "1.43.0", Some(67217), None), + // ------------------------------------------------------------------------- // feature-group-end: actual feature gates // ------------------------------------------------------------------------- diff --git a/src/librustc_feature/builtin_attrs.rs b/src/librustc_feature/builtin_attrs.rs index e2e061c185c03..c140adf64d511 100644 --- a/src/librustc_feature/builtin_attrs.rs +++ b/src/librustc_feature/builtin_attrs.rs @@ -239,6 +239,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // Limits: ungated!(recursion_limit, CrateLevel, template!(NameValueStr: "N")), ungated!(type_length_limit, CrateLevel, template!(NameValueStr: "N")), + gated!( + const_eval_limit, CrateLevel, template!(NameValueStr: "N"), const_eval_limit, + experimental!(const_eval_limit) + ), // Entry point: ungated!(main, Normal, template!(Word)), diff --git a/src/librustc_interface/passes.rs b/src/librustc_interface/passes.rs index 1e9b76c3e14d2..29e9ea1833f8e 100644 --- a/src/librustc_interface/passes.rs +++ b/src/librustc_interface/passes.rs @@ -189,7 +189,7 @@ pub fn register_plugins<'a>( } sess.time("recursion_limit", || { - middle::recursion_limit::update_limits(sess, &krate); + middle::limits::update_limits(sess, &krate); }); let mut lint_store = rustc_lint::new_lint_store( diff --git a/src/librustc_mir/const_eval/eval_queries.rs b/src/librustc_mir/const_eval/eval_queries.rs index 4d5464f774ff5..1bf748e66e2a0 100644 --- a/src/librustc_mir/const_eval/eval_queries.rs +++ b/src/librustc_mir/const_eval/eval_queries.rs @@ -89,7 +89,7 @@ pub(super) fn mk_eval_cx<'mir, 'tcx>( InterpCx::new( tcx.at(span), param_env, - CompileTimeInterpreter::new(), + CompileTimeInterpreter::new(*tcx.sess.const_eval_limit.get()), MemoryExtra { can_access_statics }, ) } @@ -297,7 +297,7 @@ pub fn const_eval_raw_provider<'tcx>( let mut ecx = InterpCx::new( tcx.at(span), key.param_env, - CompileTimeInterpreter::new(), + CompileTimeInterpreter::new(*tcx.sess.const_eval_limit.get()), MemoryExtra { can_access_statics: is_static }, ); diff --git a/src/librustc_mir/const_eval/machine.rs b/src/librustc_mir/const_eval/machine.rs index 25727b75faf14..ed8029834680c 100644 --- a/src/librustc_mir/const_eval/machine.rs +++ b/src/librustc_mir/const_eval/machine.rs @@ -3,6 +3,7 @@ use rustc::ty::layout::HasTyCtxt; use rustc::ty::{self, Ty}; use std::borrow::{Borrow, Cow}; use std::collections::hash_map::Entry; +use std::convert::TryFrom; use std::hash::Hash; use rustc_data_structures::fx::FxHashMap; @@ -85,9 +86,6 @@ impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> { } } -/// Number of steps until the detector even starts doing anything. -/// Also, a warning is shown to the user when this number is reached. -const STEPS_UNTIL_DETECTOR_ENABLED: isize = 1_000_000; /// The number of steps between loop detector snapshots. /// Should be a power of two for performance reasons. const DETECTOR_SNAPSHOT_PERIOD: isize = 256; @@ -100,6 +98,8 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> { /// detector period. pub(super) steps_since_detector_enabled: isize, + pub(super) is_detector_enabled: bool, + /// Extra state to detect loops. pub(super) loop_detector: snapshot::InfiniteLoopDetector<'mir, 'tcx>, } @@ -111,10 +111,14 @@ pub struct MemoryExtra { } impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> { - pub(super) fn new() -> Self { + pub(super) fn new(const_eval_limit: usize) -> Self { + let steps_until_detector_enabled = + isize::try_from(const_eval_limit).unwrap_or(std::isize::MAX); + CompileTimeInterpreter { loop_detector: Default::default(), - steps_since_detector_enabled: -STEPS_UNTIL_DETECTOR_ENABLED, + steps_since_detector_enabled: -steps_until_detector_enabled, + is_detector_enabled: const_eval_limit != 0, } } } @@ -343,6 +347,10 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, } fn before_terminator(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> { + if !ecx.machine.is_detector_enabled { + return Ok(()); + } + { let steps = &mut ecx.machine.steps_since_detector_enabled; diff --git a/src/librustc_session/session.rs b/src/librustc_session/session.rs index 2fb7977dce9ee..173b120e1f6d7 100644 --- a/src/librustc_session/session.rs +++ b/src/librustc_session/session.rs @@ -88,6 +88,9 @@ pub struct Session { /// The maximum length of types during monomorphization. pub type_length_limit: Once, + /// The maximum blocks a const expression can evaluate. + pub const_eval_limit: Once, + /// Map from imported macro spans (which consist of /// the localized span for the macro body) to the /// macro name and definition span in the source crate. @@ -1053,6 +1056,7 @@ fn build_session_( features: Once::new(), recursion_limit: Once::new(), type_length_limit: Once::new(), + const_eval_limit: Once::new(), imported_macro_spans: OneThread::new(RefCell::new(FxHashMap::default())), incr_comp_session: OneThread::new(RefCell::new(IncrCompSession::NotInitialized)), cgu_reuse_tracker, diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs index d6232f32f4c1b..c39f9f360c027 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -208,6 +208,7 @@ symbols! { console, const_compare_raw_pointers, const_constructor, + const_eval_limit, const_extern_fn, const_fn, const_fn_union, diff --git a/src/test/ui/consts/const_limit/const_eval_limit_not_reached.rs b/src/test/ui/consts/const_limit/const_eval_limit_not_reached.rs new file mode 100644 index 0000000000000..4ed908312fb6d --- /dev/null +++ b/src/test/ui/consts/const_limit/const_eval_limit_not_reached.rs @@ -0,0 +1,15 @@ +// check-pass +#![feature(const_eval_limit)] +#![const_eval_limit="1000"] + +const CONSTANT: usize = limit(); + +fn main() { + assert_eq!(CONSTANT, 1764); +} + +const fn limit() -> usize { + let x = 42; + + x * 42 +} diff --git a/src/test/ui/consts/const_limit/const_eval_limit_overflow.rs b/src/test/ui/consts/const_limit/const_eval_limit_overflow.rs new file mode 100644 index 0000000000000..1c49593cd53fa --- /dev/null +++ b/src/test/ui/consts/const_limit/const_eval_limit_overflow.rs @@ -0,0 +1,15 @@ +#![feature(const_eval_limit)] +#![const_eval_limit="18_446_744_073_709_551_615"] +//~^ ERROR `limit` must be a non-negative integer + +const CONSTANT: usize = limit(); + +fn main() { + assert_eq!(CONSTANT, 1764); +} + +const fn limit() -> usize { + let x = 42; + + x * 42 +} diff --git a/src/test/ui/consts/const_limit/const_eval_limit_overflow.stderr b/src/test/ui/consts/const_limit/const_eval_limit_overflow.stderr new file mode 100644 index 0000000000000..7f5d5e6cd4c5a --- /dev/null +++ b/src/test/ui/consts/const_limit/const_eval_limit_overflow.stderr @@ -0,0 +1,10 @@ +error: `limit` must be a non-negative integer + --> $DIR/const_eval_limit_overflow.rs:2:1 + | +LL | #![const_eval_limit="18_446_744_073_709_551_615"] + | ^^^^^^^^^^^^^^^^^^^^----------------------------^ + | | + | not a valid integer + +error: aborting due to previous error + diff --git a/src/test/ui/consts/const_limit/const_eval_limit_reached.rs b/src/test/ui/consts/const_limit/const_eval_limit_reached.rs new file mode 100644 index 0000000000000..d962398d4136e --- /dev/null +++ b/src/test/ui/consts/const_limit/const_eval_limit_reached.rs @@ -0,0 +1,21 @@ +// ignore-tidy-linelength +// only-x86_64 +// check-pass +// NOTE: We always compile this test with -Copt-level=0 because higher opt-levels +// optimize away the const function +// compile-flags:-Copt-level=0 +#![feature(const_eval_limit)] +#![const_eval_limit="2"] + +const CONSTANT: usize = limit(); +//~^ WARNING Constant evaluating a complex constant, this might take some time + +fn main() { + assert_eq!(CONSTANT, 1764); +} + +const fn limit() -> usize { //~ WARNING Constant evaluating a complex constant, this might take some time + let x = 42; + + x * 42 +} diff --git a/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr b/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr new file mode 100644 index 0000000000000..e0871ff718561 --- /dev/null +++ b/src/test/ui/consts/const_limit/const_eval_limit_reached.stderr @@ -0,0 +1,16 @@ +warning: Constant evaluating a complex constant, this might take some time + --> $DIR/const_eval_limit_reached.rs:17:1 + | +LL | / const fn limit() -> usize { +LL | | let x = 42; +LL | | +LL | | x * 42 +LL | | } + | |_^ + +warning: Constant evaluating a complex constant, this might take some time + --> $DIR/const_eval_limit_reached.rs:10:1 + | +LL | const CONSTANT: usize = limit(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + diff --git a/src/test/ui/consts/const_limit/feature-gate-const_eval_limit.rs b/src/test/ui/consts/const_limit/feature-gate-const_eval_limit.rs new file mode 100644 index 0000000000000..61119d7511d49 --- /dev/null +++ b/src/test/ui/consts/const_limit/feature-gate-const_eval_limit.rs @@ -0,0 +1,14 @@ +#![const_eval_limit="42"] +//~^ ERROR the `#[const_eval_limit]` attribute is an experimental feature [E0658] + +const CONSTANT: usize = limit(); + +fn main() { + assert_eq!(CONSTANT, 1764); +} + +const fn limit() -> usize { + let x = 42; + + x * 42 +} diff --git a/src/test/ui/consts/const_limit/feature-gate-const_eval_limit.stderr b/src/test/ui/consts/const_limit/feature-gate-const_eval_limit.stderr new file mode 100644 index 0000000000000..5bd29c7dfd22b --- /dev/null +++ b/src/test/ui/consts/const_limit/feature-gate-const_eval_limit.stderr @@ -0,0 +1,12 @@ +error[E0658]: the `#[const_eval_limit]` attribute is an experimental feature + --> $DIR/feature-gate-const_eval_limit.rs:1:1 + | +LL | #![const_eval_limit="42"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #67217 for more information + = help: add `#![feature(const_eval_limit)]` to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/recursion_limit/empty.rs b/src/test/ui/recursion_limit/empty.rs index 2a064f3e11599..31ff9c1e3a72e 100644 --- a/src/test/ui/recursion_limit/empty.rs +++ b/src/test/ui/recursion_limit/empty.rs @@ -1,6 +1,6 @@ // Test the parse error for an empty recursion_limit -#![recursion_limit = ""] //~ ERROR `recursion_limit` must be a non-negative integer - //~| `recursion_limit` must be a non-negative integer +#![recursion_limit = ""] //~ ERROR `limit` must be a non-negative integer + //~| `limit` must be a non-negative integer fn main() {} diff --git a/src/test/ui/recursion_limit/empty.stderr b/src/test/ui/recursion_limit/empty.stderr index 690c33a746307..bcd1d27e59b56 100644 --- a/src/test/ui/recursion_limit/empty.stderr +++ b/src/test/ui/recursion_limit/empty.stderr @@ -1,10 +1,10 @@ -error: `recursion_limit` must be a non-negative integer +error: `limit` must be a non-negative integer --> $DIR/empty.rs:3:1 | LL | #![recursion_limit = ""] | ^^^^^^^^^^^^^^^^^^^^^--^ | | - | `recursion_limit` must be a non-negative integer + | `limit` must be a non-negative integer error: aborting due to previous error diff --git a/src/test/ui/recursion_limit/invalid_digit.rs b/src/test/ui/recursion_limit/invalid_digit.rs index 903d804047696..759d69d0af20d 100644 --- a/src/test/ui/recursion_limit/invalid_digit.rs +++ b/src/test/ui/recursion_limit/invalid_digit.rs @@ -1,6 +1,6 @@ // Test the parse error for an invalid digit in recursion_limit -#![recursion_limit = "-100"] //~ ERROR `recursion_limit` must be a non-negative integer +#![recursion_limit = "-100"] //~ ERROR `limit` must be a non-negative integer //~| not a valid integer fn main() {} diff --git a/src/test/ui/recursion_limit/invalid_digit.stderr b/src/test/ui/recursion_limit/invalid_digit.stderr index 1dcfea547c0bd..e6fd6b72a0900 100644 --- a/src/test/ui/recursion_limit/invalid_digit.stderr +++ b/src/test/ui/recursion_limit/invalid_digit.stderr @@ -1,4 +1,4 @@ -error: `recursion_limit` must be a non-negative integer +error: `limit` must be a non-negative integer --> $DIR/invalid_digit.rs:3:1 | LL | #![recursion_limit = "-100"] diff --git a/src/test/ui/recursion_limit/overflow.rs b/src/test/ui/recursion_limit/overflow.rs index 6487b1350aa98..8eee2792b2383 100644 --- a/src/test/ui/recursion_limit/overflow.rs +++ b/src/test/ui/recursion_limit/overflow.rs @@ -1,7 +1,7 @@ // Test the parse error for an overflowing recursion_limit #![recursion_limit = "999999999999999999999999"] -//~^ ERROR `recursion_limit` must be a non-negative integer -//~| `recursion_limit` is too large +//~^ ERROR `limit` must be a non-negative integer +//~| `limit` is too large fn main() {} diff --git a/src/test/ui/recursion_limit/overflow.stderr b/src/test/ui/recursion_limit/overflow.stderr index c3fc11989dcec..f6ed76c1ebc0e 100644 --- a/src/test/ui/recursion_limit/overflow.stderr +++ b/src/test/ui/recursion_limit/overflow.stderr @@ -1,10 +1,10 @@ -error: `recursion_limit` must be a non-negative integer +error: `limit` must be a non-negative integer --> $DIR/overflow.rs:3:1 | LL | #![recursion_limit = "999999999999999999999999"] | ^^^^^^^^^^^^^^^^^^^^^--------------------------^ | | - | `recursion_limit` is too large + | `limit` is too large error: aborting due to previous error diff --git a/src/test/ui/recursion_limit/zero.rs b/src/test/ui/recursion_limit/zero.rs index f7199944e0063..eb95d7babc6b2 100644 --- a/src/test/ui/recursion_limit/zero.rs +++ b/src/test/ui/recursion_limit/zero.rs @@ -1,4 +1,4 @@ -// Test that a `recursion_limit` of 0 is valid +// Test that a `limit` of 0 is valid #![recursion_limit = "0"]