Auto merge of #152484 - matthiaskrgr:rollup-h4u26eb, r=matthiaskrgr

Rollup of 9 pull requests

Successful merges:

 - rust-lang/rust#152419 (Move more query system code)
 - rust-lang/rust#152431 (Restrict the set of things that const stability can be applied to)
 - rust-lang/rust#152436 (Reenable a GCI+mGCA+GCPT test case)
 - rust-lang/rust#152021 (Bump tvOS, visionOS and watchOS Aarch64 targets to tier 2)
 - rust-lang/rust#152146 (mGCA: Add associated const type check)
 - rust-lang/rust#152372 (style: remove unneeded trailing commas)
 - rust-lang/rust#152383 (BikeshedGuaranteedNoDrop trait: add comments indicating that it can be observed on stable)
 - rust-lang/rust#152397 (Update books)
 - rust-lang/rust#152441 (Fix typos and grammar in top-level and src/doc documentation)
This commit is contained in:
bors 2026-02-11 22:07:19 +00:00
commit 5fdff787e6
61 changed files with 922 additions and 744 deletions

View file

@ -10,7 +10,7 @@ the Zulip stream is the best place to *ask* for help.
Documentation for contributing to the compiler or tooling is located in the [Guide to Rustc
Development][rustc-dev-guide], commonly known as the [rustc-dev-guide]. Documentation for the
standard library in the [Standard library developers Guide][std-dev-guide], commonly known as the [std-dev-guide].
standard library is in the [Standard library developers Guide][std-dev-guide], commonly known as the [std-dev-guide].
## Making changes to subtrees and submodules

View file

@ -4492,6 +4492,7 @@ name = "rustc_query_impl"
version = "0.0.0"
dependencies = [
"measureme",
"rustc_abi",
"rustc_data_structures",
"rustc_errors",
"rustc_hashes",
@ -4501,7 +4502,9 @@ dependencies = [
"rustc_middle",
"rustc_query_system",
"rustc_serialize",
"rustc_session",
"rustc_span",
"rustc_thread_pool",
"tracing",
]

View file

@ -233,7 +233,7 @@ itself back on after some time).
### MSVC
MSVC builds of Rust additionally requires an installation of:
MSVC builds of Rust additionally require an installation of:
- Visual Studio 2022 (or later) build tools so `rustc` can use its linker. Older
Visual Studio versions such as 2019 *may* work but aren't actively tested.

View file

@ -1546,7 +1546,7 @@ Compatibility Notes
- [Check well-formedness of the source type's signature in fn pointer casts.](https://github.com/rust-lang/rust/pull/129021) This partly closes a soundness hole that comes when casting a function item to function pointer
- [Use equality instead of subtyping when resolving type dependent paths.](https://github.com/rust-lang/rust/pull/129073)
- Linking on macOS now correctly includes Rust's default deployment target. Due to a linker bug, you might have to pass `MACOSX_DEPLOYMENT_TARGET` or fix your `#[link]` attributes to point to the correct frameworks. See <https://github.com/rust-lang/rust/pull/129369>.
- [Rust will now correctly raise an error for `repr(Rust)` written on non-`struct`/`enum`/`union` items, since it previous did not have any effect.](https://github.com/rust-lang/rust/pull/129422)
- [Rust will now correctly raise an error for `repr(Rust)` written on non-`struct`/`enum`/`union` items, since it previously did not have any effect.](https://github.com/rust-lang/rust/pull/129422)
- The future incompatibility lint `deprecated_cfg_attr_crate_type_name` [has been made into a hard error](https://github.com/rust-lang/rust/pull/129670). It was used to deny usage of `#![crate_type]` and `#![crate_name]` attributes in `#![cfg_attr]`, which required a hack in the compiler to be able to change the used crate type and crate name after cfg expansion.
Users can use `--crate-type` instead of `#![cfg_attr(..., crate_type = "...")]` and `--crate-name` instead of `#![cfg_attr(..., crate_name = "...")]` when running `rustc`/`cargo rustc` on the command line.
Use of those two attributes outside of `#![cfg_attr]` continue to be fully supported.
@ -1722,7 +1722,7 @@ Cargo
Compatibility Notes
-------------------
- We now [disallow setting some built-in cfgs via the command-line](https://github.com/rust-lang/rust/pull/126158) with the newly added [`explicit_builtin_cfgs_in_flags`](https://doc.rust-lang.org/rustc/lints/listing/deny-by-default.html#explicit-builtin-cfgs-in-flags) lint in order to prevent incoherent state, eg. `windows` cfg active but target is Linux based. The appropriate [`rustc` flag](https://doc.rust-lang.org/rustc/command-line-arguments.html) should be used instead.
- The standard library has a new implementation of `binary_search` which is significantly improves performance ([#128254](https://github.com/rust-lang/rust/pull/128254)). However when a sorted slice has multiple values which compare equal, the new implementation may select a different value among the equal ones than the old implementation.
- The standard library has a new implementation of `binary_search` which significantly improves performance ([#128254](https://github.com/rust-lang/rust/pull/128254)). However when a sorted slice has multiple values which compare equal, the new implementation may select a different value among the equal ones than the old implementation.
- [illumos/Solaris now sets `MSG_NOSIGNAL` when writing to sockets](https://github.com/rust-lang/rust/pull/128259). This avoids killing the process with SIGPIPE when writing to a closed socket, which matches the existing behavior on other UNIX targets.
- [Removes a problematic hack that always passed the --whole-archive linker flag for tests, which may cause linker errors for code accidentally relying on it.](https://github.com/rust-lang/rust/pull/128400)
- The WebAssembly target features `multivalue` and `reference-types` are now
@ -1872,7 +1872,7 @@ These changes do not affect any public interfaces of Rust, but they represent
significant improvements to the performance or internals of rustc and related
tools.
- [Add a Rust-for Linux `auto` CI job to check kernel builds.](https://github.com/rust-lang/rust/pull/125209/)
- [Add a Rust-for-Linux `auto` CI job to check kernel builds.](https://github.com/rust-lang/rust/pull/125209/)
Version 1.80.1 (2024-08-08)
===========================
@ -4510,7 +4510,7 @@ Compatibility Notes
saturating to `0` instead][89926]. In the real world the panic happened mostly
on platforms with buggy monotonic clock implementations rather than catching
programming errors like reversing the start and end times. Such programming
errors will now results in `0` rather than a panic.
errors will now result in `0` rather than a panic.
- In a future release we're planning to increase the baseline requirements for
the Linux kernel to version 3.2, and for glibc to version 2.17. We'd love
your feedback in [PR #95026][95026].

View file

@ -244,7 +244,20 @@ impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
this.promotable = true;
}),
];
const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS;
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
Allow(Target::Fn),
Allow(Target::Method(MethodKind::Inherent)),
Allow(Target::Method(MethodKind::TraitImpl)),
Allow(Target::Method(MethodKind::Trait { body: true })),
Allow(Target::Impl { of_trait: false }),
Allow(Target::Impl { of_trait: true }),
Allow(Target::Use), // FIXME I don't think this does anything?
Allow(Target::Const),
Allow(Target::AssocConst),
Allow(Target::Trait),
Allow(Target::Static),
Allow(Target::Crate),
]);
fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
if self.promotable {

View file

@ -962,15 +962,6 @@ impl SyntaxExtension {
let stability = find_attr!(attrs, AttributeKind::Stability { stability, .. } => *stability);
// FIXME(jdonszelmann): make it impossible to miss the or_else in the typesystem
if let Some(sp) =
find_attr!(attrs, AttributeKind::RustcConstStability { span, .. } => *span)
{
sess.dcx().emit_err(errors::MacroConstStability {
span: sp,
head_span: sess.source_map().guess_head_span(span),
});
}
if let Some(sp) = find_attr!(attrs, AttributeKind::RustcBodyStability{ span, .. } => *span)
{
sess.dcx().emit_err(errors::MacroBodyStability {

View file

@ -80,16 +80,6 @@ pub(crate) struct ResolveRelativePath {
pub path: String,
}
#[derive(Diagnostic)]
#[diag("macros cannot have const stability attributes")]
pub(crate) struct MacroConstStability {
#[primary_span]
#[label("invalid const stability attribute")]
pub span: Span,
#[label("const stability attribute affects this macro")]
pub head_span: Span,
}
#[derive(Diagnostic)]
#[diag("macros cannot have body stability attributes")]
pub(crate) struct MacroBodyStability {

View file

@ -1569,11 +1569,40 @@ pub(super) fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, def_id:
let predicates = predicates.instantiate_identity(tcx);
let assoc_const_obligations: Vec<_> = predicates
.predicates
.iter()
.copied()
.zip(predicates.spans.iter().copied())
.filter_map(|(clause, sp)| {
let proj = clause.as_projection_clause()?;
let pred_binder = proj
.map_bound(|pred| {
pred.term.as_const().map(|ct| {
let assoc_const_ty = tcx
.type_of(pred.projection_term.def_id)
.instantiate(tcx, pred.projection_term.args);
ty::ClauseKind::ConstArgHasType(ct, assoc_const_ty)
})
})
.transpose();
pred_binder.map(|pred_binder| {
let cause = traits::ObligationCause::new(
sp,
wfcx.body_def_id,
ObligationCauseCode::WhereClause(def_id.to_def_id(), sp),
);
Obligation::new(tcx, cause, wfcx.param_env, pred_binder)
})
})
.collect();
assert_eq!(predicates.predicates.len(), predicates.spans.len());
let wf_obligations = predicates.into_iter().flat_map(|(p, sp)| {
traits::wf::clause_obligations(infcx, wfcx.param_env, wfcx.body_def_id, p, sp)
});
let obligations: Vec<_> = wf_obligations.chain(default_obligations).collect();
let obligations: Vec<_> =
wf_obligations.chain(default_obligations).chain(assoc_const_obligations).collect();
wfcx.register_obligations(obligations);
}

View file

@ -16,8 +16,7 @@ use rustc_parse::lexer::StripTokens;
use rustc_parse::new_parser_from_source_str;
use rustc_parse::parser::Recovery;
use rustc_parse::parser::attr::AllowLeadingUnsafe;
use rustc_query_impl::QueryCtxt;
use rustc_query_system::query::print_query_stack;
use rustc_query_impl::{QueryCtxt, print_query_stack};
use rustc_session::config::{self, Cfg, CheckCfg, ExpectedValues, Input, OutFileName};
use rustc_session::parse::ParseSess;
use rustc_session::{CompilerIO, EarlyDiagCtxt, Session, lint};

View file

@ -184,8 +184,7 @@ pub(crate) fn run_in_thread_pool_with_globals<
use rustc_data_structures::defer;
use rustc_data_structures::sync::FromDyn;
use rustc_middle::ty::tls;
use rustc_query_impl::QueryCtxt;
use rustc_query_system::query::{QueryContext, break_query_cycles};
use rustc_query_impl::{QueryCtxt, break_query_cycles};
let thread_stack_size = init_stack_size(thread_builder_diag);

View file

@ -13,7 +13,6 @@ mod keys;
pub mod on_disk_cache;
#[macro_use]
pub mod plumbing;
pub mod values;
pub fn describe_as_module(def_id: impl Into<LocalDefId>, tcx: TyCtxt<'_>) -> String {
let def_id = def_id.into();

View file

@ -687,6 +687,9 @@ where
///
/// because these impls overlap, and I'd rather not build a coherence hack for
/// this harmless overlap.
///
/// This trait is indirectly exposed on stable, so do *not* extend the set of types that
/// implement the trait without FCP!
fn consider_builtin_bikeshed_guaranteed_no_drop_candidate(
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,

View file

@ -6,6 +6,7 @@ edition = "2024"
[dependencies]
# tidy-alphabetical-start
measureme = "12.0.1"
rustc_abi = { path = "../rustc_abi" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }
rustc_hashes = { path = "../rustc_hashes" }
@ -15,6 +16,8 @@ rustc_macros = { path = "../rustc_macros" }
rustc_middle = { path = "../rustc_middle" }
rustc_query_system = { path = "../rustc_query_system" }
rustc_serialize = { path = "../rustc_serialize" }
rustc_session = { path = "../rustc_session" }
rustc_span = { path = "../rustc_span" }
rustc_thread_pool = { path = "../rustc_thread_pool" }
tracing = "0.1"
# tidy-alphabetical-end

View file

@ -1,3 +1,4 @@
use rustc_errors::codes::*;
use rustc_hir::limit::Limit;
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_span::{Span, Symbol};
@ -22,3 +23,59 @@ pub(crate) struct QueryOverflowNote {
pub desc: String,
pub depth: usize,
}
#[derive(Subdiagnostic)]
#[note("...which requires {$desc}...")]
pub(crate) struct CycleStack {
#[primary_span]
pub span: Span,
pub desc: String,
}
#[derive(Subdiagnostic)]
pub(crate) enum StackCount {
#[note("...which immediately requires {$stack_bottom} again")]
Single,
#[note("...which again requires {$stack_bottom}, completing the cycle")]
Multiple,
}
#[derive(Subdiagnostic)]
pub(crate) enum Alias {
#[note("type aliases cannot be recursive")]
#[help("consider using a struct, enum, or union instead to break the cycle")]
#[help(
"see <https://doc.rust-lang.org/reference/types.html#recursive-types> for more information"
)]
Ty,
#[note("trait aliases cannot be recursive")]
Trait,
}
#[derive(Subdiagnostic)]
#[note("cycle used when {$usage}")]
pub(crate) struct CycleUsage {
#[primary_span]
pub span: Span,
pub usage: String,
}
#[derive(Diagnostic)]
#[diag("cycle detected when {$stack_bottom}", code = E0391)]
pub(crate) struct Cycle {
#[primary_span]
pub span: Span,
pub stack_bottom: String,
#[subdiagnostic]
pub cycle_stack: Vec<CycleStack>,
#[subdiagnostic]
pub stack_count: StackCount,
#[subdiagnostic]
pub alias: Option<Alias>,
#[subdiagnostic]
pub cycle_usage: Option<CycleUsage>,
#[note(
"see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information"
)]
pub note_span: (),
}

View file

@ -9,13 +9,13 @@ use rustc_middle::dep_graph::DepsType;
use rustc_middle::ty::TyCtxt;
use rustc_query_system::dep_graph::{DepGraphData, DepNodeKey, HasDepContext};
use rustc_query_system::query::{
ActiveKeyStatus, CycleError, CycleErrorHandling, QueryCache, QueryContext, QueryJob,
QueryJobId, QueryJobInfo, QueryLatch, QueryMap, QueryMode, QueryStackDeferred, QueryStackFrame,
QueryState, incremental_verify_ich, report_cycle,
ActiveKeyStatus, CycleError, CycleErrorHandling, QueryCache, QueryJob, QueryJobId, QueryLatch,
QueryMode, QueryStackDeferred, QueryStackFrame, QueryState, incremental_verify_ich,
};
use rustc_span::{DUMMY_SP, Span};
use crate::dep_graph::{DepContext, DepNode, DepNodeIndex};
use crate::job::{QueryJobInfo, QueryMap, find_cycle_in_stack, report_cycle};
use crate::{QueryCtxt, QueryFlags, SemiDynamicQueryDispatcher};
#[inline]
@ -218,7 +218,7 @@ fn cycle_error<'tcx, C: QueryCache, const FLAGS: QueryFlags>(
.ok()
.expect("failed to collect active queries");
let error = try_execute.find_cycle_in_stack(query_map, &qcx.current_query_job(), span);
let error = find_cycle_in_stack(try_execute, query_map, &qcx.current_query_job(), span);
(mk_cycle(query, qcx, error.lift()), None)
}

View file

@ -0,0 +1,500 @@
use std::io::Write;
use std::iter;
use std::sync::Arc;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{Diag, DiagCtxtHandle};
use rustc_hir::def::DefKind;
use rustc_query_system::query::{
CycleError, QueryInfo, QueryJob, QueryJobId, QueryLatch, QueryStackDeferred, QueryStackFrame,
QueryWaiter,
};
use rustc_session::Session;
use rustc_span::{DUMMY_SP, Span};
use crate::QueryCtxt;
use crate::dep_graph::DepContext;
/// Map from query job IDs to job information collected by
/// `collect_active_jobs_from_all_queries`.
pub type QueryMap<'tcx> = FxHashMap<QueryJobId, QueryJobInfo<'tcx>>;
fn query_job_id_frame<'a, 'tcx>(
id: QueryJobId,
map: &'a QueryMap<'tcx>,
) -> QueryStackFrame<QueryStackDeferred<'tcx>> {
map.get(&id).unwrap().frame.clone()
}
fn query_job_id_span<'a, 'tcx>(id: QueryJobId, map: &'a QueryMap<'tcx>) -> Span {
map.get(&id).unwrap().job.span
}
fn query_job_id_parent<'a, 'tcx>(id: QueryJobId, map: &'a QueryMap<'tcx>) -> Option<QueryJobId> {
map.get(&id).unwrap().job.parent
}
fn query_job_id_latch<'a, 'tcx>(
id: QueryJobId,
map: &'a QueryMap<'tcx>,
) -> Option<&'a QueryLatch<'tcx>> {
map.get(&id).unwrap().job.latch.as_ref()
}
#[derive(Clone, Debug)]
pub struct QueryJobInfo<'tcx> {
pub frame: QueryStackFrame<QueryStackDeferred<'tcx>>,
pub job: QueryJob<'tcx>,
}
pub(crate) fn find_cycle_in_stack<'tcx>(
id: QueryJobId,
query_map: QueryMap<'tcx>,
current_job: &Option<QueryJobId>,
span: Span,
) -> CycleError<QueryStackDeferred<'tcx>> {
// Find the waitee amongst `current_job` parents
let mut cycle = Vec::new();
let mut current_job = Option::clone(current_job);
while let Some(job) = current_job {
let info = query_map.get(&job).unwrap();
cycle.push(QueryInfo { span: info.job.span, frame: info.frame.clone() });
if job == id {
cycle.reverse();
// This is the end of the cycle
// The span entry we included was for the usage
// of the cycle itself, and not part of the cycle
// Replace it with the span which caused the cycle to form
cycle[0].span = span;
// Find out why the cycle itself was used
let usage = info
.job
.parent
.as_ref()
.map(|parent| (info.job.span, query_job_id_frame(*parent, &query_map)));
return CycleError { usage, cycle };
}
current_job = info.job.parent;
}
panic!("did not find a cycle")
}
#[cold]
#[inline(never)]
pub(crate) fn find_dep_kind_root<'tcx>(
id: QueryJobId,
query_map: QueryMap<'tcx>,
) -> (QueryJobInfo<'tcx>, usize) {
let mut depth = 1;
let info = query_map.get(&id).unwrap();
let dep_kind = info.frame.dep_kind;
let mut current_id = info.job.parent;
let mut last_layout = (info.clone(), depth);
while let Some(id) = current_id {
let info = query_map.get(&id).unwrap();
if info.frame.dep_kind == dep_kind {
depth += 1;
last_layout = (info.clone(), depth);
}
current_id = info.job.parent;
}
last_layout
}
/// A resumable waiter of a query. The usize is the index into waiters in the query's latch
type Waiter = (QueryJobId, usize);
/// Visits all the non-resumable and resumable waiters of a query.
/// Only waiters in a query are visited.
/// `visit` is called for every waiter and is passed a query waiting on `query_ref`
/// and a span indicating the reason the query waited on `query_ref`.
/// If `visit` returns Some, this function returns.
/// For visits of non-resumable waiters it returns the return value of `visit`.
/// For visits of resumable waiters it returns Some(Some(Waiter)) which has the
/// required information to resume the waiter.
/// If all `visit` calls returns None, this function also returns None.
fn visit_waiters<'tcx, F>(
query_map: &QueryMap<'tcx>,
query: QueryJobId,
mut visit: F,
) -> Option<Option<Waiter>>
where
F: FnMut(Span, QueryJobId) -> Option<Option<Waiter>>,
{
// Visit the parent query which is a non-resumable waiter since it's on the same stack
if let Some(parent) = query_job_id_parent(query, query_map)
&& let Some(cycle) = visit(query_job_id_span(query, query_map), parent)
{
return Some(cycle);
}
// Visit the explicit waiters which use condvars and are resumable
if let Some(latch) = query_job_id_latch(query, query_map) {
for (i, waiter) in latch.info.lock().waiters.iter().enumerate() {
if let Some(waiter_query) = waiter.query {
if visit(waiter.span, waiter_query).is_some() {
// Return a value which indicates that this waiter can be resumed
return Some(Some((query, i)));
}
}
}
}
None
}
/// Look for query cycles by doing a depth first search starting at `query`.
/// `span` is the reason for the `query` to execute. This is initially DUMMY_SP.
/// If a cycle is detected, this initial value is replaced with the span causing
/// the cycle.
fn cycle_check<'tcx>(
query_map: &QueryMap<'tcx>,
query: QueryJobId,
span: Span,
stack: &mut Vec<(Span, QueryJobId)>,
visited: &mut FxHashSet<QueryJobId>,
) -> Option<Option<Waiter>> {
if !visited.insert(query) {
return if let Some(p) = stack.iter().position(|q| q.1 == query) {
// We detected a query cycle, fix up the initial span and return Some
// Remove previous stack entries
stack.drain(0..p);
// Replace the span for the first query with the cycle cause
stack[0].0 = span;
Some(None)
} else {
None
};
}
// Query marked as visited is added it to the stack
stack.push((span, query));
// Visit all the waiters
let r = visit_waiters(query_map, query, |span, successor| {
cycle_check(query_map, successor, span, stack, visited)
});
// Remove the entry in our stack if we didn't find a cycle
if r.is_none() {
stack.pop();
}
r
}
/// Finds out if there's a path to the compiler root (aka. code which isn't in a query)
/// from `query` without going through any of the queries in `visited`.
/// This is achieved with a depth first search.
fn connected_to_root<'tcx>(
query_map: &QueryMap<'tcx>,
query: QueryJobId,
visited: &mut FxHashSet<QueryJobId>,
) -> bool {
// We already visited this or we're deliberately ignoring it
if !visited.insert(query) {
return false;
}
// This query is connected to the root (it has no query parent), return true
if query_job_id_parent(query, query_map).is_none() {
return true;
}
visit_waiters(query_map, query, |_, successor| {
connected_to_root(query_map, successor, visited).then_some(None)
})
.is_some()
}
// Deterministically pick an query from a list
fn pick_query<'a, 'tcx, T, F>(query_map: &QueryMap<'tcx>, queries: &'a [T], f: F) -> &'a T
where
F: Fn(&T) -> (Span, QueryJobId),
{
// Deterministically pick an entry point
// FIXME: Sort this instead
queries
.iter()
.min_by_key(|v| {
let (span, query) = f(v);
let hash = query_job_id_frame(query, query_map).hash;
// Prefer entry points which have valid spans for nicer error messages
// We add an integer to the tuple ensuring that entry points
// with valid spans are picked first
let span_cmp = if span == DUMMY_SP { 1 } else { 0 };
(span_cmp, hash)
})
.unwrap()
}
/// Looks for query cycles starting from the last query in `jobs`.
/// If a cycle is found, all queries in the cycle is removed from `jobs` and
/// the function return true.
/// If a cycle was not found, the starting query is removed from `jobs` and
/// the function returns false.
fn remove_cycle<'tcx>(
query_map: &QueryMap<'tcx>,
jobs: &mut Vec<QueryJobId>,
wakelist: &mut Vec<Arc<QueryWaiter<'tcx>>>,
) -> bool {
let mut visited = FxHashSet::default();
let mut stack = Vec::new();
// Look for a cycle starting with the last query in `jobs`
if let Some(waiter) =
cycle_check(query_map, jobs.pop().unwrap(), DUMMY_SP, &mut stack, &mut visited)
{
// The stack is a vector of pairs of spans and queries; reverse it so that
// the earlier entries require later entries
let (mut spans, queries): (Vec<_>, Vec<_>) = stack.into_iter().rev().unzip();
// Shift the spans so that queries are matched with the span for their waitee
spans.rotate_right(1);
// Zip them back together
let mut stack: Vec<_> = iter::zip(spans, queries).collect();
// Remove the queries in our cycle from the list of jobs to look at
for r in &stack {
if let Some(pos) = jobs.iter().position(|j| j == &r.1) {
jobs.remove(pos);
}
}
// Find the queries in the cycle which are
// connected to queries outside the cycle
let entry_points = stack
.iter()
.filter_map(|&(span, query)| {
if query_job_id_parent(query, query_map).is_none() {
// This query is connected to the root (it has no query parent)
Some((span, query, None))
} else {
let mut waiters = Vec::new();
// Find all the direct waiters who lead to the root
visit_waiters(query_map, query, |span, waiter| {
// Mark all the other queries in the cycle as already visited
let mut visited = FxHashSet::from_iter(stack.iter().map(|q| q.1));
if connected_to_root(query_map, waiter, &mut visited) {
waiters.push((span, waiter));
}
None
});
if waiters.is_empty() {
None
} else {
// Deterministically pick one of the waiters to show to the user
let waiter = *pick_query(query_map, &waiters, |s| *s);
Some((span, query, Some(waiter)))
}
}
})
.collect::<Vec<(Span, QueryJobId, Option<(Span, QueryJobId)>)>>();
// Deterministically pick an entry point
let (_, entry_point, usage) = pick_query(query_map, &entry_points, |e| (e.0, e.1));
// Shift the stack so that our entry point is first
let entry_point_pos = stack.iter().position(|(_, query)| query == entry_point);
if let Some(pos) = entry_point_pos {
stack.rotate_left(pos);
}
let usage =
usage.as_ref().map(|(span, query)| (*span, query_job_id_frame(*query, query_map)));
// Create the cycle error
let error = CycleError {
usage,
cycle: stack
.iter()
.map(|&(s, ref q)| QueryInfo { span: s, frame: query_job_id_frame(*q, query_map) })
.collect(),
};
// We unwrap `waiter` here since there must always be one
// edge which is resumable / waited using a query latch
let (waitee_query, waiter_idx) = waiter.unwrap();
// Extract the waiter we want to resume
let waiter =
query_job_id_latch(waitee_query, query_map).unwrap().extract_waiter(waiter_idx);
// Set the cycle error so it will be picked up when resumed
*waiter.cycle.lock() = Some(error);
// Put the waiter on the list of things to resume
wakelist.push(waiter);
true
} else {
false
}
}
/// Detects query cycles by using depth first search over all active query jobs.
/// If a query cycle is found it will break the cycle by finding an edge which
/// uses a query latch and then resuming that waiter.
/// There may be multiple cycles involved in a deadlock, so this searches
/// all active queries for cycles before finally resuming all the waiters at once.
pub fn break_query_cycles<'tcx>(query_map: QueryMap<'tcx>, registry: &rustc_thread_pool::Registry) {
let mut wakelist = Vec::new();
// It is OK per the comments:
// - https://github.com/rust-lang/rust/pull/131200#issuecomment-2798854932
// - https://github.com/rust-lang/rust/pull/131200#issuecomment-2798866392
#[allow(rustc::potential_query_instability)]
let mut jobs: Vec<QueryJobId> = query_map.keys().cloned().collect();
let mut found_cycle = false;
while jobs.len() > 0 {
if remove_cycle(&query_map, &mut jobs, &mut wakelist) {
found_cycle = true;
}
}
// Check that a cycle was found. It is possible for a deadlock to occur without
// a query cycle if a query which can be waited on uses Rayon to do multithreading
// internally. Such a query (X) may be executing on 2 threads (A and B) and A may
// wait using Rayon on B. Rayon may then switch to executing another query (Y)
// which in turn will wait on X causing a deadlock. We have a false dependency from
// X to Y due to Rayon waiting and a true dependency from Y to X. The algorithm here
// only considers the true dependency and won't detect a cycle.
if !found_cycle {
panic!(
"deadlock detected as we're unable to find a query cycle to break\n\
current query map:\n{:#?}",
query_map
);
}
// Mark all the thread we're about to wake up as unblocked. This needs to be done before
// we wake the threads up as otherwise Rayon could detect a deadlock if a thread we
// resumed fell asleep and this thread had yet to mark the remaining threads as unblocked.
for _ in 0..wakelist.len() {
rustc_thread_pool::mark_unblocked(registry);
}
for waiter in wakelist.into_iter() {
waiter.condvar.notify_one();
}
}
pub fn print_query_stack<'tcx>(
qcx: QueryCtxt<'tcx>,
mut current_query: Option<QueryJobId>,
dcx: DiagCtxtHandle<'_>,
limit_frames: Option<usize>,
mut file: Option<std::fs::File>,
) -> usize {
// Be careful relying on global state here: this code is called from
// a panic hook, which means that the global `DiagCtxt` may be in a weird
// state if it was responsible for triggering the panic.
let mut count_printed = 0;
let mut count_total = 0;
// Make use of a partial query map if we fail to take locks collecting active queries.
let query_map = match qcx.collect_active_jobs_from_all_queries(false) {
Ok(query_map) => query_map,
Err(query_map) => query_map,
};
if let Some(ref mut file) = file {
let _ = writeln!(file, "\n\nquery stack during panic:");
}
while let Some(query) = current_query {
let Some(query_info) = query_map.get(&query) else {
break;
};
let query_extra = query_info.frame.info.extract();
if Some(count_printed) < limit_frames || limit_frames.is_none() {
// Only print to stderr as many stack frames as `num_frames` when present.
dcx.struct_failure_note(format!(
"#{} [{:?}] {}",
count_printed, query_info.frame.dep_kind, query_extra.description
))
.with_span(query_info.job.span)
.emit();
count_printed += 1;
}
if let Some(ref mut file) = file {
let _ = writeln!(
file,
"#{} [{}] {}",
count_total,
qcx.tcx.dep_kind_vtable(query_info.frame.dep_kind).name,
query_extra.description
);
}
current_query = query_info.job.parent;
count_total += 1;
}
if let Some(ref mut file) = file {
let _ = writeln!(file, "end of query stack");
}
count_total
}
#[inline(never)]
#[cold]
pub(crate) fn report_cycle<'a>(
sess: &'a Session,
CycleError { usage, cycle: stack }: &CycleError,
) -> Diag<'a> {
assert!(!stack.is_empty());
let span = stack[0].frame.info.default_span(stack[1 % stack.len()].span);
let mut cycle_stack = Vec::new();
use crate::error::StackCount;
let stack_count = if stack.len() == 1 { StackCount::Single } else { StackCount::Multiple };
for i in 1..stack.len() {
let frame = &stack[i].frame;
let span = frame.info.default_span(stack[(i + 1) % stack.len()].span);
cycle_stack
.push(crate::error::CycleStack { span, desc: frame.info.description.to_owned() });
}
let mut cycle_usage = None;
if let Some((span, ref query)) = *usage {
cycle_usage = Some(crate::error::CycleUsage {
span: query.info.default_span(span),
usage: query.info.description.to_string(),
});
}
let alias =
if stack.iter().all(|entry| matches!(entry.frame.info.def_kind, Some(DefKind::TyAlias))) {
Some(crate::error::Alias::Ty)
} else if stack.iter().all(|entry| entry.frame.info.def_kind == Some(DefKind::TraitAlias)) {
Some(crate::error::Alias::Trait)
} else {
None
};
let cycle_diag = crate::error::Cycle {
span,
cycle_stack,
stack_bottom: stack[0].frame.info.description.to_owned(),
alias,
cycle_usage,
stack_count,
note_span: (),
};
sess.dcx().create_err(cycle_diag)
}

View file

@ -19,24 +19,27 @@ use rustc_middle::queries::{
use rustc_middle::query::AsLocalKey;
use rustc_middle::query::on_disk_cache::{CacheEncoder, EncodedDepNodeIndex, OnDiskCache};
use rustc_middle::query::plumbing::{HashResult, QuerySystem, QuerySystemFns, QueryVTable};
use rustc_middle::query::values::Value;
use rustc_middle::ty::TyCtxt;
use rustc_query_system::dep_graph::SerializedDepNodeIndex;
use rustc_query_system::query::{
CycleError, CycleErrorHandling, QueryCache, QueryMap, QueryMode, QueryState,
CycleError, CycleErrorHandling, QueryCache, QueryMode, QueryState,
};
use rustc_span::{ErrorGuaranteed, Span};
pub use crate::job::{QueryMap, break_query_cycles, print_query_stack};
pub use crate::plumbing::{QueryCtxt, query_key_hash_verify_all};
use crate::plumbing::{encode_all_query_results, try_mark_green};
use crate::profiling_support::QueryKeyStringCache;
pub use crate::profiling_support::alloc_self_profile_query_strings;
use crate::values::Value;
mod error;
mod execution;
mod job;
#[macro_use]
mod plumbing;
mod profiling_support;
mod values;
#[derive(ConstParamTy)] // Allow this struct to be used for const-generic values.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

View file

@ -28,14 +28,15 @@ use rustc_middle::ty::tls::{self, ImplicitCtxt};
use rustc_middle::ty::{self, TyCtxt};
use rustc_query_system::dep_graph::{DepNodeKey, FingerprintStyle, HasDepContext};
use rustc_query_system::query::{
QueryCache, QueryContext, QueryJobId, QueryMap, QuerySideEffect, QueryStackDeferred,
QueryStackFrame, QueryStackFrameExtra,
QueryCache, QueryContext, QueryJobId, QuerySideEffect, QueryStackDeferred, QueryStackFrame,
QueryStackFrameExtra,
};
use rustc_serialize::{Decodable, Encodable};
use rustc_span::def_id::LOCAL_CRATE;
use crate::error::{QueryOverflow, QueryOverflowNote};
use crate::execution::{all_inactive, force_query};
use crate::job::{QueryMap, find_dep_kind_root};
use crate::{QueryDispatcherUnerased, QueryFlags, SemiDynamicQueryDispatcher};
/// Implements [`QueryContext`] for use by [`rustc_query_system`], since that
@ -55,7 +56,7 @@ impl<'tcx> QueryCtxt<'tcx> {
let query_map = self
.collect_active_jobs_from_all_queries(true)
.expect("failed to collect active queries");
let (info, depth) = job.find_dep_kind_root(query_map);
let (info, depth) = find_dep_kind_root(job, query_map);
let suggested_limit = match self.tcx.recursion_limit() {
Limit(0) => Limit(2),
@ -116,6 +117,32 @@ impl<'tcx> QueryCtxt<'tcx> {
tls::enter_context(&new_icx, compute)
})
}
/// Returns a map of currently active query jobs, collected from all queries.
///
/// If `require_complete` is `true`, this function locks all shards of the
/// query results to produce a complete map, which always returns `Ok`.
/// Otherwise, it may return an incomplete map as an error if any shard
/// lock cannot be acquired.
///
/// Prefer passing `false` to `require_complete` to avoid potential deadlocks,
/// especially when called from within a deadlock handler, unless a
/// complete map is needed and no deadlock is possible at this call site.
pub fn collect_active_jobs_from_all_queries(
self,
require_complete: bool,
) -> Result<QueryMap<'tcx>, QueryMap<'tcx>> {
let mut jobs = QueryMap::default();
let mut complete = true;
for gather_fn in crate::PER_QUERY_GATHER_ACTIVE_JOBS_FNS.iter() {
if gather_fn(self.tcx, &mut jobs, require_complete).is_none() {
complete = false;
}
}
if complete { Ok(jobs) } else { Err(jobs) }
}
}
impl<'tcx> HasDepContext for QueryCtxt<'tcx> {
@ -134,32 +161,6 @@ impl<'tcx> QueryContext<'tcx> for QueryCtxt<'tcx> {
&self.tcx.jobserver_proxy
}
/// Returns a map of currently active query jobs, collected from all queries.
///
/// If `require_complete` is `true`, this function locks all shards of the
/// query results to produce a complete map, which always returns `Ok`.
/// Otherwise, it may return an incomplete map as an error if any shard
/// lock cannot be acquired.
///
/// Prefer passing `false` to `require_complete` to avoid potential deadlocks,
/// especially when called from within a deadlock handler, unless a
/// complete map is needed and no deadlock is possible at this call site.
fn collect_active_jobs_from_all_queries(
self,
require_complete: bool,
) -> Result<QueryMap<'tcx>, QueryMap<'tcx>> {
let mut jobs = QueryMap::default();
let mut complete = true;
for gather_fn in crate::PER_QUERY_GATHER_ACTIVE_JOBS_FNS.iter() {
if gather_fn(self.tcx, &mut jobs, require_complete).is_none() {
complete = false;
}
}
if complete { Ok(jobs) } else { Err(jobs) }
}
// Interactions with on_disk_cache
fn load_side_effect(
self,

View file

@ -7,15 +7,17 @@ use rustc_errors::codes::*;
use rustc_errors::{Applicability, MultiSpan, pluralize, struct_span_code_err};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_query_system::query::{CycleError, report_cycle};
use rustc_middle::dep_graph::dep_kinds;
use rustc_middle::query::plumbing::CyclePlaceholder;
use rustc_middle::ty::{self, Representability, Ty, TyCtxt};
use rustc_middle::{bug, span_bug};
use rustc_query_system::query::CycleError;
use rustc_span::def_id::LocalDefId;
use rustc_span::{ErrorGuaranteed, Span};
use crate::dep_graph::dep_kinds;
use crate::query::plumbing::CyclePlaceholder;
use crate::ty::{self, Representability, Ty, TyCtxt};
use crate::job::report_cycle;
pub trait Value<'tcx>: Sized {
pub(crate) trait Value<'tcx>: Sized {
fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle_error: &CycleError, guar: ErrorGuaranteed)
-> Self;
}

View file

@ -1,62 +1,4 @@
use rustc_errors::codes::*;
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_span::Span;
#[derive(Subdiagnostic)]
#[note("...which requires {$desc}...")]
pub(crate) struct CycleStack {
#[primary_span]
pub span: Span,
pub desc: String,
}
#[derive(Subdiagnostic)]
pub(crate) enum StackCount {
#[note("...which immediately requires {$stack_bottom} again")]
Single,
#[note("...which again requires {$stack_bottom}, completing the cycle")]
Multiple,
}
#[derive(Subdiagnostic)]
pub(crate) enum Alias {
#[note("type aliases cannot be recursive")]
#[help("consider using a struct, enum, or union instead to break the cycle")]
#[help(
"see <https://doc.rust-lang.org/reference/types.html#recursive-types> for more information"
)]
Ty,
#[note("trait aliases cannot be recursive")]
Trait,
}
#[derive(Subdiagnostic)]
#[note("cycle used when {$usage}")]
pub(crate) struct CycleUsage {
#[primary_span]
pub span: Span,
pub usage: String,
}
#[derive(Diagnostic)]
#[diag("cycle detected when {$stack_bottom}", code = E0391)]
pub(crate) struct Cycle {
#[primary_span]
pub span: Span,
pub stack_bottom: String,
#[subdiagnostic]
pub cycle_stack: Vec<CycleStack>,
#[subdiagnostic]
pub stack_count: StackCount,
#[subdiagnostic]
pub alias: Option<Alias>,
#[subdiagnostic]
pub cycle_usage: Option<CycleUsage>,
#[note(
"see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information"
)]
pub note_span: (),
}
use rustc_macros::Diagnostic;
#[derive(Diagnostic)]
#[diag("internal compiler error: reentrant incremental verify failure, suppressing message")]

View file

@ -21,9 +21,6 @@ pub trait QueryCacheKey = Hash + Eq + Copy + Debug + for<'a> HashStable<StableHa
/// Types implementing this trait are associated with actual key/value types
/// by the `Cache` associated type of the `rustc_middle::query::Key` trait.
pub trait QueryCache: Sized {
// `Key` and `Value` are `Copy` instead of `Clone` to ensure copying them stays cheap,
// but it isn't strictly necessary.
// FIXME: Is that comment still true?
type Key: QueryCacheKey;
type Value: Copy;

View file

@ -1,20 +1,12 @@
use std::fmt::Debug;
use std::hash::Hash;
use std::io::Write;
use std::iter;
use std::num::NonZero;
use std::sync::Arc;
use parking_lot::{Condvar, Mutex};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::{Diag, DiagCtxtHandle};
use rustc_hir::def::DefKind;
use rustc_session::Session;
use rustc_span::{DUMMY_SP, Span};
use rustc_span::Span;
use super::{QueryStackDeferred, QueryStackFrameExtra};
use crate::dep_graph::DepContext;
use crate::error::CycleStack;
use crate::query::plumbing::CycleError;
use crate::query::{QueryContext, QueryStackFrame};
@ -32,38 +24,10 @@ impl<'tcx> QueryInfo<QueryStackDeferred<'tcx>> {
}
}
/// Map from query job IDs to job information collected by
/// [`QueryContext::collect_active_jobs_from_all_queries`].
pub type QueryMap<'tcx> = FxHashMap<QueryJobId, QueryJobInfo<'tcx>>;
/// A value uniquely identifying an active query job.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct QueryJobId(pub NonZero<u64>);
impl QueryJobId {
fn frame<'a, 'tcx>(self, map: &'a QueryMap<'tcx>) -> QueryStackFrame<QueryStackDeferred<'tcx>> {
map.get(&self).unwrap().frame.clone()
}
fn span<'a, 'tcx>(self, map: &'a QueryMap<'tcx>) -> Span {
map.get(&self).unwrap().job.span
}
fn parent<'a, 'tcx>(self, map: &'a QueryMap<'tcx>) -> Option<QueryJobId> {
map.get(&self).unwrap().job.parent
}
fn latch<'a, 'tcx>(self, map: &'a QueryMap<'tcx>) -> Option<&'a QueryLatch<'tcx>> {
map.get(&self).unwrap().job.latch.as_ref()
}
}
#[derive(Clone, Debug)]
pub struct QueryJobInfo<'tcx> {
pub frame: QueryStackFrame<QueryStackDeferred<'tcx>>,
pub job: QueryJob<'tcx>,
}
/// Represents an active query job.
#[derive(Clone, Debug)]
pub struct QueryJob<'tcx> {
@ -76,7 +40,7 @@ pub struct QueryJob<'tcx> {
pub parent: Option<QueryJobId>,
/// The latch that is used to wait on this job.
latch: Option<QueryLatch<'tcx>>,
pub latch: Option<QueryLatch<'tcx>>,
}
impl<'tcx> QueryJob<'tcx> {
@ -105,91 +69,23 @@ impl<'tcx> QueryJob<'tcx> {
}
}
impl QueryJobId {
pub fn find_cycle_in_stack<'tcx>(
&self,
query_map: QueryMap<'tcx>,
current_job: &Option<QueryJobId>,
span: Span,
) -> CycleError<QueryStackDeferred<'tcx>> {
// Find the waitee amongst `current_job` parents
let mut cycle = Vec::new();
let mut current_job = Option::clone(current_job);
while let Some(job) = current_job {
let info = query_map.get(&job).unwrap();
cycle.push(QueryInfo { span: info.job.span, frame: info.frame.clone() });
if job == *self {
cycle.reverse();
// This is the end of the cycle
// The span entry we included was for the usage
// of the cycle itself, and not part of the cycle
// Replace it with the span which caused the cycle to form
cycle[0].span = span;
// Find out why the cycle itself was used
let usage = info
.job
.parent
.as_ref()
.map(|parent| (info.job.span, parent.frame(&query_map)));
return CycleError { usage, cycle };
}
current_job = info.job.parent;
}
panic!("did not find a cycle")
}
#[cold]
#[inline(never)]
pub fn find_dep_kind_root<'tcx>(
&self,
query_map: QueryMap<'tcx>,
) -> (QueryJobInfo<'tcx>, usize) {
let mut depth = 1;
let info = query_map.get(&self).unwrap();
let dep_kind = info.frame.dep_kind;
let mut current_id = info.job.parent;
let mut last_layout = (info.clone(), depth);
while let Some(id) = current_id {
let info = query_map.get(&id).unwrap();
if info.frame.dep_kind == dep_kind {
depth += 1;
last_layout = (info.clone(), depth);
}
current_id = info.job.parent;
}
last_layout
}
#[derive(Debug)]
pub struct QueryWaiter<'tcx> {
pub query: Option<QueryJobId>,
pub condvar: Condvar,
pub span: Span,
pub cycle: Mutex<Option<CycleError<QueryStackDeferred<'tcx>>>>,
}
#[derive(Debug)]
struct QueryWaiter<'tcx> {
query: Option<QueryJobId>,
condvar: Condvar,
span: Span,
cycle: Mutex<Option<CycleError<QueryStackDeferred<'tcx>>>>,
pub struct QueryLatchInfo<'tcx> {
pub complete: bool,
pub waiters: Vec<Arc<QueryWaiter<'tcx>>>,
}
#[derive(Debug)]
struct QueryLatchInfo<'tcx> {
complete: bool,
waiters: Vec<Arc<QueryWaiter<'tcx>>>,
}
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct QueryLatch<'tcx> {
info: Arc<Mutex<QueryLatchInfo<'tcx>>>,
}
impl<'tcx> Clone for QueryLatch<'tcx> {
fn clone(&self) -> Self {
Self { info: Arc::clone(&self.info) }
}
pub info: Arc<Mutex<QueryLatchInfo<'tcx>>>,
}
impl<'tcx> QueryLatch<'tcx> {
@ -256,399 +152,10 @@ impl<'tcx> QueryLatch<'tcx> {
/// Removes a single waiter from the list of waiters.
/// This is used to break query cycles.
fn extract_waiter(&self, waiter: usize) -> Arc<QueryWaiter<'tcx>> {
pub fn extract_waiter(&self, waiter: usize) -> Arc<QueryWaiter<'tcx>> {
let mut info = self.info.lock();
debug_assert!(!info.complete);
// Remove the waiter from the list of waiters
info.waiters.remove(waiter)
}
}
/// A resumable waiter of a query. The usize is the index into waiters in the query's latch
type Waiter = (QueryJobId, usize);
/// Visits all the non-resumable and resumable waiters of a query.
/// Only waiters in a query are visited.
/// `visit` is called for every waiter and is passed a query waiting on `query_ref`
/// and a span indicating the reason the query waited on `query_ref`.
/// If `visit` returns Some, this function returns.
/// For visits of non-resumable waiters it returns the return value of `visit`.
/// For visits of resumable waiters it returns Some(Some(Waiter)) which has the
/// required information to resume the waiter.
/// If all `visit` calls returns None, this function also returns None.
fn visit_waiters<'tcx, F>(
query_map: &QueryMap<'tcx>,
query: QueryJobId,
mut visit: F,
) -> Option<Option<Waiter>>
where
F: FnMut(Span, QueryJobId) -> Option<Option<Waiter>>,
{
// Visit the parent query which is a non-resumable waiter since it's on the same stack
if let Some(parent) = query.parent(query_map)
&& let Some(cycle) = visit(query.span(query_map), parent)
{
return Some(cycle);
}
// Visit the explicit waiters which use condvars and are resumable
if let Some(latch) = query.latch(query_map) {
for (i, waiter) in latch.info.lock().waiters.iter().enumerate() {
if let Some(waiter_query) = waiter.query {
if visit(waiter.span, waiter_query).is_some() {
// Return a value which indicates that this waiter can be resumed
return Some(Some((query, i)));
}
}
}
}
None
}
/// Look for query cycles by doing a depth first search starting at `query`.
/// `span` is the reason for the `query` to execute. This is initially DUMMY_SP.
/// If a cycle is detected, this initial value is replaced with the span causing
/// the cycle.
fn cycle_check<'tcx>(
query_map: &QueryMap<'tcx>,
query: QueryJobId,
span: Span,
stack: &mut Vec<(Span, QueryJobId)>,
visited: &mut FxHashSet<QueryJobId>,
) -> Option<Option<Waiter>> {
if !visited.insert(query) {
return if let Some(p) = stack.iter().position(|q| q.1 == query) {
// We detected a query cycle, fix up the initial span and return Some
// Remove previous stack entries
stack.drain(0..p);
// Replace the span for the first query with the cycle cause
stack[0].0 = span;
Some(None)
} else {
None
};
}
// Query marked as visited is added it to the stack
stack.push((span, query));
// Visit all the waiters
let r = visit_waiters(query_map, query, |span, successor| {
cycle_check(query_map, successor, span, stack, visited)
});
// Remove the entry in our stack if we didn't find a cycle
if r.is_none() {
stack.pop();
}
r
}
/// Finds out if there's a path to the compiler root (aka. code which isn't in a query)
/// from `query` without going through any of the queries in `visited`.
/// This is achieved with a depth first search.
fn connected_to_root<'tcx>(
query_map: &QueryMap<'tcx>,
query: QueryJobId,
visited: &mut FxHashSet<QueryJobId>,
) -> bool {
// We already visited this or we're deliberately ignoring it
if !visited.insert(query) {
return false;
}
// This query is connected to the root (it has no query parent), return true
if query.parent(query_map).is_none() {
return true;
}
visit_waiters(query_map, query, |_, successor| {
connected_to_root(query_map, successor, visited).then_some(None)
})
.is_some()
}
// Deterministically pick an query from a list
fn pick_query<'a, 'tcx, T, F>(query_map: &QueryMap<'tcx>, queries: &'a [T], f: F) -> &'a T
where
F: Fn(&T) -> (Span, QueryJobId),
{
// Deterministically pick an entry point
// FIXME: Sort this instead
queries
.iter()
.min_by_key(|v| {
let (span, query) = f(v);
let hash = query.frame(query_map).hash;
// Prefer entry points which have valid spans for nicer error messages
// We add an integer to the tuple ensuring that entry points
// with valid spans are picked first
let span_cmp = if span == DUMMY_SP { 1 } else { 0 };
(span_cmp, hash)
})
.unwrap()
}
/// Looks for query cycles starting from the last query in `jobs`.
/// If a cycle is found, all queries in the cycle is removed from `jobs` and
/// the function return true.
/// If a cycle was not found, the starting query is removed from `jobs` and
/// the function returns false.
fn remove_cycle<'tcx>(
query_map: &QueryMap<'tcx>,
jobs: &mut Vec<QueryJobId>,
wakelist: &mut Vec<Arc<QueryWaiter<'tcx>>>,
) -> bool {
let mut visited = FxHashSet::default();
let mut stack = Vec::new();
// Look for a cycle starting with the last query in `jobs`
if let Some(waiter) =
cycle_check(query_map, jobs.pop().unwrap(), DUMMY_SP, &mut stack, &mut visited)
{
// The stack is a vector of pairs of spans and queries; reverse it so that
// the earlier entries require later entries
let (mut spans, queries): (Vec<_>, Vec<_>) = stack.into_iter().rev().unzip();
// Shift the spans so that queries are matched with the span for their waitee
spans.rotate_right(1);
// Zip them back together
let mut stack: Vec<_> = iter::zip(spans, queries).collect();
// Remove the queries in our cycle from the list of jobs to look at
for r in &stack {
if let Some(pos) = jobs.iter().position(|j| j == &r.1) {
jobs.remove(pos);
}
}
// Find the queries in the cycle which are
// connected to queries outside the cycle
let entry_points = stack
.iter()
.filter_map(|&(span, query)| {
if query.parent(query_map).is_none() {
// This query is connected to the root (it has no query parent)
Some((span, query, None))
} else {
let mut waiters = Vec::new();
// Find all the direct waiters who lead to the root
visit_waiters(query_map, query, |span, waiter| {
// Mark all the other queries in the cycle as already visited
let mut visited = FxHashSet::from_iter(stack.iter().map(|q| q.1));
if connected_to_root(query_map, waiter, &mut visited) {
waiters.push((span, waiter));
}
None
});
if waiters.is_empty() {
None
} else {
// Deterministically pick one of the waiters to show to the user
let waiter = *pick_query(query_map, &waiters, |s| *s);
Some((span, query, Some(waiter)))
}
}
})
.collect::<Vec<(Span, QueryJobId, Option<(Span, QueryJobId)>)>>();
// Deterministically pick an entry point
let (_, entry_point, usage) = pick_query(query_map, &entry_points, |e| (e.0, e.1));
// Shift the stack so that our entry point is first
let entry_point_pos = stack.iter().position(|(_, query)| query == entry_point);
if let Some(pos) = entry_point_pos {
stack.rotate_left(pos);
}
let usage = usage.as_ref().map(|(span, query)| (*span, query.frame(query_map)));
// Create the cycle error
let error = CycleError {
usage,
cycle: stack
.iter()
.map(|&(s, ref q)| QueryInfo { span: s, frame: q.frame(query_map) })
.collect(),
};
// We unwrap `waiter` here since there must always be one
// edge which is resumable / waited using a query latch
let (waitee_query, waiter_idx) = waiter.unwrap();
// Extract the waiter we want to resume
let waiter = waitee_query.latch(query_map).unwrap().extract_waiter(waiter_idx);
// Set the cycle error so it will be picked up when resumed
*waiter.cycle.lock() = Some(error);
// Put the waiter on the list of things to resume
wakelist.push(waiter);
true
} else {
false
}
}
/// Detects query cycles by using depth first search over all active query jobs.
/// If a query cycle is found it will break the cycle by finding an edge which
/// uses a query latch and then resuming that waiter.
/// There may be multiple cycles involved in a deadlock, so this searches
/// all active queries for cycles before finally resuming all the waiters at once.
pub fn break_query_cycles<'tcx>(query_map: QueryMap<'tcx>, registry: &rustc_thread_pool::Registry) {
let mut wakelist = Vec::new();
// It is OK per the comments:
// - https://github.com/rust-lang/rust/pull/131200#issuecomment-2798854932
// - https://github.com/rust-lang/rust/pull/131200#issuecomment-2798866392
#[allow(rustc::potential_query_instability)]
let mut jobs: Vec<QueryJobId> = query_map.keys().cloned().collect();
let mut found_cycle = false;
while jobs.len() > 0 {
if remove_cycle(&query_map, &mut jobs, &mut wakelist) {
found_cycle = true;
}
}
// Check that a cycle was found. It is possible for a deadlock to occur without
// a query cycle if a query which can be waited on uses Rayon to do multithreading
// internally. Such a query (X) may be executing on 2 threads (A and B) and A may
// wait using Rayon on B. Rayon may then switch to executing another query (Y)
// which in turn will wait on X causing a deadlock. We have a false dependency from
// X to Y due to Rayon waiting and a true dependency from Y to X. The algorithm here
// only considers the true dependency and won't detect a cycle.
if !found_cycle {
panic!(
"deadlock detected as we're unable to find a query cycle to break\n\
current query map:\n{:#?}",
query_map
);
}
// Mark all the thread we're about to wake up as unblocked. This needs to be done before
// we wake the threads up as otherwise Rayon could detect a deadlock if a thread we
// resumed fell asleep and this thread had yet to mark the remaining threads as unblocked.
for _ in 0..wakelist.len() {
rustc_thread_pool::mark_unblocked(registry);
}
for waiter in wakelist.into_iter() {
waiter.condvar.notify_one();
}
}
#[inline(never)]
#[cold]
pub fn report_cycle<'a>(
sess: &'a Session,
CycleError { usage, cycle: stack }: &CycleError,
) -> Diag<'a> {
assert!(!stack.is_empty());
let span = stack[0].frame.info.default_span(stack[1 % stack.len()].span);
let mut cycle_stack = Vec::new();
use crate::error::StackCount;
let stack_count = if stack.len() == 1 { StackCount::Single } else { StackCount::Multiple };
for i in 1..stack.len() {
let frame = &stack[i].frame;
let span = frame.info.default_span(stack[(i + 1) % stack.len()].span);
cycle_stack.push(CycleStack { span, desc: frame.info.description.to_owned() });
}
let mut cycle_usage = None;
if let Some((span, ref query)) = *usage {
cycle_usage = Some(crate::error::CycleUsage {
span: query.info.default_span(span),
usage: query.info.description.to_string(),
});
}
let alias =
if stack.iter().all(|entry| matches!(entry.frame.info.def_kind, Some(DefKind::TyAlias))) {
Some(crate::error::Alias::Ty)
} else if stack.iter().all(|entry| entry.frame.info.def_kind == Some(DefKind::TraitAlias)) {
Some(crate::error::Alias::Trait)
} else {
None
};
let cycle_diag = crate::error::Cycle {
span,
cycle_stack,
stack_bottom: stack[0].frame.info.description.to_owned(),
alias,
cycle_usage,
stack_count,
note_span: (),
};
sess.dcx().create_err(cycle_diag)
}
pub fn print_query_stack<'tcx, Qcx: QueryContext<'tcx>>(
qcx: Qcx,
mut current_query: Option<QueryJobId>,
dcx: DiagCtxtHandle<'_>,
limit_frames: Option<usize>,
mut file: Option<std::fs::File>,
) -> usize {
// Be careful relying on global state here: this code is called from
// a panic hook, which means that the global `DiagCtxt` may be in a weird
// state if it was responsible for triggering the panic.
let mut count_printed = 0;
let mut count_total = 0;
// Make use of a partial query map if we fail to take locks collecting active queries.
let query_map = match qcx.collect_active_jobs_from_all_queries(false) {
Ok(query_map) => query_map,
Err(query_map) => query_map,
};
if let Some(ref mut file) = file {
let _ = writeln!(file, "\n\nquery stack during panic:");
}
while let Some(query) = current_query {
let Some(query_info) = query_map.get(&query) else {
break;
};
let query_extra = query_info.frame.info.extract();
if Some(count_printed) < limit_frames || limit_frames.is_none() {
// Only print to stderr as many stack frames as `num_frames` when present.
dcx.struct_failure_note(format!(
"#{} [{:?}] {}",
count_printed, query_info.frame.dep_kind, query_extra.description
))
.with_span(query_info.job.span)
.emit();
count_printed += 1;
}
if let Some(ref mut file) = file {
let _ = writeln!(
file,
"#{} [{}] {}",
count_total,
qcx.dep_context().dep_kind_vtable(query_info.frame.dep_kind).name,
query_extra.description
);
}
current_query = query_info.job.parent;
count_total += 1;
}
if let Some(ref mut file) = file {
let _ = writeln!(file, "end of query stack");
}
count_total
}

View file

@ -15,10 +15,7 @@ use rustc_span::def_id::DefId;
pub use self::caches::{
DefIdCache, DefaultCache, QueryCache, QueryCacheKey, SingleCache, VecCache,
};
pub use self::job::{
QueryInfo, QueryJob, QueryJobId, QueryJobInfo, QueryLatch, QueryMap, break_query_cycles,
print_query_stack, report_cycle,
};
pub use self::job::{QueryInfo, QueryJob, QueryJobId, QueryLatch, QueryWaiter};
pub use self::plumbing::*;
use crate::dep_graph::{DepKind, DepNodeIndex, HasDepContext, SerializedDepNodeIndex};
@ -52,7 +49,7 @@ pub struct QueryStackFrame<I> {
pub dep_kind: DepKind,
/// This hash is used to deterministically pick
/// a query to remove cycles in the parallel compiler.
hash: Hash64,
pub hash: Hash64,
pub def_id: Option<DefId>,
/// A def-id that is extracted from a `Ty` in a query key
pub def_id_for_ty_in_cycle: Option<DefId>,
@ -161,11 +158,6 @@ pub trait QueryContext<'tcx>: HasDepContext {
/// a token while waiting on a query.
fn jobserver_proxy(&self) -> &Proxy;
fn collect_active_jobs_from_all_queries(
self,
require_complete: bool,
) -> Result<QueryMap<'tcx>, QueryMap<'tcx>>;
/// Load a side effect associated to the node in the previous session.
fn load_side_effect(
self,

View file

@ -7,7 +7,7 @@ pub(crate) fn target() -> Target {
llvm_target,
metadata: TargetMetadata {
description: Some("ARM64 Apple tvOS".into()),
tier: Some(3),
tier: Some(2),
host_tools: Some(false),
std: Some(true),
},

View file

@ -7,7 +7,7 @@ pub(crate) fn target() -> Target {
llvm_target,
metadata: TargetMetadata {
description: Some("ARM64 Apple tvOS Simulator".into()),
tier: Some(3),
tier: Some(2),
host_tools: Some(false),
std: Some(true),
},

View file

@ -7,7 +7,7 @@ pub(crate) fn target() -> Target {
llvm_target,
metadata: TargetMetadata {
description: Some("ARM64 Apple visionOS".into()),
tier: Some(3),
tier: Some(2),
host_tools: Some(false),
std: Some(true),
},

View file

@ -7,7 +7,7 @@ pub(crate) fn target() -> Target {
llvm_target,
metadata: TargetMetadata {
description: Some("ARM64 Apple visionOS simulator".into()),
tier: Some(3),
tier: Some(2),
host_tools: Some(false),
std: Some(true),
},

View file

@ -7,7 +7,7 @@ pub(crate) fn target() -> Target {
llvm_target,
metadata: TargetMetadata {
description: Some("ARM64 Apple watchOS".into()),
tier: Some(3),
tier: Some(2),
host_tools: Some(false),
std: Some(true),
},

View file

@ -7,7 +7,7 @@ pub(crate) fn target() -> Target {
llvm_target,
metadata: TargetMetadata {
description: Some("ARM64 Apple watchOS Simulator".into()),
tier: Some(3),
tier: Some(2),
host_tools: Some(false),
std: Some(true),
},

View file

@ -190,8 +190,8 @@ impl<'tcx> Visitor<'tcx> for TyPathVisitor<'tcx> {
}
Some(rbv::ResolvedArg::LateBound(debruijn_index, _, id)) => {
debug!("FindNestedTypeVisitor::visit_ty: LateBound depth = {:?}", debruijn_index,);
debug!("id={:?}", id);
debug!("FindNestedTypeVisitor::visit_ty: LateBound depth = {debruijn_index:?}");
debug!("id={id:?}");
if debruijn_index == self.current_index && id.to_def_id() == self.region_def_id {
return ControlFlow::Break(());
}

View file

@ -239,14 +239,14 @@ pub fn suggest_new_region_bound(
};
spans_suggs.push((fn_return.span.shrink_to_hi(), format!(" + {name} ")));
err.multipart_suggestion_verbose(
format!("{declare} `{ty}` {captures}, {use_lt}",),
format!("{declare} `{ty}` {captures}, {use_lt}"),
spans_suggs,
Applicability::MaybeIncorrect,
);
} else {
err.span_suggestion_verbose(
fn_return.span.shrink_to_hi(),
format!("{declare} `{ty}` {captures}, {explicit}",),
format!("{declare} `{ty}` {captures}, {explicit}"),
&plus_lt,
Applicability::MaybeIncorrect,
);
@ -257,7 +257,7 @@ pub fn suggest_new_region_bound(
if let LifetimeKind::ImplicitObjectLifetimeDefault = lt.kind {
err.span_suggestion_verbose(
fn_return.span.shrink_to_hi(),
format!("{declare} the trait object {captures}, {explicit}",),
format!("{declare} the trait object {captures}, {explicit}"),
&plus_lt,
Applicability::MaybeIncorrect,
);

View file

@ -710,7 +710,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
predicate
);
let post = if post.len() > 1 || (post.len() == 1 && post[0].contains('\n')) {
format!(":\n{}", post.iter().map(|p| format!("- {p}")).collect::<Vec<_>>().join("\n"),)
format!(":\n{}", post.iter().map(|p| format!("- {p}")).collect::<Vec<_>>().join("\n"))
} else if post.len() == 1 {
format!(": `{}`", post[0])
} else {
@ -722,7 +722,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
err.note(format!("cannot satisfy `{predicate}`"));
}
(0, _, 1) => {
err.note(format!("{} in the `{}` crate{}", msg, crates[0], post,));
err.note(format!("{msg} in the `{}` crate{post}", crates[0]));
}
(0, _, _) => {
err.note(format!(
@ -739,7 +739,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
(_, 1, 1) => {
let span: MultiSpan = spans.into();
err.span_note(span, msg);
err.note(format!("and another `impl` found in the `{}` crate{}", crates[0], post,));
err.note(format!("and another `impl` found in the `{}` crate{post}", crates[0]));
}
_ => {
let span: MultiSpan = spans.into();

View file

@ -2263,7 +2263,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
err.highlighted_span_help(
self.tcx.def_span(def_id),
vec![
StringPart::normal(format!("the trait `{trait_}` ",)),
StringPart::normal(format!("the trait `{trait_}` ")),
StringPart::highlighted("is"),
StringPart::normal(desc),
StringPart::highlighted(self_ty),

View file

@ -888,7 +888,7 @@ impl<'tcx> OnUnimplementedFormatString {
}
} else {
let reported =
struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description,)
struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description)
.emit();
result = Err(reported);
}

View file

@ -312,7 +312,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
// `async`/`gen` constructs get lowered to a special kind of coroutine that
// should *not* `impl Coroutine`.
ty::Coroutine(did, ..) if self.tcx().is_general_coroutine(*did) => {
debug!(?self_ty, ?obligation, "assemble_coroutine_candidates",);
debug!(?self_ty, ?obligation, "assemble_coroutine_candidates");
candidates.vec.push(CoroutineCandidate);
}
@ -334,7 +334,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
// async constructs get lowered to a special kind of coroutine that
// should directly `impl Future`.
if self.tcx().coroutine_is_async(*did) {
debug!(?self_ty, ?obligation, "assemble_future_candidates",);
debug!(?self_ty, ?obligation, "assemble_future_candidates");
candidates.vec.push(FutureCandidate);
}
@ -352,7 +352,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
if let ty::Coroutine(did, ..) = self_ty.kind()
&& self.tcx().coroutine_is_gen(*did)
{
debug!(?self_ty, ?obligation, "assemble_iterator_candidates",);
debug!(?self_ty, ?obligation, "assemble_iterator_candidates");
candidates.vec.push(IteratorCandidate);
}
@ -378,7 +378,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
// gen constructs get lowered to a special kind of coroutine that
// should directly `impl AsyncIterator`.
if self.tcx().coroutine_is_async_gen(did) {
debug!(?self_ty, ?obligation, "assemble_iterator_candidates",);
debug!(?self_ty, ?obligation, "assemble_iterator_candidates");
// Can only confirm this candidate if we have constrained
// the `Yield` type to at least `Poll<Option<?0>>`..

View file

@ -1246,6 +1246,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
})
}
/// This trait is indirectly exposed on stable, so do *not* extend the set of types that
/// implement the trait without FCP!
fn confirm_bikeshed_guaranteed_no_drop_candidate(
&mut self,
obligation: &PolyTraitObligation<'tcx>,

View file

@ -1223,7 +1223,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
&& self.match_fresh_trait_preds(stack.fresh_trait_pred, prev.fresh_trait_pred)
})
{
debug!("evaluate_stack --> unbound argument, recursive --> giving up",);
debug!("evaluate_stack --> unbound argument, recursive --> giving up");
return Ok(EvaluatedToAmbigStackDependent);
}

View file

@ -951,6 +951,34 @@ impl<'a, 'tcx> TypeVisitor<TyCtxt<'tcx>> for WfPredicates<'a, 'tcx> {
ty::Binder::dummy(ty::PredicateKind::DynCompatible(principal)),
));
}
if !t.has_escaping_bound_vars() {
for projection in data.projection_bounds() {
let pred_binder = projection
.with_self_ty(tcx, t)
.map_bound(|p| {
p.term.as_const().map(|ct| {
let assoc_const_ty = tcx
.type_of(p.projection_term.def_id)
.instantiate(tcx, p.projection_term.args);
ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(
ct,
assoc_const_ty,
))
})
})
.transpose();
if let Some(pred_binder) = pred_binder {
self.out.push(traits::Obligation::with_depth(
tcx,
self.cause(ObligationCauseCode::WellFormed(None)),
self.recursion_depth,
self.param_env,
pred_binder,
));
}
}
}
}
// Inference variables are the complicated case, since we don't

View file

@ -31,7 +31,6 @@ impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> {
}
/// See [`Drain::new`]; this is our fake iterator.
#[rustc_const_unstable(feature = "array_try_map", issue = "79711")]
#[unstable(feature = "array_try_map", issue = "79711")]
pub(super) struct Drain<'l, 'f, T, const N: usize, F> {
// FIXME(const-hack): This is essentially a slice::IterMut<'static>, replace when possible.

View file

@ -466,10 +466,34 @@ auto:
- name: dist-apple-various
env:
SCRIPT: ./x.py dist bootstrap --include-default-paths --host='' --target=aarch64-apple-ios,x86_64-apple-ios,aarch64-apple-ios-sim,aarch64-apple-ios-macabi,x86_64-apple-ios-macabi
# Build and distribute the standard library for these targets.
TARGETS: "aarch64-apple-ios,\
aarch64-apple-ios-sim,\
x86_64-apple-ios,\
aarch64-apple-ios-macabi,\
x86_64-apple-ios-macabi,\
aarch64-apple-tvos,\
aarch64-apple-tvos-sim,\
aarch64-apple-visionos,\
aarch64-apple-visionos-sim,\
aarch64-apple-watchos,\
aarch64-apple-watchos-sim"
SCRIPT: ./x.py dist bootstrap --include-default-paths --host='' --target=$TARGETS
# Mac Catalyst cannot currently compile the sanitizer:
# https://github.com/rust-lang/rust/issues/129069
RUST_CONFIGURE_ARGS: --enable-sanitizers --enable-profiler --set rust.jemalloc --set target.aarch64-apple-ios-macabi.sanitizers=false --set target.x86_64-apple-ios-macabi.sanitizers=false
#
# And tvOS and watchOS don't currently support the profiler runtime:
# https://github.com/rust-lang/rust/issues/152426
RUST_CONFIGURE_ARGS: >-
--enable-sanitizers
--enable-profiler
--set rust.jemalloc
--set target.aarch64-apple-ios-macabi.sanitizers=false
--set target.x86_64-apple-ios-macabi.sanitizers=false
--set target.aarch64-apple-tvos.profiler=false
--set target.aarch64-apple-tvos-sim.profiler=false
--set target.aarch64-apple-watchos.profiler=false
--set target.aarch64-apple-watchos-sim.profiler=false
# Ensure that host tooling is built to support our minimum support macOS version.
# FIXME(madsmtm): This might be redundant, as we're not building host tooling here (?)
MACOSX_DEPLOYMENT_TARGET: 10.12

@ -1 +1 @@
Subproject commit 39aeceaa3aeab845bc4517e7a44e48727d3b9dbe
Subproject commit 05d114287b7d6f6c9253d5242540f00fbd6172ab

View file

@ -194,9 +194,8 @@ resources maintained by the [Embedded Working Group] useful.
#### The Embedded Rust Book
[The Embedded Rust Book] is targeted at developers familiar with embedded
development and familiar with Rust, but have not used Rust for embedded
development.
[The Embedded Rust Book] is targeted at developers who are familiar with embedded
development and Rust, but who have not used Rust for embedded development.
[The Embedded Rust Book]: embedded-book/index.html
[Rust project]: https://www.rust-lang.org

@ -1 +1 @@
Subproject commit 050c002a360fa45b701ea34feed7a860dc8a41bf
Subproject commit b8f254a991b8b7e8f704527f0d4f343a4697dfa9

View file

@ -41,7 +41,7 @@ Some things that might be helpful to you though:
<input type="submit" value="Search" id="search-but">
<!--
Don't show the options by default,
since "From the Standary Library" doesn't work without JavaScript
since "From the Standard Library" doesn't work without JavaScript
-->
<fieldset id="search-from" style="display:none">
<label><input name="from" value="library" type="radio"> From the Standard Library</label>

@ -1 +1 @@
Subproject commit 990819b86c22bbf538c0526f0287670f3dc1a67a
Subproject commit addd0602c819b6526b9cc97653b0fadca395528c

View file

@ -148,6 +148,12 @@ target | std | notes
[`aarch64-apple-ios`](platform-support/apple-ios.md) | ✓ | ARM64 iOS
[`aarch64-apple-ios-macabi`](platform-support/apple-ios-macabi.md) | ✓ | Mac Catalyst on ARM64
[`aarch64-apple-ios-sim`](platform-support/apple-ios.md) | ✓ | Apple iOS Simulator on ARM64
[`aarch64-apple-tvos`](platform-support/apple-tvos.md) | ✓ | ARM64 tvOS
[`aarch64-apple-tvos-sim`](platform-support/apple-tvos.md) | ✓ | ARM64 tvOS Simulator
[`aarch64-apple-visionos`](platform-support/apple-visionos.md) | ✓ | ARM64 Apple visionOS
[`aarch64-apple-visionos-sim`](platform-support/apple-visionos.md) | ✓ | ARM64 Apple visionOS Simulator
[`aarch64-apple-watchos`](platform-support/apple-watchos.md) | ✓ | ARM64 Apple WatchOS
[`aarch64-apple-watchos-sim`](platform-support/apple-watchos.md) | ✓ | ARM64 Apple WatchOS Simulator
[`aarch64-linux-android`](platform-support/android.md) | ✓ | ARM64 Android
[`aarch64-unknown-fuchsia`](platform-support/fuchsia.md) | ✓ | ARM64 Fuchsia
[`aarch64-unknown-none`](platform-support/aarch64-unknown-none.md) | * | Bare ARM64, hardfloat
@ -250,12 +256,6 @@ host tools.
target | std | host | notes
-------|:---:|:----:|-------
[`aarch64-apple-tvos`](platform-support/apple-tvos.md) | ✓ | | ARM64 tvOS
[`aarch64-apple-tvos-sim`](platform-support/apple-tvos.md) | ✓ | | ARM64 tvOS Simulator
[`aarch64-apple-visionos`](platform-support/apple-visionos.md) | ✓ | | ARM64 Apple visionOS
[`aarch64-apple-visionos-sim`](platform-support/apple-visionos.md) | ✓ | | ARM64 Apple visionOS Simulator
[`aarch64-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | ARM64 Apple WatchOS
[`aarch64-apple-watchos-sim`](platform-support/apple-watchos.md) | ✓ | | ARM64 Apple WatchOS Simulator
[`aarch64-kmc-solid_asp3`](platform-support/kmc-solid.md) | ✓ | | ARM64 SOLID with TOPPERS/ASP3
[`aarch64-nintendo-switch-freestanding`](platform-support/aarch64-nintendo-switch-freestanding.md) | * | | ARM64 Nintendo Switch, Horizon
[`aarch64-unknown-freebsd`](platform-support/freebsd.md) | ✓ | ✓ | ARM64 FreeBSD

View file

@ -2,10 +2,13 @@
Apple tvOS targets.
**Tier: 3**
**Tier: 2 (without Host Tools)**
- `aarch64-apple-tvos`: Apple tvOS on ARM64.
- `aarch64-apple-tvos-sim`: Apple tvOS Simulator on ARM64.
**Tier: 3**
- `x86_64-apple-tvos`: Apple tvOS Simulator on x86_64.
## Target maintainers
@ -52,16 +55,13 @@ The following APIs are currently known to have missing or incomplete support:
## Building the target
The targets can be built by enabling them for a `rustc` build in
`bootstrap.toml`, by adding, for example:
```toml
[build]
build-stage = 1
target = ["aarch64-apple-tvos", "aarch64-apple-tvos-sim"]
The tier 2 targets are distributed through `rustup`, and can be installed using one of:
```console
$ rustup target add aarch64-apple-tvos
$ rustup target add aarch64-apple-tvos-sim
```
Using the unstable `-Zbuild-std` with a nightly Cargo may also work.
See [the instructions for iOS](./apple-ios.md#building-the-target) for how to build the tier 3 target.
## Building Rust programs

View file

@ -2,7 +2,7 @@
Apple visionOS / xrOS targets.
**Tier: 3**
**Tier: 2 (without Host Tools)**
- `aarch64-apple-visionos`: Apple visionOS on arm64.
- `aarch64-apple-visionos-sim`: Apple visionOS Simulator on arm64.
@ -31,19 +31,12 @@ case `XROS_DEPLOYMENT_TARGET`.
## Building the target
The targets can be built by enabling them for a `rustc` build in
`bootstrap.toml`, by adding, for example:
```toml
[build]
target = ["aarch64-apple-visionos", "aarch64-apple-visionos-sim"]
The targets are distributed through `rustup`, and can be installed using one of:
```console
$ rustup target add aarch64-apple-visionos
$ rustup target add aarch64-apple-visionos-sim
```
Using the unstable `-Zbuild-std` with a nightly Cargo may also work.
Note: Currently, a newer version of `libc` and `cc` may be required, this will
be fixed in [#124560](https://github.com/rust-lang/rust/pull/124560).
## Building Rust programs
See [the instructions for iOS](./apple-ios.md#building-rust-programs).

View file

@ -2,10 +2,13 @@
Apple watchOS targets.
**Tier: 3**
**Tier: 2 (without Host Tools)**
- `aarch64-apple-watchos`: Apple WatchOS on ARM64.
- `aarch64-apple-watchos-sim`: Apple WatchOS Simulator on ARM64.
**Tier: 3**
- `x86_64-apple-watchos-sim`: Apple WatchOS Simulator on 64-bit x86.
- `arm64_32-apple-watchos`: Apple WatchOS on Arm 64_32.
- `armv7k-apple-watchos`: Apple WatchOS on Armv7k.
@ -37,16 +40,13 @@ case `WATCHOS_DEPLOYMENT_TARGET`.
## Building the target
The targets can be built by enabling them for a `rustc` build in
`bootstrap.toml`, by adding, for example:
```toml
[build]
build-stage = 1
target = ["aarch64-apple-watchos", "aarch64-apple-watchos-sim"]
The tier 2 targets are distributed through `rustup`, and can be installed using one of:
```console
$ rustup target add aarch64-apple-watchos
$ rustup target add aarch64-apple-watchos-sim
```
Using the unstable `-Zbuild-std` with a nightly Cargo may also work.
See [the instructions for iOS](./apple-ios.md#building-the-target) for how to build the tier 3 targets.
## Building Rust programs

View file

@ -2,13 +2,13 @@
#![stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "foo", since = "3.3.3")]
//~^ ERROR macros cannot have const stability attributes
//~^ ERROR attribute cannot be used on macro defs
macro_rules! foo {
() => {};
}
#[rustc_const_unstable(feature = "bar", issue="none")]
//~^ ERROR macros cannot have const stability attributes
#[rustc_const_unstable(feature = "bar", issue = "none")]
//~^ ERROR attribute cannot be used on macro defs
macro_rules! bar {
() => {};
}

View file

@ -1,20 +1,18 @@
error: macros cannot have const stability attributes
error: `#[rustc_const_stable]` attribute cannot be used on macro defs
--> $DIR/const-stability-on-macro.rs:4:1
|
LL | #[rustc_const_stable(feature = "foo", since = "3.3.3")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid const stability attribute
LL |
LL | macro_rules! foo {
| ---------------- const stability attribute affects this macro
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: `#[rustc_const_stable]` can be applied to associated consts, constants, crates, functions, impl blocks, statics, traits, and use statements
error: macros cannot have const stability attributes
error: `#[rustc_const_unstable]` attribute cannot be used on macro defs
--> $DIR/const-stability-on-macro.rs:10:1
|
LL | #[rustc_const_unstable(feature = "bar", issue="none")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid const stability attribute
LL |
LL | macro_rules! bar {
| ---------------- const stability attribute affects this macro
LL | #[rustc_const_unstable(feature = "bar", issue = "none")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: `#[rustc_const_unstable]` can be applied to associated consts, constants, crates, functions, impl blocks, statics, traits, and use statements
error: aborting due to 2 previous errors

View file

@ -15,6 +15,7 @@ trait Trait {
struct Hold<T: ?Sized>(T);
trait Bound = Trait<Y = { Hold::<Self> }>;
//~^ ERROR the constant `Hold::<Self>` is not of type `i32`
fn main() {
let _: dyn Bound; //~ ERROR associated constant binding in trait object type mentions `Self`

View file

@ -1,5 +1,17 @@
error: the constant `Hold::<Self>` is not of type `i32`
--> $DIR/dyn-compat-const-projection-behind-trait-alias-mentions-self.rs:17:21
|
LL | trait Bound = Trait<Y = { Hold::<Self> }>;
| ^^^^^^^^^^^^^^^^^^^^ expected `i32`, found struct constructor
|
note: required by a const generic parameter in `Bound`
--> $DIR/dyn-compat-const-projection-behind-trait-alias-mentions-self.rs:17:21
|
LL | trait Bound = Trait<Y = { Hold::<Self> }>;
| ^^^^^^^^^^^^^^^^^^^^ required by this const generic parameter in `Bound`
error: associated constant binding in trait object type mentions `Self`
--> $DIR/dyn-compat-const-projection-behind-trait-alias-mentions-self.rs:20:12
--> $DIR/dyn-compat-const-projection-behind-trait-alias-mentions-self.rs:21:12
|
LL | trait Bound = Trait<Y = { Hold::<Self> }>;
| -------------------- this binding mentions `Self`
@ -7,5 +19,5 @@ LL | trait Bound = Trait<Y = { Hold::<Self> }>;
LL | let _: dyn Bound;
| ^^^^^^^^^ contains a mention of `Self`
error: aborting due to 1 previous error
error: aborting due to 2 previous errors

View file

@ -0,0 +1,11 @@
//! Check associated const binding with escaping bound vars doesn't cause ICE
//! (#151642)
//@ check-pass
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
trait Trait2<'a> { type const ASSOC: i32; }
fn g(_: for<'a> fn(Box<dyn Trait2<'a, ASSOC = 10>>)) {}
fn main() {}

View file

@ -0,0 +1,11 @@
//! Check that we correctly handle associated const bindings
//! in `impl Trait` where the RHS is a const param (#151642).
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
trait Trait { type const CT: bool; }
fn f<const N: i32>(_: impl Trait<CT = { N }>) {}
//~^ ERROR the constant `N` is not of type `bool`
fn main() {}

View file

@ -0,0 +1,14 @@
error: the constant `N` is not of type `bool`
--> $DIR/wf-mismatch-1.rs:9:34
|
LL | fn f<const N: i32>(_: impl Trait<CT = { N }>) {}
| ^^^^^^^^^^ expected `bool`, found `i32`
|
note: required by a const generic parameter in `f`
--> $DIR/wf-mismatch-1.rs:9:34
|
LL | fn f<const N: i32>(_: impl Trait<CT = { N }>) {}
| ^^^^^^^^^^ required by this const generic parameter in `f`
error: aborting due to 1 previous error

View file

@ -0,0 +1,13 @@
//! Check that we correctly handle associated const bindings
//! in `dyn Trait` where the RHS is a const param (#151642).
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
trait Trait { type const CT: bool; }
fn f<const N: i32>() {
let _: dyn Trait<CT = { N }>;
//~^ ERROR the constant `N` is not of type `bool`
}
fn main() {}

View file

@ -0,0 +1,8 @@
error: the constant `N` is not of type `bool`
--> $DIR/wf-mismatch-2.rs:10:12
|
LL | let _: dyn Trait<CT = { N }>;
| ^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found `i32`
error: aborting due to 1 previous error

View file

@ -0,0 +1,17 @@
//! Check that we correctly handle associated const bindings
//! where the RHS is a normalizable const projection (#151642).
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
trait Trait { type const CT: bool; }
trait Bound { type const N: u32; }
impl Bound for () { type const N: u32 = 0; }
fn f() { let _: dyn Trait<CT = { <() as Bound>::N }>; }
//~^ ERROR the constant `0` is not of type `bool`
fn g(_: impl Trait<CT = { <() as Bound>::N }>) {}
//~^ ERROR the constant `0` is not of type `bool`
fn main() {}

View file

@ -0,0 +1,20 @@
error: the constant `0` is not of type `bool`
--> $DIR/wf-mismatch-3.rs:14:20
|
LL | fn g(_: impl Trait<CT = { <() as Bound>::N }>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found `u32`
|
note: required by a const generic parameter in `g`
--> $DIR/wf-mismatch-3.rs:14:20
|
LL | fn g(_: impl Trait<CT = { <() as Bound>::N }>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this const generic parameter in `g`
error: the constant `0` is not of type `bool`
--> $DIR/wf-mismatch-3.rs:12:17
|
LL | fn f() { let _: dyn Trait<CT = { <() as Bound>::N }>; }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `bool`, found `u32`
error: aborting due to 2 previous errors

View file

@ -1,35 +1,33 @@
//@ check-pass
#![feature(generic_const_items, min_generic_const_args)]
#![feature(adt_const_params)]
#![allow(incomplete_features)]
#![feature(adt_const_params, unsized_const_params, generic_const_parameter_types)]
#![expect(incomplete_features)]
use std::marker::{ConstParamTy, ConstParamTy_};
trait Owner {
type const C<const N: u32>: u32;
type const K<const N: u32>: u32;
// #[type_const]
// const Q<T>: Maybe<T>;
type const Q<T: ConstParamTy_>: Maybe<T>;
}
impl Owner for () {
type const C<const N: u32>: u32 = N;
type const K<const N: u32>: u32 = const { 99 + 1 };
// FIXME(mgca): re-enable once we properly support ctors and generics on paths
// #[type_const]
// const Q<T>: Maybe<T> = Maybe::Nothing;
type const Q<T: ConstParamTy_>: Maybe<T> = Maybe::Nothing::<T>;
}
fn take0<const N: u32>(_: impl Owner<C<N> = { N }>) {}
fn take1(_: impl Owner<K<99> = 100>) {}
// FIXME(mgca): re-enable once we properly support ctors and generics on paths
// fn take2(_: impl Owner<Q<()> = { Maybe::Just(()) }>) {}
fn take2(_: impl Owner<Q<()> = { Maybe::Just::<()>(()) }>) {}
fn main() {
take0::<128>(());
take1(());
}
#[derive(PartialEq, Eq, std::marker::ConstParamTy)]
#[derive(PartialEq, Eq, ConstParamTy)]
enum Maybe<T> {
Nothing,
Just(T),