Skip to content

Commit

Permalink
Re-do recursive const stability checks
Browse files Browse the repository at this point in the history
Fundamentally, we have *three* disjoint categories of functions:
1. const-stable functions
2. private/unstable functions that are meant to be callable from const-stable functions
3. functions that can make use of unstable const features

This PR implements the following system:
- `#[rustc_const_stable]` puts functions in the first category. It may only be applied to `#[stable]` functions.
- `#[rustc_const_unstable]` by default puts functions in the third category. The new attribute `#[rustc_const_stable_indirect]` can be added to such a function to move it into the second category.
- `const fn` without a const stability marker are in the second category if they are still unstable. They automatically inherit the feature gate for regular calls, it can now also be used for const-calls.

Also, several holes in recursive const stability checking are being closed.
There's still one potential hole that is hard to avoid, which is when MIR
building automatically inserts calls to a particular function in stable
functions -- which happens in the panic machinery. Those need to *not* be
`rustc_const_unstable` (or manually get a `rustc_const_stable_indirect`) to be
sure they follow recursive const stability. But that's a fairly rare and special
case so IMO it's fine.

The net effect of this is that a `#[unstable]` or unmarked function can be
constified simply by marking it as `const fn`, and it will then be
const-callable from stable `const fn` and subject to recursive const stability
requirements. If it is publicly reachable (which implies it cannot be unmarked),
it will be const-unstable under the same feature gate. Only if the function ever
becomes `#[stable]` does it need a `#[rustc_const_unstable]` or
`#[rustc_const_stable]` marker to decide if this should also imply
const-stability.

Adding `#[rustc_const_unstable]` is only needed for (a) functions that need to
use unstable const lang features (including intrinsics), or (b) `#[stable]`
functions that are not yet intended to be const-stable. Adding
`#[rustc_const_stable]` is only needed for functions that are actually meant to
be directly callable from stable const code. `#[rustc_const_stable_indirect]` is
used to mark intrinsics as const-callable and for `#[rustc_const_unstable]`
functions that are actually called from other, exposed-on-stable `const fn`. No
other attributes are required.
  • Loading branch information
RalfJung committed Oct 25, 2024
1 parent 45089ec commit a0215d8
Show file tree
Hide file tree
Showing 102 changed files with 1,511 additions and 654 deletions.
3 changes: 3 additions & 0 deletions compiler/rustc_attr/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ attr_non_ident_feature =
attr_rustc_allowed_unstable_pairing =
`rustc_allowed_through_unstable_modules` attribute must be paired with a `stable` attribute
attr_rustc_const_stable_indirect_pairing =
`const_stable_indirect` attribute does not make sense on `rustc_const_stable` function, its behavior is already implied
attr_rustc_promotable_pairing =
`rustc_promotable` attribute must be paired with either a `rustc_const_unstable` or a `rustc_const_stable` attribute
Expand Down
78 changes: 71 additions & 7 deletions compiler/rustc_attr/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ use rustc_session::lint::BuiltinLintDiag;
use rustc_session::lint::builtin::UNEXPECTED_CFGS;
use rustc_session::parse::feature_err;
use rustc_session::{RustcVersion, Session};
use rustc_span::Span;
use rustc_span::hygiene::Transparency;
use rustc_span::symbol::{Symbol, kw, sym};
use rustc_span::{DUMMY_SP, Span};

use crate::fluent_generated;
use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause};
Expand Down Expand Up @@ -92,7 +92,11 @@ impl Stability {
#[derive(HashStable_Generic)]
pub struct ConstStability {
pub level: StabilityLevel,
pub feature: Symbol,
/// This can be `None` for functions that do not have an explicit const feature.
/// We still track them for recursive const stability checks.
pub feature: Option<Symbol>,
/// This is true iff the `const_stable_indirect` attribute is present.
pub const_stable_indirect: bool,
/// whether the function has a `#[rustc_promotable]` attribute
pub promotable: bool,
}
Expand Down Expand Up @@ -268,17 +272,23 @@ pub fn find_stability(

/// Collects stability info from `rustc_const_stable`/`rustc_const_unstable`/`rustc_promotable`
/// attributes in `attrs`. Returns `None` if no stability attributes are found.
///
/// `is_const_fn` indicates whether this is a function marked as `const`. It will always
/// be false for intrinsics in an `extern` block!
pub fn find_const_stability(
sess: &Session,
attrs: &[Attribute],
item_sp: Span,
is_const_fn: bool,
) -> Option<(ConstStability, Span)> {
let mut const_stab: Option<(ConstStability, Span)> = None;
let mut promotable = false;
let mut const_stable_indirect = None;

for attr in attrs {
match attr.name_or_empty() {
sym::rustc_promotable => promotable = true,
sym::rustc_const_stable_indirect => const_stable_indirect = Some(attr.span),
sym::rustc_const_unstable => {
if const_stab.is_some() {
sess.dcx()
Expand All @@ -287,8 +297,15 @@ pub fn find_const_stability(
}

if let Some((feature, level)) = parse_unstability(sess, attr) {
const_stab =
Some((ConstStability { level, feature, promotable: false }, attr.span));
const_stab = Some((
ConstStability {
level,
feature: Some(feature),
const_stable_indirect: false,
promotable: false,
},
attr.span,
));
}
}
sym::rustc_const_stable => {
Expand All @@ -298,15 +315,22 @@ pub fn find_const_stability(
break;
}
if let Some((feature, level)) = parse_stability(sess, attr) {
const_stab =
Some((ConstStability { level, feature, promotable: false }, attr.span));
const_stab = Some((
ConstStability {
level,
feature: Some(feature),
const_stable_indirect: false,
promotable: false,
},
attr.span,
));
}
}
_ => {}
}
}

// Merge the const-unstable info into the stability info
// Merge promotable and not_exposed_on_stable into stability info
if promotable {
match &mut const_stab {
Some((stab, _)) => stab.promotable = promotable,
Expand All @@ -317,6 +341,46 @@ pub fn find_const_stability(
}
}
}
if const_stable_indirect.is_some() {
match &mut const_stab {
Some((stab, _)) => {
if stab.is_const_unstable() {
stab.const_stable_indirect = true;
} else {
_ = sess.dcx().emit_err(session_diagnostics::RustcConstStableIndirectPairing {
span: item_sp,
})
}
}
_ => {
// We ignore the `#[rustc_const_stable_indirect]` here, it should be picked up by
// the `default_const_unstable` logic.
}
}
}
// Make sure if `const_stable_indirect` is present, that is recorded. Also make sure all `const
// fn` get *some* marker, since we are a staged_api crate and therefore will do recursive const
// stability checks for them. We need to do this because the default for whether an unmarked
// function enforces recursive stability differs between staged-api crates and force-unmarked
// crates: in force-unmarked crates, only functions *explicitly* marked `const_stable_indirect`
// enforce recursive stability. Therefore when `lookup_const_stability` is `None`, we have to
// assume the function does not have recursive stability. All functions that *do* have recursive
// stability must explicitly record this, and so that's what we do for all `const fn` in a
// staged_api crate.
if (is_const_fn || const_stable_indirect.is_some()) && const_stab.is_none() {
let c = ConstStability {
feature: None,
const_stable_indirect: const_stable_indirect.is_some(),
promotable: false,
level: StabilityLevel::Unstable {
reason: UnstableReason::Default,
issue: None,
is_soft: false,
implied_by: None,
},
};
const_stab = Some((c, const_stable_indirect.unwrap_or(DUMMY_SP)));
}

const_stab
}
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_attr/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,13 @@ pub(crate) struct RustcPromotablePairing {
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(attr_rustc_const_stable_indirect_pairing)]
pub(crate) struct RustcConstStableIndirectPairing {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(attr_rustc_allowed_unstable_pairing, code = E0789)]
pub(crate) struct RustcAllowedUnstablePairing {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs
index d9de37e..8293fce 100644
--- a/library/core/src/sync/atomic.rs
+++ b/library/core/src/sync/atomic.rs
@@ -2996,42 +2996,6 @@ atomic_int! {
@@ -2996,44 +2996,6 @@ atomic_int! {
8,
u64 AtomicU64
}
Expand All @@ -52,7 +52,8 @@ index d9de37e..8293fce 100644
- unstable(feature = "integer_atomics", issue = "99069"),
- unstable(feature = "integer_atomics", issue = "99069"),
- unstable(feature = "integer_atomics", issue = "99069"),
- rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
- rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
- rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
- cfg_attr(not(test), rustc_diagnostic_item = "AtomicI128"),
- "i128",
- "#![feature(integer_atomics)]\n\n",
Expand All @@ -70,7 +71,8 @@ index d9de37e..8293fce 100644
- unstable(feature = "integer_atomics", issue = "99069"),
- unstable(feature = "integer_atomics", issue = "99069"),
- unstable(feature = "integer_atomics", issue = "99069"),
- rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
- rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
- rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
- cfg_attr(not(test), rustc_diagnostic_item = "AtomicU128"),
- "u128",
- "#![feature(integer_atomics)]\n\n",
Expand Down
27 changes: 20 additions & 7 deletions compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ const_eval_const_context = {$kind ->
*[other] {""}
}
const_eval_const_stable = const-stable functions can only call other const-stable functions
const_eval_copy_nonoverlapping_overlapping =
`copy_nonoverlapping` called on overlapping ranges
Expand Down Expand Up @@ -259,6 +257,9 @@ const_eval_non_const_fn_call =
const_eval_non_const_impl =
impl defined here, but it is not `const`
const_eval_non_const_intrinsic =
cannot call non-const intrinsic `{$name}` in {const_eval_const_context}s
const_eval_not_enough_caller_args =
calling a function with fewer arguments than it requires
Expand Down Expand Up @@ -397,17 +398,29 @@ const_eval_uninhabited_enum_variant_read =
read discriminant of an uninhabited enum variant
const_eval_uninhabited_enum_variant_written =
writing discriminant of an uninhabited enum variant
const_eval_unmarked_const_fn_exposed = `{$def_path}` cannot be (indirectly) exposed to stable
.help = either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
const_eval_unmarked_intrinsic_exposed = intrinsic `{$def_path}` cannot be (indirectly) exposed to stable
.help = mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_const_stable_indirect]` (but this requires team approval)
const_eval_unreachable = entering unreachable code
const_eval_unreachable_unwind =
unwinding past a stack frame that does not allow unwinding
const_eval_unsized_local = unsized locals are not supported
const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn
const_eval_unstable_in_stable =
const-stable function cannot use `#[feature({$gate})]`
.unstable_sugg = if the function is not (yet) meant to be stable, make this function unstably const
.bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (but requires team approval)
const_eval_unstable_in_stable_exposed =
const function that might be (indirectly) exposed to stable cannot use `#[feature({$gate})]`
.is_function_call = mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
.unstable_sugg = if the {$is_function_call2 ->
[true] caller
*[false] function
} is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
.bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
const_eval_unstable_intrinsic = `{$name}` is not yet stable as a const intrinsic
.help = add `#![feature({$feature})]` to the crate attributes to enable
const_eval_unterminated_c_string =
reading a null-terminated string starting at {$pointer} with no null found before end of allocation
Expand Down
Loading

0 comments on commit a0215d8

Please sign in to comment.