From ca718ffd2dc27f3d45a1f0a80ba405bd0bfa8ecb Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 18 Dec 2023 07:49:46 +0100 Subject: [PATCH 01/10] track the source of nested goals --- compiler/rustc_middle/src/traits/solve.rs | 18 +++++ .../rustc_middle/src/traits/solve/inspect.rs | 6 +- .../src/traits/solve/inspect/format.rs | 8 ++- .../src/solve/alias_relate.rs | 16 +++-- .../src/solve/assembly/mod.rs | 23 ++++--- .../src/solve/eval_ctxt/mod.rs | 34 ++++++---- .../src/solve/inspect/analyse.rs | 2 +- .../src/solve/inspect/build.rs | 16 +++-- .../rustc_trait_selection/src/solve/mod.rs | 22 ++++--- .../src/solve/normalizes_to/inherent.rs | 10 ++- .../src/solve/normalizes_to/mod.rs | 21 ++++-- .../src/solve/normalizes_to/weak_types.rs | 5 +- .../src/solve/project_goals.rs | 7 +- .../src/solve/trait_goals.rs | 66 ++++++++++++------- 14 files changed, 173 insertions(+), 81 deletions(-) diff --git a/compiler/rustc_middle/src/traits/solve.rs b/compiler/rustc_middle/src/traits/solve.rs index 27a1e64a78bd5..dd5b57eed46b1 100644 --- a/compiler/rustc_middle/src/traits/solve.rs +++ b/compiler/rustc_middle/src/traits/solve.rs @@ -233,6 +233,24 @@ impl<'tcx> TypeVisitable> for PredefinedOpaques<'tcx> { } } +/// Why a specific goal has to be proven. +/// +/// This is necessary as we treat nested goals different depending on +/// their source. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum GoalSource { + Misc, + /// We're proving a where-bound of an impl. + /// + /// FIXME(-Znext-solver=coinductive): Explain how and why this + /// changes whether cycles are coinductive. + /// + /// This also impacts whether we erase constraints on overflow. + /// Erasing constraints is generally very useful for perf and also + /// results in better error messages by avoiding spurious errors. + ImplWhereBound, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, HashStable)] pub enum IsNormalizesToHack { Yes, diff --git a/compiler/rustc_middle/src/traits/solve/inspect.rs b/compiler/rustc_middle/src/traits/solve/inspect.rs index 7883cd338befd..77d112d0afcc8 100644 --- a/compiler/rustc_middle/src/traits/solve/inspect.rs +++ b/compiler/rustc_middle/src/traits/solve/inspect.rs @@ -19,8 +19,8 @@ //! [canonicalized]: https://rustc-dev-guide.rust-lang.org/solve/canonicalization.html use super::{ - CandidateSource, Canonical, CanonicalInput, Certainty, Goal, IsNormalizesToHack, NoSolution, - QueryInput, QueryResult, + CandidateSource, Canonical, CanonicalInput, Certainty, Goal, GoalSource, IsNormalizesToHack, + NoSolution, QueryInput, QueryResult, }; use crate::{infer::canonical::CanonicalVarValues, ty}; use format::ProofTreeFormatter; @@ -115,7 +115,7 @@ impl Debug for Probe<'_> { pub enum ProbeStep<'tcx> { /// We added a goal to the `EvalCtxt` which will get proven /// the next time `EvalCtxt::try_evaluate_added_goals` is called. - AddGoal(CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>), + AddGoal(GoalSource, CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>), /// The inside of a `EvalCtxt::try_evaluate_added_goals` call. EvaluateGoals(AddedGoalsEvaluation<'tcx>), /// A call to `probe` while proving the current goal. This is diff --git a/compiler/rustc_middle/src/traits/solve/inspect/format.rs b/compiler/rustc_middle/src/traits/solve/inspect/format.rs index ab9e0283918d9..4e2207ed52302 100644 --- a/compiler/rustc_middle/src/traits/solve/inspect/format.rs +++ b/compiler/rustc_middle/src/traits/solve/inspect/format.rs @@ -123,7 +123,13 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> { self.nested(|this| { for step in &probe.steps { match step { - ProbeStep::AddGoal(goal) => writeln!(this.f, "ADDED GOAL: {goal:?}")?, + ProbeStep::AddGoal(source, goal) => { + let source = match source { + GoalSource::Misc => "misc", + GoalSource::ImplWhereBound => "impl where-bound", + }; + writeln!(this.f, "ADDED GOAL ({source}): {goal:?}")? + } ProbeStep::EvaluateGoals(eval) => this.format_added_goals_evaluation(eval)?, ProbeStep::NestedProbe(probe) => this.format_probe(probe)?, ProbeStep::CommitIfOkStart => writeln!(this.f, "COMMIT_IF_OK START")?, diff --git a/compiler/rustc_trait_selection/src/solve/alias_relate.rs b/compiler/rustc_trait_selection/src/solve/alias_relate.rs index 2e99854ddc676..626569fb40fc0 100644 --- a/compiler/rustc_trait_selection/src/solve/alias_relate.rs +++ b/compiler/rustc_trait_selection/src/solve/alias_relate.rs @@ -11,7 +11,7 @@ //! * bidirectional-normalizes-to: If `A` and `B` are both projections, and both //! may apply, then we can compute the "intersection" of both normalizes-to by //! performing them together. This is used specifically to resolve ambiguities. -use super::EvalCtxt; +use super::{EvalCtxt, GoalSource}; use rustc_infer::infer::DefineOpaqueTypes; use rustc_infer::traits::query::NoSolution; use rustc_middle::traits::solve::{Certainty, Goal, QueryResult}; @@ -89,11 +89,10 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { ty::TermKind::Const(_) => { if let Some(alias) = term.to_alias_ty(self.tcx()) { let term = self.next_term_infer_of_kind(term); - self.add_goal(Goal::new( - self.tcx(), - param_env, - ty::NormalizesTo { alias, term }, - )); + self.add_goal( + GoalSource::Misc, + Goal::new(self.tcx(), param_env, ty::NormalizesTo { alias, term }), + ); self.try_evaluate_added_goals()?; Ok(Some(self.resolve_vars_if_possible(term))) } else { @@ -109,7 +108,10 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { opaque: ty::AliasTy<'tcx>, term: ty::Term<'tcx>, ) -> QueryResult<'tcx> { - self.add_goal(Goal::new(self.tcx(), param_env, ty::NormalizesTo { alias: opaque, term })); + self.add_goal( + GoalSource::Misc, + Goal::new(self.tcx(), param_env, ty::NormalizesTo { alias: opaque, term }), + ); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } diff --git a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs index 62d62bdfd114d..81a766f24b048 100644 --- a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs @@ -1,6 +1,7 @@ //! Code shared by trait and projection goals for candidate assembly. use super::{EvalCtxt, SolverMode}; +use crate::solve::GoalSource; use crate::traits::coherence; use rustc_hir::def_id::DefId; use rustc_infer::traits::query::NoSolution; @@ -62,7 +63,9 @@ pub(super) trait GoalKind<'tcx>: requirements: impl IntoIterator>>, ) -> QueryResult<'tcx> { Self::probe_and_match_goal_against_assumption(ecx, goal, assumption, |ecx| { - ecx.add_goals(requirements); + // FIXME(-Znext-solver=coinductive): check whether this should be + // `GoalSource::ImplWhereBound` for any caller. + ecx.add_goals(GoalSource::Misc, requirements); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) } @@ -94,12 +97,16 @@ pub(super) trait GoalKind<'tcx>: let ty::Dynamic(bounds, _, _) = *goal.predicate.self_ty().kind() else { bug!("expected object type in `consider_object_bound_candidate`"); }; - ecx.add_goals(structural_traits::predicates_for_object_candidate( - ecx, - goal.param_env, - goal.predicate.trait_ref(tcx), - bounds, - )); + // FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`? + ecx.add_goals( + GoalSource::Misc, + structural_traits::predicates_for_object_candidate( + ecx, + goal.param_env, + goal.predicate.trait_ref(tcx), + bounds, + ), + ); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) } @@ -364,7 +371,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { let normalized_ty = ecx.next_ty_infer(); let normalizes_to_goal = goal.with(tcx, ty::NormalizesTo { alias, term: normalized_ty.into() }); - ecx.add_goal(normalizes_to_goal); + ecx.add_goal(GoalSource::Misc, normalizes_to_goal); if let Err(NoSolution) = ecx.try_evaluate_added_goals() { debug!("self type normalization failed"); return vec![]; diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs index cafb858794afb..7f5e565465700 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs @@ -23,14 +23,15 @@ use rustc_middle::ty::{ use rustc_session::config::DumpSolverProofTree; use rustc_span::DUMMY_SP; use std::io::Write; +use std::iter; use std::ops::ControlFlow; use crate::traits::vtable::{count_own_vtable_entries, prepare_vtable_segments, VtblSegment}; use super::inspect::ProofTreeBuilder; -use super::SolverMode; use super::{search_graph, GoalEvaluationKind}; use super::{search_graph::SearchGraph, Goal}; +use super::{GoalSource, SolverMode}; pub use select::InferCtxtSelectExt; mod canonical; @@ -105,7 +106,7 @@ pub(super) struct NestedGoals<'tcx> { /// can be unsound with more powerful coinduction in the future. pub(super) normalizes_to_hack_goal: Option>>, /// The rest of the goals which have not yet processed or remain ambiguous. - pub(super) goals: Vec>>, + pub(super) goals: Vec<(GoalSource, Goal<'tcx, ty::Predicate<'tcx>>)>, } impl<'tcx> NestedGoals<'tcx> { @@ -439,7 +440,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { } else { let kind = self.infcx.instantiate_binder_with_placeholders(kind); let goal = goal.with(self.tcx(), ty::Binder::dummy(kind)); - self.add_goal(goal); + self.add_goal(GoalSource::Misc, goal); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } } @@ -488,6 +489,13 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { let mut goals = core::mem::replace(&mut self.nested_goals, NestedGoals::new()); self.inspect.evaluate_added_goals_loop_start(); + + fn with_misc_source<'tcx>( + it: impl IntoIterator>>, + ) -> impl Iterator>)> { + iter::zip(iter::repeat(GoalSource::Misc), it) + } + // If this loop did not result in any progress, what's our final certainty. let mut unchanged_certainty = Some(Certainty::Yes); if let Some(goal) = goals.normalizes_to_hack_goal.take() { @@ -503,7 +511,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::Yes }, unconstrained_goal, )?; - self.nested_goals.goals.extend(instantiate_goals); + self.nested_goals.goals.extend(with_misc_source(instantiate_goals)); // Finally, equate the goal's RHS with the unconstrained var. // We put the nested goals from this into goals instead of @@ -512,7 +520,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { // matters in practice, though. let eq_goals = self.eq_and_get_goals(goal.param_env, goal.predicate.term, unconstrained_rhs)?; - goals.goals.extend(eq_goals); + goals.goals.extend(with_misc_source(eq_goals)); // We only look at the `projection_ty` part here rather than // looking at the "has changed" return from evaluate_goal, @@ -533,12 +541,12 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { } } - for goal in goals.goals.drain(..) { + for (source, goal) in goals.goals.drain(..) { let (has_changed, certainty, instantiate_goals) = self.evaluate_goal( GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::No }, goal, )?; - self.nested_goals.goals.extend(instantiate_goals); + self.nested_goals.goals.extend(with_misc_source(instantiate_goals)); if has_changed { unchanged_certainty = None; } @@ -546,7 +554,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { match certainty { Certainty::Yes => {} Certainty::Maybe(_) => { - self.nested_goals.goals.push(goal); + self.nested_goals.goals.push((source, goal)); unchanged_certainty = unchanged_certainty.map(|c| c.unify_with(certainty)); } } @@ -670,7 +678,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { .at(&ObligationCause::dummy(), param_env) .eq(DefineOpaqueTypes::No, lhs, rhs) .map(|InferOk { value: (), obligations }| { - self.add_goals(obligations.into_iter().map(|o| o.into())); + self.add_goals(GoalSource::Misc, obligations.into_iter().map(|o| o.into())); }) .map_err(|e| { debug!(?e, "failed to equate"); @@ -689,7 +697,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { .at(&ObligationCause::dummy(), param_env) .sub(DefineOpaqueTypes::No, sub, sup) .map(|InferOk { value: (), obligations }| { - self.add_goals(obligations.into_iter().map(|o| o.into())); + self.add_goals(GoalSource::Misc, obligations.into_iter().map(|o| o.into())); }) .map_err(|e| { debug!(?e, "failed to subtype"); @@ -709,7 +717,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { .at(&ObligationCause::dummy(), param_env) .relate(DefineOpaqueTypes::No, lhs, variance, rhs) .map(|InferOk { value: (), obligations }| { - self.add_goals(obligations.into_iter().map(|o| o.into())); + self.add_goals(GoalSource::Misc, obligations.into_iter().map(|o| o.into())); }) .map_err(|e| { debug!(?e, "failed to relate"); @@ -842,7 +850,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { true, &mut obligations, )?; - self.add_goals(obligations.into_iter().map(|o| o.into())); + self.add_goals(GoalSource::Misc, obligations.into_iter().map(|o| o.into())); Ok(()) } @@ -862,7 +870,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { hidden_ty, &mut obligations, ); - self.add_goals(obligations.into_iter().map(|o| o.into())); + self.add_goals(GoalSource::Misc, obligations.into_iter().map(|o| o.into())); } // Do something for each opaque/hidden pair defined with `def_id` in the diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs index a287582dca7b6..6db53d6ddc4d0 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -119,7 +119,7 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { ) { for step in &probe.steps { match step { - &inspect::ProbeStep::AddGoal(goal) => nested_goals.push(goal), + &inspect::ProbeStep::AddGoal(_source, goal) => nested_goals.push(goal), inspect::ProbeStep::NestedProbe(ref probe) => { // Nested probes have to prove goals added in their parent // but do not leak them, so we truncate the added goals diff --git a/compiler/rustc_trait_selection/src/solve/inspect/build.rs b/compiler/rustc_trait_selection/src/solve/inspect/build.rs index c857aae572d1b..d8caef5b03f01 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/build.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/build.rs @@ -7,7 +7,7 @@ use std::mem; use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::solve::{ - CanonicalInput, Certainty, Goal, IsNormalizesToHack, QueryInput, QueryResult, + CanonicalInput, Certainty, Goal, GoalSource, IsNormalizesToHack, QueryInput, QueryResult, }; use rustc_middle::ty::{self, TyCtxt}; use rustc_session::config::DumpSolverProofTree; @@ -216,7 +216,7 @@ impl<'tcx> WipProbe<'tcx> { #[derive(Eq, PartialEq, Debug)] enum WipProbeStep<'tcx> { - AddGoal(inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>), + AddGoal(GoalSource, inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>), EvaluateGoals(WipAddedGoalsEvaluation<'tcx>), NestedProbe(WipProbe<'tcx>), CommitIfOkStart, @@ -226,7 +226,7 @@ enum WipProbeStep<'tcx> { impl<'tcx> WipProbeStep<'tcx> { fn finalize(self) -> inspect::ProbeStep<'tcx> { match self { - WipProbeStep::AddGoal(goal) => inspect::ProbeStep::AddGoal(goal), + WipProbeStep::AddGoal(source, goal) => inspect::ProbeStep::AddGoal(source, goal), WipProbeStep::EvaluateGoals(eval) => inspect::ProbeStep::EvaluateGoals(eval.finalize()), WipProbeStep::NestedProbe(probe) => inspect::ProbeStep::NestedProbe(probe.finalize()), WipProbeStep::CommitIfOkStart => inspect::ProbeStep::CommitIfOkStart, @@ -428,7 +428,11 @@ impl<'tcx> ProofTreeBuilder<'tcx> { } } - pub fn add_goal(ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, ty::Predicate<'tcx>>) { + pub fn add_goal( + ecx: &mut EvalCtxt<'_, 'tcx>, + source: GoalSource, + goal: Goal<'tcx, ty::Predicate<'tcx>>, + ) { // Can't use `if let Some(this) = ecx.inspect.as_mut()` here because // we have to immutably use the `EvalCtxt` for `make_canonical_state`. if ecx.inspect.is_noop() { @@ -442,7 +446,9 @@ impl<'tcx> ProofTreeBuilder<'tcx> { evaluation: WipProbe { steps, .. }, .. }) - | DebugSolver::Probe(WipProbe { steps, .. }) => steps.push(WipProbeStep::AddGoal(goal)), + | DebugSolver::Probe(WipProbe { steps, .. }) => { + steps.push(WipProbeStep::AddGoal(source, goal)) + } s => unreachable!("tried to add {goal:?} to {s:?}"), } } diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs index 1e58106e3533c..2f3111a2414d5 100644 --- a/compiler/rustc_trait_selection/src/solve/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/mod.rs @@ -19,8 +19,8 @@ use rustc_infer::infer::DefineOpaqueTypes; use rustc_infer::traits::query::NoSolution; use rustc_middle::infer::canonical::CanonicalVarInfos; use rustc_middle::traits::solve::{ - CanonicalResponse, Certainty, ExternalConstraintsData, Goal, IsNormalizesToHack, QueryResult, - Response, + CanonicalResponse, Certainty, ExternalConstraintsData, Goal, GoalSource, IsNormalizesToHack, + QueryResult, Response, }; use rustc_middle::ty::{self, OpaqueTypeKey, Ty, TyCtxt, UniverseIndex}; use rustc_middle::ty::{ @@ -157,7 +157,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { ) -> QueryResult<'tcx> { match self.well_formed_goals(goal.param_env, goal.predicate) { Some(goals) => { - self.add_goals(goals); + self.add_goals(GoalSource::Misc, goals); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } None => self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS), @@ -223,15 +223,19 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { } #[instrument(level = "debug", skip(self))] - fn add_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) { - inspect::ProofTreeBuilder::add_goal(self, goal); - self.nested_goals.goals.push(goal); + fn add_goal(&mut self, source: GoalSource, goal: Goal<'tcx, ty::Predicate<'tcx>>) { + inspect::ProofTreeBuilder::add_goal(self, source, goal); + self.nested_goals.goals.push((source, goal)); } #[instrument(level = "debug", skip(self, goals))] - fn add_goals(&mut self, goals: impl IntoIterator>>) { + fn add_goals( + &mut self, + source: GoalSource, + goals: impl IntoIterator>>, + ) { for goal in goals { - self.add_goal(goal); + self.add_goal(source, goal); } } @@ -335,7 +339,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { param_env, ty::NormalizesTo { alias, term: normalized_ty.into() }, ); - this.add_goal(normalizes_to_goal); + this.add_goal(GoalSource::Misc, normalizes_to_goal); this.try_evaluate_added_goals()?; let ty = this.resolve_vars_if_possible(normalized_ty); Ok(this.try_normalize_ty_recur(param_env, define_opaque_types, depth + 1, ty)) diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/inherent.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/inherent.rs index c3b8ae9a94362..b2dff9b48ff38 100644 --- a/compiler/rustc_trait_selection/src/solve/normalizes_to/inherent.rs +++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/inherent.rs @@ -4,10 +4,10 @@ //! 1. instantiate substs, //! 2. equate the self type, and //! 3. instantiate and register where clauses. -use rustc_middle::traits::solve::{Certainty, Goal, QueryResult}; +use rustc_middle::traits::solve::{Certainty, Goal, GoalSource, QueryResult}; use rustc_middle::ty; -use super::EvalCtxt; +use crate::solve::EvalCtxt; impl<'tcx> EvalCtxt<'_, 'tcx> { pub(super) fn normalize_inherent_associated_type( @@ -38,7 +38,13 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { .expect("expected goal term to be fully unconstrained"); // Check both where clauses on the impl and IAT + // + // FIXME(-Znext-solver=coinductive): I think this should be split + // and we tag the impl bounds with `GoalSource::ImplWhereBound`? + // Right not this includes both the impl and the assoc item where bounds, + // and I don't think the assoc item where-bounds are allowed to be coinductive. self.add_goals( + GoalSource::Misc, tcx.predicates_of(inherent.def_id) .instantiate(tcx, inherent_substs) .into_iter() diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs index 980ef86236656..0e9656a1e1844 100644 --- a/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/mod.rs @@ -1,7 +1,7 @@ use crate::traits::{check_args_compatible, specialization_graph}; use super::assembly::{self, structural_traits, Candidate}; -use super::EvalCtxt; +use super::{EvalCtxt, GoalSource}; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_hir::LangItem; @@ -128,6 +128,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> { // Add GAT where clauses from the trait's definition ecx.add_goals( + GoalSource::Misc, tcx.predicates_of(goal.predicate.def_id()) .instantiate_own(tcx, goal.predicate.alias.args) .map(|(pred, _)| goal.with(tcx, pred)), @@ -169,10 +170,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> { .predicates .into_iter() .map(|pred| goal.with(tcx, pred)); - ecx.add_goals(where_clause_bounds); + ecx.add_goals(GoalSource::ImplWhereBound, where_clause_bounds); // Add GAT where clauses from the trait's definition ecx.add_goals( + GoalSource::Misc, tcx.predicates_of(goal.predicate.def_id()) .instantiate_own(tcx, goal.predicate.alias.args) .map(|(pred, _)| goal.with(tcx, pred)), @@ -413,7 +415,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> { DUMMY_SP, [ty::GenericArg::from(goal.predicate.self_ty())], ); - ecx.add_goal(goal.with(tcx, sized_predicate)); + // FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`? + ecx.add_goal(GoalSource::Misc, goal.with(tcx, sized_predicate)); tcx.types.unit } @@ -421,7 +424,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> { None => tcx.types.unit, Some(field_def) => { let self_ty = field_def.ty(tcx, args); - ecx.add_goal(goal.with(tcx, goal.predicate.with_self_ty(tcx, self_ty))); + // FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`? + ecx.add_goal( + GoalSource::Misc, + goal.with(tcx, goal.predicate.with_self_ty(tcx, self_ty)), + ); return ecx .evaluate_added_goals_and_make_canonical_response(Certainty::Yes); } @@ -431,7 +438,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> { ty::Tuple(elements) => match elements.last() { None => tcx.types.unit, Some(&self_ty) => { - ecx.add_goal(goal.with(tcx, goal.predicate.with_self_ty(tcx, self_ty))); + // FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`? + ecx.add_goal( + GoalSource::Misc, + goal.with(tcx, goal.predicate.with_self_ty(tcx, self_ty)), + ); return ecx .evaluate_added_goals_and_make_canonical_response(Certainty::Yes); } diff --git a/compiler/rustc_trait_selection/src/solve/normalizes_to/weak_types.rs b/compiler/rustc_trait_selection/src/solve/normalizes_to/weak_types.rs index 8d2bbec6d8b30..6d5728797d1ef 100644 --- a/compiler/rustc_trait_selection/src/solve/normalizes_to/weak_types.rs +++ b/compiler/rustc_trait_selection/src/solve/normalizes_to/weak_types.rs @@ -3,10 +3,10 @@ //! //! Since a weak alias is not ambiguous, this just computes the `type_of` of //! the alias and registers the where-clauses of the type alias. -use rustc_middle::traits::solve::{Certainty, Goal, QueryResult}; +use rustc_middle::traits::solve::{Certainty, Goal, GoalSource, QueryResult}; use rustc_middle::ty; -use super::EvalCtxt; +use crate::solve::EvalCtxt; impl<'tcx> EvalCtxt<'_, 'tcx> { pub(super) fn normalize_weak_type( @@ -22,6 +22,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { // Check where clauses self.add_goals( + GoalSource::Misc, tcx.predicates_of(weak_ty.def_id) .instantiate(tcx, weak_ty.args) .predicates diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index d0e92a54cebbc..30ae385a8a0b7 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -1,3 +1,5 @@ +use crate::solve::GoalSource; + use super::EvalCtxt; use rustc_middle::traits::solve::{Certainty, Goal, QueryResult}; use rustc_middle::ty::{self, ProjectionPredicate}; @@ -22,14 +24,15 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { ) .into(), }; - self.add_goal(goal.with( + let goal = goal.with( tcx, ty::PredicateKind::AliasRelate( projection_term, goal.predicate.term, ty::AliasRelationDirection::Equate, ), - )); + ); + self.add_goal(GoalSource::Misc, goal); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } } diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs index deb50e6aefdb3..ac3ffd2d6c2c9 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -1,7 +1,7 @@ //! Dealing with trait goals, i.e. `T: Trait<'a, U>`. use super::assembly::{self, structural_traits, Candidate}; -use super::{EvalCtxt, SolverMode}; +use super::{EvalCtxt, GoalSource, SolverMode}; use rustc_hir::def_id::DefId; use rustc_hir::{LangItem, Movability}; use rustc_infer::traits::query::NoSolution; @@ -72,7 +72,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { .predicates .into_iter() .map(|pred| goal.with(tcx, pred)); - ecx.add_goals(where_clause_bounds); + ecx.add_goals(GoalSource::ImplWhereBound, where_clause_bounds); ecx.evaluate_added_goals_and_make_canonical_response(maximal_certainty) }) @@ -172,7 +172,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { let nested_obligations = tcx .predicates_of(goal.predicate.def_id()) .instantiate(tcx, goal.predicate.trait_ref.args); - ecx.add_goals(nested_obligations.predicates.into_iter().map(|p| goal.with(tcx, p))); + // FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`? + ecx.add_goals( + GoalSource::Misc, + nested_obligations.predicates.into_iter().map(|p| goal.with(tcx, p)), + ); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) } @@ -512,17 +516,23 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { // Check that the type implements all of the predicates of the trait object. // (i.e. the principal, all of the associated types match, and any auto traits) - ecx.add_goals(b_data.iter().map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty)))); + ecx.add_goals( + GoalSource::ImplWhereBound, + b_data.iter().map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty))), + ); // The type must be `Sized` to be unsized. if let Some(sized_def_id) = tcx.lang_items().sized_trait() { - ecx.add_goal(goal.with(tcx, ty::TraitRef::new(tcx, sized_def_id, [a_ty]))); + ecx.add_goal( + GoalSource::ImplWhereBound, + goal.with(tcx, ty::TraitRef::new(tcx, sized_def_id, [a_ty])), + ); } else { return Err(NoSolution); } // The type must outlive the lifetime of the `dyn` we're unsizing into. - ecx.add_goal(goal.with(tcx, ty::OutlivesPredicate(a_ty, b_region))); + ecx.add_goal(GoalSource::Misc, goal.with(tcx, ty::OutlivesPredicate(a_ty, b_region))); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) } @@ -749,11 +759,14 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { } // Also require that a_ty's lifetime outlives b_ty's lifetime. - self.add_goal(Goal::new( - self.tcx(), - param_env, - ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)), - )); + self.add_goal( + GoalSource::ImplWhereBound, + Goal::new( + self.tcx(), + param_env, + ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)), + ), + ); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } @@ -826,14 +839,17 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { // Finally, we require that `TailA: Unsize` for the tail field // types. self.eq(goal.param_env, unsized_a_ty, b_ty)?; - self.add_goal(goal.with( - tcx, - ty::TraitRef::new( + self.add_goal( + GoalSource::ImplWhereBound, + goal.with( tcx, - tcx.lang_items().unsize_trait().unwrap(), - [a_tail_ty, b_tail_ty], + ty::TraitRef::new( + tcx, + tcx.lang_items().unsize_trait().unwrap(), + [a_tail_ty, b_tail_ty], + ), ), - )); + ); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } @@ -865,14 +881,17 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { self.eq(goal.param_env, unsized_a_ty, b_ty)?; // Similar to ADTs, require that we can unsize the tail. - self.add_goal(goal.with( - tcx, - ty::TraitRef::new( + self.add_goal( + GoalSource::ImplWhereBound, + goal.with( tcx, - tcx.lang_items().unsize_trait().unwrap(), - [a_last_ty, b_last_ty], + ty::TraitRef::new( + tcx, + tcx.lang_items().unsize_trait().unwrap(), + [a_last_ty, b_last_ty], + ), ), - )); + ); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } @@ -981,6 +1000,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { ) -> QueryResult<'tcx> { self.probe_misc_candidate("constituent tys").enter(|ecx| { ecx.add_goals( + GoalSource::ImplWhereBound, constituent_tys(ecx, goal.predicate.self_ty())? .into_iter() .map(|ty| goal.with(ecx.tcx(), goal.predicate.with_self_ty(ecx.tcx(), ty))) From 4a38442c900bd859ecece400454d5abc56647b64 Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 18 Dec 2023 08:31:00 +0100 Subject: [PATCH 02/10] dont discard overflow from normalizes-to goals --- compiler/rustc_middle/src/traits/solve.rs | 3 ++ .../src/solve/eval_ctxt/canonical.rs | 14 ------ .../src/solve/eval_ctxt/mod.rs | 43 +++++++++++++++---- .../src/solve/search_graph.rs | 10 +++++ .../traits/issue-90662-projection-caching.rs | 10 ++++- ...cursion-limit-normalizes-to-constraints.rs | 25 +++++++++++ 6 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 tests/ui/traits/next-solver/overflow/recursion-limit-normalizes-to-constraints.rs diff --git a/compiler/rustc_middle/src/traits/solve.rs b/compiler/rustc_middle/src/traits/solve.rs index dd5b57eed46b1..048df367bd65c 100644 --- a/compiler/rustc_middle/src/traits/solve.rs +++ b/compiler/rustc_middle/src/traits/solve.rs @@ -248,6 +248,9 @@ pub enum GoalSource { /// This also impacts whether we erase constraints on overflow. /// Erasing constraints is generally very useful for perf and also /// results in better error messages by avoiding spurious errors. + /// We do not erase overflow constraints in `normalizes-to` goals unless + /// they are from an impl where-clause. This is necessary due to + /// backwards compatability, cc trait-system-refactor-initiatitive#70. ImplWhereBound, } diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs index 7457ba837f505..ecdae2521b933 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs @@ -94,20 +94,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { ); let certainty = certainty.unify_with(goals_certainty); - if let Certainty::OVERFLOW = certainty { - // If we have overflow, it's probable that we're substituting a type - // into itself infinitely and any partial substitutions in the query - // response are probably not useful anyways, so just return an empty - // query response. - // - // This may prevent us from potentially useful inference, e.g. - // 2 candidates, one ambiguous and one overflow, which both - // have the same inference constraints. - // - // Changing this to retain some constraints in the future - // won't be a breaking change, so this is good enough for now. - return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Overflow)); - } let var_values = self.var_values; let external_constraints = self.compute_external_query_constraints()?; diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs index 7f5e565465700..ee0fc8139a22c 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs @@ -157,7 +157,7 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> { Option>, ) { EvalCtxt::enter_root(self, generate_proof_tree, |ecx| { - ecx.evaluate_goal(GoalEvaluationKind::Root, goal) + ecx.evaluate_goal(GoalEvaluationKind::Root, GoalSource::Misc, goal) }) } } @@ -335,6 +335,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { fn evaluate_goal( &mut self, goal_evaluation_kind: GoalEvaluationKind, + source: GoalSource, goal: Goal<'tcx, ty::Predicate<'tcx>>, ) -> Result<(bool, Certainty, Vec>>), NoSolution> { let (orig_values, canonical_goal) = self.canonicalize_goal(goal); @@ -354,13 +355,13 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { Ok(response) => response, }; - let has_changed = !canonical_response.value.var_values.is_identity_modulo_regions() - || !canonical_response.value.external_constraints.opaque_types.is_empty(); - let (certainty, nested_goals) = match self.instantiate_and_apply_query_response( - goal.param_env, - orig_values, - canonical_response, - ) { + let (certainty, has_changed, nested_goals) = match self + .instantiate_response_discarding_overflow( + goal.param_env, + source, + orig_values, + canonical_response, + ) { Err(e) => { self.inspect.goal_evaluation(goal_evaluation); return Err(e); @@ -387,6 +388,30 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { Ok((has_changed, certainty, nested_goals)) } + fn instantiate_response_discarding_overflow( + &mut self, + param_env: ty::ParamEnv<'tcx>, + source: GoalSource, + original_values: Vec>, + response: CanonicalResponse<'tcx>, + ) -> Result<(Certainty, bool, Vec>>), NoSolution> { + let keep_overflow_constraints = || { + self.search_graph.current_goal_is_normalizes_to() + && source != GoalSource::ImplWhereBound + }; + + if response.value.certainty == Certainty::OVERFLOW && !keep_overflow_constraints() { + Ok((Certainty::OVERFLOW, false, Vec::new())) + } else { + let has_changed = !response.value.var_values.is_identity_modulo_regions() + || !response.value.external_constraints.opaque_types.is_empty(); + + let (certainty, nested_goals) = + self.instantiate_and_apply_query_response(param_env, original_values, response)?; + Ok((certainty, has_changed, nested_goals)) + } + } + fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> { let Goal { param_env, predicate } = goal; let kind = predicate.kind(); @@ -509,6 +534,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { let (_, certainty, instantiate_goals) = self.evaluate_goal( GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::Yes }, + GoalSource::Misc, unconstrained_goal, )?; self.nested_goals.goals.extend(with_misc_source(instantiate_goals)); @@ -544,6 +570,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { for (source, goal) in goals.goals.drain(..) { let (has_changed, certainty, instantiate_goals) = self.evaluate_goal( GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::No }, + source, goal, )?; self.nested_goals.goals.extend(with_misc_source(instantiate_goals)); diff --git a/compiler/rustc_trait_selection/src/solve/search_graph.rs b/compiler/rustc_trait_selection/src/solve/search_graph.rs index 2a08b80e02aa9..2a161c2d95644 100644 --- a/compiler/rustc_trait_selection/src/solve/search_graph.rs +++ b/compiler/rustc_trait_selection/src/solve/search_graph.rs @@ -8,6 +8,7 @@ use rustc_index::IndexVec; use rustc_middle::dep_graph::dep_kinds; use rustc_middle::traits::solve::CacheData; use rustc_middle::traits::solve::{CanonicalInput, Certainty, EvaluationCache, QueryResult}; +use rustc_middle::ty; use rustc_middle::ty::TyCtxt; use rustc_session::Limit; use std::collections::hash_map::Entry; @@ -111,6 +112,15 @@ impl<'tcx> SearchGraph<'tcx> { self.stack.is_empty() } + pub(super) fn current_goal_is_normalizes_to(&self) -> bool { + self.stack.raw.last().map_or(false, |e| { + matches!( + e.input.value.goal.predicate.kind().skip_binder(), + ty::PredicateKind::NormalizesTo(..) + ) + }) + } + /// Returns the remaining depth allowed for nested goals. /// /// This is generally simply one less than the current depth. diff --git a/tests/ui/traits/issue-90662-projection-caching.rs b/tests/ui/traits/issue-90662-projection-caching.rs index 879f30071bfdb..e08ab53fbb058 100644 --- a/tests/ui/traits/issue-90662-projection-caching.rs +++ b/tests/ui/traits/issue-90662-projection-caching.rs @@ -1,7 +1,15 @@ +// revisions: old next +//[next] compile-flags: -Znext-solver=coherence // check-pass // Regression test for issue #90662 -// Tests that projection caching does not cause a spurious error +// Tests that projection caching does not cause a spurious error. +// Coherence relies on the following overflowing goal to still constrain +// `?0` to `dyn Service`. +// +// Projection(>::Interface. ?0) +// +// cc https://github.com/rust-lang/trait-system-refactor-initiative/issues/70. trait HasProvider {} trait Provider { diff --git a/tests/ui/traits/next-solver/overflow/recursion-limit-normalizes-to-constraints.rs b/tests/ui/traits/next-solver/overflow/recursion-limit-normalizes-to-constraints.rs new file mode 100644 index 0000000000000..03ef93dc233df --- /dev/null +++ b/tests/ui/traits/next-solver/overflow/recursion-limit-normalizes-to-constraints.rs @@ -0,0 +1,25 @@ +// compile-flags: -Znext-solver=coherence +// check-pass + +// A regression test for trait-system-refactor-initiative#70. + +trait Trait { + type Assoc; +} + +struct W(*mut T); +impl Trait for W> +where + W: Trait, +{ + type Assoc = (); +} + +trait NoOverlap {} +impl> NoOverlap for T {} +// `Projection( as Trait>::Assoc, u32)` should result in error even +// though applying the impl results in overflow. This is necessary to match +// the behavior of the old solver. +impl NoOverlap for W {} + +fn main() {} From 1a83c5b55bdad84baca69d121d9c3e6f357e8570 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Mon, 18 Dec 2023 19:52:25 +0000 Subject: [PATCH 03/10] Add function ABI and type layout to StableMIR This change introduces a new module to StableMIR named `abi` with information from `rustc_target::abi` and `rustc_abi`, that allow users to retrieve more low level information required to perform bit-precise analysis. The layout of a type can be retrieved via `Ty::layout`, and the instance ABI can be retrieved via `Instance::fn_abi()`. To properly handle errors while retrieve layout information, we had to implement a few layout related traits. --- .../rustc_smir/src/rustc_internal/internal.rs | 9 + compiler/rustc_smir/src/rustc_internal/mod.rs | 6 + compiler/rustc_smir/src/rustc_smir/context.rs | 75 ++++- .../rustc_smir/src/rustc_smir/convert/abi.rs | 229 ++++++++++++++ .../rustc_smir/src/rustc_smir/convert/mod.rs | 20 +- compiler/rustc_smir/src/rustc_smir/mod.rs | 13 + compiler/stable_mir/src/abi.rs | 286 ++++++++++++++++++ compiler/stable_mir/src/compiler_interface.rs | 10 + compiler/stable_mir/src/lib.rs | 1 + compiler/stable_mir/src/mir/mono.rs | 6 + compiler/stable_mir/src/ty.rs | 6 + .../stable-mir/check_allocation.rs | 1 - tests/ui-fulldeps/stable-mir/check_defs.rs | 6 +- tests/ui-fulldeps/stable-mir/check_layout.rs | 117 +++++++ 14 files changed, 760 insertions(+), 25 deletions(-) create mode 100644 compiler/rustc_smir/src/rustc_smir/convert/abi.rs create mode 100644 compiler/stable_mir/src/abi.rs create mode 100644 tests/ui-fulldeps/stable-mir/check_layout.rs diff --git a/compiler/rustc_smir/src/rustc_internal/internal.rs b/compiler/rustc_smir/src/rustc_internal/internal.rs index 632072003539f..bbc98af45c0d7 100644 --- a/compiler/rustc_smir/src/rustc_internal/internal.rs +++ b/compiler/rustc_smir/src/rustc_internal/internal.rs @@ -7,6 +7,7 @@ use crate::rustc_smir::Tables; use rustc_middle::ty::{self as rustc_ty, Ty as InternalTy}; use rustc_span::Symbol; +use stable_mir::abi::Layout; use stable_mir::mir::alloc::AllocId; use stable_mir::mir::mono::{Instance, MonoItem, StaticDef}; use stable_mir::mir::{Mutability, Safety}; @@ -460,6 +461,14 @@ impl<'tcx> RustcInternal<'tcx> for Span { } } +impl<'tcx> RustcInternal<'tcx> for Layout { + type T = rustc_target::abi::Layout<'tcx>; + + fn internal(&self, tables: &mut Tables<'tcx>) -> Self::T { + tables.layouts[*self] + } +} + impl<'tcx, T> RustcInternal<'tcx> for &T where T: RustcInternal<'tcx>, diff --git a/compiler/rustc_smir/src/rustc_internal/mod.rs b/compiler/rustc_smir/src/rustc_internal/mod.rs index c3db9b358e8b5..4bac98909add0 100644 --- a/compiler/rustc_smir/src/rustc_internal/mod.rs +++ b/compiler/rustc_smir/src/rustc_internal/mod.rs @@ -12,6 +12,7 @@ use rustc_middle::ty::TyCtxt; use rustc_span::def_id::{CrateNum, DefId}; use rustc_span::Span; use scoped_tls::scoped_thread_local; +use stable_mir::abi::Layout; use stable_mir::ty::IndexedVal; use stable_mir::Error; use std::cell::Cell; @@ -136,6 +137,10 @@ impl<'tcx> Tables<'tcx> { pub(crate) fn static_def(&mut self, did: DefId) -> stable_mir::mir::mono::StaticDef { stable_mir::mir::mono::StaticDef(self.create_def_id(did)) } + + pub(crate) fn layout_id(&mut self, layout: rustc_target::abi::Layout<'tcx>) -> Layout { + self.layouts.create_or_fetch(layout) + } } pub fn crate_num(item: &stable_mir::Crate) -> CrateNum { @@ -180,6 +185,7 @@ where types: IndexMap::default(), instances: IndexMap::default(), constants: IndexMap::default(), + layouts: IndexMap::default(), })); stable_mir::compiler_interface::run(&tables, || init(&tables, f)) } diff --git a/compiler/rustc_smir/src/rustc_smir/context.rs b/compiler/rustc_smir/src/rustc_smir/context.rs index 2361a04a6d7c2..621766c695e43 100644 --- a/compiler/rustc_smir/src/rustc_smir/context.rs +++ b/compiler/rustc_smir/src/rustc_smir/context.rs @@ -3,12 +3,19 @@ //! This trait is currently the main interface between the Rust compiler, //! and the `stable_mir` crate. +#![allow(rustc::usage_of_qualified_ty)] + +use rustc_abi::HasDataLayout; use rustc_middle::ty; +use rustc_middle::ty::layout::{ + FnAbiOf, FnAbiOfHelpers, HasParamEnv, HasTyCtxt, LayoutOf, LayoutOfHelpers, +}; use rustc_middle::ty::print::{with_forced_trimmed_paths, with_no_trimmed_paths}; use rustc_middle::ty::{ - GenericPredicates, Instance, ParamEnv, ScalarInt, TypeVisitableExt, ValTree, + GenericPredicates, Instance, List, ParamEnv, ScalarInt, TyCtxt, TypeVisitableExt, ValTree, }; use rustc_span::def_id::LOCAL_CRATE; +use stable_mir::abi::{FnAbi, Layout, LayoutShape}; use stable_mir::compiler_interface::Context; use stable_mir::mir::alloc::GlobalAlloc; use stable_mir::mir::mono::{InstanceDef, StaticDef}; @@ -280,7 +287,6 @@ impl<'tcx> Context for TablesWrapper<'tcx> { tables.tcx.mk_ty_from_kind(internal_kind).stable(&mut *tables) } - #[allow(rustc::usage_of_qualified_ty)] fn new_box_ty(&self, ty: stable_mir::ty::Ty) -> stable_mir::ty::Ty { let mut tables = self.0.borrow_mut(); let inner = ty.internal(&mut *tables); @@ -335,6 +341,12 @@ impl<'tcx> Context for TablesWrapper<'tcx> { instance.ty(tables.tcx, ParamEnv::reveal_all()).stable(&mut *tables) } + fn instance_abi(&self, def: InstanceDef) -> Result { + let mut tables = self.0.borrow_mut(); + let instance = tables.instances[def]; + Ok(tables.fn_abi_of_instance(instance, List::empty())?.stable(&mut *tables)) + } + fn instance_def_id(&self, def: InstanceDef) -> stable_mir::DefId { let mut tables = self.0.borrow_mut(); let def_id = tables.instances[def].def_id(); @@ -473,6 +485,65 @@ impl<'tcx> Context for TablesWrapper<'tcx> { ) } } + + fn ty_layout(&self, ty: Ty) -> Result { + let mut tables = self.0.borrow_mut(); + let ty = ty.internal(&mut *tables); + let layout = tables.layout_of(ty)?.layout; + Ok(layout.stable(&mut *tables)) + } + + fn layout_shape(&self, id: Layout) -> LayoutShape { + let mut tables = self.0.borrow_mut(); + id.internal(&mut *tables).0.stable(&mut *tables) + } } pub struct TablesWrapper<'tcx>(pub RefCell>); + +/// Implement error handling for extracting function ABI information. +impl<'tcx> FnAbiOfHelpers<'tcx> for Tables<'tcx> { + type FnAbiOfResult = Result<&'tcx rustc_target::abi::call::FnAbi<'tcx, ty::Ty<'tcx>>, Error>; + + #[inline] + fn handle_fn_abi_err( + &self, + err: ty::layout::FnAbiError<'tcx>, + _span: rustc_span::Span, + fn_abi_request: ty::layout::FnAbiRequest<'tcx>, + ) -> Error { + Error::new(format!("Failed to get ABI for `{fn_abi_request:?}`: {err:?}")) + } +} + +impl<'tcx> LayoutOfHelpers<'tcx> for Tables<'tcx> { + type LayoutOfResult = Result, Error>; + + #[inline] + fn handle_layout_err( + &self, + err: ty::layout::LayoutError<'tcx>, + _span: rustc_span::Span, + ty: ty::Ty<'tcx>, + ) -> Error { + Error::new(format!("Failed to get layout for `{ty}`: {err}")) + } +} + +impl<'tcx> HasParamEnv<'tcx> for Tables<'tcx> { + fn param_env(&self) -> ty::ParamEnv<'tcx> { + ty::ParamEnv::reveal_all() + } +} + +impl<'tcx> HasTyCtxt<'tcx> for Tables<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } +} + +impl<'tcx> HasDataLayout for Tables<'tcx> { + fn data_layout(&self) -> &rustc_abi::TargetDataLayout { + self.tcx.data_layout() + } +} diff --git a/compiler/rustc_smir/src/rustc_smir/convert/abi.rs b/compiler/rustc_smir/src/rustc_smir/convert/abi.rs new file mode 100644 index 0000000000000..46ae0929bcd4a --- /dev/null +++ b/compiler/rustc_smir/src/rustc_smir/convert/abi.rs @@ -0,0 +1,229 @@ +//! Conversion of internal Rust compiler `rustc_target::abi` and `rustc_abi` items to stable ones. + +#![allow(rustc::usage_of_qualified_ty)] + +use crate::rustc_smir::{Stable, Tables}; +use rustc_middle::ty; +use rustc_target::abi::call::Conv; +use stable_mir::abi::{ + ArgAbi, CallConvention, FieldsShape, FnAbi, Layout, LayoutShape, PassMode, TagEncoding, + TyAndLayout, ValueAbi, VariantsShape, +}; +use stable_mir::ty::{Align, IndexedVal, Size, VariantIdx}; +use stable_mir::{opaque, Opaque}; + +impl<'tcx> Stable<'tcx> for rustc_target::abi::VariantIdx { + type T = VariantIdx; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + VariantIdx::to_val(self.as_usize()) + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Endian { + type T = stable_mir::target::Endian; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_abi::Endian::Little => stable_mir::target::Endian::Little, + rustc_abi::Endian::Big => stable_mir::target::Endian::Big, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::TyAndLayout<'tcx, ty::Ty<'tcx>> { + type T = TyAndLayout; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + TyAndLayout { ty: self.ty.stable(tables), layout: self.layout.stable(tables) } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::Layout<'tcx> { + type T = Layout; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + tables.layout_id(*self) + } +} + +impl<'tcx> Stable<'tcx> + for rustc_abi::LayoutS +{ + type T = LayoutShape; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + LayoutShape { + fields: self.fields.stable(tables), + variants: self.variants.stable(tables), + abi: self.abi.stable(tables), + abi_align: self.align.abi.stable(tables), + size: self.size.stable(tables), + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::call::FnAbi<'tcx, ty::Ty<'tcx>> { + type T = FnAbi; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + FnAbi { + args: self.args.as_ref().stable(tables), + ret: self.ret.stable(tables), + fixed_count: self.fixed_count, + conv: self.conv.stable(tables), + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::call::ArgAbi<'tcx, ty::Ty<'tcx>> { + type T = ArgAbi; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + ArgAbi { + ty: self.layout.ty.stable(tables), + layout: self.layout.layout.stable(tables), + mode: self.mode.stable(tables), + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::call::Conv { + type T = CallConvention; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + match self { + Conv::C => CallConvention::C, + Conv::Rust => CallConvention::Rust, + Conv::Cold => CallConvention::Cold, + Conv::PreserveMost => CallConvention::PreserveMost, + Conv::PreserveAll => CallConvention::PreserveAll, + Conv::ArmAapcs => CallConvention::ArmAapcs, + Conv::CCmseNonSecureCall => CallConvention::CCmseNonSecureCall, + Conv::Msp430Intr => CallConvention::Msp430Intr, + Conv::PtxKernel => CallConvention::PtxKernel, + Conv::X86Fastcall => CallConvention::X86Fastcall, + Conv::X86Intr => CallConvention::X86Intr, + Conv::X86Stdcall => CallConvention::X86Stdcall, + Conv::X86ThisCall => CallConvention::X86ThisCall, + Conv::X86VectorCall => CallConvention::X86VectorCall, + Conv::X86_64SysV => CallConvention::X86_64SysV, + Conv::X86_64Win64 => CallConvention::X86_64Win64, + Conv::AmdGpuKernel => CallConvention::AmdGpuKernel, + Conv::AvrInterrupt => CallConvention::AvrInterrupt, + Conv::AvrNonBlockingInterrupt => CallConvention::AvrNonBlockingInterrupt, + Conv::RiscvInterrupt { .. } => CallConvention::RiscvInterrupt, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::call::PassMode { + type T = PassMode; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_target::abi::call::PassMode::Ignore => PassMode::Ignore, + rustc_target::abi::call::PassMode::Direct(_) => PassMode::Direct, + rustc_target::abi::call::PassMode::Pair(_, _) => PassMode::Pair, + rustc_target::abi::call::PassMode::Cast { .. } => PassMode::Cast, + rustc_target::abi::call::PassMode::Indirect { .. } => PassMode::Indirect, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::FieldsShape { + type T = FieldsShape; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_abi::FieldsShape::Primitive => FieldsShape::Primitive, + rustc_abi::FieldsShape::Union(count) => FieldsShape::Union(*count), + rustc_abi::FieldsShape::Array { stride, count } => { + FieldsShape::Array { stride: stride.stable(tables), count: *count } + } + rustc_abi::FieldsShape::Arbitrary { offsets, .. } => { + FieldsShape::Arbitrary { offsets: offsets.iter().as_slice().stable(tables) } + } + } + } +} + +impl<'tcx> Stable<'tcx> + for rustc_abi::Variants +{ + type T = VariantsShape; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_abi::Variants::Single { index } => { + VariantsShape::Single { index: index.stable(tables) } + } + rustc_abi::Variants::Multiple { tag, tag_encoding, tag_field, variants } => { + VariantsShape::Multiple { + tag: tag.stable(tables), + tag_encoding: tag_encoding.stable(tables), + tag_field: *tag_field, + variants: variants.iter().as_slice().stable(tables), + } + } + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::TagEncoding { + type T = TagEncoding; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_abi::TagEncoding::Direct => TagEncoding::Direct, + rustc_abi::TagEncoding::Niche { untagged_variant, niche_variants, niche_start } => { + TagEncoding::Niche { + untagged_variant: untagged_variant.stable(tables), + niche_variants: niche_variants.stable(tables), + niche_start: *niche_start, + } + } + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Abi { + type T = ValueAbi; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match *self { + rustc_abi::Abi::Uninhabited => ValueAbi::Uninhabited, + rustc_abi::Abi::Scalar(scalar) => ValueAbi::Scalar(scalar.stable(tables)), + rustc_abi::Abi::ScalarPair(first, second) => { + ValueAbi::ScalarPair(first.stable(tables), second.stable(tables)) + } + rustc_abi::Abi::Vector { element, count } => { + ValueAbi::Vector { element: element.stable(tables), count } + } + rustc_abi::Abi::Aggregate { sized } => ValueAbi::Aggregate { sized }, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Size { + type T = Size; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + self.bytes_usize() + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Align { + type T = Align; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + self.bytes() + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Scalar { + type T = Opaque; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + opaque(self) + } +} diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mod.rs b/compiler/rustc_smir/src/rustc_smir/convert/mod.rs index 7021bdda73501..8b7b26f969c96 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mod.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mod.rs @@ -1,10 +1,10 @@ //! Conversion of internal Rust compiler items to stable ones. use rustc_target::abi::FieldIdx; -use stable_mir::ty::{IndexedVal, VariantIdx}; use crate::rustc_smir::{Stable, Tables}; +mod abi; mod error; mod mir; mod ty; @@ -26,13 +26,6 @@ impl<'tcx> Stable<'tcx> for FieldIdx { } } -impl<'tcx> Stable<'tcx> for rustc_target::abi::VariantIdx { - type T = VariantIdx; - fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { - VariantIdx::to_val(self.as_usize()) - } -} - impl<'tcx> Stable<'tcx> for rustc_hir::CoroutineSource { type T = stable_mir::mir::CoroutineSource; fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { @@ -79,14 +72,3 @@ impl<'tcx> Stable<'tcx> for rustc_span::Span { tables.create_span(*self) } } - -impl<'tcx> Stable<'tcx> for rustc_abi::Endian { - type T = stable_mir::target::Endian; - - fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { - match self { - rustc_abi::Endian::Little => stable_mir::target::Endian::Little, - rustc_abi::Endian::Big => stable_mir::target::Endian::Big, - } - } -} diff --git a/compiler/rustc_smir/src/rustc_smir/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs index ae6cf3fe3e86d..cf6ac5285c245 100644 --- a/compiler/rustc_smir/src/rustc_smir/mod.rs +++ b/compiler/rustc_smir/src/rustc_smir/mod.rs @@ -12,9 +12,11 @@ use rustc_middle::mir; use rustc_middle::mir::interpret::AllocId; use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; use rustc_span::def_id::{CrateNum, DefId, LOCAL_CRATE}; +use stable_mir::abi::Layout; use stable_mir::mir::mono::InstanceDef; use stable_mir::ty::{ConstId, Span}; use stable_mir::ItemKind; +use std::ops::RangeInclusive; use tracing::debug; use crate::rustc_internal::IndexMap; @@ -32,6 +34,7 @@ pub struct Tables<'tcx> { pub(crate) types: IndexMap, stable_mir::ty::Ty>, pub(crate) instances: IndexMap, InstanceDef>, pub(crate) constants: IndexMap, ConstId>, + pub(crate) layouts: IndexMap, Layout>, } impl<'tcx> Tables<'tcx> { @@ -162,3 +165,13 @@ where (self.0.stable(tables), self.1.stable(tables)) } } + +impl<'tcx, T> Stable<'tcx> for RangeInclusive +where + T: Stable<'tcx>, +{ + type T = RangeInclusive; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + RangeInclusive::new(self.start().stable(tables), self.end().stable(tables)) + } +} diff --git a/compiler/stable_mir/src/abi.rs b/compiler/stable_mir/src/abi.rs new file mode 100644 index 0000000000000..d4e8e874c1f98 --- /dev/null +++ b/compiler/stable_mir/src/abi.rs @@ -0,0 +1,286 @@ +use crate::compiler_interface::with; +use crate::mir::FieldIdx; +use crate::ty::{Align, IndexedVal, Size, Ty, VariantIdx}; +use crate::Opaque; +use std::num::NonZeroUsize; +use std::ops::RangeInclusive; + +/// A function ABI definition. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct FnAbi { + /// The types of each argument. + pub args: Vec, + + /// The expected return type. + pub ret: ArgAbi, + + /// The count of non-variadic arguments. + /// + /// Should only be different from `args.len()` when a function is a C variadic function. + pub fixed_count: u32, + + /// The ABI convention. + pub conv: CallConvention, +} + +impl FnAbi { + pub fn is_c_variadic(&self) -> bool { + self.args.len() > self.fixed_count as usize + } +} + +/// Information about the ABI of a function's argument, or return value. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ArgAbi { + pub ty: Ty, + pub layout: Layout, + pub mode: PassMode, +} + +/// How a function argument should be passed in to the target function. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum PassMode { + /// Ignore the argument. + /// + /// The argument is either uninhabited or a ZST. + Ignore, + /// Pass the argument directly. + /// + /// The argument has a layout abi of `Scalar` or `Vector`. + Direct, + /// Pass a pair's elements directly in two arguments. + /// + /// The argument has a layout abi of `ScalarPair`. + Pair, + /// Pass the argument after casting it. + Cast, + /// Pass the argument indirectly via a hidden pointer. + Indirect, +} + +/// The layout of a type, alongside the type itself. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct TyAndLayout { + pub ty: Ty, + pub layout: Layout, +} + +/// The layout of a type in memory. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct LayoutShape { + /// The fields location withing the layout + pub fields: FieldsShape, + + /// Encodes information about multi-variant layouts. + /// Even with `Multiple` variants, a layout still has its own fields! Those are then + /// shared between all variants. + /// + /// To access all fields of this layout, both `fields` and the fields of the active variant + /// must be taken into account. + pub variants: VariantsShape, + + /// The `abi` defines how this data is passed between functions. + pub abi: ValueAbi, + + /// The ABI mandated alignment in bytes. + pub abi_align: Align, + + /// The size of this layout in bytes. + pub size: Size, +} + +impl LayoutShape { + /// Returns `true` if the layout corresponds to an unsized type. + #[inline] + pub fn is_unsized(&self) -> bool { + self.abi.is_unsized() + } + + #[inline] + pub fn is_sized(&self) -> bool { + !self.abi.is_unsized() + } + + /// Returns `true` if the type is sized and a 1-ZST (meaning it has size 0 and alignment 1). + pub fn is_1zst(&self) -> bool { + self.is_sized() && self.size == 0 && self.abi_align == 1 + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Layout(usize); + +impl Layout { + pub fn shape(self) -> LayoutShape { + with(|cx| cx.layout_shape(self)) + } +} + +impl IndexedVal for Layout { + fn to_val(index: usize) -> Self { + Layout(index) + } + fn to_index(&self) -> usize { + self.0 + } +} + +/// Describes how the fields of a type are shaped in memory. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum FieldsShape { + /// Scalar primitives and `!`, which never have fields. + Primitive, + + /// All fields start at no offset. The `usize` is the field count. + Union(NonZeroUsize), + + /// Array/vector-like placement, with all fields of identical types. + Array { stride: Size, count: u64 }, + + /// Struct-like placement, with precomputed offsets. + /// + /// Fields are guaranteed to not overlap, but note that gaps + /// before, between and after all the fields are NOT always + /// padding, and as such their contents may not be discarded. + /// For example, enum variants leave a gap at the start, + /// where the discriminant field in the enum layout goes. + Arbitrary { + /// Offsets for the first byte of each field, + /// ordered to match the source definition order. + /// I.e.: It follows the same order as [crate::ty::VariantDef::fields()]. + /// This vector does not go in increasing order. + offsets: Vec, + }, +} + +impl FieldsShape { + pub fn fields_by_offset_order(&self) -> Vec { + match self { + FieldsShape::Primitive => vec![], + FieldsShape::Union(_) | FieldsShape::Array { .. } => (0..self.count()).collect(), + FieldsShape::Arbitrary { offsets, .. } => { + let mut indices = (0..offsets.len()).collect::>(); + indices.sort_by_key(|idx| offsets[*idx]); + indices + } + } + } + + pub fn count(&self) -> usize { + match self { + FieldsShape::Primitive => 0, + FieldsShape::Union(count) => count.get(), + FieldsShape::Array { count, .. } => *count as usize, + FieldsShape::Arbitrary { offsets, .. } => offsets.len(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum VariantsShape { + /// Single enum variants, structs/tuples, unions, and all non-ADTs. + Single { index: VariantIdx }, + + /// Enum-likes with more than one inhabited variant: each variant comes with + /// a *discriminant* (usually the same as the variant index but the user can + /// assign explicit discriminant values). That discriminant is encoded + /// as a *tag* on the machine. The layout of each variant is + /// a struct, and they all have space reserved for the tag. + /// For enums, the tag is the sole field of the layout. + Multiple { + tag: Scalar, + tag_encoding: TagEncoding, + tag_field: usize, + variants: Vec, + }, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum TagEncoding { + /// The tag directly stores the discriminant, but possibly with a smaller layout + /// (so converting the tag to the discriminant can require sign extension). + Direct, + + /// Niche (values invalid for a type) encoding the discriminant: + /// Discriminant and variant index coincide. + /// The variant `untagged_variant` contains a niche at an arbitrary + /// offset (field `tag_field` of the enum), which for a variant with + /// discriminant `d` is set to + /// `(d - niche_variants.start).wrapping_add(niche_start)`. + /// + /// For example, `Option<(usize, &T)>` is represented such that + /// `None` has a null pointer for the second tuple field, and + /// `Some` is the identity function (with a non-null reference). + Niche { + untagged_variant: VariantIdx, + niche_variants: RangeInclusive, + niche_start: u128, + }, +} + +/// Describes how values of the type are passed by target ABIs, +/// in terms of categories of C types there are ABI rules for. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum ValueAbi { + Uninhabited, + Scalar(Scalar), + ScalarPair(Scalar, Scalar), + Vector { + element: Scalar, + count: u64, + }, + Aggregate { + /// If true, the size is exact, otherwise it's only a lower bound. + sized: bool, + }, +} + +impl ValueAbi { + /// Returns `true` if the layout corresponds to an unsized type. + pub fn is_unsized(&self) -> bool { + match *self { + ValueAbi::Uninhabited + | ValueAbi::Scalar(_) + | ValueAbi::ScalarPair(..) + | ValueAbi::Vector { .. } => false, + ValueAbi::Aggregate { sized } => !sized, + } + } +} + +/// We currently do not support `Scalar`, and use opaque instead. +type Scalar = Opaque; + +/// General language calling conventions. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum CallConvention { + C, + Rust, + + Cold, + PreserveMost, + PreserveAll, + + // Target-specific calling conventions. + ArmAapcs, + CCmseNonSecureCall, + + Msp430Intr, + + PtxKernel, + + X86Fastcall, + X86Intr, + X86Stdcall, + X86ThisCall, + X86VectorCall, + + X86_64SysV, + X86_64Win64, + + AmdGpuKernel, + AvrInterrupt, + AvrNonBlockingInterrupt, + + RiscvInterrupt, +} diff --git a/compiler/stable_mir/src/compiler_interface.rs b/compiler/stable_mir/src/compiler_interface.rs index 57c60b70d8aa3..98b1d484c034e 100644 --- a/compiler/stable_mir/src/compiler_interface.rs +++ b/compiler/stable_mir/src/compiler_interface.rs @@ -5,6 +5,7 @@ use std::cell::Cell; +use crate::abi::{FnAbi, Layout, LayoutShape}; use crate::mir::alloc::{AllocId, GlobalAlloc}; use crate::mir::mono::{Instance, InstanceDef, StaticDef}; use crate::mir::Body; @@ -173,6 +174,15 @@ pub trait Context { /// Return information about the target machine. fn target_info(&self) -> MachineInfo; + + /// Get an instance ABI. + fn instance_abi(&self, def: InstanceDef) -> Result; + + /// Get the layout of a type. + fn ty_layout(&self, ty: Ty) -> Result; + + /// Get the layout shape. + fn layout_shape(&self, id: Layout) -> LayoutShape; } // A thread local variable that stores a pointer to the tables mapping between TyCtxt diff --git a/compiler/stable_mir/src/lib.rs b/compiler/stable_mir/src/lib.rs index 8c66bfb2e9890..4941e54fe4b7e 100644 --- a/compiler/stable_mir/src/lib.rs +++ b/compiler/stable_mir/src/lib.rs @@ -33,6 +33,7 @@ use crate::mir::Body; use crate::mir::Mutability; use crate::ty::{ImplDef, ImplTrait, IndexedVal, Span, TraitDecl, TraitDef, Ty}; +pub mod abi; #[macro_use] pub mod crate_def; pub mod compiler_interface; diff --git a/compiler/stable_mir/src/mir/mono.rs b/compiler/stable_mir/src/mir/mono.rs index c126de23c4b6f..70d44ef8c2256 100644 --- a/compiler/stable_mir/src/mir/mono.rs +++ b/compiler/stable_mir/src/mir/mono.rs @@ -1,3 +1,4 @@ +use crate::abi::FnAbi; use crate::crate_def::CrateDef; use crate::mir::Body; use crate::ty::{Allocation, ClosureDef, ClosureKind, FnDef, GenericArgs, IndexedVal, Ty}; @@ -56,6 +57,11 @@ impl Instance { with(|context| context.instance_ty(self.def)) } + /// Retrieve information about this instance binary interface. + pub fn fn_abi(&self) -> Result { + with(|cx| cx.instance_abi(self.def)) + } + /// Retrieve the instance's mangled name used for calling the given instance. /// /// This will also look up the correct name of instances from upstream crates. diff --git a/compiler/stable_mir/src/ty.rs b/compiler/stable_mir/src/ty.rs index 4807a9028eb83..1d4d7b6d3520f 100644 --- a/compiler/stable_mir/src/ty.rs +++ b/compiler/stable_mir/src/ty.rs @@ -3,6 +3,7 @@ use super::{ mir::{Body, Mutability}, with, DefId, Error, Symbol, }; +use crate::abi::Layout; use crate::crate_def::CrateDef; use crate::mir::alloc::{read_target_int, read_target_uint, AllocId}; use crate::target::MachineInfo; @@ -85,6 +86,11 @@ impl Ty { pub fn unsigned_ty(inner: UintTy) -> Ty { Ty::from_rigid_kind(RigidTy::Uint(inner)) } + + /// Get a type layout. + pub fn layout(self) -> Result { + with(|cx| cx.ty_layout(self)) + } } impl Ty { diff --git a/tests/ui-fulldeps/stable-mir/check_allocation.rs b/tests/ui-fulldeps/stable-mir/check_allocation.rs index 8554630e9c999..7ce3597206b62 100644 --- a/tests/ui-fulldeps/stable-mir/check_allocation.rs +++ b/tests/ui-fulldeps/stable-mir/check_allocation.rs @@ -209,7 +209,6 @@ fn check_len(item: CrateItem) { assert_eq!(alloc.read_uint(), Ok(2)); } -// Use internal API to find a function in a crate. fn get_item<'a>( items: &'a CrateItems, item: (ItemKind, &str), diff --git a/tests/ui-fulldeps/stable-mir/check_defs.rs b/tests/ui-fulldeps/stable-mir/check_defs.rs index ad66751133214..e9a2599d8732c 100644 --- a/tests/ui-fulldeps/stable-mir/check_defs.rs +++ b/tests/ui-fulldeps/stable-mir/check_defs.rs @@ -69,9 +69,9 @@ fn extract_elem_ty(ty: Ty) -> Ty { /// Check signature and type of `Vec::::new` and its generic version. fn test_vec_new(instance: mir::mono::Instance) { - let sig = instance.ty().kind().fn_sig().unwrap().skip_binder(); - assert_matches!(sig.inputs(), &[]); - let elem_ty = extract_elem_ty(sig.output()); + let sig = instance.fn_abi().unwrap(); + assert_eq!(&sig.args, &[]); + let elem_ty = extract_elem_ty(sig.ret.ty); assert_matches!(elem_ty.kind(), TyKind::RigidTy(RigidTy::Uint(UintTy::U8))); // Get the signature for Vec::::new. diff --git a/tests/ui-fulldeps/stable-mir/check_layout.rs b/tests/ui-fulldeps/stable-mir/check_layout.rs new file mode 100644 index 0000000000000..1c33103b0feed --- /dev/null +++ b/tests/ui-fulldeps/stable-mir/check_layout.rs @@ -0,0 +1,117 @@ +// run-pass +//! Test information regarding type layout. + +// ignore-stage1 +// ignore-cross-compile +// ignore-remote +// ignore-windows-gnu mingw has troubles with linking https://github.com/rust-lang/rust/pull/116837 + +#![feature(rustc_private)] +#![feature(assert_matches)] +#![feature(control_flow_enum)] +#![feature(ascii_char, ascii_char_variants)] + +extern crate rustc_hir; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_smir; +extern crate rustc_driver; +extern crate rustc_interface; +extern crate stable_mir; + +use rustc_middle::ty::TyCtxt; +use rustc_smir::rustc_internal; +use stable_mir::abi::{ArgAbi, CallConvention, FieldsShape, PassMode, VariantsShape}; +use stable_mir::mir::mono::Instance; +use stable_mir::{CrateDef, CrateItems, ItemKind}; +use std::assert_matches::assert_matches; +use std::convert::TryFrom; +use std::io::Write; +use std::ops::ControlFlow; + +const CRATE_NAME: &str = "input"; + +/// This function uses the Stable MIR APIs to get information about the test crate. +fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> { + // Find items in the local crate. + let items = stable_mir::all_local_items(); + let target_fn = *get_item(&items, (ItemKind::Fn, "fn_abi")).unwrap(); + let instance = Instance::try_from(target_fn).unwrap(); + let fn_abi = instance.fn_abi().unwrap(); + assert_eq!(fn_abi.conv, CallConvention::Rust); + assert_eq!(fn_abi.args.len(), 2); + + check_ignore(&fn_abi.args[0]); + check_primitive(&fn_abi.args[1]); + check_result(fn_abi.ret); + + ControlFlow::Continue(()) +} + +/// Check the argument to be ignored: `ignore: [u8; 0]`. +fn check_ignore(abi: &ArgAbi) { + assert!(abi.ty.kind().is_array()); + assert_eq!(abi.mode, PassMode::Ignore); + let layout = abi.layout.shape(); + assert!(layout.is_sized()); + assert!(layout.is_1zst()); +} + +/// Check the primitive argument: `primitive: char`. +fn check_primitive(abi: &ArgAbi) { + assert!(abi.ty.kind().is_char()); + assert_eq!(abi.mode, PassMode::Direct); + let layout = abi.layout.shape(); + assert!(layout.is_sized()); + assert!(!layout.is_1zst()); + assert_matches!(layout.fields, FieldsShape::Primitive); +} + +/// Check the return value: `Result`. +fn check_result(abi: ArgAbi) { + assert!(abi.ty.kind().is_enum()); + assert_eq!(abi.mode, PassMode::Indirect); + let layout = abi.layout.shape(); + assert!(layout.is_sized()); + assert_matches!(layout.fields, FieldsShape::Arbitrary { .. }); + assert_matches!(layout.variants, VariantsShape::Multiple { .. }) +} + +fn get_item<'a>( + items: &'a CrateItems, + item: (ItemKind, &str), +) -> Option<&'a stable_mir::CrateItem> { + items.iter().find(|crate_item| (item.0 == crate_item.kind()) && crate_item.name() == item.1) +} + +/// This test will generate and analyze a dummy crate using the stable mir. +/// For that, it will first write the dummy crate into a file. +/// Then it will create a `StableMir` using custom arguments and then +/// it will run the compiler. +fn main() { + let path = "alloc_input.rs"; + generate_input(&path).unwrap(); + let args = vec![ + "rustc".to_string(), + "--crate-type=lib".to_string(), + "--crate-name".to_string(), + CRATE_NAME.to_string(), + path.to_string(), + ]; + run!(args, tcx, test_stable_mir(tcx)).unwrap(); +} + +fn generate_input(path: &str) -> std::io::Result<()> { + let mut file = std::fs::File::create(path)?; + write!( + file, + r#" + #[allow(unused_variables)] + pub fn fn_abi(ignore: [u8; 0], primitive: char) -> Result {{ + // We only care about the signature. + todo!() + }} + "# + )?; + Ok(()) +} From df5b604945276994ad662bd0fd34face0ad609eb Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 19 Dec 2023 07:40:06 +0100 Subject: [PATCH 04/10] subtype_predicate: remove unnecessary probe --- compiler/rustc_infer/src/infer/mod.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 3de269da22d65..e092bbbfdd674 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -713,10 +713,6 @@ impl<'tcx> InferCtxtBuilder<'tcx> { } impl<'tcx, T> InferOk<'tcx, T> { - pub fn unit(self) -> InferOk<'tcx, ()> { - InferOk { value: (), obligations: self.obligations } - } - /// Extracts `value`, registering any obligations into `fulfill_cx`. pub fn into_value_registering_obligations( self, @@ -1025,15 +1021,10 @@ impl<'tcx> InferCtxt<'tcx> { _ => {} } - Ok(self.commit_if_ok(|_snapshot| { - let ty::SubtypePredicate { a_is_expected, a, b } = - self.instantiate_binder_with_placeholders(predicate); - - let ok = - self.at(cause, param_env).sub_exp(DefineOpaqueTypes::No, a_is_expected, a, b)?; + let ty::SubtypePredicate { a_is_expected, a, b } = + self.instantiate_binder_with_placeholders(predicate); - Ok(ok.unit()) - })) + Ok(self.at(cause, param_env).sub_exp(DefineOpaqueTypes::No, a_is_expected, a, b)) } pub fn region_outlives_predicate( From 90893e4850f463618bd06ae6d3e30f0075559e24 Mon Sep 17 00:00:00 2001 From: Chris Copeland Date: Sun, 13 Aug 2023 03:51:03 -0700 Subject: [PATCH 05/10] Add arm-none-eabi and armv7r-none-eabi platform-support documentation. --- src/doc/rustc/src/SUMMARY.md | 2 + src/doc/rustc/src/platform-support.md | 42 ++++---- .../src/platform-support/arm-none-eabi.md | 96 +++++++++++++++++++ .../src/platform-support/armv4t-none-eabi.md | 45 +-------- .../src/platform-support/armv5te-none-eabi.md | 50 +--------- .../src/platform-support/armv7r-none-eabi.md | 47 +++++++++ 6 files changed, 176 insertions(+), 106 deletions(-) create mode 100644 src/doc/rustc/src/platform-support/arm-none-eabi.md create mode 100644 src/doc/rustc/src/platform-support/armv7r-none-eabi.md diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index f46daca1f3075..fadd64a0353cb 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -22,8 +22,10 @@ - [\*-apple-watchos\*](platform-support/apple-watchos.md) - [aarch64-nintendo-switch-freestanding](platform-support/aarch64-nintendo-switch-freestanding.md) - [armeb-unknown-linux-gnueabi](platform-support/armeb-unknown-linux-gnueabi.md) + - [arm-none-eabi](platform-support/arm-none-eabi.md) - [armv4t-none-eabi](platform-support/armv4t-none-eabi.md) - [armv5te-none-eabi](platform-support/armv5te-none-eabi.md) + - [armv7r-none-eabi](platform-support/armv7r-none-eabi.md) - [armv6k-nintendo-3ds](platform-support/armv6k-nintendo-3ds.md) - [armv7-sony-vita-newlibeabihf](platform-support/armv7-sony-vita-newlibeabihf.md) - [armv7-unknown-linux-uclibceabi](platform-support/armv7-unknown-linux-uclibceabi.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 5535e69c86acd..a1d2281ca4884 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -137,17 +137,17 @@ target | std | notes [`arm-linux-androideabi`](platform-support/android.md) | ✓ | ARMv6 Android `arm-unknown-linux-musleabi` | ✓ | ARMv6 Linux with MUSL `arm-unknown-linux-musleabihf` | ✓ | ARMv6 Linux with MUSL, hardfloat -`armebv7r-none-eabi` | * | Bare ARMv7-R, Big Endian -`armebv7r-none-eabihf` | * | Bare ARMv7-R, Big Endian, hardfloat +[`armebv7r-none-eabi`](platform-support/armv7r-none-eabi.md) | * | Bare ARMv7-R, Big Endian +[`armebv7r-none-eabihf`](platform-support/armv7r-none-eabi.md) | * | Bare ARMv7-R, Big Endian, hardfloat `armv5te-unknown-linux-gnueabi` | ✓ | ARMv5TE Linux (kernel 4.4, glibc 2.23) `armv5te-unknown-linux-musleabi` | ✓ | ARMv5TE Linux with MUSL [`armv7-linux-androideabi`](platform-support/android.md) | ✓ | ARMv7-A Android `armv7-unknown-linux-gnueabi` | ✓ | ARMv7-A Linux (kernel 4.15, glibc 2.27) `armv7-unknown-linux-musleabi` | ✓ | ARMv7-A Linux with MUSL `armv7-unknown-linux-musleabihf` | ✓ | ARMv7-A Linux with MUSL, hardfloat -`armv7a-none-eabi` | * | Bare ARMv7-A -`armv7r-none-eabi` | * | Bare ARMv7-R -`armv7r-none-eabihf` | * | Bare ARMv7-R, hardfloat +[`armv7a-none-eabi`](platform-support/arm-none-eabi.md) | * | Bare ARMv7-A +[`armv7r-none-eabi`](platform-support/armv7r-none-eabi.md) | * | Bare ARMv7-R +[`armv7r-none-eabihf`](platform-support/armv7r-none-eabi.md) | * | Bare ARMv7-R, hardfloat `i586-pc-windows-msvc` | * | 32-bit Windows w/o SSE [^x86_32-floats-x87] `i586-unknown-linux-gnu` | ✓ | 32-bit Linux w/o SSE (kernel 3.2, glibc 2.17) [^x86_32-floats-x87] `i586-unknown-linux-musl` | ✓ | 32-bit Linux w/o SSE, MUSL [^x86_32-floats-x87] @@ -166,15 +166,15 @@ target | std | notes `riscv64imac-unknown-none-elf` | * | Bare RISC-V (RV64IMAC ISA) `sparc64-unknown-linux-gnu` | ✓ | SPARC Linux (kernel 4.4, glibc 2.23) `sparcv9-sun-solaris` | ✓ | SPARC Solaris 11, illumos -`thumbv6m-none-eabi` | * | Bare ARMv6-M -`thumbv7em-none-eabi` | * | Bare ARMv7E-M -`thumbv7em-none-eabihf` | * | Bare ARMV7E-M, hardfloat -`thumbv7m-none-eabi` | * | Bare ARMv7-M +[`thumbv6m-none-eabi`](platform-support/arm-none-eabi.md) | * | Bare ARMv6-M +[`thumbv7em-none-eabi`](platform-support/arm-none-eabi.md) | * | Bare ARMv7E-M +[`thumbv7em-none-eabihf`](platform-support/arm-none-eabi.md) | * | Bare ARMV7E-M, hardfloat +[`thumbv7m-none-eabi`](platform-support/arm-none-eabi.md) | * | Bare ARMv7-M [`thumbv7neon-linux-androideabi`](platform-support/android.md) | ✓ | Thumb2-mode ARMv7-A Android with NEON `thumbv7neon-unknown-linux-gnueabihf` | ✓ | Thumb2-mode ARMv7-A Linux with NEON (kernel 4.4, glibc 2.23) -`thumbv8m.base-none-eabi` | * | Bare ARMv8-M Baseline -`thumbv8m.main-none-eabi` | * | Bare ARMv8-M Mainline -`thumbv8m.main-none-eabihf` | * | Bare ARMv8-M Mainline, hardfloat +[`thumbv8m.base-none-eabi`](platform-support/arm-none-eabi.md) | * | Bare ARMv8-M Baseline +[`thumbv8m.main-none-eabi`](platform-support/arm-none-eabi.md) | * | Bare ARMv8-M Mainline +[`thumbv8m.main-none-eabihf`](platform-support/arm-none-eabi.md) | * | Bare ARMv8-M Mainline, hardfloat `wasm32-unknown-emscripten` | ✓ | WebAssembly via Emscripten `wasm32-unknown-unknown` | ✓ | WebAssembly `wasm32-wasi` | ✓ | WebAssembly with WASI @@ -238,11 +238,11 @@ target | std | host | notes `aarch64_be-unknown-linux-gnu_ilp32` | ✓ | ✓ | ARM64 Linux (big-endian, ILP32 ABI) `aarch64_be-unknown-linux-gnu` | ✓ | ✓ | ARM64 Linux (big-endian) [`aarch64_be-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | ARM64 NetBSD (big-endian) -[`arm64_32-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | ARM Apple WatchOS 64-bit with 32-bit pointers +[`arm64_32-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | ARM Apple WatchOS 64-bit with 32-bit pointers [`armeb-unknown-linux-gnueabi`](platform-support/armeb-unknown-linux-gnueabi.md) | ✓ | ? | ARM BE8 the default ARM big-endian architecture since [ARMv6](https://developer.arm.com/documentation/101754/0616/armlink-Reference/armlink-Command-line-Options/--be8?lang=en). -`armv4t-none-eabi` | * | | Bare ARMv4T +[`armv4t-none-eabi`](platform-support/armv4t-none-eabi.md) | * | | Bare ARMv4T `armv4t-unknown-linux-gnueabi` | ? | | ARMv4T Linux -[`armv5te-none-eabi`](platform-support/armv5te-none-eabi.md) | * | | Bare ARMv5TE +[`armv5te-none-eabi`](platform-support/armv5te-none-eabi.md) | * | | Bare ARMv5TE `armv5te-unknown-linux-uclibceabi` | ? | | ARMv5TE Linux with uClibc `armv6-unknown-freebsd` | ✓ | ✓ | ARMv6 FreeBSD [`armv6-unknown-netbsd-eabihf`](platform-support/netbsd.md) | ✓ | ✓ | ARMv6 NetBSD w/hard-float @@ -256,8 +256,8 @@ target | std | host | notes `armv7-wrs-vxworks-eabihf` | ? | | ARMv7-A for VxWorks [`armv7a-kmc-solid_asp3-eabi`](platform-support/kmc-solid.md) | ✓ | | ARM SOLID with TOPPERS/ASP3 [`armv7a-kmc-solid_asp3-eabihf`](platform-support/kmc-solid.md) | ✓ | | ARM SOLID with TOPPERS/ASP3, hardfloat -`armv7a-none-eabihf` | * | | Bare ARMv7-A, hardfloat -[`armv7k-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | ARMv7-A Apple WatchOS +[`armv7a-none-eabihf`](platform-support/arm-none-eabi.md) | * | | Bare ARMv7-A, hardfloat +[`armv7k-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | ARMv7-A Apple WatchOS `armv7s-apple-ios` | ✓ | | ARMv7-A Apple-A6 Apple iOS `avr-unknown-gnu-atmega328` | * | | AVR. Requires `-Z build-std=core` `bpfeb-unknown-none` | * | | BPF (big endian) @@ -332,15 +332,15 @@ target | std | host | notes [`sparc-unknown-none-elf`](./platform-support/sparc-unknown-none-elf.md) | * | | Bare 32-bit SPARC V7+ [`sparc64-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | NetBSD/sparc64 [`sparc64-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | OpenBSD/sparc64 -`thumbv4t-none-eabi` | * | | Thumb-mode Bare ARMv4T -[`thumbv5te-none-eabi`](platform-support/armv5te-none-eabi.md) | * | | Thumb-mode Bare ARMv5TE +[`thumbv4t-none-eabi`](platform-support/armv4t-none-eabi.md) | * | | Thumb-mode Bare ARMv4T +[`thumbv5te-none-eabi`](platform-support/armv5te-none-eabi.md) | * | | Thumb-mode Bare ARMv5TE `thumbv7a-pc-windows-msvc` | ? | | `thumbv7a-uwp-windows-msvc` | ✓ | | `thumbv7neon-unknown-linux-musleabihf` | ? | | Thumb2-mode ARMv7-A Linux with NEON, MUSL [`wasm64-unknown-unknown`](platform-support/wasm64-unknown-unknown.md) | ? | | WebAssembly `x86_64-apple-ios-macabi` | ✓ | | Apple Catalyst on x86_64 -[`x86_64-apple-tvos`](platform-support/apple-tvos.md) | ? | | x86 64-bit tvOS -[`x86_64-apple-watchos-sim`](platform-support/apple-watchos.md) | ✓ | | x86 64-bit Apple WatchOS simulator +[`x86_64-apple-tvos`](platform-support/apple-tvos.md) | ? | | x86 64-bit tvOS +[`x86_64-apple-watchos-sim`](platform-support/apple-watchos.md) | ✓ | | x86 64-bit Apple WatchOS simulator [`x86_64-pc-nto-qnx710`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX Neutrino 7.1 RTOS | [`x86_64-pc-windows-gnullvm`](platform-support/pc-windows-gnullvm.md) | ✓ | ✓ | `x86_64-pc-windows-msvc` | * | | 64-bit Windows XP support diff --git a/src/doc/rustc/src/platform-support/arm-none-eabi.md b/src/doc/rustc/src/platform-support/arm-none-eabi.md new file mode 100644 index 0000000000000..4f76d0d7bbce7 --- /dev/null +++ b/src/doc/rustc/src/platform-support/arm-none-eabi.md @@ -0,0 +1,96 @@ +# `{arm,thumb}*-none-eabi(hf)?` + +**Tier: 2** +- [arm(eb)?v7r-none-eabi(hf)?](armv7r-none-eabi.md) +- armv7a-none-eabi +- thumbv6m-none-eabi +- thumbv7m-none-eabi +- thumbv7em-none-eabi(hf)? +- thumbv8m.base-none-eabi +- thumbv8m.main-none-eabi(hf)? + +**Tier: 3** +- [{arm,thumb}v4t-none-eabi](armv4t-none-eabi.md) +- [{arm,thumb}v5te-none-eabi](armv5te-none-eabi.md) +- armv7a-none-eabihf + +Bare-metal target for 32-bit ARM CPUs. + +If a target has a `*hf` variant, that variant uses the hardware floating-point +ABI and enables some minimum set of floating-point features based on the FPU(s) +available in that processor family. + +## Requirements + +These targets are cross-compiled and use static linking. + +By default, the `lld` linker included with Rust will be used; however, you may +want to use the GNU linker instead. This can be obtained for Windows/Mac/Linux +from the [Arm Developer Website][arm-gnu-toolchain], or possibly from your OS's +package manager. To use it, add the following to your `.cargo/config.toml`: + +```toml +[target.] +linker = "arm-none-eabi-ld" +``` + +The GNU linker can also be used by specifying `arm-none-eabi-gcc` as the +linker. This is needed when using GCC's link time optimization. + +[arm-gnu-toolchain]: https://developer.arm.com/Tools%20and%20Software/GNU%20Toolchain + +These targets don't provide a linker script, so you'll need to bring your own +according to the specific device you are using. Pass +`-Clink-arg=-Tyour_script.ld` as a rustc argument to make the linker use +`your_script.ld` during linking. + +Targets named `thumb*` instead of `arm*` +generate Thumb-mode code by default. M-profile processors (`thumbv*m*-*` +targets) only support Thumb-mode code. +For the `arm*` targets, Thumb-mode code generation can be enabled by using +`-C target-feature=+thumb-mode`. Using the unstable +`#![feature(arm_target_feature)]`, the attribute +`#[target_feature(enable = "thumb-mode")]` can be applied to individual +`unsafe` functions to cause those functions to be compiled to Thumb-mode code. + +## Building Rust Programs + +For the Tier 3 targets in this family, rust does not ship pre-compiled +artifacts. + +Just use the `build-std` nightly cargo feature to build the `core` library. You +can pass this as a command line argument to cargo, or your `.cargo/config.toml` +file might include the following lines: + +```toml +[unstable] +build-std = ["core"] +``` + +Most of `core` should work as expected, with the following notes: +* If the target is not `*hf`, then floating-point operations are emulated in + software. +* Integer division is also emulated in software on some targets, depending on + the CPU. +* Architectures prior to ARMv7 don't have atomic instructions. + +`alloc` is also supported, as long as you provide your own global allocator. + +Rust programs are output as ELF files. + +## Testing + +This is a cross-compiled target that you will need to emulate during testing. + +The exact emulator that you'll need depends on the specific device you want to +run your code on. + +## Cross-compilation toolchains and C code + +The target supports C code compiled with the `arm-none-eabi` target triple and +a suitable `-march` or `-mcpu` flag. + +`gcc` or `clang` can be used, but note that `gcc` uses `-fshort-enums` by +default for `arm-none*` targets, while `clang` does not. `rustc` matches the +`gcc` behavior, i.e., the size of a `#[repr(C)] enum` in Rust can be as little +as 1 byte, rather than 4, as they are on `arm-linux` targets. diff --git a/src/doc/rustc/src/platform-support/armv4t-none-eabi.md b/src/doc/rustc/src/platform-support/armv4t-none-eabi.md index a230eba6bf95c..29c47db8351c7 100644 --- a/src/doc/rustc/src/platform-support/armv4t-none-eabi.md +++ b/src/doc/rustc/src/platform-support/armv4t-none-eabi.md @@ -6,51 +6,16 @@ Bare-metal target for any cpu in the ARMv4T architecture family, supporting ARM/Thumb code interworking (aka `a32`/`t32`), with ARM code as the default code generation. -In particular this supports the Gameboy Advance (GBA), but there's nothing GBA -specific with this target, so any ARMv4T device should work fine. +In particular this supports the Game Boy Advance (GBA), but there's nothing +GBA-specific with this target, so any ARMv4T device should work fine. + +See [`arm-none-eabi`](arm-none-eabi.md) for information applicable to all +`arm-none-eabi` targets. ## Target Maintainers * [@Lokathor](https://github.com/lokathor) -## Requirements - -The target is cross-compiled, and uses static linking. - -This target doesn't provide a linker script, you'll need to bring your own -according to the specific device you want to target. Pass -`-Clink-arg=-Tyour_script.ld` as a rustc argument to make the linker use -`your_script.ld` during linking. - -## Building Rust Programs - -Because it is Tier 3, rust does not yet ship pre-compiled artifacts for this target. - -Just use the `build-std` nightly cargo feature to build the `core` library. You -can pass this as a command line argument to cargo, or your `.cargo/config.toml` -file might include the following lines: - -```toml -[unstable] -build-std = ["core"] -``` - -Most of `core` should work as expected, with the following notes: -* the target is "soft float", so `f32` and `f64` operations are emulated in - software. -* integer division is also emulated in software. -* the target is old enough that it doesn't have atomic instructions. - -Rust programs are output as ELF files. - -For running on hardware, you'll generally need to extract the "raw" program code -out of the ELF and into a file of its own. The `objcopy` program provided as -part of the GNU Binutils can do this: - -```shell -arm-none-eabi-objcopy --output-target binary [in_file] [out_file] -``` - ## Testing This is a cross-compiled target that you will need to emulate during testing. diff --git a/src/doc/rustc/src/platform-support/armv5te-none-eabi.md b/src/doc/rustc/src/platform-support/armv5te-none-eabi.md index f469dab1c42fa..37284ba720997 100644 --- a/src/doc/rustc/src/platform-support/armv5te-none-eabi.md +++ b/src/doc/rustc/src/platform-support/armv5te-none-eabi.md @@ -8,54 +8,13 @@ generation. The `thumbv5te-none-eabi` target is the same as this one, but the instruction set defaults to `t32`. +See [`arm-none-eabi`](arm-none-eabi.md) for information applicable to all +`arm-none-eabi` targets. + ## Target Maintainers * [@QuinnPainter](https://github.com/QuinnPainter) -## Requirements - -The target is cross-compiled, and uses static linking. - -By default, the `lld` linker included with Rust will be used. - -However, you may want to use the `arm-none-eabi-ld` linker instead. This can be obtained for Windows/Mac/Linux from the [ARM -Developer Website][arm-dev], or possibly from your OS's package manager. To use it, add the following to your `.cargo/config.toml`: - -```toml -[target.armv5te-none-eabi] -linker = "arm-none-eabi-ld" -``` - -[arm-dev]: https://developer.arm.com/Tools%20and%20Software/GNU%20Toolchain - -This target doesn't provide a linker script, you'll need to bring your own -according to the specific device you want to target. Pass -`-Clink-arg=-Tyour_script.ld` as a rustc argument to make the linker use -`your_script.ld` during linking. - -## Building Rust Programs - -Because it is Tier 3, rust does not yet ship pre-compiled artifacts for this target. - -Just use the `build-std` nightly cargo feature to build the `core` library. You -can pass this as a command line argument to cargo, or your `.cargo/config.toml` -file might include the following lines: - -```toml -[unstable] -build-std = ["core"] -``` - -Most of `core` should work as expected, with the following notes: -* the target is "soft float", so `f32` and `f64` operations are emulated in - software. -* integer division is also emulated in software. -* the target is old enough that it doesn't have atomic instructions. - -`alloc` is also supported, as long as you provide your own global allocator. - -Rust programs are output as ELF files. - ## Testing This is a cross-compiled target that you will need to emulate during testing. @@ -63,4 +22,5 @@ This is a cross-compiled target that you will need to emulate during testing. Because this is a device-agnostic target, and the exact emulator that you'll need depends on the specific device you want to run your code on. -For example, when programming for the DS, you can use one of the several available DS emulators, such as [melonDS](https://melonds.kuribo64.net/). +For example, when programming for the DS, you can use one of the several +available DS emulators, such as [melonDS](https://melonds.kuribo64.net/). diff --git a/src/doc/rustc/src/platform-support/armv7r-none-eabi.md b/src/doc/rustc/src/platform-support/armv7r-none-eabi.md new file mode 100644 index 0000000000000..670cead9e006b --- /dev/null +++ b/src/doc/rustc/src/platform-support/armv7r-none-eabi.md @@ -0,0 +1,47 @@ +# `arm(eb)?v7r-none-eabi(hf)?` + +**Tier: 2** + +Bare-metal target for CPUs in the ARMv7-R architecture family, supporting +dual ARM/Thumb mode, with ARM mode as the default. + +Processors in this family include the [Arm Cortex-R4, 5, 7, and 8][cortex-r]. + +The `eb` versions of this target generate code for big-endian processors. + +See [`arm-none-eabi`](arm-none-eabi.md) for information applicable to all +`arm-none-eabi` targets. + +[cortex-r]: https://en.wikipedia.org/wiki/ARM_Cortex-R + +## Target maintainers + +- [Chris Copeland](https://github.com/chrisnc), `chris@chrisnc.net` + +## Requirements + +When using the big-endian version of this target, note that some variants of +the Cortex-R have both big-endian instructions and data. This configuration is +known as BE-32, while data-only big-endianness is known as BE-8. To build +programs for BE-32 processors, the GNU linker must be used with the `-mbe32` +option. See [ARM Cortex-R Series Programmer's Guide: Endianness][endianness] +for more details about different endian modes. + +When using the hardfloat targets, the minimum floating-point features assumed +are those of the `vfpv3-d16`, which includes single- and double-precision, with +16 double-precision registers. This floating-point unit appears in Cortex-R4F +and Cortex-R5F processors. See [VFP in the Cortex-R processors][vfp] +for more details on the possible FPU variants. + +If your processor supports a different set of floating-point features than the +default expectations of `vfpv3-d16`, then these should also be enabled or +disabled as needed with `-C target-feature=(+/-)`. + +[endianness]: https://developer.arm.com/documentation/den0042/a/Coding-for-Cortex-R-Processors/Endianness + +[vfp]: https://developer.arm.com/documentation/den0042/a/Floating-Point/Floating-point-basics-and-the-IEEE-754-standard/VFP-in-the-Cortex-R-processors + +## Cross-compilation toolchains and C code + +This target supports C code compiled with the `arm-none-eabi` target triple and +`-march=armv7-r` or a suitable `-mcpu` flag. From ffdc6c7b65b3d5bc8b2f1a6aaf1877552cb2541e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Tue, 19 Dec 2023 14:25:24 +0100 Subject: [PATCH 06/10] effects: fix comment --- compiler/rustc_hir_analysis/src/astconv/generics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_hir_analysis/src/astconv/generics.rs b/compiler/rustc_hir_analysis/src/astconv/generics.rs index be73c027fdc86..b495b00ec7047 100644 --- a/compiler/rustc_hir_analysis/src/astconv/generics.rs +++ b/compiler/rustc_hir_analysis/src/astconv/generics.rs @@ -262,7 +262,7 @@ pub fn create_args_for_parent_generic_args<'tcx, 'a>( // impl const PartialEq for () {} // ``` // - // Since this is a const impl, we need to insert `` at the end of + // Since this is a const impl, we need to insert a host arg at the end of // `PartialEq`'s generics, but this errors since `Rhs` isn't specified. // To work around this, we infer all arguments until we reach the host param. args.push(ctx.inferred_kind(Some(&args), param, infer_args)); From 17705ea0a85237969d29f63a3d828d6fbaba8e2f Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 19 Dec 2023 17:39:48 +0100 Subject: [PATCH 07/10] add comment --- .../src/solve/eval_ctxt/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs index ee0fc8139a22c..76c50a111027d 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs @@ -395,6 +395,20 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { original_values: Vec>, response: CanonicalResponse<'tcx>, ) -> Result<(Certainty, bool, Vec>>), NoSolution> { + // The old solver did not evaluate nested goals when normalizing. + // It returned the selection constraints allowing a `Projection` + // obligation to not hold in coherence while avoiding the fatal error + // from overflow. + // + // We match this behavior here by considering all constraints + // from nested goals which are not from where-bounds. We will already + // need to track which nested goals are required by impl where-bounds + // for coinductive cycles, so we simply reuse that here. + // + // While we could consider overflow constraints in more cases, this should + // not be necessary for backcompat and results in better perf. It also + // avoids a potential inconsistency which would otherwise require some + // tracking for root goals as well. See #119071 for an example. let keep_overflow_constraints = || { self.search_graph.current_goal_is_normalizes_to() && source != GoalSource::ImplWhereBound From 76b3e6de558cc67dca0098def00b0ee55dfc8d58 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Tue, 19 Dec 2023 11:04:34 -0800 Subject: [PATCH 08/10] Fix c_variadic flag and add opaque info to PassMode We should expand the information in PassMode later. --- .../rustc_smir/src/rustc_smir/convert/abi.rs | 21 +++++++++--- compiler/stable_mir/src/abi.rs | 15 ++++---- .../{check_layout.rs => check_abi.rs} | 34 ++++++++++++++++--- 3 files changed, 53 insertions(+), 17 deletions(-) rename tests/ui-fulldeps/stable-mir/{check_layout.rs => check_abi.rs} (79%) diff --git a/compiler/rustc_smir/src/rustc_smir/convert/abi.rs b/compiler/rustc_smir/src/rustc_smir/convert/abi.rs index 46ae0929bcd4a..632e97b32f520 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/abi.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/abi.rs @@ -66,11 +66,14 @@ impl<'tcx> Stable<'tcx> for rustc_target::abi::call::FnAbi<'tcx, ty::Ty<'tcx>> { type T = FnAbi; fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + assert!(self.args.len() >= self.fixed_count as usize); + assert!(!self.c_variadic || matches!(self.conv, Conv::C)); FnAbi { args: self.args.as_ref().stable(tables), ret: self.ret.stable(tables), fixed_count: self.fixed_count, conv: self.conv.stable(tables), + c_variadic: self.c_variadic, } } } @@ -122,10 +125,20 @@ impl<'tcx> Stable<'tcx> for rustc_target::abi::call::PassMode { fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { match self { rustc_target::abi::call::PassMode::Ignore => PassMode::Ignore, - rustc_target::abi::call::PassMode::Direct(_) => PassMode::Direct, - rustc_target::abi::call::PassMode::Pair(_, _) => PassMode::Pair, - rustc_target::abi::call::PassMode::Cast { .. } => PassMode::Cast, - rustc_target::abi::call::PassMode::Indirect { .. } => PassMode::Indirect, + rustc_target::abi::call::PassMode::Direct(attr) => PassMode::Direct(opaque(attr)), + rustc_target::abi::call::PassMode::Pair(first, second) => { + PassMode::Pair(opaque(first), opaque(second)) + } + rustc_target::abi::call::PassMode::Cast { pad_i32, cast } => { + PassMode::Cast { pad_i32: *pad_i32, cast: opaque(cast) } + } + rustc_target::abi::call::PassMode::Indirect { attrs, meta_attrs, on_stack } => { + PassMode::Indirect { + attrs: opaque(attrs), + meta_attrs: opaque(meta_attrs), + on_stack: *on_stack, + } + } } } } diff --git a/compiler/stable_mir/src/abi.rs b/compiler/stable_mir/src/abi.rs index d4e8e874c1f98..53dac6abefb30 100644 --- a/compiler/stable_mir/src/abi.rs +++ b/compiler/stable_mir/src/abi.rs @@ -21,12 +21,9 @@ pub struct FnAbi { /// The ABI convention. pub conv: CallConvention, -} -impl FnAbi { - pub fn is_c_variadic(&self) -> bool { - self.args.len() > self.fixed_count as usize - } + /// Whether this is a variadic C function, + pub c_variadic: bool, } /// Information about the ABI of a function's argument, or return value. @@ -47,15 +44,15 @@ pub enum PassMode { /// Pass the argument directly. /// /// The argument has a layout abi of `Scalar` or `Vector`. - Direct, + Direct(Opaque), /// Pass a pair's elements directly in two arguments. /// /// The argument has a layout abi of `ScalarPair`. - Pair, + Pair(Opaque, Opaque), /// Pass the argument after casting it. - Cast, + Cast { pad_i32: bool, cast: Opaque }, /// Pass the argument indirectly via a hidden pointer. - Indirect, + Indirect { attrs: Opaque, meta_attrs: Opaque, on_stack: bool }, } /// The layout of a type, alongside the type itself. diff --git a/tests/ui-fulldeps/stable-mir/check_layout.rs b/tests/ui-fulldeps/stable-mir/check_abi.rs similarity index 79% rename from tests/ui-fulldeps/stable-mir/check_layout.rs rename to tests/ui-fulldeps/stable-mir/check_abi.rs index 1c33103b0feed..30b42bc3bfafc 100644 --- a/tests/ui-fulldeps/stable-mir/check_layout.rs +++ b/tests/ui-fulldeps/stable-mir/check_abi.rs @@ -23,7 +23,7 @@ use rustc_middle::ty::TyCtxt; use rustc_smir::rustc_internal; use stable_mir::abi::{ArgAbi, CallConvention, FieldsShape, PassMode, VariantsShape}; use stable_mir::mir::mono::Instance; -use stable_mir::{CrateDef, CrateItems, ItemKind}; +use stable_mir::{CrateDef, CrateItem, CrateItems, ItemKind}; use std::assert_matches::assert_matches; use std::convert::TryFrom; use std::io::Write; @@ -35,6 +35,8 @@ const CRATE_NAME: &str = "input"; fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> { // Find items in the local crate. let items = stable_mir::all_local_items(); + + // Test fn_abi let target_fn = *get_item(&items, (ItemKind::Fn, "fn_abi")).unwrap(); let instance = Instance::try_from(target_fn).unwrap(); let fn_abi = instance.fn_abi().unwrap(); @@ -45,9 +47,26 @@ fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> { check_primitive(&fn_abi.args[1]); check_result(fn_abi.ret); + // Test variadic function. + let variadic_fn = *get_item(&items, (ItemKind::Fn, "variadic_fn")).unwrap(); + check_variadic(variadic_fn); + ControlFlow::Continue(()) } +/// Check the variadic function ABI: +/// ```no_run +/// pub unsafe extern "C" fn variadic_fn(n: usize, mut args: ...) -> usize { +/// 0 +/// } +/// ``` +fn check_variadic(variadic_fn: CrateItem) { + let instance = Instance::try_from(variadic_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + assert!(abi.c_variadic); + assert_eq!(abi.args.len(), 1); +} + /// Check the argument to be ignored: `ignore: [u8; 0]`. fn check_ignore(abi: &ArgAbi) { assert!(abi.ty.kind().is_array()); @@ -60,7 +79,7 @@ fn check_ignore(abi: &ArgAbi) { /// Check the primitive argument: `primitive: char`. fn check_primitive(abi: &ArgAbi) { assert!(abi.ty.kind().is_char()); - assert_eq!(abi.mode, PassMode::Direct); + assert_matches!(abi.mode, PassMode::Direct(_)); let layout = abi.layout.shape(); assert!(layout.is_sized()); assert!(!layout.is_1zst()); @@ -70,7 +89,7 @@ fn check_primitive(abi: &ArgAbi) { /// Check the return value: `Result`. fn check_result(abi: ArgAbi) { assert!(abi.ty.kind().is_enum()); - assert_eq!(abi.mode, PassMode::Indirect); + assert_matches!(abi.mode, PassMode::Indirect { .. }); let layout = abi.layout.shape(); assert!(layout.is_sized()); assert_matches!(layout.fields, FieldsShape::Arbitrary { .. }); @@ -106,11 +125,18 @@ fn generate_input(path: &str) -> std::io::Result<()> { write!( file, r#" - #[allow(unused_variables)] + #![feature(c_variadic)] + #![allow(unused_variables)] + pub fn fn_abi(ignore: [u8; 0], primitive: char) -> Result {{ // We only care about the signature. todo!() }} + + + pub unsafe extern "C" fn variadic_fn(n: usize, mut args: ...) -> usize {{ + 0 + }} "# )?; Ok(()) From d46df80c73aefdbad1a872337c3708f51ff3c376 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Tue, 12 Dec 2023 16:47:32 +0100 Subject: [PATCH 09/10] rustc_codegen_ssa: Don't let `IncorrectCguReuseType` errors get lost In [100753], `IncorrectCguReuseType` accidentally stopped being emitted. Begin emitting it again rather than just blindly dropping it, and adjust tests accordingly. [100753]: https://github.com/rust-lang/rust/pull/100753/commits/706452eba74026c51e8d0fa30aee2497c69eafc0#diff-048389738ddcbe0f9765291a29db1fed9a5f03693d4781cfb5aaa97ffb3c7f84 --- .../rustc_codegen_ssa/src/assert_module_sources.rs | 4 ++-- .../thinlto/cgu_invalidated_via_import.rs | 4 ++-- tests/incremental/thinlto/cgu_keeps_identical_fn.rs | 12 ++++++++---- .../independent_cgus_dont_affect_each_other.rs | 8 ++++---- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs index 01d1b1059b91c..a5bd10ecb34b5 100644 --- a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs +++ b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs @@ -278,13 +278,13 @@ impl CguReuseTracker { if error { let at_least = if at_least { 1 } else { 0 }; - errors::IncorrectCguReuseType { + sess.emit_err(errors::IncorrectCguReuseType { span: *error_span, cgu_user_name, actual_reuse, expected_reuse, at_least, - }; + }); } } else { sess.emit_fatal(errors::CguNotRecorded { cgu_user_name, cgu_name }); diff --git a/tests/incremental/thinlto/cgu_invalidated_via_import.rs b/tests/incremental/thinlto/cgu_invalidated_via_import.rs index e0cd385eff3b9..a81b4f7e9d0d2 100644 --- a/tests/incremental/thinlto/cgu_invalidated_via_import.rs +++ b/tests/incremental/thinlto/cgu_invalidated_via_import.rs @@ -14,14 +14,14 @@ kind="no")] #![rustc_expected_cgu_reuse(module="cgu_invalidated_via_import-foo", cfg="cfail3", - kind="post-lto")] + kind="pre-lto")] // Should be "post-lto", see issue #119076 #![rustc_expected_cgu_reuse(module="cgu_invalidated_via_import-bar", cfg="cfail2", kind="pre-lto")] #![rustc_expected_cgu_reuse(module="cgu_invalidated_via_import-bar", cfg="cfail3", - kind="post-lto")] + kind="pre-lto")] // Should be "post-lto", see issue #119076 mod foo { diff --git a/tests/incremental/thinlto/cgu_keeps_identical_fn.rs b/tests/incremental/thinlto/cgu_keeps_identical_fn.rs index 781aae578d443..9e840f67ab27a 100644 --- a/tests/incremental/thinlto/cgu_keeps_identical_fn.rs +++ b/tests/incremental/thinlto/cgu_keeps_identical_fn.rs @@ -9,21 +9,25 @@ #![feature(rustc_attrs)] #![crate_type = "rlib"] -#![rustc_expected_cgu_reuse(module = "cgu_keeps_identical_fn-foo", cfg = "cfail2", kind = "no")] +#![rustc_expected_cgu_reuse( + module = "cgu_keeps_identical_fn-foo", + cfg = "cfail2", + kind = "pre-lto" +)] #![rustc_expected_cgu_reuse( module = "cgu_keeps_identical_fn-foo", cfg = "cfail3", - kind = "post-lto" + kind = "pre-lto" // Should be "post-lto", see issue #119076 )] #![rustc_expected_cgu_reuse( module = "cgu_keeps_identical_fn-bar", cfg = "cfail2", - kind = "post-lto" + kind = "pre-lto" // Should be "post-lto", see issue #119076 )] #![rustc_expected_cgu_reuse( module = "cgu_keeps_identical_fn-bar", cfg = "cfail3", - kind = "post-lto" + kind = "pre-lto" // Should be "post-lto", see issue #119076 )] mod foo { diff --git a/tests/incremental/thinlto/independent_cgus_dont_affect_each_other.rs b/tests/incremental/thinlto/independent_cgus_dont_affect_each_other.rs index 8aa036ec97876..45eb13828743e 100644 --- a/tests/incremental/thinlto/independent_cgus_dont_affect_each_other.rs +++ b/tests/incremental/thinlto/independent_cgus_dont_affect_each_other.rs @@ -13,21 +13,21 @@ kind="no")] #![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-foo", cfg="cfail3", - kind="post-lto")] + kind="pre-lto")] // Should be "post-lto", see issue #119076 #![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-bar", cfg="cfail2", kind="pre-lto")] #![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-bar", cfg="cfail3", - kind="post-lto")] + kind="pre-lto")] // Should be "post-lto", see issue #119076 #![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-baz", cfg="cfail2", - kind="post-lto")] + kind="pre-lto")] // Should be "post-lto", see issue #119076 #![rustc_expected_cgu_reuse(module="independent_cgus_dont_affect_each_other-baz", cfg="cfail3", - kind="post-lto")] + kind="pre-lto")] // Should be "post-lto", see issue #119076 mod foo { #[cfg(cfail1)] From 2c9602587472a94afc595f0f5adc7a0b85e15201 Mon Sep 17 00:00:00 2001 From: Carter Hunt Fogelman Date: Wed, 6 Dec 2023 16:56:45 -0800 Subject: [PATCH 10/10] Improve compiler error for c-strings in pre-2021 --- .../rustc_parse/src/parser/diagnostics.rs | 36 ++++++++-- tests/ui/editions/edition-cstr-2015-2018.rs | 62 +++++++++++++++++ .../ui/editions/edition-cstr-2015-2018.stderr | 67 +++++++++++++++++++ 3 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 tests/ui/editions/edition-cstr-2015-2018.rs create mode 100644 tests/ui/editions/edition-cstr-2015-2018.stderr diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 7ab0d3f35ea66..5770c038e9e8a 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -10,13 +10,13 @@ use crate::errors::{ ConstGenericWithoutBracesSugg, DocCommentDoesNotDocumentAnything, DocCommentOnParamType, DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg, GenericParamsWithoutAngleBrackets, GenericParamsWithoutAngleBracketsSugg, - HelpIdentifierStartsWithNumber, InInTypo, IncorrectAwait, IncorrectSemicolon, - IncorrectUseOfAwait, PatternMethodParamWithoutBody, QuestionMarkInType, QuestionMarkInTypeSugg, - SelfParamNotFirst, StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg, - StructLiteralNeedingParens, StructLiteralNeedingParensSugg, SuggAddMissingLetStmt, - SuggEscapeIdentifier, SuggRemoveComma, TernaryOperator, UnexpectedConstInGenericParam, - UnexpectedConstParamDeclaration, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, - UseEqInstead, WrapType, + HelpIdentifierStartsWithNumber, HelpUseLatestEdition, InInTypo, IncorrectAwait, + IncorrectSemicolon, IncorrectUseOfAwait, PatternMethodParamWithoutBody, QuestionMarkInType, + QuestionMarkInTypeSugg, SelfParamNotFirst, StructLiteralBodyWithoutPath, + StructLiteralBodyWithoutPathSugg, StructLiteralNeedingParens, StructLiteralNeedingParensSugg, + SuggAddMissingLetStmt, SuggEscapeIdentifier, SuggRemoveComma, TernaryOperator, + UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration, + UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, WrapType, }; use crate::fluent_generated as fluent; use crate::parser; @@ -640,6 +640,28 @@ impl<'a> Parser<'a> { } } + // Try to detect an intended c-string literal while using a pre-2021 edition. The heuristic + // here is to identify a cooked, uninterpolated `c` id immediately followed by a string, or + // a cooked, uninterpolated `cr` id immediately followed by a string or a `#`, in an edition + // where c-string literals are not allowed. There is the very slight possibility of a false + // positive for a `cr#` that wasn't intended to start a c-string literal, but identifying + // that in the parser requires unbounded lookahead, so we only add a hint to the existing + // error rather than replacing it entirely. + if ((self.prev_token.kind == TokenKind::Ident(sym::c, false) + && matches!(&self.token.kind, TokenKind::Literal(token::Lit { kind: token::Str, .. }))) + || (self.prev_token.kind == TokenKind::Ident(sym::cr, false) + && matches!( + &self.token.kind, + TokenKind::Literal(token::Lit { kind: token::Str, .. }) | token::Pound + ))) + && self.prev_token.span.hi() == self.token.span.lo() + && !self.token.span.at_least_rust_2021() + { + err.note("you may be trying to write a c-string literal"); + err.note("c-string literals require Rust 2021 or later"); + HelpUseLatestEdition::new().add_to_diagnostic(&mut err); + } + // `pub` may be used for an item or `pub(crate)` if self.prev_token.is_ident_named(sym::public) && (self.token.can_begin_item() diff --git a/tests/ui/editions/edition-cstr-2015-2018.rs b/tests/ui/editions/edition-cstr-2015-2018.rs new file mode 100644 index 0000000000000..4c35c48646a03 --- /dev/null +++ b/tests/ui/editions/edition-cstr-2015-2018.rs @@ -0,0 +1,62 @@ +macro_rules! construct { ($x:ident) => { $x"str" } } + //~^ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"str"` + //~| NOTE expected one of 8 possible tokens + +macro_rules! contain { () => { c"str" } } + //~^ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"str"` + //~| NOTE expected one of 8 possible tokens + //~| NOTE you may be trying to write a c-string literal + //~| NOTE c-string literals require Rust 2021 or later + //~| HELP pass `--edition 2021` to `rustc` + //~| NOTE for more on editions, read https://doc.rust-lang.org/edition-guide + +fn check_macro_construct() { + construct!(c); //~ NOTE in this expansion of construct! +} + +fn check_macro_contain() { + contain!(); + //~^ NOTE in this expansion of contain! + //~| NOTE in this expansion of contain! + //~| NOTE in this expansion of contain! + //~| NOTE in this expansion of contain! + //~| NOTE in this expansion of contain! +} + +fn check_basic() { + c"str"; + //~^ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"str"` + //~| NOTE expected one of 8 possible tokens + //~| NOTE you may be trying to write a c-string literal + //~| NOTE c-string literals require Rust 2021 or later + //~| HELP pass `--edition 2021` to `rustc` + //~| NOTE for more on editions, read https://doc.rust-lang.org/edition-guide +} + +fn check_craw() { + cr"str"; + //~^ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"str"` + //~| NOTE expected one of 8 possible tokens + //~| NOTE you may be trying to write a c-string literal + //~| NOTE c-string literals require Rust 2021 or later + //~| HELP pass `--edition 2021` to `rustc` + //~| NOTE for more on editions, read https://doc.rust-lang.org/edition-guide +} + +fn check_craw_hash() { + cr##"str"##; + //~^ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `#` + //~| NOTE expected one of 8 possible tokens + //~| NOTE you may be trying to write a c-string literal + //~| NOTE c-string literals require Rust 2021 or later + //~| HELP pass `--edition 2021` to `rustc` + //~| NOTE for more on editions, read https://doc.rust-lang.org/edition-guide +} + +fn check_cstr_space() { + c "str"; + //~^ ERROR expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"str"` + //~| NOTE expected one of 8 possible tokens +} + +fn main() {} diff --git a/tests/ui/editions/edition-cstr-2015-2018.stderr b/tests/ui/editions/edition-cstr-2015-2018.stderr new file mode 100644 index 0000000000000..b864df308ef99 --- /dev/null +++ b/tests/ui/editions/edition-cstr-2015-2018.stderr @@ -0,0 +1,67 @@ +error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"str"` + --> $DIR/edition-cstr-2015-2018.rs:27:6 + | +LL | c"str"; + | ^^^^^ expected one of 8 possible tokens + | + = note: you may be trying to write a c-string literal + = note: c-string literals require Rust 2021 or later + = help: pass `--edition 2021` to `rustc` + = note: for more on editions, read https://doc.rust-lang.org/edition-guide + +error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"str"` + --> $DIR/edition-cstr-2015-2018.rs:37:7 + | +LL | cr"str"; + | ^^^^^ expected one of 8 possible tokens + | + = note: you may be trying to write a c-string literal + = note: c-string literals require Rust 2021 or later + = help: pass `--edition 2021` to `rustc` + = note: for more on editions, read https://doc.rust-lang.org/edition-guide + +error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `#` + --> $DIR/edition-cstr-2015-2018.rs:47:7 + | +LL | cr##"str"##; + | ^ expected one of 8 possible tokens + | + = note: you may be trying to write a c-string literal + = note: c-string literals require Rust 2021 or later + = help: pass `--edition 2021` to `rustc` + = note: for more on editions, read https://doc.rust-lang.org/edition-guide + +error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"str"` + --> $DIR/edition-cstr-2015-2018.rs:57:7 + | +LL | c "str"; + | ^^^^^ expected one of 8 possible tokens + +error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"str"` + --> $DIR/edition-cstr-2015-2018.rs:1:44 + | +LL | macro_rules! construct { ($x:ident) => { $x"str" } } + | ^^^^^ expected one of 8 possible tokens +... +LL | construct!(c); + | ------------- in this macro invocation + | + = note: this error originates in the macro `construct` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `"str"` + --> $DIR/edition-cstr-2015-2018.rs:5:33 + | +LL | macro_rules! contain { () => { c"str" } } + | ^^^^^ expected one of 8 possible tokens +... +LL | contain!(); + | ---------- in this macro invocation + | + = note: you may be trying to write a c-string literal + = note: c-string literals require Rust 2021 or later + = help: pass `--edition 2021` to `rustc` + = note: for more on editions, read https://doc.rust-lang.org/edition-guide + = note: this error originates in the macro `contain` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 6 previous errors +