diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 1c1b9a247f7a2..c3bc86a352644 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1018,7 +1018,7 @@ impl<'hir> LoweringContext<'_, 'hir> { expr.span, hir::ExprKind::Err(self.dcx().emit_err(AwaitOnlyInAsyncFnAndBlocks { await_kw_span, - item_span: self.current_item, + item_span: self.current_item_span, })), ); return hir::ExprKind::Block( @@ -1712,7 +1712,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } Some(hir::CoroutineKind::Coroutine(_)) => false, None => { - let suggestion = self.current_item.map(|s| s.shrink_to_lo()); + let suggestion = self.current_item_span.map(|s| s.shrink_to_lo()); self.dcx().emit_err(YieldInClosure { span, suggestion }); self.coroutine_kind = Some(hir::CoroutineKind::Coroutine(Movability::Movable)); diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index edf184f568b22..1073706499919 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -5,11 +5,8 @@ use rustc_errors::{E0570, ErrorGuaranteed, struct_span_code_err}; use rustc_hir::attrs::{AttributeKind, EiiImplResolution}; use rustc_hir::def::{DefKind, PerNS, Res}; use rustc_hir::{ - self as hir, CRATE_OWNER_ID, HirId, ImplItemImplKind, LifetimeSource, PredicateOrigin, Target, - find_attr, + self as hir, HirId, ImplItemImplKind, LifetimeSource, PredicateOrigin, Target, find_attr, }; -use rustc_middle::middle::resolve::ResolverAstLowering; -use rustc_middle::ty::TyCtxt; use rustc_middle::ty::data_structures::IndexMap; use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::edit_distance::find_best_match_for_name; @@ -28,14 +25,9 @@ use super::{ }; use crate::diagnostics::{ConstComptimeFn, ResolvingRestrictionKind, RestrictionAncestorOnly}; -pub(super) struct ItemLowerer<'a, 'hir> { - pub(super) tcx: TyCtxt<'hir>, - pub(super) resolver: &'a ResolverAstLowering<'hir>, -} - -/// When we have a ty alias we *may* have two where clauses. To give the best diagnostics, we set the span -/// to the where clause that is preferred, if it exists. Otherwise, it sets the span to the other where -/// clause if it exists. +/// When we have a ty alias we *may* have two where clauses. To give the best diagnostics, we set +/// the span to the where clause that is preferred, if it exists. Otherwise, it sets the span to +/// the other where clause if it exists. fn add_ty_alias_where_clause( generics: &mut ast::Generics, after_where_clause: &ast::WhereClause, @@ -52,48 +44,6 @@ fn add_ty_alias_where_clause( if before.0 || !after.0 { before } else { after }; } -impl<'hir> ItemLowerer<'_, 'hir> { - fn with_lctx( - &mut self, - owner: NodeId, - f: impl FnOnce(&mut LoweringContext<'_, 'hir>) -> hir::OwnerNode<'hir>, - ) -> hir::MaybeOwner<'hir> { - let mut lctx = LoweringContext::new(self.tcx, self.resolver, owner); - - let item = f(&mut lctx); - - let info = lctx.curr_owner.into_owner_info(self.tcx, item); - hir::MaybeOwner::Owner(lctx.arena.alloc(info)) - } - - #[instrument(level = "debug", skip(self, c))] - pub(super) fn lower_crate(&mut self, c: &Crate) -> hir::MaybeOwner<'hir> { - self.with_lctx(CRATE_NODE_ID, |lctx| { - debug_assert_eq!(lctx.curr_owner.owner_id(), CRATE_OWNER_ID); - let module = lctx.lower_mod(&c.items, &c.spans); - lctx.lower_attrs(hir::CRATE_HIR_ID, &c.attrs, c.spans.inner_span, Target::Crate); - hir::OwnerNode::Crate(module) - }) - } - - #[instrument(level = "debug", skip(self))] - pub(super) fn lower_item(&mut self, item: &Item) -> hir::MaybeOwner<'hir> { - self.with_lctx(item.id, |lctx| hir::OwnerNode::Item(lctx.lower_item(item))) - } - - pub(super) fn lower_trait_item(&mut self, item: &AssocItem) -> hir::MaybeOwner<'hir> { - self.with_lctx(item.id, |lctx| hir::OwnerNode::TraitItem(lctx.lower_trait_item(item))) - } - - pub(super) fn lower_impl_item(&mut self, item: &AssocItem) -> hir::MaybeOwner<'hir> { - self.with_lctx(item.id, |lctx| hir::OwnerNode::ImplItem(lctx.lower_impl_item(item))) - } - - pub(super) fn lower_foreign_item(&mut self, item: &ForeignItem) -> hir::MaybeOwner<'hir> { - self.with_lctx(item.id, |lctx| hir::OwnerNode::ForeignItem(lctx.lower_foreign_item(item))) - } -} - impl<'hir> LoweringContext<'_, 'hir> { pub(super) fn lower_mod( &mut self, @@ -203,7 +153,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn lower_item(&mut self, i: &Item) -> &'hir hir::Item<'hir> { + pub(super) fn lower_item(&mut self, i: &Item) -> &'hir hir::Item<'hir> { let owner_id = self.curr_owner.owner_id(); let hir_id: HirId = owner_id.into(); let vis_span = self.lower_span(i.vis.span); @@ -544,7 +494,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } ItemKind::MacroDef(ident, MacroDef { body, macro_rules, eii_declaration: _ }) => { let ident = self.lower_ident(*ident); - let body = Box::new(self.lower_delim_args(body)); + let body = body.clone(); let def_id = self.curr_owner.owner.def_id; let def_kind = self.tcx.def_kind(def_id); let DefKind::Macro(macro_kinds) = def_kind else { @@ -730,7 +680,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn lower_foreign_item(&mut self, i: &ForeignItem) -> &'hir hir::ForeignItem<'hir> { + pub(super) fn lower_foreign_item(&mut self, i: &ForeignItem) -> &'hir hir::ForeignItem<'hir> { let owner_id = self.curr_owner.owner_id(); let hir_id: HirId = owner_id.into(); let attrs = @@ -911,7 +861,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn lower_trait_item(&mut self, i: &AssocItem) -> &'hir hir::TraitItem<'hir> { + pub(super) fn lower_trait_item(&mut self, i: &AssocItem) -> &'hir hir::TraitItem<'hir> { let trait_item_def_id = self.curr_owner.owner_id(); let hir_id: HirId = trait_item_def_id.into(); let attrs = self.lower_attrs( @@ -1160,7 +1110,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ident } - fn lower_impl_item(&mut self, i: &AssocItem) -> &'hir hir::ImplItem<'hir> { + pub(super) fn lower_impl_item(&mut self, i: &AssocItem) -> &'hir hir::ImplItem<'hir> { let owner_id = self.curr_owner.owner_id(); let hir_id: HirId = owner_id.into(); let parent_id = self.tcx.local_parent(owner_id.def_id); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 19c37f4a76065..410823919c523 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -60,8 +60,9 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId, LocalDefIdMap}; use rustc_hir::definitions::PerParentDisambiguatorState; use rustc_hir::lints::DelayedLint; use rustc_hir::{ - self as hir, AngleBrackets, ConstArg, GenericArg, HirId, ItemLocalMap, LifetimeSource, - LifetimeSyntax, MissingLifetimeKind, ParamName, Target, TraitCandidate, find_attr, + self as hir, AngleBrackets, CRATE_OWNER_ID, ConstArg, GenericArg, HirId, ItemLocalMap, + LifetimeSource, LifetimeSyntax, MissingLifetimeKind, ParamName, Target, TraitCandidate, + find_attr, }; use rustc_index::{Idx, IndexSlice, IndexVec}; use rustc_macros::extension; @@ -296,7 +297,7 @@ struct LoweringContext<'a, 'hir> { /// Used to get the current `fn`'s def span to point to when using `await` /// outside of an `async fn`. - current_item: Option, + current_item_span: Option, try_block_scope: TryBlockScope, loop_scope: Option, @@ -366,7 +367,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { is_in_dyn_type: false, coroutine_kind: None, task_context: None, - current_item: None, + current_item_span: None, move_expr_bindings: Vec::new(), lowering_move_expr_initializer: false, @@ -783,15 +784,37 @@ fn lower_to_hir(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::MaybeOwner<'_> { return fallback_to_ancestor(tcx.local_parent(def_id)); }; - let mut item_lowerer = item::ItemLowerer { tcx, resolver: &*resolver }; + fn with_lctx<'hir>( + tcx: TyCtxt<'hir>, + resolver: &ResolverAstLowering<'hir>, + owner: NodeId, + f: impl FnOnce(&mut LoweringContext<'_, 'hir>) -> hir::OwnerNode<'hir>, + ) -> hir::MaybeOwner<'hir> { + let mut lctx = LoweringContext::new(tcx, resolver, owner); + let item = f(&mut lctx); + hir::MaybeOwner::Owner(lctx.curr_owner.into_owner_info(tcx, item)) + } let item = match &node { // The item existed in the AST. - AstOwner::Crate(c) => item_lowerer.lower_crate(&c), - AstOwner::Item(item) => item_lowerer.lower_item(&item), - AstOwner::TraitItem(item) => item_lowerer.lower_trait_item(&item), - AstOwner::ImplItem(item) => item_lowerer.lower_impl_item(&item), - AstOwner::ForeignItem(item) => item_lowerer.lower_foreign_item(&item), + AstOwner::Crate(c) => with_lctx(tcx, &*resolver, CRATE_NODE_ID, |lctx| { + debug_assert_eq!(lctx.curr_owner.owner_id(), CRATE_OWNER_ID); + let module = lctx.lower_mod(&c.items, &c.spans); + lctx.lower_attrs(hir::CRATE_HIR_ID, &c.attrs, c.spans.inner_span, Target::Crate); + hir::OwnerNode::Crate(module) + }), + AstOwner::Item(item) => { + with_lctx(tcx, &*resolver, item.id, |lctx| hir::OwnerNode::Item(lctx.lower_item(item))) + } + AstOwner::TraitItem(item) => with_lctx(tcx, &*resolver, item.id, |lctx| { + hir::OwnerNode::TraitItem(lctx.lower_trait_item(item)) + }), + AstOwner::ImplItem(item) => with_lctx(tcx, &*resolver, item.id, |lctx| { + hir::OwnerNode::ImplItem(lctx.lower_impl_item(item)) + }), + AstOwner::ForeignItem(item) => with_lctx(tcx, &*resolver, item.id, |lctx| { + hir::OwnerNode::ForeignItem(lctx.lower_foreign_item(item)) + }), AstOwner::NestedUseTree(owner_id) => fallback_to_ancestor(*owner_id), // The item existed in the AST, but is not a HIR owner. // Fetch the correct information from its parent. @@ -824,7 +847,7 @@ enum GenericArgsMode { ParenSugar, /// Allow RTN, don't allow paren sugar. ReturnTypeNotation, - // Error if parenthesized generics or RTN are encountered. + /// Error if parenthesized generics or RTN are encountered. Err, /// Silence errors when lowering generics. Only used with `Res::Err`. Silence, @@ -982,7 +1005,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } #[instrument(level = "trace", skip(self))] - fn lower_res(&mut self, res: Res) -> Res { + fn lower_res(&self, res: Res) -> Res { let res: Result = res.apply_id(|id| { let owner = self.curr_owner.owner_id(); let local_id = @@ -999,11 +1022,11 @@ impl<'hir> LoweringContext<'_, 'hir> { res.unwrap_or(Res::Err) } - fn expect_full_res(&mut self, id: NodeId) -> Res { + fn expect_full_res(&self, id: NodeId) -> Res { self.get_partial_res(id).map_or(Res::Err, |pr| pr.expect_full_res()) } - fn lower_import_res(&mut self, id: NodeId, span: Span) -> PerNS> { + fn lower_import_res(&self, id: NodeId, span: Span) -> PerNS> { debug_assert_eq!(id, self.curr_owner.owner.id); let per_ns = self.curr_owner.owner.import_res.map(|res| res.map(|res| self.lower_res(res))); if per_ns.is_empty() { @@ -1154,8 +1177,8 @@ impl<'hir> LoweringContext<'_, 'hir> { } fn with_new_scopes(&mut self, scope_span: Span, f: impl FnOnce(&mut Self) -> T) -> T { - let current_item = self.current_item; - self.current_item = Some(scope_span); + let current_item_span = self.current_item_span; + self.current_item_span = Some(scope_span); let was_in_loop_condition = self.is_in_loop_condition; self.is_in_loop_condition = false; @@ -1172,7 +1195,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.is_in_loop_condition = was_in_loop_condition; - self.current_item = current_item; + self.current_item_span = current_item_span; ret } @@ -1261,10 +1284,6 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn lower_delim_args(&self, args: &DelimArgs) -> DelimArgs { - args.clone() - } - /// Lower an associated item constraint. #[instrument(level = "debug", skip_all)] fn lower_assoc_item_constraint( @@ -1647,8 +1666,8 @@ impl<'hir> LoweringContext<'_, 'hir> { self.lower_array_length_to_const_arg(length), ), TyKind::TraitObject(bounds, kind) => { - let mut lifetime_bound = None; let (bounds, lifetime_bound) = self.with_dyn_type_scope(true, |this| { + let mut lifetime_bound = None; let bounds = this.arena.alloc_from_iter(bounds.iter().filter_map(|bound| match bound { // We can safely ignore constness here since AST validation @@ -1681,9 +1700,7 @@ impl<'hir> LoweringContext<'_, 'hir> { None } })); - let lifetime_bound = - lifetime_bound.unwrap_or_else(|| this.elided_dyn_bound(t.span)); - (bounds, lifetime_bound) + (bounds, lifetime_bound.unwrap_or_else(|| this.elided_dyn_bound(t.span))) }); hir::TyKind::TraitObject(bounds, TaggedRef::new(lifetime_bound, *kind)) } @@ -3058,7 +3075,7 @@ impl<'hir> LoweringContext<'_, 'hir> { })) } - fn lower_unsafe_source(&mut self, u: UnsafeSource) -> hir::UnsafeSource { + fn lower_unsafe_source(&self, u: UnsafeSource) -> hir::UnsafeSource { match u { CompilerGenerated => hir::UnsafeSource::CompilerGenerated, UserProvided => hir::UnsafeSource::UserProvided, @@ -3066,7 +3083,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } fn lower_trait_bound_modifiers( - &mut self, + &self, modifiers: TraitBoundModifiers, ) -> hir::TraitBoundModifiers { let constness = match modifiers.constness { diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 35009c3bad485..2f03fec0d2245 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -409,16 +409,6 @@ impl<'tcx, N: Idx> RegionValues<'tcx, N> { } } -/// For debugging purposes, returns a pretty-printed string of the given points. -pub(crate) fn pretty_print_points( - location_map: &DenseLocationMap, - points: impl IntoIterator, -) -> String { - pretty_print_region_elements( - points.into_iter().map(|p| location_map.to_location(p)).map(RegionElement::Location), - ) -} - /// For debugging purposes, returns a pretty-printed string of the given region elements. fn pretty_print_region_elements<'tcx>( elements: impl IntoIterator>, diff --git a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs index 2de6635e93ac7..c2fa8ab8637af 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs @@ -1,15 +1,18 @@ use itertools::{Either, Itertools}; use rustc_data_structures::fx::FxHashSet; +use rustc_index::interval::IntervalSet; use rustc_middle::mir::visit::{TyContext, Visitor}; use rustc_middle::mir::{Body, Local, Location, SourceInfo}; use rustc_middle::ty::relate::Relate; use rustc_middle::ty::{GenericArgsRef, Region, RegionVid, Ty, TyCtxt, TypeVisitable}; use rustc_mir_dataflow::move_paths::MoveData; -use rustc_mir_dataflow::points::DenseLocationMap; +use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex}; use rustc_span::span_bug; +use rustc_trait_selection::traits::outlives_for_liveness::FreeRegionsVisitor; use tracing::debug; use super::TypeChecker; +use crate::BorrowckInferCtxt; use crate::constraints::OutlivesConstraintSet; use crate::polonius::{PoloniusContext, record_live_region_variance}; use crate::region_infer::values::LivenessValues; @@ -229,3 +232,18 @@ impl<'a, 'tcx> LiveVariablesVisitor<'a, 'tcx> { } } } + +pub(crate) fn make_all_regions_live<'tcx>( + infcx: &BorrowckInferCtxt<'tcx>, + universal_regions: &UniversalRegions<'tcx>, + liveness: &mut LivenessValues, + value: impl TypeVisitable>, + live_at: &IntervalSet, +) { + debug!("make_all_regions_live(value={value:?})"); + value.visit_with(&mut FreeRegionsVisitor { + tcx: infcx.tcx, + param_env: infcx.param_env, + op: |r| liveness.add_points(universal_regions.to_region_vid(r), live_at), + }); +} diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 90126866cd500..d0302faa513b2 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -6,7 +6,7 @@ use rustc_infer::infer::canonical::QueryRegionConstraints; use rustc_infer::traits::TraitErrors; use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location}; use rustc_middle::traits::query::DropckOutlivesResult; -use rustc_middle::ty::{Ty, TyCtxt, TypeVisitable, TypeVisitableExt}; +use rustc_middle::ty::{GenericArg, Ty, TypeVisitableExt}; use rustc_mir_dataflow::impls::MaybeInitializedPlaces; use rustc_mir_dataflow::move_paths::{HasMoveData, MoveData, MovePathIndex}; use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex}; @@ -14,16 +14,17 @@ use rustc_mir_dataflow::{Analysis, MaybeReachable, ResultsCursor}; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::traits::ObligationCtxt; -use rustc_trait_selection::traits::outlives_for_liveness::FreeRegionsVisitor; use rustc_trait_selection::traits::query::dropck_outlives; use rustc_trait_selection::traits::query::type_op::{DropckOutlives, TypeOpOutput}; use tracing::debug; -use crate::BorrowckInferCtxt; -use crate::polonius::{self, record_live_region_variance}; -use crate::region_infer::values; +use crate::polonius::{LiveRegionVariances, record_live_region_variance}; +use crate::region_infer::values::LivenessValues; use crate::type_check::liveness::local_use_map::LocalUseMap; +use crate::type_check::liveness::make_all_regions_live; use crate::type_check::{NormalizeLocation, TypeChecker}; +use crate::universal_regions::UniversalRegions; +use crate::{BorrowckInferCtxt, polonius}; /// This is the heart of the liveness computation. For each variable X /// that requires a liveness computation, it walks over all the uses @@ -48,33 +49,28 @@ pub(super) fn trace<'tcx>( ) { let _timer = typeck.tcx().prof.generic_activity("borrowck_liveness_trace"); - let local_use_map = &LocalUseMap::build(&relevant_live_locals, location_map, typeck.body); - let cx = LivenessContext { - typeck, - flow_inits: None, + let local_use_map = LocalUseMap::build(&relevant_live_locals, location_map, typeck.body); + let comp = LivenessComputation::new( + typeck.infcx, + typeck.body, location_map, - local_use_map, move_data, - term_states: IndexVec::new(), - exit_states: IndexVec::new(), - drop_data: FxIndexMap::default(), - }; + &local_use_map, + ); - let mut results = LivenessResults::new(cx); + let mut results = LivenessResults::new(typeck, comp); - results.add_extra_drop_facts(relevant_live_locals); + results.record_legacy_polonius_drop_facts(relevant_live_locals); results.compute_for_all_locals(relevant_live_locals); results.dropck_boring_locals(boring_locals); } -/// Contextual state for the type-liveness coroutine. -struct LivenessContext<'a, 'typeck, 'tcx> { - /// Current type-checker, giving us our inference context etc. - /// - /// This also stores the body we're currently analyzing. - typeck: &'a mut TypeChecker<'typeck, 'tcx>, +pub(crate) struct LivenessComputation<'a, 'tcx> { + pub(crate) infcx: &'a BorrowckInferCtxt<'tcx>, + + pub(crate) body: &'a Body<'tcx>, /// Defines the `PointIndex` mapping location_map: &'a DenseLocationMap, @@ -82,9 +78,6 @@ struct LivenessContext<'a, 'typeck, 'tcx> { /// Mapping to/from the various indices used for initialization tracking. move_data: &'a MoveData<'tcx>, - /// Cache for the results of `dropck_outlives` query. - drop_data: FxIndexMap, DropData<'tcx>>, - /// Results of dataflow tracking which variables (and paths) have been /// initialized. Computed lazily when needed by drop-liveness. flow_inits: Option>>, @@ -96,15 +89,6 @@ struct LivenessContext<'a, 'typeck, 'tcx> { // Caches for the results of `initialized_at_terminator` and `initialized_at_exit`. term_states: IndexVec>>>, exit_states: IndexVec>>>, -} - -struct DropData<'tcx> { - dropck_result: DropckOutlivesResult<'tcx>, - region_constraint_data: Option<&'tcx QueryRegionConstraints<'tcx>>, -} - -struct LivenessResults<'a, 'typeck, 'tcx> { - cx: LivenessContext<'a, 'typeck, 'tcx>, /// Set of points that define the current local. defs: DenseBitSet, @@ -125,43 +109,70 @@ struct LivenessResults<'a, 'typeck, 'tcx> { stack: Vec, } +struct LivenessResults<'a, 'typeck, 'tcx> { + /// Current type-checker, giving us our inference context etc. + typeck: &'a mut TypeChecker<'typeck, 'tcx>, + + /// Cache for the results of `dropck_outlives` query. + drop_data: FxIndexMap, DropData<'tcx>>, + + comp: LivenessComputation<'a, 'tcx>, +} + impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { - fn new(cx: LivenessContext<'a, 'typeck, 'tcx>) -> Self { - let num_points = cx.location_map.num_points(); - LivenessResults { - cx, - defs: DenseBitSet::new_empty(num_points), - use_live_at: IntervalSet::new(num_points), - drop_live_at: DenseBitSet::new_empty(num_points), - drop_locations: vec![], - stack: vec![], - } + fn new( + typeck: &'a mut TypeChecker<'typeck, 'tcx>, + comp: LivenessComputation<'a, 'tcx>, + ) -> Self { + LivenessResults { typeck, drop_data: FxIndexMap::default(), comp } } fn compute_for_all_locals(&mut self, relevant_live_locals: &[Local]) { for &local in relevant_live_locals { - self.reset_local_state(); - self.add_defs_for(local); - self.compute_use_live_points_for(local); - self.compute_drop_live_points_for(local); + self.compute_for_local(local); + } + } - let local_ty = self.cx.body().local_decls[local].ty; + fn compute_for_local(&mut self, local: Local) { + // If we end up needing to compute the drop data (because there are + // drop-live points), then we need to register region constraints and + // emit drop facts. + let mut computed_drop_data = None; + + self.comp.compute( + local, + self.typeck.universal_regions, + self.typeck.polonius_context.as_mut().map(|c| &mut c.live_region_variances), + &mut self.typeck.constraints.liveness_constraints, + || { + let local_ty = self.comp.body.local_decls[local].ty; + let local_span = self.comp.body.local_decls[local].source_info.span; + let drop_data = + dropck_local(&self.typeck.infcx, &mut self.drop_data, local_ty, local_span); + let drop_data = computed_drop_data.insert(drop_data); + &drop_data.dropck_result.kinds + }, + ); - if !self.use_live_at.is_empty() { - self.cx.add_use_live_facts_for(local_ty, &self.use_live_at); + if let Some(drop_data) = computed_drop_data { + if let Some(data) = &drop_data.region_constraint_data { + for &drop_location in &self.comp.drop_locations { + self.typeck.push_region_constraints( + drop_location.to_locations(), + ConstraintCategory::Boring, + data, + ); + } } - if !self.drop_live_at.is_empty() { - // `drop_live_at` is using a DenseBitSet, but `add_drop_live_facts_for` expects - // an IntervalSet. We thus convert between those two here. - let mut set: IntervalSet = - IntervalSet::new(self.drop_live_at.domain_size()); - for item in self.drop_live_at.iter() { - // We iterate the `drop_live_at` set from smallest to largest values, so - // we can use append to add things to the interval set at the end. - set.append(item); - } - self.cx.add_drop_live_facts_for(local, local_ty, &self.drop_locations, &set); + for &kind in &drop_data.dropck_result.kinds { + polonius::legacy::emit_drop_facts( + self.typeck.tcx(), + local, + &kind, + self.typeck.universal_regions, + self.typeck.polonius_facts, + ); } } } @@ -174,27 +185,26 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { /// and can therefore safely be dropped. fn dropck_boring_locals(&mut self, boring_locals: &[Local]) { for &local in boring_locals { - let local_ty = self.cx.body().local_decls[local].ty; - let local_span = self.cx.body().local_decls[local].source_info.span; - dropck_local(&self.cx.typeck.infcx, &mut self.cx.drop_data, local_ty, local_span); + let local_ty = self.comp.body.local_decls[local].ty; + let local_span = self.comp.body.local_decls[local].source_info.span; + dropck_local(&self.typeck.infcx, &mut self.drop_data, local_ty, local_span); } } - /// Add extra drop facts needed for Polonius. + /// Add extra drop facts needed for Polonius Legacy. /// /// Add facts for all locals with free regions, since regions may outlive /// the function body only at certain nodes in the CFG. - fn add_extra_drop_facts(&mut self, relevant_live_locals: &[Local]) { - // This collect is more necessary than immediately apparent - // because these facts go into `add_drop_live_facts_for()`, - // which also writes to `polonius_facts`, and so this is genuinely - // a simultaneous overlapping mutable borrow. + fn record_legacy_polonius_drop_facts(&mut self, relevant_live_locals: &[Local]) { + // This is *all wonky* because this used to call a shared + // `add_drop_live_facts_for` function that was also used for regular + // relevant locals. Presumably, this can be cleaned up quite a bit. // FIXME for future hackers: investigate whether this is // actually necessary; these facts come from Polonius // and probably maybe plausibly does not need to go back in. // It may be necessary to just pick out the parts of // `add_drop_live_facts_for()` that make sense. - let Some(facts) = self.cx.typeck.polonius_facts.as_ref() else { return }; + let Some(facts) = self.typeck.polonius_facts.as_ref() else { return }; let facts_to_add: Vec<_> = { let relevant_live_locals: FxIndexSet<_> = relevant_live_locals.iter().copied().collect(); @@ -203,20 +213,155 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { .var_dropped_at .iter() .filter_map(|&(local, location_index)| { - let local_ty = self.cx.body().local_decls[local].ty; + let local_ty = self.comp.body.local_decls[local].ty; if relevant_live_locals.contains(&local) || !local_ty.has_free_regions() { return None; } - let location = self.cx.typeck.location_table.to_location(location_index); + let location = self.typeck.location_table.to_location(location_index); Some((local, local_ty, location)) }) .collect() }; - let live_at = IntervalSet::new(self.cx.location_map.num_points()); + let live_at = IntervalSet::new(self.comp.location_map.num_points()); for (local, local_ty, location) in facts_to_add { - self.cx.add_drop_live_facts_for(local, local_ty, &[location], &live_at); + let local_span = self.comp.body.local_decls[local].source_info.span; + let drop_data = + dropck_local(&self.typeck.infcx, &mut self.drop_data, local_ty, local_span); + + if let Some(data) = &drop_data.region_constraint_data { + self.typeck.push_region_constraints( + location.to_locations(), + ConstraintCategory::Boring, + data, + ); + } + + for &kind in &drop_data.dropck_result.kinds { + make_all_regions_live( + self.typeck.infcx, + self.typeck.universal_regions, + &mut self.typeck.constraints.liveness_constraints, + kind, + &live_at, + ); + polonius::legacy::emit_drop_facts( + self.typeck.tcx(), + local, + &kind, + self.typeck.universal_regions, + self.typeck.polonius_facts, + ); + } + + if let Some(polonius_context) = self.typeck.polonius_context.as_mut() { + record_live_region_variance( + self.typeck.infcx.tcx, + &mut polonius_context.live_region_variances, + self.typeck.universal_regions, + local_ty, + ); + } + } + } +} + +enum InitAtLocation { + Terminator, + Exit, +} + +impl<'a, 'tcx> LivenessComputation<'a, 'tcx> { + pub(crate) fn new( + infcx: &'a BorrowckInferCtxt<'tcx>, + body: &'a Body<'tcx>, + location_map: &'a DenseLocationMap, + move_data: &'a MoveData<'tcx>, + local_use_map: &'a LocalUseMap, + ) -> Self { + let num_points = location_map.num_points(); + LivenessComputation { + infcx, + body, + location_map, + move_data, + flow_inits: None, + local_use_map, + term_states: IndexVec::new(), + exit_states: IndexVec::new(), + defs: DenseBitSet::new_empty(num_points), + use_live_at: IntervalSet::new(num_points), + drop_live_at: DenseBitSet::new_empty(num_points), + drop_locations: vec![], + stack: vec![], + } + } + + /// Compute for a given local the use- and drop-live points + fn compute<'drop_data>( + &mut self, + local: Local, + universal_regions: &UniversalRegions<'tcx>, + live_region_variances: Option<&mut LiveRegionVariances>, + liveness_constraints: &mut LivenessValues, + get_drop_args: impl FnOnce() -> &'drop_data Vec>, + ) where + 'tcx: 'drop_data, + { + self.reset_local_state(); + self.add_defs_for(local); + self.compute_use_live_points_for(local); + self.compute_drop_live_points_for(local); + + let local_ty = self.body.local_decls[local].ty; + + // When using `-Zpolonius=next`, we also record the variance of regions in this live type. + // For dropck in particular, note that we walk the type and not its live components seen in + // the dropck results. See issue #160670. + let is_live_anywhere = !self.use_live_at.is_empty() || !self.drop_live_at.is_empty(); + if is_live_anywhere && let Some(live_region_variances) = live_region_variances { + record_live_region_variance( + self.infcx.tcx, + live_region_variances, + universal_regions, + local_ty, + ); + } + if !self.use_live_at.is_empty() { + make_all_regions_live( + self.infcx, + universal_regions, + liveness_constraints, + local_ty, + &self.use_live_at, + ); + } + if !self.drop_live_at.is_empty() { + let drop_data = get_drop_args(); + + // `drop_live_at` is using a DenseBitSet, but `make_all_regions_live` + // expects an IntervalSet. We thus convert between those two here. + // Using a `DenseBitSet` has better performance, but storing liveness + // as a dense matrix has worse performance. There's probably room here + // for some cleanup, but this works for now. + let mut drop_live_at: IntervalSet = + IntervalSet::new(self.drop_live_at.domain_size()); + for item in self.drop_live_at.iter() { + // We iterate the `drop_live_at` set from smallest to largest values, so + // we can use append to add things to the interval set at the end. + drop_live_at.append(item); + } + + for &kind in drop_data { + make_all_regions_live( + self.infcx, + universal_regions, + liveness_constraints, + kind, + &drop_live_at, + ); + } } } @@ -231,7 +376,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { /// Adds the definitions of `local` into `self.defs`. fn add_defs_for(&mut self, local: Local) { - for def in self.cx.local_use_map.defs(local) { + for def in self.local_use_map.defs(local) { debug!("- defined at {:?}", def); self.defs.insert(def); } @@ -246,14 +391,14 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { fn compute_use_live_points_for(&mut self, local: Local) { debug!("compute_use_live_points_for(local={:?})", local); - self.stack.extend(self.cx.local_use_map.uses(local)); + self.stack.extend(self.local_use_map.uses(local)); while let Some(p) = self.stack.pop() { // We are live in this block from the closest to us of: // // * Inclusively, the block start // * Exclusively, the previous definition (if it's in this block) // * Exclusively, the previous live_at setting (an optimization) - let block_start = self.cx.location_map.to_block_start(p); + let block_start = self.location_map.to_block_start(p); let previous_defs = self.defs.last_set_in(block_start..=p); let previous_live_at = self.use_live_at.last_set_in(block_start..=p); @@ -277,12 +422,12 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // terminators of predecessor basic blocks. Push those onto the // stack so that the next iteration(s) will process them. - let block = self.cx.location_map.to_location(block_start).block; + let block = self.location_map.to_location(block_start).block; self.stack.extend( - self.cx.body().basic_blocks.predecessors()[block] + self.body.basic_blocks.predecessors()[block] .iter() - .map(|&pred_bb| self.cx.body().terminator_loc(pred_bb)) - .map(|pred_loc| self.cx.location_map.point_from_location(pred_loc)), + .map(|&pred_bb| self.body.terminator_loc(pred_bb)) + .map(|pred_loc| self.location_map.point_from_location(pred_loc)), ); } } @@ -300,15 +445,15 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { fn compute_drop_live_points_for(&mut self, local: Local) { debug!("compute_drop_live_points_for(local={:?})", local); - let Some(mpi) = self.cx.move_data.rev_lookup.find_local(local) else { return }; + let Some(mpi) = self.move_data.rev_lookup.find_local(local) else { return }; debug!("compute_drop_live_points_for: mpi = {:?}", mpi); // Find the drops where `local` is initialized. - for drop_point in self.cx.local_use_map.drops(local) { - let location = self.cx.location_map.to_location(drop_point); - debug_assert_eq!(self.cx.body().terminator_loc(location.block), location,); + for drop_point in self.local_use_map.drops(local) { + let location = self.location_map.to_location(drop_point); + debug_assert_eq!(self.body.terminator_loc(location.block), location,); - if self.cx.initialized_at_terminator(location.block, mpi) { + if self.initialized_at_terminator(location.block, mpi) { let inserted = self.drop_live_at.insert(drop_point); // Right now, we should not visit a drop_point twice. // If we do, this will trigger a debug assert so we know we can optimize. @@ -342,8 +487,8 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { fn compute_drop_live_points_for_block(&mut self, mpi: MovePathIndex, term_point: PointIndex) { debug!( "compute_drop_live_points_for_block(mpi={:?}, term_point={:?})", - self.cx.move_data.move_paths[mpi].place, - self.cx.location_map.to_location(term_point), + self.move_data.move_paths[mpi].place, + self.location_map.to_location(term_point), ); // We are only invoked with terminators where `mpi` is @@ -353,14 +498,14 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // Otherwise, scan backwards through the statements in the // block. One of them may be either a definition or use // live point. - let term_location = self.cx.location_map.to_location(term_point); - debug_assert_eq!(self.cx.body().terminator_loc(term_location.block), term_location,); + let term_location = self.location_map.to_location(term_point); + debug_assert_eq!(self.body.terminator_loc(term_location.block), term_location,); let block = term_location.block; - let entry_point = self.cx.location_map.entry_point(term_location.block); + let entry_point = self.location_map.entry_point(term_location.block); for p in (entry_point..term_point).rev() { debug!( "compute_drop_live_points_for_block: p = {:?}", - self.cx.location_map.to_location(p) + self.location_map.to_location(p) ); if self.defs.contains(p) { @@ -379,7 +524,7 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { } } - let body = self.cx.typeck.body; + let body = self.body; for &pred_block in body.basic_blocks.predecessors()[block].iter() { debug!("compute_drop_live_points_for_block: pred_block = {:?}", pred_block,); @@ -401,13 +546,13 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // terminator. *But*, in that case, the terminator is also // a *definition* of the variable, in which case we want // to stop the search anyhow. (But see Note 1 below.) - if !self.cx.initialized_at_exit(pred_block, mpi) { + if !self.initialized_at_exit(pred_block, mpi) { debug!("compute_drop_live_points_for_block: not initialized"); continue; } - let pred_term_loc = self.cx.body().terminator_loc(pred_block); - let pred_term_point = self.cx.location_map.point_from_location(pred_term_loc); + let pred_term_loc = self.body.terminator_loc(pred_block); + let pred_term_point = self.location_map.point_from_location(pred_term_loc); // If the terminator of this predecessor either *assigns* // our value or is a "normal use", then stop. @@ -463,17 +608,6 @@ impl<'a, 'typeck, 'tcx> LivenessResults<'a, 'typeck, 'tcx> { // for the call (`TMP = call()...`) and then a // `Drop(X)` followed by `X = TMP` to swap that with `X`. } -} - -enum InitAtLocation { - Terminator, - Exit, -} - -impl<'tcx> LivenessContext<'_, '_, 'tcx> { - fn body(&self) -> &Body<'tcx> { - self.typeck.body - } /// Returns `true` if the local variable (or some part of it) is initialized /// at the location defined by `init_at_location`. @@ -490,8 +624,8 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { // - there are relevant live locals // - there are drop points for these relevant live locals. let flow_inits = self.flow_inits.get_or_insert_with(|| { - let tcx = self.typeck.tcx(); - let body = self.typeck.body; + let tcx = self.infcx.tcx; + let body = self.body; // FIXME: reduce the `MaybeInitializedPlaces` domain to the useful `MovePath`s. // // This dataflow analysis computes maybe-initializedness of all move paths, which @@ -515,7 +649,7 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { InitAtLocation::Exit => &mut self.exit_states, }; let state = states.get_or_insert_with(block, || { - let terminator_location = self.typeck.body.terminator_loc(block); + let terminator_location = self.body.terminator_loc(block); match init_at_location { InitAtLocation::Terminator => { flow_inits.seek_before_primary_effect(terminator_location) @@ -548,109 +682,14 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { fn initialized_at_exit(&mut self, block: BasicBlock, mpi: MovePathIndex) -> bool { self.initialized_at(block, mpi, InitAtLocation::Exit) } +} - /// Stores the result that all regions in `value` are live for the - /// points `live_at`. - fn add_use_live_facts_for(&mut self, value: Ty<'tcx>, live_at: &IntervalSet) { - debug!("add_use_live_facts_for(value={:?})", value); - Self::make_all_regions_live(self.location_map, self.typeck, value, live_at); - - // When using `-Zpolonius=next`, we also record the variance of regions in this live type. - if let Some(polonius_context) = self.typeck.polonius_context.as_mut() { - record_live_region_variance( - self.typeck.infcx.tcx, - &mut polonius_context.live_region_variances, - self.typeck.universal_regions, - value, - ); - } - } - - /// Some variable with type `live_ty` is "drop live" at `location` - /// -- i.e., it may be dropped later. This means that *some* of - /// the regions in its type must be live at `location`. The - /// precise set will depend on the dropck constraints, and in - /// particular this takes `#[may_dangle]` into account. - fn add_drop_live_facts_for( - &mut self, - dropped_local: Local, - dropped_ty: Ty<'tcx>, - drop_locations: &[Location], - live_at: &IntervalSet, - ) { - debug!( - "add_drop_live_constraint(\ - dropped_local={:?}, \ - dropped_ty={:?}, \ - drop_locations={:?}, \ - live_at={:?})", - dropped_local, - dropped_ty, - drop_locations, - values::pretty_print_points(self.location_map, live_at.iter()), - ); - - let dropped_span = self.body().local_decls[dropped_local].source_info.span; - let drop_data = - dropck_local(&self.typeck.infcx, &mut self.drop_data, dropped_ty, dropped_span); - - if let Some(data) = &drop_data.region_constraint_data { - for &drop_location in drop_locations { - self.typeck.push_region_constraints( - drop_location.to_locations(), - ConstraintCategory::Boring, - data, - ); - } - } - - // All things in the `outlives` array may be touched by - // the destructor and must be live at this point. - for &kind in &drop_data.dropck_result.kinds { - Self::make_all_regions_live(self.location_map, self.typeck, kind, live_at); - polonius::legacy::emit_drop_facts( - self.typeck.tcx(), - dropped_local, - &kind, - self.typeck.universal_regions, - self.typeck.polonius_facts, - ); - } - - // For polonius: since the local is drop live, record the variance of the regions in its - // type, not the ones in the type's live components seen in the dropck results above. See - // issue #160670. - if let Some(polonius_context) = self.typeck.polonius_context.as_mut() { - record_live_region_variance( - self.typeck.infcx.tcx, - &mut polonius_context.live_region_variances, - self.typeck.universal_regions, - dropped_ty, - ); - } - } - - fn make_all_regions_live( - location_map: &DenseLocationMap, - typeck: &mut TypeChecker<'_, 'tcx>, - value: impl TypeVisitable>, - live_at: &IntervalSet, - ) { - debug!("make_all_regions_live(value={:?})", value); - debug!( - "make_all_regions_live: live_at={}", - values::pretty_print_points(location_map, live_at.iter()), - ); - - value.visit_with(&mut FreeRegionsVisitor { - tcx: typeck.tcx(), - param_env: typeck.infcx.param_env, - op: |r| { - let live_region_vid = typeck.universal_regions.to_region_vid(r); - typeck.constraints.liveness_constraints.add_points(live_region_vid, live_at); - }, - }); - } +/// Contains the results of computing dropck for a local. Namely, this includes +/// the dropped types, and overflows found, and the region constraints that must +/// hold at drop. +struct DropData<'tcx> { + dropck_result: DropckOutlivesResult<'tcx>, + region_constraint_data: Option<&'tcx QueryRegionConstraints<'tcx>>, } /// Computes the `DropData` for a given type, caching the result. diff --git a/compiler/rustc_codegen_gcc/.cspell.json b/compiler/rustc_codegen_gcc/.cspell.json deleted file mode 100644 index 556432d69a41b..0000000000000 --- a/compiler/rustc_codegen_gcc/.cspell.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "allowCompoundWords": true, - "dictionaries": ["cpp", "rust-extra", "rustc_codegen_gcc"], - "dictionaryDefinitions": [ - { - "name": "rust-extra", - "path": "tools/cspell_dicts/rust.txt", - "addWords": true - }, - { - "name": "rustc_codegen_gcc", - "path": "tools/cspell_dicts/rustc_codegen_gcc.txt", - "addWords": true - } - ], - "files": [ - "src/**/*.rs" - ], - "ignorePaths": [ - "src/intrinsic/archs.rs", - "src/intrinsic/old_archs.rs", - "src/intrinsic/llvm.rs" - ], - "ignoreRegExpList": [ - "/(FIXME|NOTE|TODO)\\([^)]+\\)/", - "__builtin_\\w*" - ] -} diff --git a/compiler/rustc_codegen_gcc/.github/workflows/ci.yml b/compiler/rustc_codegen_gcc/.github/workflows/ci.yml index fa9535a3729c3..2f5cc409e363d 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/ci.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/ci.yml @@ -26,16 +26,18 @@ jobs: - { gcc: "gcc-15.deb" } - { gcc: "gcc-15-without-int128.deb" } commands: [ - "--std-tests", + "--std-tests --alloc-tests", # FIXME: re-enable asm tests when GCC can emit in the right syntax. # "--asm-tests", "--test-libcore", "--extended-rand-tests", - "--extended-regex-example-tests", + "--extended-regex-example-tests --test-libcore-doctests", "--extended-regex-tests", "--test-successful-rustc --nb-parts 2 --current-part 0", "--test-successful-rustc --nb-parts 2 --current-part 1", - "--projects", + "--projects --nb-parts 2 --current-part 0", + "--projects --nb-parts 2 --current-part 1", + "--gcc-asm-tests --test-release-libcore", ] steps: @@ -52,8 +54,9 @@ jobs: # `llvm-14-tools` is needed to install the `FileCheck` binary which is used for asm tests. run: sudo apt-get install ninja-build ripgrep llvm-14-tools llvm - - name: Install rustfmt & clippy - run: rustup component add rustfmt clippy + - name: Install the libraries needed to build librsvg + if: ${{ contains(matrix.commands, '--projects') }} + run: sudo apt-get install libcairo2-dev libpango1.0-dev libfontconfig1-dev libfreetype-dev libharfbuzz-dev libxml2-dev libglib2.0-dev - name: Download artifact run: curl -LO https://github.com/rust-lang/gcc/releases/latest/download/${{ matrix.libgccjit_version.gcc }} @@ -88,16 +91,17 @@ jobs: - name: Check formatting run: ./y.sh fmt --check - - name: clippy - run: | - cargo clippy --all-targets -- -D warnings - cargo clippy --all-targets --no-default-features -- -D warnings - cargo clippy --manifest-path build_system/Cargo.toml --all-targets -- -D warnings + - name: Check todo + run: ./y.sh check-todo + + - name: Check lints + run: ./y.sh clippy - name: Build run: | ./y.sh build --sysroot ./y.sh test --cargo-tests + CARGO_TEST_FLAGS="-Zmir-preserve-ub" ./y.sh test --cargo-tests -- mir_preserve_ub_empty_switch - name: Run y.sh cargo build run: | @@ -127,13 +131,6 @@ jobs: - uses: actions/checkout@v4 - run: python tools/check_intrinsics_duplicates.py - spell_check: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - uses: crate-ci/typos@v1.32.0 - - uses: streetsidesoftware/cspell-action@v7 - build_system: runs-on: ubuntu-24.04 steps: diff --git a/compiler/rustc_codegen_gcc/.github/workflows/failures.yml b/compiler/rustc_codegen_gcc/.github/workflows/failures.yml index 2c9e4950706b2..52e96726129b3 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/failures.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/failures.yml @@ -98,7 +98,14 @@ jobs: if: matrix.libgccjit_version.gcc != 'libgccjit12.so' id: tests run: | - ${{ matrix.libgccjit_version.env_extra }} ./y.sh test --release --clean --build-sysroot --test-failing-rustc ${{ matrix.libgccjit_version.extra }} 2>&1 | tee output_log + # Without this, `tee` masks the exit status of `y.sh test`. + set -o pipefail + status=0 + ${{ matrix.libgccjit_version.env_extra }} ./y.sh test --release --clean --build-sysroot --test-failing-rustc ${{ matrix.libgccjit_version.extra }} 2>&1 | tee output_log || status=$? + # This suite runs the tests known to fail, so only a build system error must fail the job. + if [ "$status" -ne 0 ] && [ "$status" -ne 2 ]; then + exit "$status" + fi rg --text "test result" output_log >> $GITHUB_STEP_SUMMARY - name: Run failing ui pattern tests for ICE @@ -106,7 +113,14 @@ jobs: if: matrix.libgccjit_version.gcc != 'libgccjit12.so' id: ui-tests run: | - ${{ matrix.libgccjit_version.env_extra }} ./y.sh test --release --test-failing-ui-pattern-tests ${{ matrix.libgccjit_version.extra }} 2>&1 | tee output_log_ui + # Without this, `tee` masks the exit status of `y.sh test`. + set -o pipefail + status=0 + ${{ matrix.libgccjit_version.env_extra }} ./y.sh test --release --test-failing-ui-pattern-tests ${{ matrix.libgccjit_version.extra }} 2>&1 | tee output_log_ui || status=$? + # This suite runs tests that fail, so only a build system error must fail the job here. + if [ "$status" -ne 0 ] && [ "$status" -ne 2 ]; then + exit "$status" + fi if grep -q "the compiler unexpectedly panicked" output_log_ui; then echo "Error: 'the compiler unexpectedly panicked' found in output logs. CI Error!!" exit 1 diff --git a/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml b/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml index 66f30b147b4c0..17d6449c85e08 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml @@ -21,7 +21,7 @@ jobs: fail-fast: false matrix: cargo_runner: [ - "sde -future -rtm_mode full --", + "sde -cpuid-in /home/runner/work/rustc_codegen_gcc/rustc_codegen_gcc/tests/cpuid.def -rtm_mode full --", "", ] @@ -42,8 +42,14 @@ jobs: - name: Install more recent binutils run: | echo "deb http://archive.ubuntu.com/ubuntu plucky main universe" | sudo tee /etc/apt/sources.list.d/plucky-copies.list - sudo apt-get update + sudo apt-get update -o Acquire::Retries=3 sudo apt-get install binutils + installed="$(dpkg-query --showformat='${Version}' --show binutils)" + echo "Installed binutils: $installed" + if dpkg --compare-versions "$installed" lt "2.44"; then + echo "::error::binutils upgrade failed (got $installed, need >= 2.44); the apt fetch probably failed" + exit 1 + fi - name: Install Intel Software Development Emulator if: ${{ matrix.cargo_runner }} @@ -51,10 +57,9 @@ jobs: mkdir intel-sde cd intel-sde version=10.8.0-2026-03-15 - url_path=915934 dir=sde-external-$version-lin file=$dir.tar.xz - wget https://downloadmirror.intel.com/$url_path/$file + wget http://ci-mirrors.rust-lang.org/$file tar xvf $file sudo mkdir /usr/share/intel-sde sudo cp -r $dir/* /usr/share/intel-sde @@ -90,14 +95,15 @@ jobs: - name: Run stdarch tests if: ${{ !matrix.cargo_runner }} run: | - CHANNEL=release TARGET=x86_64-unknown-linux-gnu CG_RUSTFLAGS="-Ainternal_features" ./y.sh cargo test --manifest-path build/build_sysroot/sysroot_src/library/stdarch/Cargo.toml + # FIXME: remove --skip test_tile_ and --skip --skip test__tile when it's implemented. + ./y.sh test --release --stdarch-tests -- --skip test_tile_ --skip test__tile - name: Run stdarch tests if: ${{ matrix.cargo_runner }} run: | # FIXME: these tests fail when the sysroot is compiled with LTO because of a missing symbol in proc-macro. - # FIXME: remove --skip test_tile_ when it's implemented. - STDARCH_TEST_SKIP_FUNCTION="xsave,xsaveopt,xsave64,xsaveopt64" STDARCH_TEST_EVERYTHING=1 CHANNEL=release CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="${{ matrix.cargo_runner }}" TARGET=x86_64-unknown-linux-gnu CG_RUSTFLAGS="-Ainternal_features" ./y.sh cargo test --manifest-path build/build_sysroot/sysroot_src/library/stdarch/Cargo.toml -- --skip rtm --skip tbm --skip sse4a --skip test_tile_ + # FIXME: remove --skip test_tile_ and --skip --skip test__tile when it's implemented. + STDARCH_TEST_SKIP_FUNCTION="xsave,xsaveopt,xsave64,xsaveopt64" STDARCH_TEST_EVERYTHING=1 CHANNEL=release CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="${{ matrix.cargo_runner }}" TARGET=x86_64-unknown-linux-gnu CG_RUSTFLAGS="-Ainternal_features" ./y.sh cargo test --manifest-path build/build_sysroot/sysroot_src/library/stdarch/Cargo.toml -- --skip rtm --skip tbm --skip sse4a --skip test_tile_ --skip test__tile # Summary job for the merge queue. # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! diff --git a/compiler/rustc_codegen_gcc/.gitignore b/compiler/rustc_codegen_gcc/.gitignore index 8f73d3eb972a0..13bd0d0ffde9b 100644 --- a/compiler/rustc_codegen_gcc/.gitignore +++ b/compiler/rustc_codegen_gcc/.gitignore @@ -7,7 +7,7 @@ perf.data.old *.events *.string* gimple* -*asm +*_asm res test-backend projects @@ -20,4 +20,5 @@ llvm build_system/target config.toml build -rustlantis \ No newline at end of file +rustlantis +stuff/ diff --git a/compiler/rustc_codegen_gcc/CONTRIBUTING.md b/compiler/rustc_codegen_gcc/CONTRIBUTING.md index 8f81ecca445a8..c5c2a783b1ee7 100644 --- a/compiler/rustc_codegen_gcc/CONTRIBUTING.md +++ b/compiler/rustc_codegen_gcc/CONTRIBUTING.md @@ -112,7 +112,7 @@ Full list of debugging options can be found in the [README](Readme.md#env-vars). ### Code Style Guidelines - Follow Rust standard coding conventions -- Ensure your code passes `rustfmt` and `clippy` +- Ensure your code passes `rustfmt` and `clippy` (you can run them with `y.sh fmt` and `y.sh clippy`) - Add comments explaining complex logic, especially in GCC interface code ## Additional Resources diff --git a/compiler/rustc_codegen_gcc/Cargo.lock b/compiler/rustc_codegen_gcc/Cargo.lock index 7ce94b58c0516..c174628d0d188 100644 --- a/compiler/rustc_codegen_gcc/Cargo.lock +++ b/compiler/rustc_codegen_gcc/Cargo.lock @@ -31,9 +31,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys", @@ -56,18 +56,18 @@ dependencies = [ [[package]] name = "gccjit" -version = "3.3.0" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b73d18b642ce16378af78f89664841d7eeafa113682ff5d14573424eb0232a" +checksum = "6d85b5754389edaad832ba320709a25086b3081a8c6c0fab2322965e5fb512b3" dependencies = [ "gccjit_sys", ] [[package]] name = "gccjit_sys" -version = "1.3.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee689456c013616942d5aef9a84d613cefcc3b335340d036f3650fc1a7459e15" +checksum = "e081669728b490723537f9def7eb674b7c9acd8de0b92ad4f4abf5f5cc75ea4b" dependencies = [ "libc", ] @@ -117,15 +117,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.168" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "memchr" @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -216,9 +216,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom", @@ -311,78 +311,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/compiler/rustc_codegen_gcc/Cargo.toml b/compiler/rustc_codegen_gcc/Cargo.toml index ac5e94b9454e2..02be6d56c2310 100644 --- a/compiler/rustc_codegen_gcc/Cargo.toml +++ b/compiler/rustc_codegen_gcc/Cargo.toml @@ -20,11 +20,11 @@ default = ["master"] [dependencies] object = { version = "0.39.0", default-features = false, features = ["std", "read"] } tempfile = "3.20" -gccjit = { version = "3.3.0", features = ["dlopen"] } +gccjit = { version = "6.1.0", features = ["dlopen"] } #gccjit = { git = "https://github.com/rust-lang/gccjit.rs", branch = "error-dlopen", features = ["dlopen"] } # Local copy. -#gccjit = { path = "../gccjit.rs", features = ["dlopen"] } +# gccjit = { path = "../gccjit.rs", features = ["dlopen"] } [dev-dependencies] boml = "0.3.1" diff --git a/compiler/rustc_codegen_gcc/Readme.md b/compiler/rustc_codegen_gcc/Readme.md index ce5ee1e4adee6..9a7c624c9bc22 100644 --- a/compiler/rustc_codegen_gcc/Readme.md +++ b/compiler/rustc_codegen_gcc/Readme.md @@ -136,19 +136,21 @@ $ ./y.sh cargo build --manifest-path tests/hello-world/Cargo.toml ### Cargo ```bash -$ CHANNEL="release" $CG_GCCJIT_DIR/y.sh cargo run +$ CHANNEL=release $CG_GCCJIT_DIR/y.sh cargo run ``` -If you compiled cg_gccjit in debug mode (aka you didn't pass `--release` to `./y.sh test`) you should use `CHANNEL="debug"` instead or omit `CHANNEL="release"` completely. +If you compiled `cg_gcc` in debug mode (aka you didn't pass `--release` to `./y.sh build`) you should use `CHANNEL=debug` instead or omit `CHANNEL=release` completely. ### Rustc If you want to run `rustc` directly, you can do so with: ```bash -$ ./y.sh rustc my_crate.rs +$ CHANNEL=release ./y.sh rustc my_crate.rs ``` +If you compiled `cg_gcc` in debug mode (aka you didn't pass `--release` to `./y.sh build`) you should use `CHANNEL=debug` instead or omit `CHANNEL=release` completely. + You can do the same manually (although we don't recommend it): ```bash @@ -176,7 +178,7 @@ $ LIBRARY_PATH="[gcc-path value]" LD_LIBRARY_PATH="[gcc-path value]" rustc +$(ca More specific documentation is available in the [`doc`](./doc) folder: * [Common errors](./doc/errors.md) - * [Debugging GCC LTO](./doc/debugging-gcc-lto.md) + * [Debugging](./doc/debugging.md) * [Debugging libgccjit](./doc/debugging-libgccjit.md) * [Git subtree sync](./doc/subtree.md) * [List of useful commands](./doc/tips.md) diff --git a/compiler/rustc_codegen_gcc/build_system/Cargo.lock b/compiler/rustc_codegen_gcc/build_system/Cargo.lock index e727561a2bfba..5e761149eb3bc 100644 --- a/compiler/rustc_codegen_gcc/build_system/Cargo.lock +++ b/compiler/rustc_codegen_gcc/build_system/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "boml" diff --git a/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.lock b/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.lock new file mode 100644 index 0000000000000..9ad96acfda407 --- /dev/null +++ b/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.lock @@ -0,0 +1,507 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "asm-tester" +version = "0.1.0" +dependencies = [ + "compiletest_rs", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "compiletest_rs" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f150fe9105fcd2a57cad53f0c079a24de65195903ef670990f5909f695eac04c" +dependencies = [ + "diff", + "filetime", + "getopts", + "lazy_static", + "libc", + "log", + "miow", + "regex", + "rustfix", + "serde", + "serde_derive", + "serde_json", + "tester", + "windows-sys 0.59.0", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "miow" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustfix" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fa69b198d894d84e23afde8e9ab2af4400b2cba20d6bf2b428a8b01c222c5a" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "tester" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8bf7e0eb2dd7b4228cc1b6821fc5114cd6841ae59f652a85488c016091e5f" +dependencies = [ + "cfg-if", + "getopts", + "libc", + "num_cpus", + "term", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.toml b/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.toml new file mode 100644 index 0000000000000..eeefe61bdc75b --- /dev/null +++ b/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "asm-tester" +version = "0.1.0" +edition = "2024" + +[dependencies] +compiletest_rs = "0.11.2" + +[[bin]] +name = "asm-tester" +path = "src/main.rs" + +[workspace] diff --git a/compiler/rustc_codegen_gcc/build_system/asm-tester/src/main.rs b/compiler/rustc_codegen_gcc/build_system/asm-tester/src/main.rs new file mode 100644 index 0000000000000..00ee4ac936520 --- /dev/null +++ b/compiler/rustc_codegen_gcc/build_system/asm-tester/src/main.rs @@ -0,0 +1,66 @@ +use std::path::PathBuf; + +#[derive(Default)] +struct Config { + llvm_filecheck: Option, + filters: Vec, + rustc_flags: Vec, +} + +impl Config { + fn new() -> Result { + // We skip the program's name. + let mut args = std::env::args().skip(1); + let mut config = Self::default(); + + while let Some(arg) = args.next() { + match arg.as_str() { + "--llvm-filecheck" => { + config.llvm_filecheck = args.next().map(PathBuf::from); + } + "--filter" => { + if let Some(arg) = args.next() { + config.filters.push(arg); + } + } + "--" => { + config.rustc_flags.extend(&mut args); + // Nothing else to be read but the `break` makes it more clear. + break; + } + arg => return Err(format!("Unknown argument {arg:?}")), + } + } + if config.llvm_filecheck.is_none() { + Err("Missing `--llvm-filecheck` option".to_owned()) + } else if config.rustc_flags.is_empty() { + Err("Missing rustc flags (passed after `--`)".to_owned()) + } else { + Ok(config) + } + } +} + +fn main() { + let Config { llvm_filecheck, filters, rustc_flags } = match Config::new() { + Ok(c) => c, + Err(error) => { + eprintln!("{error}"); + std::process::exit(1); + } + }; + + let mut test_config = compiletest_rs::Config::default(); + + test_config.mode = compiletest_rs::common::Mode::Assembly; + test_config.src_base = PathBuf::from("tests/asm"); + test_config.llvm_filecheck = llvm_filecheck; + test_config.filters = filters; + test_config.strict_headers = true; + test_config.build_base = PathBuf::from("build/tests/asm"); + test_config.target_rustcflags = Some(rustc_flags.join(" ")); + test_config.link_deps(); + test_config.clean_rmeta(); + + compiletest_rs::run_tests(&test_config) +} diff --git a/compiler/rustc_codegen_gcc/build_system/src/build.rs b/compiler/rustc_codegen_gcc/build_system/src/build.rs index 839c762fed742..bcd386bfebdae 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/build.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/build.rs @@ -132,6 +132,24 @@ pub fn build_sysroot(env: &HashMap, config: &ConfigInfo) -> Resu // Builds libs let mut rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default(); + + // Record the sysroot sources under the path the `rust-src` component uses, which is where + // rustc looks for them to turn a sysroot span into `/rustc/$hash`. Without this, ui tests + // print the build path where they expect `$SRC_DIR`. + let sysroot_source_dir = lib_path.join("rustlib/src/rust/library"); + rustflags.push_str(&format!( + " --remap-path-prefix={library_dir}={sysroot_source_dir}", + library_dir = std::path::absolute(&library_dir) + .map_err(|error| format!( + "Failed to get the absolute path of the sysroot sources: {error:?}" + ))? + .display(), + sysroot_source_dir = std::path::absolute(&sysroot_source_dir) + .map_err(|error| format!( + "Failed to get the absolute path of the sysroot sources: {error:?}" + ))? + .display(), + )); if config.sysroot_panic_abort { rustflags.push_str(" -Cpanic=abort -Zpanic-abort-tests"); } @@ -188,12 +206,33 @@ pub fn build_sysroot(env: &HashMap, config: &ConfigInfo) -> Resu // FIXME: should not use shell command! run_command(&[&"cp", &"-r", &dir_to_copy, &sysroot_path], None).map(|_| ()) }; - walk_dir( - library_dir.join(format!("target/{}/{}/deps", config.target_triple, channel)), - &mut copier.clone(), - &mut copier, - false, - )?; + let target_dir = library_dir.join(format!("target/{}/{}", config.target_triple, channel)); + let deps_dir = target_dir.join("deps"); + if deps_dir.is_dir() { + // Keep copying in the old directory just in case. + walk_dir(&deps_dir, &mut copier.clone(), &mut copier, false)?; + } else { + let build_dir = target_dir.join("build"); + walk_dir( + &build_dir, + &mut |package_dir: &Path| { + walk_dir( + package_dir, + &mut |unit_dir: &Path| { + let out_dir = unit_dir.join("out"); + if out_dir.is_dir() { + walk_dir(&out_dir, &mut copier.clone(), &mut copier.clone(), false)?; + } + Ok(()) + }, + &mut |_| Ok(()), + false, + ) + }, + &mut |_| Ok(()), + false, + )?; + } // Copy the source files to the sysroot (Rust for Linux needs this). let sysroot_src_path = start_dir.join("sysroot/lib/rustlib/src/rust"); @@ -227,7 +266,7 @@ fn build_codegen(args: &mut BuildArg) -> Result<(), String> { } run_command_with_output_and_env(&command, None, Some(&env))?; - args.config_info.setup(&mut env, false)?; + args.config_info.setup(&mut env, false, true)?; // We voluntarily ignore the error. let _ = fs::remove_dir_all("target/out"); diff --git a/compiler/rustc_codegen_gcc/build_system/src/clean.rs b/compiler/rustc_codegen_gcc/build_system/src/clean.rs index 43f01fdf35ecb..ec2092ee92ef5 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/clean.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/clean.rs @@ -74,7 +74,8 @@ fn clean_ui_tests() -> Result<(), String> { let path = Path::new(crate::BUILD_DIR) .join("rust/build/x86_64-unknown-linux-gnu/test/") .join(directory); - run_command(&[&"find", &path, &"-name", &"stamp", &"-delete"], None)?; + // The directory might not exist, so ignore the error. + let _ = run_command(&[&"find", &path, &"-name", &"stamp", &"-delete"], None); } Ok(()) } diff --git a/compiler/rustc_codegen_gcc/build_system/src/clippy.rs b/compiler/rustc_codegen_gcc/build_system/src/clippy.rs new file mode 100644 index 0000000000000..813d4b9141e1c --- /dev/null +++ b/compiler/rustc_codegen_gcc/build_system/src/clippy.rs @@ -0,0 +1,62 @@ +use std::path::Path; + +use crate::utils::{run_command_with_output, run_tool_and_install_it_if_not_present}; + +fn show_usage() { + println!( + r#" +`clippy` command help: + + --help : Show this help"# + ); +} + +pub fn run() -> Result<(), String> { + // We skip binary name and the `info` command. + let args = std::env::args().skip(2); + #[allow(clippy::never_loop)] + for arg in args { + match arg.as_str() { + "--help" => { + show_usage(); + return Ok(()); + } + _ => return Err(format!("Unknown option {arg}")), + } + } + + run_tool_and_install_it_if_not_present(&[ + &"cargo", + &"clippy", + &"--all-targets", + &"--", + &"-D", + &"warnings", + ])?; + run_command_with_output( + &[ + &"cargo", + &"clippy", + &"--all-targets", + &"--no-default-features", + &"--", + &"-D", + &"warnings", + ], + Some(Path::new(".")), + )?; + run_command_with_output( + &[ + &"cargo", + &"clippy", + &"--all-targets", + &"--manifest-path", + &"build_system/Cargo.toml", + &"--", + &"-D", + &"warnings", + ], + Some(Path::new(".")), + )?; + Ok(()) +} diff --git a/compiler/rustc_codegen_gcc/build_system/src/config.rs b/compiler/rustc_codegen_gcc/build_system/src/config.rs index 8eb6d8f019e1c..fd78f691d1657 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/config.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/config.rs @@ -314,6 +314,7 @@ impl ConfigInfo { &mut self, env: &mut HashMap, use_system_gcc: bool, + generate_out_dir: bool, ) -> Result<(), String> { env.insert("CARGO_INCREMENTAL".to_string(), "0".to_string()); @@ -444,12 +445,12 @@ impl ConfigInfo { self.rustc_command = vec![rustc]; self.rustc_command.extend_from_slice(&rustflags); - self.rustc_command.extend_from_slice(&[ - "-L".to_string(), - format!("crate={}", self.cargo_target_dir), - "--out-dir".to_string(), - self.cargo_target_dir.clone(), - ]); + self.rustc_command + .extend_from_slice(&["-L".to_string(), format!("crate={}", self.cargo_target_dir)]); + if generate_out_dir { + self.rustc_command + .extend_from_slice(&["--out-dir".to_string(), self.cargo_target_dir.clone()]); + } if !env.contains_key("RUSTC_LOG") { env.insert("RUSTC_LOG".to_string(), "warn".to_string()); diff --git a/compiler/rustc_codegen_gcc/build_system/src/fmt.rs b/compiler/rustc_codegen_gcc/build_system/src/fmt.rs index 91535f217e351..dc1ca1d3e82ae 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/fmt.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/fmt.rs @@ -1,7 +1,7 @@ use std::ffi::OsStr; use std::path::Path; -use crate::utils::{run_command_with_output, walk_dir}; +use crate::utils::{run_command_with_output, run_tool_and_install_it_if_not_present, walk_dir}; fn show_usage() { println!( @@ -31,8 +31,9 @@ pub fn run() -> Result<(), String> { let cmd: &[&dyn AsRef] = if check { &[&"cargo", &"fmt", &"--check"] } else { &[&"cargo", &"fmt"] }; - run_command_with_output(cmd, Some(Path::new(".")))?; + run_tool_and_install_it_if_not_present(cmd)?; run_command_with_output(cmd, Some(Path::new("build_system")))?; + run_command_with_output(cmd, Some(Path::new("build_system/asm-tester")))?; run_rustfmt_recursively("tests/run", check) } diff --git a/compiler/rustc_codegen_gcc/build_system/src/main.rs b/compiler/rustc_codegen_gcc/build_system/src/main.rs index ae975c94fff25..37b1f306817fd 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/main.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/main.rs @@ -3,6 +3,7 @@ use std::{env, process}; mod abi_test; mod build; mod clean; +mod clippy; mod clone_gcc; mod config; mod fmt; @@ -12,6 +13,7 @@ mod prepare; mod rust_tools; mod rustc_info; mod test; +mod todo; mod utils; const BUILD_DIR: &str = "build"; @@ -24,43 +26,67 @@ macro_rules! arg_error { }}; } -fn usage() { - println!( - "\ +macro_rules! commands_decl { + ($($variant:ident: $doc_name:literal => $doc:literal ,)+) => { + enum Command { + $($variant),+ + } + + impl<'a> From> for Command { + fn from(arg: Option<&'a str>) -> Self { + match arg { + $(Some($doc_name) => Self::$variant,)+ + Some("--help") => { + usage(); + process::exit(0); + } + Some(flag) if flag.starts_with('-') => arg_error!("Expected command found flag {}", flag), + Some(command) => arg_error!("Unknown command {}", command), + None => { + usage(); + process::exit(0); + } + } + } + } + + fn usage() { + println!("\ rustc_codegen_gcc build system Usage: build_system [command] [options] Options: - --help : Displays this help message. + --help : Displays this help message. + +Commands:", + ); + let mut commands = vec![$(($doc_name, $doc),)+]; + let longest = commands.iter().map(|(name, _)| name.len()).max().unwrap(); -Commands: - cargo : Executes a cargo command. - rustc : Compiles the program using the GCC compiler. - clean : Cleans the build directory, removing all compiled files and artifacts. - prepare : Prepares the environment for building, including fetching dependencies and setting up configurations. - build : Compiles the project. - test : Runs tests for the project. - info : Displays information about the build environment and project configuration. - clone-gcc : Clones the GCC compiler from a specified source. - fmt : Runs rustfmt - fuzz : Fuzzes `cg_gcc` using rustlantis - abi-test : Runs the abi-cafe test suite on the codegen, checking for ABI compatibility with LLVM" - ); + commands.sort_unstable_by(|a, b| a.0.cmp(b.0)); + for (name, doc) in commands { + let spacing = std::iter::repeat(' ').take(longest - name.len() + 1).collect::(); + eprintln!(" {name}{spacing}: {doc}."); + } + } + } } -pub enum Command { - Cargo, - Clean, - CloneGcc, - Prepare, - Build, - Rustc, - Test, - Info, - Fmt, - Fuzz, - AbiTest, +commands_decl! { + Cargo: "cargo" => "Executes a cargo command", + Clean: "clean" => "Cleans the build directory, removing all compiled files and artifacts", + Clippy: "clippy" => "Runs clippy", + CloneGcc: "clone-gcc" => "Clones the GCC compiler from a specified source", + Prepare: "prepare" => "Prepares the environment for building, including fetching dependencies and setting up configurations", + Build: "build" => "Compiles the project", + Rustc: "rustc" => "Compiles the program using the GCC compiler", + Test: "test" => "Runs tests for the project", + Info: "info" => "Displays information about the build environment and project configuration", + Fmt: "fmt" => "Runs rustfmt", + Fuzz: "fuzz" => "Fuzzes `cg_gcc` using `rustlantis`", + AbiTest: "abi-test" => "Runs the abi-cafe test suite on the codegen, checking for ABI compatibility with LLVM", + CheckTodo: "check-todo" => "Checks todo in the project", } fn main() { @@ -70,31 +96,7 @@ fn main() { } } - let command = match env::args().nth(1).as_deref() { - Some("cargo") => Command::Cargo, - Some("rustc") => Command::Rustc, - Some("clean") => Command::Clean, - Some("prepare") => Command::Prepare, - Some("build") => Command::Build, - Some("test") => Command::Test, - Some("info") => Command::Info, - Some("clone-gcc") => Command::CloneGcc, - Some("abi-test") => Command::AbiTest, - Some("fmt") => Command::Fmt, - Some("fuzz") => Command::Fuzz, - Some("--help") => { - usage(); - process::exit(0); - } - Some(flag) if flag.starts_with('-') => arg_error!("Expected command found flag {}", flag), - Some(command) => arg_error!("Unknown command {}", command), - None => { - usage(); - process::exit(0); - } - }; - - if let Err(e) = match command { + if let Err(e) = match Command::from(env::args().nth(1).as_deref()) { Command::Cargo => rust_tools::run_cargo(), Command::Rustc => rust_tools::run_rustc(), Command::Clean => clean::run(), @@ -106,8 +108,13 @@ fn main() { Command::Fmt => fmt::run(), Command::Fuzz => fuzz::run(), Command::AbiTest => abi_test::run(), + Command::Clippy => clippy::run(), + Command::CheckTodo => todo::run(), } { eprintln!("Command failed to run: {e}"); - process::exit(1); + // CI needs to tell a build system error apart from the test failures some suites expect. + let exit_code = + if e == test::TESTS_FAILED_ERROR { test::TESTS_FAILED_EXIT_CODE } else { 1 }; + process::exit(exit_code); } } diff --git a/compiler/rustc_codegen_gcc/build_system/src/rust_tools.rs b/compiler/rustc_codegen_gcc/build_system/src/rust_tools.rs index b1faa27acc4a2..1b50f11c3d324 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/rust_tools.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/rust_tools.rs @@ -72,7 +72,7 @@ impl RustcTools { let mut env: HashMap = std::env::vars().collect(); let mut config = ConfigInfo::default(); - config.setup(&mut env, false)?; + config.setup(&mut env, false, false)?; let toolchain = get_toolchain()?; let toolchain_version = rustc_toolchain_version_info(&toolchain)?; diff --git a/compiler/rustc_codegen_gcc/build_system/src/test.rs b/compiler/rustc_codegen_gcc/build_system/src/test.rs index 2475a3a6a7155..a55c2dcf83ddc 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/test.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/test.rs @@ -1,18 +1,29 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; -use std::fs::{File, remove_dir_all}; +use std::fs::{File, read_to_string, remove_dir_all}; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::str::FromStr; +use boml::Toml; + use crate::build; use crate::config::{Channel, ConfigInfo}; use crate::utils::{ create_dir, get_sysroot_dir, get_toolchain, git_clone, git_clone_root_dir, remove_file, - run_command, run_command_with_env, run_command_with_output_and_env, rustc_version_info, - split_args, walk_dir, + run_command, run_command_with_env, run_command_with_output_and_env, + run_command_with_output_and_env_no_err, rustc_version_info, split_args, walk_dir, }; +/// Exit code of `y.sh test` when the tests ran and reported failures, as opposed to the build +/// system failing to run them at all. CI relies on the distinction: the suites of known-failing +/// tests are expected to report failures, but a broken build system must never pass silently. +pub const TESTS_FAILED_EXIT_CODE: i32 = 2; + +/// The error returned for that case. `main` compares against it to pick the exit code, so no other +/// error may use this message. +pub const TESTS_FAILED_ERROR: &str = "the test suite reported failures"; + type Env = HashMap; type Runner = fn(&Env, &TestArg) -> Result<(), String>; type Runners = HashMap<&'static str, (&'static str, Runner)>; @@ -28,8 +39,12 @@ fn get_runners() -> Runners { ("Run failing ui pattern tests", test_failing_ui_pattern_tests), ); runners.insert("--test-failing-rustc", ("Run failing rustc tests", test_failing_rustc)); + runners.insert("--run-ui-tests", ("Run specified rustc UI tests", run_ui_tests)); runners.insert("--projects", ("Run the tests of popular crates", test_projects)); runners.insert("--test-libcore", ("Run libcore tests", test_libcore)); + runners.insert("--test-release-libcore", ("Run libcore tests", test_release_libcore)); + runners.insert("--test-libcore-doctests", ("Run libcore doc-tests", test_libcore_doctests)); + runners.insert("--alloc-tests", ("Run alloc tests", test_alloc)); runners.insert("--clean", ("Empty cargo target directory", clean)); runners.insert("--build-sysroot", ("Build sysroot", build_sysroot)); runners.insert("--std-tests", ("Run std tests", std_tests)); @@ -42,8 +57,10 @@ fn get_runners() -> Runners { ); runners.insert("--extended-regex-tests", ("Run extended regex tests", extended_regex_tests)); runners.insert("--mini-tests", ("Run mini tests", mini_tests)); + runners.insert("--gcc-asm-tests", ("Run cg_gcc asm tests", test_asm)); runners.insert("--cargo-tests", ("Run cargo tests", cargo_tests)); runners.insert("--no-builtins-tests", ("Test #![no_builtins] attribute", no_builtins_tests)); + runners.insert("--stdarch-tests", ("Run stdarch tests", test_stdarch as Runner)); runners } @@ -505,6 +522,26 @@ fn std_tests(env: &Env, args: &TestArg) -> Result<(), String> { Ok(()) } +fn get_llvm_filecheck(env: &Env) -> Result { + match run_command_with_env( + &[ + &"bash", + &"-c", + &"which FileCheck-10 || \ + which FileCheck-11 || \ + which FileCheck-12 || \ + which FileCheck-13 || \ + which FileCheck-14 || \ + which FileCheck", + ], + None, + Some(env), + ) { + Ok(cmd) => Ok(String::from_utf8_lossy(&cmd.stdout).trim().to_string()), + Err(_) => Err("Failed to retrieve LLVM FileCheck, ignoring...".to_owned()), + } +} + fn setup_rustc(env: &mut Env, args: &TestArg) -> Result { let toolchain = format!( "+{channel}-{host}", @@ -548,23 +585,10 @@ fn setup_rustc(env: &mut Env, args: &TestArg) -> Result { let rustc = rustc.trim().to_owned(); if rustc.is_empty() { Err("`rustc` path is empty".to_string()) } else { Ok(rustc) } })?; - let llvm_filecheck = match run_command_with_env( - &[ - &"bash", - &"-c", - &"which FileCheck-10 || \ - which FileCheck-11 || \ - which FileCheck-12 || \ - which FileCheck-13 || \ - which FileCheck-14 || \ - which FileCheck", - ], - rust_dir, - Some(env), - ) { - Ok(cmd) => String::from_utf8_lossy(&cmd.stdout).to_string(), - Err(_) => { - eprintln!("Failed to retrieve LLVM FileCheck, ignoring..."); + let llvm_filecheck = match get_llvm_filecheck(env) { + Ok(l) => l, + Err(error) => { + eprintln!("{error}"); // FIXME: the test tests/run-make/no-builtins-attribute will fail if we cannot find // FileCheck. String::new() @@ -634,7 +658,7 @@ fn asm_tests(env: &Env, args: &TestArg) -> Result<(), String> { &"0", &"--set", &"build.compiletest-allow-stage0=true", - &"tests/assembly-llvm/asm", + &"tests/assembly-gcc/asm", &"--compiletest-rustc-args", &rustc_args, ], @@ -700,40 +724,109 @@ where // echo "[BUILD] sysroot in release mode" // ./build_sysroot/build_sysroot.sh --release +struct Project { + url: &'static str, + /// Arguments added to both the `cargo build` and the `cargo test` invocations. + cargo_arguments: &'static [&'static str], + /// Arguments forwarded to the test harness by `cargo test`. + test_harness_arguments: &'static [&'static str], + /// Variables added to the environment of both invocations. + environment_variables: &'static [(&'static str, &'static str)], +} + +impl Project { + const fn new(url: &'static str) -> Self { + Self { url, cargo_arguments: &[], test_harness_arguments: &[], environment_variables: &[] } + } + + const fn cargo_arguments(mut self, arguments: &'static [&'static str]) -> Self { + self.cargo_arguments = arguments; + self + } + + const fn test_harness_arguments(mut self, arguments: &'static [&'static str]) -> Self { + self.test_harness_arguments = arguments; + self + } + + const fn environment_variables( + mut self, + variables: &'static [(&'static str, &'static str)], + ) -> Self { + self.environment_variables = variables; + self + } +} + fn test_projects(env: &Env, args: &TestArg) -> Result<(), String> { let projects = [ - //"https://gitlab.gnome.org/GNOME/librsvg", // FIXME: doesn't compile in the CI since the - // version of cairo and other libraries is too old. - "https://github.com/rust-random/getrandom", - "https://github.com/BurntSushi/memchr", - "https://github.com/dtolnay/itoa", - "https://github.com/rust-lang/cfg-if", - //"https://github.com/rust-lang-nursery/lazy-static.rs", // FIXME: re-enable when the - //failing test is fixed upstream. - //"https://github.com/marshallpierce/rust-base64", // FIXME: one test is OOM-killed. - // FIXME: ignore the base64 test that is OOM-killed. - //"https://github.com/time-rs/time", // FIXME: one test fails (https://github.com/time-rs/time/issues/719). - "https://github.com/rust-lang/log", - "https://github.com/bitflags/bitflags", - //"https://github.com/serde-rs/serde", // FIXME: one test fails. - //"https://github.com/rayon-rs/rayon", // FIXME: very slow, only run on master? - //"https://github.com/rust-lang/cargo", // FIXME: very slow, only run on master? + // The reference images assume the exact cairo, pango and freetype that librsvg pins in its + // own CI; this one renders text decorations a pixel off with the versions Ubuntu ships. + Project::new("https://gitlab.gnome.org/GNOME/librsvg") + .test_harness_arguments(&["--skip", "tests::svg1_1_text_text_03_b_svg", "--exact"]) + // A debug build of librsvg needs about 5 MB of stack per `cargo test` thread to reach + // its maximum layer nesting depth; librsvg's own CI sets the same value. + .environment_variables(&[("RUST_MIN_STACK", "8388608")]), + Project::new("https://github.com/rust-random/getrandom"), + Project::new("https://github.com/BurntSushi/memchr"), + Project::new("https://github.com/dtolnay/itoa"), + Project::new("https://github.com/rust-lang/cfg-if"), + // The `ui` test compares against the diagnostics of the compiler it was blessed with, so it + // fails on the nightly we use no matter which backend produces the code. + Project::new("https://github.com/rust-lang-nursery/lazy-static.rs") + .test_harness_arguments(&["--skip", "ui", "--exact"]), + Project::new("https://github.com/marshallpierce/rust-base64"), + // The test suite refuses to build unless every feature is enabled; it otherwise spawns a + // nested `cargo test --all-features` which would not use this backend. + Project::new("https://github.com/time-rs/time").cargo_arguments(&["--all-features"]), + Project::new("https://github.com/rust-lang/log"), + Project::new("https://github.com/bitflags/bitflags"), + Project::new("https://github.com/serde-rs/serde"), + Project::new("https://github.com/rayon-rs/rayon"), + // FIXME: too slow to run in the CI: the release build alone takes 46 minutes and the + // `cargo` crate itself needs 5.4 GB of memory in a single rustc process. + //Project::new("https://github.com/rust-lang/cargo"), ]; let mut env = env.clone(); let rustflags = format!("{} --cap-lints allow", env.get("RUSTFLAGS").cloned().unwrap_or_default()); env.insert("RUSTFLAGS".to_string(), rustflags); - let run_tests = |projects_path, iter: &mut dyn Iterator| -> Result<(), String> { - for project in iter { - let clone_result = git_clone_root_dir(project, projects_path, true)?; - let repo_path = Path::new(&clone_result.repo_dir); - run_cargo_command(&[&"build", &"--release"], Some(repo_path), &env, args)?; - run_cargo_command(&[&"test"], Some(repo_path), &env, args)?; - } + let run_tests = + |projects_path, iter: &mut dyn Iterator| -> Result<(), String> { + for project in iter { + let clone_result = git_clone_root_dir(project.url, projects_path, true)?; + let repo_path = Path::new(&clone_result.repo_dir); + + let mut project_environment = env.clone(); + for (name, value) in project.environment_variables { + project_environment.insert(name.to_string(), value.to_string()); + } - Ok(()) - }; + let mut build_command: Vec<&dyn AsRef> = vec![&"build", &"--release"]; + build_command.extend( + project.cargo_arguments.iter().map(|argument| argument as &dyn AsRef), + ); + run_cargo_command(&build_command, Some(repo_path), &project_environment, args)?; + + let mut test_command: Vec<&dyn AsRef> = vec![&"test"]; + test_command.extend( + project.cargo_arguments.iter().map(|argument| argument as &dyn AsRef), + ); + if !project.test_harness_arguments.is_empty() { + test_command.push(&"--"); + test_command.extend( + project + .test_harness_arguments + .iter() + .map(|argument| argument as &dyn AsRef), + ); + } + run_cargo_command(&test_command, Some(repo_path), &project_environment, args)?; + } + + Ok(()) + }; let projects_path = Path::new("projects"); create_dir(projects_path)?; @@ -755,10 +848,117 @@ fn test_projects(env: &Env, args: &TestArg) -> Result<(), String> { } fn test_libcore(env: &Env, args: &TestArg) -> Result<(), String> { + test_libcore_inner(env, args, false) +} + +fn test_release_libcore(env: &Env, args: &TestArg) -> Result<(), String> { + test_libcore_inner(env, args, true) +} + +fn test_libcore_inner(env: &Env, args: &TestArg, release: bool) -> Result<(), String> { // FIXME: create a function "display_if_not_quiet" or something along the line. println!("[TEST] libcore"); let path = get_sysroot_dir().join("sysroot_src/library/coretests"); let _ = remove_dir_all(path.join("target")); + let mut command: Vec<&dyn AsRef> = vec![&"test"]; + if release { + command.push(&"--release"); + } + run_cargo_command(&command, Some(&path), env, args)?; + Ok(()) +} + +/// Returns the edition declared in the manifest of the given library crate, so that the doctests +/// are run with the same edition as the crate they are extracted from. +fn get_crate_edition(crate_dir: &Path) -> Result { + let manifest_path = crate_dir.join("Cargo.toml"); + let content = read_to_string(&manifest_path) + .map_err(|error| format!("Failed to read `{}`: {error:?}", manifest_path.display()))?; + let manifest = Toml::parse(&content) + .map_err(|error| format!("Failed to parse `{}`: {error:?}", manifest_path.display()))?; + manifest + .get_table("package") + .and_then(|package| package.get_string("edition")) + .map(|edition| edition.to_string()) + .map_err(|error| { + format!("Failed to get `package.edition` from `{}`: {error:?}", manifest_path.display()) + }) +} + +fn test_libcore_doctests(env: &Env, args: &TestArg) -> Result<(), String> { + // FIXME: create a function "display_if_not_quiet" or something along the line. + println!("[TEST] libcore doctests"); + + let library_dir = get_sysroot_dir().join("sysroot_src/library"); + let edition = get_crate_edition(&library_dir.join("core"))?; + // `rustdoc` is called directly instead of through `cargo test --doc` because `cargo` builds its + // own `core` and passes it with `--extern`, which then conflicts with the `core` of the sysroot + // the doctests are linked against ("duplicate lang item" errors). + let toolchain = get_toolchain()?; + let toolchain_arg = format!("+{toolchain}"); + let rustflags = split_args(&env.get("RUSTFLAGS").cloned().unwrap_or_default())?; + // `-Zunstable-options` is needed for `--test-args`. + let mut command: Vec<&dyn AsRef> = vec![ + &"rustdoc", + &toolchain_arg, + &"--test", + &"core/src/lib.rs", + &"--crate-name", + &"core", + &"--crate-type", + &"lib", + &"--edition", + &edition, + &"-Zunstable-options", + // FIXME: remove `-Zforce-unstable-if-unmarked` once the doctest of + // `core::io::ErrorKind`'s `Display` impl declares `#![feature(core_io)]` upstream: without + // it, that doctest fails to compile with `E0658` on any backend. + &"-Zforce-unstable-if-unmarked", + // FIXME: one test cannot compile due to an upstream bug in the new trait solver. + &"-Znext-solver=coherence", + ]; + for flag in &rustflags { + command.push(flag); + } + // Additional arguments are forwarded to the test harness, so that a subset of the doctests can + // be run. + let test_args = + args.test_args.iter().map(|test_arg| format!("--test-args={test_arg}")).collect::>(); + for test_arg in &test_args { + command.push(test_arg); + } + run_command_with_output_and_env(&command, Some(&library_dir), Some(env))?; + Ok(()) +} + +fn test_stdarch(env: &Env, args: &TestArg) -> Result<(), String> { + println!("[TEST] stdarch"); + let manifest_path = get_sysroot_dir().join("sysroot_src/library/stdarch/Cargo.toml"); + let mut env = env.clone(); + + // `config.setup` already baked `CG_RUSTFLAGS` into `RUSTFLAGS`, so append the lint-allow to + // `RUSTFLAGS` directly (which `run_cargo_command` also propagates to `RUSTDOCFLAGS`). + let rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default(); + env.insert( + "RUSTFLAGS".to_string(), + format!("{rustflags} -Ainternal_features").trim().to_owned(), + ); + env.insert("TARGET".to_string(), args.config_info.target_triple.clone()); + + let mut command: Vec<&dyn AsRef> = + vec![&"test", &"--manifest-path", &manifest_path, &"--"]; + for test_name in &args.test_args { + command.push(test_name); + } + run_cargo_command(&command, None, &env, args)?; + Ok(()) +} + +fn test_alloc(env: &Env, args: &TestArg) -> Result<(), String> { + // FIXME: create a function "display_if_not_quiet" or something along the line. + println!("[TEST] alloc"); + let path = get_sysroot_dir().join("sysroot_src/library/alloctests"); + let _ = remove_dir_all(path.join("target")); // FIXME(antoyo): run in release mode when we fix the failures. run_cargo_command(&[&"test"], Some(&path), env, args)?; Ok(()) @@ -908,7 +1108,6 @@ fn contains_ui_error_patterns(file_path: &Path, keep_lto_tests: bool) -> Result< "//@ known-bug", "-Cllvm-args", "//~", - "thread", ] .iter() .any(|check| line.contains(check)) @@ -934,10 +1133,7 @@ fn contains_ui_error_patterns(file_path: &Path, keep_lto_tests: bool) -> Result< eprintln!("nothing found for {file_path:?}"); } // The files in this directory contain errors. - if file_path.contains("/error-emitter/") { - return Ok(true); - } - Ok(false) + Ok(file_path.contains("/error-emitter/")) } // # Parameters @@ -947,6 +1143,8 @@ fn contains_ui_error_patterns(file_path: &Path, keep_lto_tests: bool) -> Result< // * `prepare_files_callback`: A callback function that prepares the files needed for the test. Its used to remove/retain tests giving Error to run various rust test suits. // * `run_error_pattern_test`: A boolean that determines whether to run only error pattern tests. // * `test_type`: A string that indicates the type of the test being run. +// * `retained_tests_list_path`: The list of tests that `prepare_files_callback` retained, if any. +// It is checked against the tests remaining after the filtering to report dead lines. // fn test_rustc_inner( env: &Env, @@ -954,7 +1152,7 @@ fn test_rustc_inner( prepare_files_callback: F, run_error_pattern_test: bool, test_type: &str, - run_ignored_tests: bool, + retained_tests_list_path: Option<&str>, ) -> Result<(), String> where F: Fn(&Path) -> Result, @@ -970,74 +1168,57 @@ where } if test_type == "ui" { - if run_error_pattern_test { - // After we removed the error tests that are known to panic with rustc_codegen_gcc, we now remove the passing tests since this runs the error tests. - walk_dir( - rust_path.join("tests/ui"), - &mut |_dir| Ok(()), - &mut |file_path| { - if contains_ui_error_patterns(file_path, args.keep_lto_tests)? { - Ok(()) - } else { - remove_file(file_path).map_err(|e| e.to_string()) - } - }, - true, - )?; - } else { - walk_dir( - rust_path.join("tests/ui"), - &mut |dir| { - let dir_name = dir.file_name().and_then(|name| name.to_str()).unwrap_or(""); - if ["abi", "extern", "proc-macro", "threads-sendsync"].contains(&dir_name) { - remove_dir_all(dir).map_err(|error| { - format!("Failed to remove folder `{}`: {:?}", dir.display(), error) - })?; - } - Ok(()) - }, - &mut |_| Ok(()), - false, - )?; - - // These two functions are used to remove files that are known to not be working currently - // with the GCC backend to reduce noise. - fn dir_handling(keep_lto_tests: bool) -> impl Fn(&Path) -> Result<(), String> { - move |dir| { - if dir.file_name().map(|name| name == "auxiliary").unwrap_or(true) { - return Ok(()); - } - - walk_dir( - dir, - &mut dir_handling(keep_lto_tests), - &mut file_handling(keep_lto_tests), - false, - ) + // Each mode runs one half of the ui tests and removes the other: `run_error_pattern_test` + // runs the tests expected to error, the other mode runs the rest. Only `.rs` files outside + // `auxiliary` are tests, so the expected output and the auxiliary crates are left alone. + fn dir_handling( + keep_lto_tests: bool, + remove_error_pattern_tests: bool, + ) -> impl Fn(&Path) -> Result<(), String> { + move |dir| { + if dir.file_name().map(|name| name == "auxiliary").unwrap_or(true) { + return Ok(()); } + + walk_dir( + dir, + &mut dir_handling(keep_lto_tests, remove_error_pattern_tests), + &mut file_handling(keep_lto_tests, remove_error_pattern_tests), + false, + ) } + } - fn file_handling(keep_lto_tests: bool) -> impl Fn(&Path) -> Result<(), String> { - move |file_path| { - if !file_path.extension().map(|extension| extension == "rs").unwrap_or(false) { - return Ok(()); - } - let path_str = file_path.display().to_string().replace("\\", "/"); - if valid_ui_error_pattern_test(&path_str) { - return Ok(()); - } else if contains_ui_error_patterns(file_path, keep_lto_tests)? { - return remove_file(&file_path); - } - Ok(()) + fn file_handling( + keep_lto_tests: bool, + remove_error_pattern_tests: bool, + ) -> impl Fn(&Path) -> Result<(), String> { + move |file_path| { + if !file_path.extension().map(|extension| extension == "rs").unwrap_or(false) { + return Ok(()); + } + let path_str = file_path.display().to_string().replace("\\", "/"); + if valid_ui_error_pattern_test(&path_str) { + return Ok(()); } + if contains_ui_error_patterns(file_path, keep_lto_tests)? + == remove_error_pattern_tests + { + return remove_file(file_path); + } + Ok(()) } + } - walk_dir( - rust_path.join("tests/ui"), - &mut dir_handling(args.keep_lto_tests), - &mut file_handling(args.keep_lto_tests), - false, - )?; + let remove_error_pattern_tests = !run_error_pattern_test; + walk_dir( + rust_path.join("tests/ui"), + &mut dir_handling(args.keep_lto_tests, remove_error_pattern_tests), + &mut file_handling(args.keep_lto_tests, remove_error_pattern_tests), + false, + )?; + if let Some(retained_tests_list_path) = retained_tests_list_path { + check_for_dead_listed_tests(&rust_path, retained_tests_list_path)?; } let nb_parts = args.nb_parts.unwrap_or(0); if nb_parts > 0 { @@ -1097,7 +1278,7 @@ where env.get_mut("RUSTFLAGS").unwrap().clear(); let test_dir = format!("tests/{test_type}"); - let mut command: Vec<&dyn AsRef> = vec![ + let command: Vec<&dyn AsRef> = vec![ &"./x.py", &"test", &"--run", @@ -1112,19 +1293,85 @@ where &"--bypass-ignore-backends", ]; - if run_ignored_tests { - command.push(&"--"); - command.push(&"--ignored"); + run_test_command(&command, &rust_path, &env) +} + +/// Reads the list of tests at `list_path`, checking that each of them still exists in the rust +/// checkout at `rust_path` and that none is listed twice. +/// +/// Both problems make a line a no-op: the test it names is neither kept nor removed, so the test +/// suite silently drifts away from what the list claims to describe. +fn read_test_list(rust_path: &Path, list_path: &str) -> Result, String> { + let content = std::fs::read_to_string(list_path) + .map_err(|error| format!("Failed to read `{list_path}`: {error:?}"))?; + + let mut tests = Vec::new(); + let mut seen = HashSet::new(); + let mut missing = Vec::new(); + let mut duplicated = Vec::new(); + + for line in content.lines().map(|line| line.trim()).filter(|line| !line.is_empty()) { + if !seen.insert(line) { + duplicated.push(line); + continue; + } + if !rust_path.join(line.trim_end_matches('/')).exists() { + missing.push(line); + } + tests.push(line.to_string()); + } + + if missing.is_empty() && duplicated.is_empty() { + return Ok(tests); } - run_command_with_output_and_env(&command, Some(&rust_path), Some(&env))?; - Ok(()) + let mut error = format!("`{list_path}` is out of date:\n"); + if !missing.is_empty() { + error.push_str(&format!( + "\nThese tests no longer exist in `{rust_path}`:\n{missing}\n", + rust_path = rust_path.display(), + missing = missing.join("\n"), + )); + } + if !duplicated.is_empty() { + error.push_str(&format!( + "\nThese tests are listed more than once:\n{}\n", + duplicated.join("\n") + )); + } + error.push_str( + "\nEvery line must name a test that exists, exactly once, otherwise the line filters \ + nothing. Delete the stale lines, or update them to the test's current path.", + ); + Err(error) +} + +/// Checks that every test listed in `list_path` survived the filtering done by +/// `contains_ui_error_patterns`. +fn check_for_dead_listed_tests(rust_path: &Path, list_path: &str) -> Result<(), String> { + let listed_tests = std::fs::read_to_string(list_path) + .map_err(|error| format!("Failed to read `{list_path}`: {error:?}"))?; + let dead_tests = listed_tests + .lines() + .map(|line| line.trim()) + .filter(|line| !line.is_empty() && !rust_path.join(line).exists()) + .collect::>(); + if dead_tests.is_empty() { + return Ok(()); + } + Err(format!( + "The following tests listed in `{list_path}` are filtered out before the tests are run, \ + so listing them has no effect:\n{}\n\nThis happens when a test contains an error pattern \ + (like `//~` or `//@ known-bug`), in which case it should be removed from `{list_path}`, \ + or when it uses LTO, in which case it should be moved to `tests/failing-lto-tests.txt`.", + dead_tests.join("\n") + )) } fn test_rustc(env: &Env, args: &TestArg) -> Result<(), String> { - test_rustc_inner(env, args, |_| Ok(false), false, "run-make", false)?; - test_rustc_inner(env, args, |_| Ok(false), false, "run-make-cargo", false)?; - test_rustc_inner(env, args, |_| Ok(false), false, "ui", false) + test_rustc_inner(env, args, |_| Ok(false), false, "run-make", None)?; + test_rustc_inner(env, args, |_| Ok(false), false, "run-make-cargo", None)?; + test_rustc_inner(env, args, |_| Ok(false), false, "ui", None) } fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> { @@ -1134,7 +1381,7 @@ fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> { retain_files_callback("tests/failing-run-make-tests.txt", "run-make"), false, "run-make", - true, + None, ); let run_make_cargo_result = test_rustc_inner( @@ -1142,8 +1389,8 @@ fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> { args, retain_files_callback("tests/failing-run-make-tests.txt", "run-make-cargo"), false, - "run-make", - true, + "run-make-cargo", + None, ); let ui_result = test_rustc_inner( @@ -1152,36 +1399,51 @@ fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> { retain_files_callback("tests/failing-ui-tests.txt", "ui"), false, "ui", - true, + Some("tests/failing-ui-tests.txt"), ); - run_make_result.and(run_make_cargo_result).and(ui_result) + combine_test_results([run_make_result, run_make_cargo_result, ui_result]) +} + +/// Combines the results of several test suites, letting a build system error win over a test +/// failure so that a broken build system is never reported to CI as the failures those suites +/// expect. +fn combine_test_results(results: [Result<(), String>; N]) -> Result<(), String> { + let mut tests_failed = false; + for result in results { + match result { + Ok(()) => {} + Err(error) if error == TESTS_FAILED_ERROR => tests_failed = true, + Err(error) => return Err(error), + } + } + if tests_failed { Err(TESTS_FAILED_ERROR.to_string()) } else { Ok(()) } } fn test_successful_rustc(env: &Env, args: &TestArg) -> Result<(), String> { test_rustc_inner( env, args, - remove_files_callback("tests/failing-ui-tests.txt", "ui"), + remove_files_callback("tests/failing-ui-tests.txt"), false, "ui", - false, + None, )?; test_rustc_inner( env, args, - remove_files_callback("tests/failing-run-make-tests.txt", "run-make"), + remove_files_callback("tests/failing-run-make-tests.txt"), false, "run-make", - false, + None, )?; test_rustc_inner( env, args, - remove_files_callback("tests/failing-run-make-tests.txt", "run-make-cargo"), + remove_files_callback("tests/failing-run-make-tests.txt"), false, "run-make-cargo", - false, + None, ) } @@ -1189,20 +1451,73 @@ fn test_failing_ui_pattern_tests(env: &Env, args: &TestArg) -> Result<(), String test_rustc_inner( env, args, - remove_files_callback("tests/failing-ice-tests.txt", "ui"), + remove_files_callback("tests/failing-ice-tests.txt"), true, "ui", - false, + None, ) } +fn run_ui_tests(env: &Env, args: &TestArg) -> Result<(), String> { + let mut env = env.clone(); + let rust_path = setup_rustc(&mut env, args)?; + + let extra = + if args.is_using_gcc_master_branch() { "" } else { " -Csymbol-mangling-version=v0" }; + + let rustc_args = format!( + "{test_flags} -Zcodegen-backend={backend} --sysroot {sysroot}{extra}", + test_flags = env.get("TEST_FLAGS").unwrap_or(&String::new()), + backend = args.config_info.cg_backend_path, + sysroot = args.config_info.sysroot_path, + extra = extra, + ); + + env.get_mut("RUSTFLAGS").unwrap().clear(); + + let mut command: Vec<&dyn AsRef> = vec![ + &"./x.py", + &"test", + &"--run", + &"always", + &"--stage", + &"0", + &"--set", + &"build.compiletest-allow-stage0=true", + &"--compiletest-rustc-args", + &rustc_args, + &"--bypass-ignore-backends", + &"--force-rerun", + ]; + + for test_name in &args.test_args { + command.push(test_name); + } + + run_test_command(&command, &rust_path, &env) +} + +/// Runs the command that actually runs a test suite, mapping its failure to `TESTS_FAILED_ERROR`. +fn run_test_command( + command: &[&dyn AsRef], + rust_path: &Path, + env: &Env, +) -> Result<(), String> { + if let Err(error) = run_command_with_output_and_env(command, Some(rust_path), Some(env)) { + // The failures themselves were already streamed to the console. + eprintln!("{error}"); + return Err(TESTS_FAILED_ERROR.to_string()); + } + Ok(()) +} + fn retain_files_callback<'a>( file_path: &'a str, test_type: &'a str, ) -> impl Fn(&Path) -> Result + 'a { move |rust_path| { - let files = std::fs::read_to_string(file_path).unwrap_or_default(); - let first_file_name = files.lines().next().unwrap_or(""); + let tests = read_test_list(rust_path, file_path)?; + let first_file_name = tests.first().map(String::as_str).unwrap_or(""); // If the first line ends with a `/`, we treat all lines in the file as a directory. if first_file_name.ends_with('/') { // Treat as directory @@ -1244,59 +1559,91 @@ fn retain_files_callback<'a>( } // Putting back only the failing ones. - if let Ok(files) = std::fs::read_to_string(file_path) { - for file in files.split('\n').map(|line| line.trim()).filter(|line| !line.is_empty()) { - run_command(&[&"git", &"checkout", &"--", &file], Some(rust_path))?; - } - } else { - println!("Failed to read `{file_path}`, not putting back failing {test_type} tests"); + for test in &tests { + run_command(&[&"git", &"checkout", &"--", test], Some(rust_path))?; } Ok(true) } } -fn remove_files_callback<'a>( - file_path: &'a str, - test_type: &'a str, -) -> impl Fn(&Path) -> Result + 'a { +fn remove_files_callback(file_path: &str) -> impl Fn(&Path) -> Result + '_ { move |rust_path| { - let files = std::fs::read_to_string(file_path).unwrap_or_default(); - let first_file_name = files.lines().next().unwrap_or(""); + let tests = read_test_list(rust_path, file_path)?; + let first_file_name = tests.first().map(String::as_str).unwrap_or(""); // If the first line ends with a `/`, we treat all lines in the file as a directory. if first_file_name.ends_with('/') { // Removing the failing tests. - if let Ok(files) = std::fs::read_to_string(file_path) { - for file in - files.split('\n').map(|line| line.trim()).filter(|line| !line.is_empty()) - { - let path = rust_path.join(file); - if let Err(e) = remove_dir_all(&path) { - println!("Failed to remove directory `{}`: {}", path.display(), e); - } - } - } else { - println!( - "Failed to read `{file_path}`, not putting back failing {test_type} tests" - ); + for test in &tests { + let path = rust_path.join(test); + remove_dir_all(&path).map_err(|error| { + format!("Failed to remove directory `{}`: {error}", path.display()) + })?; } } else { // Removing the failing tests. - if let Ok(files) = std::fs::read_to_string(file_path) { - for file in - files.split('\n').map(|line| line.trim()).filter(|line| !line.is_empty()) - { - let path = rust_path.join(file); - remove_file(&path)?; - } - } else { - println!("Failed to read `{file_path}`, not putting back failing ui tests"); + for test in &tests { + remove_file(&rust_path.join(test))?; } } Ok(true) } } +fn test_asm(env: &Env, args: &TestArg) -> Result<(), String> { + fn is_path_time_more_recent(ref_time: std::time::SystemTime, path: &str) -> bool { + std::fs::metadata(path) + .and_then(|metadata| metadata.modified()) + .is_ok_and(|time| ref_time < time) + } + + // FIXME: create a function "display_if_not_quiet" or something along the line. + println!("[TEST] cg_gcc assembly"); + let llvm_filecheck = get_llvm_filecheck(env)?; + + let target_dir = std::env::current_dir().unwrap().join("build_system/asm-tester/target"); + + // All this code is because `cargo` keeps recompiling this file, and we can't figure out why. + let binary_file_path = "build_system/asm-tester/target/debug/asm-tester"; + let mut need_recompilation = true; + if let Ok(metadata) = std::fs::metadata(binary_file_path) + && let Ok(ref_time) = metadata.modified() + && !is_path_time_more_recent(ref_time, "build_system/asm-tester/Cargo.toml") + && !is_path_time_more_recent(ref_time, "build_system/asm-tester/Cargo.lock") + && !is_path_time_more_recent(ref_time, "build_system/asm-tester/src/main.rs") + { + need_recompilation = false; + } + + if need_recompilation { + let build_asm_args: Vec<&dyn AsRef> = vec![ + &"cargo", + &"build", + &"--manifest-path", + &"build_system/asm-tester/Cargo.toml", + &"--target-dir", + &target_dir, + &"--", + ]; + run_command_with_output_and_env_no_err(&build_asm_args, Some(Path::new(".")), Some(env))?; + } + + let mut test_asm_args: Vec<&dyn AsRef> = vec![ + &"build_system/asm-tester/target/debug/asm-tester", + &"--llvm-filecheck", + &llvm_filecheck, + ]; + for test_arg in &args.test_args { + test_asm_args.push(&"--filter"); + test_asm_args.push(test_arg); + } + test_asm_args.push(&"--"); + for arg in args.config_info.rustc_command_vec().into_iter().skip(1) { + test_asm_args.push(arg); + } + run_command_with_output_and_env_no_err(&test_asm_args, Some(Path::new(".")), Some(env)) +} + fn run_all(env: &Env, args: &TestArg) -> Result<(), String> { clean(env, args)?; mini_tests(env, args)?; @@ -1308,6 +1655,7 @@ fn run_all(env: &Env, args: &TestArg) -> Result<(), String> { cargo_tests(env, args)?; no_builtins_tests(env, args)?; test_rustc(env, args)?; + test_asm(env, args)?; Ok(()) } @@ -1329,7 +1677,7 @@ pub fn run() -> Result<(), String> { return Ok(()); } - args.config_info.setup(&mut env, args.use_system_gcc)?; + args.config_info.setup(&mut env, args.use_system_gcc, true)?; if args.runners.is_empty() { run_all(&env, &args)?; @@ -1342,3 +1690,58 @@ pub fn run() -> Result<(), String> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + fn write_test_list(directory: &Path, content: &str) -> PathBuf { + let list_path = directory.join("failing-tests.txt"); + std::fs::write(&list_path, content).unwrap(); + list_path + } + + #[test] + fn test_combine_test_results() { + let tests_failed = || Err(TESTS_FAILED_ERROR.to_string()); + let build_error = || Err("could not clone rust".to_string()); + + assert_eq!(combine_test_results([Ok(()), Ok(())]), Ok(())); + assert_eq!(combine_test_results([Ok(()), tests_failed()]), tests_failed()); + assert_eq!(combine_test_results([Ok(()), build_error()]), build_error()); + // A build system error wins, whichever suite reported it. + assert_eq!(combine_test_results([tests_failed(), build_error()]), build_error()); + assert_eq!(combine_test_results([build_error(), tests_failed()]), build_error()); + } + + #[test] + fn test_read_test_list() { + let rust_path = std::env::temp_dir().join("cg_gcc_read_test_list"); + let _ = remove_dir_all(&rust_path); + create_dir(rust_path.join("tests/ui")).unwrap(); + std::fs::write(rust_path.join("tests/ui/alive.rs"), "").unwrap(); + + let list_path = write_test_list(&rust_path, "\ntests/ui/alive.rs\n \n"); + let list_path = list_path.display().to_string(); + assert_eq!( + read_test_list(&rust_path, &list_path), + Ok(vec!["tests/ui/alive.rs".to_string()]) + ); + + write_test_list(&rust_path, "tests/ui/alive.rs\ntests/ui/gone.rs\n"); + let error = read_test_list(&rust_path, &list_path).unwrap_err(); + assert!(error.contains("no longer exist"), "{error}"); + assert!(error.contains("tests/ui/gone.rs"), "{error}"); + + write_test_list(&rust_path, "tests/ui/alive.rs\ntests/ui/alive.rs\n"); + let error = read_test_list(&rust_path, &list_path).unwrap_err(); + assert!(error.contains("listed more than once"), "{error}"); + assert!(error.contains("tests/ui/alive.rs"), "{error}"); + + // Directories are listed with a trailing `/`. + write_test_list(&rust_path, "tests/ui/\n"); + assert_eq!(read_test_list(&rust_path, &list_path), Ok(vec!["tests/ui/".to_string()])); + + remove_dir_all(&rust_path).unwrap(); + } +} diff --git a/compiler/rustc_codegen_gcc/build_system/src/todo.rs b/compiler/rustc_codegen_gcc/build_system/src/todo.rs new file mode 100644 index 0000000000000..5b89410844788 --- /dev/null +++ b/compiler/rustc_codegen_gcc/build_system/src/todo.rs @@ -0,0 +1,72 @@ +use std::ffi::OsStr; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +const EXTENSIONS: &[&str] = + &["rs", "py", "js", "sh", "c", "cpp", "h", "md", "css", "ftl", "toml", "yml", "yaml"]; + +fn has_supported_extension(path: &Path) -> bool { + path.extension().is_some_and(|ext| EXTENSIONS.iter().any(|e| ext == OsStr::new(e))) +} + +fn list_tracked_files() -> Result, String> { + let output = Command::new("git") + .args(["ls-files", "-z"]) + .output() + .map_err(|e| format!("Failed to run `git ls-files`: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("`git ls-files` failed: {stderr}")); + } + + let mut files = Vec::new(); + for entry in output.stdout.split(|b| *b == 0) { + if entry.is_empty() { + continue; + } + let path = std::str::from_utf8(entry).unwrap(); + files.push(PathBuf::from(path)); + } + + Ok(files) +} + +pub(crate) fn run() -> Result<(), String> { + let files = list_tracked_files()?; + let mut error_count = 0; + // Avoid embedding the task marker in source so greps only find real occurrences. + let todo_marker = "todo".to_ascii_uppercase(); + + for file in files { + if !has_supported_extension(&file) { + continue; + } + + let file_handle = + File::open(&file).map_err(|e| format!("Failed to open {}: {e}", file.display()))?; + let reader = BufReader::new(file_handle); + + for (i, line) in reader.lines().enumerate() { + let line = line.map_err(|e| format!("Failed to read {}: {e}", file.display()))?; + let trimmed = line.trim(); + if trimmed.contains(&todo_marker) { + eprintln!( + "{}:{}: {} is used for tasks that should be done before merging a PR; if you want to leave a message in the codebase use FIXME", + file.display(), + i + 1, + todo_marker + ); + error_count += 1; + } + } + } + + if error_count == 0 { + return Ok(()); + } + + Err(format!("found {} {}(s)", error_count, todo_marker)) +} diff --git a/compiler/rustc_codegen_gcc/build_system/src/utils.rs b/compiler/rustc_codegen_gcc/build_system/src/utils.rs index 112322f8688c1..4c67156a85fb2 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/utils.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/utils.rs @@ -2,10 +2,11 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Debug; use std::fs; +use std::io::{BufReader, Read}; #[cfg(unix)] use std::os::unix::process::ExitStatusExt; use std::path::{Path, PathBuf}; -use std::process::{Command, ExitStatus, Output}; +use std::process::{Command, ExitStatus, Output, Stdio}; fn exec_command( input: &[&dyn AsRef], @@ -47,7 +48,7 @@ pub(crate) fn get_command_inner( command } -fn check_exit_status( +pub(crate) fn check_exit_status( input: &[&dyn AsRef], cwd: Option<&Path>, exit_status: ExitStatus, @@ -115,6 +116,30 @@ pub fn run_command_with_output( check_exit_status(input, cwd, exit_status, None, true) } +pub fn run_command_with_output_and_get_it( + input: &[&dyn AsRef], + cwd: Option<&Path>, +) -> Result<(ExitStatus, String), String> { + let mut child = get_command_inner(input, cwd, None) + .stderr(Stdio::piped()) + .spawn() + .map_err(|e| command_error(input, &cwd, e))?; + + let stderr = child.stderr.take().expect("Failed to capture stderr"); + let mut captured = String::new(); + BufReader::new(stderr).read_to_string(&mut captured).expect("failed to read stderr"); + + let status = child.wait().map_err(|e| command_error(input, &cwd, e))?; + #[cfg(unix)] + { + if let Some(signal) = status.signal() { + // In case the signal didn't kill the current process. + return Err(command_error(input, &cwd, format!("Process received signal {signal}"))); + } + } + Ok((status, captured)) +} + pub fn run_command_with_output_and_env( input: &[&dyn AsRef], cwd: Option<&Path>, @@ -124,7 +149,6 @@ pub fn run_command_with_output_and_env( check_exit_status(input, cwd, exit_status, None, true) } -#[cfg(not(unix))] pub fn run_command_with_output_and_env_no_err( input: &[&dyn AsRef], cwd: Option<&Path>, @@ -419,6 +443,34 @@ pub fn get_sysroot_dir() -> PathBuf { Path::new(crate::BUILD_DIR).join("build_sysroot") } +pub fn run_tool_and_install_it_if_not_present(cmd: &[&dyn AsRef]) -> Result<(), String> { + let (exit_status, stderr) = run_command_with_output_and_get_it(cmd, Some(Path::new(".")))?; + if exit_status.success() { + return Ok(()); + } + let mut iter = stderr.split('\n'); + if let Some(line) = iter.next() + && line.contains("is not installed for the toolchain") + && let Some(line) = iter.next() + && line.contains("run `rustup component add") + && let Some(cmd) = line.split('`').nth(1) + && let Some(tool_name) = cmd.rsplit(' ').next() + { + println!("`{tool_name}` is not installed for this toolchain, installing it..."); + // A weird round-about way to get a `&&str` so I can get a `&dyn AsRef` but + // as long as it works... + let cmd = cmd.split(' ').collect::>(); + let cmd = cmd.iter().map(|s: &&str| s as &dyn AsRef).collect::>(); + run_command_with_output(cmd.as_slice(), Some(Path::new(".")))?; + } else { + // If the component is installed, then it's something else. In this case we fail like we + // should have and let the user handles the error. + return check_exit_status(cmd, Some(Path::new(".")), exit_status, None, true); + } + // We retry the command... + run_command_with_output(cmd, Some(Path::new("."))) +} + #[cfg(test)] mod tests { use super::*; diff --git a/compiler/rustc_codegen_gcc/clippy.toml b/compiler/rustc_codegen_gcc/clippy.toml new file mode 100644 index 0000000000000..cf1593c691734 --- /dev/null +++ b/compiler/rustc_codegen_gcc/clippy.toml @@ -0,0 +1,3 @@ +disallowed-methods = [ + { path = "gccjit::types::Type::add_attribute", reason = "go through `type_::apply_struct_attributes` instead: an attribute set directly on a type would not be part of the `CodegenCx::struct_types` cache key, so it would silently change every other use of that type" }, +] diff --git a/compiler/rustc_codegen_gcc/doc/subtree.md b/compiler/rustc_codegen_gcc/doc/subtree.md index a81b6c9c74bdd..fcac399e46542 100644 --- a/compiler/rustc_codegen_gcc/doc/subtree.md +++ b/compiler/rustc_codegen_gcc/doc/subtree.md @@ -1,7 +1,7 @@ # git subtree sync `rustc_codegen_gcc` is a subtree of the rust compiler. As such, it needs to be -sync from time to time to ensure changes that happened on their side are also +synced from time to time to ensure changes that happened on their side are also included on our side. ### How to install a forked git-subtree @@ -41,6 +41,8 @@ cd ../rust git pull origin master git checkout -b subtree-update_cg_gcc_YYYY-MM-DD PATH="$HOME/bin:$PATH" ~/bin/git-subtree pull --prefix=compiler/rustc_codegen_gcc/ https://github.com/rust-lang/rustc_codegen_gcc.git master +# Don't forget to update the `gcc` submodule to the same version as the +# one in `rustc_codegen_gcc/libgccjit.version`. git push # Immediately merge the merge commit into cg_gcc to prevent merge conflicts when syncing from rust-lang/rust later. diff --git a/compiler/rustc_codegen_gcc/doc/tips.md b/compiler/rustc_codegen_gcc/doc/tips.md index ff92566d4a1ab..dc40ee4d39952 100644 --- a/compiler/rustc_codegen_gcc/doc/tips.md +++ b/compiler/rustc_codegen_gcc/doc/tips.md @@ -58,7 +58,7 @@ If you wish to build a custom sysroot, pass the path of your sysroot source to ` ### How to generate GIMPLE If you need to check what gccjit is generating (GIMPLE), then take a look at how to -generate it in [gimple.md](./doc/gimple.md). +generate it in [gimple.md](./gimple.md). ### How to build a cross-compiling libgccjit diff --git a/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs b/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs index 6e155f89ee5cc..ab841d51a7f53 100644 --- a/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs +++ b/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs @@ -6,7 +6,7 @@ )] #![no_core] #![allow(dead_code, internal_features, non_camel_case_types)] -#![rustfmt_skip] +#![cfg_attr(rustfmt, rustfmt_skip)] extern crate mini_core; diff --git a/compiler/rustc_codegen_gcc/libgccjit.version b/compiler/rustc_codegen_gcc/libgccjit.version index 5eef70260466f..62417a80f827e 100644 --- a/compiler/rustc_codegen_gcc/libgccjit.version +++ b/compiler/rustc_codegen_gcc/libgccjit.version @@ -1 +1 @@ -6f155cc3f5a2dff33afe6cc3ed6c2e0e605ae6a3 +badf78d09d16e66f4ca07971c51aa6a227558d4f diff --git a/compiler/rustc_codegen_gcc/patches/0001-Add-stdarch-Cargo.toml-for-testing.patch b/compiler/rustc_codegen_gcc/patches/0001-Add-stdarch-Cargo.toml-for-testing.patch deleted file mode 100644 index 3a8c37a8b8d9a..0000000000000 --- a/compiler/rustc_codegen_gcc/patches/0001-Add-stdarch-Cargo.toml-for-testing.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 190e26c9274b3c93a9ee3516b395590e6bd9213b Mon Sep 17 00:00:00 2001 -From: None -Date: Sun, 3 Aug 2025 19:54:56 -0400 -Subject: [PATCH] Patch 0001-Add-stdarch-Cargo.toml-for-testing.patch - ---- - library/stdarch/Cargo.toml | 20 ++++++++++++++++++++ - 1 file changed, 20 insertions(+) - create mode 100644 library/stdarch/Cargo.toml - -diff --git a/library/stdarch/Cargo.toml b/library/stdarch/Cargo.toml -new file mode 100644 -index 0000000..bd6725c ---- /dev/null -+++ b/library/stdarch/Cargo.toml -@@ -0,0 +1,20 @@ -+[workspace] -+resolver = "1" -+members = [ -+ "crates/*", -+ #"examples/" -+] -+exclude = [ -+ "crates/wasm-assert-instr-tests", -+ "rust_programs", -+] -+ -+[profile.release] -+debug = true -+opt-level = 3 -+incremental = true -+ -+[profile.bench] -+debug = 1 -+opt-level = 3 -+incremental = true --- -2.50.1 - diff --git a/compiler/rustc_codegen_gcc/rust-toolchain b/compiler/rustc_codegen_gcc/rust-toolchain index 56fcfdff1c719..0c81f7c7c7398 100644 --- a/compiler/rustc_codegen_gcc/rust-toolchain +++ b/compiler/rustc_codegen_gcc/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-04-29" +channel = "nightly-2026-09-18" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] diff --git a/compiler/rustc_codegen_gcc/src/abi.rs b/compiler/rustc_codegen_gcc/src/abi.rs index 6a05f1cbbeef1..63eaf52ce9f01 100644 --- a/compiler/rustc_codegen_gcc/src/abi.rs +++ b/compiler/rustc_codegen_gcc/src/abi.rs @@ -73,7 +73,7 @@ impl GccType for CastTarget { args.push(cx.type_ix(rem_bytes * 8)); } - cx.type_struct(&args, false) + cx.type_struct(&args, &[]) } } @@ -146,12 +146,23 @@ impl<'gcc, 'tcx> FnAbiGccExt<'gcc, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { if attrs.regular.contains(rustc_target::callconv::ArgAttribute::NonNull) { non_null_args.push(arg_index as i32 + 1); } + // There are a few others `ArgAttribute` variants" + // + // * ArgAttribute::ReadOnly: `access(read_only())`, but it's only used for emitting + // warning, not for optimization. + // * ArgAttribute::NoUndef: No equivalent in GCC + // * ArgAttribute::Writable: `access(read_write())` or `access(write_only())`, but it's + // only used for emitting warning, not for optimization. + // * ArgAttribute::NoFree: No equivalent in GCC ty }; #[cfg(not(feature = "master"))] let apply_attrs = |ty: Type<'gcc>, _attrs: &ArgAttributes, _arg_index: usize| ty; - for arg in self.args.iter() { + for (source_arg_index, arg) in self.args.iter().enumerate() { + #[cfg(not(feature = "master"))] + let _ = source_arg_index; + let arg_ty = match arg.mode { PassMode::Ignore => continue, PassMode::Pair(a, b) => { @@ -179,9 +190,31 @@ impl<'gcc, 'tcx> FnAbiGccExt<'gcc, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { apply_attrs(ty, &cast.attrs, argument_tys.len()) } PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: true } => { - // This is a "byval" argument, so we don't apply the `restrict` attribute on it. - on_stack_param_indices.insert(argument_tys.len()); - arg.layout.gcc_type(cx) + let x86_interrupt_first_arg = { + #[cfg(feature = "master")] + { + source_arg_index == 0 + && matches!(self.conv, CanonAbi::Interrupt(InterruptKind::X86)) + } + #[cfg(not(feature = "master"))] + { + false + } + }; + + if x86_interrupt_first_arg { + // Rust lowers the first `x86-interrupt` argument as a byval stack slot. + // LLVM represents that as a pointer parameter with `byval`; GCC's + // interrupt attribute likewise requires a pointer-shaped first parameter. + // Do not add this parameter to `on_stack_param_indices`: that set is only + // needed when GCC represents a byval argument as a value parameter, while + // this parameter is already pointer-shaped. + cx.type_ptr_to(arg.layout.gcc_type(cx)) + } else { + // This is a "byval" argument, so we don't apply the `restrict` attribute on it. + on_stack_param_indices.insert(argument_tys.len()); + arg.layout.gcc_type(cx) + } } PassMode::Direct(attrs) => { apply_attrs(arg.layout.immediate_gcc_type(cx), &attrs, argument_tys.len()) diff --git a/compiler/rustc_codegen_gcc/src/asm.rs b/compiler/rustc_codegen_gcc/src/asm.rs index 420bf1e7a31c1..75a48c217c49b 100644 --- a/compiler/rustc_codegen_gcc/src/asm.rs +++ b/compiler/rustc_codegen_gcc/src/asm.rs @@ -297,7 +297,9 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { out_place, }); - if !readwrite { + if readwrite { + self.llbb().add_assignment(None, tmp_var, in_value.immediate()); + } else { let out_gcc_idx = outputs.len() - 1; let constraint = Cow::Owned(out_gcc_idx.to_string()); @@ -363,7 +365,14 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { let ty = value.layout.gcc_type(self.cx); let reg_var = self.current_func().new_local(None, ty, "input_register"); reg_var.set_register_name(reg_name); - self.llbb().add_assignment(None, reg_var, value.immediate()); + // FIXME: We should remove this when switching to "untyped" pointers + let value = value.immediate(); + let value = if value.get_type() != ty { + self.context.new_cast(None, value, ty) + } else { + value + }; + self.llbb().add_assignment(None, reg_var, value); inputs.push(AsmInOperand { constraint: "r".into(), @@ -602,6 +611,12 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { self.llbb().add_eval(None, self.context.new_call(None, builtin_unreachable, &[])); } + if !options.contains(InlineAsmOptions::NORETURN) + && let Some(dest) = dest + { + self.switch_to_block(dest); + } + // Write results to outputs. // // We need to do this because: @@ -681,6 +696,7 @@ fn explicit_reg_to_gcc(reg: InlineAsmReg) -> &'static str { } InlineAsmReg::Arm(reg) => reg.name(), InlineAsmReg::AArch64(reg) => reg.name(), + InlineAsmReg::M68k(reg) => reg.name(), _ => unimplemented!(), } } diff --git a/compiler/rustc_codegen_gcc/src/attributes.rs b/compiler/rustc_codegen_gcc/src/attributes.rs index a5cc44a46e154..41db5e83bdcc9 100644 --- a/compiler/rustc_codegen_gcc/src/attributes.rs +++ b/compiler/rustc_codegen_gcc/src/attributes.rs @@ -2,6 +2,8 @@ use gccjit::FnAttribute; use gccjit::Function; #[cfg(feature = "master")] +use rustc_abi::{CanonAbi, InterruptKind}; +#[cfg(feature = "master")] use rustc_hir::attrs::InlineAttr; use rustc_hir::attrs::InstructionSetAttr; #[cfg(feature = "master")] @@ -9,9 +11,12 @@ use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; #[cfg(feature = "master")] use rustc_middle::mir::TerminatorKind; use rustc_middle::ty; +use rustc_target::callconv::FnAbi; #[cfg(feature = "master")] use rustc_target::spec::Arch; +#[cfg(feature = "master")] +use crate::base; use crate::context::CodegenCx; use crate::gcc_util::to_gcc_features; @@ -82,12 +87,23 @@ fn inline_attr<'gcc, 'tcx>( } } +#[cfg(feature = "master")] +fn is_x86_interrupt<'tcx>(fn_abi: Option<&FnAbi<'tcx, ty::Ty<'tcx>>>) -> bool { + matches!( + fn_abi, + Some(fn_abi) if matches!(fn_abi.conv, CanonAbi::Interrupt(InterruptKind::X86)) + ) +} + /// Composite function which sets GCC attributes for function depending on its AST (`#[attribute]`) /// attributes. pub fn from_fn_attrs<'gcc, 'tcx>( cx: &CodegenCx<'gcc, 'tcx>, #[cfg_attr(not(feature = "master"), expect(unused_variables))] func: Function<'gcc>, instance: ty::Instance<'tcx>, + #[cfg_attr(not(feature = "master"), expect(unused_variables))] fn_abi: Option< + &FnAbi<'tcx, ty::Ty<'tcx>>, + >, ) { let codegen_fn_attrs = cx.tcx.codegen_instance_attrs(instance.def); @@ -102,6 +118,16 @@ pub fn from_fn_attrs<'gcc, 'tcx>( } else { codegen_fn_attrs.inline }; + // GCC drops `weak` from a function that is also `inline`, leaving the symbol strong, and + // the linkage is what has to survive. `inline(never)` does not conflict. + let inline = match inline { + InlineAttr::Always | InlineAttr::Hint | InlineAttr::Force { .. } + if codegen_fn_attrs.linkage.is_some_and(base::linkage_needs_weak_attribute) => + { + InlineAttr::None + } + inline => inline, + }; if let Some(attr) = inline_attr(cx, inline, instance) { if let FnAttribute::AlwaysInline = attr { func.add_attribute(FnAttribute::Inline); @@ -120,6 +146,11 @@ pub fn from_fn_attrs<'gcc, 'tcx>( } } + #[cfg(feature = "master")] + let x86_interrupt = is_x86_interrupt(fn_abi); + #[cfg(not(feature = "master"))] + let x86_interrupt = false; + let mut function_features = codegen_fn_attrs .target_features .iter() @@ -135,6 +166,13 @@ pub fn from_fn_attrs<'gcc, 'tcx>( // Check if GCC requires the same. let mut global_features = cx.tcx.sess.global_backend_features.iter().map(|s| s.as_str()); function_features.extend(&mut global_features); + if x86_interrupt { + // GCC does not preserve SSE, MMX, or x87 state in interrupt handlers and rejects + // them whenever those instruction sets are enabled, even if the handler does not + // emit such instructions. Restrict the function to general registers so the + // interrupt attribute works with the default x86_64 target features. + function_features.push("general-regs-only"); + } let target_features = function_features .iter() .filter_map(|feature| { diff --git a/compiler/rustc_codegen_gcc/src/back/lto.rs b/compiler/rustc_codegen_gcc/src/back/lto.rs index 98f9abdb05c4c..baf1fda02e258 100644 --- a/compiler/rustc_codegen_gcc/src/back/lto.rs +++ b/compiler/rustc_codegen_gcc/src/back/lto.rs @@ -20,6 +20,7 @@ use std::ffi::CString; use std::fs::{self, File}; use std::path::{Path, PathBuf}; +use std::sync::Arc; use gccjit::OutputKind; use object::read::archive::ArchiveFile; @@ -29,14 +30,15 @@ use rustc_codegen_ssa::back::write::{CodegenContext, FatLtoInput, SharedEmitter} use rustc_codegen_ssa::traits::*; use rustc_codegen_ssa::{CompiledModule, ModuleCodegen, ModuleKind}; use rustc_data_structures::memmap::Mmap; -use rustc_data_structures::profiling::SelfProfilerRef; use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_log::tracing::info; +use rustc_session::Session; use tempfile::{TempDir, tempdir}; use crate::back::write::{codegen, save_temp_bitcode}; use crate::diagnostics::LtoBitcodeFromRlib; -use crate::{GccCodegenBackend, GccContext, LtoMode, to_gcc_opt_level}; +use crate::gcc_util::new_context; +use crate::{GccCodegenBackend, GccContext, LtoMode, SyncContext, to_gcc_opt_level}; struct LtoData { // FIXME(antoyo): use symbols_below_threshold. @@ -102,8 +104,8 @@ fn save_as_file(obj: &[u8], path: &Path) -> Result<(), LtoBitcodeFromRlib> { /// Performs fat LTO by merging all modules into a single one and returning it /// for further optimization. pub(crate) fn run_fat( + sess: &Session, cgcx: &CodegenContext, - prof: &SelfProfilerRef, shared_emitter: &SharedEmitter, each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, @@ -114,8 +116,8 @@ pub(crate) fn run_fat( /*let symbols_below_threshold = lto_data.symbols_below_threshold.iter().map(|c| c.as_ptr()).collect::>();*/ fat_lto( + sess, cgcx, - prof, dcx, modules, lto_data.upstream_modules, @@ -125,15 +127,15 @@ pub(crate) fn run_fat( } fn fat_lto( + sess: &Session, cgcx: &CodegenContext, - prof: &SelfProfilerRef, dcx: DiagCtxtHandle<'_>, modules: Vec>, mut serialized_modules: Vec<(SerializedModule, CString)>, tmp_path: TempDir, //symbols_below_threshold: &[String], ) -> CompiledModule { - let _timer = prof.generic_activity("GCC_fat_lto_build_monolithic_module"); + let _timer = sess.prof.generic_activity("GCC_fat_lto_build_monolithic_module"); info!("going for a fat lto"); // Sort out all our lists of incoming modules into two lists. @@ -183,17 +185,16 @@ fn fat_lto( // module and create a linker with it. let mut module: ModuleCodegen = match costliest_module { Some((_cost, i)) => in_memory.remove(i), - None => { - unimplemented!("Incremental"); - /*assert!(!serialized_modules.is_empty(), "must have at least one serialized module"); - let (buffer, name) = serialized_modules.remove(0); - info!("no in-memory regular modules to choose from, parsing {:?}", name); - ModuleCodegen { - module_llvm: GccContext::parse(cgcx, &name, buffer.data(), dcx)?, - name: name.into_string().unwrap(), - kind: ModuleKind::Regular, - }*/ - } + None => ModuleCodegen::new_regular( + "lto_module".to_string(), + GccContext { + context: Arc::new(SyncContext::new(new_context(sess))), + relocation_model: sess.relocation_model(), + lto_supported: true, + lto_mode: LtoMode::None, + temp_dir: None, + }, + ), }; { info!("using {:?} as a base module", module.name); @@ -220,7 +221,8 @@ fn fat_lto( // We add the object files and save in should_combine_object_files that we should combine // them into a single object file when compiling later. for (bc_decoded, name) in serialized_modules { - let _timer = prof + let _timer = sess + .prof .generic_activity_with_arg_recorder("GCC_fat_lto_link_module", |recorder| { recorder.record_arg(format!("{:?}", name)) }); @@ -258,7 +260,7 @@ fn fat_lto( // of now. module.module_llvm.temp_dir = Some(tmp_path); - codegen(cgcx, prof, dcx, module, &cgcx.module_config) + codegen(cgcx, &sess.prof, dcx, module, &cgcx.module_config) } pub struct ModuleBuffer(PathBuf); diff --git a/compiler/rustc_codegen_gcc/src/back/write.rs b/compiler/rustc_codegen_gcc/src/back/write.rs index cf5514412f745..1f4fd8a314ad2 100644 --- a/compiler/rustc_codegen_gcc/src/back/write.rs +++ b/compiler/rustc_codegen_gcc/src/back/write.rs @@ -11,8 +11,8 @@ use rustc_log::tracing::debug; use rustc_session::config::OutputType; use rustc_target::spec::SplitDebuginfo; -use crate::base::add_pic_option; use crate::diagnostics::CopyBitcode; +use crate::gcc_util::add_pic_option; use crate::{GccContext, LtoMode}; pub(crate) fn codegen( @@ -60,9 +60,6 @@ pub(crate) fn codegen( let _timer = prof .generic_activity_with_arg("GCC_module_codegen_embed_bitcode", &*module.name); if lto_supported { - // FIXME(antoyo): maybe we should call embed_bitcode to have the proper iOS fixes? - //embed_bitcode(cgcx, llcx, llmod, &config.bc_cmdline, data); - context.add_command_line_option("-flto=auto"); context.add_command_line_option("-flto-partition=one"); context.add_command_line_option("-ffat-lto-objects"); diff --git a/compiler/rustc_codegen_gcc/src/base.rs b/compiler/rustc_codegen_gcc/src/base.rs index 101af0bb0bff1..436a9e5227352 100644 --- a/compiler/rustc_codegen_gcc/src/base.rs +++ b/compiler/rustc_codegen_gcc/src/base.rs @@ -1,9 +1,9 @@ -use std::collections::HashSet; -use std::env; use std::sync::Arc; use std::time::Instant; -use gccjit::{CType, Context, FunctionType, GlobalKind}; +#[cfg(feature = "master")] +use gccjit::VarAttribute; +use gccjit::{CType, FunctionType, GlobalKind}; use rustc_codegen_ssa::ModuleCodegen; use rustc_codegen_ssa::base::maybe_create_entry_wrapper; use rustc_codegen_ssa::mono_item::MonoItemExt; @@ -17,11 +17,11 @@ use rustc_session::config::DebugInfo; use rustc_span::Symbol; #[cfg(feature = "master")] use rustc_target::spec::SymbolVisibility; -use rustc_target::spec::{Arch, RelocModel}; use crate::builder::Builder; use crate::context::CodegenCx; -use crate::{GccContext, LtoMode, SharedTargetInfo, SyncContext, gcc_util, new_context}; +use crate::gcc_util::new_context; +use crate::{GccContext, LtoMode, SharedTargetInfo, SyncContext}; #[cfg(feature = "master")] pub fn visibility_to_gcc(visibility: Visibility) -> gccjit::Visibility { @@ -41,32 +41,72 @@ pub fn symbol_visibility_to_gcc(visibility: SymbolVisibility) -> gccjit::Visibil } } +/// The kind of a global *definition* with an explicit `#[linkage]`. +/// +/// The flavours that another object file is allowed to override also need +/// `global_linkage_attribute` from the caller: `GlobalKind` alone cannot express weakness. pub fn global_linkage_to_gcc(linkage: Linkage) -> GlobalKind { match linkage { - Linkage::External => GlobalKind::Imported, - Linkage::AvailableExternally => GlobalKind::Imported, - Linkage::LinkOnceAny => unimplemented!(), - Linkage::LinkOnceODR => unimplemented!(), - Linkage::WeakAny => unimplemented!(), - Linkage::WeakODR => unimplemented!(), - Linkage::Internal => GlobalKind::Internal, - Linkage::ExternalWeak => GlobalKind::Imported, // FIXME(antoyo): should be weak linkage. - Linkage::Common => unimplemented!(), + Linkage::External => GlobalKind::Exported, + // libgccjit cannot emit a definition that the linker discards in favour of the one in + // another object file, so emit a private copy of it instead. + Linkage::AvailableExternally | Linkage::Internal => GlobalKind::Internal, + // libgccjit exposes no comdat, so `weak` stands in for the linkonce flavours. + Linkage::LinkOnceAny + | Linkage::LinkOnceODR + | Linkage::WeakAny + | Linkage::WeakODR + | Linkage::ExternalWeak + | Linkage::Common => GlobalKind::Exported, + } +} + +/// The attribute a global *definition* needs on top of its [`GlobalKind`] to get this linkage. +#[cfg(feature = "master")] +pub fn global_linkage_attribute<'gcc>(linkage: Linkage) -> Option> { + match linkage { + Linkage::Common => Some(VarAttribute::Common), + _ if linkage_needs_weak_attribute(linkage) => Some(VarAttribute::Weak), + _ => None, } } +/// The type of a function *definition* with an explicit `#[linkage]`. +/// +/// The flavours that another object file is allowed to override also need +/// `linkage_needs_weak_attribute` from the caller: `FunctionType` alone cannot express weakness. pub fn linkage_to_gcc(linkage: Linkage) -> FunctionType { match linkage { Linkage::External => FunctionType::Exported, - // FIXME(antoyo): set the attribute externally_visible. - Linkage::AvailableExternally => FunctionType::Extern, - Linkage::LinkOnceAny => unimplemented!(), - Linkage::LinkOnceODR => unimplemented!(), - Linkage::WeakAny => FunctionType::Exported, // FIXME(antoyo): should be similar to linkonce. - Linkage::WeakODR => unimplemented!(), - Linkage::Internal => FunctionType::Internal, - Linkage::ExternalWeak => unimplemented!(), - Linkage::Common => unimplemented!(), + // libgccjit cannot emit a definition that the linker discards in favour of the one in + // another object file, so emit a private copy of it instead. + Linkage::AvailableExternally | Linkage::Internal => FunctionType::Internal, + // libgccjit exposes no comdat, so `weak` stands in for every overridable flavour. + Linkage::LinkOnceAny + | Linkage::LinkOnceODR + | Linkage::WeakAny + | Linkage::WeakODR + | Linkage::ExternalWeak + | Linkage::Common => FunctionType::Exported, + } +} + +/// Whether a definition with this linkage must carry the `weak` attribute, so that a strong +/// definition in another object file wins over it instead of clashing with it. +/// +/// `common` is in here for functions only: GCC honours that attribute on a variable, but drops it +/// on a function, so a common function falls back to weak. Globals go through +/// `global_linkage_attribute` instead. +#[cfg(feature = "master")] +pub fn linkage_needs_weak_attribute(linkage: Linkage) -> bool { + match linkage { + Linkage::LinkOnceAny + | Linkage::LinkOnceODR + | Linkage::WeakAny + | Linkage::WeakODR + | Linkage::ExternalWeak + | Linkage::Common => true, + Linkage::External | Linkage::AvailableExternally | Linkage::Internal => false, } } @@ -101,41 +141,7 @@ pub fn compile_codegen_unit( ) -> ModuleCodegen { let cgu = tcx.codegen_unit(cgu_name); // Instantiate monomorphizations without filling out definitions yet... - let context = new_context(tcx); - - if tcx.sess.panic_strategy().unwinds() { - context.add_command_line_option("-fexceptions"); - context.add_driver_option("-fexceptions"); - } - - let disabled_features: HashSet<_> = tcx - .sess - .opts - .cg - .target_feature - .split(',') - .filter(|feature| feature.starts_with('-')) - .map(|string| &string[1..]) - .collect(); - - if !disabled_features.contains("avx") && tcx.sess.target.arch == Arch::X86_64 { - // NOTE: we always enable AVX because the equivalent of llvm.x86.sse2.cmp.pd in GCC for - // SSE2 is multiple builtins, so we use the AVX __builtin_ia32_cmppd instead. - // FIXME(antoyo): use the proper builtins for llvm.x86.sse2.cmp.pd and similar. - context.add_command_line_option("-mavx"); - } - - for arg in &tcx.sess.opts.cg.llvm_args { - context.add_command_line_option(arg); - } - // NOTE: This is needed to compile the file src/intrinsic/archs.rs during a bootstrap of rustc. - context.add_command_line_option("-fno-var-tracking-assignments"); - // NOTE: an optimization (https://github.com/rust-lang/rustc_codegen_gcc/issues/53). - context.add_command_line_option("-fno-semantic-interposition"); - // NOTE: Rust relies on LLVM not doing TBAA (https://github.com/rust-lang/unsafe-code-guidelines/issues/292). - context.add_command_line_option("-fno-strict-aliasing"); - // NOTE: Rust relies on LLVM doing wrapping on overflow. - context.add_command_line_option("-fwrapv"); + let context = new_context(tcx.sess); // NOTE: We need to honor the `#![no_builtins]` attribute to prevent GCC from // replacing code patterns (like loops) with calls to builtins (like memset). @@ -148,64 +154,6 @@ pub fn compile_codegen_unit( context.add_command_line_option("-fno-tree-loop-distribute-patterns"); } - if let Some(model) = tcx.sess.code_model() { - use rustc_target::spec::CodeModel; - - context.add_command_line_option(match model { - CodeModel::Tiny => "-mcmodel=tiny", - CodeModel::Small => "-mcmodel=small", - CodeModel::Kernel => "-mcmodel=kernel", - CodeModel::Medium => "-mcmodel=medium", - CodeModel::Large => "-mcmodel=large", - }); - } - - add_pic_option(&context, tcx.sess.relocation_model()); - - let target_cpu = gcc_util::target_cpu(tcx.sess); - if target_cpu != "generic" { - context.add_command_line_option(format!("-march={}", target_cpu)); - } - - if tcx - .sess - .opts - .unstable_opts - .function_sections - .unwrap_or(tcx.sess.target.function_sections) - { - context.add_command_line_option("-ffunction-sections"); - context.add_command_line_option("-fdata-sections"); - } - - if env::var("CG_GCCJIT_DUMP_RTL").as_deref() == Ok("1") { - context.add_command_line_option("-fdump-rtl-vregs"); - } - if env::var("CG_GCCJIT_DUMP_RTL_ALL").as_deref() == Ok("1") { - context.add_command_line_option("-fdump-rtl-all"); - } - if env::var("CG_GCCJIT_DUMP_TREE_ALL").as_deref() == Ok("1") { - context.add_command_line_option("-fdump-tree-all-eh"); - } - if env::var("CG_GCCJIT_DUMP_IPA_ALL").as_deref() == Ok("1") { - context.add_command_line_option("-fdump-ipa-all-eh"); - } - if env::var("CG_GCCJIT_DUMP_CODE").as_deref() == Ok("1") { - context.set_dump_code_on_compile(true); - } - if env::var("CG_GCCJIT_DUMP_GIMPLE").as_deref() == Ok("1") { - context.set_dump_initial_gimple(true); - } - if env::var("CG_GCCJIT_DUMP_EVERYTHING").as_deref() == Ok("1") { - context.set_dump_everything(true); - } - if env::var("CG_GCCJIT_KEEP_INTERMEDIATES").as_deref() == Ok("1") { - context.set_keep_intermediates(true); - } - if env::var("CG_GCCJIT_VERBOSE").as_deref() == Ok("1") { - context.add_driver_option("-v"); - } - // NOTE: The codegen generates unreachable blocks. context.set_allow_unreachable_blocks(true); @@ -243,6 +191,11 @@ pub fn compile_codegen_unit( // ... and now that we have everything pre-defined, fill out those definitions. for &(mono_item, item_data) in &mono_items { mono_item.define::>(&mut cx, cgu_name.as_str(), item_data); + + // Now that this function's blocks all exist, fill in the cleanup + // regions reconstructed from MIR while lowering its `invoke`s. + #[cfg(feature = "master")] + cx.populate_cleanup_regions(); } // If this codegen unit contains the main function, also create the @@ -269,24 +222,3 @@ pub fn compile_codegen_unit( (module, cost) } - -pub fn add_pic_option<'gcc>(context: &Context<'gcc>, relocation_model: RelocModel) { - match relocation_model { - rustc_target::spec::RelocModel::Static => { - context.add_command_line_option("-fno-pie"); - context.add_driver_option("-fno-pie"); - } - rustc_target::spec::RelocModel::Pic => { - context.add_command_line_option("-fPIC"); - // NOTE: we use both add_command_line_option and add_driver_option because the usage in - // this module (compile_codegen_unit) requires add_command_line_option while the usage - // in the back::write module (codegen) requires add_driver_option. - context.add_driver_option("-fPIC"); - } - rustc_target::spec::RelocModel::Pie => { - context.add_command_line_option("-fPIE"); - context.add_driver_option("-fPIE"); - } - model => eprintln!("Unsupported relocation model: {:?}", model), - } -} diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index 0a88085960e89..a1eab8f448990 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -4,8 +4,8 @@ use std::convert::TryFrom; use std::ops::Deref; use gccjit::{ - BinaryOp, Block, ComparisonOp, Context, Function, LValue, Location, RValue, ToRValue, Type, - UnaryOp, + BinaryOp, Block, CType, ComparisonOp, Context, Function, LValue, Location, RValue, ToRValue, + Type, UnaryOp, }; use rustc_abi as abi; use rustc_abi::{Align, HasDataLayout, Size, TargetDataLayout, WrappingRange}; @@ -33,9 +33,11 @@ use rustc_target::callconv::FnAbi; use rustc_target::spec::{HasTargetSpec, HasX86AbiOpt, Target, X86Abi}; use crate::abi::FnAbiGccExt; +use crate::builder; use crate::common::{SignType, TypeReflection, type_is_pointer}; use crate::context::CodegenCx; -use crate::diagnostics; +#[cfg(feature = "master")] +use crate::context::PendingCleanup; use crate::intrinsic::llvm; use crate::type_of::LayoutGccExt; @@ -64,6 +66,33 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.value_counter.get() } + /// Tell GCC that `pointer` is `align`-aligned, so that the bulk memory builtins can widen their + /// accesses: a pointer cast to an aligned type would be dropped as a useless conversion. + fn assume_aligned(&mut self, pointer: RValue<'gcc>, align: Align) -> RValue<'gcc> { + if align.bytes() <= 1 { + return pointer; + } + let assume_aligned = self.context.get_builtin_function("__builtin_assume_aligned"); + let alignment = self.context.new_rvalue_from_long(self.type_size_t(), align.bytes() as i64); + let pointer_type = pointer.get_type(); + let const_void_ptr_type = self.context.new_type::<()>().make_const().make_pointer(); + let pointer = self.context.new_cast(self.location, pointer, const_void_ptr_type); + let aligned = self.context.new_call(self.location, assume_aligned, &[pointer, alignment]); + self.context.new_cast(self.location, aligned, pointer_type) + } + + /// GCC ignores a volatile qualifier on the pointers given to `memcpy`/`memmove`/`memset` and + /// happily deletes the call, so a barrier is what keeps the operation observable. The pointers + /// are fed to it because a clobber alone does not reach memory GCC believes never escapes. + fn volatile_barrier(&mut self, pointers: &[RValue<'gcc>]) { + let barrier = self.block.add_extended_asm(self.location, ""); + for pointer in pointers { + barrier.add_input_operand(None, "r", *pointer); + } + barrier.add_clobber("memory"); + barrier.set_volatile_flag(true); + } + fn atomic_extremum( &mut self, operation: ExtremumOperation, @@ -89,7 +118,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { ); let previous_var = func.new_local(self.location, previous_value.get_type(), "previous_value"); - let return_value = func.new_local(self.location, previous_value.get_type(), "return_value"); + let return_value = self.new_temp(func, self.location, previous_value.get_type()); self.llbb().add_assignment(self.location, previous_var, previous_value); self.llbb().add_assignment(self.location, return_value, previous_var.to_rvalue()); @@ -316,34 +345,71 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.block.get_function() } + /// Shared implementation of `call` and `tail_call`. For tail call it is important that this + /// returns a bare call, and not the result assigned to a local, or the result of `add_eval`. + #[allow(clippy::too_many_arguments)] + fn build_call( + &mut self, + typ: Type<'gcc>, + fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, + func: RValue<'gcc>, + return_slot: ReturnSlot< as BackendTypes>::Value>, + args: &[RValue<'gcc>], + funclet: Option<&Funclet>, + must_tail: bool, + ) -> RValue<'gcc> { + // FIXME: change this in the `rustc_codegen_gcc` repo after the sync, to use the `libgccjit` indirect return suppport. + let args = match return_slot { + ReturnSlot::Direct => Cow::Borrowed(args), + ReturnSlot::Indirect(sret_ptr) => { + let mut args = args.to_vec(); + // Prepend the indirect return pointer + args.insert(0, sret_ptr); + Cow::Owned(args) + } + }; + // FIXME(antoyo): remove when having a proper API. + let gcc_func = unsafe { std::mem::transmute::, Function<'gcc>>(func) }; + let call = if self.functions.borrow().values().any(|value| *value == gcc_func) { + // FIXME(antoyo): remove when the API supports a different type for functions. + let func: Function<'gcc> = self.cx.rvalue_as_function(func); + self.function_call(func, &args, funclet, must_tail) + } else { + // If it's a not function that was defined, it's a function pointer. + self.function_ptr_call(typ, fn_abi, func, &args, funclet, must_tail) + }; + if let Some(_fn_abi) = fn_abi { + // FIXME(bjorn3): Apply function attributes + } + call + } + pub fn function_call( &mut self, func: Function<'gcc>, args: &[RValue<'gcc>], _funclet: Option<&Funclet>, + must_tail: bool, ) -> RValue<'gcc> { let args = self.check_call("call", func, args); + let call = self.cx.context.new_call(self.location, func, &args); + if must_tail { + // Return the bare tail call, don't assign or `add_eval` it yet. + return call; + } + // gccjit requires to use the result of functions, even when it's not used. // That's why we assign the result to a local or call add_eval(). let return_type = func.get_return_type(); let void_type = self.context.new_type::<()>(); let current_func = self.block.get_function(); if return_type != void_type { - let result = current_func.new_local( - self.location, - return_type, - format!("returnValue{}", self.next_value_counter()), - ); - self.block.add_assignment( - self.location, - result, - self.cx.context.new_call(self.location, func, &args), - ); + let result = self.new_temp(current_func, self.location, return_type); + self.block.add_assignment(self.location, result, call); result.to_rvalue() } else { - self.block - .add_eval(self.location, self.cx.context.new_call(self.location, func, &args)); + self.block.add_eval(self.location, call); // Return dummy value when not having return value. self.context.new_rvalue_zero(self.isize_type) } @@ -356,6 +422,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { mut func_ptr: RValue<'gcc>, args: &[RValue<'gcc>], _funclet: Option<&Funclet>, + must_tail: bool, ) -> RValue<'gcc> { let func_ptr_type = { let func_ptr_type = func_ptr.get_type(); @@ -380,6 +447,12 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let args_adjusted = args.len() != previous_arg_count; let args = self.check_ptr_call("call", func_ptr, &args, &on_stack_param_indices); + if must_tail { + // Return the bare tail call, don't assign or `add_eval` it yet. + let call = self.cx.context.new_call_through_ptr(self.location, func_ptr, &args); + return call; + } + // gccjit requires to use the result of functions, even when it's not used. // That's why we assign the result to a local or call add_eval(). let return_type = gcc_func.get_return_type(); @@ -396,11 +469,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { args_adjusted, orig_args, ); - let result = current_func.new_local( - self.location, - return_value.get_type(), - format!("ptrReturnValue{}", self.next_value_counter()), - ); + let result = self.new_temp(current_func, self.location, return_value.get_type()); self.block.add_assignment(self.location, result, return_value); result.to_rvalue() } else { @@ -422,8 +491,16 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.location, self.cx.context.new_call_through_ptr(self.location, func_ptr, &args), ); - // Return dummy value when not having return value. - self.context.new_rvalue_zero(self.isize_type) + // Return dummy value when not having return value, unless the intrinsic adapter + // needs to synthesize a non-void LLVM-level result from out-parameters. + llvm::adjust_intrinsic_return_value( + self, + self.context.new_rvalue_zero(self.isize_type), + &func_name, + &args, + args_adjusted, + orig_args, + ) } } @@ -438,11 +515,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let return_type = self.context.new_type::(); let current_func = self.block.get_function(); // FIXME(antoyo): return the new_call() directly? Since the overflow function has no side-effects. - let result = current_func.new_local( - self.location, - return_type, - format!("overflowReturnValue{}", self.next_value_counter()), - ); + let result = self.new_temp(current_func, self.location, return_type); self.block.add_assignment( self.location, result, @@ -574,6 +647,18 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { default_block: Block<'gcc>, cases: impl ExactSizeIterator)>, ) { + // A switch with no cases is equivalent to an unconditional jump to the + // default block. Such a `SwitchInt` (one with only an `otherwise` target) + // is normally simplified into a `goto`, but `-Z mir-preserve-ub` keeps it, + // so it can reach here with e.g. the `bool` discriminant produced by a + // range-pattern comparison. `gcc_jit_block_end_with_switch` rejects a + // discriminant that is not of integer type, so emit a plain jump instead + // of a (pointless) switch. + if cases.len() == 0 { + self.block.end_with_jump(self.location, default_block); + return; + } + let mut gcc_cases = vec![]; let typ = self.val_ty(value); // FIXME(FractalFir): This is a workaround for a libgccjit limitation. @@ -614,7 +699,9 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { _funclet: Option<&Funclet>, instance: Option>, ) -> RValue<'gcc> { - let try_block = self.current_func().new_block("try"); + let current_func = self.current_func(); + let try_region = current_func.new_region(self.location); + let try_block = try_region.new_block("try"); let current_block = self.block; self.block = try_block; @@ -622,17 +709,25 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { let call = self.call(typ, fn_attrs, fn_abi, func, return_slot, args, None, instance); self.block = current_block; - let return_value = - self.current_func().new_local(self.location, call.get_type(), "invokeResult"); + let return_value = self.new_temp(current_func, self.location, call.get_type()); try_block.add_assignment(self.location, return_value, call); try_block.end_with_jump(self.location, then); - if self.cleanup_blocks.borrow().contains(&catch) { - self.block.add_try_finally(self.location, try_block, catch); + if self.cx.landing_pads.borrow().contains(&catch) { + let cleanup_region = current_func.new_region(self.location); + self.block.add_cleanup(self.location, try_region, cleanup_region); + self.cx + .pending_cleanups + .borrow_mut() + .push(PendingCleanup { region: cleanup_region, landing_pad: catch }); } else { - self.block.add_try_catch(self.location, try_block, catch); + let catch_region = current_func.new_region(self.location); + for clone in gccjit::clone_blocks(&[catch]) { + catch_region.add_block(clone); + } + self.block.add_try_catch(self.location, try_region, catch_region); } self.block.end_with_jump(self.location, then); @@ -671,8 +766,9 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { if return_type == void_type { self.block.end_with_void_return(self.location) } else { - let return_value = - self.current_func().new_local(self.location, return_type, "unreachableReturn"); + let trap = self.context.get_builtin_function("__builtin_trap"); + self.block.add_eval(self.location, self.context.new_call(self.location, trap, &[])); + let return_value = self.new_temp(self.current_func(), self.location, return_type); self.block.end_with_return(self.location, return_value) } } @@ -991,11 +1087,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { // the current basic block. Otherwise, it could be used in another basic block, causing a // dereference after a drop, for instance. let deref = ptr.dereference(self.location).to_rvalue(); - let loaded_value = function.new_local( - self.location, - aligned_type, - format!("loadedValue{}", self.next_value_counter()), - ); + let loaded_value = self.new_temp(function, self.location, aligned_type); block.add_assignment(self.location, loaded_value, deref); loaded_value.to_rvalue() } @@ -1114,7 +1206,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { let next_bb = self.append_sibling_block("repeat_loop_next"); let ptr_type = start.get_type(); - let current = self.llbb().get_function().new_local(self.location, ptr_type, "loop_var"); + let current = self.new_temp(self.llbb().get_function(), self.location, ptr_type); let current_val = current.to_rvalue(); self.assign(current, start); @@ -1405,47 +1497,53 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { fn memcpy( &mut self, dst: RValue<'gcc>, - _dst_align: Align, + dst_align: Align, src: RValue<'gcc>, - _src_align: Align, + src_align: Align, size: RValue<'gcc>, flags: MemFlags, _tt: Option, // Autodiff TypeTrees are LLVM-only, ignored in GCC backend ) { assert!(!flags.contains(MemFlags::NONTEMPORAL), "non-temporal memcpy not supported"); let size = self.intcast(size, self.type_size_t(), false); - let _is_volatile = flags.contains(MemFlags::VOLATILE); let dst = self.pointercast(dst, self.type_i8p()); + let dst = self.assume_aligned(dst, dst_align); let src = self.pointercast(src, self.type_ptr_to(self.type_void())); + let src = self.assume_aligned(src, src_align); let memcpy = self.context.get_builtin_function("memcpy"); - // FIXME(antoyo): handle aligns and is_volatile. self.block.add_eval( self.location, self.context.new_call(self.location, memcpy, &[dst, src, size]), ); + if flags.contains(MemFlags::VOLATILE) { + self.volatile_barrier(&[dst, src]); + } } fn memmove( &mut self, dst: RValue<'gcc>, - _dst_align: Align, + dst_align: Align, src: RValue<'gcc>, - _src_align: Align, + src_align: Align, size: RValue<'gcc>, flags: MemFlags, ) { assert!(!flags.contains(MemFlags::NONTEMPORAL), "non-temporal memmove not supported"); let size = self.intcast(size, self.type_size_t(), false); - let _is_volatile = flags.contains(MemFlags::VOLATILE); let dst = self.pointercast(dst, self.type_i8p()); + let dst = self.assume_aligned(dst, dst_align); let src = self.pointercast(src, self.type_ptr_to(self.type_void())); + let src = self.assume_aligned(src, src_align); let memmove = self.context.get_builtin_function("memmove"); - // FIXME(antoyo): handle is_volatile. self.block.add_eval( self.location, self.context.new_call(self.location, memmove, &[dst, src, size]), ); + if flags.contains(MemFlags::VOLATILE) { + self.volatile_barrier(&[dst, src]); + } } fn memset( @@ -1453,20 +1551,22 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { ptr: RValue<'gcc>, fill_byte: RValue<'gcc>, size: RValue<'gcc>, - _align: Align, + align: Align, flags: MemFlags, ) { assert!(!flags.contains(MemFlags::NONTEMPORAL), "non-temporal memset not supported"); - let _is_volatile = flags.contains(MemFlags::VOLATILE); let ptr = self.pointercast(ptr, self.type_i8p()); + let ptr = self.assume_aligned(ptr, align); let memset = self.context.get_builtin_function("memset"); - // FIXME(antoyo): handle align and is_volatile. let fill_byte = self.context.new_cast(self.location, fill_byte, self.i32_type); let size = self.intcast(size, self.type_size_t(), false); self.block.add_eval( self.location, self.context.new_call(self.location, memset, &[ptr, fill_byte, size]), ); + if flags.contains(MemFlags::VOLATILE) { + self.volatile_barrier(&[ptr]); + } } fn vscale(&mut self, _: Self::Type) -> Self::Value { @@ -1480,7 +1580,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { mut else_val: RValue<'gcc>, ) -> RValue<'gcc> { let func = self.current_func(); - let variable = func.new_local(self.location, then_val.get_type(), "selectVar"); + let variable = self.new_temp(func, self.location, then_val.get_type()); let then_block = func.new_block("then"); let else_block = func.new_block("else"); let after_block = func.new_block("after"); @@ -1502,8 +1602,10 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { variable.to_rvalue() } - fn va_arg(&mut self, _list: RValue<'gcc>, _ty: Type<'gcc>) -> RValue<'gcc> { - unimplemented!(); + fn va_arg(&mut self, list: RValue<'gcc>, ty: Type<'gcc>) -> RValue<'gcc> { + let va_list_type = self.context.new_c_type(CType::VaList); + let list = self.context.new_cast(self.location, list, va_list_type.make_pointer()); + self.context.new_va_arg(self.location, list, ty) } #[cfg(feature = "master")] @@ -1606,45 +1708,32 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { // NOTE: insert the current block in a variable so that a later call to invoke knows to // generate a try/finally instead of a try/catch for this block. - self.cleanup_blocks.borrow_mut().insert(self.block); - - let eh_pointer_builtin = - self.cx.context.get_target_builtin_function("__builtin_eh_pointer"); - let zero = self.cx.context.new_rvalue_zero(self.int_type); - let ptr = self.cx.context.new_call(self.location, eh_pointer_builtin, &[zero]); - - let value1_type = self.u8_type.make_pointer(); - let ptr = self.cx.context.new_cast(self.location, ptr, value1_type); - let value1 = ptr; - let value2 = zero; // FIXME(antoyo): set the proper value here (the type of exception?). + self.cx.landing_pads.borrow_mut().insert(self.block); + // A cleanup resumes by falling through: it never inspects the exception + // object. + let value1 = self.context.new_null(self.u8_type.make_pointer()); + let value2 = self.context.new_rvalue_zero(self.i32_type); (value1, value2) } #[cfg(not(feature = "master"))] fn cleanup_landing_pad(&mut self, _pers_fn: Function<'gcc>) -> (RValue<'gcc>, RValue<'gcc>) { let value1 = self - .current_func() - .new_local(self.location, self.u8_type.make_pointer(), "landing_pad0") + .new_temp(self.current_func(), self.location, self.u8_type.make_pointer()) .to_rvalue(); - let value2 = - self.current_func().new_local(self.location, self.i32_type, "landing_pad1").to_rvalue(); + let value2 = self.new_temp(self.current_func(), self.location, self.i32_type).to_rvalue(); (value1, value2) } fn filter_landing_pad(&mut self, pers_fn: Function<'gcc>) { - // FIXME(antoyo): generate the correct landing pad - self.cleanup_landing_pad(pers_fn); + self.set_personality_fn(pers_fn); } #[cfg(feature = "master")] - fn resume(&mut self, exn0: RValue<'gcc>, _exn1: RValue<'gcc>) { - let exn_type = exn0.get_type(); - let exn = self.context.new_cast(self.location, exn0, exn_type); - let unwind_resume = self.context.get_target_builtin_function("__builtin_unwind_resume"); - self.llbb() - .add_eval(self.location, self.context.new_call(self.location, unwind_resume, &[exn])); - self.unreachable(); + fn resume(&mut self, _exn0: RValue<'gcc>, _exn1: RValue<'gcc>) { + // End the cleanup by falling off the end of its region body. + self.block.end_with_fallthrough(self.location); } #[cfg(not(feature = "master"))] @@ -1696,7 +1785,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { // NOTE: since success contains the call to the intrinsic, it must be added to the basic block before // expected so that we store expected after the call. - let success_var = self.current_func().new_local(self.location, self.bool_type, "success"); + let success_var = self.new_temp(self.current_func(), self.location, self.bool_type); self.llbb().add_assignment(self.location, success_var, success); (expected.to_rvalue(), success_var.to_rvalue()) @@ -1786,45 +1875,35 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { funclet: Option<&Funclet>, _instance: Option>, ) -> RValue<'gcc> { - // FIXME: change this in the `rustc_codegen_gcc` repo after the sync, to use the `libgccjit` indirect return suppport. - let args = match return_slot { - ReturnSlot::Direct => args.to_vec(), - ReturnSlot::Indirect(sret_ptr) => { - let mut args = args.to_vec(); - // Prepend the indirect return pointer - args.insert(0, sret_ptr); - args - } - }; - // FIXME(antoyo): remove when having a proper API. - let gcc_func = unsafe { std::mem::transmute::, Function<'gcc>>(func) }; - let call = if self.functions.borrow().values().any(|value| *value == gcc_func) { - // FIXME(antoyo): remove when the API supports a different type for functions. - let func: Function<'gcc> = self.cx.rvalue_as_function(func); - self.function_call(func, &args, funclet) - } else { - // If it's a not function that was defined, it's a function pointer. - self.function_ptr_call(typ, fn_abi, func, &args, funclet) - }; - if let Some(_fn_abi) = fn_abi { - // FIXME(bjorn3): Apply function attributes - } - call + self.build_call(typ, fn_abi, func, return_slot, args, funclet, false) } fn tail_call( &mut self, - _llty: Self::Type, + llty: Self::Type, _fn_attrs: Option<&CodegenFnAttrs>, - _fn_abi: &FnAbi<'tcx, Ty<'tcx>>, - _llfn: Self::Value, - _return_slot: ReturnSlot, - _args: &[Self::Value], - _funclet: Option<&Self::Funclet>, + fn_abi: &FnAbi<'tcx, Ty<'tcx>>, + llfn: Self::Value, + return_slot: ReturnSlot, + args: &[Self::Value], + funclet: Option<&Self::Funclet>, _instance: Option>, ) { - // FIXME: implement support for explicit tail calls like rustc_codegen_llvm. - self.tcx.dcx().emit_fatal(diagnostics::ExplicitTailCallsUnsupported); + // `emit_call` returns a bare call for here, it has not been assigned or passed to add_eval. + let call = self.build_call(llty, Some(fn_abi), llfn, return_slot, args, funclet, true); + call.set_require_tail_call(true); + + let return_type = self.current_func().get_return_type(); + let void_type = self.context.new_type::<()>(); + + if return_type == void_type { + // For a void return the call is emitted as its own statement, immediately + // followed by a void return, so the tail call sits in tail position. + self.llbb().add_eval(self.location, call); + self.ret_void(); + } else { + self.ret(call) + } } fn zext(&mut self, value: RValue<'gcc>, dest_typ: Type<'gcc>) -> RValue<'gcc> { @@ -2409,11 +2488,31 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.bitcast_if_needed(res, result_type) } + /// Create a temporary variable. + /// + /// GCC will use more stack space with a local variable than with a temporary variable in debug mode, + /// so in order to avoid having the stack probe test fail in CI, we avoid creating local variables for temporaries. + pub fn new_temp( + &self, + function: Function<'gcc>, + location: Option>, + typ: Type<'gcc>, + ) -> LValue<'gcc> { + #[cfg(feature = "master")] + { + function.new_temp(location, typ) + } + #[cfg(not(feature = "master"))] + { + function.new_local(location, typ, format!("temp{}", self.next_value_counter())) + } + } + // GCC doesn't like deeply nested expressions. // By assigning intermediate expressions to a variable, this allow us to avoid deeply nested // expressions and GCC will use much less RAM. fn assign_to_var(&self, value: RValue<'gcc>) -> RValue<'gcc> { - let var = self.current_func().new_local(self.location, value.get_type(), "opResult"); + let var = self.new_temp(self.current_func(), self.location, value.get_type()); self.llbb().add_assignment(self.location, var, value); var.to_rvalue() } diff --git a/compiler/rustc_codegen_gcc/src/callee.rs b/compiler/rustc_codegen_gcc/src/callee.rs index 00f095ed54371..d3f412180da55 100644 --- a/compiler/rustc_codegen_gcc/src/callee.rs +++ b/compiler/rustc_codegen_gcc/src/callee.rs @@ -70,7 +70,7 @@ pub fn get_fn<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, instance: Instance<'tcx>) cx.linkage.set(FunctionType::Extern); let func = cx.declare_fn(sym, fn_abi); - attributes::from_fn_attrs(cx, func, instance); + attributes::from_fn_attrs(cx, func, instance, Some(fn_abi)); #[cfg(feature = "master")] { diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 6bd186f1121fc..21d92c6cc2936 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -12,6 +12,7 @@ use rustc_session::PointerAuthSchema; use crate::consts::const_alloc_to_gcc; use crate::context::{CodegenCx, new_array_type}; +use crate::type_::struct_attributes; use crate::type_of::LayoutGccExt; impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { @@ -125,78 +126,86 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { } } +/// The element type and element count of the array used to represent a run of `len` constant bytes. +/// +/// Larger integers are used where possible: this reduces the number of rvalues, which is a +/// significant memory saving on constant-heavy crates. +fn byte_run_shape<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> (Type<'gcc>, u64) { + match len % 8 { + 0 => (cx.context.new_type::(), len as u64 / 8), + 4 => (cx.context.new_type::(), len as u64 / 4), + _ => (cx.context.new_type::(), len as u64), + } +} + +/// The type [`bytes_in_context`] gives a run of `len` constant bytes. +/// +/// Exposed separately so that the type of a constant allocation can be computed before any of its +/// rvalues exist; see [`crate::consts::const_alloc_type`]. +/// +/// The result is cached because `gcc_jit_context_new_array_type` mints a fresh type every call. +/// Two equal-but-distinct array types would key [`CodegenCx::type_struct`] differently and so +/// produce two distinct anonymous structs, and libgccjit compares struct types by identity. +pub fn bytes_type_in_context<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> Type<'gcc> { + let (element_type, count) = byte_run_shape(cx, len); + if let Some(&typ) = cx.byte_array_types.borrow().get(&(element_type, count)) { + return typ; + } + let typ = new_array_type(cx.context, None, element_type, count); + cx.byte_array_types.borrow_mut().insert((element_type, count), typ); + typ +} + +// FIXME(FractalFir): Consider using `global_set_initializer` instead. Before this is done, we need to confirm that +// `global_set_initializer` is more memory efficient than the current solution. +// `global_set_initializer` calls `global_set_initializer_rvalue` under the hood - does it generate an array of rvalues, +// or is it using a more efficient representation? pub fn bytes_in_context<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, bytes: &[u8]) -> RValue<'gcc> { - // Instead of always using an array of bytes, use an array of larger integers of target endianness - // if possible. This reduces the amount of `rvalues` we use, which reduces memory usage significantly. - // - // FIXME(FractalFir): Consider using `global_set_initializer` instead. Before this is done, we need to confirm that - // `global_set_initializer` is more memory efficient than the current solution. - // `global_set_initializer` calls `global_set_initializer_rvalue` under the hood - does it generate an array of rvalues, - // or is it using a more efficient representation? - match bytes.len() % 8 { + let typ = bytes_type_in_context(cx, bytes.len()); + let (element_type, _) = byte_run_shape(cx, bytes.len()); + let context = &cx.context; + // Since we are representing arbitrary byte runs as integers, we need to follow the target + // endianness. + let endian = cx.sess().target.options.endian; + let elements: Vec<_> = match bytes.len() % 8 { 0 => { - debug_assert_eq!( - bytes.len() % 8, - 0, - "bytes length is not a multiple of 8, so bytes.as_chunks will have a remainder" - ); - let context = &cx.context; - let byte_type = context.new_type::(); - let typ = new_array_type(context, None, byte_type, bytes.len() as u64 / 8); - let elements: Vec<_> = bytes - .as_chunks::<8>() - .0 + let (arrays, remainder) = bytes.as_chunks::<8>(); + debug_assert!(remainder.is_empty()); + arrays .iter() .map(|&arr| { context.new_rvalue_from_long( - byte_type, - // Since we are representing arbitrary byte runs as integers, we need to follow the target - // endianness. - match cx.sess().target.options.endian { + element_type, + match endian { rustc_abi::Endian::Little => u64::from_le_bytes(arr) as i64, rustc_abi::Endian::Big => u64::from_be_bytes(arr) as i64, }, ) }) - .collect(); - context.new_array_constructor(None, typ, &elements) + .collect() } 4 => { - debug_assert_eq!( - bytes.len() % 4, - 0, - "bytes length is not a multiple of 4, so bytes.as_chunks will have a remainder" - ); - let context = &cx.context; - let byte_type = context.new_type::(); - let typ = new_array_type(context, None, byte_type, bytes.len() as u64 / 4); - let elements: Vec<_> = bytes - .as_chunks::<4>() - .0 + let (arrays, remainder) = bytes.as_chunks::<4>(); + debug_assert!(remainder.is_empty()); + arrays .iter() .map(|&arr| { context.new_rvalue_from_int( - byte_type, - match cx.sess().target.options.endian { + element_type, + match endian { rustc_abi::Endian::Little => u32::from_le_bytes(arr) as i32, rustc_abi::Endian::Big => u32::from_be_bytes(arr) as i32, }, ) }) - .collect(); - context.new_array_constructor(None, typ, &elements) - } - _ => { - let context = cx.context; - let byte_type = context.new_type::(); - let typ = new_array_type(context, None, byte_type, bytes.len() as u64); - let elements: Vec<_> = bytes - .iter() - .map(|&byte| context.new_rvalue_from_int(byte_type, byte as i32)) - .collect(); - context.new_array_constructor(None, typ, &elements) + .collect() } - } + _ => bytes + .iter() + .map(|&byte| context.new_rvalue_from_int(element_type, byte as i32)) + .collect(), + }; + context.new_array_constructor(None, typ, &elements) } pub fn type_is_pointer(typ: Type<'_>) -> bool { @@ -298,7 +307,7 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { fn const_struct(&self, values: &[RValue<'gcc>], packed: bool) -> RValue<'gcc> { let fields: Vec<_> = values.iter().map(|value| value.get_type()).collect(); // FIXME(antoyo): cache the type? It's anonymous, so probably not. - let typ = self.type_struct(&fields, packed); + let typ = self.type_struct(&fields, &struct_attributes(packed, None)); let struct_type = typ.is_struct().expect("struct type"); self.context.new_struct_constructor(None, struct_type.as_type(), None, values) } diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index 6c3b404547cfc..8576dfe079163 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -1,3 +1,5 @@ +use std::ops::Range; + #[cfg(feature = "master")] use gccjit::{FnAttribute, VarAttribute, Visibility}; use gccjit::{Function, GlobalKind, LValue, RValue, ToRValue, Type}; @@ -11,15 +13,18 @@ use rustc_hir::def_id::LOCAL_CRATE; use rustc_log::tracing::trace; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc_middle::mir::interpret::{ - self, ConstAllocation, ErrorHandled, Scalar as InterpScalar, read_target_uint, + self, Allocation, ConstAllocation, CtfeProvenance, ErrorHandled, Scalar as InterpScalar, + read_target_uint, }; +use rustc_middle::mono::MonoItem; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, Instance}; use rustc_span::def_id::DefId; use rustc_span::{bug, span_bug}; -use crate::base; +use crate::common::bytes_type_in_context; use crate::context::CodegenCx; +use crate::type_::struct_attributes; use crate::type_of::LayoutGccExt; pub(crate) fn const_alloc_to_gcc<'gcc, 'tcx>( @@ -99,15 +104,21 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { let is_thread_local = attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL); let global = self.get_static_inner(def_id, val_llty); - #[cfg(feature = "master")] - if global.to_rvalue().get_type() != val_llty { - global.to_rvalue().set_type(val_llty); - } + debug_assert_eq!( + global.to_rvalue().get_type(), + val_llty, + "`predefine_static` declared this global with a type its initializer does not have" + ); // NOTE: Alignment from attributes has already been applied to the allocation. set_global_alignment(self, global, alloc.align); - global.global_set_initializer_rvalue(value); + // A common symbol is storage the linker allocates and zero-fills, so giving the definition + // an initializer — even an all-zero one — takes it back out of `.comm`. A non-zero one is + // kept: the symbol is then an ordinary definition, which is what GCC does with it too. + if attrs.linkage != Some(Linkage::Common) || !is_zero_initializer(alloc) { + global.global_set_initializer_rvalue(value); + } // As an optimization, all shared statics which do not have interior // mutability are placed into read-only memory. @@ -160,29 +171,52 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { } // Wasm statics with custom link sections get special treatment as they - // go into custom sections of the wasm executable. - if self.tcx.sess.target.is_like_wasm { + // go into custom sections of the wasm executable. The exception to this + // is the `.init_array` section which are treated specially by the wasm linker. + if self.tcx.sess.target.is_like_wasm + && attrs + .link_section + .map(|link_section| !link_section.as_str().starts_with(".init_array")) + .unwrap_or(true) + { if let Some(_section) = attrs.link_section { unimplemented!(); } - } else { - // FIXME(antoyo): set link section. + } else if let Some(_section) = attrs.link_section { + #[cfg(feature = "master")] + global.add_attribute(VarAttribute::Section(_section.as_str())); } - if attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) - || attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) - { - self.add_used_global(global.to_rvalue()); + if attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) { + // To copy the conditions from the LLVM backend... + assert!(!attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER)); + self.add_used_global(global); + } + if attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) { + // To copy the conditions from the LLVM backend... + assert!(!attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER)); + self.add_retained_global(global); } } } impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { - /// Add a global value to a list to be stored in the `llvm.used` variable, an array of i8*. - pub fn add_used_global(&mut self, _global: RValue<'gcc>) { - // FIXME(antoyo) + /// Need to have the `SHF_GNU_RETAIN` flag, so needs to use the `retain` attribute instead of + /// `used`. This is used by `#[used(linker)]`. + pub fn add_retained_global(&mut self, global: LValue<'gcc>) { + // We need to add the `used` C attribute in any case. + self.add_used_global(global); + #[cfg(feature = "master")] + global.add_attribute(VarAttribute::Retain); + } + + /// This is used by `#[used(compiler)]` and `#[used]`. + pub fn add_used_global(&mut self, _global: LValue<'gcc>) { + #[cfg(feature = "master")] + _global.add_attribute(VarAttribute::Used); } + // No need to have the `SHF_GNU_RETAIN` flag, so `used` attribute is ok. #[cfg_attr(not(feature = "master"), expect(unused_variables))] pub fn add_used_function(&self, function: Function<'gcc>) { #[cfg(feature = "master")] @@ -237,15 +271,14 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { return global; } - // FIXME: Once we stop removing globals in `codegen_static`, we can uncomment this code. - // let defined_in_current_codegen_unit = - // self.codegen_unit.items().contains_key(&MonoItem::Static(def_id)); - // assert!( - // !defined_in_current_codegen_unit, - // "consts::get_static() should always hit the cache for \ - // statics defined in the same CGU, but did not for `{:?}`", - // def_id - // ); + let defined_in_current_codegen_unit = + self.codegen_unit.items().contains_key(&MonoItem::Static(def_id)); + assert!( + !defined_in_current_codegen_unit, + "consts::get_static() should always hit the cache for \ + statics defined in the same CGU, but did not for `{:?}`", + def_id + ); let sym = self.tcx.symbol_name(instance).name; let fn_attrs = self.tcx.codegen_fn_attrs(def_id); @@ -309,75 +342,133 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { global } } -/// Converts a given const alloc to a gcc Rvalue, without any caching or deduplication. -/// YOU SHOULD NOT call this function directly - that may break the semantics of Rust. -/// Use `const_data_from_alloc` instead. -pub(crate) fn const_alloc_to_gcc_uncached<'gcc>( - cx: &CodegenCx<'gcc, '_>, - alloc: ConstAllocation<'_>, -) -> RValue<'gcc> { - let alloc = alloc.inner(); - let mut llvals = Vec::with_capacity(alloc.provenance().ptrs().len() + 1); - let dl = cx.data_layout(); - let pointer_size = dl.pointer_size().bytes() as usize; +/// One field of the packed struct that a constant allocation is lowered to. +enum AllocField { + /// A run of bytes carrying no provenance. + Bytes { range: Range }, + /// A pointer with provenance, occupying one target pointer worth of bytes. + Pointer { offset: usize, prov: CtfeProvenance }, +} + +/// The field-by-field shape of `alloc`. +/// +/// [`const_alloc_to_gcc_uncached`] and [`const_alloc_type`] have to agree exactly on this, down to +/// the empty trailing run an allocation ending on a pointer produces, so both derive the shape here +/// instead of each walking the allocation on its own. +fn alloc_fields(cx: &CodegenCx<'_, '_>, alloc: &interpret::Allocation) -> Vec { + let pointer_size = cx.data_layout().pointer_size().bytes() as usize; + let mut fields = Vec::with_capacity(alloc.provenance().ptrs().len() + 1); let mut next_offset = 0; for &(offset, prov) in alloc.provenance().ptrs().iter() { - let alloc_id = prov.alloc_id(); let offset = offset.bytes(); assert_eq!(offset as usize as u64, offset); let offset = offset as usize; if offset > next_offset { - // This `inspect` is okay since we have checked that it is not within a pointer with provenance, it - // is within the bounds of the allocation, and it doesn't affect interpreter execution - // (we inspect the result after interpreter execution). Any undef byte is replaced with - // some arbitrary byte value. - // - // FIXME: relay undef bytes to codegen as undef const bytes - let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(next_offset..offset); - llvals.push(cx.const_bytes(bytes)); + fields.push(AllocField::Bytes { range: next_offset..offset }); } - let ptr_offset = read_target_uint( - dl.endian, - // This `inspect` is okay since it is within the bounds of the allocation, it doesn't - // affect interpreter execution (we inspect the result after interpreter execution), - // and we properly interpret the provenance as a relocation pointer offset. - alloc.inspect_with_uninit_and_ptr_outside_interpreter(offset..(offset + pointer_size)), - ) - .expect("const_alloc_to_gcc_uncached: could not read relocation pointer") - as u64; - - let address_space = cx.tcx.global_alloc(alloc_id).address_space(cx); - - llvals.push(cx.scalar_to_backend( - InterpScalar::from_pointer( - interpret::Pointer::new(prov, Size::from_bytes(ptr_offset)), - &cx.tcx, - ), - abi::Scalar::Initialized { - value: Primitive::Pointer(address_space), - valid_range: WrappingRange::full(dl.pointer_size()), - }, - cx.type_i8p_ext(address_space), - )); + fields.push(AllocField::Pointer { offset, prov }); next_offset = offset + pointer_size; } if alloc.len() >= next_offset { - let range = next_offset..alloc.len(); - // This `inspect` is okay since we have check that it is after all provenance, it is - // within the bounds of the allocation, and it doesn't affect interpreter execution (we - // inspect the result after interpreter execution). Any undef byte is replaced with some - // arbitrary byte value. - // - // FIXME: relay undef bytes to codegen as undef const bytes - let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(range); - llvals.push(cx.const_bytes(bytes)); + fields.push(AllocField::Bytes { range: next_offset..alloc.len() }); } + fields +} + +/// The type [`const_alloc_to_gcc`] gives `alloc`, computed without building any rvalue. +/// +/// This lets `predefine_static` declare a static's global with the type its initializer will have, +/// so that the two never disagree. It must not reach for the rvalue of anything it points at: +/// during the predefine pass the pointee may not be declared yet, and `alloc_to_backend` would +/// declare it with the wrong type behind our back. +pub(crate) fn const_alloc_type<'gcc>( + cx: &CodegenCx<'gcc, '_>, + alloc: ConstAllocation<'_>, +) -> Type<'gcc> { + let fields: Vec<_> = alloc_fields(cx, alloc.inner()) + .into_iter() + .map(|field| match field { + AllocField::Bytes { range } => bytes_type_in_context(cx, range.len()), + AllocField::Pointer { prov, .. } => { + let address_space = cx.tcx.global_alloc(prov.alloc_id()).address_space(cx); + cx.type_i8p_ext(address_space) + } + }) + .collect(); + cx.type_struct(&fields, &struct_attributes(true, None)) +} + +/// Converts a given const alloc to a gcc Rvalue, without any caching or deduplication. +/// YOU SHOULD NOT call this function directly - that may break the semantics of Rust. +/// Use `const_data_from_alloc` instead. +pub(crate) fn const_alloc_to_gcc_uncached<'gcc>( + cx: &CodegenCx<'gcc, '_>, + alloc: ConstAllocation<'_>, +) -> RValue<'gcc> { + let alloc = alloc.inner(); + let dl = cx.data_layout(); + let pointer_size = dl.pointer_size(); + + let llvals: Vec<_> = alloc_fields(cx, alloc) + .into_iter() + .map(|field| match field { + AllocField::Bytes { range } => { + // This `inspect` is okay since we have checked that it is not within a pointer with + // provenance, it is within the bounds of the allocation, and it doesn't affect + // interpreter execution (we inspect the result after interpreter execution). Any + // undef byte is replaced with some arbitrary byte value. + // + // FIXME: relay undef bytes to codegen as undef const bytes + cx.const_bytes(alloc.inspect_with_uninit_and_ptr_outside_interpreter(range)) + } + AllocField::Pointer { offset, prov } => { + let ptr_offset = read_target_uint( + dl.endian, + // This `inspect` is okay since it is within the bounds of the allocation, it + // doesn't affect interpreter execution (we inspect the result after interpreter + // execution), and we properly interpret the provenance as a relocation pointer + // offset. + alloc.inspect_with_uninit_and_ptr_outside_interpreter( + offset..(offset + pointer_size.bytes() as usize), + ), + ) + .expect("const_alloc_to_gcc_uncached: could not read relocation pointer") + as u64; + + let address_space = cx.tcx.global_alloc(prov.alloc_id()).address_space(cx); + + cx.scalar_to_backend( + InterpScalar::from_pointer( + interpret::Pointer::new(prov, Size::from_bytes(ptr_offset)), + &cx.tcx, + ), + abi::Scalar::Initialized { + value: Primitive::Pointer(address_space), + valid_range: WrappingRange::full(pointer_size), + }, + cx.type_i8p_ext(address_space), + ) + } + }) + .collect(); + // FIXME(bjorn3) avoid wrapping in a struct when there is only a single element. cx.const_struct(&llvals, true) } +/// Whether this allocation is all zeroes, and so needs no initializer to be spelled out. +fn is_zero_initializer(alloc: &Allocation) -> bool { + alloc.provenance().ptrs().is_empty() + // This `inspect` is okay: it is within the bounds of the allocation, there is no provenance + // to misread, and it does not affect interpreter execution. + && alloc + .inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.size().bytes_usize()) + .iter() + .all(|&byte| byte == 0) +} + fn codegen_static_initializer<'gcc, 'tcx>( cx: &CodegenCx<'gcc, 'tcx>, def_id: DefId, @@ -394,10 +485,10 @@ fn check_and_apply_linkage<'gcc, 'tcx>( ) -> LValue<'gcc> { let is_tls = attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL); if let Some(linkage) = attrs.import_linkage { - // Declare a symbol `foo` with the desired linkage. - let global1 = - cx.declare_global_with_linkage(sym, cx.type_i8(), base::global_linkage_to_gcc(linkage)); + // Whatever the flavour, an import is an undefined reference to a symbol defined elsewhere. + let global1 = cx.declare_global_with_linkage(sym, cx.type_i8(), GlobalKind::Imported); + // Only `extern_weak` lets the symbol stay unresolved, in which case it reads as null. if linkage == Linkage::ExternalWeak { #[cfg(feature = "master")] global1.add_attribute(VarAttribute::Weak); @@ -411,8 +502,13 @@ fn check_and_apply_linkage<'gcc, 'tcx>( // zero. let real_name = format!("_rust_extern_with_linkage_{:016x}_{sym}", cx.tcx.stable_crate_id(LOCAL_CRATE)); - let global2 = cx.define_global(&real_name, gcc_type, is_tls, attrs.link_section); - // FIXME(antoyo): set linkage. + let global2 = cx.define_global( + &real_name, + gcc_type, + GlobalKind::Internal, + is_tls, + attrs.link_section, + ); let value = cx.const_ptrcast(global1.get_address(None), gcc_type); global2.global_set_initializer_rvalue(value); global2 diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs index 38e0e5f329f76..7a1931b4967ce 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -1,6 +1,8 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; +#[cfg(feature = "master")] +use gccjit::Region; use gccjit::{Block, CType, Context, Function, FunctionType, LValue, Location, RValue, Type}; use rustc_abi::{Align, HasDataLayout, PointeeInfo, Size, TargetDataLayout, VariantIdx}; use rustc_codegen_ssa::base::wants_msvc_seh; @@ -25,6 +27,13 @@ use rustc_target::spec::{HasTargetSpec, HasX86AbiOpt, Target, TlsModel, X86Abi}; use crate::abi::conv_to_fn_attribute; use crate::callee::get_fn; use crate::common::SignType; +use crate::type_::StructTypeKey; + +#[cfg(feature = "master")] +pub struct PendingCleanup<'gcc> { + pub region: Region<'gcc>, + pub landing_pad: Block<'gcc>, +} #[cfg_attr(not(feature = "master"), expect(dead_code))] pub struct CodegenCx<'gcc, 'tcx> { @@ -84,7 +93,14 @@ pub struct CodegenCx<'gcc, 'tcx> { pub types: RefCell, Option), Type<'gcc>>>, pub tcx: TyCtxt<'tcx>, - pub struct_types: RefCell>, Type<'gcc>>>, + /// Cache of the anonymous struct types. + pub struct_types: RefCell, Type<'gcc>>>, + + /// Cache of the array types used for runs of constant bytes, keyed by element type and count. + /// + /// libgccjit mints a fresh type on every `new_array_type`, and struct types are keyed on their + /// field types, so without this two equal byte runs would yield two distinct anonymous structs. + pub byte_array_types: RefCell, u64), Type<'gcc>>>, /// Cache instances of monomorphic and polymorphic items pub instances: RefCell, LValue<'gcc>>>, @@ -125,8 +141,14 @@ pub struct CodegenCx<'gcc, 'tcx> { pub pointee_infos: RefCell, Size), Option>>, + /// Blocks that are cleanup landing pads, so `invoke` can tell an unwind + /// edge into a cleanup from a catch/terminate. #[cfg(feature = "master")] - pub cleanup_blocks: RefCell>>, + pub landing_pads: RefCell>>, + /// Cleanup regions to be filled in once the function is fully codegened + /// (done in `populate_cleanup_regions`). + #[cfg(feature = "master")] + pub pending_cleanups: RefCell>>, /// The alignment of a u128/i128 type. // We cache this, since it is needed for alignment checks during loads. pub int128_align: Align, @@ -225,12 +247,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { let isize_type = usize_type; let bool_type = context.new_type::(); - let mut functions = FxHashMap::default(); - let builtins = ["abort"]; - - for builtin in builtins.iter() { - functions.insert(builtin.to_string(), context.get_builtin_function(builtin)); - } + let functions = FxHashMap::default(); let mut cx = Self { int128_align: tcx @@ -297,6 +314,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { types: Default::default(), tcx, struct_types: Default::default(), + byte_array_types: Default::default(), local_gen_sym_counter: Cell::new(0), global_gen_sym_counter: Cell::new(0), eh_personality: Cell::new(None), @@ -304,13 +322,43 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { rust_try_fn: Cell::new(None), pointee_infos: Default::default(), #[cfg(feature = "master")] - cleanup_blocks: Default::default(), + landing_pads: Default::default(), + #[cfg(feature = "master")] + pending_cleanups: Default::default(), }; // FIXME(antoyo): instead of doing this, add SsizeT to libgccjit. cx.isize_type = usize_type.to_signed(&cx); cx } + /// Fill in the member blocks of every pending cleanup region. + /// + /// Clone all blocks reachable from a cleanup block into the cleanup region. + #[cfg(feature = "master")] + pub fn populate_cleanup_regions(&self) { + let pending = std::mem::take(&mut *self.pending_cleanups.borrow_mut()); + + for cleanup in pending { + // The landing pad is the region's entry, so it must come first. + let mut blocks = vec![]; + let mut visited = FxHashSet::default(); + let mut stack = vec![cleanup.landing_pad]; + while let Some(block) = stack.pop() { + if !visited.insert(block) { + continue; + } + blocks.push(block); + stack.extend(block.get_successors()); + } + + for clone in gccjit::clone_blocks(&blocks) { + cleanup.region.add_block(clone); + } + } + + self.landing_pads.borrow_mut().clear(); + } + pub fn rvalue_as_function(&self, value: RValue<'gcc>) -> Function<'gcc> { let function: Function<'gcc> = unsafe { std::mem::transmute(value) }; debug_assert!( diff --git a/compiler/rustc_codegen_gcc/src/declare.rs b/compiler/rustc_codegen_gcc/src/declare.rs index 4174eebcf7b02..32bb7c3aa349e 100644 --- a/compiler/rustc_codegen_gcc/src/declare.rs +++ b/compiler/rustc_codegen_gcc/src/declare.rs @@ -1,12 +1,12 @@ #[cfg(feature = "master")] -use gccjit::{FnAttribute, ToRValue}; +use gccjit::{FnAttribute, ToRValue, VarAttribute}; use gccjit::{Function, FunctionType, GlobalKind, LValue, RValue, Type}; use rustc_codegen_ssa::traits::BaseTypeCodegenMethods; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; -use crate::abi::{FnAbiGcc, FnAbiGccExt}; +use crate::abi::FnAbiGccExt; use crate::context::CodegenCx; impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { @@ -14,6 +14,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { &self, name: &str, ty: Type<'gcc>, + global_kind: GlobalKind, is_tls: bool, link_section: Option, ) -> LValue<'gcc> { @@ -24,11 +25,14 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { global.set_tls_model(self.tls_model); } if let Some(link_section) = link_section { + #[cfg(feature = "master")] + global.add_attribute(VarAttribute::Section(link_section.as_str())); + #[cfg(not(feature = "master"))] global.set_link_section(link_section.as_str()); } global } else { - self.declare_global(name, ty, GlobalKind::Exported, is_tls, link_section) + self.declare_global(name, ty, global_kind, is_tls, link_section) } } @@ -73,6 +77,9 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { global.set_tls_model(self.tls_model); } if let Some(link_section) = link_section { + #[cfg(feature = "master")] + global.add_attribute(VarAttribute::Section(link_section.as_str())); + #[cfg(not(feature = "master"))] global.set_link_section(link_section.as_str()); } let global_address = global.get_address(None); @@ -110,22 +117,22 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { } pub fn declare_fn(&self, name: &str, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> Function<'gcc> { - let FnAbiGcc { - return_type, - arguments_type, - is_c_variadic, - on_stack_param_indices, - #[cfg(feature = "master")] - fn_attributes, - } = fn_abi.gcc_type(self); + let fn_abi_gcc = fn_abi.gcc_type(self); #[cfg(feature = "master")] let conv = fn_abi.gcc_cconv(self); #[cfg(not(feature = "master"))] let conv = None; - let func = declare_raw_fn(self, name, conv, return_type, &arguments_type, is_c_variadic); - self.on_stack_function_params.borrow_mut().insert(func, on_stack_param_indices); + let func = declare_raw_fn( + self, + name, + conv, + fn_abi_gcc.return_type, + &fn_abi_gcc.arguments_type, + fn_abi_gcc.is_c_variadic, + ); + self.on_stack_function_params.borrow_mut().insert(func, fn_abi_gcc.on_stack_param_indices); #[cfg(feature = "master")] - for fn_attr in fn_attributes { + for fn_attr in fn_abi_gcc.fn_attributes { func.add_attribute(fn_attr); } func @@ -135,10 +142,11 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { &self, name: &str, ty: Type<'gcc>, + global_kind: GlobalKind, is_tls: bool, link_section: Option, ) -> LValue<'gcc> { - self.get_or_insert_global(name, ty, is_tls, link_section) + self.get_or_insert_global(name, ty, global_kind, is_tls, link_section) } pub fn get_declared_value(&self, name: &str) -> Option> { diff --git a/compiler/rustc_codegen_gcc/src/diagnostics.rs b/compiler/rustc_codegen_gcc/src/diagnostics.rs index de633d3bdde79..67723ebd2f30b 100644 --- a/compiler/rustc_codegen_gcc/src/diagnostics.rs +++ b/compiler/rustc_codegen_gcc/src/diagnostics.rs @@ -20,10 +20,6 @@ pub(crate) struct LtoBitcodeFromRlib { pub gcc_err: String, } -#[derive(Diagnostic)] -#[diag("explicit tail calls with the 'become' keyword are not implemented in the GCC backend")] -pub(crate) struct ExplicitTailCallsUnsupported; - #[derive(Diagnostic)] #[diag("asm contains a NUL byte")] pub(crate) struct NulBytesInAsm { diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs index 24f552fed32c6..0628171e488b3 100644 --- a/compiler/rustc_codegen_gcc/src/gcc_util.rs +++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs @@ -1,10 +1,15 @@ -#[cfg(feature = "master")] +use std::borrow::Cow; +use std::collections::HashSet; +use std::env; + use gccjit::Context; +#[cfg(feature = "master")] +use gccjit::Version; use rustc_codegen_ssa::target_features; use rustc_data_structures::smallvec::{SmallVec, smallvec}; -use rustc_session::EarlySession; use rustc_session::config::NATIVE_CPU; -use rustc_target::spec::{Arch, Target}; +use rustc_session::{EarlySession, Session}; +use rustc_target::spec::{Arch, RelocModel, StackProbeType, StackProtector, Target}; fn gcc_features_by_flags(sess: &EarlySession, features: &mut Vec) { target_features::retpoline_features_by_flags(sess, features); @@ -110,29 +115,180 @@ pub fn to_gcc_features<'a>(target: &Target, s: &'a str) -> SmallVec<[&'a str; 2] fn arch_to_gcc(name: &str) -> &str { match name { "M68000" => "68000", + "M68010" => "68010", "M68020" => "68020", + "M68030" => "68030", + "M68040" => "68040", + "M68060" => "68060", _ => name, } } -fn handle_native(name: &str) -> &str { +fn handle_native(name: &str) -> Cow<'_, str> { if name != NATIVE_CPU { - return arch_to_gcc(name); + return arch_to_gcc(name).into(); } #[cfg(feature = "master")] { // Get the native arch. let context = Context::default(); - context.get_target_info().arch().unwrap().to_str().unwrap() + Cow::Owned(context.get_target_info().arch().to_str().unwrap().to_string()) } #[cfg(not(feature = "master"))] unimplemented!(); } -pub fn target_cpu(sess: &EarlySession) -> &str { +pub fn target_cpu(sess: &EarlySession) -> Cow<'_, str> { match sess.opts.cg.target_cpu { Some(ref name) => handle_native(name), None => handle_native(sess.target.cpu.as_ref()), } } + +pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { + let context = Context::default(); + if matches!(sess.target.arch, Arch::X86 | Arch::X86_64) { + context.add_command_line_option("-masm=intel"); + } + #[cfg(feature = "master")] + { + context.set_special_chars_allowed_in_func_names("$.*"); + let version = Version::get(); + let version = format!("{}.{}.{}", version.major, version.minor, version.patch); + context.set_output_ident(&format!( + "rustc version {} with libgccjit {}", + rustc_interface::util::rustc_version_str().unwrap_or("unknown version"), + version, + )); + } + if !sess.must_emit_unwind_tables() { + context.add_command_line_option("-fno-asynchronous-unwind-tables"); + } + + if sess.panic_strategy().unwinds() { + context.add_command_line_option("-fexceptions"); + context.add_driver_option("-fexceptions"); + } + + let disabled_features: HashSet<_> = sess + .opts + .cg + .target_feature + .split(',') + .filter(|feature| feature.starts_with('-')) + .map(|string| &string[1..]) + .collect(); + + if !disabled_features.contains("avx") && sess.target.arch == Arch::X86_64 { + // NOTE: we always enable AVX because the equivalent of llvm.x86.sse2.cmp.pd in GCC for + // SSE2 is multiple builtins, so we use the AVX __builtin_ia32_cmppd instead. + // FIXME(antoyo): use the proper builtins for llvm.x86.sse2.cmp.pd and similar. + context.add_command_line_option("-mavx"); + } + + for arg in &sess.opts.cg.llvm_args { + context.add_command_line_option(arg); + } + // NOTE: This is needed to compile the file src/intrinsic/archs.rs during a bootstrap of rustc. + context.add_command_line_option("-fno-var-tracking-assignments"); + // NOTE: an optimization (https://github.com/rust-lang/rustc_codegen_gcc/issues/53). + context.add_command_line_option("-fno-semantic-interposition"); + // NOTE: Rust relies on LLVM not doing TBAA (https://github.com/rust-lang/unsafe-code-guidelines/issues/292). + context.add_command_line_option("-fno-strict-aliasing"); + // NOTE: Rust relies on LLVM doing wrapping on overflow. + context.add_command_line_option("-fwrapv"); + // NOTE: This is needed to hide a warning caused by the alignment fix on byval arguments. + context.add_command_line_option("-Wno-psabi"); + + if let Some(model) = sess.code_model() { + use rustc_target::spec::CodeModel; + + context.add_command_line_option(match model { + CodeModel::Tiny => "-mcmodel=tiny", + CodeModel::Small => "-mcmodel=small", + CodeModel::Kernel => "-mcmodel=kernel", + CodeModel::Medium => "-mcmodel=medium", + CodeModel::Large => "-mcmodel=large", + }); + } + + match sess.stack_protector() { + StackProtector::All => context.add_command_line_option("-fstack-protector-all"), + StackProtector::Strong => context.add_command_line_option("-fstack-protector-strong"), + StackProtector::Basic => context.add_command_line_option("-fstack-protector"), + StackProtector::None => (), + } + + match sess.target.stack_probes { + StackProbeType::None => (), + StackProbeType::Inline | StackProbeType::InlineOrCall { .. } => { + context.add_command_line_option("-fstack-clash-protection") + } + // FIXME(antoyo): We should define the stack probe symbol to be __rust_probestack, but it seems GCC cannot do that. + StackProbeType::Call => (), + }; + + add_pic_option(&context, sess.relocation_model()); + + let target_cpu = target_cpu(sess); + if target_cpu != "generic" { + context.add_command_line_option(format!("-march={}", target_cpu)); + } + + if sess.opts.unstable_opts.function_sections.unwrap_or(sess.target.function_sections) { + context.add_command_line_option("-ffunction-sections"); + context.add_command_line_option("-fdata-sections"); + } + + if env::var("CG_GCCJIT_DUMP_RTL").as_deref() == Ok("1") { + context.add_command_line_option("-fdump-rtl-vregs"); + } + if env::var("CG_GCCJIT_DUMP_RTL_ALL").as_deref() == Ok("1") { + context.add_command_line_option("-fdump-rtl-all"); + } + if env::var("CG_GCCJIT_DUMP_TREE_ALL").as_deref() == Ok("1") { + context.add_command_line_option("-fdump-tree-all-eh"); + } + if env::var("CG_GCCJIT_DUMP_IPA_ALL").as_deref() == Ok("1") { + context.add_command_line_option("-fdump-ipa-all-eh"); + } + if env::var("CG_GCCJIT_DUMP_CODE").as_deref() == Ok("1") { + context.set_dump_code_on_compile(true); + } + if env::var("CG_GCCJIT_DUMP_GIMPLE").as_deref() == Ok("1") { + context.set_dump_initial_gimple(true); + } + if env::var("CG_GCCJIT_DUMP_EVERYTHING").as_deref() == Ok("1") { + context.set_dump_everything(true); + } + if env::var("CG_GCCJIT_KEEP_INTERMEDIATES").as_deref() == Ok("1") { + context.set_keep_intermediates(true); + } + if env::var("CG_GCCJIT_VERBOSE").as_deref() == Ok("1") { + context.add_driver_option("-v"); + } + + context +} + +pub fn add_pic_option<'gcc>(context: &Context<'gcc>, relocation_model: RelocModel) { + match relocation_model { + rustc_target::spec::RelocModel::Static => { + context.add_command_line_option("-fno-pie"); + context.add_driver_option("-fno-pie"); + } + rustc_target::spec::RelocModel::Pic => { + context.add_command_line_option("-fPIC"); + // NOTE: we use both add_command_line_option and add_driver_option because the usage in + // base (compile_codegen_unit) requires add_command_line_option while the usage + // in the back::write module (codegen) requires add_driver_option. + context.add_driver_option("-fPIC"); + } + rustc_target::spec::RelocModel::Pie => { + context.add_command_line_option("-fPIE"); + context.add_driver_option("-fPIE"); + } + model => eprintln!("Unsupported relocation model: {:?}", model), + } +} diff --git a/compiler/rustc_codegen_gcc/src/int.rs b/compiler/rustc_codegen_gcc/src/int.rs index dfae4eceebe44..4e4b911666143 100644 --- a/compiler/rustc_codegen_gcc/src/int.rs +++ b/compiler/rustc_codegen_gcc/src/int.rs @@ -21,12 +21,12 @@ use crate::context::CodegenCx; impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { pub fn gcc_urem(&self, a: RValue<'gcc>, b: RValue<'gcc>) -> RValue<'gcc> { // 128-bit unsigned %: __umodti3 - self.multiplicative_operation(BinaryOp::Modulo, "mod", false, a, b) + self.division_operation(BinaryOp::Modulo, "mod", false, a, b) } pub fn gcc_srem(&self, a: RValue<'gcc>, b: RValue<'gcc>) -> RValue<'gcc> { // 128-bit signed %: __modti3 - self.multiplicative_operation(BinaryOp::Modulo, "mod", true, a, b) + self.division_operation(BinaryOp::Modulo, "mod", true, a, b) } pub fn gcc_not(&self, a: RValue<'gcc>) -> RValue<'gcc> { @@ -178,6 +178,9 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { } else { debug_assert!(a_type.dyncast_array().is_some()); debug_assert!(b_type.dyncast_array().is_some()); + if a_type != b_type { + b = self.gcc_int_cast(b, a_type); + } let signed = a_type.is_compatible_with(self.i128_type); let func_name = match (operation, signed) { (BinaryOp::Plus, true) => "__rust_i128_add", @@ -187,7 +190,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { _ => unreachable!("unexpected additive operation {:?}", operation), }; let param_a = self.context.new_parameter(self.location, a_type, "a"); - let param_b = self.context.new_parameter(self.location, b_type, "b"); + let param_b = self.context.new_parameter(self.location, a_type, "b"); let func = self.context.new_function( self.location, FunctionType::Extern, @@ -212,6 +215,27 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.additive_operation(BinaryOp::Minus, a, b) } + fn division_operation( + &self, + operation: BinaryOp, + operation_name: &str, + signed: bool, + mut a: RValue<'gcc>, + mut b: RValue<'gcc>, + ) -> RValue<'gcc> { + let a_type = a.get_type(); + if self.is_native_int_type(a_type) && self.is_native_int_type(b.get_type()) { + let typ = if signed { a_type.to_signed(self.cx) } else { a_type.to_unsigned(self.cx) }; + if !typ.is_compatible_with(a_type) { + a = self.context.new_cast(self.location, a, typ); + } + if !typ.is_compatible_with(b.get_type()) { + b = self.context.new_cast(self.location, b, typ); + } + } + self.multiplicative_operation(operation, operation_name, signed, a, b) + } + fn multiplicative_operation( &self, operation: BinaryOp, @@ -238,10 +262,13 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { } else { debug_assert!(a_type.dyncast_array().is_some()); debug_assert!(b_type.dyncast_array().is_some()); + if a_type != b_type { + b = self.gcc_int_cast(b, a_type); + } let sign = if signed { "" } else { "u" }; let func_name = format!("__{}{}ti3", sign, operation_name); let param_a = self.context.new_parameter(self.location, a_type, "a"); - let param_b = self.context.new_parameter(self.location, b_type, "b"); + let param_b = self.context.new_parameter(self.location, a_type, "b"); let func = self.context.new_function( self.location, FunctionType::Extern, @@ -255,15 +282,13 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { } pub fn gcc_sdiv(&self, a: RValue<'gcc>, b: RValue<'gcc>) -> RValue<'gcc> { - // FIXME(antoyo): check if the types are signed? // 128-bit, signed: __divti3 - // FIXME(antoyo): convert the arguments to signed? - self.multiplicative_operation(BinaryOp::Divide, "div", true, a, b) + self.division_operation(BinaryOp::Divide, "div", true, a, b) } pub fn gcc_udiv(&self, a: RValue<'gcc>, b: RValue<'gcc>) -> RValue<'gcc> { // 128-bit, unsigned: __udivti3 - self.multiplicative_operation(BinaryOp::Divide, "div", false, a, b) + self.division_operation(BinaryOp::Divide, "div", false, a, b) } pub fn gcc_checked_binop( @@ -432,7 +457,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { if self.is_non_native_int_type(a_type) || self.is_non_native_int_type(b_type) { // This algorithm is based on compiler-rt's __cmpti2: // https://github.com/llvm-mirror/compiler-rt/blob/f0745e8476f069296a7c71accedd061dce4cdf79/lib/builtins/cmpti2.c#L21 - let result = self.current_func().new_local(self.location, self.int_type, "icmp_result"); + let result = self.new_temp(self.current_func(), self.location, self.int_type); let block1 = self.current_func().new_block("block1"); let block2 = self.current_func().new_block("block2"); let block3 = self.current_func().new_block("block3"); @@ -462,9 +487,18 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { lhs_high = self.context.new_cast(self.location, lhs_high, unsigned_type); rhs_high = self.context.new_cast(self.location, rhs_high, unsigned_type); } - // FIXME(antoyo): we probably need to handle signed comparison for unsigned - // integers. - _ => (), + IntPredicate::IntSGT + | IntPredicate::IntSGE + | IntPredicate::IntSLT + | IntPredicate::IntSLE => { + let signed_type = native_int_type.to_signed(self.cx); + lhs_high = self.context.new_cast(self.location, lhs_high, signed_type); + rhs_high = self.context.new_cast(self.location, rhs_high, signed_type); + } + IntPredicate::IntEQ | IntPredicate::IntNE => { + lhs_high = self.context.new_cast(self.location, lhs_high, unsigned_type); + rhs_high = self.context.new_cast(self.location, rhs_high, unsigned_type); + } } let condition = self.context.new_comparison( @@ -602,9 +636,17 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { rhs = self.context.new_cast(self.location, rhs, unsigned_type); } } - // FIXME(antoyo): we probably need to handle signed comparison for unsigned - // integers. - _ => (), + IntPredicate::IntSGT + | IntPredicate::IntSGE + | IntPredicate::IntSLT + | IntPredicate::IntSLE => { + if !a_type.is_vector() { + let signed_type = a_type.to_signed(self.cx); + lhs = self.context.new_cast(self.location, lhs, signed_type); + rhs = self.context.new_cast(self.location, rhs, signed_type); + } + } + IntPredicate::IntEQ | IntPredicate::IntNE => (), } self.context.new_comparison(self.location, op.to_gcc_comparison(), lhs, rhs) } @@ -623,6 +665,9 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { } a ^ b } else { + if a_type != b_type { + b = self.gcc_int_cast(b, a_type); + } self.concat_low_high_rvalues( a_type, self.low(a) ^ self.low(b), @@ -832,6 +877,9 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { !a_native && !b_native, "both types should either be native or non-native for or operation" ); + if a_type != b_type { + b = self.gcc_int_cast(b, a_type); + } let native_int_type = a_type.dyncast_array().expect("get element type"); self.concat_low_high_rvalues( a_type, @@ -862,7 +910,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.bitwise_operation(BinaryOp::BitwiseOr, a, b, loc) } - // FIXME(antoyo): can we use https://github.com/rust-lang/compiler-builtins/blob/master/src/int/mod.rs#L379 instead? + // FIXME(antoyo): can we use https://github.com/rust-lang/compiler-builtins/blob/1a99c2aa295bb2d507fa0e67a3b5eef64fba92a0/libm/src/math/support/int_traits.rs#L485 instead? pub fn gcc_int_cast(&self, value: RValue<'gcc>, dest_typ: Type<'gcc>) -> RValue<'gcc> { let value_type = value.get_type(); if self.is_native_int_type_or_bool(dest_typ) && self.is_native_int_type_or_bool(value_type) diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs b/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs index 3c1698df6dec2..1856c2468616d 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs @@ -24,6 +24,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "gcsss" => "__builtin_arm_gcsss", "isb" => "__builtin_arm_isb", "prefetch" => "__builtin_arm_prefetch", + "prefetch.ir" => "__builtin_arm_prefetch_ir", "range.prefetch" => "__builtin_arm_range_prefetch", "sme.in.streaming.mode" => "__builtin_arm_in_streaming_mode", "sve.aesd" => "__builtin_sve_svaesd_u8", @@ -53,6 +54,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "alignbyte" => "__builtin_amdgcn_alignbyte", "ashr.pk.i8.i32" => "__builtin_amdgcn_ashr_pk_i8_i32", "ashr.pk.u8.i32" => "__builtin_amdgcn_ashr_pk_u8_i32", + "asyncmark" => "__builtin_amdgcn_asyncmark", "buffer.wbinvl1" => "__builtin_amdgcn_buffer_wbinvl1", "buffer.wbinvl1.sc" => "__builtin_amdgcn_buffer_wbinvl1_sc", "buffer.wbinvl1.vol" => "__builtin_amdgcn_buffer_wbinvl1_vol", @@ -270,6 +272,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "fdot2c.f32.bf16" => "__builtin_amdgcn_fdot2c_f32_bf16", "flat.prefetch" => "__builtin_amdgcn_flat_prefetch", "fmul.legacy" => "__builtin_amdgcn_fmul_legacy", + "global.load.async.lds" => "__builtin_amdgcn_global_load_async_lds", "global.load.async.to.lds.b128" => { "__builtin_amdgcn_global_load_async_to_lds_b128" } @@ -361,11 +364,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "perm.pk16.b4.u4" => "__builtin_amdgcn_perm_pk16_b4_u4", "perm.pk16.b6.u4" => "__builtin_amdgcn_perm_pk16_b6_u4", "perm.pk16.b8.u4" => "__builtin_amdgcn_perm_pk16_b8_u4", - "permlane.bcast" => "__builtin_amdgcn_permlane_bcast", - "permlane.down" => "__builtin_amdgcn_permlane_down", "permlane.idx.gen" => "__builtin_amdgcn_permlane_idx_gen", - "permlane.up" => "__builtin_amdgcn_permlane_up", - "permlane.xor" => "__builtin_amdgcn_permlane_xor", "permlane16.var" => "__builtin_amdgcn_permlane16_var", "permlanex16.var" => "__builtin_amdgcn_permlanex16_var", "pk.add.max.i16" => "__builtin_amdgcn_pk_add_max_i16", @@ -375,6 +374,9 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "prng.b32" => "__builtin_amdgcn_prng_b32", "qsad.pk.u16.u8" => "__builtin_amdgcn_qsad_pk_u16_u8", "queue.ptr" => "__builtin_amdgcn_queue_ptr", + "raw.ptr.buffer.load.async.lds" => { + "__builtin_amdgcn_raw_ptr_buffer_load_async_lds" + } "raw.ptr.buffer.load.lds" => "__builtin_amdgcn_raw_ptr_buffer_load_lds", "rcp.legacy" => "__builtin_amdgcn_rcp_legacy", "rsq.legacy" => "__builtin_amdgcn_rsq_legacy", @@ -386,6 +388,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "s.barrier.signal.isfirst" => "__builtin_amdgcn_s_barrier_signal_isfirst", "s.barrier.signal.var" => "__builtin_amdgcn_s_barrier_signal_var", "s.barrier.wait" => "__builtin_amdgcn_s_barrier_wait", + "s.bitreplicate" => "__builtin_amdgcn_s_bitreplicate", "s.buffer.prefetch.data" => "__builtin_amdgcn_s_buffer_prefetch_data", "s.cluster.barrier" => "__builtin_amdgcn_s_cluster_barrier", "s.dcache.inv" => "__builtin_amdgcn_s_dcache_inv", @@ -412,6 +415,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "s.ttracedata" => "__builtin_amdgcn_s_ttracedata", "s.ttracedata.imm" => "__builtin_amdgcn_s_ttracedata_imm", "s.wait.asynccnt" => "__builtin_amdgcn_s_wait_asynccnt", + "s.wait.event" => "__builtin_amdgcn_s_wait_event", "s.wait.event.export.ready" => "__builtin_amdgcn_s_wait_event_export_ready", "s.wait.tensorcnt" => "__builtin_amdgcn_s_wait_tensorcnt", "s.waitcnt" => "__builtin_amdgcn_s_waitcnt", @@ -462,16 +466,18 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "smfmac.i32.16x16x64.i8" => "__builtin_amdgcn_smfmac_i32_16x16x64_i8", "smfmac.i32.32x32x32.i8" => "__builtin_amdgcn_smfmac_i32_32x32x32_i8", "smfmac.i32.32x32x64.i8" => "__builtin_amdgcn_smfmac_i32_32x32x64_i8", + "struct.ptr.buffer.load.async.lds" => { + "__builtin_amdgcn_struct_ptr_buffer_load_async_lds" + } "struct.ptr.buffer.load.lds" => "__builtin_amdgcn_struct_ptr_buffer_load_lds", "sudot4" => "__builtin_amdgcn_sudot4", "sudot8" => "__builtin_amdgcn_sudot8", "tensor.load.to.lds" => "__builtin_amdgcn_tensor_load_to_lds", - "tensor.load.to.lds.d2" => "__builtin_amdgcn_tensor_load_to_lds_d2", "tensor.store.from.lds" => "__builtin_amdgcn_tensor_store_from_lds", - "tensor.store.from.lds.d2" => "__builtin_amdgcn_tensor_store_from_lds_d2", "udot2" => "__builtin_amdgcn_udot2", "udot4" => "__builtin_amdgcn_udot4", "udot8" => "__builtin_amdgcn_udot8", + "wait.asyncmark" => "__builtin_amdgcn_wait_asyncmark", "wave.barrier" => "__builtin_amdgcn_wave_barrier", "wavefrontsize" => "__builtin_amdgcn_wavefrontsize", "workgroup.id.x" => "__builtin_amdgcn_workgroup_id_x", @@ -4844,7 +4850,11 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "add.rn.f" => "__nvvm_add_rn_f", "add.rn.ftz.f" => "__nvvm_add_rn_ftz_f", "add.rn.ftz.sat.f" => "__nvvm_add_rn_ftz_sat_f", + "add.rn.ftz.sat.f16" => "__nvvm_add_rn_ftz_sat_f16", + "add.rn.ftz.sat.v2f16" => "__nvvm_add_rn_ftz_sat_v2f16", "add.rn.sat.f" => "__nvvm_add_rn_sat_f", + "add.rn.sat.f16" => "__nvvm_add_rn_sat_f16", + "add.rn.sat.v2f16" => "__nvvm_add_rn_sat_v2f16", "add.rp.d" => "__nvvm_add_rp_d", "add.rp.f" => "__nvvm_add_rp_f", "add.rp.ftz.f" => "__nvvm_add_rp_ftz_f", @@ -5063,18 +5073,10 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "fma.rn.bf16x2" => "__nvvm_fma_rn_bf16x2", "fma.rn.d" => "__nvvm_fma_rn_d", "fma.rn.f" => "__nvvm_fma_rn_f", - "fma.rn.ftz.bf16" => "__nvvm_fma_rn_ftz_bf16", - "fma.rn.ftz.bf16x2" => "__nvvm_fma_rn_ftz_bf16x2", "fma.rn.ftz.f" => "__nvvm_fma_rn_ftz_f", - "fma.rn.ftz.relu.bf16" => "__nvvm_fma_rn_ftz_relu_bf16", - "fma.rn.ftz.relu.bf16x2" => "__nvvm_fma_rn_ftz_relu_bf16x2", - "fma.rn.ftz.sat.bf16" => "__nvvm_fma_rn_ftz_sat_bf16", - "fma.rn.ftz.sat.bf16x2" => "__nvvm_fma_rn_ftz_sat_bf16x2", "fma.rn.ftz.sat.f" => "__nvvm_fma_rn_ftz_sat_f", "fma.rn.relu.bf16" => "__nvvm_fma_rn_relu_bf16", "fma.rn.relu.bf16x2" => "__nvvm_fma_rn_relu_bf16x2", - "fma.rn.sat.bf16" => "__nvvm_fma_rn_sat_bf16", - "fma.rn.sat.bf16x2" => "__nvvm_fma_rn_sat_bf16x2", "fma.rn.sat.f" => "__nvvm_fma_rn_sat_f", "fma.rp.d" => "__nvvm_fma_rp_d", "fma.rp.f" => "__nvvm_fma_rp_f", @@ -5195,6 +5197,10 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "mul.rn.d" => "__nvvm_mul_rn_d", "mul.rn.f" => "__nvvm_mul_rn_f", "mul.rn.ftz.f" => "__nvvm_mul_rn_ftz_f", + "mul.rn.ftz.sat.f16" => "__nvvm_mul_rn_ftz_sat_f16", + "mul.rn.ftz.sat.v2f16" => "__nvvm_mul_rn_ftz_sat_v2f16", + "mul.rn.sat.f16" => "__nvvm_mul_rn_sat_f16", + "mul.rn.sat.v2f16" => "__nvvm_mul_rn_sat_v2f16", "mul.rp.d" => "__nvvm_mul_rp_d", "mul.rp.f" => "__nvvm_mul_rp_f", "mul.rp.ftz.f" => "__nvvm_mul_rp_ftz_f", @@ -5827,8 +5833,10 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "altivec.vmuleuh" => "__builtin_altivec_vmuleuh", "altivec.vmuleuw" => "__builtin_altivec_vmuleuw", "altivec.vmulhsd" => "__builtin_altivec_vmulhsd", + "altivec.vmulhsh" => "__builtin_altivec_vmulhsh", "altivec.vmulhsw" => "__builtin_altivec_vmulhsw", "altivec.vmulhud" => "__builtin_altivec_vmulhud", + "altivec.vmulhuh" => "__builtin_altivec_vmulhuh", "altivec.vmulhuw" => "__builtin_altivec_vmulhuw", "altivec.vmulosb" => "__builtin_altivec_vmulosb", "altivec.vmulosd" => "__builtin_altivec_vmulosd", @@ -5912,22 +5920,45 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "altivec.vsum4shs" => "__builtin_altivec_vsum4shs", "altivec.vsum4ubs" => "__builtin_altivec_vsum4ubs", "altivec.vsumsws" => "__builtin_altivec_vsumsws", + "altivec.vucmprhb" => "__builtin_altivec_vucmprhb", + "altivec.vucmprhh" => "__builtin_altivec_vucmprhh", + "altivec.vucmprhn" => "__builtin_altivec_vucmprhn", + "altivec.vucmprlb" => "__builtin_altivec_vucmprlb", + "altivec.vucmprlh" => "__builtin_altivec_vucmprlh", + "altivec.vucmprln" => "__builtin_altivec_vucmprln", "altivec.vupkhpx" => "__builtin_altivec_vupkhpx", "altivec.vupkhsb" => "__builtin_altivec_vupkhsb", "altivec.vupkhsh" => "__builtin_altivec_vupkhsh", + "altivec.vupkhsntob" => "__builtin_altivec_vupkhsntob", "altivec.vupkhsw" => "__builtin_altivec_vupkhsw", + "altivec.vupkint4tobf16" => "__builtin_altivec_vupkint4tobf16", + "altivec.vupkint4tofp32" => "__builtin_altivec_vupkint4tofp32", + "altivec.vupkint8tobf16" => "__builtin_altivec_vupkint8tobf16", + "altivec.vupkint8tofp32" => "__builtin_altivec_vupkint8tofp32", "altivec.vupklpx" => "__builtin_altivec_vupklpx", "altivec.vupklsb" => "__builtin_altivec_vupklsb", "altivec.vupklsh" => "__builtin_altivec_vupklsh", + "altivec.vupklsntob" => "__builtin_altivec_vupklsntob", "altivec.vupklsw" => "__builtin_altivec_vupklsw", "amo.ldat" => "__builtin_amo_ldat", + "amo.ldat.cond" => "__builtin_amo_ldat_cond", + "amo.ldat.csne" => "__builtin_amo_ldat_csne", "amo.lwat" => "__builtin_amo_lwat", + "amo.lwat.cond" => "__builtin_amo_lwat_cond", + "amo.lwat.csne" => "__builtin_amo_lwat_csne", + "amo.stdat" => "__builtin_amo_stdat", + "amo.stwat" => "__builtin_amo_stwat", "bcdadd" => "__builtin_ppc_bcdadd", "bcdadd.p" => "__builtin_ppc_bcdadd_p", "bcdcopysign" => "__builtin_ppc_bcdcopysign", "bcdsetsign" => "__builtin_ppc_bcdsetsign", + "bcdshift" => "__builtin_ppc_bcdshift", + "bcdshiftround" => "__builtin_ppc_bcdshiftround", "bcdsub" => "__builtin_ppc_bcdsub", "bcdsub.p" => "__builtin_ppc_bcdsub_p", + "bcdtruncate" => "__builtin_ppc_bcdtruncate", + "bcdunsignedshift" => "__builtin_ppc_bcdunsignedshift", + "bcdunsignedtruncate" => "__builtin_ppc_bcdunsignedtruncate", "bpermd" => "__builtin_bpermd", "cbcdtd" => "__builtin_cbcdtd", "cbcdtdd" => "__builtin_ppc_cbcdtd", @@ -6126,6 +6157,27 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "vsx.xxinsertw" => "__builtin_vsx_xxinsertw", "vsx.xxleqv" => "__builtin_vsx_xxleqv", "vsx.xxpermx" => "__builtin_vsx_xxpermx", + "xsaddaddsuqm" => "__builtin_xsaddaddsuqm", + "xsaddadduqm" => "__builtin_xsaddadduqm", + "xsaddsubsuqm" => "__builtin_xsaddsubsuqm", + "xsaddsubuqm" => "__builtin_xsaddsubuqm", + "xsmerge2t1uqm" => "__builtin_xsmerge2t1uqm", + "xsmerge2t2uqm" => "__builtin_xsmerge2t2uqm", + "xsmerge2t3uqm" => "__builtin_xsmerge2t3uqm", + "xsmerge3t1uqm" => "__builtin_xsmerge3t1uqm", + "xsrebase2t1uqm" => "__builtin_xsrebase2t1uqm", + "xsrebase2t2uqm" => "__builtin_xsrebase2t2uqm", + "xsrebase2t3uqm" => "__builtin_xsrebase2t3uqm", + "xsrebase2t4uqm" => "__builtin_xsrebase2t4uqm", + "xsrebase3t1uqm" => "__builtin_xsrebase3t1uqm", + "xsrebase3t2uqm" => "__builtin_xsrebase3t2uqm", + "xsrebase3t3uqm" => "__builtin_xsrebase3t3uqm", + "xxmulmul" => "__builtin_xxmulmul", + "xxmulmulhiadd" => "__builtin_xxmulmulhiadd", + "xxmulmulloadd" => "__builtin_xxmulmulloadd", + "xxssumudm" => "__builtin_xxssumudm", + "xxssumudmc" => "__builtin_xxssumudmc", + "xxssumudmcext" => "__builtin_xxssumudmcext", "zoned2packed" => "__builtin_ppc_zoned2packed", _ => unimplemented!("***** unsupported LLVM intrinsic {full_name}"), } @@ -6388,13 +6440,13 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { // spv "group.memory.barrier.with.group.sync" => "__builtin_spirv_group_barrier", "num.subgroups" => "__builtin_spirv_num_subgroups", + "subgroup.ballot" => "__builtin_spirv_subgroup_ballot", "subgroup.id" => "__builtin_spirv_subgroup_id", "subgroup.local.invocation.id" => { "__builtin_spirv_subgroup_local_invocation_id" } "subgroup.max.size" => "__builtin_spirv_subgroup_max_size", "subgroup.size" => "__builtin_spirv_subgroup_size", - "wave.ballot" => "__builtin_spirv_subgroup_ballot", _ => unimplemented!("***** unsupported LLVM intrinsic {full_name}"), } } @@ -8661,10 +8713,6 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "bmi.bextr.64" => "__builtin_ia32_bextr_u64", "bmi.bzhi.32" => "__builtin_ia32_bzhi_si", "bmi.bzhi.64" => "__builtin_ia32_bzhi_di", - "bmi.pdep.32" => "__builtin_ia32_pdep_si", - "bmi.pdep.64" => "__builtin_ia32_pdep_di", - "bmi.pext.32" => "__builtin_ia32_pext_si", - "bmi.pext.64" => "__builtin_ia32_pext_di", "cldemote" => "__builtin_ia32_cldemote", "clflushopt" => "__builtin_ia32_clflushopt", "clrssbsy" => "__builtin_ia32_clrssbsy", diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs b/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs index 41efe3e8209bf..ef381715c1ea2 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs @@ -5,6 +5,7 @@ use rustc_codegen_ssa::traits::BuilderMethods; use crate::builder::Builder; use crate::context::{CodegenCx, new_array_type}; +use crate::type_::{StructAttribute, apply_struct_attributes}; fn encode_key_128_type<'a, 'gcc, 'tcx>( builder: &Builder<'a, 'gcc, 'tcx>, @@ -22,8 +23,7 @@ fn encode_key_128_type<'a, 'gcc, 'tcx>( "EncodeKey128Output", &[field1, field2, field3, field4, field5, field6, field7], ); - #[cfg(feature = "master")] - encode_type.as_type().set_packed(); + apply_struct_attributes(encode_type.as_type(), &[StructAttribute::Packed]); (encode_type.as_type(), field1, field2) } @@ -44,8 +44,7 @@ fn encode_key_256_type<'a, 'gcc, 'tcx>( "EncodeKey256Output", &[field1, field2, field3, field4, field5, field6, field7, field8], ); - #[cfg(feature = "master")] - encode_type.as_type().set_packed(); + apply_struct_attributes(encode_type.as_type(), &[StructAttribute::Packed]); (encode_type.as_type(), field1, field2) } @@ -57,8 +56,7 @@ fn aes_output_type<'a, 'gcc, 'tcx>( let field2 = builder.context.new_field(None, m128i, "field2"); let aes_output_type = builder.context.new_struct_type(None, "AesOutput", &[field1, field2]); let typ = aes_output_type.as_type(); - #[cfg(feature = "master")] - typ.set_packed(); + apply_struct_attributes(typ, &[StructAttribute::Packed]); (typ, field1, field2) } @@ -80,8 +78,7 @@ fn wide_aes_output_type<'a, 'gcc, 'tcx>( "WideAesOutput", &[field1, field2, field3, field4, field5, field6, field7, field8, field9], ); - #[cfg(feature = "master")] - aes_output_type.as_type().set_packed(); + apply_struct_attributes(aes_output_type.as_type(), &[StructAttribute::Packed]); (aes_output_type.as_type(), field1, field2) } @@ -478,6 +475,26 @@ pub fn adjust_intrinsic_arguments<'a, 'b, 'gcc, 'tcx>( new_args.push(variable.get_address(None)); args = new_args.into(); } + "__builtin_ia32_2intersectd128" + | "__builtin_ia32_2intersectq128" + | "__builtin_ia32_2intersectd256" + | "__builtin_ia32_2intersectq256" + | "__builtin_ia32_2intersectd512" + | "__builtin_ia32_2intersectq512" => { + let old_args = args.to_vec(); + let mut new_args = vec![]; + let arg1_type = gcc_func.get_param_type(0); + let first_mask = + builder.current_func().new_local(None, arg1_type, "return_2intersect_arg1"); + let arg2_type = gcc_func.get_param_type(1); + let second_mask = + builder.current_func().new_local(None, arg2_type, "return_2intersect_arg2"); + new_args.push(first_mask.get_address(None)); + new_args.push(second_mask.get_address(None)); + new_args.push(old_args[0]); + new_args.push(old_args[1]); + args = new_args.into(); + } "__builtin_ia32_vpermt2varqi512_mask" | "__builtin_ia32_vpermt2varqi256_mask" | "__builtin_ia32_vpermt2varqi128_mask" @@ -489,6 +506,23 @@ pub fn adjust_intrinsic_arguments<'a, 'b, 'gcc, 'tcx>( let minus_one = builder.context.new_rvalue_from_int(arg4_type, -1); args = vec![new_args[1], new_args[0], new_args[2], minus_one].into(); } + "__builtin_ia32_fpclassph128_mask" + | "__builtin_ia32_fpclassph256_mask" + | "__builtin_ia32_fpclassph512_mask" + | "__builtin_ia32_fpclasspd128_mask" + | "__builtin_ia32_fpclassps128_mask" + | "__builtin_ia32_fpclasspd256_mask" + | "__builtin_ia32_fpclassps256_mask" + | "__builtin_ia32_fpclasspd512_mask" + | "__builtin_ia32_fpclassps512_mask" + | "__builtin_ia32_vpshufbitqmb128_mask" + | "__builtin_ia32_vpshufbitqmb256_mask" + | "__builtin_ia32_vpshufbitqmb512_mask" => { + let new_args = args.to_vec(); + let arg3_type = gcc_func.get_param_type(2); + let minus_one = builder.context.new_rvalue_from_int(arg3_type, -1); + args = vec![new_args[0], new_args[1], minus_one].into(); + } "__builtin_ia32_xrstor" | "__builtin_ia32_xrstor64" | "__builtin_ia32_xsavec" @@ -840,7 +874,7 @@ pub fn adjust_intrinsic_return_value<'a, 'gcc, 'tcx>( "__builtin_ia32_rdrand64_step" => { let random_number = args[0].dereference(None).to_rvalue(); let success_variable = - builder.current_func().new_local(None, return_value.get_type(), "success"); + builder.new_temp(builder.current_func(), None, return_value.get_type()); builder.llbb().add_assignment(None, success_variable, return_value); let field1 = builder.context.new_field(None, random_number.get_type(), "random_number"); @@ -854,6 +888,25 @@ pub fn adjust_intrinsic_return_value<'a, 'gcc, 'tcx>( &[random_number, success_variable.to_rvalue()], ); } + "__builtin_ia32_2intersectd128" + | "__builtin_ia32_2intersectq128" + | "__builtin_ia32_2intersectd256" + | "__builtin_ia32_2intersectq256" + | "__builtin_ia32_2intersectd512" + | "__builtin_ia32_2intersectq512" => { + let first_mask = args[0].dereference(None).to_rvalue(); + let second_mask = args[1].dereference(None).to_rvalue(); + let field1 = builder.context.new_field(None, first_mask.get_type(), "first_mask"); + let field2 = builder.context.new_field(None, second_mask.get_type(), "second_mask"); + let struct_type = + builder.context.new_struct_type(None, "vp2intersect_result", &[field1, field2]); + return_value = builder.context.new_struct_constructor( + None, + struct_type.as_type(), + None, + &[first_mask, second_mask], + ); + } "fma" => { let f16_type = builder.context.new_c_type(CType::Float16); return_value = builder.context.new_cast(None, return_value, f16_type); @@ -1182,6 +1235,9 @@ pub fn intrinsic<'gcc, 'tcx>(name: &str, cx: &CodegenCx<'gcc, 'tcx>) -> Function "llvm.x86.avx512.mask.vpshufbitqmb.512" => "__builtin_ia32_vpshufbitqmb512_mask", "llvm.x86.avx512.mask.vpshufbitqmb.256" => "__builtin_ia32_vpshufbitqmb256_mask", "llvm.x86.avx512.mask.vpshufbitqmb.128" => "__builtin_ia32_vpshufbitqmb128_mask", + "llvm.x86.avx512.vpshufbitqmb.512" => "__builtin_ia32_vpshufbitqmb512_mask", + "llvm.x86.avx512.vpshufbitqmb.256" => "__builtin_ia32_vpshufbitqmb256_mask", + "llvm.x86.avx512.vpshufbitqmb.128" => "__builtin_ia32_vpshufbitqmb128_mask", "llvm.x86.avx512.mask.ucmp.w.512" => "__builtin_ia32_ucmpw512_mask", "llvm.x86.avx512.mask.ucmp.w.256" => "__builtin_ia32_ucmpw256_mask", "llvm.x86.avx512.mask.ucmp.w.128" => "__builtin_ia32_ucmpw128_mask", @@ -1339,11 +1395,20 @@ pub fn intrinsic<'gcc, 'tcx>(name: &str, cx: &CodegenCx<'gcc, 'tcx>) -> Function "llvm.x86.avx512bf16.cvtne2ps2bf16.128" => "__builtin_ia32_cvtne2ps2bf16_v8bf", "llvm.x86.avx512bf16.cvtne2ps2bf16.256" => "__builtin_ia32_cvtne2ps2bf16_v16bf", "llvm.x86.avx512bf16.cvtne2ps2bf16.512" => "__builtin_ia32_cvtne2ps2bf16_v32bf", + "llvm.x86.vcvtneps2bf16128" => "__builtin_ia32_cvtneps2bf16_v4sf", + "llvm.x86.vcvtneps2bf16256" => "__builtin_ia32_cvtneps2bf16_v8sf", + "llvm.x86.avx512bf16.mask.cvtneps2bf16.128" => "__builtin_ia32_cvtneps2bf16_v4sf_mask", "llvm.x86.avx512bf16.cvtneps2bf16.256" => "__builtin_ia32_cvtneps2bf16_v8sf", "llvm.x86.avx512bf16.cvtneps2bf16.512" => "__builtin_ia32_cvtneps2bf16_v16sf", "llvm.x86.avx512bf16.dpbf16ps.128" => "__builtin_ia32_dpbf16ps_v4sf", "llvm.x86.avx512bf16.dpbf16ps.256" => "__builtin_ia32_dpbf16ps_v8sf", "llvm.x86.avx512bf16.dpbf16ps.512" => "__builtin_ia32_dpbf16ps_v16sf", + "llvm.x86.avx512.vp2intersect.d.128" => "__builtin_ia32_2intersectd128", + "llvm.x86.avx512.vp2intersect.q.128" => "__builtin_ia32_2intersectq128", + "llvm.x86.avx512.vp2intersect.d.256" => "__builtin_ia32_2intersectd256", + "llvm.x86.avx512.vp2intersect.q.256" => "__builtin_ia32_2intersectq256", + "llvm.x86.avx512.vp2intersect.d.512" => "__builtin_ia32_2intersectd512", + "llvm.x86.avx512.vp2intersect.q.512" => "__builtin_ia32_2intersectq512", "llvm.x86.pclmulqdq.512" => "__builtin_ia32_vpclmulqdq_v8di", "llvm.x86.pclmulqdq.256" => "__builtin_ia32_vpclmulqdq_v4di", "llvm.x86.avx512.pmulhu.w.512" => "__builtin_ia32_pmulhuw512_mask", @@ -1577,38 +1642,79 @@ pub fn intrinsic<'gcc, 'tcx>(name: &str, cx: &CodegenCx<'gcc, 'tcx>) -> Function "llvm.x86.avx512.uitofp.round.v4f64.v4i64" => "__builtin_ia32_cvtuqq2pd256_mask", "llvm.x86.avx512.uitofp.round.v8f32.v8i64" => "__builtin_ia32_cvtuqq2ps512_mask", "llvm.x86.avx512.uitofp.round.v4f32.v4i64" => "__builtin_ia32_cvtuqq2ps256_mask", + "llvm.x86.avx512fp16.fpclass.ph.128" => "__builtin_ia32_fpclassph128_mask", + "llvm.x86.avx512fp16.mask.cmp.ph.128" => "__builtin_ia32_cmpph128_mask", + "llvm.x86.avx512fp16.fpclass.ph.256" => "__builtin_ia32_fpclassph256_mask", + "llvm.x86.avx512fp16.fpclass.ph.512" => "__builtin_ia32_fpclassph512_mask", + "llvm.x86.avx512fp16.mask.cmp.ph.256" => "__builtin_ia32_cmpph256_mask", + "llvm.x86.avx512fp16.mask.cmp.ph.512" => "__builtin_ia32_cmpph512_mask_round", + "llvm.x86.avx512.fpclass.pd.128" => "__builtin_ia32_fpclasspd128_mask", + "llvm.x86.avx512.fpclass.ps.128" => "__builtin_ia32_fpclassps128_mask", + "llvm.x86.avx512.fpclass.pd.256" => "__builtin_ia32_fpclasspd256_mask", + "llvm.x86.avx512.fpclass.ps.256" => "__builtin_ia32_fpclassps256_mask", + "llvm.x86.avx512.fpclass.pd.512" => "__builtin_ia32_fpclasspd512_mask", + "llvm.x86.avx512.fpclass.ps.512" => "__builtin_ia32_fpclassps512_mask", // FIXME: support the tile builtins: "llvm.x86.ldtilecfg" => "__builtin_trap", "llvm.x86.sttilecfg" => "__builtin_trap", "llvm.x86.tileloadd64" => "__builtin_trap", + "llvm.x86.tileloadd64.internal" => "__builtin_trap", "llvm.x86.tilerelease" => "__builtin_trap", "llvm.x86.tilestored64" => "__builtin_trap", + "llvm.x86.tilestored64.internal" => "__builtin_trap", "llvm.x86.tileloaddrs64" => "__builtin_trap", + "llvm.x86.tileloaddrs64.internal" => "__builtin_trap", "llvm.x86.tileloaddt164" => "__builtin_trap", + "llvm.x86.tileloaddt164.internal" => "__builtin_trap", "llvm.x86.tileloaddrst164" => "__builtin_trap", + "llvm.x86.tileloaddrst164.internal" => "__builtin_trap", "llvm.x86.tilezero" => "__builtin_trap", + "llvm.x86.tilezero.internal" => "__builtin_trap", "llvm.x86.tilemovrow" => "__builtin_trap", + "llvm.x86.tilemovrow.internal" => "__builtin_trap", "llvm.x86.tilemovrowi" => "__builtin_trap", "llvm.x86.tdpbhf8ps" => "__builtin_trap", + "llvm.x86.tdpbhf8ps.internal" => "__builtin_trap", "llvm.x86.tdphbf8ps" => "__builtin_trap", + "llvm.x86.tdphbf8ps.internal" => "__builtin_trap", "llvm.x86.tdpbf8ps" => "__builtin_trap", + "llvm.x86.tdpbf8ps.internal" => "__builtin_trap", "llvm.x86.tdphf8ps" => "__builtin_trap", + "llvm.x86.tdphf8ps.internal" => "__builtin_trap", "llvm.x86.tdpbf16ps" => "__builtin_trap", + "llvm.x86.tdpbf16ps.internal" => "__builtin_trap", "llvm.x86.tdpbssd" => "__builtin_trap", + "llvm.x86.tdpbssd.internal" => "__builtin_trap", "llvm.x86.tdpbsud" => "__builtin_trap", + "llvm.x86.tdpbsud.internal" => "__builtin_trap", "llvm.x86.tdpbusd" => "__builtin_trap", + "llvm.x86.tdpbusd.internal" => "__builtin_trap", "llvm.x86.tdpbuud" => "__builtin_trap", + "llvm.x86.tdpbuud.internal" => "__builtin_trap", "llvm.x86.tdpfp16ps" => "__builtin_trap", + "llvm.x86.tdpfp16ps.internal" => "__builtin_trap", "llvm.x86.tmmultf32ps" => "__builtin_trap", + "llvm.x86.tmmultf32ps.internal" => "__builtin_trap", "llvm.x86.tcvtrowps2phh" => "__builtin_trap", + "llvm.x86.tcvtrowps2phh.internal" => "__builtin_trap", "llvm.x86.tcvtrowps2phl" => "__builtin_trap", + "llvm.x86.tcvtrowps2phl.internal" => "__builtin_trap", "llvm.x86.tcvtrowd2ps" => "__builtin_trap", + "llvm.x86.tcvtrowd2ps.internal" => "__builtin_trap", "llvm.x86.tcvtrowd2psi" => "__builtin_trap", "llvm.x86.tcvtrowps2phhi" => "__builtin_trap", "llvm.x86.tcvtrowps2phli" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16h" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16h.internal" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16hi" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16l" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16l.internal" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16li" => "__builtin_trap", "llvm.x86.tcmmimfp16ps" => "__builtin_trap", + "llvm.x86.tcmmimfp16ps.internal" => "__builtin_trap", "llvm.x86.tcmmrlfp16ps" => "__builtin_trap", + "llvm.x86.tcmmrlfp16ps.internal" => "__builtin_trap", // NOTE: this file is generated by https://github.com/GuillaumeGomez/llvmint/blob/master/generate_list.py _ => map_arch_intrinsic(name), diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index 41fa7b3f9f162..4d2590ac81e41 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -4,7 +4,7 @@ mod simd; #[cfg(feature = "master")] use std::iter; -use gccjit::{ComparisonOp, Function, FunctionType, RValue, ToRValue, Type, UnaryOp}; +use gccjit::{CType, ComparisonOp, Function, FunctionType, RValue, ToRValue, Type, UnaryOp}; use rustc_abi::{Align, BackendRepr, HasDataLayout, WrappingRange}; use rustc_codegen_ssa::base::wants_msvc_seh; use rustc_codegen_ssa::common::IntPredicate; @@ -24,6 +24,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_middle::ty::layout::FnAbiOf; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, Instance, Ty}; +use rustc_session::config::OptLevel; use rustc_span::{Span, Symbol, bug, span_bug, sym}; use rustc_target::callconv::{ArgAbi, PassMode}; @@ -80,7 +81,6 @@ fn get_simple_intrinsic<'gcc, 'tcx>( sym::floorf64 => "floor", sym::ceilf32 => "ceilf", sym::ceilf64 => "ceil", - sym::powf128 => return float_intrinsic(cx, cx.type_f128(), "powf128"), sym::truncf32 => "truncf", sym::truncf64 => "trunc", // We match the LLVM backend and lower this to `rint`. @@ -88,7 +88,6 @@ fn get_simple_intrinsic<'gcc, 'tcx>( sym::round_ties_even_f64 => "rint", sym::roundf32 => "roundf", sym::roundf64 => "round", - sym::abort => "abort", _ => return None, }; Some(cx.context.get_builtin_function(gcc_name)) @@ -180,14 +179,11 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc let simple = get_simple_intrinsic(self, name); let value = match name { - _ if simple.is_some() => { - let func = simple.expect("simple intrinsic function"); - self.cx.context.new_call( - self.location, - func, - &args.iter().map(|arg| arg.immediate()).collect::>(), - ) - } + _ if let Some(func) = simple => self.cx.context.new_call( + self.location, + func, + &args.iter().map(|arg| arg.immediate()).collect::>(), + ), // FIXME(antoyo): We can probably remove these and use the fallback intrinsic implementation. sym::minimumf32 | sym::minimumf64 | sym::maximumf32 | sym::maximumf64 => { let (ty, func_name) = match name { @@ -322,7 +318,9 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc unimplemented!(); } sym::va_arg => { - unimplemented!(); + let va_list = args[0].immediate(); + let gcc_type = self.immediate_backend_type(result.layout); + self.va_arg(va_list, gcc_type) } sym::volatile_load | sym::unaligned_volatile_load => { @@ -611,7 +609,7 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc self.on_stack_function_params.borrow_mut().insert(func, FxHashSet::default()); - crate::attributes::from_fn_attrs(self, func, instance); + crate::attributes::from_fn_attrs(self, func, instance, None); func }; @@ -664,16 +662,26 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc } fn abort(&mut self) { - let func = self.context.get_builtin_function("abort"); - let func: RValue<'gcc> = unsafe { std::mem::transmute(func) }; - self.call(self.type_void(), None, None, func, ReturnSlot::Direct, &[], None, None); + let func = self.context.get_builtin_function("__builtin_trap"); + self.block.add_eval(self.location, self.context.new_call(self.location, func, &[])); } fn assume(&mut self, value: Self::Value) { - // FIXME(antoyo): switch to assume when it exists. - // Or use something like this: - // #define __assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0) - self.expect(value, true); + // libgccjit currently has no direct equivalent of LLVM's `llvm.assume`, + // so use the idiom `if (!cond) __builtin_unreachable()`. + // FIXME: this should use IFN_ASSUME when we have internal functions in + // libgccjit. + if self.sess().opts.optimize == OptLevel::No { + return; + } + let then_block = self.append_sibling_block("assume_holds"); + let unreachable_block = self.append_sibling_block("assume_violated"); + self.block.end_with_conditional(self.location, value, then_block, unreachable_block); + + self.switch_to_block(unreachable_block); + self.unreachable(); + + self.switch_to_block(then_block); } fn expect(&mut self, cond: Self::Value, _expected: bool) -> Self::Value { @@ -691,8 +699,18 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc self.context.new_rvalue_from_int(self.int_type, 0) } - fn va_start(&mut self, _va_list: RValue<'gcc>) { - unimplemented!(); + fn va_start(&mut self, va_list: RValue<'gcc>) { + let func = self.context.get_builtin_function("__builtin_va_start"); + + let va_list_type = self.context.new_c_type(CType::VaList); + let va_list = self.context.new_cast(self.location, va_list, va_list_type.make_pointer()); + + // Pre-C23 requires that the last "normal" argument was passed to va_start. + // Just pass 0, this appears to be handled correctly. + let last_normal_arg = self.context.new_rvalue_from_int(self.int_type, 0); + + let call = self.context.new_call(self.location, func, &[va_list, last_normal_arg]); + self.block.add_eval(self.location, call); } fn retag_reg(&mut self, _ptr: Self::Value, _info: &RetagInfo) -> Self::Value { @@ -950,7 +968,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let else_block = func.new_block("else"); let after_block = func.new_block("after"); - let result = func.new_local(None, self.u32_type, "zeros"); + let result = self.new_temp(func, None, self.u32_type); let zero = self.cx.gcc_zero(arg.get_type()); let cond = self.gcc_icmp(IntPredicate::IntEQ, arg, zero); self.llbb().end_with_conditional(None, cond, then_block, else_block); @@ -1031,7 +1049,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { // else call it on the 64 high bits and add 64. In the else case, 64 high bits can't be 0 // because arg is not 0. - let result = self.current_func().new_local(None, result_type, "count_zeroes_results"); + let result = self.new_temp(self.current_func(), None, result_type); let cz_then_block = self.current_func().new_block("cz_then"); let cz_else_block = self.current_func().new_block("cz_else"); @@ -1146,8 +1164,8 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let loop_tail = func.new_block("tail"); let counter_type = self.int_type; - let counter = self.current_func().new_local(None, counter_type, "popcount_counter"); - let val = self.current_func().new_local(None, value_type, "popcount_value"); + let counter = self.new_temp(self.current_func(), None, counter_type); + let val = self.new_temp(self.current_func(), None, value_type); let zero = self.gcc_zero(counter_type); self.llbb().add_assignment(self.location, counter, zero); self.llbb().add_assignment(self.location, val, value); diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/old_archs.rs b/compiler/rustc_codegen_gcc/src/intrinsic/old_archs.rs index 8d3e3487b5cb4..1aac52c28d220 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/old_archs.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/old_archs.rs @@ -1240,6 +1240,10 @@ pub(crate) fn old_archs(arch: &str, name: &str) -> ArchCheckResult { "avx512.vbroadcast.sd.pd.512" => "__builtin_ia32_vbroadcastsd_pd512", "avx512.vbroadcast.ss.512" => "__builtin_ia32_vbroadcastss512", "avx512.vbroadcast.ss.ps.512" => "__builtin_ia32_vbroadcastss_ps512", + "bmi.pdep.32" => "__builtin_ia32_pdep_si", + "bmi.pdep.64" => "__builtin_ia32_pdep_di", + "bmi.pext.32" => "__builtin_ia32_pext_si", + "bmi.pext.64" => "__builtin_ia32_pext_di", "fma.mask.vfmadd.pd.512" => "__builtin_ia32_vfmaddpd512_mask", "fma.mask.vfmadd.ps.512" => "__builtin_ia32_vfmaddps512_mask", "fma.mask.vfmaddsub.pd.512" => "__builtin_ia32_vfmaddsubpd512_mask", diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs index 1416f4eec9c4a..bb32a85f193ea 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs @@ -11,13 +11,13 @@ use rustc_codegen_ssa::diagnostics::ExpectedPointerMutability; use rustc_codegen_ssa::diagnostics::InvalidMonomorphization; use rustc_codegen_ssa::mir::operand::OperandRef; use rustc_codegen_ssa::mir::place::PlaceRef; -use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods}; +use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods, LayoutTypeCodegenMethods}; #[cfg(feature = "master")] use rustc_hir as hir; use rustc_middle::mir::BinOp; -use rustc_middle::ty::layout::HasTyCtxt; +use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf}; use rustc_middle::ty::{self, Ty}; -use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; +use rustc_span::{ErrorGuaranteed, Span, Symbol, span_bug, sym}; use crate::builder::Builder; #[cfg(not(feature = "master"))] @@ -655,6 +655,39 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &values)); } + if name == sym::simd_arith_offset { + // This also checks that the first operand is a ptr type. + let pointee = in_elem.builtin_deref(true).unwrap_or_else(|| { + span_bug!(span, "must be called with a vector of pointer types as first argument") + }); + let layout = bx.layout_of(pointee); + // The second argument must be a ptr-sized integer. + // (We don't care about the signedness, this is wrapping anyway.) + let (_, offsets_elem) = args[1].layout.ty.simd_size_and_type(bx.tcx()); + if !matches!(offsets_elem.kind(), ty::Int(ty::IntTy::Isize) | ty::Uint(ty::UintTy::Usize)) { + span_bug!( + span, + "must be called with a vector of pointer-sized integers as second argument" + ); + } + + let pointee_type = bx.backend_type(layout); + let pointers = args[0].immediate(); + let offsets = args[1].immediate(); + let elem_type = llret_ty.dyncast_vector().expect("vector return type").get_element_type(); + let values: Vec<_> = (0..in_len) + .map(|i| { + let index = bx.context.new_rvalue_from_long(bx.usize_type, i as _); + let pointer = bx.extract_element(pointers, index); + let offset = bx.extract_element(offsets, index); + let pointer = bx.gep(pointee_type, pointer, &[offset]); + // GCC has no pointer vectors, so the lanes are `usize`. + bx.ptrtoint(pointer, elem_type) + }) + .collect(); + return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &values)); + } + #[cfg(feature = "master")] if name == sym::simd_cast || name == sym::simd_as { require_simd!(ret_ty, InvalidMonomorphization::SimdReturn { span, name, ty: ret_ty }); @@ -675,20 +708,28 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return Ok(args[0].immediate()); } + #[derive(Copy, Clone)] + enum Sign { + Unsigned, + Signed, + } + use Sign::*; + enum Style { Float, - Int, + Int(Sign), Unsupported, } let in_style = match *in_elem.kind() { - ty::Int(_) | ty::Uint(_) => Style::Int, + ty::Int(_) => Style::Int(Signed), + ty::Uint(_) => Style::Int(Unsigned), ty::Float(_) => Style::Float, _ => Style::Unsupported, }; - let out_style = match *out_elem.kind() { - ty::Int(_) | ty::Uint(_) => Style::Int, + ty::Int(_) => Style::Int(Signed), + ty::Uint(_) => Style::Int(Unsigned), ty::Float(_) => Style::Float, _ => Style::Unsupported, }; @@ -707,6 +748,19 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( } ); } + (Style::Float, Style::Int(sign)) if name == sym::simd_as => { + let vector = args[0].immediate(); + let elem_type = + llret_ty.dyncast_vector().expect("vector return type").get_element_type(); + let values: Vec<_> = (0..in_len) + .map(|i| { + let index = bx.context.new_rvalue_from_long(bx.usize_type, i as _); + let value = bx.extract_element(vector, index); + bx.cast_float_to_int(matches!(sign, Sign::Signed), value, elem_type) + }) + .collect(); + return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &values)); + } _ => return Ok(bx.context.convert_vector(None, args[0].immediate(), llret_ty)), } } @@ -1310,32 +1364,28 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( (true, false) => { // FIXME(antoyo): dyncast_vector should not require a call to unqualified. let arg_type = lhs.get_type().unqualified(); - // FIXME(antoyo): this uses the same algorithm from saturating add, but add the - // negative of the right operand. Find a proper subtraction algorithm. - let rhs = bx.context.new_unary_op(None, UnaryOp::Minus, arg_type, rhs); - // FIXME(antoyo): convert lhs and rhs to unsigned. - let sum = lhs + rhs; + let difference = lhs - rhs; let vector_type = arg_type.dyncast_vector().expect("vector type"); let unit = vector_type.get_num_units(); let a = bx.context.new_rvalue_from_int(elem_ty, ((elem_width as i32) << 3) - 1); let width = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![a; unit]); + // The subtraction overflows when the operands have different signs and the result + // has a different sign than the left operand. let xor1 = lhs ^ rhs; - let xor2 = lhs ^ sum; - let and = - bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, xor1) & xor2; - let mask = and >> width; + let xor2 = lhs ^ difference; + let mask = (xor1 & xor2) >> width; let one = bx.context.new_rvalue_one(elem_ty); let ones = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![one; unit]); let shift1 = ones << width; - let shift2 = sum >> width; + let shift2 = difference >> width; let mask_min = shift1 ^ shift2; - let and1 = - bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, mask) & sum; + let and1 = bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, mask) + & difference; let and2 = mask & mask_min; and1 + and2 diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index d7a3ef3b4a6c5..695bca1d69ec1 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -74,9 +74,9 @@ use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::Arc; -use gccjit::{CType, Context, OptimizationLevel}; #[cfg(feature = "master")] -use gccjit::{TargetInfo, Version}; +use gccjit::TargetInfo; +use gccjit::{CType, Context, OptimizationLevel}; use rustc_ast::expand::allocator::AllocatorMethod; use rustc_codegen_ssa::back::lto::ThinModule; use rustc_codegen_ssa::back::write::{ @@ -94,7 +94,7 @@ use rustc_middle::ty::TyCtxt; use rustc_session::config::{OptLevel, OutputFilenames}; use rustc_session::{CodegenBackendInit, EarlySession, IncrCompSession, Session}; use rustc_span::{Symbol, sym}; -use rustc_target::spec::{Arch, RelocModel}; +use rustc_target::spec::{RelocModel, TargetTuple}; use tempfile::TempDir; use crate::back::lto::ModuleBuffer; @@ -176,15 +176,21 @@ impl CodegenBackend for GccCodegenBackend { } fn init(&mut self, sess: &EarlySession) -> CodegenBackendInit { - fn file_path(sysroot_path: &Path, sess: &EarlySession) -> PathBuf { - let rustlib_path = - rustc_target::relative_target_rustlib_path(sysroot_path, &sess.host.llvm_target); - sysroot_path - .join(rustlib_path) - .join("codegen-backends") - .join("lib") - .join(sess.target.llvm_target.as_ref()) - .join("libgccjit.so") + fn file_paths(sysroot_path: &Path, sess: &EarlySession) -> Vec { + let rustlib_path = rustc_target::relative_target_rustlib_path( + sysroot_path, + rustc_session::config::host_tuple(), + ); + let lib_path = sysroot_path.join(rustlib_path).join("codegen-backends").join("lib"); + let rust_target_path = + lib_path.join(sess.opts.target_triple.tuple()).join("libgccjit.so"); + let mut paths = vec![rust_target_path]; + if matches!(sess.opts.target_triple, TargetTuple::TargetJson { .. }) { + let llvm_target_path = + lib_path.join(sess.target.llvm_target.as_ref()).join("libgccjit.so"); + paths.push(llvm_target_path); + } + paths } let global_backend_features = gcc_util::global_gcc_features(sess); @@ -192,21 +198,24 @@ impl CodegenBackend for GccCodegenBackend { // We use all_paths() instead of only path() in case the path specified by --sysroot is // invalid. // This is the case for instance in Rust for Linux where they specify --sysroot=/dev/null. - for path in sess.opts.sysroot.all_paths() { - let libgccjit_target_lib_file = file_path(path, sess); - if let Ok(true) = fs::exists(&libgccjit_target_lib_file) { - load_libgccjit_if_needed(&libgccjit_target_lib_file); - break; + 'sysroot: for path in sess.opts.sysroot.all_paths() { + for libgccjit_target_lib_file in file_paths(path, sess) { + if let Ok(true) = fs::exists(&libgccjit_target_lib_file) { + load_libgccjit_if_needed(&libgccjit_target_lib_file); + break 'sysroot; + } } } if !gccjit::is_loaded() { let mut paths = vec![]; for path in sess.opts.sysroot.all_paths() { - let libgccjit_target_lib_file = file_path(path, sess); - paths.push(libgccjit_target_lib_file); + for libgccjit_target_lib_file in file_paths(path, sess) { + paths.push(libgccjit_target_lib_file); + } } + paths.dedup(); panic!("Could not load libgccjit.so. Attempted paths: {:#?}", paths); } @@ -259,7 +268,7 @@ impl CodegenBackend for GccCodegenBackend { } fn target_cpu(&self, sess: &Session) -> String { - target_cpu(sess).to_owned() + target_cpu(sess).into_owned() } fn codegen_crate(&self, tcx: TyCtxt<'_>) -> Box { @@ -285,27 +294,6 @@ impl CodegenBackend for GccCodegenBackend { } } -fn new_context<'gcc, 'tcx>(tcx: TyCtxt<'tcx>) -> Context<'gcc> { - let context = Context::default(); - if matches!(tcx.sess.target.arch, Arch::X86 | Arch::X86_64) { - context.add_command_line_option("-masm=intel"); - } - #[cfg(feature = "master")] - { - context.set_special_chars_allowed_in_func_names("$.*"); - let version = Version::get(); - let version = format!("{}.{}.{}", version.major, version.minor, version.patch); - context.set_output_ident(&format!( - "rustc version {} with libgccjit {}", - rustc_interface::util::rustc_version_str().unwrap_or("unknown version"), - version, - )); - } - // FIXME(antoyo): check if this should only be added when using -Cforce-unwind-tables=n. - context.add_command_line_option("-fno-asynchronous-unwind-tables"); - context -} - impl ExtraBackendMethods for GccCodegenBackend { type Module = GccContext; @@ -316,7 +304,7 @@ impl ExtraBackendMethods for GccCodegenBackend { methods: &[AllocatorMethod], ) -> Self::Module { let mut mods = GccContext { - context: Arc::new(SyncContext::new(new_context(tcx))), + context: Arc::new(SyncContext::new(gcc_util::new_context(tcx.sess))), relocation_model: tcx.sess.relocation_model(), lto_mode: LtoMode::None, lto_supported: self.config().lto_supported, @@ -409,7 +397,7 @@ impl WriteBackendMethods for GccCodegenBackend { each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, ) -> CompiledModule { - back::lto::run_fat(cgcx, &sess.prof, shared_emitter, each_linked_rlib_for_lto, modules) + back::lto::run_fat(sess, cgcx, shared_emitter, each_linked_rlib_for_lto, modules) } fn run_thin_lto( diff --git a/compiler/rustc_codegen_gcc/src/mono_item.rs b/compiler/rustc_codegen_gcc/src/mono_item.rs index fc92cc3d5c7a9..d8170fbb085a7 100644 --- a/compiler/rustc_codegen_gcc/src/mono_item.rs +++ b/compiler/rustc_codegen_gcc/src/mono_item.rs @@ -1,15 +1,17 @@ +use gccjit::Function; #[cfg(feature = "master")] -use gccjit::{FnAttribute, VarAttribute}; +use gccjit::{FnAttribute, GlobalKind, ToRValue, Type, VarAttribute}; use rustc_codegen_ssa::traits::PreDefineCodegenMethods; use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; -use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; +use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc_middle::mono::Visibility; use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv, LayoutOf}; use rustc_middle::ty::{self, Instance, TypeVisitableExt}; use rustc_span::bug; +use crate::consts::const_alloc_type; use crate::context::CodegenCx; use crate::type_of::LayoutGccExt; use crate::{attributes, base}; @@ -19,25 +21,59 @@ impl<'gcc, 'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { fn predefine_static( &mut self, def_id: DefId, - _linkage: Linkage, + linkage: Linkage, visibility: Visibility, - symbol_name: &str, + global_name: &str, ) { let attrs = self.tcx.codegen_fn_attrs(def_id); let instance = Instance::mono(self.tcx, def_id); - let DefKind::Static { nested, .. } = self.tcx.def_kind(def_id) else { bug!() }; - // Nested statics do not have a type, so pick a dummy type and let `codegen_static` figure out - // the gcc type from the actual evaluated initializer. - let ty = - if nested { self.tcx.types.unit } else { instance.ty(self.tcx, self.typing_env()) }; - let gcc_type = self.layout_of(ty).gcc_type(self); + // Declare the global with the type its initializer will have, so that `codegen_static` + // never has to retype it afterwards. The initializer is lowered as a packed struct of byte + // runs and relocations, which almost never matches the layout type. + let gcc_type = match self.tcx.eval_static_initializer(def_id) { + Ok(alloc) => const_alloc_type(self, alloc), + // The initializer failed to evaluate; `codegen_static` bails out on it too, so this + // type is never used to hold one. + Err(_) => { + let DefKind::Static { nested, .. } = self.tcx.def_kind(def_id) else { bug!() }; + // Nested statics do not have a type, so pick a dummy one. + let ty = if nested { + self.tcx.types.unit + } else { + instance.ty(self.tcx, self.typing_env()) + }; + self.layout_of(ty).gcc_type(self) + } + }; let is_tls = attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL); - let global = self.define_global(symbol_name, gcc_type, is_tls, attrs.link_section); + let global_kind = base::global_linkage_to_gcc(linkage); + let global = + self.define_global(global_name, gcc_type, global_kind, is_tls, attrs.link_section); + #[cfg(feature = "master")] + { + // Visibility is meaningless on an internal global: GCC ignores the attribute and + // warns about it. + if !matches!(global_kind, GlobalKind::Internal) { + // If we're compiling the compiler-builtins crate, e.g., the equivalent of + // compiler-rt, then we want to implicitly compile everything with hidden + // visibility as we're going to link this object all over the place but + // don't want the symbols to get exported. + let visibility = if self.tcx.is_compiler_builtins(LOCAL_CRATE) { + gccjit::Visibility::Hidden + } else { + base::visibility_to_gcc(visibility) + }; + global.add_attribute(VarAttribute::Visibility(visibility)); + } + if let Some(attribute) = base::global_linkage_attribute(linkage) { + global.add_attribute(attribute); + } + } + #[cfg(feature = "master")] - global.add_attribute(VarAttribute::Visibility(base::visibility_to_gcc(visibility))); + self.add_static_aliases(gcc_type, global_name, attrs, &attrs.foreign_item_symbol_aliases); - // FIXME(antoyo): set linkage. self.instances.borrow_mut().insert(instance, global); } @@ -50,12 +86,114 @@ impl<'gcc, 'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { ) { assert!(!instance.args.has_infer()); + let attrs = self.tcx.codegen_instance_attrs(instance.def); + + let decl = + self.predefine_without_aliases(instance, &attrs, linkage, visibility, symbol_name); + + #[cfg(feature = "master")] + self.add_function_aliases(instance, decl, &attrs, &attrs.foreign_item_symbol_aliases); + + self.functions.borrow_mut().insert(symbol_name.to_string(), decl); + self.function_instances.borrow_mut().insert(instance, decl); + } +} + +impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { + #[cfg(feature = "master")] + fn add_static_aliases( + &self, + gcc_type: Type<'gcc>, + aliased: &str, + attrs: &CodegenFnAttrs, + aliases: &[(DefId, Linkage, Visibility)], + ) { + let is_tls = attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL); + + for &(alias, linkage, visibility) in aliases { + let instance = Instance::mono(self.tcx, alias); + let symbol_name = self.tcx.symbol_name(instance); + + let alias = self.declare_global( + symbol_name.name, + gcc_type, + GlobalKind::Imported, + is_tls, + attrs.link_section, + ); + alias.add_attribute(VarAttribute::Visibility(base::visibility_to_gcc(visibility))); + alias.add_attribute(VarAttribute::Alias(aliased)); + if linkage == Linkage::WeakAny { + alias.add_attribute(VarAttribute::Weak); + } + + // Add the alias name to the set of cached items, so there is no duplicate + // instance added to it during the normal `external static` codegen + let prev_entry = self.instances.borrow_mut().insert(instance, alias); + + // If there already was a previous entry, then `add_static_aliases` was called multiple times for the same `alias` + // which would result in incorrect codegen + assert!(prev_entry.is_none(), "An instance was already present for {instance:?}"); + } + } + + #[cfg(feature = "master")] + fn add_function_aliases( + &self, + aliased_instance: Instance<'tcx>, + aliased: Function<'gcc>, + attrs: &CodegenFnAttrs, + aliases: &[(DefId, Linkage, Visibility)], + ) { + for &(alias, linkage, visibility) in aliases { + let symbol_name = self.tcx.symbol_name(Instance::mono(self.tcx, alias)); + + // predefine another copy of the original instance + // with a new symbol name + let alias_fn_decl = self.predefine_without_aliases( + aliased_instance, + attrs, + linkage, + visibility, + symbol_name.name, + ); + + let block = alias_fn_decl.new_block("start"); + let nb_params = alias_fn_decl.get_param_count(); + let mut args = Vec::with_capacity(nb_params); + for idx in 0..nb_params { + args.push(alias_fn_decl.get_param(idx as _).to_rvalue()); + } + + let void_type = self.context.new_type::<()>(); + let call = self.context.new_call(None, aliased, &args); + if alias_fn_decl.get_return_type() == void_type { + block.add_eval(None, call); + block.end_with_void_return(None); + } else { + block.end_with_return(None, call); + } + } + } + + fn predefine_without_aliases( + &self, + instance: Instance<'tcx>, + _attrs: &CodegenFnAttrs, + linkage: Linkage, + visibility: Visibility, + symbol_name: &str, + ) -> Function<'gcc> { let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty()); self.linkage.set(base::linkage_to_gcc(linkage)); - let decl = self.declare_fn(symbol_name, fn_abi); - //let attrs = self.tcx.codegen_instance_attrs(instance.def); + let fn_decl = self.declare_fn(symbol_name, fn_abi); + + attributes::from_fn_attrs(self, fn_decl, instance, Some(fn_abi)); - attributes::from_fn_attrs(self, decl, instance); + #[cfg(feature = "master")] + if base::linkage_needs_weak_attribute(linkage) { + fn_decl.add_attribute(FnAttribute::Weak); + } // If we're compiling the compiler-builtins crate, e.g., the equivalent of // compiler-rt, then we want to implicitly compile everything with hidden @@ -63,17 +201,21 @@ impl<'gcc, 'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { // don't want the symbols to get exported. if linkage != Linkage::Internal && self.tcx.is_compiler_builtins(LOCAL_CRATE) { #[cfg(feature = "master")] - decl.add_attribute(FnAttribute::Visibility(gccjit::Visibility::Hidden)); + fn_decl.add_attribute(FnAttribute::Visibility(gccjit::Visibility::Hidden)); } else if visibility != Visibility::Default { #[cfg(feature = "master")] - decl.add_attribute(FnAttribute::Visibility(base::visibility_to_gcc(visibility))); + fn_decl.add_attribute(FnAttribute::Visibility(base::visibility_to_gcc(visibility))); + } + + #[cfg(feature = "master")] + if let Some(section) = _attrs.link_section { + fn_decl.add_attribute(FnAttribute::Section(section.as_str())); } - // FIXME(antoyo): call set_link_section() to allow initializing argc/argv. // FIXME(antoyo): set unique comdat. // FIXME(antoyo): use inline attribute from there in linkage.set() above. + // FIXME: Should we handle dso? - self.functions.borrow_mut().insert(symbol_name.to_string(), decl); - self.function_instances.borrow_mut().insert(instance, decl); + fn_decl } } diff --git a/compiler/rustc_codegen_gcc/src/type_.rs b/compiler/rustc_codegen_gcc/src/type_.rs index 27b0d2079e63e..55b37ee89b63f 100644 --- a/compiler/rustc_codegen_gcc/src/type_.rs +++ b/compiler/rustc_codegen_gcc/src/type_.rs @@ -1,8 +1,9 @@ #[cfg(feature = "master")] use std::convert::TryInto; +use std::mem::discriminant; #[cfg(feature = "master")] -use gccjit::CType; +use gccjit::{CType, TypeAttribute}; use gccjit::{RValue, Struct, Type}; use rustc_abi::{AddressSpace, Align, Integer, Size}; use rustc_codegen_ssa::common::TypeKind; @@ -102,9 +103,10 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.bool_type } - pub fn type_struct(&self, fields: &[Type<'gcc>], packed: bool) -> Type<'gcc> { - let types = fields.to_vec(); - if let Some(typ) = self.struct_types.borrow().get(fields) { + pub fn type_struct(&self, fields: &[Type<'gcc>], attributes: &[StructAttribute]) -> Type<'gcc> { + let key = + StructTypeKey { fields: fields.to_vec(), attributes: canonical_attributes(attributes) }; + if let Some(typ) = self.struct_types.borrow().get(&key) { return *typ; } let fields: Vec<_> = fields @@ -115,15 +117,91 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { }) .collect(); let typ = self.context.new_struct_type(None, "struct", &fields).as_type(); - if packed { - #[cfg(feature = "master")] - typ.set_packed(); - } - self.struct_types.borrow_mut().insert(types, typ); + apply_struct_attributes(typ, &key.attributes); + self.struct_types.borrow_mut().insert(key, typ); typ } } +/// An attribute that can be set on a GCC struct type. +/// +/// This mirrors the subset of `gccjit::TypeAttribute` that cg_gcc needs, rather than using it +/// directly, because it must exist without the `master` feature and because it is what +/// `StructTypeKey` is keyed on. Adding a variant here is therefore all it takes to make a new +/// attribute part of the cache key: there is no second place to remember to update. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum StructAttribute { + /// Alignment, in bytes. + Aligned(u32), + /// Lay the fields out without inserting padding between them. + Packed, +} + +/// Identifies an anonymous struct type in `CodegenCx::struct_types`. +/// +/// Two Rust types with the same field list can still need distinct GCC types — `struct { a: u64, +/// b: u64 }` with and without `repr(align(16))` produces the same fields — so every attribute has +/// to be part of the key. Holding them as one [`StructAttribute`] list rather than as separate +/// fields is what keeps that true when a new attribute is added. +#[derive(Clone, Eq, Hash, PartialEq)] +pub struct StructTypeKey<'gcc> { + pub fields: Vec>, + pub attributes: Vec, +} + +/// The attributes a GCC struct needs in order to match the Rust layout it is built from. +pub fn struct_attributes(packed: bool, align: Option) -> Vec { + let mut attributes = Vec::new(); + if packed { + attributes.push(StructAttribute::Packed); + } + if let Some(align) = align + && align.bytes() > 1 + && align.bytes() <= MAX_STRUCT_ALIGNMENT + { + attributes.push(StructAttribute::Aligned(align.bytes() as u32)); + } + attributes +} + +/// The largest alignment GCC accepts on a type, in bytes. +const MAX_STRUCT_ALIGNMENT: u64 = 1 << 28; + +/// Put an attribute list into a canonical form so that it can be used as a cache key. +/// +/// Without this, `[Packed, Aligned(8)]` and `[Aligned(8), Packed]` would hash differently and mint +/// two GCC types for what is one Rust type. +fn canonical_attributes(attributes: &[StructAttribute]) -> Vec { + let mut attributes = attributes.to_vec(); + attributes.sort_unstable(); + attributes.dedup(); + debug_assert!( + attributes.windows(2).all(|pair| discriminant(&pair[0]) != discriminant(&pair[1])), + "contradictory struct attributes: {attributes:?}" + ); + attributes +} + +/// Set `attributes` on the struct type `typ`. +/// +/// This is the only place allowed to call `Type::add_attribute`; `clippy.toml` forbids it +/// everywhere else. An attribute set on a type that `CodegenCx::struct_types` handed out would +/// change every other use of that type, so attributes have to be decided when the type is created +/// and be part of its cache key. Going through [`StructAttribute`] is what enforces that. +#[cfg(feature = "master")] +#[allow(clippy::disallowed_methods)] +pub fn apply_struct_attributes(typ: Type<'_>, attributes: &[StructAttribute]) { + for attribute in attributes { + typ.add_attribute(match *attribute { + StructAttribute::Aligned(align) => TypeAttribute::Aligned(align), + StructAttribute::Packed => TypeAttribute::Packed, + }); + } +} + +#[cfg(not(feature = "master"))] +pub fn apply_struct_attributes(_typ: Type<'_>, _attributes: &[StructAttribute]) {} + impl<'gcc, 'tcx> BaseTypeCodegenMethods for CodegenCx<'gcc, 'tcx> { fn type_i8(&self) -> Type<'gcc> { self.i8_type @@ -154,7 +232,7 @@ impl<'gcc, 'tcx> BaseTypeCodegenMethods for CodegenCx<'gcc, 'tcx> { if self.supports_f16_type { return self.context.new_c_type(CType::Float16); } - bug!("unsupported float width 16") + self.u16_type } fn type_f32(&self) -> Type<'gcc> { @@ -325,17 +403,19 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.type_array(self.type_from_integer(unit), size / unit_size) } - pub fn set_struct_body(&self, typ: Struct<'gcc>, fields: &[Type<'gcc>], packed: bool) { + pub fn set_struct_body( + &self, + typ: Struct<'gcc>, + fields: &[Type<'gcc>], + attributes: &[StructAttribute], + ) { let fields: Vec<_> = fields .iter() .enumerate() .map(|(index, field)| self.context.new_field(None, *field, format!("field_{}", index))) .collect(); typ.set_fields(None, &fields); - if packed { - #[cfg(feature = "master")] - typ.as_type().set_packed(); - } + apply_struct_attributes(typ.as_type(), &canonical_attributes(attributes)); } pub fn type_named_struct(&self, name: &str) -> Struct<'gcc> { diff --git a/compiler/rustc_codegen_gcc/src/type_of.rs b/compiler/rustc_codegen_gcc/src/type_of.rs index 31654d0e6be81..833ba9efe4efb 100644 --- a/compiler/rustc_codegen_gcc/src/type_of.rs +++ b/compiler/rustc_codegen_gcc/src/type_of.rs @@ -17,7 +17,7 @@ use rustc_target::callconv::{CastTarget, FnAbi}; use crate::abi::{FnAbiGcc, FnAbiGccExt, GccType}; use crate::context::CodegenCx; -use crate::type_::struct_fields; +use crate::type_::{struct_attributes, struct_fields}; impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { fn type_from_unsigned_integer(&self, i: Integer) -> Type<'gcc> { @@ -81,7 +81,7 @@ fn uncached_gcc_type<'gcc, 'tcx>( layout.scalar_pair_element_gcc_type(cx, 0), layout.scalar_pair_element_gcc_type(cx, 1), ], - false, + &struct_attributes(false, Some(layout.align.abi)), ); } BackendRepr::Memory { .. } => {} @@ -129,11 +129,12 @@ fn uncached_gcc_type<'gcc, 'tcx>( FieldsShape::Primitive | FieldsShape::Union(_) => { let fill = cx.type_padding_filler(layout.size, layout.align.abi); let packed = false; + let attributes = struct_attributes(packed, Some(layout.align.abi)); match name { - None => cx.type_struct(&[fill], packed), + None => cx.type_struct(&[fill], &attributes), Some(ref name) => { let gcc_type = cx.type_named_struct(name); - cx.set_struct_body(gcc_type, &[fill], packed); + cx.set_struct_body(gcc_type, &[fill], &attributes); gcc_type.as_type() } } @@ -142,7 +143,7 @@ fn uncached_gcc_type<'gcc, 'tcx>( FieldsShape::Arbitrary { .. } => match name { None => { let (gcc_fields, packed) = struct_fields(cx, layout); - cx.type_struct(&gcc_fields, packed) + cx.type_struct(&gcc_fields, &struct_attributes(packed, Some(layout.align.abi))) } Some(ref name) => { let gcc_type = cx.type_named_struct(name); @@ -240,7 +241,11 @@ impl<'tcx> LayoutGccExt<'tcx> for TyAndLayout<'tcx> { if let Some((deferred_ty, layout)) = defer { let (fields, packed) = struct_fields(cx, layout); - cx.set_struct_body(deferred_ty, &fields, packed); + cx.set_struct_body( + deferred_ty, + &fields, + &struct_attributes(packed, Some(layout.align.abi)), + ); } ty diff --git a/compiler/rustc_codegen_gcc/tests/asm/asm/comments.rs b/compiler/rustc_codegen_gcc/tests/asm/asm/comments.rs new file mode 100644 index 0000000000000..603bb014930c4 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/asm/comments.rs @@ -0,0 +1,12 @@ +//@ assembly-output: emit-asm +//@ only-x86_64 +// Check that comments in assembly get passed + +#![crate_type = "lib"] + +// CHECK-LABEL: "test_comments": +#[no_mangle] +pub fn test_comments() { + // CHECK: example comment + unsafe { core::arch::asm!("nop // example comment") }; +} diff --git a/compiler/rustc_codegen_gcc/tests/asm/bulk_memory_alignment.rs b/compiler/rustc_codegen_gcc/tests/asm/bulk_memory_alignment.rs new file mode 100644 index 0000000000000..6115467447104 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/bulk_memory_alignment.rs @@ -0,0 +1,45 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu +//@ compile-flags: -Copt-level=3 + +#![crate_type = "lib"] + +// The alignment reaches GCC's `memcpy`/`memset` expansion only through +// `__builtin_assume_aligned`; a pointer cast to an aligned type is stripped as a useless +// conversion. An over-aligned type therefore has to expand to aligned moves and a packed one +// to unaligned moves. The alignment is 64 so that the contrast holds whatever vector width +// the host picks. + +#[repr(align(64))] +pub struct Aligned([u8; 64]); + +#[repr(C, packed)] +pub struct Packed([u8; 64]); + +// CHECK-LABEL: "copy_aligned": +// CHECK: {{(v)?mov(dqa|aps)}} +#[no_mangle] +pub unsafe fn copy_aligned(destination: *mut Aligned, source: *const Aligned) { + core::ptr::copy_nonoverlapping(source, destination, 1); +} + +// CHECK-LABEL: "copy_packed": +// CHECK: {{(v)?mov(dqu|ups)}} +#[no_mangle] +pub unsafe fn copy_packed(destination: *mut Packed, source: *const Packed) { + core::ptr::copy_nonoverlapping(source, destination, 1); +} + +// CHECK-LABEL: "set_aligned": +// CHECK: {{(v)?mov(dqa|aps)}} +#[no_mangle] +pub unsafe fn set_aligned(destination: *mut Aligned) { + core::ptr::write_bytes(destination, 0, 1); +} + +// CHECK-LABEL: "set_packed": +// CHECK: {{(v)?mov(dqu|ups)}} +#[no_mangle] +pub unsafe fn set_packed(destination: *mut Packed) { + core::ptr::write_bytes(destination, 0, 1); +} diff --git a/compiler/rustc_codegen_gcc/tests/asm/naked-functions/x86_64-naked-fn-no-cet-prolog.rs b/compiler/rustc_codegen_gcc/tests/asm/naked-functions/x86_64-naked-fn-no-cet-prolog.rs new file mode 100644 index 0000000000000..81ee9b13b4eca --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/naked-functions/x86_64-naked-fn-no-cet-prolog.rs @@ -0,0 +1,24 @@ +//@ compile-flags: -C no-prepopulate-passes -Zcf-protection=full +//@ assembly-output: emit-asm +//@ needs-asm-support +//@ only-x86_64 + +#![crate_type = "lib"] + +use std::arch::naked_asm; + +// The problem at hand: Rust has adopted a fairly strict meaning for "naked functions", +// meaning "no prologue whatsoever, no, really, not one instruction." +// Unfortunately, x86's control-flow enforcement, specifically indirect branch protection, +// works by using an instruction for each possible landing site, +// and LLVM implements this via making sure of that. +#[no_mangle] +#[unsafe(naked)] +pub extern "sysv64" fn will_halt() -> ! { + // CHECK-NOT: endbr{{32|64}} + // CHECK: hlt + naked_asm!("hlt") +} + +// what about aarch64? +// "branch-protection"=false diff --git a/compiler/rustc_codegen_gcc/tests/asm/panic-no-unwind-no-uwtable.rs b/compiler/rustc_codegen_gcc/tests/asm/panic-no-unwind-no-uwtable.rs new file mode 100644 index 0000000000000..b51b173e9616e --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/panic-no-unwind-no-uwtable.rs @@ -0,0 +1,8 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu +//@ compile-flags: -C panic=unwind -C force-unwind-tables=n -Copt-level=3 + +#![crate_type = "lib"] + +// CHECK-NOT: .cfi_startproc +pub fn foo() {} diff --git a/compiler/rustc_codegen_gcc/tests/asm/used.rs b/compiler/rustc_codegen_gcc/tests/asm/used.rs new file mode 100644 index 0000000000000..deb0c69dc48fa --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/used.rs @@ -0,0 +1,14 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu + +#![feature(used_with_arg)] +#![crate_type = "lib"] + +// CHECK: .section .rodata.X,"a" +#[used(compiler)] +#[no_mangle] +pub static X: u32 = 12; +// CHECK: .section .rodata.Y,"aR" +#[used(linker)] +#[no_mangle] +pub static Y: u32 = 12; diff --git a/compiler/rustc_codegen_gcc/tests/asm/volatile_bulk_memory.rs b/compiler/rustc_codegen_gcc/tests/asm/volatile_bulk_memory.rs new file mode 100644 index 0000000000000..6233b8ac74c0e --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/volatile_bulk_memory.rs @@ -0,0 +1,37 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu +//@ compile-flags: -Copt-level=3 + +#![feature(core_intrinsics)] +#![crate_type = "lib"] + +use std::intrinsics::{ + volatile_copy_memory, volatile_copy_nonoverlapping_memory, volatile_set_memory, +}; + +// The buffers below are never read back, so the writes only survive because they are volatile. +// The functions are ordered alphabetically because that is the order they are emitted in. + +// CHECK-LABEL: "volatile_copy": +// CHECK: mov +#[no_mangle] +pub unsafe fn volatile_copy(source: *const u8) { + let mut buffer = [1u8; 64]; + volatile_copy_memory(buffer.as_mut_ptr(), source, 64); +} + +// CHECK-LABEL: "volatile_copy_nonoverlapping": +// CHECK: mov +#[no_mangle] +pub unsafe fn volatile_copy_nonoverlapping(source: *const u8) { + let mut buffer = [1u8; 64]; + volatile_copy_nonoverlapping_memory(buffer.as_mut_ptr(), source, 64); +} + +// CHECK-LABEL: "volatile_set": +// CHECK: mov +#[no_mangle] +pub unsafe fn volatile_set() { + let mut buffer = [1u8; 64]; + volatile_set_memory(buffer.as_mut_ptr(), 0, 64); +} diff --git a/compiler/rustc_codegen_gcc/tests/asm/x86_64-sse_crc.rs b/compiler/rustc_codegen_gcc/tests/asm/x86_64-sse_crc.rs new file mode 100644 index 0000000000000..bde58955a2146 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/x86_64-sse_crc.rs @@ -0,0 +1,12 @@ +//@ only-x86_64 +//@ assembly-output: emit-asm +//@ compile-flags: --crate-type staticlib -Ctarget-feature=+sse4.2 + +// CHECK-LABEL: banana +// CHECK: crc32 +#[no_mangle] +pub unsafe fn banana(v: u8) -> u32 { + use std::arch::x86_64::*; + let out = !0u32; + _mm_crc32_u8(out, v) +} diff --git a/compiler/rustc_codegen_gcc/tests/c/import_linkage.c b/compiler/rustc_codegen_gcc/tests/c/import_linkage.c new file mode 100644 index 0000000000000..f2beb9603d08b --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/c/import_linkage.c @@ -0,0 +1,17 @@ +/* The symbols that `tests/run/import_linkage.rs` imports with an explicit `#[linkage]`. + * + * Such an import is a pointer whose value is the address of the symbol, so what the Rust side + * reads back is `&value_*`, not the pointer stored in it. The distinct values make a mix-up + * visible. */ + +#include + +int32_t external_value = 1; +int32_t available_externally_value = 2; +int32_t linkonce_value = 3; +int32_t linkonce_odr_value = 4; +int32_t weak_value = 5; +int32_t weak_odr_value = 6; +int32_t common_value = 7; +int32_t extern_weak_value = 8; +int32_t internal_value = 9; diff --git a/compiler/rustc_codegen_gcc/tests/c/overaligned_byval_abi.c b/compiler/rustc_codegen_gcc/tests/c/overaligned_byval_abi.c new file mode 100644 index 0000000000000..826a104f185ea --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/c/overaligned_byval_abi.c @@ -0,0 +1,54 @@ +/* Reference side of `tests/run/overaligned_byval_abi.rs`, compiled by the real GCC. + * + * `Aligned` is an over-aligned aggregate passed by value ("byval"): the ABI places it in a stack + * slot aligned to its own alignment, not packed right after the preceding argument. cg_gcc used + * to build the GCC struct type from the field list alone, which dropped Rust's `repr(align(64))`, + * so it placed the argument at an offset nobody else agreed on. + * + * The two functions here check both directions: `c_take_both` is a GCC-built callee for a cg_gcc + * caller, and `c_call_rust` is a GCC-built caller for a cg_gcc callee. + * + * The checks are on the *values* received rather than on the address of the argument: which + * alignment the ABI gives a stack slot is target-specific, but caller and callee agreeing on it + * is not. A disagreement makes the arguments arrive as garbage. */ + +#include + +struct Big { + int64_t a, b, c; +}; + +struct __attribute__((aligned(64))) Aligned { + int32_t x; +}; + +/* Defined on the Rust side. */ +extern int32_t rust_take_both(struct Big first, struct Aligned second, struct Big third, + struct Aligned fourth); + +/* Called from Rust: checks what a cg_gcc caller passed. */ +int32_t c_take_both(struct Big first, struct Aligned second, struct Big third, + struct Aligned fourth) +{ + if (first.a != 1 || first.b != 2 || first.c != 3) + return 1; + if (second.x != 42) + return 2; + if (third.a != 4 || third.b != 5 || third.c != 6) + return 3; + if (fourth.x != 43) + return 4; + return 0; +} + +/* Called from Rust: passes the arguments the way the ABI says, for a cg_gcc callee to read. */ +int32_t c_call_rust(void) +{ + struct Big first = {1, 2, 3}; + struct Big third = {4, 5, 6}; + struct Aligned second, fourth; + + second.x = 42; + fourth.x = 43; + return rust_take_both(first, second, third, fourth); +} diff --git a/compiler/rustc_codegen_gcc/tests/c/static_linkage.c b/compiler/rustc_codegen_gcc/tests/c/static_linkage.c new file mode 100644 index 0000000000000..787e61f9cf105 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/c/static_linkage.c @@ -0,0 +1,37 @@ +/* Strong definitions of the statics that `tests/run/static_linkage.rs` also defines, but weakly. + * The linker has to keep these and drop the Rust ones; a backend that emits the Rust definitions + * as ordinary global symbols fails the link with a duplicate definition instead. + * + * `internal_static` is the opposite case: the Rust side keeps its own, and the two definitions + * coexist because the Rust one is local. */ + +#include + +int32_t weak_static = 1; +int32_t weak_odr_static = 2; +int32_t linkonce_static = 3; +int32_t linkonce_odr_static = 4; +int32_t common_static = 5; +int32_t internal_static = 200; + +/* `available_externally` promises the real definition lives elsewhere: a backend may read this one + * or emit an equivalent copy of the Rust initializer, so the two have to hold the same value. */ +int32_t available_externally_static = 7; + +/* Called from Rust, so that the reads also happen in a translation unit GCC compiled. */ +int32_t c_read_all(void) +{ + if (weak_static != 1) + return 11; + if (weak_odr_static != 2) + return 12; + if (linkonce_static != 3) + return 13; + if (linkonce_odr_static != 4) + return 14; + if (common_static != 5) + return 15; + if (internal_static != 200) + return 16; + return 0; +} diff --git a/compiler/rustc_codegen_gcc/tests/c/weak_function_linkage.c b/compiler/rustc_codegen_gcc/tests/c/weak_function_linkage.c new file mode 100644 index 0000000000000..25dcdedbcd950 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/c/weak_function_linkage.c @@ -0,0 +1,54 @@ +/* Strong definitions of the functions that `tests/run/weak_function_linkage.rs` also defines, but + * weakly. The linker has to keep these and drop the Rust ones. + * + * A backend that emits the Rust definitions as ordinary global symbols does not merely pick the + * wrong one: the link fails outright with a duplicate definition. */ + +#include + +int32_t weak_function(void) +{ + return 1; +} + +int32_t weak_odr_function(void) +{ + return 2; +} + +int32_t linkonce_function(void) +{ + return 3; +} + +int32_t linkonce_odr_function(void) +{ + return 4; +} + +int32_t weak_inline_function(void) +{ + return 8; +} + +/* `available_externally` promises the real definition lives elsewhere: a backend may call this one + * or emit an equivalent copy of the Rust body, so the two have to return the same value. */ +int32_t available_externally_function(void) +{ + return 7; +} + +/* Called from Rust, so that the calls also go through a caller that GCC compiled: a cg_gcc caller + * could inline the weak body it can see instead of calling the symbol. */ +int32_t c_call_all(void) +{ + if (weak_function() != 1) + return 11; + if (weak_odr_function() != 2) + return 12; + if (linkonce_function() != 3) + return 13; + if (linkonce_odr_function() != 4) + return 14; + return 0; +} diff --git a/compiler/rustc_codegen_gcc/tests/compile/asm_noreturn_call.rs b/compiler/rustc_codegen_gcc/tests/compile/asm_noreturn_call.rs new file mode 100644 index 0000000000000..c9238697d8097 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/compile/asm_noreturn_call.rs @@ -0,0 +1,15 @@ +// Compiler: + +// Regression test for https://github.com/rust-lang/rustc_codegen_gcc/issues/827 + +#![crate_type = "lib"] + +#[cfg(target_arch = "x86_64")] +pub type NoReturn = extern "sysv64" fn(&'static u8) -> !; + +#[cfg(target_arch = "x86_64")] +pub fn call_no_return(function: *const NoReturn) -> ! { + unsafe { + std::arch::asm!("call {}", in(reg) function, options(noreturn)); + } +} diff --git a/compiler/rustc_codegen_gcc/tests/compile/recursive_types.rs b/compiler/rustc_codegen_gcc/tests/compile/recursive_types.rs new file mode 100644 index 0000000000000..3c69074887f3e --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/compile/recursive_types.rs @@ -0,0 +1,22 @@ +// Compiler: + +// `Set` reaches itself by value through `*mut Root`, so a backend that emits `Root`'s +// fields with the still-incomplete `Set` type fails to compile this. + +#![crate_type = "lib"] + +#[repr(C)] +pub struct Set { + pub root: *mut Root, + pub first: usize, + pub second: usize, +} + +#[repr(C)] +pub struct Root { + pub default_set: Set, +} + +pub fn identity(set: Set) -> Set { + set +} diff --git a/compiler/rustc_codegen_gcc/tests/compile/x86_interrupt_first_arg_byval.rs b/compiler/rustc_codegen_gcc/tests/compile/x86_interrupt_first_arg_byval.rs new file mode 100644 index 0000000000000..4b6bbd48f7ad5 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/compile/x86_interrupt_first_arg_byval.rs @@ -0,0 +1,16 @@ +// Compiler: + +// Test that `x86-interrupt` functions whose first argument is passed by value +// emit pointer-shaped GCC parameters and compile with interrupt-safe target features. + +#![feature(abi_x86_interrupt)] +#![crate_type = "lib"] + +#[repr(C)] +pub struct Frame { + ip: u64, +} + +pub extern "x86-interrupt" fn scalar(_a: i64) {} + +pub extern "x86-interrupt" fn aggregate(_frame: Frame) {} diff --git a/compiler/rustc_codegen_gcc/tests/cpuid.def b/compiler/rustc_codegen_gcc/tests/cpuid.def new file mode 100644 index 0000000000000..05fe8e94a8282 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/cpuid.def @@ -0,0 +1,27 @@ +# Input => Output +# EAX ECX => EAX EBX ECX EDX +00000000 ******** => 00000024 756e6547 6c65746e 49656e69 #Processor ID and Manufacturer +00000001 ******** => 00400f10 00100800 7ffaf3ff bfebfbff +00000007 00000000 => 00000002 f3bfbfbf bac05ffe 03d54130 #Extended Features +00000007 00000001 => 98ee00bf 00000002 00000020 1d29cd3e +0000000d 00000000 => 000e02e7 00002b00 00002b00 00000000 #xcr0 +0000000d 00000001 => 0000001f 00000240 00000100 00000000 #Supervisor State +0000000d 00000002 => 00000100 00000240 00000000 00000000 +0000000d 00000005 => 00000040 00000440 00000000 00000000 #zmasks +0000000d 00000006 => 00000200 00000480 00000000 00000000 #zmmh +0000000d 00000007 => 00000400 00000680 00000000 00000000 #zmm +0000000d 00000011 => 00000040 00000ac0 00000002 00000000 #tileconfig +0000000d 00000012 => 00002000 00000b00 00000006 00000000 #tiles +0000000d 00000013 => 00000080 000003c0 00000000 00000000 #APX +00000019 ******** => 00000000 00000005 00000000 00000000 #Key Locker +0000001d 00000000 => 00000001 00000000 00000000 00000000 #AMX Tile +0000001d 00000001 => 04002000 00080040 00000010 00000000 #AMX Palette1 +0000001e 00000000 => 00000001 00004010 00000000 00000000 #AMX Tmul +0000001e 00000001 => 000001ff 00000000 00000000 00000000 +00000024 00000000 => 00000001 00070002 00000000 00000000 #AVX10 +00000024 00000001 => 00000000 00000000 00000004 00000000 +80000000 ******** => 80000004 00000000 00000000 00000000 +80000001 ******** => 00000000 00000000 00000121 2c100000 +80000002 ******** => 00000000 00000000 00000000 00000000 +80000003 ******** => 00000000 00000000 00000000 00000000 +80000004 ******** => 00000000 00000000 00000000 00000000 diff --git a/compiler/rustc_codegen_gcc/tests/failing-ice-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-ice-tests.txt index ff1b6f1489468..f9d6f99e7b42b 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-ice-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-ice-tests.txt @@ -10,7 +10,6 @@ tests/ui/simd/intrinsic/generic-arithmetic-saturating-2.rs tests/ui/simd/intrinsic/generic-arithmetic-2.rs tests/ui/panics/default-backtrace-ice.rs tests/ui/mir/lint/storage-live.rs -tests/ui/layout/valid_range_oob.rs tests/ui/higher-ranked/trait-bounds/future.rs tests/ui/consts/const-eval/const-eval-query-stack.rs tests/ui/simd/masked-load-store.rs @@ -28,13 +27,21 @@ tests/ui/lto/thin-lto-global-allocator.rs tests/ui/lto/msvc-imp-present.rs tests/ui/lto/dylib-works.rs tests/ui/lto/all-crates.rs -tests/ui/issues/issue-47364.rs +tests/ui/codegen/no-segfault-with-multiple-codegen-units.rs tests/ui/functions-closures/parallel-codegen-closures.rs tests/ui/sepcomp/sepcomp-unwind.rs tests/ui/extern/issue-64655-extern-rust-must-allow-unwind.rs tests/ui/extern/issue-64655-allow-unwind-when-calling-panic-directly.rs -tests/ui/unwind-no-uwtable.rs +tests/ui/panics/unwind-force-no-unwind-tables.rs tests/ui/delegation/fn-header.rs tests/ui/simd/intrinsic/generic-arithmetic-pass.rs -tests/ui/simd/masked-load-store.rs -tests/ui/rfcs/rfc-2632-const-trait-impl/effects/minicore.rs +tests/ui/codegen/unknown-llvm-intrinsic.rs +tests/ui/codegen/incorrect-llvm-intrinsic-signature.rs +tests/ui/codegen/incorrect-arch-intrinsic.rs +tests/ui/codegen/custom-target-invalid-llvm-target.rs +tests/ui/asm/x86_64/naked_asm_escape.rs +tests/ui/lto/debuginfo-lto-alloc.rs +tests/ui/codegen/normalization-overflow/recursion-issue-118590.rs +tests/ui/codegen/normalization-overflow/recursion-issue-122823.rs +tests/ui/codegen/normalization-overflow/recursion-issue-131342.rs +tests/ui/codegen/normalization-overflow/recursion-issue-92004.rs diff --git a/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt index 4c62c35a512c1..7527005321991 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt @@ -1,6 +1,5 @@ tests/ui/lto/debuginfo-lto-alloc.rs -tests/ui/panic-runtime/lto-unwind.rs -tests/ui/uninhabited/uninhabited-transparent-return-abi.rs -tests/ui/coroutine/panic-drops-resume.rs -tests/ui/coroutine/panic-drops.rs -tests/ui/coroutine/panic-safe.rs +tests/ui/extern/issue-64655-allow-unwind-when-calling-panic-directly.rs +tests/ui/extern/issue-64655-extern-rust-must-allow-unwind.rs +tests/ui/lto/thin-lto-inlines2.rs +tests/ui/lto/lto-thin-rustc-loads-linker-plugin.rs diff --git a/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt index 528ee1df9f583..d5297e069f72f 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt @@ -11,4 +11,5 @@ tests/run-make/foreign-exceptions/ tests/run-make/glibc-staticlib-args/ tests/run-make/lto-smoke-c/ tests/run-make/return-non-c-like-enum/ -tests/run-make/short-ice +tests/run-make/short-ice/ +tests/run-make/embed-source-dwarf/ diff --git a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt index e8a26a90890c1..7f96bfabedbea 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt @@ -1,113 +1,21 @@ tests/ui/asm/may_unwind.rs tests/ui/asm/x86_64/may_unwind.rs -tests/ui/drop/dynamic-drop-async.rs -tests/ui/cfg/cfg-panic-abort.rs tests/ui/intrinsics/panic-uninitialized-zeroed.rs -tests/ui/iterators/iter-sum-overflow-debug.rs -tests/ui/iterators/iter-sum-overflow-overflow-checks.rs -tests/ui/mir/mir_drop_order.rs -tests/ui/mir/mir_let_chains_drop_order.rs -tests/ui/mir/mir_match_guard_let_chains_drop_order.rs -tests/ui/panic-runtime/abort-link-to-unwinding-crates.rs -tests/ui/panic-runtime/abort.rs -tests/ui/panic-runtime/link-to-abort.rs -tests/ui/parser/unclosed-delimiter-in-dep.rs -tests/ui/consts/missing_span_in_backtrace.rs -tests/ui/drop/dynamic-drop.rs tests/ui/simd/issue-17170.rs tests/ui/simd/issue-39720.rs -tests/ui/drop/panic-during-drop-14875.rs -tests/ui/issues/issue-29948.rs tests/ui/process/println-with-broken-pipe.rs -tests/ui/lto/thin-lto-inlines2.rs -tests/ui/panic-runtime/lto-abort.rs -tests/ui/lto/lto-thin-rustc-loads-linker-plugin.rs -tests/ui/async-await/deep-futures-are-freeze.rs -tests/ui/coroutine/resume-after-return.rs -tests/ui/simd/masked-load-store.rs tests/ui/simd/repr_packed.rs -tests/ui/async-await/in-trait/dont-project-to-specializable-projection.rs -tests/ui/coroutine/unwind-abort-mix.rs -tests/ui/consts/issue-miri-1910.rs -tests/ui/consts/const_cmp_type_id.rs -tests/ui/consts/issue-94675.rs -tests/ui/traits/const-traits/const-drop-fail.rs -tests/ui/runtime/on-broken-pipe/child-processes.rs -tests/ui/sanitizer/cfi/assoc-ty-lifetime-issue-123053.rs -tests/ui/sanitizer/cfi/async-closures.rs -tests/ui/sanitizer/cfi/closures.rs -tests/ui/sanitizer/cfi/complex-receiver.rs -tests/ui/sanitizer/cfi/coroutine.rs -tests/ui/sanitizer/cfi/drop-in-place.rs -tests/ui/sanitizer/cfi/drop-no-principal.rs -tests/ui/sanitizer/cfi/fn-ptr.rs -tests/ui/sanitizer/cfi/self-ref.rs -tests/ui/sanitizer/cfi/supertraits.rs -tests/ui/sanitizer/cfi/virtual-auto.rs -tests/ui/sanitizer/cfi/sized-associated-ty.rs -tests/ui/sanitizer/cfi/can-reveal-opaques.rs -tests/ui/sanitizer/kcfi-mangling.rs -tests/ui/delegation/fn-header.rs -tests/ui/consts/const-eval/parse_ints.rs -tests/ui/simd/intrinsic/generic-as.rs -tests/ui/runtime/rt-explody-panic-payloads.rs -tests/ui/codegen/equal-pointers-unequal/as-cast/inline1.rs -tests/ui/codegen/equal-pointers-unequal/as-cast/inline2.rs -tests/ui/codegen/equal-pointers-unequal/as-cast/segfault.rs -tests/ui/codegen/equal-pointers-unequal/as-cast/zero.rs -tests/ui/codegen/equal-pointers-unequal/exposed-provenance/inline1.rs -tests/ui/codegen/equal-pointers-unequal/exposed-provenance/inline2.rs -tests/ui/codegen/equal-pointers-unequal/exposed-provenance/segfault.rs -tests/ui/codegen/equal-pointers-unequal/exposed-provenance/zero.rs -tests/ui/codegen/equal-pointers-unequal/strict-provenance/inline1.rs -tests/ui/codegen/equal-pointers-unequal/strict-provenance/inline2.rs -tests/ui/codegen/equal-pointers-unequal/strict-provenance/segfault.rs -tests/ui/codegen/equal-pointers-unequal/strict-provenance/zero.rs tests/ui/simd/simd-bitmask-notpow2.rs tests/ui/codegen/StackColoring-not-blowup-stack-issue-40883.rs tests/ui/numbers-arithmetic/u128-as-f32.rs tests/ui/process/nofile-limit.rs -tests/ui/linking/no-gc-encapsulation-symbols.rs -tests/ui/panics/unwind-force-no-unwind-tables.rs tests/ui/attributes/fn-align-dyn.rs tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs -tests/ui/explicit-tail-calls/recursion-etc.rs -tests/ui/explicit-tail-calls/indexer.rs -tests/ui/explicit-tail-calls/drop-order.rs -tests/ui/c-variadic/valid.rs -tests/ui/c-variadic/inherent-method.rs -tests/ui/c-variadic/trait-method.rs -tests/ui/explicit-tail-calls/become-cast-return.rs -tests/ui/explicit-tail-calls/become-indirect-return.rs -tests/ui/panics/panic-abort-backtrace-without-debuginfo.rs -tests/ui/sanitizer/kcfi-c-variadic.rs -tests/ui/sanitizer/kcfi/fn-trait-objects.rs tests/ui/statics/const_generics.rs -tests/ui/test-attrs/test-panic-while-printing.rs tests/ui/thir-print/offset_of.rs -tests/ui/iterators/rangefrom-overflow-debug.rs -tests/ui/iterators/rangefrom-overflow-overflow-checks.rs -tests/ui/iterators/iter-filter-count-debug-check.rs -tests/ui/eii/linking/codegen_single_crate.rs -tests/ui/eii/linking/codegen_cross_crate.rs -tests/ui/eii/default/local_crate.rs -tests/ui/eii/duplicate/multiple_impls.rs -tests/ui/eii/default/call_default.rs -tests/ui/eii/linking/same-symbol.rs -tests/ui/eii/privacy1.rs -tests/ui/eii/default/call_impl.rs -tests/ui/c-variadic/copy.rs tests/ui/asm/x86_64/global_asm_escape.rs tests/ui/lto/all-crates.rs -tests/ui/consts/const-eval/c-variadic.rs -tests/ui/eii/default/call_default_panics.rs -tests/ui/explicit-tail-calls/indirect.rs -tests/ui/traits/inheritance/self-in-supertype.rs -tests/ui/fmt/fmt_debug/shallow.rs -tests/ui/c-variadic/roundtrip.rs -tests/ui/eii/eii_impl_with_contract.rs -tests/ui/eii/static/cross_crate_decl.rs -tests/ui/eii/static/cross_crate_def.rs -tests/ui/eii/static/same_address.rs -tests/ui/eii/static/simple.rs -tests/ui/explicit-tail-calls/default-trait-method.rs +tests/ui/explicit-tail-calls/tailcc-no-signature-restriction.rs +tests/ui/abi/rust-tail-cc.rs +tests/ui/abi/rust-preserve-none-cc.rs +tests/ui/extern/extern-types-field-offset.rs diff --git a/compiler/rustc_codegen_gcc/tests/lang_tests.rs b/compiler/rustc_codegen_gcc/tests/lang_tests.rs index 6afd54e1c3fe0..7ec0ab877b025 100644 --- a/compiler/rustc_codegen_gcc/tests/lang_tests.rs +++ b/compiler/rustc_codegen_gcc/tests/lang_tests.rs @@ -7,6 +7,68 @@ use std::process::Command; use lang_tester::LangTester; use tempfile::TempDir; +/// Directory holding the C files that the `tests/run` tests can link against. +/// +/// A `tests/c/.c` is compiled by the real GCC and linked into `tests/run/.rs`. +const C_TESTS_DIR: &str = "tests/c"; + +/// The m68k cross toolchain is not on the default `PATH` in CI. +// FIXME(antoyo): find a better way to add the PATH necessary locally. +const M68K_TOOLCHAIN_DIR: &str = "/opt/m68k-unknown-linux-gnu/bin"; + +fn target_path(test_target: &Option) -> Option { + test_target.as_ref().map(|_| { + let env_path = std::env::var("PATH").unwrap_or_default(); + format!("{}:{}", M68K_TOOLCHAIN_DIR, env_path) + }) +} + +/// Compile every C file in `tests/c` to an object file in `objects_dir`. +fn compile_c_files(objects_dir: &Path, test_target: &Option) { + let c_tests_dir = Path::new(C_TESTS_DIR); + if !c_tests_dir.is_dir() { + return; + } + std::fs::create_dir_all(objects_dir).expect("create the directory for the C object files"); + + let compiler = match test_target { + Some(target) => format!("{}-gcc", target), + None => "gcc".to_string(), + }; + + for entry in std::fs::read_dir(c_tests_dir).expect("read the C tests directory") { + let source = entry.expect("directory entry").path(); + if source.extension().and_then(|extension| extension.to_str()) != Some("c") { + continue; + } + let object = c_object_path(objects_dir, &source); + + let mut command = Command::new(&compiler); + command.arg("-c"); + // Optimize: an unoptimized C caller can happen to agree with a wrong callee. + command.arg("-O1"); + // GCC notes that the ABI of over-aligned arguments changed in GCC 4.6. That is the ABI + // being tested in overaligned_byval_abi, so the note is expected rather than a problem. + command.arg("-Wno-psabi"); + command.arg("-o"); + command.arg(&object); + command.arg(&source); + if let Some(env_path) = target_path(test_target) { + command.env("PATH", env_path); + } + + let status = command + .status() + .unwrap_or_else(|error| panic!("failed to run `{}`: {}", compiler, error)); + assert!(status.success(), "failed to compile `{}`", source.display()); + } +} + +/// The object file that a test source links against, if any: `tests/c/x.c` for `tests/run/x.rs`. +fn c_object_path(objects_dir: &Path, source: &Path) -> PathBuf { + objects_dir.join(source.file_stem().expect("file_stem")).with_extension("o") +} + fn compile_and_run_cmds( compiler_args: Vec, test_target: &Option, @@ -18,10 +80,7 @@ fn compile_and_run_cmds( // Test command 2: run `tempdir/x`. if test_target.is_some() { - let mut env_path = std::env::var("PATH").unwrap_or_default(); - // FIXME(antoyo): find a better way to add the PATH necessary locally. - env_path = format!("/opt/m68k-unknown-linux-gnu/bin:{}", env_path); - compiler.env("PATH", env_path); + compiler.env("PATH", target_path(test_target).expect("target PATH")); let mut commands = vec![("Compiler", compiler)]; if test_mode.should_run() { @@ -82,8 +141,10 @@ impl TestMode { } } +#[allow(clippy::too_many_arguments)] fn build_test_runner( tempdir: PathBuf, + c_objects_dir: PathBuf, current_dir: String, build_mode: BuildMode, test_kind: &str, @@ -159,6 +220,13 @@ fn build_test_runner( path.to_str().expect("to_str").into(), ]; + // Link against `tests/c/.c`, when the test has one. + let c_object = c_object_path(&c_objects_dir, path); + if c_object.exists() { + compiler_args.push("-C".into()); + compiler_args.push(format!("link-arg={}", c_object.display())); + } + if let Some(ref target) = test_target { compiler_args.extend_from_slice(&["--target".into(), target.into()]); @@ -172,6 +240,16 @@ fn build_test_runner( } } + // Extra flags passed at run time (as opposed to the compile-time + // `TEST_FLAGS`). This lets a single test opt into flags like + // `-Zmir-preserve-ub` via an `ignore-if` directive that checks + // whether `CARGO_TEST_FLAGS` is set. + if let Ok(flags) = std::env::var("CARGO_TEST_FLAGS") { + for flag in flags.split_whitespace() { + compiler_args.push(flag.into()); + } + } + if build_mode.is_debug() { compiler_args .extend_from_slice(&["-C".to_string(), "llvm-args=sanitize-undefined".into()]); @@ -193,36 +271,51 @@ fn build_test_runner( .run(); } -fn compile_tests(tempdir: PathBuf, current_dir: String) { +fn compile_tests(tempdir: PathBuf, c_objects_dir: PathBuf, current_dir: String) { build_test_runner( tempdir, + c_objects_dir, current_dir, BuildMode::Debug, "lang compile", "tests/compile", TestMode::Compile, - &["simd-ffi.rs", "asm_nul_byte.rs", "global_asm_nul_byte.rs", "naked_asm_nul_byte.rs"], + &[ + "simd-ffi.rs", + "asm_nul_byte.rs", + "global_asm_nul_byte.rs", + "naked_asm_nul_byte.rs", + "x86_interrupt_first_arg_byval.rs", + ], ); } -fn run_tests(tempdir: PathBuf, current_dir: String) { +fn run_tests(tempdir: PathBuf, c_objects_dir: PathBuf, current_dir: String) { build_test_runner( tempdir.clone(), + c_objects_dir.clone(), current_dir.clone(), BuildMode::Debug, "[DEBUG] lang run", "tests/run", TestMode::CompileAndRun, - &[], + &[ + // FIXME: remove this when the unwind issue is fixed in GCC m68k upstream. + "catch_unwind.rs", + ], ); build_test_runner( tempdir, + c_objects_dir, current_dir.to_string(), BuildMode::Release, "[RELEASE] lang run", "tests/run", TestMode::CompileAndRun, - &[], + &[ + // FIXME: remove this when the unwind issue is fixed in GCC m68k upstream. + "catch_unwind.rs", + ], ); } @@ -232,6 +325,11 @@ fn main() { let current_dir = current_dir.to_str().expect("current dir").to_string(); let tempdir_path: PathBuf = tempdir.as_ref().into(); - compile_tests(tempdir_path.clone(), current_dir.clone()); - run_tests(tempdir_path, current_dir); + let c_objects_dir = tempdir_path.join("c-objects"); + // FIXME(antoyo): find a way to send this via a cli argument. + let test_target = std::env::var("CG_GCC_TEST_TARGET").ok(); + compile_c_files(&c_objects_dir, &test_target); + + compile_tests(tempdir_path.clone(), c_objects_dir.clone(), current_dir.clone()); + run_tests(tempdir_path, c_objects_dir, current_dir); } diff --git a/compiler/rustc_codegen_gcc/tests/run/asm.rs b/compiler/rustc_codegen_gcc/tests/run/asm.rs index 01775c92ffc8a..42141c671b596 100644 --- a/compiler/rustc_codegen_gcc/tests/run/asm.rs +++ b/compiler/rustc_codegen_gcc/tests/run/asm.rs @@ -3,6 +3,8 @@ // Run-time: // status: 0 +#![feature(asm_goto_with_outputs)] + #[cfg(target_arch = "x86_64")] use std::arch::{asm, global_asm}; @@ -32,6 +34,20 @@ pub unsafe fn mem_cpy(dst: *mut u8, src: *const u8, len: usize) { ); } +#[cfg(target_arch = "x86_64")] +#[unsafe(no_mangle)] +pub fn asm_goto_test(mut a: i16) -> i16 { + unsafe { + std::arch::asm!( + "jmp {op}", + inout("eax") a, + op = label { a = 7; }, + options(nostack,nomem) + ); + a + } +} + #[cfg(target_arch = "x86_64")] fn asm() { unsafe { @@ -190,6 +206,14 @@ fn asm() { } assert_eq!((x, y), (8, 8)); + // Regression test for + // typed pointer inputs to explicit registers need a cast. + let mut x = 123_i32; + unsafe { + asm!("", in("rdi") &mut x, options(nostack, preserves_flags)); + } + assert_eq!(x, 123); + // sysv64 is the default calling convention on unix systems. The rdi register is // used to pass arguments in the sysv64 calling convention, so this register will be clobbered #[cfg(unix)] @@ -227,6 +251,24 @@ fn asm() { out("r15b") _, ); } + + // Make sure the input value from inout is assigned to the input value + unsafe { + // Use a very distinctive value unlikely to live in any register. + let input: u64 = 0x1234567890ABCDEF; + let mut output: u64; + + asm!( + "push {1}", + "pop {0}", + out(reg) output, + inout(reg) input => _, + ); + + assert_eq!(output, 0x1234567890ABCDEF); + } + + asm_goto_test(0); } #[cfg(not(target_arch = "x86_64"))] diff --git a/compiler/rustc_codegen_gcc/tests/run/catch_unwind.rs b/compiler/rustc_codegen_gcc/tests/run/catch_unwind.rs new file mode 100644 index 0000000000000..919213db5a4a5 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/catch_unwind.rs @@ -0,0 +1,25 @@ +// Compiler: +// +// Run-time: +// status: 0 +// stdout: Caught + +#![feature(fn_traits, unboxed_closures)] + +struct Wrapper(A); + +impl R> FnOnce<()> for Wrapper { + type Output = R; + + #[inline] + extern "rust-call" fn call_once(self, _args: ()) -> R { + (self.0)() + } +} + +fn main() { + std::panic::set_hook(Box::new(|_| {})); + let result = std::panic::catch_unwind(Wrapper(|| panic!())); + assert!(result.is_err()); + println!("Caught"); +} diff --git a/compiler/rustc_codegen_gcc/tests/run/custom_abort.rs b/compiler/rustc_codegen_gcc/tests/run/custom_abort.rs new file mode 100644 index 0000000000000..eafa4321a4324 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/custom_abort.rs @@ -0,0 +1,27 @@ +// Compiler: +// +// Run-time: +// status: 42 + +// Check that a program can define its own `abort`. + +#![feature(no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[no_mangle] +extern "C" fn abort() { + unsafe { + libc::exit(42); + } +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + abort(); + 0 +} diff --git a/compiler/rustc_codegen_gcc/tests/run/import_linkage.rs b/compiler/rustc_codegen_gcc/tests/run/import_linkage.rs new file mode 100644 index 0000000000000..bf5cb9e532799 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/import_linkage.rs @@ -0,0 +1,84 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks the `#[linkage]` flavours an `extern` static can be imported with, against the symbols +// `tests/c/import_linkage.c` defines. +// +// The value of such an import is the address of the symbol rather than its contents, which is why +// the types are pointers: an `extern_weak` import of a symbol nobody defines reads as null instead +// of failing the link. + +#![feature(linkage, no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +extern "C" { + #[linkage = "external"] + static external_value: *const i32; + #[linkage = "available_externally"] + static available_externally_value: *const i32; + #[linkage = "linkonce"] + static linkonce_value: *const i32; + #[linkage = "linkonce_odr"] + static linkonce_odr_value: *const i32; + #[linkage = "weak"] + static weak_value: *const i32; + #[linkage = "weak_odr"] + static weak_odr_value: *const i32; + #[linkage = "common"] + static common_value: *const i32; + #[linkage = "extern_weak"] + static extern_weak_value: *const i32; + // An import is an undefined reference whatever the flavour says. Upstream bug: rustc lowers + // this one to an internal declaration, which LLVM's verifier rejects ("Global is external, but + // doesn't have external or weak linkage!") and which crashes cg_llvm at -O3. + #[linkage = "internal"] + static internal_value: *const i32; + + // Nothing defines this one, so it stays null instead of breaking the link. + #[linkage = "extern_weak"] + static undefined_value: *const i32; +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + unsafe { + if *external_value != 1 { + return 1; + } + if *available_externally_value != 2 { + return 2; + } + if *linkonce_value != 3 { + return 3; + } + if *linkonce_odr_value != 4 { + return 4; + } + if *weak_value != 5 { + return 5; + } + if *weak_odr_value != 6 { + return 6; + } + if *common_value != 7 { + return 7; + } + if *extern_weak_value != 8 { + return 8; + } + if *internal_value != 9 { + return 9; + } + if undefined_value as usize != 0 { + return 10; + } + } + 0 +} diff --git a/compiler/rustc_codegen_gcc/tests/run/int.rs b/compiler/rustc_codegen_gcc/tests/run/int.rs index 78675acb5447b..ef825b4d80185 100644 --- a/compiler/rustc_codegen_gcc/tests/run/int.rs +++ b/compiler/rustc_codegen_gcc/tests/run/int.rs @@ -319,4 +319,29 @@ fn main() { const VAL5: T = 73236519889708027473620326106273939584_i128; check_ops128!(); } + + { + #[allow(dead_code)] + #[repr(u8)] + enum Inner { + L0 = 0, + H255 = 255, + } + #[allow(dead_code)] + enum O { + A(Inner), + B, + C, + } + + #[inline(never)] + fn which(o: &O) -> &'static str { + match o { + O::A(_) => "a", + O::B => "b", + O::C => "c", + } + } + assert_eq!(which(black_box(&O::A(Inner::H255))), "a"); + } } diff --git a/compiler/rustc_codegen_gcc/tests/run/mir_preserve_ub_empty_switch.rs b/compiler/rustc_codegen_gcc/tests/run/mir_preserve_ub_empty_switch.rs new file mode 100644 index 0000000000000..26056360b9212 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/mir_preserve_ub_empty_switch.rs @@ -0,0 +1,35 @@ +// ignore-if: test -z "$CARGO_TEST_FLAGS" +// Compiler: +// +// Run-time: +// status: 0 + +// Regression test for https://github.com/rust-lang/rustc_codegen_gcc/issues/881 +// +// This needs `-Zmir-preserve-ub`, so it is skipped unless that flag is passed +// through `CARGO_TEST_FLAGS` (see the `ignore-if` directive above). Run it with: +// CARGO_TEST_FLAGS="-Zmir-preserve-ub" ./y.sh test --cargo-tests -- mir_preserve_ub_empty_switch + +#![feature(no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use intrinsics::black_box; +use mini_core::*; + +#[no_mangle] +extern "C" fn main(argc: i32, _argv: *const *const u8) -> i32 { + // With `-Zmir-preserve-ub`, the range pattern below is lowered to a pair of + // comparisons and the second one becomes a `SwitchInt` with no cases (only + // an `otherwise` target) whose discriminant is the `bool` comparison + // result. `gcc_jit_block_end_with_switch` rejects a non-integer + // discriminant, so the backend must emit a plain jump for it instead. + let value = black_box(argc); + match value { + 0..=9 => (), + _ => (), + } + 0 +} diff --git a/compiler/rustc_codegen_gcc/tests/run/nonzero_div_ceil.rs b/compiler/rustc_codegen_gcc/tests/run/nonzero_div_ceil.rs new file mode 100644 index 0000000000000..e6bd3c2ec9845 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/nonzero_div_ceil.rs @@ -0,0 +1,17 @@ +// Compiler: +// +// Run-time: +// status: 0 + +use std::hint::black_box; +use std::num::NonZero; + +fn main() { + for (dividend, divisor, expected) in + [(10u8, 3u8, 4u8), (1, 254, 1), (1, 255, 1), (2, 254, 1), (2, 255, 1), (200, 100, 2)] + { + let dividend = NonZero::new(black_box(dividend)).unwrap(); + let divisor = NonZero::new(black_box(divisor)).unwrap(); + assert_eq!(dividend.div_ceil(divisor).get(), expected); + } +} diff --git a/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_abi.rs b/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_abi.rs new file mode 100644 index 0000000000000..78ee35f05ca58 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_abi.rs @@ -0,0 +1,89 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks that cg_gcc passes an over-aligned by-value ("byval") argument where the platform ABI +// says it goes, by calling in both directions with `tests/c/overaligned_byval_abi.c`, which is +// compiled by the real GCC. +// +// `tests/run/overaligned_byval_arg.rs` covers the Rust-visible half of the same bug. It cannot +// cover this one: with cg_gcc on both sides of a call, caller and callee place the argument at +// the same wrong offset and agree with each other. +// +// Two over-aligned arguments are used rather than one so that the failure is deterministic. A +// backend that drops `align(64)` packs the arguments at offsets 0, 24, 88 and 112 of the argument +// area; 112 - 24 = 88 is not a multiple of 64, so the two of them cannot both land on a 64-byte +// boundary however the argument area itself is aligned. With a single over-aligned argument the +// frame often happens to be 64-aligned and the bug hides. +// +// Only the values received are checked, never the address an argument landed at: which alignment +// a target gives a by-value stack slot differs between targets, but the two sides of a call +// agreeing on it does not. `overaligned_byval_arg.rs` is where the alignment itself is asserted. + +#![feature(no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[repr(C)] +struct Big { + a: i64, + b: i64, + c: i64, +} + +#[repr(C, align(64))] +struct Aligned { + x: i32, +} + +extern "C" { + fn c_take_both(first: Big, second: Aligned, third: Big, fourth: Aligned) -> i32; + fn c_call_rust() -> i32; +} + +// The callee for the GCC-built caller in `c_call_rust`. +// +// `#[no_mangle]` is not only about the symbol name: it makes the symbol externally visible, which +// pins the calling convention. Without it the function has internal linkage and GCC is free to +// clone it with a changed convention at `-O3` (the symbol comes out as `...constprop.0.isra.0`), +// so the arguments never travel through the stack slots and the release build passes spuriously. +#[no_mangle] +extern "C" fn rust_take_both(first: Big, second: Aligned, third: Big, fourth: Aligned) -> i32 { + if first.a as i32 != 1 || first.b as i32 != 2 || first.c as i32 != 3 { + return 5; + } + if second.x != 42 { + return 6; + } + if third.a as i32 != 4 || third.b as i32 != 5 || third.c as i32 != 6 { + return 7; + } + if fourth.x != 43 { + return 8; + } + 0 +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + // cg_gcc as the caller, GCC as the callee. + let result = unsafe { + c_take_both( + Big { a: 1, b: 2, c: 3 }, + Aligned { x: 42 }, + Big { a: 4, b: 5, c: 6 }, + Aligned { x: 43 }, + ) + }; + if result != 0 { + return result; + } + + // GCC as the caller, cg_gcc as the callee. + unsafe { c_call_rust() } +} diff --git a/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_arg.rs b/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_arg.rs new file mode 100644 index 0000000000000..e20ec11732962 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_arg.rs @@ -0,0 +1,41 @@ +// Compiler: +// +// Run-time: +// status: 0 + +#![feature(no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[repr(C)] +struct Big { + a: i64, + b: i64, + c: i64, +} + +#[repr(C, align(64))] +struct Aligned { + x: i32, +} + +#[inline(never)] +#[no_mangle] +extern "C" fn check(_b1: Big, a1: Aligned, _b2: Big, a2: Aligned) -> i32 { + if (&a1 as *const Aligned as usize) % 64 != 0 { + return 1; + } + if (&a2 as *const Aligned as usize) % 64 != 0 { + return 2; + } + 0 +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + check(Big { a: 1, b: 2, c: 3 }, Aligned { x: 42 }, Big { a: 4, b: 5, c: 6 }, Aligned { x: 43 }) +} diff --git a/compiler/rustc_codegen_gcc/tests/run/ptr_to_int_div.rs b/compiler/rustc_codegen_gcc/tests/run/ptr_to_int_div.rs new file mode 100644 index 0000000000000..afc563d6c977b --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/ptr_to_int_div.rs @@ -0,0 +1,19 @@ +// Compiler: +// +// Run-time: +// status: 0 + +use std::hint::black_box; +use std::mem::transmute; + +fn main() { + let pointer = black_box(usize::MAX) as *const (); + + let unsigned = unsafe { transmute::<*const (), usize>(pointer) }; + assert_eq!(unsigned / black_box(2), usize::MAX / 2); + assert_eq!(unsigned % black_box(2), usize::MAX % 2); + + let signed = unsafe { transmute::<*const (), isize>(pointer) }; + assert_eq!(signed / black_box(2), -1isize / 2); + assert_eq!(signed % black_box(2), -1isize % 2); +} diff --git a/compiler/rustc_codegen_gcc/tests/run/simd.rs b/compiler/rustc_codegen_gcc/tests/run/simd.rs new file mode 100644 index 0000000000000..e0a23fdccf8ce --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/simd.rs @@ -0,0 +1,81 @@ +// Compiler: +// +// Run-time: +// status: 0 + +#![feature(portable_simd)] + +use std::hint::black_box; +use std::simd::prelude::*; + +fn test_saturating_add() { + let values = i32x4::from_array([i32::MIN, -2, 3, i32::MAX]); + let ones = i32x4::splat(1); + assert_eq!( + black_box(values).saturating_add(black_box(ones)).to_array(), + [i32::MIN + 1, -1, 4, i32::MAX] + ); + + let values = u32x4::from_array([0, 2, 3, u32::MAX]); + let ones = u32x4::splat(1); + assert_eq!(black_box(values).saturating_add(black_box(ones)).to_array(), [1, 3, 4, u32::MAX]); +} + +fn test_saturating_sub() { + let values = i32x4::from_array([i32::MIN, -2, 3, i32::MAX]); + let zero = i32x4::splat(0); + assert_eq!( + black_box(zero).saturating_sub(black_box(values)).to_array(), + [i32::MAX, 2, -3, i32::MIN + 1] + ); + assert_eq!(black_box(values).saturating_neg().to_array(), [i32::MAX, 2, -3, i32::MIN + 1]); + assert_eq!(black_box(values).saturating_abs().to_array(), [i32::MAX, 2, 3, i32::MAX]); + + let values = i32x4::from_array([i32::MIN, -2, 3, i32::MAX]); + let ones = i32x4::splat(1); + assert_eq!( + black_box(values).saturating_sub(black_box(ones)).to_array(), + [i32::MIN, -3, 2, i32::MAX - 1] + ); + + let values = u32x4::from_array([0, 2, 3, u32::MAX]); + let ones = u32x4::splat(1); + assert_eq!( + black_box(values).saturating_sub(black_box(ones)).to_array(), + [0, 1, 2, u32::MAX - 1] + ); +} + +fn test_float_cast() { + let floats = f32x4::from_array([1.9, -4.5, f32::INFINITY, f32::NAN]); + assert_eq!(black_box(floats).cast::().to_array(), [1, -4, i32::MAX, 0]); + + let floats = f32x4::from_array([f32::NEG_INFINITY, 1e20, -1e20, -0.0]); + assert_eq!(black_box(floats).cast::().to_array(), [i32::MIN, i32::MAX, i32::MIN, 0]); + + let floats = f32x4::from_array([-1.0, 3.7, f32::NAN, 1e20]); + assert_eq!(black_box(floats).cast::().to_array(), [0, 3, 0, u32::MAX]); + + let floats = f64x4::from_array([-1.5, 2.5, f64::NAN, f64::INFINITY]); + assert_eq!(black_box(floats).cast::().to_array(), [-1, 2, 0, i64::MAX]); +} + +fn test_arith_offset() { + let values = [10i32, 11, 12, 13, 14, 15, 16, 17]; + let indices = usizex4::from_array([7, 5, 3, 1]); + assert_eq!( + i32x4::gather_or_default(black_box(&values), black_box(indices)).to_array(), + [17, 15, 13, 11] + ); + + let mut destination = [0i32; 8]; + i32x4::from_array([1, 2, 3, 4]).scatter(black_box(&mut destination), black_box(indices)); + assert_eq!(destination, [0, 4, 0, 3, 0, 2, 0, 1]); +} + +fn main() { + test_saturating_add(); + test_saturating_sub(); + test_float_cast(); + test_arith_offset(); +} diff --git a/compiler/rustc_codegen_gcc/tests/run/static_alloc_shapes.rs b/compiler/rustc_codegen_gcc/tests/run/static_alloc_shapes.rs new file mode 100644 index 0000000000000..39a4d07bdf638 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/static_alloc_shapes.rs @@ -0,0 +1,50 @@ +// Compiler: +// +// Run-time: +// status: 0 +// stdout: 8 +// 12 +// 5 +// 7 +// 7 +// 9 + +#![feature(no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +// One byte run of each length class that maps to a distinct array element type. +static mut BYTES8: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; +static mut BYTES12: [u8; 12] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; +static mut BYTES5: [u8; 5] = [1, 2, 3, 4, 5]; + +static mut VALUE: isize = 7; +static mut OTHER: isize = 9; + +// An allocation that is exactly one relocation, so it ends on a pointer with no trailing bytes. +static mut PTR: &isize = unsafe { &VALUE }; + +struct TwoRefs { + first: &'static isize, + second: &'static isize, +} + +// Two adjacent relocations, with no byte run between them. +static mut TWO_REFS: TwoRefs = TwoRefs { first: unsafe { &VALUE }, second: unsafe { &OTHER } }; + +#[no_mangle] +extern "C" fn main(_argc: isize, _argv: *const *const u8) -> i32 { + unsafe { + libc::printf(b"%ld\n\0" as *const u8 as *const i8, BYTES8[7] as isize); + libc::printf(b"%ld\n\0" as *const u8 as *const i8, BYTES12[11] as isize); + libc::printf(b"%ld\n\0" as *const u8 as *const i8, BYTES5[4] as isize); + libc::printf(b"%ld\n\0" as *const u8 as *const i8, *PTR); + libc::printf(b"%ld\n\0" as *const u8 as *const i8, *TWO_REFS.first); + libc::printf(b"%ld\n\0" as *const u8 as *const i8, *TWO_REFS.second); + } + 0 +} diff --git a/compiler/rustc_codegen_gcc/tests/run/static_linkage.rs b/compiler/rustc_codegen_gcc/tests/run/static_linkage.rs new file mode 100644 index 0000000000000..7b911c064d797 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/static_linkage.rs @@ -0,0 +1,79 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks that `#[linkage]` on a static that this crate defines reaches the symbol, against +// `tests/c/static_linkage.c`, which defines the overridable ones strongly. +// +// If `predefine_static` were to ignore its `linkage` argument outright, every static would come out as +// an ordinary global symbol: the overridable ones would clash with the C definitions at link time, and +// `internal` would export a symbol it should have kept private. + +#![feature(linkage, no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[linkage = "weak"] +#[no_mangle] +pub static weak_static: i32 = 0; + +#[linkage = "weak_odr"] +#[no_mangle] +pub static weak_odr_static: i32 = 0; + +#[linkage = "linkonce"] +#[no_mangle] +pub static linkonce_static: i32 = 0; + +#[linkage = "linkonce_odr"] +#[no_mangle] +pub static linkonce_odr_static: i32 = 0; + +// `common` is only valid on a mutable global: LLVM rejects a constant one. +#[linkage = "common"] +#[no_mangle] +pub static mut common_static: i32 = 0; + +// Private to this crate, so the C definition of the same name is a different object. +#[linkage = "internal"] +#[no_mangle] +pub static internal_static: i32 = 100; + +// Not overridden by the C side: the definition here is the one that survives. +#[linkage = "weak"] +#[no_mangle] +pub static only_weak_static: i32 = 6; + +// The real definition is the one in the C file; a backend may read it or emit an equivalent copy of +// this initializer, so both spell the same value. +#[linkage = "available_externally"] +#[no_mangle] +pub static available_externally_static: i32 = 7; + +extern "C" { + fn c_read_all() -> i32; +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + let result = unsafe { c_read_all() }; + if result != 0 { + return result; + } + + if internal_static != 100 { + return 1; + } + if only_weak_static != 6 { + return 2; + } + if available_externally_static != 7 { + return 3; + } + 0 +} diff --git a/compiler/rustc_codegen_gcc/tests/run/weak_function_linkage.rs b/compiler/rustc_codegen_gcc/tests/run/weak_function_linkage.rs new file mode 100644 index 0000000000000..677f01353401a --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/weak_function_linkage.rs @@ -0,0 +1,106 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks that the `#[linkage]` flavours another object file is allowed to override are emitted as +// weak symbols, by linking against `tests/c/weak_function_linkage.c`, which defines the same +// symbols strongly. + +#![feature(linkage, no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[linkage = "weak"] +#[no_mangle] +extern "C" fn weak_function() -> i32 { + 0 +} + +// `_odr` promises every definition of the symbol is equivalent, which lets a backend call this body +// instead of the one in the C file. They spell the same value for that reason. +#[linkage = "weak_odr"] +#[no_mangle] +extern "C" fn weak_odr_function() -> i32 { + 2 +} + +#[linkage = "linkonce"] +#[no_mangle] +extern "C" fn linkonce_function() -> i32 { + 0 +} + +#[linkage = "linkonce_odr"] +#[no_mangle] +extern "C" fn linkonce_odr_function() -> i32 { + 4 +} + +// `#[linkage = "common"]` is absent on purpose: a common symbol is `SHN_COMMON`, which the object +// format only allows for objects, so no backend can give a function that linkage. + +// Not overridden by the C side: the definition here is the one that runs. +#[linkage = "weak"] +#[no_mangle] +extern "C" fn only_weak_function() -> i32 { + 6 +} + +// The real definition is the one in the C file; a backend may call it or emit an equivalent copy of +// this body, so both spell the same value. +#[linkage = "available_externally"] +#[no_mangle] +extern "C" fn available_externally_function() -> i32 { + 7 +} + +// GCC drops `weak` from a function that is also `inline`: a backend that keeps the hint emits this +// as an ordinary global symbol and clashes with the C definition. rustc lints the hint as ignored +// on a function with an explicit `#[linkage]`, hence the `allow`. +#[linkage = "weak"] +#[inline] +#[no_mangle] +#[allow(unused_attributes)] +extern "C" fn weak_inline_function() -> i32 { + 0 +} + +extern "C" { + fn c_call_all() -> i32; +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + let result = unsafe { c_call_all() }; + if result != 0 { + return result; + } + + if weak_function() != 1 { + return 1; + } + if weak_odr_function() != 2 { + return 2; + } + if linkonce_function() != 3 { + return 3; + } + if linkonce_odr_function() != 4 { + return 4; + } + if only_weak_function() != 6 { + return 6; + } + if available_externally_function() != 7 { + return 7; + } + if weak_inline_function() != 8 { + return 8; + } + 0 +} diff --git a/compiler/rustc_codegen_gcc/tools/cspell_dicts/rust.txt b/compiler/rustc_codegen_gcc/tools/cspell_dicts/rust.txt deleted file mode 100644 index 379cbd77eef01..0000000000000 --- a/compiler/rustc_codegen_gcc/tools/cspell_dicts/rust.txt +++ /dev/null @@ -1,2 +0,0 @@ -lateout -repr diff --git a/compiler/rustc_codegen_gcc/tools/cspell_dicts/rustc_codegen_gcc.txt b/compiler/rustc_codegen_gcc/tools/cspell_dicts/rustc_codegen_gcc.txt deleted file mode 100644 index 4fb018b3ecd87..0000000000000 --- a/compiler/rustc_codegen_gcc/tools/cspell_dicts/rustc_codegen_gcc.txt +++ /dev/null @@ -1,78 +0,0 @@ -aapcs -addo -archs -ashl -ashr -cgcx -clzll -cmse -codegened -csky -ctfe -ctlz -ctpop -cttz -ctzll -flto -fmaximumf -fmuladd -fmuladdf -fminimumf -fmul -fptosi -fptosui -fptoui -fwrapv -gimple -hrtb -immediates -interner -liblto -llbb -llcx -llextra -llfn -lgcc -llmod -llresult -llret -ltrans -llty -llval -llvals -loong -lshr -masm -maximumf -maxnumf -mavx -mcmodel -minimumf -minnumf -miri -monomorphization -monomorphizations -monomorphized -monomorphizing -movnt -mulo -nvptx -pointee -powitf -reassoc -riscv -rlib -roundevenf -rustc -sitofp -sizet -spir -subo -sysv -tbaa -uitofp -unord -uninlined -utrunc -xabort -zext diff --git a/compiler/rustc_codegen_gcc/tools/generate_intrinsics.py b/compiler/rustc_codegen_gcc/tools/generate_intrinsics.py index 5390323407779..06425f682a88b 100644 --- a/compiler/rustc_codegen_gcc/tools/generate_intrinsics.py +++ b/compiler/rustc_codegen_gcc/tools/generate_intrinsics.py @@ -84,6 +84,10 @@ def update_intrinsics(llvm_path): # This speeds up the comparison, and makes our code considerably smaller. # Since all intrinsic names start with "llvm.", we skip that prefix. print("Updating content of `{}`...".format(output_file)) + indent4 = " " + indent8 = indent4 + indent4 + indent12 = indent8 + indent4 + indent16 = indent12 + indent4 with open(output_file, "w", encoding="utf8") as out: out.write("""// File generated by `rustc_codegen_gcc/tools/generate_intrinsics.py` // DO NOT EDIT IT! @@ -95,33 +99,35 @@ def update_intrinsics(llvm_path): if let ArchCheckResult::Ok(res) = old_arch_res { return res; } -match arch {""") + match arch { +""") for arch in archs: if len(intrinsics[arch]) == 0: continue attribute = "#[expect(non_snake_case)]" if arch[0].isupper() else "" - out.write("\"{}\" => {{ {} fn {}(name: &str,full_name:&str) -> &'static str {{ match name {{".format(arch, attribute, arch)) + out.write(f"""{indent4}"{arch}" => {{ +{indent8}{attribute} fn {arch}(name: &str,full_name:&str) -> &'static str {{ +{indent12}match name {{""") intrinsics[arch].sort(key=lambda x: (x[0], x[1])) - out.write(' // {}\n'.format(arch)) + out.write(f'{indent16}// {arch}\n') for entry in intrinsics[arch]: llvm_name = entry[0].removeprefix("llvm."); llvm_name = llvm_name.removeprefix(arch); llvm_name = llvm_name.removeprefix("."); if "_round_mask" in entry[1]: - out.write(' // [INVALID CONVERSION]: "{}" => "{}",\n'.format(llvm_name, entry[1])) + out.write(f'{indent16}// [INVALID CONVERSION]: "{llvm_name}" => "{entry[1]}",\n') else: - out.write(' "{}" => "{}",\n'.format(llvm_name, entry[1])) - out.write(' _ => unimplemented!("***** unsupported LLVM intrinsic {full_name}"),\n') - out.write("}} }} {}(name,full_name) }}\n,".format(arch)) - out.write(""" _ => { - match old_arch_res { - ArchCheckResult::UnknownIntrinsic => unimplemented!("***** unsupported LLVM intrinsic {full_name}"), - ArchCheckResult::UnknownArch => unimplemented!("***** unsupported LLVM architecture {arch}, intrinsic: {full_name}"), - ArchCheckResult::Ok(_) => unreachable!(), - } - }""") + out.write(f'{indent16}"{llvm_name}" => "{entry[1]}",\n') + out.write(f'{indent16}_ => unimplemented!("***** unsupported LLVM intrinsic {{full_name}}"),\n') + out.write(f"{indent16}}}\n{indent12}}}\n{indent8}{arch}(name,full_name)\n{indent8}}}\n,") + out.write(f"""{indent4}_ => {{ +{indent8}match old_arch_res {{ +{indent8}ArchCheckResult::UnknownIntrinsic => unimplemented!("***** unsupported LLVM intrinsic {{full_name}}"), +{indent8}ArchCheckResult::UnknownArch => unimplemented!("***** unsupported LLVM architecture {{arch}}, intrinsic: {{full_name}}"), +{indent8}ArchCheckResult::Ok(_) => unreachable!(), +{indent4}}} +}}""") out.write("}\n}") - subprocess.call(["rustfmt", output_file]) print("Done!") diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs index e0fc60e1a9b7a..735dd0fbaba10 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use std::{assert_matches, iter, ptr}; use libc::{c_longlong, c_uint}; -use rustc_abi::{Align, Layout, NumScalableVectors, Size}; +use rustc_abi::{Align, Endian, Layout, NumScalableVectors, Size}; use rustc_codegen_ssa::debuginfo::type_names::{VTableNameKind, cpp_like_debuginfo}; use rustc_codegen_ssa::traits::*; use rustc_hir::def::{CtorKind, DefKind}; @@ -21,7 +21,7 @@ use rustc_span::{ DUMMY_SP, FileName, RemapPathScopeComponents, SourceFile, Span, Symbol, bug, hygiene, }; use rustc_symbol_mangling::typeid_for_trait_ref; -use rustc_target::spec::{Arch, DebuginfoKind}; +use rustc_target::spec::{Arch, DebuginfoKind, HasTargetSpec}; use smallvec::smallvec; use tracing::{debug, instrument}; @@ -693,33 +693,22 @@ impl MsvcBasicName for ty::UintTy { } } -impl MsvcBasicName for ty::FloatTy { - fn msvc_basic_name(self) -> &'static str { - // FIXME(f128): `f128` has no MSVC representation. We could improve the debuginfo. - // See: - match self { - ty::FloatTy::F16 => { - bug!("`f16` should have been handled in `build_basic_type_di_node`") - } - ty::FloatTy::F32 => "float", - ty::FloatTy::F64 => "double", - ty::FloatTy::F128 => "fp128", - } - } -} - -fn build_cpp_f16_di_node<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) -> DINodeCreationResult<'ll> { - // MSVC has no native support for `f16`. Instead, emit `struct f16 { bits: u16 }` to allow the - // `f16`'s value to be displayed using a Natvis visualiser in `intrinsic.natvis`. - let float_ty = cx.tcx.types.f16; - let bits_ty = cx.tcx.types.u16; - let def_location = if cx.sess().opts.unstable_opts.debug_info_type_line_numbers { - match float_ty.kind() { - ty::Adt(def, _) => Some(file_metadata_from_def_id(cx, Some(def.did()))), - _ => None, - } +/// `float_ty` must be a [`ty::Float`] and `bits_ty` must be a [`ty::Uint`]. +/// `cx.size_of(bits_ty) * bits_names.len()` must equal `cx.size_of(float_ty)`. +fn build_cpp_float_struct_di_node<'ll, 'tcx>( + cx: &CodegenCx<'ll, 'tcx>, + float_ty: Ty<'tcx>, + bits_ty: Ty<'tcx>, + bits_names: &[&str], +) -> DINodeCreationResult<'ll> { + debug_assert!(matches!(bits_ty.kind(), ty::Uint(_))); + debug_assert_eq!(cx.size_of(bits_ty) * (bits_names.len() as u64), cx.size_of(float_ty)); + // MSVC has no native support for `f16` or `f128`. Instead, emit a struct containing the bits as + // field(s) to allow the value to be displayed using a Natvis visualiser in `intrinsic.natvis`. + let name = if let ty::Float(f) = float_ty.kind() { + f.name_str() } else { - None + bug!("{float_ty:?} was not a float"); }; type_map::build_type_with_children( cx, @@ -727,32 +716,33 @@ fn build_cpp_f16_di_node<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) -> DINodeCreation cx, Stub::Struct, UniqueTypeId::for_ty(cx.tcx, float_ty), - "f16", - def_location, + name, + None, cx.size_and_align_of(float_ty), NO_SCOPE_METADATA, DIFlags::FlagZero, ), // Fields: |cx, float_di_node| { - let def_id = if cx.sess().opts.unstable_opts.debug_info_type_line_numbers { - match bits_ty.kind() { - ty::Adt(def, _) => Some(def.did()), - _ => None, - } - } else { - None - }; - smallvec![build_field_di_node( - cx, - float_di_node, - "bits", - cx.layout_of(bits_ty), - Size::ZERO, - DIFlags::FlagZero, - type_di_node(cx, bits_ty), - def_id, - )] + let bits_layout = cx.layout_of(bits_ty); + let bits_node = type_di_node(cx, bits_ty); + bits_names + .iter() + .copied() + .enumerate() + .map(|(i, field_name)| { + build_field_di_node( + cx, + float_di_node, + field_name, + bits_layout, + bits_layout.size * (i as u64), + DIFlags::FlagZero, + bits_node, + None, + ) + }) + .collect() }, NO_GENERICS, ) @@ -784,9 +774,20 @@ fn build_basic_type_di_node<'ll, 'tcx>( ty::Int(int_ty) if cpp_like_debuginfo => (int_ty.msvc_basic_name(), DW_ATE_signed), ty::Uint(uint_ty) if cpp_like_debuginfo => (uint_ty.msvc_basic_name(), DW_ATE_unsigned), ty::Float(ty::FloatTy::F16) if cpp_like_debuginfo => { - return build_cpp_f16_di_node(cx); + return build_cpp_float_struct_di_node(cx, t, cx.tcx.types.u16, &["bits"]); + } + ty::Float(ty::FloatTy::F128) if cpp_like_debuginfo => { + // All MSVC architectures are little endian. + assert_eq!(cx.target_spec().endian, Endian::Little); + return build_cpp_float_struct_di_node( + cx, + t, + cx.tcx.types.u64, + &["low_bits", "high_bits"], + ); } - ty::Float(float_ty) if cpp_like_debuginfo => (float_ty.msvc_basic_name(), DW_ATE_float), + ty::Float(ty::FloatTy::F32) if cpp_like_debuginfo => ("float", DW_ATE_float), + ty::Float(ty::FloatTy::F64) if cpp_like_debuginfo => ("double", DW_ATE_float), ty::Int(int_ty) => (int_ty.name_str(), DW_ATE_signed), ty::Uint(uint_ty) => (uint_ty.name_str(), DW_ATE_unsigned), ty::Float(float_ty) => (float_ty.name_str(), DW_ATE_float), diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index db896fa9c0f2b..c71e83d8f99bd 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -347,8 +347,7 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { // 64-bit floats are always OK. } Primitive::Float(Float::F128) => { - // FIXME(f128) figure out whether we should support this. - bug!("the va_arg intrinsic does not support `f128`") + // Supported on some targets, especially where long double is IEEE f128. } } diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 90f31e0598f2d..5324ec240b0ab 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -6,6 +6,7 @@ use std::sync::Once; use std::{ptr, slice, str}; use libc::c_int; +use rustc_abi::Endian; use rustc_codegen_ssa::back::versioned_llvm_target; use rustc_codegen_ssa::base::wants_wasm_eh; use rustc_codegen_ssa::target_features::internal_target_features; @@ -15,7 +16,7 @@ use rustc_data_structures::small_c_str::SmallCStr; use rustc_fs_util::path_to_c_string; use rustc_session::config::{NATIVE_CPU, PrintKind, PrintRequest}; use rustc_session::{EarlySession, Session}; -use rustc_span::bug; +use rustc_span::{bug, sym}; use rustc_target::spec::{ Arch, CfgAbi, Env, MergeFunctions, Os, PanicStrategy, SmallDataThresholdSupport, Target, }; @@ -394,6 +395,7 @@ pub(crate) fn target_config(sess: &EarlySession) -> TargetConfig { fn update_target_reliable_float_cfg(target: &Target, cfg: &mut TargetConfig) { let target_arch = &target.arch; let target_os = &target.options.os; + let target_endian = &target.options.endian; let target_env = &target.options.env; let target_abi = &target.options.cfg_abi; let target_pointer_width = target.pointer_width; @@ -426,9 +428,12 @@ fn update_target_reliable_float_cfg(target: &Target, cfg: &mut TargetConfig) { // Selection bug . This issue is closed // but basic math still does not work. (Arch::Nvptx64, _) => false, - // ABI bugs et al. (full - // list at ) - (Arch::PowerPC | Arch::PowerPC64, _) => false, + // ABI/LLVM bugs: + // - with +vsx + // - without +vsx + (Arch::PowerPC, _) => false, + // ABI bugs on BE without +vsx . + (Arch::PowerPC64, _) => cfg.internal_target_features.contains(&sym::vsx), // ABI unsupported (fixed in llvm22) (Arch::Sparc, _) if major < 22 => false, // MinGW ABI bugs (fixed in llvm23) @@ -456,9 +461,13 @@ fn update_target_reliable_float_cfg(target: &Target, cfg: &mut TargetConfig) { // (ld is `f64`), anything other than Linux (Windows and MacOS use `f64`), and `x86` // (ld is 80-bit extended precision). // + // On big-endian powerpc the symbol selection is correct, despite __ibmf128 being + // long double on the target, but the f128 symbols are not defined. + // // musl does not implement the symbols required for f128 math at all. _ if *target_env == Env::Musl => false, (Arch::X86_64, _) => false, + (Arch::PowerPC | Arch::PowerPC64, _) if *target_endian == Endian::Big => false, (_, Os::Linux) if target_pointer_width == 64 => true, _ => false, } && cfg.has_reliable_f128; diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 987c9818bf0e5..888636365dc0c 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -93,10 +93,13 @@ pub struct DiagLocation { } impl DiagLocation { + pub fn from_location(loc: &'static panic::Location<'static>) -> Self { + DiagLocation { file: loc.file().into(), line: loc.line(), col: loc.column() } + } + #[track_caller] pub fn caller() -> Self { - let loc = panic::Location::caller(); - DiagLocation { file: loc.file().into(), line: loc.line(), col: loc.column() } + Self::from_location(panic::Location::caller()) } } @@ -1300,7 +1303,7 @@ impl<'a, G> Diag<'a, G> { } /// Most `emit` methods use this as a starting point. - pub fn emit_producing_nothing(mut self) { + fn emit_producing_nothing(mut self) { let diag = self.take_diag(); self.dcx.emit_diagnostic(diag); } diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index f7f42c9d366f5..89209b64e0108 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -57,6 +57,7 @@ pub use rustc_macros::msg; use rustc_macros::{Decodable, Encodable}; pub use rustc_span::ErrorGuaranteed; pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker, catch_fatal_errors}; +pub use rustc_span::macros::ExplicitBug; use rustc_span::source_map::SourceMap; use rustc_span::{DUMMY_SP, Span}; use tracing::debug; @@ -256,10 +257,6 @@ fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a s } } -/// Signifies that the compiler died with an explicit call to `.bug` -/// or `.span_bug` rather than a failed assertion, etc. -pub struct ExplicitBug; - /// Signifies that the compiler died due to a delayed bug rather than a failed /// assertion, etc. pub struct DelayedBugPanic; diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 705bb780a3ecf..52b69e6050a32 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -775,9 +775,23 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), if has_default { // need to store default and type of default let ct = tcx.const_param_default(param.def_id).skip_binder(); - if let ty::ConstKind::Alias(_, alias_const) = ct.kind() - && let Some(def_id) = alias_const.kind.opt_def_id() - { + if let ty::ConstKind::Alias(_, alias_const) = ct.kind() { + let def_id = match alias_const.kind { + ty::AliasConstKind::Projection { def_id } => def_id, + ty::AliasConstKind::InherentSelf { def_id } => { + // NOTE: typically, InherentSelf is illegal to pass to type_of, + // because the generic args are incorrect (type_of expects impl-form + // arguments). However, we are just checking ensure_ok().type_of(), + // we are not instantiating the result, so it's OK here. + def_id + } + ty::AliasConstKind::InherentImpl { .. } => span_bug!( + tcx.def_span(param.def_id), + "const_param_default should return an unnormalized constant, which should always be InherentSelf, not InherentImpl" + ), + ty::AliasConstKind::Free { def_id } => def_id, + ty::AliasConstKind::Anon { def_id } => def_id, + }; tcx.ensure_ok().type_of(def_id); } } diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index e89caa6aeff8c..ecfe5d3c2c7c2 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -1480,9 +1480,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { )? { TypeRelativePath::AssocItem(alias_term) => { let alias_ct = alias_term.expect_ct(); - if let Some(def_id) = alias_ct.kind.opt_def_id() { - self.check_const_item_in_type_system(def_id, span)?; - } + self.check_const_item_in_type_system(alias_ct.kind, span)?; let ct = Const::new_alias(tcx, ty::IsRigid::No, alias_ct); let ct = self.check_param_uses_if_mcg(ct, span, false); Ok(ct) @@ -1948,13 +1946,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { item_segment, ty::AssocTag::Const, )?; - self.check_const_item_in_type_system(item_def_id, span)?; - let alias_const = ty::AliasConst::new( - tcx, - ty::AliasConstKind::Projection { def_id: item_def_id }, - item_args, - ); - Ok(Const::new_alias(tcx, ty::IsRigid::No, alias_const)) + let kind = ty::AliasConstKind::Projection { def_id: item_def_id }; + self.check_const_item_in_type_system(kind, span)?; + let alias = ty::AliasConst::new(tcx, kind, item_args); + Ok(Const::new_alias(tcx, ty::IsRigid::No, alias)) } /// Lower a [resolved][hir::QPath::Resolved] (type-level) associated item path. @@ -2879,7 +2874,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { self.lower_const_param(def_id, hir_id) } Res::Def(DefKind::Const, did) => { - if let Err(guar) = self.check_const_item_in_type_system(did, span) { + let kind = ty::AliasConstKind::Free { def_id: did }; + if let Err(guar) = self.check_const_item_in_type_system(kind, span) { return Const::new_error(self.tcx(), guar); } @@ -2888,11 +2884,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let _ = self .prohibit_generic_args(leading_segments.iter(), GenericsArgsErrExtend::None); let args = self.lower_generic_args_of_path_segment(span, did, segment); - ty::Const::new_alias( - tcx, - ty::IsRigid::No, - ty::AliasConst::new(tcx, ty::AliasConstKind::Free { def_id: did }, args), - ) + let alias = ty::AliasConst::new(tcx, kind, args); + ty::Const::new_alias(tcx, ty::IsRigid::No, alias) } Res::Def(kind @ DefKind::Ctor(ctor_of, CtorKind::Const), did) => { assert_eq!(opt_self_ty, None); @@ -3126,18 +3119,27 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { /// `def_id` is a const item used in the type system. Checks if that's OK. fn check_const_item_in_type_system( &self, - def_id: DefId, + alias_const: ty::AliasConstKind<'tcx>, span: Span, ) -> Result<(), ErrorGuaranteed> { let tcx = self.tcx(); - if tcx.features().generic_const_args() || tcx.is_direct_const(def_id) { + if tcx.features().generic_const_args() || alias_const.is_direct_const(tcx) { Ok(()) } else { let mut err = self .dcx() .struct_span_err(span, "use of `const` in the type system not marked as direct"); - if let Some(local_def_id) = def_id.as_local() { - if let Some(body_id) = tcx.hir_node_by_def_id(local_def_id).body_id() { + let hir_node = match alias_const { + ty::AliasConstKind::Projection { def_id } + | ty::AliasConstKind::InherentSelf { def_id } + | ty::AliasConstKind::InherentImpl { def_id } + | ty::AliasConstKind::Free { def_id } + | ty::AliasConstKind::Anon { def_id } => { + def_id.as_local().map(|id| tcx.hir_node_by_def_id(id)) + } + }; + if let Some(hir_node) = hir_node { + if let Some(body_id) = hir_node.body_id() { let body_span = tcx.hir_body(body_id).value.span; err.multipart_suggestion( @@ -3148,10 +3150,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ], Applicability::MaybeIncorrect, ); - } else if let DefKind::AssocConst = tcx.def_kind(def_id) - && let DefKind::Trait = tcx.def_kind(tcx.parent(def_id)) - { - let node = tcx.hir_node_by_def_id(local_def_id).expect_trait_item(); + } else if let ty::AliasConstKind::Projection { .. } = alias_const { + let node = hir_node.expect_trait_item(); let sp = node.span.shrink_to_lo(); err.span_suggestion_verbose( sp, diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 07c98f884cebb..545516f2e41a0 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -425,12 +425,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { )); } else { msg += &format!(" but {} not reachable", pluralize!("is", suggs.len())); - err.span_suggestions( - span, - msg, - suggs, - Applicability::MaybeIncorrect, - ); + err.help(format!("{msg}:\n{}", suggs.join("").trim_end())); } }; if accessible_sugg.is_empty() { @@ -4001,6 +3996,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { candidates.into_iter().partition(|id| { let vis = self.tcx.visibility(*id); vis.is_accessible_from(scope, self.tcx) + // Visibility alone does not make `fn_name::Trait` an importable path. + // We need to make sure all parent are modules, otherwise the path is not importable. + && std::iter::successors(self.tcx.opt_parent(*id), |&id| self.tcx.opt_parent(id)) + .all(|id| self.tcx.def_kind(id) == DefKind::Mod) }); let sugg = |candidates: Vec<_>, visible| { @@ -4114,7 +4113,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if suggs.len() == 1 { err.help(msg); } else { - err.span_suggestions(span, msg, suggs, Applicability::MaybeIncorrect); + err.help(format!("{msg}:\n{}", suggs.join("").trim_end())); } }; if accessible_sugg.is_empty() { diff --git a/compiler/rustc_interface/src/callbacks.rs b/compiler/rustc_interface/src/callbacks.rs index 0d8b565b4e384..2b9170c80de21 100644 --- a/compiler/rustc_interface/src/callbacks.rs +++ b/compiler/rustc_interface/src/callbacks.rs @@ -13,7 +13,7 @@ use std::fmt; use std::fmt::Arguments; use std::panic::Location; -use rustc_errors::DiagInner; +use rustc_errors::{DiagInner, DiagLocation, Level}; use rustc_middle::dep_graph::{QuerySideEffect, TaskDepsRef}; use rustc_middle::ty::tls; use rustc_span::{Span, Symbol}; @@ -86,16 +86,26 @@ fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> write!(f, ")") } -fn emit_bug_diagnostic(span: Option, args: Arguments<'_>, location: &Location<'_>) { +/// Returns true if it printed the diagnostic, which happens if a `tcx` is available. +fn emit_bug_diagnostic( + span: Option, + args: Arguments<'_>, + location: &'static Location<'static>, +) -> bool { tls::with_opt(move |tcx| { if let Some(tcx) = tcx { - let message = format!("{location}: {args}"); + let mut diag = DiagInner::new(Level::Bug, format!("{location}: {args}")); if let Some(span) = span { - tcx.dcx().struct_span_bug(span, message) - } else { - tcx.dcx().struct_bug(message) + diag.span = span.into(); } - .emit_producing_nothing(); + diag.emitted_at = DiagLocation::from_location(location); + // Emit the bug without aborting. We let `bug_impl` do the abort because it has + // `#[track_caller]` which gives a better location. (`#[track_caller]` doesn't work + // here because this function is called via a function pointer.) + tcx.dcx().emit_diagnostic(diag); + true + } else { + false } }) } diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index fe7b1bf493051..b20dfe68d98e2 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -661,7 +661,7 @@ pub struct PatExtra<'tcx> { /// /// This is used by some diagnostics for non-exhaustive matches, to map /// the pattern node back to the `DefId` of its original constant. - pub expanded_const: Option, + pub expanded_const: Option>, /// User-written types that must be preserved into MIR so that they can be /// checked. diff --git a/compiler/rustc_middle/src/ty/abstract_const.rs b/compiler/rustc_middle/src/ty/abstract_const.rs index 2853c43ae079d..2227841923514 100644 --- a/compiler/rustc_middle/src/ty/abstract_const.rs +++ b/compiler/rustc_middle/src/ty/abstract_const.rs @@ -52,9 +52,14 @@ impl<'tcx> TyCtxt<'tcx> { } fn fold_const(&mut self, c: Const<'tcx>) -> Const<'tcx> { let ct = match c.kind() { - ty::ConstKind::Alias(_, alias_const) - if let Some(def_id) = alias_const.kind.opt_def_id() => - { + ty::ConstKind::Alias(_, alias_const) => { + let def_id = match alias_const.kind { + ty::AliasConstKind::Projection { def_id } + | ty::AliasConstKind::InherentSelf { def_id } + | ty::AliasConstKind::InherentImpl { def_id } + | ty::AliasConstKind::Free { def_id } + | ty::AliasConstKind::Anon { def_id } => def_id, + }; match self.tcx.thir_abstract_const(def_id) { Err(e) => ty::Const::new_error(self.tcx, e), Ok(Some(bac)) => { diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index befe4d67253a3..3414ee751585c 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -1229,8 +1229,7 @@ fn is_const_pat_that_looks_like_binding<'tcx>(tcx: TyCtxt<'tcx>, pat: &Pat<'tcx> // The pattern must be a named constant, and the name that appears in // the pattern's source text must resemble a plain identifier without any // `::` namespace separators or other non-identifier characters. - if let Some(def_id) = try { pat.extra.as_deref()?.expanded_const? } - && tcx.def_kind(def_id) == DefKind::Const + if let ty::AliasConstKind::Free { def_id } = pat.extra.as_deref()?.expanded_const? && let Ok(snippet) = tcx.sess.source_map().span_to_snippet(pat.span) && snippet.chars().all(|c| c.is_alphanumeric() || c == '_') { diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs index 55eef6006f278..e24ef5ea5b52d 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs @@ -224,7 +224,7 @@ impl<'tcx> ConstToPat<'tcx> { // Mark the pattern to indicate that it is the result of lowering a named // constant. This is used for diagnostics. - thir_pat.extra.get_or_insert_default().expanded_const = alias_const.kind.opt_def_id(); + thir_pat.extra.get_or_insert_default().expanded_const = Some(alias_const.kind); thir_pat } diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index 829fc5a600e8a..4f3c702c77ef9 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -257,7 +257,7 @@ pub(crate) enum InvalidComparisonOperatorSub { pub(crate) struct InvalidLogicalOperator { #[primary_span] pub span: Span, - pub incorrect: String, + pub incorrect: Symbol, #[subdiagnostic] pub sub: InvalidLogicalOperatorSub, } @@ -797,7 +797,7 @@ pub(crate) struct EqFieldInit { #[derive(Diagnostic)] #[diag("unexpected token: `...`")] -pub(crate) struct DotDotDot { +pub(crate) struct DotDotDotExprOp { #[primary_span] #[suggestion( "use `..` for an exclusive range", @@ -816,7 +816,7 @@ pub(crate) struct DotDotDot { #[derive(Diagnostic)] #[diag("unexpected token: `<-`")] -pub(crate) struct LeftArrowOperator { +pub(crate) struct LArrowExprOp { #[primary_span] #[suggestion( "if you meant to write a comparison against a negative value, add a space in between `<` and `-`", diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 40dbda2466de4..f5fa592585099 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -141,64 +141,6 @@ impl AttemptLocalParseRecovery { } } -/// Information for emitting suggestions and recovering from -/// C-style `i++`, `--i`, etc. -#[derive(Debug, Copy, Clone)] -struct IncDecRecovery { - /// Is this increment/decrement its own statement? - standalone: IsStandalone, - /// Is this an increment or decrement? - op: IncOrDec, - /// Is this pre- or postfix? - fixity: UnaryFixity, -} - -/// Is an increment or decrement expression its own statement? -#[derive(Debug, Copy, Clone)] -enum IsStandalone { - /// It's standalone, i.e., its own statement. - Standalone, - /// It's a subexpression, i.e., *not* standalone. - Subexpr, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum IncOrDec { - Inc, - Dec, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum UnaryFixity { - Pre, - Post, -} - -impl IncOrDec { - fn chr(&self) -> char { - match self { - Self::Inc => '+', - Self::Dec => '-', - } - } - - fn name(&self) -> &'static str { - match self { - Self::Inc => "increment", - Self::Dec => "decrement", - } - } -} - -impl std::fmt::Display for UnaryFixity { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Pre => write!(f, "prefix"), - Self::Post => write!(f, "postfix"), - } - } -} - /// Checks if the given `lookup` identifier is similar to any keyword symbol in `candidates`. /// /// This is a specialized version of [`Symbol::find_similar`] that constructs an error when a @@ -211,22 +153,6 @@ fn find_similar_kw(lookup: Ident, candidates: &[Symbol]) -> Option }) } -struct MultiSugg { - msg: String, - patches: Vec<(Span, String)>, - applicability: Applicability, -} - -impl MultiSugg { - fn emit(self, err: &mut Diag<'_>) { - err.multipart_suggestion(self.msg, self.patches, self.applicability); - } - - fn emit_verbose(self, err: &mut Diag<'_>) { - err.multipart_suggestion(self.msg, self.patches, self.applicability); - } -} - /// SnapshotParser is used to create a snapshot of the parser /// without causing duplicate errors being emitted when the `Parser` /// is dropped. @@ -1649,146 +1575,6 @@ impl<'a> Parser<'a> { Ok(()) } - pub(super) fn recover_from_prefix_increment( - &mut self, - operand_expr: Box, - op_span: Span, - start_stmt: bool, - ) -> PResult<'a, Box> { - let standalone = if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }; - let kind = IncDecRecovery { standalone, op: IncOrDec::Inc, fixity: UnaryFixity::Pre }; - self.recover_from_inc_dec(operand_expr, kind, op_span) - } - - pub(super) fn recover_from_postfix_increment( - &mut self, - operand_expr: Box, - op_span: Span, - start_stmt: bool, - ) -> PResult<'a, Box> { - let kind = IncDecRecovery { - standalone: if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }, - op: IncOrDec::Inc, - fixity: UnaryFixity::Post, - }; - self.recover_from_inc_dec(operand_expr, kind, op_span) - } - - pub(super) fn recover_from_postfix_decrement( - &mut self, - operand_expr: Box, - op_span: Span, - start_stmt: bool, - ) -> PResult<'a, Box> { - let kind = IncDecRecovery { - standalone: if start_stmt { IsStandalone::Standalone } else { IsStandalone::Subexpr }, - op: IncOrDec::Dec, - fixity: UnaryFixity::Post, - }; - self.recover_from_inc_dec(operand_expr, kind, op_span) - } - - fn recover_from_inc_dec( - &mut self, - base: Box, - kind: IncDecRecovery, - op_span: Span, - ) -> PResult<'a, Box> { - let mut err = self.dcx().struct_span_err( - op_span, - format!("Rust has no {} {} operator", kind.fixity, kind.op.name()), - ); - err.span_label(op_span, format!("not a valid {} operator", kind.fixity)); - - let help_base_case = |mut err: Diag<'_, ErrorGuaranteed>, base| { - err.help(format!("use `{}= 1` instead", kind.op.chr())); - err.emit(); - Ok(base) - }; - - // (pre, post) - let spans = match kind.fixity { - UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()), - UnaryFixity::Post => (base.span.shrink_to_lo(), op_span), - }; - - match kind.standalone { - IsStandalone::Standalone => { - self.inc_dec_standalone_suggest(kind, spans).emit_verbose(&mut err) - } - IsStandalone::Subexpr => { - let Ok(base_src) = self.span_to_snippet(base.span) else { - return help_base_case(err, base); - }; - match kind.fixity { - UnaryFixity::Pre => { - self.prefix_inc_dec_suggest(base_src, kind, spans).emit(&mut err) - } - UnaryFixity::Post => { - // won't suggest since we can not handle the precedences - // for example: `a + b++` has been parsed (a + b)++ and we can not suggest here - if !matches!(base.kind, ExprKind::Binary(_, _, _)) { - self.postfix_inc_dec_suggest(base_src, kind, spans).emit(&mut err) - } - } - } - } - } - Err(err) - } - - fn prefix_inc_dec_suggest( - &mut self, - base_src: String, - kind: IncDecRecovery, - (pre_span, post_span): (Span, Span), - ) -> MultiSugg { - MultiSugg { - msg: format!("use `{}= 1` instead", kind.op.chr()), - patches: vec![ - (pre_span, "{ ".to_string()), - (post_span, format!(" {}= 1; {} }}", kind.op.chr(), base_src)), - ], - applicability: Applicability::MachineApplicable, - } - } - - fn postfix_inc_dec_suggest( - &mut self, - base_src: String, - kind: IncDecRecovery, - (pre_span, post_span): (Span, Span), - ) -> MultiSugg { - let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" }; - MultiSugg { - msg: format!("use `{}= 1` instead", kind.op.chr()), - patches: vec![ - (pre_span, format!("{{ let {tmp_var} = ")), - (post_span, format!("; {} {}= 1; {} }}", base_src, kind.op.chr(), tmp_var)), - ], - applicability: Applicability::HasPlaceholders, - } - } - - fn inc_dec_standalone_suggest( - &mut self, - kind: IncDecRecovery, - (pre_span, post_span): (Span, Span), - ) -> MultiSugg { - let mut patches = Vec::new(); - - if !pre_span.is_empty() { - patches.push((pre_span, String::new())); - } - - patches.push((post_span, format!(" {}= 1", kind.op.chr()))); - MultiSugg { - msg: format!("use `{}= 1` instead", kind.op.chr()), - patches, - applicability: Applicability::MachineApplicable, - } - } - /// Tries to recover from associated item paths like `[T]::AssocItem` / `(T, U)::AssocItem`. /// Attempts to convert the base expression/pattern/type into a type, parses the `::AssocItem` /// tail, and combines them into a `::AssocItem` expression/pattern/type. diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 8238a6518e41d..58e98a64b5e41 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -34,8 +34,9 @@ use super::{ AttrWrapper, BlockMode, ClosureSpans, ExpTokenPair, ForceCollect, Parser, PathStyle, Restrictions, SemiColonMode, SeqSep, TokenType, Trailing, UsePreAttrPos, }; -use crate::diagnostics::ExprParenthesesNeeded; -use crate::{diagnostics, exp, maybe_recover_from_interpolated_ty_qpath}; +use crate::{exp, maybe_recover_from_interpolated_ty_qpath}; + +mod diagnostics; #[derive(Debug)] pub(super) enum DestructuredFloat { @@ -151,7 +152,6 @@ impl<'a> Parser<'a> { self.expected_token_types.insert(TokenType::Operator); while let Some(op) = self.check_assoc_op() { let lhs_span = self.interpolated_or_expr_span(&lhs); - let cur_op_span = self.token.span; let restrictions = if op.node.is_assign_like() { self.restrictions & Restrictions::NO_STRUCT_LITERAL } else { @@ -165,134 +165,60 @@ impl<'a> Parser<'a> { } { break; } - // Check for deprecated `...` syntax - if self.token == token::DotDotDot && op.node == AssocOp::Range(RangeLimits::Closed) { - self.err_dotdotdot_syntax(self.token.span); - } - if self.token == token::LArrow { - self.err_larrow_operator(self.token.span); - } + self.reject_dotdotdot_expr_op(); + self.reject_larrow_expr_op(); parsed_something = true; self.bump(); - if op.node.is_comparison() { - if let Some(expr) = self.check_no_chained_comparison(&lhs, &op)? { - return Ok((expr, parsed_something)); - } - } - // Look for JS' `===` and `!==` and recover - if let AssocOp::Binary(bop @ BinOpKind::Eq | bop @ BinOpKind::Ne) = op.node - && self.token == token::Eq - && self.prev_token.span.hi() == self.token.span.lo() + if op.node.is_comparison() + && let Some(expr) = self.check_no_chained_comparison(&lhs, &op)? { - let sp = op.span.to(self.token.span); - let sugg = bop.as_str().into(); - let invalid = format!("{sugg}="); - self.dcx().emit_err(diagnostics::InvalidComparisonOperator { - span: sp, - invalid: invalid.clone(), - sub: diagnostics::InvalidComparisonOperatorSub::Correctable { - span: sp, - invalid, - correct: sugg, - }, - }); - self.bump(); + return Ok((expr, parsed_something)); } - // Look for PHP's `<>` and recover - if op.node == AssocOp::Binary(BinOpKind::Lt) - && self.token == token::Gt - && self.prev_token.span.hi() == self.token.span.lo() - { - let sp = op.span.to(self.token.span); - self.dcx().emit_err(diagnostics::InvalidComparisonOperator { - span: sp, - invalid: "<>".into(), - sub: diagnostics::InvalidComparisonOperatorSub::Correctable { - span: sp, - invalid: "<>".into(), - correct: "!=".into(), - }, - }); - self.bump(); - } + self.recover_from_strict_eq_op(op); + self.recover_from_diamond_ne_op(); + self.recover_from_spaceship_cmp_op(); + self.recover_from_postfix_inc_op(&lhs, starts_stmt)?; + self.recover_from_postfix_dec_op(&lhs, starts_stmt)?; - // Look for C++'s `<=>` and recover - if op.node == AssocOp::Binary(BinOpKind::Le) - && self.token == token::Gt - && self.prev_token.span.hi() == self.token.span.lo() - { - let sp = op.span.to(self.token.span); - self.dcx().emit_err(diagnostics::InvalidComparisonOperator { - span: sp, - invalid: "<=>".into(), - sub: diagnostics::InvalidComparisonOperatorSub::Spaceship(sp), - }); - self.bump(); - } - - if self.prev_token == token::Plus - && self.token == token::Plus - && self.prev_token.span.between(self.token.span).is_empty() - { - let op_span = self.prev_token.span.to(self.token.span); - // Eat the second `+` - self.bump(); - lhs = self.recover_from_postfix_increment(lhs, op_span, starts_stmt)?; - continue; - } - - if self.prev_token == token::Minus - && self.token == token::Minus - && self.prev_token.span.between(self.token.span).is_empty() - && !self.look_ahead(1, |tok| tok.can_begin_expr()) - { - let op_span = self.prev_token.span.to(self.token.span); - // Eat the second `-` - self.bump(); - lhs = self.recover_from_postfix_decrement(lhs, op_span, starts_stmt)?; - continue; - } - - let op_span = op.span; - let op = op.node; - // Special cases: - if op == AssocOp::Cast { - lhs = self.parse_assoc_op_cast(lhs, lhs_span, op_span, ExprKind::Cast)?; - continue; - } else if let AssocOp::Range(limits) = op { - // If we didn't have to handle `x..`/`x..=`, it would be pretty easy to - // generalise it to the Fixity::None code. - lhs = self.parse_expr_range(prec, lhs, limits, cur_op_span)?; - break; - } - - let min_prec = match op.fixity() { + let min_prec = match op.node.fixity() { Fixity::Right => Bound::Included(prec), Fixity::Left | Fixity::None => Bound::Excluded(prec), }; - let rhs = self.with_res(restrictions - Restrictions::STMT_EXPR, |this| { - this.parse_expr_assoc(min_prec) - })?; - let span = self.mk_expr_sp(&lhs, lhs_span, op_span, rhs.span); - lhs = match op { + let finish_parsing_bin_op = |this: &mut Self| { + let rhs = this.with_res(restrictions - Restrictions::STMT_EXPR, |this| { + this.parse_expr_assoc(min_prec) + })?; + let span = this.mk_expr_sp(&lhs, lhs_span, op.span, rhs.span); + Ok((rhs, span)) + }; + + lhs = match op.node { AssocOp::Binary(ast_op) => { - let binary = self.mk_binary(respan(cur_op_span, ast_op), lhs, rhs); - self.mk_expr(span, binary) + let (rhs, span) = finish_parsing_bin_op(self)?; + self.mk_expr(span, self.mk_binary(respan(op.span, ast_op), lhs, rhs)) } - AssocOp::Assign => self.mk_expr(span, ExprKind::Assign(lhs, rhs, cur_op_span)), AssocOp::AssignOp(aop) => { - let aopexpr = self.mk_assign_op(respan(cur_op_span, aop), lhs, rhs); - self.mk_expr(span, aopexpr) + let (rhs, span) = finish_parsing_bin_op(self)?; + self.mk_expr(span, self.mk_assign_op(respan(op.span, aop), lhs, rhs)) + } + AssocOp::Assign => { + let (rhs, span) = finish_parsing_bin_op(self)?; + self.mk_expr(span, ExprKind::Assign(lhs, rhs, op.span)) } - AssocOp::Cast | AssocOp::Range(_) => { - self.dcx().span_bug(span, "AssocOp should have been handled by special case") + AssocOp::Cast => { + self.parse_assoc_op_cast(lhs, lhs_span, op.span, ExprKind::Cast)? } + AssocOp::Range(limits) => self.parse_expr_range(min_prec, lhs, limits, op.span)?, }; + + if let AssocOp::Range(_) = op.node { + break; + } } Ok((lhs, parsed_something)) @@ -337,64 +263,44 @@ impl<'a> Parser<'a> { /// but the next token implies this should be parsed as an expression. /// For example: `if let Some(x) = x { x } else { 0 } / 2`. fn error_found_expr_would_be_stmt(&self, lhs: &Expr) { - self.dcx().emit_err(diagnostics::FoundExprWouldBeStmt { + self.dcx().emit_err(crate::diagnostics::FoundExprWouldBeStmt { span: self.token.span, token: pprust::token_to_string(&self.token), - suggestion: ExprParenthesesNeeded::surrounding(lhs.span), + suggestion: crate::diagnostics::ExprParenthesesNeeded::surrounding(lhs.span), }); } /// Possibly translate the current token to an associative operator. /// The method does not advance the current token. - /// - /// Also performs recovery for `and` / `or` which are mistaken for `&&` and `||` respectively. pub(super) fn check_assoc_op(&self) -> Option> { - let (op, span) = match (AssocOp::from_token(&self.token), self.token.ident()) { - // When parsing const expressions, stop parsing when encountering `>`. - ( - Some( - AssocOp::Binary(BinOpKind::Shr | BinOpKind::Gt | BinOpKind::Ge) - | AssocOp::AssignOp(AssignOpKind::ShrAssign), - ), - _, - ) if self.restrictions.contains(Restrictions::CONST_EXPR) => { - return None; - } - // When recovering patterns as expressions, stop parsing when encountering an - // assignment `=`, an alternative `|`, or a range `..`. - ( - Some( - AssocOp::Assign - | AssocOp::AssignOp(_) - | AssocOp::Binary(BinOpKind::BitOr) - | AssocOp::Range(_), - ), - _, - ) if self.restrictions.contains(Restrictions::IS_PAT) => { - return None; - } - (Some(op), _) => (op, self.token.span), - (None, Some((Ident { name: sym::and, span }, IdentIsRaw::No))) - if self.may_recover() => - { - self.dcx().emit_err(diagnostics::InvalidLogicalOperator { - span: self.token.span, - incorrect: "and".into(), - sub: diagnostics::InvalidLogicalOperatorSub::Conjunction(self.token.span), - }); - (AssocOp::Binary(BinOpKind::And), span) - } - (None, Some((Ident { name: sym::or, span }, IdentIsRaw::No))) if self.may_recover() => { - self.dcx().emit_err(diagnostics::InvalidLogicalOperator { - span: self.token.span, - incorrect: "or".into(), - sub: diagnostics::InvalidLogicalOperatorSub::Disjunction(self.token.span), - }); - (AssocOp::Binary(BinOpKind::Or), span) - } - _ => return None, - }; - Some(respan(span, op)) + let op = AssocOp::from_token(&self.token); + + // When parsing const expressions, stop parsing when encountering `>`. + if self.restrictions.contains(Restrictions::CONST_EXPR) + && let Some(op) = op + && let AssocOp::Binary(BinOpKind::Shr | BinOpKind::Gt | BinOpKind::Ge) + | AssocOp::AssignOp(AssignOpKind::ShrAssign) = op + { + return None; + } + + // When recovering patterns as expressions, stop parsing when encountering an + // assignment `=`, an alternative `|`, or a range `..`. + if self.restrictions.contains(Restrictions::IS_PAT) + && let Some(op) = op + && let AssocOp::Assign + | AssocOp::AssignOp(_) + | AssocOp::Binary(BinOpKind::BitOr) + | AssocOp::Range(_) = op + { + return None; + } + + if let Some(op) = op { + return Some(respan(self.token.span, op)); + } + + self.recover_from_alpha_logic_op() } /// Checks if this expression is a successfully parsed statement. @@ -406,7 +312,7 @@ impl<'a> Parser<'a> { /// The other two variants are handled in `parse_prefix_range_expr` below. fn parse_expr_range( &mut self, - prec: ExprPrecedence, + min_prec: Bound, lhs: Box, limits: RangeLimits, cur_op_span: Span, @@ -414,7 +320,7 @@ impl<'a> Parser<'a> { let rhs = if self.is_at_start_of_range_notation_rhs() { let maybe_lt = self.token; Some( - self.parse_expr_assoc(Bound::Excluded(prec)) + self.parse_expr_assoc(min_prec) .map_err(|err| self.maybe_err_dotdotlt_syntax(maybe_lt, err))?, ) } else { @@ -441,14 +347,11 @@ impl<'a> Parser<'a> { /// Parses prefix-forms of range notation: `..expr`, `..`, `..=expr`. fn parse_expr_prefix_range(&mut self, attrs: AttrWrapper) -> PResult<'a, Box> { if !attrs.is_empty() { - let err = diagnostics::DotDotRangeAttribute { span: self.token.span }; + let err = crate::diagnostics::DotDotRangeAttribute { span: self.token.span }; self.dcx().emit_err(err); } - // Check for deprecated `...` syntax. - if self.token == token::DotDotDot { - self.err_dotdotdot_syntax(self.token.span); - } + self.reject_dotdotdot_expr_op(); debug_assert!( self.token.is_range_separator(), @@ -513,7 +416,7 @@ impl<'a> Parser<'a> { } // `+lit` token::Plus if this.look_ahead(1, |tok| tok.is_numeric_lit()) => { - let mut err = diagnostics::LeadingPlusNotSupported { + let mut err = crate::diagnostics::LeadingPlusNotSupported { span: lo, remove_plus: None, add_parentheses: None, @@ -521,7 +424,8 @@ impl<'a> Parser<'a> { // a block on the LHS might have been intended to be an expression instead if let Some(sp) = this.psess.ambiguous_block_expr_parse.borrow().get(&lo) { - err.add_parentheses = Some(ExprParenthesesNeeded::surrounding(*sp)); + err.add_parentheses = + Some(crate::diagnostics::ExprParenthesesNeeded::surrounding(*sp)); } else { err.remove_plus = Some(lo); } @@ -539,8 +443,14 @@ impl<'a> Parser<'a> { this.bump(); this.bump(); - let operand_expr = this.parse_expr_dot_or_call(attrs)?; - this.recover_from_prefix_increment(operand_expr, pre_span, starts_stmt) + let operand = this.parse_expr_dot_or_call(attrs)?; + return Err(this.report_inc_dec_op( + &operand, + starts_stmt, + diagnostics::IncOrDec::Inc, + diagnostics::UnaryFixity::Pre, + pre_span, + )); } token::Ident(..) if this.token.is_keyword(kw::Move) @@ -551,7 +461,7 @@ impl<'a> Parser<'a> { token::Ident(..) if this.may_recover() && this.is_mistaken_not_ident_negation() => { make_it!(this, attrs, |this, _| this.recover_not_expr(lo)) } - _ => return this.parse_expr_dot_or_call(attrs), + _ => this.parse_expr_dot_or_call(attrs), } } @@ -574,7 +484,7 @@ impl<'a> Parser<'a> { /// Recover on `~expr` in favor of `!expr`. fn recover_tilde_expr(&mut self, lo: Span) -> PResult<'a, (Span, ExprKind)> { - self.dcx().emit_err(diagnostics::TildeAsUnaryOperator(lo)); + self.dcx().emit_err(crate::diagnostics::TildeAsUnaryOperator(lo)); self.parse_expr_unary(lo, UnOp::Not) } @@ -605,14 +515,14 @@ impl<'a> Parser<'a> { let negated_token = self.look_ahead(1, |t| *t); let sub_diag = if negated_token.is_numeric_lit() { - diagnostics::NotAsNegationOperatorSub::SuggestNotBitwise + crate::diagnostics::NotAsNegationOperatorSub::SuggestNotBitwise } else if negated_token.is_bool_lit() { - diagnostics::NotAsNegationOperatorSub::SuggestNotLogical + crate::diagnostics::NotAsNegationOperatorSub::SuggestNotLogical } else { - diagnostics::NotAsNegationOperatorSub::SuggestNotDefault + crate::diagnostics::NotAsNegationOperatorSub::SuggestNotDefault }; - self.dcx().emit_err(diagnostics::NotAsNegationOperator { + self.dcx().emit_err(crate::diagnostics::NotAsNegationOperator { negated: negated_token.span, negated_desc: super::token_descr(&negated_token), // Span the `not` plus trailing whitespace to avoid @@ -683,7 +593,7 @@ impl<'a> Parser<'a> { match self.parse_expr_labeled(label, false) { Ok(expr) => { type_err.cancel(); - self.dcx().emit_err(diagnostics::MalformedLoopLabel { + self.dcx().emit_err(crate::diagnostics::MalformedLoopLabel { span: label.ident.span, suggestion: label.ident.span.shrink_to_lo(), }); @@ -709,23 +619,24 @@ impl<'a> Parser<'a> { let args_span = self.look_ahead(1, |t| t.span).to(span_after_type); match self.token.kind { - token::Lt => { - self.dcx().emit_err(diagnostics::ComparisonInterpretedAsGeneric { + token::Lt => self.dcx().emit_err( + crate::diagnostics::ComparisonInterpretedAsGeneric { comparison: self.token.span, r#type: pprust::path_to_string(&path), args: args_span, - suggestion: diagnostics::ComparisonInterpretedAsGenericSugg { - left: expr.span.shrink_to_lo(), - right: expr.span.shrink_to_hi(), - }, - }) - } + suggestion: + crate::diagnostics::ComparisonInterpretedAsGenericSugg { + left: expr.span.shrink_to_lo(), + right: expr.span.shrink_to_hi(), + }, + }, + ), token::Shl => { - self.dcx().emit_err(diagnostics::ShiftInterpretedAsGeneric { + self.dcx().emit_err(crate::diagnostics::ShiftInterpretedAsGeneric { shift: self.token.span, r#type: pprust::path_to_string(&path), args: args_span, - suggestion: diagnostics::ShiftInterpretedAsGenericSugg { + suggestion: crate::diagnostics::ShiftInterpretedAsGenericSugg { left: expr.span.shrink_to_lo(), right: expr.span.shrink_to_hi(), }, @@ -835,8 +746,10 @@ impl<'a> Parser<'a> { } fn error_remove_borrow_lifetime(&self, span: Span, lt_span: Span) { - self.dcx() - .emit_err(diagnostics::LifetimeInBorrowExpression { span, lifetime_span: lt_span }); + self.dcx().emit_err(crate::diagnostics::LifetimeInBorrowExpression { + span, + lifetime_span: lt_span, + }); } /// Parse `mut?` or `[ raw | pin ] [ const | mut ]`. @@ -895,7 +808,7 @@ impl<'a> Parser<'a> { // Recovery for `expr->suffix`. self.bump(); let span = self.prev_token.span; - self.dcx().emit_err(diagnostics::ExprRArrowCall { span }); + self.dcx().emit_err(crate::diagnostics::ExprRArrowCall { span }); true } else { self.eat(exp!(Dot)) @@ -1018,7 +931,7 @@ impl<'a> Parser<'a> { } _ => (span, actual), }; - self.dcx().emit_err(diagnostics::UnexpectedTokenAfterDot { span, actual }); + self.dcx().emit_err(crate::diagnostics::UnexpectedTokenAfterDot { span, actual }); } /// We need an identifier or integer, but the next token is a float. @@ -1135,7 +1048,7 @@ impl<'a> Parser<'a> { // Parse this both to give helpful error messages and to // verify it can be done with this parser setup. ExprKind::Index(ref left, ref _right, span) => { - self.dcx().emit_err(diagnostics::ArrayIndexInOffsetOf(span)); + self.dcx().emit_err(crate::diagnostics::ArrayIndexInOffsetOf(span)); current = left; } ExprKind::Lit(token::Lit { @@ -1144,10 +1057,12 @@ impl<'a> Parser<'a> { suffix, }) => { if let Some(suffix) = suffix { - self.dcx().emit_err(diagnostics::InvalidLiteralSuffixOnTupleIndex { - span: current.span, - suffix, - }); + self.dcx().emit_err( + crate::diagnostics::InvalidLiteralSuffixOnTupleIndex { + span: current.span, + suffix, + }, + ); } match self.break_up_float(symbol, current.span) { // 1e2 @@ -1187,14 +1102,15 @@ impl<'a> Parser<'a> { fields.insert(start_idx, *ident) } _ => { - self.dcx().emit_err(diagnostics::InvalidOffsetOf(current.span)); + self.dcx() + .emit_err(crate::diagnostics::InvalidOffsetOf(current.span)); break; } } break; } _ => { - self.dcx().emit_err(diagnostics::InvalidOffsetOf(current.span)); + self.dcx().emit_err(crate::diagnostics::InvalidOffsetOf(current.span)); break; } } @@ -1204,12 +1120,12 @@ impl<'a> Parser<'a> { break; } else if trailing_dot.is_none() { // This loop should only repeat if there is a trailing dot. - self.dcx().emit_err(diagnostics::InvalidOffsetOf(self.token.span)); + self.dcx().emit_err(crate::diagnostics::InvalidOffsetOf(self.token.span)); break; } } if let Some(dot) = trailing_dot { - self.dcx().emit_err(diagnostics::InvalidOffsetOf(dot)); + self.dcx().emit_err(crate::diagnostics::InvalidOffsetOf(dot)); } Ok(fields.into_iter().collect()) } @@ -1223,7 +1139,7 @@ impl<'a> Parser<'a> { suffix: Option, ) -> Box { if let Some(suffix) = suffix { - self.dcx().emit_err(diagnostics::InvalidLiteralSuffixOnTupleIndex { + self.dcx().emit_err(crate::diagnostics::InvalidLiteralSuffixOnTupleIndex { span: ident_span, suffix, }); @@ -1310,14 +1226,14 @@ impl<'a> Parser<'a> { err.cancel(); let type_str = pprust::path_to_string(&path); self.dcx() - .create_err(diagnostics::ParenthesesWithStructFields { + .create_err(crate::diagnostics::ParenthesesWithStructFields { span, - braces_for_struct: diagnostics::BracesForStructLiteral { + braces_for_struct: crate::diagnostics::BracesForStructLiteral { first: open_paren, second: close_paren, r#type: type_str.clone(), }, - no_fields_for_fn: diagnostics::NoFieldsForFnCall { + no_fields_for_fn: crate::diagnostics::NoFieldsForFnCall { r#type: type_str, fields: fields .into_iter() @@ -1419,7 +1335,7 @@ impl<'a> Parser<'a> { if let Some(args) = seg.args { // See `StashKey::GenericInFieldExpr` for more info on why we stash this. self.dcx() - .create_err(diagnostics::FieldExpressionWithGeneric(args.span())) + .create_err(crate::diagnostics::FieldExpressionWithGeneric(args.span())) .stash(seg.ident.span, StashKey::GenericInFieldExpr); } @@ -1491,7 +1407,9 @@ impl<'a> Parser<'a> { // If the input is something like `if a { 1 } else { 2 } | if a { 3 } else { 4 }` // then suggest parens around the lhs. if let Some(sp) = this.psess.ambiguous_block_expr_parse.borrow().get(&lo) { - err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); + err.subdiagnostic(crate::diagnostics::ExprParenthesesNeeded::surrounding( + *sp, + )); } err }) @@ -1689,7 +1607,8 @@ impl<'a> Parser<'a> { let (span, kind) = if self.eat(exp!(Bang)) { // MACRO INVOCATION expression if qself.is_some() { - self.dcx().emit_err(diagnostics::MacroInvocationWithQualifiedPath(path.span)); + self.dcx() + .emit_err(crate::diagnostics::MacroInvocationWithQualifiedPath(path.span)); } let lo = path.span; let mac = Box::new(MacCall { path, args: self.parse_delim_args()? }); @@ -1734,7 +1653,7 @@ impl<'a> Parser<'a> { { let (lit, _) = self.recover_unclosed_char(label_.ident, Parser::mk_token_lit_char, |self_| { - self_.dcx().create_err(diagnostics::UnexpectedTokenAfterLabel { + self_.dcx().create_err(crate::diagnostics::UnexpectedTokenAfterLabel { span: self_.token.span, remove_label: None, enclose_in_block: None, @@ -1746,7 +1665,7 @@ impl<'a> Parser<'a> { && (self.check_noexpect(&TokenKind::Comma) || self.check_noexpect(&TokenKind::Gt)) { // We're probably inside of a `Path<'a>` that needs a turbofish - let guar = self.dcx().emit_err(diagnostics::UnexpectedTokenAfterLabel { + let guar = self.dcx().emit_err(crate::diagnostics::UnexpectedTokenAfterLabel { span: self.token.span, remove_label: None, enclose_in_block: None, @@ -1754,7 +1673,7 @@ impl<'a> Parser<'a> { consume_colon = false; Ok(self.mk_expr_err(lo, guar)) } else { - let mut err = diagnostics::UnexpectedTokenAfterLabel { + let mut err = crate::diagnostics::UnexpectedTokenAfterLabel { span: self.token.span, remove_label: None, enclose_in_block: None, @@ -1791,7 +1710,7 @@ impl<'a> Parser<'a> { return expr; } - err.enclose_in_block = Some(diagnostics::UnexpectedTokenAfterLabelSugg { + err.enclose_in_block = Some(crate::diagnostics::UnexpectedTokenAfterLabelSugg { left: span.shrink_to_lo(), right: span.shrink_to_hi(), }); @@ -1807,7 +1726,7 @@ impl<'a> Parser<'a> { }?; if !ate_colon && consume_colon { - self.dcx().emit_err(diagnostics::RequireColonAfterLabeledExpression { + self.dcx().emit_err(crate::diagnostics::RequireColonAfterLabeledExpression { span: expr.span, label: lo, label_end: lo.between(tok_sp), @@ -1856,7 +1775,7 @@ impl<'a> Parser<'a> { self.bump(); // `catch` let span = lo.to(self.prev_token.span); - self.dcx().emit_err(diagnostics::DoCatchSyntaxRemoved { span }); + self.dcx().emit_err(crate::diagnostics::DoCatchSyntaxRemoved { span }); self.parse_try_block(lo) } @@ -1916,9 +1835,9 @@ impl<'a> Parser<'a> { // The value expression can be a labeled loop, see issue #86948, e.g.: // `loop { break 'label: loop { break 'label 42; }; }` let lexpr = self.parse_expr_labeled(label, true)?; - self.dcx().emit_err(diagnostics::LabeledLoopInBreak { + self.dcx().emit_err(crate::diagnostics::LabeledLoopInBreak { span: lexpr.span, - sub: diagnostics::WrapInParentheses::Expression { + sub: crate::diagnostics::WrapInParentheses::Expression { left: lexpr.span.shrink_to_lo(), right: lexpr.span.shrink_to_hi(), }, @@ -1945,8 +1864,8 @@ impl<'a> Parser<'a> { BREAK_WITH_LABEL_AND_LOOP, lo.to(expr.span), ast::CRATE_NODE_ID, - diagnostics::BreakWithLabelAndLoop { - sub: diagnostics::BreakWithLabelAndLoopSub { + crate::diagnostics::BreakWithLabelAndLoop { + sub: crate::diagnostics::BreakWithLabelAndLoopSub { left: span.shrink_to_lo(), right: span.shrink_to_hi(), }, @@ -2028,8 +1947,9 @@ impl<'a> Parser<'a> { self.bump(); // `#` let Some((ident, IdentIsRaw::No)) = self.token.ident() else { - let err = - self.dcx().create_err(diagnostics::ExpectedBuiltinIdent { span: self.token.span }); + let err = self + .dcx() + .create_err(crate::diagnostics::ExpectedBuiltinIdent { span: self.token.span }); return Err(err); }; self.psess.gated_spans.gate(sym::builtin_syntax, ident.span); @@ -2039,7 +1959,7 @@ impl<'a> Parser<'a> { let ret = if let Some(res) = parse(self, lo, ident)? { Ok(res) } else { - let err = self.dcx().create_err(diagnostics::UnknownBuiltinConstruct { + let err = self.dcx().create_err(crate::diagnostics::UnknownBuiltinConstruct { span: lo.to(ident.span), name: ident, }); @@ -2188,7 +2108,7 @@ impl<'a> Parser<'a> { } }); if let Some(recovered) = recovered { - self.dcx().emit_err(diagnostics::FloatLiteralRequiresIntegerPart { + self.dcx().emit_err(crate::diagnostics::FloatLiteralRequiresIntegerPart { span: recovered.span, suggestion: recovered.span.shrink_to_lo(), }); @@ -2322,9 +2242,9 @@ impl<'a> Parser<'a> { let mut snapshot = self.create_snapshot_for_diagnostic(); match snapshot.parse_expr_array_or_repeat(exp!(CloseBrace)) { Ok(arr) => { - let guar = self.dcx().emit_err(diagnostics::ArrayBracketsInsteadOfBraces { + let guar = self.dcx().emit_err(crate::diagnostics::ArrayBracketsInsteadOfBraces { span: arr.span, - sub: diagnostics::ArrayBracketsInsteadOfBracesSugg { + sub: crate::diagnostics::ArrayBracketsInsteadOfBracesSugg { left: lo, right: snapshot.prev_token.span, }, @@ -2370,7 +2290,7 @@ impl<'a> Parser<'a> { .span_to_snippet(snapshot.token.span) .is_ok_and(|snippet| snippet == "]") => { - return Err(self.dcx().create_err(diagnostics::MissingSemicolonBeforeArray { + return Err(self.dcx().create_err(crate::diagnostics::MissingSemicolonBeforeArray { open_delim: open_delim_span, semicolon: prev_span.shrink_to_hi(), })); @@ -2396,10 +2316,10 @@ impl<'a> Parser<'a> { } if self.token.is_metavar_block() { - self.dcx().emit_err(diagnostics::InvalidBlockMacroSegment { + self.dcx().emit_err(crate::diagnostics::InvalidBlockMacroSegment { span: self.token.span, context: lo.to(self.token.span), - wrap: diagnostics::WrapInExplicitBlock { + wrap: crate::diagnostics::WrapInExplicitBlock { lo: self.token.span.shrink_to_lo(), hi: self.token.span.shrink_to_hi(), }, @@ -2571,9 +2491,9 @@ impl<'a> Parser<'a> { // Check for `move async` and recover if self.check_keyword(exp!(Async)) { let move_async_span = self.token.span.with_lo(self.prev_token.span.data().lo); - Err(self - .dcx() - .create_err(diagnostics::AsyncMoveOrderIncorrect { span: move_async_span })) + Err(self.dcx().create_err(crate::diagnostics::AsyncMoveOrderIncorrect { + span: move_async_span, + })) } else { Ok(CaptureBy::Value { move_kw: move_kw_span }) } @@ -2583,9 +2503,9 @@ impl<'a> Parser<'a> { // Check for `use async` and recover if self.check_keyword(exp!(Async)) { let use_async_span = self.token.span.with_lo(self.prev_token.span.data().lo); - Err(self - .dcx() - .create_err(diagnostics::AsyncUseOrderIncorrect { span: use_async_span })) + Err(self.dcx().create_err(crate::diagnostics::AsyncUseOrderIncorrect { + span: use_async_span, + })) } else { Ok(CaptureBy::Use { use_kw: use_kw_span }) } @@ -2667,10 +2587,10 @@ impl<'a> Parser<'a> { ExprKind::Binary(Spanned { span: binop_span, .. }, _, right) if let ExprKind::Block(_, None) = right.kind => { - let guar = this.dcx().emit_err(diagnostics::IfExpressionMissingThenBlock { + let guar = this.dcx().emit_err(crate::diagnostics::IfExpressionMissingThenBlock { if_span: lo, missing_then_block_sub: - diagnostics::IfExpressionMissingThenBlockSub::UnfinishedCondition( + crate::diagnostics::IfExpressionMissingThenBlockSub::UnfinishedCondition( cond_span.shrink_to_lo().to(*binop_span), ), let_else_sub: None, @@ -2678,10 +2598,11 @@ impl<'a> Parser<'a> { std::mem::replace(right, this.mk_expr_err(binop_span.shrink_to_hi(), guar)) } ExprKind::Block(_, None) => { - let guar = this.dcx().emit_err(diagnostics::IfExpressionMissingCondition { - if_span: lo.with_neighbor(cond.span).shrink_to_hi(), - block_span: self.psess.source_map().start_point(cond_span), - }); + let guar = + this.dcx().emit_err(crate::diagnostics::IfExpressionMissingCondition { + if_span: lo.with_neighbor(cond.span).shrink_to_hi(), + block_span: self.psess.source_map().start_point(cond_span), + }); std::mem::replace(&mut cond, this.mk_expr_err(cond_span.shrink_to_hi(), guar)) } _ => { @@ -2699,13 +2620,14 @@ impl<'a> Parser<'a> { if let Some(block) = recover_block_from_condition(self) { block } else { - let let_else_sub = matches!(cond.kind, ExprKind::Let(..)) - .then(|| diagnostics::IfExpressionLetSomeSub { if_span: lo.until(cond_span) }); + let let_else_sub = matches!(cond.kind, ExprKind::Let(..)).then(|| { + crate::diagnostics::IfExpressionLetSomeSub { if_span: lo.until(cond_span) } + }); - let guar = self.dcx().emit_err(diagnostics::IfExpressionMissingThenBlock { + let guar = self.dcx().emit_err(crate::diagnostics::IfExpressionMissingThenBlock { if_span: lo, missing_then_block_sub: - diagnostics::IfExpressionMissingThenBlockSub::AddThenBlock( + crate::diagnostics::IfExpressionMissingThenBlockSub::AddThenBlock( cond_span.shrink_to_hi(), ), let_else_sub, @@ -2798,9 +2720,9 @@ impl<'a> Parser<'a> { /// Parses a `let $pat = $expr` pseudo-expression. fn parse_expr_let(&mut self, restrictions: Restrictions) -> PResult<'a, Box> { let recovered: Recovered = if !restrictions.contains(Restrictions::ALLOW_LET) { - let err = diagnostics::ExpectedExpressionFoundLet { + let err = crate::diagnostics::ExpectedExpressionFoundLet { span: self.token.span, - reason: diagnostics::ForbiddenLetReason::OtherForbidden, + reason: crate::diagnostics::ForbiddenLetReason::OtherForbidden, missing_let: None, comparison: None, }; @@ -2822,7 +2744,7 @@ impl<'a> Parser<'a> { CommaRecoveryMode::LikelyTuple, )?; if self.token == token::EqEq { - self.dcx().emit_err(diagnostics::ExpectedEqForLetExpr { + self.dcx().emit_err(crate::diagnostics::ExpectedEqForLetExpr { span: self.token.span, sugg_span: self.token.span, }); @@ -2888,7 +2810,7 @@ impl<'a> Parser<'a> { || matches!(cond.kind, ExprKind::MacCall(..))) => { - self.dcx().emit_err(diagnostics::ExpectedElseBlock { + self.dcx().emit_err(crate::diagnostics::ExpectedElseBlock { first_tok_span, first_tok, else_span, @@ -2924,7 +2846,7 @@ impl<'a> Parser<'a> { let attributes = x0.span.until(branch_span); let last = xn.span; let ctx = if is_ctx_else { "else" } else { "if" }; - self.dcx().emit_err(diagnostics::OuterAttributeNotAllowedOnIfElse { + self.dcx().emit_err(crate::diagnostics::OuterAttributeNotAllowedOnIfElse { last, branch_span, ctx_span, @@ -2939,7 +2861,7 @@ impl<'a> Parser<'a> { && let BinOpKind::And = binop && let ExprKind::If(cond, ..) = &right.kind { - Err(self.dcx().create_err(diagnostics::UnexpectedIfWithIf( + Err(self.dcx().create_err(crate::diagnostics::UnexpectedIfWithIf( binop_span.shrink_to_hi().to(cond.span.shrink_to_lo()), ))) } else { @@ -2989,12 +2911,12 @@ impl<'a> Parser<'a> { let right = self.prev_token.span.between(self.look_ahead(1, |t| t.span)); self.bump(); // ) err.cancel(); - self.dcx().emit_err(diagnostics::ParenthesesInForHead { + self.dcx().emit_err(crate::diagnostics::ParenthesesInForHead { span, // With e.g. `for (x) in y)` this would replace `(x) in y)` // with `x) in y)` which is syntactically invalid. // However, this is prevented before we get here. - sugg: diagnostics::ParenthesesInForHeadSugg { left, right }, + sugg: crate::diagnostics::ParenthesesInForHeadSugg { left, right }, }); Ok((self.mk_pat(start_span.to(right), ast::PatKind::Wild), expr)) } else { @@ -3029,7 +2951,7 @@ impl<'a> Parser<'a> { && self.token.kind != token::OpenBrace && self.may_recover() { - let guar = self.dcx().emit_err(diagnostics::MissingExpressionInForLoop { + let guar = self.dcx().emit_err(crate::diagnostics::MissingExpressionInForLoop { span: expr.span.shrink_to_lo(), }); let err_expr = self.mk_expr(expr.span, ExprKind::Err(guar)); @@ -3071,7 +2993,7 @@ impl<'a> Parser<'a> { let else_span = self.token.span; self.bump(); let else_clause = self.parse_expr_else()?; - self.dcx().emit_err(diagnostics::LoopElseNotSupported { + self.dcx().emit_err(crate::diagnostics::LoopElseNotSupported { span: else_span.to(else_clause.span), loop_kind, loop_kw, @@ -3085,18 +3007,18 @@ impl<'a> Parser<'a> { // Possibly using JS syntax (#75311). let span = self.token.span; self.bump(); - (span, Some(diagnostics::MissingInInForLoopSub::InNotOf(span))) + (span, Some(crate::diagnostics::MissingInInForLoopSub::InNotOf(span))) } else if self.eat(exp!(Eq)) { let span = self.prev_token.span; - (span, Some(diagnostics::MissingInInForLoopSub::InNotEq(span))) + (span, Some(crate::diagnostics::MissingInInForLoopSub::InNotEq(span))) } else { let span = self.prev_token.span.between(self.token.span); let sub = (!self.for_loop_head_has_in()) - .then_some(diagnostics::MissingInInForLoopSub::AddIn(span)); + .then_some(crate::diagnostics::MissingInInForLoopSub::AddIn(span)); (span, sub) }; - self.dcx().emit_err(diagnostics::MissingInInForLoop { span, sub }); + self.dcx().emit_err(crate::diagnostics::MissingInInForLoop { span, sub }); } /// Whether the `for` loop header already contains an `in` before its body. @@ -3166,7 +3088,7 @@ impl<'a> Parser<'a> { if let Some((ident, is_raw)) = self.token.lifetime() { // Disallow `'fn`, but with a better error message than `expect_lifetime`. if is_raw == IdentIsRaw::No && ident.without_first_quote().is_reserved() { - self.dcx().emit_err(diagnostics::KeywordLabel { span: ident.span }); + self.dcx().emit_err(crate::diagnostics::KeywordLabel { span: ident.span }); } self.bump(); @@ -3263,18 +3185,20 @@ impl<'a> Parser<'a> { let err = |this: &Parser<'_>, stmts: Vec| { let span = stmts[0].span.to(stmts[stmts.len() - 1].span); - let guar = this.dcx().emit_err(diagnostics::MatchArmBodyWithoutBraces { + let guar = this.dcx().emit_err(crate::diagnostics::MatchArmBodyWithoutBraces { statements: span, arrow: arrow_span, num_statements: stmts.len(), sub: if stmts.len() > 1 { - diagnostics::MatchArmBodyWithoutBracesSugg::AddBraces { + crate::diagnostics::MatchArmBodyWithoutBracesSugg::AddBraces { left: span.shrink_to_lo(), right: span.shrink_to_hi(), num_statements: stmts.len(), } } else { - diagnostics::MatchArmBodyWithoutBracesSugg::UseComma { semicolon: semi_sp } + crate::diagnostics::MatchArmBodyWithoutBracesSugg::UseComma { + semicolon: semi_sp, + } }, }); (span, guar) @@ -3492,7 +3416,7 @@ impl<'a> Parser<'a> { .is_ok(); if pattern_follows && snapshot.check(exp!(FatArrow)) { err.cancel(); - let guar = this.dcx().emit_err(diagnostics::MissingCommaAfterMatchArm { + let guar = this.dcx().emit_err(crate::diagnostics::MissingCommaAfterMatchArm { span: arm_span.shrink_to_hi(), }); return Ok(Recovered::Yes(guar)); @@ -3585,9 +3509,9 @@ impl<'a> Parser<'a> { checker.visit_expr(&mut guard.cond); let right = self.prev_token.span; - self.dcx().emit_err(diagnostics::ParenthesesInMatchPat { + self.dcx().emit_err(crate::diagnostics::ParenthesesInMatchPat { span: vec![left, right], - sugg: diagnostics::ParenthesesInMatchPatSugg { left, right }, + sugg: crate::diagnostics::ParenthesesInMatchPatSugg { left, right }, }); if let Some(guar) = checker.found_incorrect_let_chain { @@ -3664,7 +3588,9 @@ impl<'a> Parser<'a> { let (attrs, body) = self.parse_inner_attrs_and_block(None)?; if self.eat_keyword(exp!(Catch)) { - Err(self.dcx().create_err(diagnostics::CatchAfterTry { span: self.prev_token.span })) + Err(self + .dcx() + .create_err(crate::diagnostics::CatchAfterTry { span: self.prev_token.span })) } else { let span = span_lo.to(body.span); let gate_sym = @@ -3767,9 +3693,9 @@ impl<'a> Parser<'a> { match self.parse_expr_struct(qself.clone(), path.clone(), false) { Ok(expr) => { // This is a struct literal, but we don't accept them here. - self.dcx().emit_err(diagnostics::StructLiteralNotAllowedHere { + self.dcx().emit_err(crate::diagnostics::StructLiteralNotAllowedHere { span: expr.span, - sub: diagnostics::StructLiteralNotAllowedHereSugg { + sub: crate::diagnostics::StructLiteralNotAllowedHereSugg { left: path.span.shrink_to_lo(), right: expr.span.shrink_to_hi(), }, @@ -3811,10 +3737,12 @@ impl<'a> Parser<'a> { )?; let guar = if is_underscore_entry_point { - self.dcx().create_err(diagnostics::StructLiteralPlaceholderPath { span }).emit() + self.dcx() + .create_err(crate::diagnostics::StructLiteralPlaceholderPath { span }) + .emit() } else { self.dcx() - .create_err(diagnostics::StructLiteralWithoutPathLate { + .create_err(crate::diagnostics::StructLiteralWithoutPathLate { span: expr.span, suggestion_span: expr.span.shrink_to_lo(), }) @@ -3846,8 +3774,8 @@ impl<'a> Parser<'a> { let in_if_guard = self.restrictions.contains(Restrictions::IN_IF_GUARD); let async_block_err = |e: &mut Diag<'_>, span: Span| { - diagnostics::AsyncBlockIn2015 { span }.add_to_diag(e); - diagnostics::HelpUseLatestEdition::new().add_to_diag(e); + crate::diagnostics::AsyncBlockIn2015 { span }.add_to_diag(e); + crate::diagnostics::HelpUseLatestEdition::new().add_to_diag(e); }; while self.token != close.tok { @@ -4029,7 +3957,7 @@ impl<'a> Parser<'a> { if self.token != token::Comma { return; } - self.dcx().emit_err(diagnostics::CommaAfterBaseStruct { + self.dcx().emit_err(crate::diagnostics::CommaAfterBaseStruct { span: span.to(self.prev_token.span), comma: self.token.span, }); @@ -4040,7 +3968,8 @@ impl<'a> Parser<'a> { if !self.look_ahead(1, |t| t == close) && self.eat(exp!(DotDotDot)) { // recover from typo of `...`, suggest `..` let span = self.prev_token.span; - self.dcx().emit_err(diagnostics::MissingDotDot { token_span: span, sugg_span: span }); + self.dcx() + .emit_err(crate::diagnostics::MissingDotDot { token_span: span, sugg_span: span }); return true; } false @@ -4053,7 +3982,7 @@ impl<'a> Parser<'a> { let label = format!("'{}", ident.name); let ident = Ident::new(Symbol::intern(&label), ident.span); - self.dcx().emit_err(diagnostics::ExpectedLabelFoundIdent { + self.dcx().emit_err(crate::diagnostics::ExpectedLabelFoundIdent { span: ident.span, start: ident.span.shrink_to_lo(), }); @@ -4080,7 +4009,7 @@ impl<'a> Parser<'a> { || t == &token::CloseParen }); if is_wrong { - return Err(this.dcx().create_err(diagnostics::ExpectedStructField { + return Err(this.dcx().create_err(crate::diagnostics::ExpectedStructField { span: this.look_ahead(1, |t| t.span), ident_span: this.token.span, token: pprust::token_to_string(&this.look_ahead(1, |t| *t)), @@ -4121,20 +4050,12 @@ impl<'a> Parser<'a> { return; } - self.dcx().emit_err(diagnostics::EqFieldInit { + self.dcx().emit_err(crate::diagnostics::EqFieldInit { span: self.token.span, eq: field_name.span.shrink_to_hi().to(self.token.span), }); } - fn err_dotdotdot_syntax(&self, span: Span) { - self.dcx().emit_err(diagnostics::DotDotDot { span }); - } - - fn err_larrow_operator(&self, span: Span) { - self.dcx().emit_err(diagnostics::LeftArrowOperator { span }); - } - fn mk_assign_op(&self, assign_op: AssignOp, lhs: Box, rhs: Box) -> ExprKind { ExprKind::AssignOp(assign_op, lhs, rhs) } @@ -4282,9 +4203,9 @@ struct CondChecker<'a> { parser: &'a Parser<'a>, let_chains_policy: LetChainsPolicy, depth: u32, - forbid_let_reason: Option, - missing_let: Option, - comparison: Option, + forbid_let_reason: Option, + missing_let: Option, + comparison: Option, found_incorrect_let_chain: Option, } @@ -4311,12 +4232,13 @@ impl MutVisitor for CondChecker<'_> { ExprKind::Let(_, _, _, ref mut recovered @ Recovered::No) => { if let Some(reason) = self.forbid_let_reason { let error = match reason { - diagnostics::ForbiddenLetReason::NotSupportedOr(or_span) => { - self.parser.dcx().emit_err(diagnostics::OrInLetChain { span: or_span }) - } + crate::diagnostics::ForbiddenLetReason::NotSupportedOr(or_span) => self + .parser + .dcx() + .emit_err(crate::diagnostics::OrInLetChain { span: or_span }), _ => { let guar = self.parser.dcx().emit_err( - diagnostics::ExpectedExpressionFoundLet { + crate::diagnostics::ExpectedExpressionFoundLet { span, reason, missing_let: self.missing_let, @@ -4336,7 +4258,9 @@ impl MutVisitor for CondChecker<'_> { LetChainsPolicy::AlwaysAllowed => (), LetChainsPolicy::EditionDependent { current_edition } => { if !current_edition.at_least_rust_2024() || !span.at_least_rust_2024() { - self.parser.dcx().emit_err(diagnostics::LetChainPre2024 { span }); + self.parser + .dcx() + .emit_err(crate::diagnostics::LetChainPre2024 { span }); } } } @@ -4346,22 +4270,24 @@ impl MutVisitor for CondChecker<'_> { mut_visit::walk_expr(self, e); } ExprKind::Binary(Spanned { node: BinOpKind::Or, span: or_span }, _, _) - if let None | Some(diagnostics::ForbiddenLetReason::NotSupportedOr(_)) = + if let None | Some(crate::diagnostics::ForbiddenLetReason::NotSupportedOr(_)) = self.forbid_let_reason => { let forbid_let_reason = self.forbid_let_reason; self.forbid_let_reason = - Some(diagnostics::ForbiddenLetReason::NotSupportedOr(or_span)); + Some(crate::diagnostics::ForbiddenLetReason::NotSupportedOr(or_span)); mut_visit::walk_expr(self, e); self.forbid_let_reason = forbid_let_reason; } ExprKind::Paren(ref inner) - if let None | Some(diagnostics::ForbiddenLetReason::NotSupportedParentheses(_)) = + if let None + | Some(crate::diagnostics::ForbiddenLetReason::NotSupportedParentheses(_)) = self.forbid_let_reason => { let forbid_let_reason = self.forbid_let_reason; - self.forbid_let_reason = - Some(diagnostics::ForbiddenLetReason::NotSupportedParentheses(inner.span)); + self.forbid_let_reason = Some( + crate::diagnostics::ForbiddenLetReason::NotSupportedParentheses(inner.span), + ); mut_visit::walk_expr(self, e); self.forbid_let_reason = forbid_let_reason; } @@ -4399,13 +4325,14 @@ impl MutVisitor for CondChecker<'_> { if let Some(later_rhs) = find_let_some(rhs) && depth > 0 { - let guar = - self.parser.dcx().emit_err(diagnostics::LetChainMissingLet { + let guar = self.parser.dcx().emit_err( + crate::diagnostics::LetChainMissingLet { span: lhs.span, label_span: expr_span, rhs_span: later_rhs.span, sug_span: lhs.span.shrink_to_lo(), - }); + }, + ); self.found_incorrect_let_chain = Some(guar); } @@ -4413,7 +4340,8 @@ impl MutVisitor for CondChecker<'_> { } let forbid_let_reason = self.forbid_let_reason; - self.forbid_let_reason = Some(diagnostics::ForbiddenLetReason::OtherForbidden); + self.forbid_let_reason = + Some(crate::diagnostics::ForbiddenLetReason::OtherForbidden); let missing_let = self.missing_let; if let ExprKind::Binary(_, _, rhs) = &lhs.kind && let ExprKind::Path(_, _) @@ -4422,10 +4350,11 @@ impl MutVisitor for CondChecker<'_> { | ExprKind::Array(_) = rhs.kind { self.missing_let = - Some(diagnostics::MaybeMissingLet { span: rhs.span.shrink_to_lo() }); + Some(crate::diagnostics::MaybeMissingLet { span: rhs.span.shrink_to_lo() }); } let comparison = self.comparison; - self.comparison = Some(diagnostics::MaybeComparison { span: span.shrink_to_hi() }); + self.comparison = + Some(crate::diagnostics::MaybeComparison { span: span.shrink_to_hi() }); mut_visit::walk_expr(self, e); self.forbid_let_reason = forbid_let_reason; self.missing_let = missing_let; @@ -4447,7 +4376,8 @@ impl MutVisitor for CondChecker<'_> { | ExprKind::Tup(_) | ExprKind::Paren(_) => { let forbid_let_reason = self.forbid_let_reason; - self.forbid_let_reason = Some(diagnostics::ForbiddenLetReason::OtherForbidden); + self.forbid_let_reason = + Some(crate::diagnostics::ForbiddenLetReason::OtherForbidden); mut_visit::walk_expr(self, e); self.forbid_let_reason = forbid_let_reason; } @@ -4455,7 +4385,8 @@ impl MutVisitor for CondChecker<'_> { | ExprKind::Type(ref mut op, _) | ExprKind::UnsafeBinderCast(_, ref mut op, _) => { let forbid_let_reason = self.forbid_let_reason; - self.forbid_let_reason = Some(diagnostics::ForbiddenLetReason::OtherForbidden); + self.forbid_let_reason = + Some(crate::diagnostics::ForbiddenLetReason::OtherForbidden); self.visit_expr(op); self.forbid_let_reason = forbid_let_reason; } diff --git a/compiler/rustc_parse/src/parser/expr/diagnostics.rs b/compiler/rustc_parse/src/parser/expr/diagnostics.rs new file mode 100644 index 0000000000000..707ae5d34bc75 --- /dev/null +++ b/compiler/rustc_parse/src/parser/expr/diagnostics.rs @@ -0,0 +1,231 @@ +use rustc_ast::util::parser::AssocOp; +use rustc_ast::{BinOpKind, Expr, ExprKind, token}; +use rustc_errors::{Applicability, Diag, PResult}; +use rustc_span::{Span, Spanned, respan, sym}; + +use crate::diagnostics; +use crate::parser::Parser; + +impl<'a> Parser<'a> { + /// Recover from alphabetic logic operators `and` and `or` as found in e.g., Python and PHP. + pub(super) fn recover_from_alpha_logic_op(&self) -> Option> { + if self.may_recover() + && let Some((ident, token::IdentIsRaw::No)) = self.token.ident() + { + let (op, sub): (_, fn(_) -> _) = match ident.name { + sym::and => (BinOpKind::And, diagnostics::InvalidLogicalOperatorSub::Conjunction), + sym::or => (BinOpKind::Or, diagnostics::InvalidLogicalOperatorSub::Disjunction), + _ => return None, + }; + + self.dcx().emit_err(diagnostics::InvalidLogicalOperator { + span: self.token.span, + incorrect: ident.name, + sub: sub(self.token.span), + }); + + Some(respan(self.token.span, AssocOp::Binary(op))) + } else { + None + } + } + + /// Reject `...` being used as an expression operator. + pub(super) fn reject_dotdotdot_expr_op(&self) { + if self.token == token::DotDotDot { + self.dcx().emit_err(diagnostics::DotDotDotExprOp { span: self.token.span }); + } + } + + /// Reject `<-` being used as an expression operator. + pub(super) fn reject_larrow_expr_op(&self) { + if self.token == token::LArrow { + self.dcx().emit_err(diagnostics::LArrowExprOp { span: self.token.span }); + } + } + + /// Recover from strict equality operators `===` and `!==` as found in e.g., JS and PHP. + pub(super) fn recover_from_strict_eq_op(&mut self, op: Spanned) { + if let AssocOp::Binary(bop @ BinOpKind::Eq | bop @ BinOpKind::Ne) = op.node + && self.token == token::Eq + && self.prev_token.span.hi() == self.token.span.lo() + { + let sp = op.span.to(self.token.span); + let sugg = bop.as_str().into(); + let invalid = format!("{sugg}="); + self.dcx().emit_err(diagnostics::InvalidComparisonOperator { + span: sp, + invalid: invalid.clone(), + sub: diagnostics::InvalidComparisonOperatorSub::Correctable { + span: sp, + invalid, + correct: sugg, + }, + }); + self.bump(); + } + } + + /// Recover from inequality operator `<>` ("diamond") as found in e.g., PHP. + pub(super) fn recover_from_diamond_ne_op(&mut self) { + if let (token::Lt, token::Gt) = (self.prev_token.kind, self.token.kind) + && self.prev_token.span.hi() == self.token.span.lo() + { + let sp = self.prev_token.span.to(self.token.span); + self.dcx().emit_err(diagnostics::InvalidComparisonOperator { + span: sp, + invalid: "<>".into(), + sub: diagnostics::InvalidComparisonOperatorSub::Correctable { + span: sp, + invalid: "<>".into(), + correct: "!=".into(), + }, + }); + self.bump(); + } + } + + /// Recover from comparison operator `<=>` ("spaceship") as found in e.g., C++. + pub(super) fn recover_from_spaceship_cmp_op(&mut self) { + if let (token::Le, token::Gt) = (self.prev_token.kind, self.token.kind) + && self.prev_token.span.hi() == self.token.span.lo() + { + let sp = self.prev_token.span.to(self.token.span); + self.dcx().emit_err(diagnostics::InvalidComparisonOperator { + span: sp, + invalid: "<=>".into(), + sub: diagnostics::InvalidComparisonOperatorSub::Spaceship(sp), + }); + self.bump(); + } + } + + /// Recover from postfix increment operator `++` as found in many C-style languages. + pub(super) fn recover_from_postfix_inc_op( + &mut self, + lhs: &Expr, + starts_stmt: bool, + ) -> PResult<'a, ()> { + if let (token::Plus, token::Plus) = (self.prev_token.kind, self.token.kind) + && self.prev_token.span.hi() == self.token.span.lo() + { + let op_span = self.prev_token.span.to(self.token.span); + self.bump(); // eat the second `+` + Err(self.report_inc_dec_op(lhs, starts_stmt, IncOrDec::Inc, UnaryFixity::Post, op_span)) + } else { + Ok(()) + } + } + + /// Recover from postfix decrement operator `--` as found in many C-style languages. + pub(super) fn recover_from_postfix_dec_op( + &mut self, + lhs: &Expr, + starts_stmt: bool, + ) -> PResult<'a, ()> { + if let (token::Minus, token::Minus) = (self.prev_token.kind, self.token.kind) + && self.prev_token.span.hi() == self.token.span.lo() + && !self.look_ahead(1, |tok| tok.can_begin_expr()) + { + let op_span = self.prev_token.span.to(self.token.span); + self.bump(); // eat the second `-` + Err(self.report_inc_dec_op(lhs, starts_stmt, IncOrDec::Dec, UnaryFixity::Post, op_span)) + } else { + Ok(()) + } + } + + /// Report increment operator `++` & decrement operator `--` as found in many C-style languages. + pub(super) fn report_inc_dec_op( + &mut self, + base: &Expr, + starts_stmt: bool, + op: IncOrDec, + fixity: UnaryFixity, + op_span: Span, + ) -> Diag<'a> { + // FIXME: Don't return an error diag, emit the diag here *and* return a new expr of the form + // `$base += 1` / `$base -= 1` (taking `base: Expr` by value) for *proper* recovery. + // (Just emitting the diag would be insufficient since callers would most likely just + // use `$base` as the recovered AST node which would lead to annoying follow-up diags + // like "variable doesn't need to be mutable" getting emitted in some cases.) + + let mut err = { + let fixity = match fixity { + UnaryFixity::Pre => "prefix", + UnaryFixity::Post => "postfix", + }; + let op = match op { + IncOrDec::Inc => "increment", + IncOrDec::Dec => "decrement", + }; + self.dcx() + .struct_span_err(op_span, format!("Rust has no {fixity} {op} operator")) + .with_span_label(op_span, format!("not a valid {fixity} operator")) + }; + + let op = match op { + IncOrDec::Inc => "+= 1", + IncOrDec::Dec => "-= 1", + }; + let (pre_span, post_span) = match fixity { + UnaryFixity::Pre => (op_span, base.span.shrink_to_hi()), + UnaryFixity::Post => (base.span.shrink_to_lo(), op_span), + }; + + if starts_stmt { + let mut patches = Vec::new(); + if !pre_span.is_empty() { + patches.push((pre_span, String::new())); + } + patches.push((post_span, format!(" {op}"))); + err.multipart_suggestion( + format!("use `{op}` instead"), + patches, + Applicability::MachineApplicable, + ); + } else { + let Ok(base_src) = self.span_to_snippet(base.span) else { + err.help(format!("use `{op}` instead")); + return err; + }; + match fixity { + UnaryFixity::Pre => { + err.multipart_suggestion( + format!("use `{op}` instead"), + vec![(pre_span, "{ ".into()), (post_span, format!(" {op}; {base_src} }}"))], + Applicability::MachineApplicable, + ); + } + UnaryFixity::Post => { + // won't suggest since we can not handle the precedences + // for example: `a + b++` has been parsed (a + b)++ and we can not suggest here + if !matches!(base.kind, ExprKind::Binary(..)) { + let tmp_var = if base_src.trim() == "tmp" { "tmp_" } else { "tmp" }; + err.multipart_suggestion( + format!("use `{op}` instead"), + vec![ + (pre_span, format!("{{ let {tmp_var} = ")), + (post_span, format!("; {base_src} {op}; {tmp_var} }}")), + ], + Applicability::HasPlaceholders, + ); + } + } + } + } + err + } +} + +#[derive(Copy, Clone)] +pub(super) enum IncOrDec { + Inc, + Dec, +} + +#[derive(Copy, Clone)] +pub(super) enum UnaryFixity { + Pre, + Post, +} diff --git a/compiler/rustc_public/src/unstable/convert/stable/ty.rs b/compiler/rustc_public/src/unstable/convert/stable/ty.rs index ad1aa1b47132a..c3eebccb6b762 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/ty.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/ty.rs @@ -557,9 +557,13 @@ impl<'tcx> Stable<'tcx> for ty::Const<'tcx> { } ty::ConstKind::Param(param) => crate::ty::TyConstKind::Param(param.stable(tables, cx)), ty::ConstKind::Alias(_, alias_const) => { - let Some(def_id) = alias_const.kind.opt_def_id() else { - // FIXME: implement (both AliasTy and AliasConst will be needing this soon) - panic!("non-defid alias consts are not supported by rustc_public at the moment") + // rustc_public must change its API once we introduce a variant without a def_id. + let def_id = match alias_const.kind { + ty::AliasConstKind::Projection { def_id } + | ty::AliasConstKind::InherentSelf { def_id } + | ty::AliasConstKind::InherentImpl { def_id } + | ty::AliasConstKind::Free { def_id } + | ty::AliasConstKind::Anon { def_id } => def_id, }; crate::ty::TyConstKind::Unevaluated( tables.const_def(def_id), diff --git a/compiler/rustc_span/src/macros.rs b/compiler/rustc_span/src/macros.rs index 9b042a2b416c5..db8b7ebbfa11f 100644 --- a/compiler/rustc_span/src/macros.rs +++ b/compiler/rustc_span/src/macros.rs @@ -1,10 +1,14 @@ use std::fmt; -use std::panic::Location; +use std::panic::{Location, panic_any}; use rustc_data_structures::AtomicRef; use crate::Span; +/// Signifies that the compiler died with an explicit call to `.bug` or `.span_bug` rather than a +/// failed assertion, etc. +pub struct ExplicitBug; + /// A macro for triggering an ICE. /// Calling `bug` instead of panicking will result in a nicer error message and should /// therefore be preferred over `panic`/`unreachable` or others. @@ -40,12 +44,32 @@ pub macro span_bug($span:expr, $($arg:tt)+){ #[cold] #[track_caller] -pub fn bug_impl(span: Option, args: fmt::Arguments<'_>, location: &Location<'_>) -> ! { - (*EMIT_BUG_DIAGNOSTIC)(span, args, location); - panic!("{args}") +pub fn bug_impl( + span: Option, + args: fmt::Arguments<'_>, + location: &'static Location<'static>, +) -> ! { + // Emit the bug without aborting. + let emitted = (*EMIT_BUG_DIAGNOSTIC)(span, args, location); + + if emitted { + // Panic with `ExplicitBug`, which tells `report_ice` that it's expected, e.g. originating + // from `bug!` or `dcx.emit_bug(..)`. + panic_any(ExplicitBug); + } else { + // Panic with just a string, which means it's unexpected. + panic_any(format!("{args}")); + } } -pub static EMIT_BUG_DIAGNOSTIC: AtomicRef, fmt::Arguments<'_>, &Location<'_>)> = - AtomicRef::new(&(default_emit_diagnostic as _)); +pub static EMIT_BUG_DIAGNOSTIC: AtomicRef< + fn(Option, fmt::Arguments<'_>, &'static Location<'static>) -> bool, +> = AtomicRef::new(&(default_emit_bug_diagnostic as _)); -fn default_emit_diagnostic(_: Option, _: fmt::Arguments<'_>, _: &Location<'_>) {} +fn default_emit_bug_diagnostic( + _: Option, + _args: fmt::Arguments<'_>, + _location: &'static Location<'static>, +) -> bool { + false +} diff --git a/compiler/rustc_type_ir/src/const_kind.rs b/compiler/rustc_type_ir/src/const_kind.rs index dd0578610a2a0..f66322e034814 100644 --- a/compiler/rustc_type_ir/src/const_kind.rs +++ b/compiler/rustc_type_ir/src/const_kind.rs @@ -160,16 +160,6 @@ impl AliasConstKind { AliasConstKind::Anon { def_id } => interner.def_span(def_id.into()), } } - - pub fn opt_def_id(self) -> Option { - match self { - AliasConstKind::Projection { def_id } => Some(def_id.into()), - AliasConstKind::InherentSelf { def_id } => Some(def_id.into()), - AliasConstKind::InherentImpl { def_id } => Some(def_id.into()), - AliasConstKind::Free { def_id } => Some(def_id.into()), - AliasConstKind::Anon { def_id } => Some(def_id.into()), - } - } } rustc_index::newtype_index! { diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs index 7e793aea71e3c..229de8337d94b 100644 --- a/library/alloc/src/rc.rs +++ b/library/alloc/src/rc.rs @@ -4319,6 +4319,13 @@ impl UniqueRc { pub fn new(value: T) -> Self { Self::new_in(value, Global) } + + /// Like [`new`](Self::new), but returns an error if the allocation + /// fails, instead of calling [`handle_alloc_error`]. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + pub fn try_new(value: T) -> Result { + Self::try_new_in(value, Global) + } } impl UniqueRc { @@ -4330,8 +4337,8 @@ impl UniqueRc { /// point to the new [`Rc`]. #[cfg(not(no_global_oom_handling))] #[unstable(feature = "unique_rc_arc", issue = "112566")] - #[must_use] // #[unstable(feature = "allocator_api", issue = "32838")] + #[must_use] pub fn new_in(value: T, alloc: A) -> Self { let (ptr, alloc) = Box::into_non_null_with_allocator(Box::new_in( RcInner { @@ -4346,8 +4353,29 @@ impl UniqueRc { Self { ptr, _marker: PhantomData, _marker2: PhantomData, alloc } } - #[cfg(not(no_global_oom_handling))] - fn unwrap_with_allocator(this: Self) -> (T, A) { + /// Like [`new_in`](Self::new_in), but returns an error if the allocation + /// fails, instead of calling [`handle_alloc_error`]. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + // #[unstable(feature = "allocator_api", issue = "32838")] + pub fn try_new_in(value: T, alloc: A) -> Result { + let (ptr, alloc) = Box::into_non_null_with_allocator(Box::try_new_in( + RcInner { + strong: Cell::new(0), + // keep one weak reference so if all the weak pointers that are created are dropped + // the UniqueRc still stays valid. + weak: Cell::new(1), + value, + }, + alloc, + )?); + Ok(Self { ptr, _marker: PhantomData, _marker2: PhantomData, alloc }) + } + + /// Consumes the `UniqueRc`, returning its wrapped value and allocator. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + // #[unstable(feature = "allocator_api", issue = "32838")] + #[must_use] + pub fn unwrap_with_allocator(this: Self) -> (T, A) { let inner_ptr = this.ptr; let (data_ptr, alloc) = Self::into_raw_with_allocator(this); @@ -4361,6 +4389,13 @@ impl UniqueRc { (val, alloc) } + /// Consumes the `UniqueRc`, returning its wrapped value. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub fn unwrap(this: Self) -> T { + Self::unwrap_with_allocator(this).0 + } + /// Maps the value in a `UniqueRc`, reusing the allocation if possible. /// /// `f` is called on a reference to the value in the `UniqueRc`, and the result is returned, @@ -4392,11 +4427,10 @@ impl UniqueRc { unsafe { let (ptr, alloc) = UniqueRc::into_raw_with_allocator(this); let value = ptr.read(); - let mut allocation = + let allocation = UniqueRc::from_raw_with_allocator(ptr.cast::>(), alloc); - allocation.write(f(value)); - allocation.assume_init() + UniqueRc::write(allocation, f(value)) } } else { let (val, alloc) = UniqueRc::unwrap_with_allocator(this); @@ -4443,13 +4477,12 @@ impl UniqueRc { unsafe { let (ptr, alloc) = UniqueRc::into_raw_with_allocator(this); let value = ptr.read(); - let mut allocation = UniqueRc::from_raw_with_allocator( + let allocation = UniqueRc::from_raw_with_allocator( ptr.cast::>(), alloc, ); - allocation.write(f(value)?); - try { allocation.assume_init() } + try { UniqueRc::write(allocation, f(value)?) } } } else { let (val, alloc) = UniqueRc::unwrap_with_allocator(this); @@ -4477,7 +4510,6 @@ impl UniqueRc { } } - #[cfg(not(no_global_oom_handling))] fn into_raw_with_allocator(this: Self) -> (*const T, A) { let this = ManuallyDrop::new(this); // SAFETY: The copy of the allocator stored in `this` is forgotten @@ -4519,7 +4551,6 @@ impl UniqueRc { unsafe { self.ptr.as_ref() } } - #[cfg(not(no_global_oom_handling))] fn as_ptr(this: &Self) -> *const T { let ptr: *mut RcInner = NonNull::as_ptr(this.ptr); @@ -4530,7 +4561,6 @@ impl UniqueRc { } #[inline] - #[cfg(not(no_global_oom_handling))] fn into_inner_with_allocator(this: Self) -> (NonNull>, A) { let this = mem::ManuallyDrop::new(this); // SAFETY: Pointer is valid for reads. @@ -4538,7 +4568,6 @@ impl UniqueRc { } #[inline] - #[cfg(not(no_global_oom_handling))] unsafe fn from_inner_in(ptr: NonNull>, alloc: A) -> Self { Self { ptr, _marker: PhantomData, _marker2: PhantomData, alloc } } @@ -4560,9 +4589,35 @@ impl UniqueRc { } } -#[cfg(not(no_global_oom_handling))] impl UniqueRc, A> { - unsafe fn assume_init(self) -> UniqueRc { + /// Writes the value and converts to `UniqueRc`. + /// + /// This method converts similarly to [`assume_init`](Self::assume_init) but + /// writes `value` into it before conversion, thus guaranteeing safety. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub fn write(mut this: Self, value: T) -> UniqueRc { + // SAFETY: Writing initialises the wrapped value. + unsafe { + this.write(value); + this.assume_init() + } + } + + /// Converts to `UniqueRc`. + /// + /// # Safety + /// + /// As with [`MaybeUninit::assume_init`], + /// it is up to the caller to guarantee that the value + /// really is in an initialized state. + /// Calling this when the content is not yet fully initialized + /// causes immediate undefined behavior. + /// + /// [`MaybeUninit::assume_init`]: mem::MaybeUninit::assume_init + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub unsafe fn assume_init(self) -> UniqueRc { let (ptr, alloc) = UniqueRc::into_inner_with_allocator(self); // SAFETY: Upheld by caller. unsafe { UniqueRc::from_inner_in(ptr.cast(), alloc) } diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs index 52b3b13fa5a0e..c71544925bef7 100644 --- a/library/alloc/src/sync.rs +++ b/library/alloc/src/sync.rs @@ -4798,6 +4798,13 @@ impl UniqueArc { pub fn new(value: T) -> Self { Self::new_in(value, Global) } + + /// Like [`new`](Self::new), but returns an error if the allocation + /// fails, instead of calling [`handle_alloc_error`]. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + pub fn try_new(value: T) -> Result { + Self::try_new_in(value, Global) + } } impl UniqueArc { @@ -4809,8 +4816,8 @@ impl UniqueArc { /// point to the new [`Arc`]. #[cfg(not(no_global_oom_handling))] #[unstable(feature = "unique_rc_arc", issue = "112566")] - #[must_use] // #[unstable(feature = "allocator_api", issue = "32838")] + #[must_use] pub fn new_in(data: T, alloc: A) -> Self { let (ptr, alloc) = Box::into_non_null_with_allocator(Box::new_in( ArcInner { @@ -4825,8 +4832,29 @@ impl UniqueArc { Self { ptr, _marker: PhantomData, _marker2: PhantomData, alloc } } - #[cfg(not(no_global_oom_handling))] - fn unwrap_with_allocator(this: Self) -> (T, A) { + /// Like [`new_in`](Self::new_in), but returns an error if the allocation + /// fails, instead of calling [`handle_alloc_error`]. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + // #[unstable(feature = "allocator_api", issue = "32838")] + pub fn try_new_in(data: T, alloc: A) -> Result { + let (ptr, alloc) = Box::into_non_null_with_allocator(Box::try_new_in( + ArcInner { + strong: atomic::AtomicUsize::new(0), + // keep one weak reference so if all the weak pointers that are created are dropped + // the UniqueArc still stays valid. + weak: atomic::AtomicUsize::new(1), + data, + }, + alloc, + )?); + Ok(Self { ptr, _marker: PhantomData, _marker2: PhantomData, alloc }) + } + + /// Consumes the `UniqueArc`, returning its wrapped value and allocator. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + // #[unstable(feature = "allocator_api", issue = "32838")] + #[must_use] + pub fn unwrap_with_allocator(this: Self) -> (T, A) { let inner_ptr = this.ptr; let (data_ptr, alloc) = Self::into_raw_with_allocator(this); @@ -4834,11 +4862,19 @@ impl UniqueArc { // We do not use the data inside ever again. let val = unsafe { data_ptr.read() }; + // Drop the strong-weak ref drop(Weak { ptr: inner_ptr, alloc: &alloc }); (val, alloc) } + /// Consumes the `UniqueArc`, returning its wrapped value. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub fn unwrap(this: Self) -> T { + Self::unwrap_with_allocator(this).0 + } + /// Maps the value in a `UniqueArc`, reusing the allocation if possible. /// /// `f` is called on a reference to the value in the `UniqueArc`, and the result is returned, @@ -4870,11 +4906,10 @@ impl UniqueArc { unsafe { let (ptr, alloc) = UniqueArc::into_raw_with_allocator(this); let value = ptr.read(); - let mut allocation = + let allocation = UniqueArc::from_raw_with_allocator(ptr.cast::>(), alloc); - allocation.write(f(value)); - allocation.assume_init() + UniqueArc::write(allocation, f(value)) } } else { let (val, alloc) = UniqueArc::unwrap_with_allocator(this); @@ -4921,13 +4956,12 @@ impl UniqueArc { unsafe { let (ptr, alloc) = UniqueArc::into_raw_with_allocator(this); let value = ptr.read(); - let mut allocation = UniqueArc::from_raw_with_allocator( + let allocation = UniqueArc::from_raw_with_allocator( ptr.cast::>(), alloc, ); - allocation.write(f(value)?); - try { allocation.assume_init() } + try { UniqueArc::write(allocation, f(value)?) } } } else { let (val, alloc) = UniqueArc::unwrap_with_allocator(this); @@ -4955,7 +4989,6 @@ impl UniqueArc { } } - #[cfg(not(no_global_oom_handling))] fn into_raw_with_allocator(this: Self) -> (*const T, A) { let this = ManuallyDrop::new(this); // SAFETY: The copy of the allocator stored in `this` is forgotten @@ -4998,7 +5031,6 @@ impl UniqueArc { unsafe { self.ptr.as_ref() } } - #[cfg(not(no_global_oom_handling))] fn as_ptr(this: &Self) -> *const T { let ptr: *mut ArcInner = NonNull::as_ptr(this.ptr); @@ -5009,7 +5041,6 @@ impl UniqueArc { } #[inline] - #[cfg(not(no_global_oom_handling))] fn into_inner_with_allocator(this: Self) -> (NonNull>, A) { let this = mem::ManuallyDrop::new(this); // SAFETY: Pointer is valid for reads and only read once. @@ -5017,7 +5048,6 @@ impl UniqueArc { } #[inline] - #[cfg(not(no_global_oom_handling))] unsafe fn from_inner_in(ptr: NonNull>, alloc: A) -> Self { Self { ptr, _marker: PhantomData, _marker2: PhantomData, alloc } } @@ -5051,9 +5081,35 @@ impl UniqueArc { } } -#[cfg(not(no_global_oom_handling))] impl UniqueArc, A> { - unsafe fn assume_init(self) -> UniqueArc { + /// Writes the value and converts to `UniqueArc`. + /// + /// This method converts similarly to [`assume_init`](Self::assume_init) but + /// writes `value` into it before conversion, thus guaranteeing safety. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub fn write(mut this: Self, value: T) -> UniqueArc { + // SAFETY: Writing initialises the wrapped value. + unsafe { + this.write(value); + this.assume_init() + } + } + + /// Converts to `UniqueArc`. + /// + /// # Safety + /// + /// As with [`MaybeUninit::assume_init`], + /// it is up to the caller to guarantee that the value + /// really is in an initialized state. + /// Calling this when the content is not yet fully initialized + /// causes immediate undefined behavior. + /// + /// [`MaybeUninit::assume_init`]: mem::MaybeUninit::assume_init + #[unstable(feature = "unique_rc_arc", issue = "112566")] + #[must_use] + pub unsafe fn assume_init(self) -> UniqueArc { let (ptr, alloc) = UniqueArc::into_inner_with_allocator(self); // SAFETY: Upheld by caller. unsafe { UniqueArc::from_inner_in(ptr.cast(), alloc) } diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 21f8e264db953..b308cb7f85df2 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -416,6 +416,55 @@ cfg_select! { #[unstable(feature = "c_variadic_va_arg_safe", issue = "162911", implied_by = "c_variadic")] unsafe impl VaArgSafe for f64 {} +// Implement `VaArgSafe` for f128 on targets where either: +// +// - clang provides `__float128` +// - `long double` is IEEE f128 on the platform. +// +// When updating this cfg, also update the tests to match. Currently this condition +// is duplicated in: +// +// - tests/ui/c-variadic/roundtrip.rs +// - tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs +// +// # Known incompatibilities +// +// Testing versus clang exposed bugs in clang. GCC has no known incompatibilities. +// +// - Clang <= 23 on sparc, see https://github.com/llvm/llvm-project/pull/214981. +// - Clang <= 23 on x86, see https://github.com/llvm/llvm-project/issues/217747. +cfg_select! { + any( + all(target_arch = "x86_64", not(target_vendor = "apple"), not(target_env = "msvc")), + all(target_arch = "x86", not(target_vendor = "apple"), not(target_env = "msvc")), + // PowerPC requires VSX (only little endian has it enabled by default). + all(target_arch = "powerpc64", target_feature = "vsx"), + all( + not(windows), + not(target_vendor = "apple"), + any( + target_arch = "aarch64", + target_arch = "loongarch32", + target_arch = "loongarch64", + target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "wasm32", + target_arch = "wasm64", + ), + ), + ) => { + #[unstable_feature_bound(f128)] + #[unstable(feature = "f128", issue = "116909")] + unsafe impl VaArgSafe for f128 {} + } + _ => { /* unsupported */ } +} + #[unstable(feature = "c_variadic_va_arg_safe", issue = "162911", implied_by = "c_variadic")] unsafe impl VaArgSafe for *mut T {} #[unstable(feature = "c_variadic_va_arg_safe", issue = "162911", implied_by = "c_variadic")] diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 148f1c32b08b9..6607d5bf5771e 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -775,6 +775,270 @@ fn file_test_io_seek_read_write() { check!(fs::remove_file(&filename)); } +#[test] +#[cfg(windows)] +fn file_test_io_seek_read_exact_write_all() { + use crate::os::windows::fs::FileExt; + + let tmpdir = tmpdir(); + let filename = tmpdir.join("file_rt_io_file_test_seek_read_exact_write_all.txt"); + let mut buf = [0; 256]; + let write1 = "asdf"; + let write2 = "qwer-"; + let write3 = "-zxcv"; + let content = "qwer-asdf-zxcv"; + { + let oo = OpenOptions::new().create_new(true).write(true).read(true).clone(); + let mut rw = check!(oo.open(&filename)); + check!(rw.seek_write_all(write1.as_bytes(), 5)); + assert_eq!(check!(rw.stream_position()), 9); + check!(rw.seek_read_exact(&mut buf[..write1.len()], 5)); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.stream_position()), 9); + assert_eq!(check!(rw.seek(SeekFrom::Start(0))), 0); + assert_eq!(check!(rw.write(write2.as_bytes())), write2.len()); + assert_eq!(check!(rw.stream_position()), 5); + assert_eq!(check!(rw.read(&mut buf)), write1.len()); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.stream_position()), 9); + check!(rw.seek_read_exact(&mut buf[..write2.len()], 0)); + assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2)); + assert_eq!(check!(rw.stream_position()), 5); + check!(rw.seek_write_all(write3.as_bytes(), 9)); + assert_eq!(check!(rw.stream_position()), 14); + } + { + let mut read = check!(File::open(&filename)); + check!(read.seek_read_exact(&mut buf[..content.len()], 0)); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.stream_position()), 14); + assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); + check!(read.seek_read_exact(&mut buf[..content.len()], 0)); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.stream_position()), 14); + assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); + assert_eq!(check!(read.read(&mut buf)), write3.len()); + assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3)); + assert_eq!(check!(read.stream_position()), 14); + check!(read.seek_read_exact(&mut buf[..content.len()], 0)); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.stream_position()), 14); + assert!(read.seek_read_exact(&mut buf, 14).is_err()); + assert!(read.seek_read_exact(&mut buf, 15).is_err()); + } + check!(fs::remove_file(&filename)); +} + +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_1() { + use crate::os::windows::fs::FileExt; + + // Test when seek_read_exact(), seek_write_all() are called with empty buffers. + struct MockFile {} + + impl FileExt for MockFile { + fn seek_read(&self, _buf: &mut [u8], _offset: u64) -> io::Result { + panic!("should not be called"); + } + + fn seek_write(&self, _buf: &[u8], _offset: u64) -> io::Result { + panic!("should not be called"); + } + } + + let mock_file = MockFile {}; + check!(mock_file.seek_read_exact(&mut [], 0)); + check!(mock_file.seek_write_all(&[], 0)); + check!(mock_file.seek_read_exact(&mut [], 420)); + check!(mock_file.seek_write_all(&[], 420)); +} + +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_2() { + use crate::os::windows::fs::FileExt; + + // Test when seek_read(), seek_write() return Ok(0) + struct MockFile { + expected_offset: u64, + } + + impl FileExt for MockFile { + fn seek_read(&self, _buf: &mut [u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + Ok(0) + } + + fn seek_write(&self, _buf: &[u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + Ok(0) + } + } + + { + let mock_file = MockFile { expected_offset: 0 }; + let mut buf = [0; 256]; + assert_eq!( + mock_file.seek_read(&mut buf, 0).unwrap_err().kind(), + io::ErrorKind::UnexpectedEof + ); + assert_eq!(mock_file.seek_write(&buf, 0).unwrap_err().kind(), io::ErrorKind::WriteZero); + } + + { + let mock_file = MockFile { expected_offset: 420 }; + let mut buf = [0; 256]; + assert_eq!( + mock_file.seek_read(&mut buf, 420).unwrap_err().kind(), + io::ErrorKind::UnexpectedEof + ); + assert_eq!(mock_file.seek_write(&buf, 420).unwrap_err().kind(), io::ErrorKind::WriteZero); + } +} + +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_3() { + use crate::os::windows::fs::FileExt; + + // Test that Err other than io::ErrorKind::Interrupted are propagated up. + struct MockFile { + expected_offset: u64, + } + + impl FileExt for MockFile { + fn seek_read(&self, _buf: &mut [u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + Err(io::Error::new(io::ErrorKind::PermissionDenied, "seek_read")) + } + + fn seek_write(&self, _buf: &[u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + Err(io::Error::new(io::ErrorKind::ConnectionRefused, "seek_write")) + } + } + + { + let mock_file = MockFile { expected_offset: 0 }; + let mut buf = [0; 256]; + assert_eq!( + mock_file.seek_read(&mut buf, 0).unwrap_err().kind(), + io::ErrorKind::PermissionDenied + ); + assert_eq!( + mock_file.seek_write(&buf, 0).unwrap_err().kind(), + io::ErrorKind::ConnectionRefused + ); + } + + { + let mock_file = MockFile { expected_offset: 420 }; + let mut buf = [0; 256]; + assert_eq!( + mock_file.seek_read(&mut buf, 420).unwrap_err().kind(), + io::ErrorKind::PermissionDenied + ); + assert_eq!( + mock_file.seek_write(&buf, 420).unwrap_err().kind(), + io::ErrorKind::ConnectionRefused + ); + } + // FIXME: Cover io::ErrorKind::Interrupted, but don't infinite loop ;) +} + +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_4() { + use crate::os::windows::fs::FileExt; + + const MSG: &[u8] = + b"The Rust programming language helps you write faster, more reliable software."; + + // Test when the entire read or write is satisfied by only one call to seek_read() or + // seek_write(), respectively. + struct MockFile { + expected_offset: u64, + } + + impl FileExt for MockFile { + fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + assert_eq!(buf.len(), MSG.len()); + assert_eq!(buf, &[0; MSG.len()]); + buf.copy_from_slice(MSG); + Ok(MSG.len()) + } + + fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + assert_eq!(buf.len(), MSG.len()); + assert_eq!(buf, MSG); + Ok(MSG.len()) + } + } + + { + let mock_file = MockFile { expected_offset: 0 }; + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, 0)); + assert_eq!(&buf, MSG); + check!(mock_file.seek_write_all(&buf, 0)); + } + + { + let mock_file = MockFile { expected_offset: 420 }; + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, 420)); + assert_eq!(&buf, MSG); + check!(mock_file.seek_write_all(&buf, 420)); + } +} + +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_5() { + use crate::os::windows::fs::FileExt; + + const MSG: &[u8] = + b"Rust is for students and those who are interested in learning about systems concepts."; + + // Test pathological case where seek_read(), seek_write() only do 1 byte per call, return Ok(1) + struct MockFile { + base_offset: u64, + } + + impl FileExt for MockFile { + fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result { + let offset = (offset - self.base_offset) as usize; + buf[0..1].copy_from_slice(&MSG[offset..offset + 1]); + Ok(1) + } + + fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result { + let offset = (offset - self.base_offset) as usize; + assert_eq!(buf[0..1], MSG[offset..offset + 1]); + Ok(1) + } + } + + { + let mock_file = MockFile { base_offset: 0 }; + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, 0)); + assert_eq!(&buf, MSG); + check!(mock_file.seek_write_all(&buf, 0)); + } + + { + let mock_file = MockFile { base_offset: 420 }; + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, 420)); + assert_eq!(&buf, MSG); + check!(mock_file.seek_write_all(&buf, 420)); + } +} + #[test] #[cfg(windows)] fn test_seek_read_buf() { diff --git a/library/std/src/num/f32.rs b/library/std/src/num/f32.rs index ac3f2e263195d..b89c71dd1abe1 100644 --- a/library/std/src/num/f32.rs +++ b/library/std/src/num/f32.rs @@ -80,6 +80,11 @@ impl f32 { /// /// This function always returns the precise result. /// + /// On most hardware platforms, [`round_ties_even`](Self::round_ties_even) may execute faster + /// than `round`. If both rounding methods fit the use case, consider using `round_ties_even`. + /// Note that the two methods apply different rounding rules to values exactly halfway between + /// two integers. + /// /// # Examples /// /// ``` diff --git a/library/std/src/num/f64.rs b/library/std/src/num/f64.rs index 9b3086b1ce12e..c3f5d8cca6014 100644 --- a/library/std/src/num/f64.rs +++ b/library/std/src/num/f64.rs @@ -80,6 +80,11 @@ impl f64 { /// /// This function always returns the precise result. /// + /// On most hardware platforms, [`round_ties_even`](Self::round_ties_even) may execute faster + /// than `round`. If both rounding methods fit the use case, consider using `round_ties_even`. + /// Note that the two methods apply different rounding rules to values exactly halfway between + /// two integers. + /// /// # Examples /// /// ``` diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index 21560638c1d0f..69975e1d4e232 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -50,6 +50,67 @@ pub trait FileExt { #[stable(feature = "file_offset", since = "1.15.0")] fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result; + /// Seeks to a given position and reads the exact number of bytes required to fill `buf`. + /// + /// The offset is relative to the start of the file and thus independent + /// from the current cursor. The current cursor **is** affected by this + /// function, it is set to the end of the read. + /// + /// Similar to [`io::Read::read_exact`] but uses [`seek_read`] instead of `read`. + /// + /// [`seek_read`]: FileExt::seek_read + /// + /// # Errors + /// + /// If this function encounters an error of the kind + /// [`io::ErrorKind::Interrupted`] then the error is ignored and the operation + /// will continue. + /// + /// If this function encounters an "end of file" before completely filling + /// the buffer, it returns an error of the kind [`io::ErrorKind::UnexpectedEof`]. + /// The contents of `buf` are unspecified in this case. + /// + /// If any other read error is encountered then this function immediately + /// returns. The contents of `buf` are unspecified in this case. + /// + /// If this function returns an error, it is unspecified how many bytes it + /// has read, but it will never read more than would be necessary to + /// completely fill the buffer. + /// + /// # Examples + /// + #[cfg_attr(windows, doc = "```no_run")] + #[cfg_attr(not(windows), doc = "```ignore (needs windows)")] + /// use std::io; + /// use std::fs::File; + /// use std::os::windows::prelude::*; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::open("foo.txt")?; + /// let mut buffer = [0; 10]; + /// + /// // Read 10 bytes, starting 72 bytes from the + /// // start of the file. + /// file.seek_read_exact(&mut buffer[..], 72)?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "seek_read_exact_seek_write_all", issue = "162868")] + fn seek_read_exact(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.seek_read(buf, offset) { + Ok(0) => break, + Ok(n) => { + buf = &mut buf[n..]; + offset += n as u64; + } + Err(ref e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + } + if !buf.is_empty() { Err(io::Error::READ_EXACT_EOF) } else { Ok(()) } + } + /// Seeks to a given position and reads some bytes into the buffer. /// /// This is equivalent to the [`seek_read`](FileExt::seek_read) method, except that it is passed @@ -122,6 +183,60 @@ pub trait FileExt { /// ``` #[stable(feature = "file_offset", since = "1.15.0")] fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result; + + /// Seeks to a given position and attempts to write an entire buffer. + /// + /// The offset is relative to the start of the file and thus independent + /// from the current cursor. The current cursor **is** affected by this + /// function, it is set to the end of the write. + /// + /// This method will continuously call [`seek_write`] until there is no more data + /// to be written or an error of non-[`io::ErrorKind::Interrupted`] kind is + /// returned. This method will not return until the entire buffer has been + /// successfully written or such an error occurs. The first error that is + /// not of [`io::ErrorKind::Interrupted`] kind generated from this method will be + /// returned. + /// + /// # Errors + /// + /// This function will return the first error of + /// non-[`io::ErrorKind::Interrupted`] kind that [`seek_write`] returns. + /// + /// [`seek_write`]: FileExt::seek_write + /// + /// # Examples + /// + #[cfg_attr(windows, doc = "```no_run")] + #[cfg_attr(not(windows), doc = "```ignore (needs windows)")] + /// use std::fs::File; + /// use std::os::windows::prelude::*; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// // Write a byte string starting 72 bytes from + /// // the start of the file. + /// buffer.seek_write_all(b"some bytes", 72)?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "seek_read_exact_seek_write_all", issue = "162868")] + fn seek_write_all(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.seek_write(buf, offset) { + Ok(0) => { + return Err(io::Error::WRITE_ALL_EOF); + } + Ok(n) => { + buf = &buf[n..]; + offset += n as u64 + } + Err(ref e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + } + Ok(()) + } } #[stable(feature = "file_offset", since = "1.15.0")] diff --git a/package.json b/package.json index d9a3ced805ba5..876d81c1d60ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "browser-ui-test": "^0.25.0", + "browser-ui-test": "^0.25.2", "es-check": "^9.4.4", "eslint": "^8.57.1", "typescript": "^5.8.3" diff --git a/src/doc/rustc-dev-guide/.github/workflows/ci.yml b/src/doc/rustc-dev-guide/.github/workflows/ci.yml index 5ff245b168b07..23b590e4aebaf 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/ci.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/ci.yml @@ -22,11 +22,14 @@ jobs: BASE_SHA: ${{ github.event.pull_request.base.sha }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # linkcheck needs the base commit. fetch-depth: 0 + - name: Test CI scripts + run: ci/tests/report-scheduled-linkcheck-failure.sh + - name: Cache binaries id: mdbook-cache uses: actions/cache@v4 @@ -94,3 +97,34 @@ jobs: run: | # using split_inclusive that uses regex feature that uses an unstable feature RUSTC_BOOTSTRAP=1 cargo run --release --manifest-path ci/sembr/Cargo.toml src + + notify-scheduled-failure: + name: Open an issue if links are broken + needs: ci + if: failure() && github.event_name == 'schedule' + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + issues: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Open an issue or comment on an existing one + run: ci/report-scheduled-linkcheck-failure.sh + env: + GH_TOKEN: ${{ github.token }} + + close-scheduled-failure: + name: Close linkcheck issue if no links are broken + needs: ci + if: success() && github.event_name == 'schedule' + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Close issue + run: ci/close-scheduled-linkcheck-issues.sh + env: + GH_TOKEN: ${{ github.token }} diff --git a/src/doc/rustc-dev-guide/ci/close-scheduled-linkcheck-issues.sh b/src/doc/rustc-dev-guide/ci/close-scheduled-linkcheck-issues.sh new file mode 100755 index 0000000000000..7fb4c74cc128c --- /dev/null +++ b/src/doc/rustc-dev-guide/ci/close-scheduled-linkcheck-issues.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -euo pipefail + +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY must be set}" +: "${GITHUB_RUN_ID:?GITHUB_RUN_ID must be set}" +: "${GITHUB_SERVER_URL:?GITHUB_SERVER_URL must be set}" +: "${GH_TOKEN:?GH_TOKEN must be set}" + +successful_run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + +issue_numbers=$(gh issue list \ + --repo "$GITHUB_REPOSITORY" \ + --state open \ + --label C-broken-links \ + --label C-CI \ + --label A-linkcheck \ + --limit 100 \ + --json number,title \ + --jq '.[] | select(.title == "[automation] Dead links found") | .number') + +if [[ -z "$issue_numbers" ]]; then + echo "No scheduled linkcheck failure issue is open." + exit 0 +fi + +while read -r issue_number; do + gh issue close "$issue_number" \ + --repo "$GITHUB_REPOSITORY" \ + --comment "The scheduled link check is succeeding again: $successful_run_url" +done <<< "$issue_numbers" diff --git a/src/doc/rustc-dev-guide/ci/report-scheduled-linkcheck-failure.sh b/src/doc/rustc-dev-guide/ci/report-scheduled-linkcheck-failure.sh new file mode 100755 index 0000000000000..178b3b40a75bf --- /dev/null +++ b/src/doc/rustc-dev-guide/ci/report-scheduled-linkcheck-failure.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +set -euo pipefail + +: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY must be set}" +: "${GITHUB_RUN_ID:?GITHUB_RUN_ID must be set}" +: "${GITHUB_RUN_NUMBER:?GITHUB_RUN_NUMBER must be set}" +: "${GITHUB_SERVER_URL:?GITHUB_SERVER_URL must be set}" +: "${GITHUB_SHA:?GITHUB_SHA must be set}" +: "${GH_TOKEN:?GH_TOKEN must be set}" + +title="[automation] Dead links found" +job_url=$(gh api \ + "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/jobs" \ + --jq '.jobs[] | select(.name == "ci") | .html_url') + +issue_number=$(gh issue list \ + --repo "$GITHUB_REPOSITORY" \ + --state open \ + --label C-broken-links \ + --label C-CI \ + --label A-linkcheck \ + --limit 100 \ + --json number,title \ + --jq '[.[] | select(.title == "[automation] Dead links found")][0].number // empty') + +if [[ -n "$issue_number" ]]; then + gh issue comment "$issue_number" \ + --repo "$GITHUB_REPOSITORY" \ + --body "The scheduled link check failed again in [CI run #$GITHUB_RUN_NUMBER]($job_url)." + exit 0 +fi + +body=$(cat < String { new_content[new_n] = format!("{line} {}", next_line.trim_start()); new_content.remove(new_n + 1); skip_next = true; - } else { - const SEP: &str = ", "; - let Some((before_comma, after_comma)) = next_line.split_once(SEP) else { continue }; - if line.len() + before_comma.len() < limit - SEP.len() { - new_content[new_n] = format!("{line} {before_comma}{}", SEP.trim_end()); + continue; + } + const SEP: &str = ", "; + let indent = next_line.find(|ch: char| !ch.is_whitespace()).unwrap(); + if next_line.contains(SEP) { + let (before_sep, after_sep) = next_line.split_once(SEP).unwrap(); + if line.len() + before_sep.len() < limit - SEP.len() { + new_content[new_n] = + format!("{line} {}{}", before_sep.trim_start(), SEP.trim_end()); + new_n += 1; + new_content[new_n] = format!("{:indent$}{after_sep}", ""); + skip_next = true; + } + } else if line.contains(SEP) { + let (before_sep, after_sep) = line.rsplit_once(SEP).unwrap(); + if after_sep.len() + next_line.len() < limit { + new_content[new_n] = format!("{before_sep}{}", SEP.trim_end()); new_n += 1; - new_content[new_n] = after_comma.to_owned(); + new_content[new_n] = + format!("{:indent$}{after_sep} {}", "", next_line.trim_start()); skip_next = true; } } @@ -334,15 +347,20 @@ fn should_pass() { } #[test] -#[ignore] fn split_on_comma_of_current_line() { let original = " -Each derived value has a dependency on other values, which could themselves be either base or +Each derived value has a dependency, on other values, which could themselves be either base or derived. + + Each derived value has a dependency, on other values, which could themselves be either base or + derived. "; let expected = " -Each derived value has a dependency on other values, +Each derived value has a dependency, on other values, which could themselves be either base or derived. + + Each derived value has a dependency, on other values, + which could themselves be either base or derived. "; assert_eq!(expected, lengthen_lines(original, 100)) } @@ -352,10 +370,16 @@ fn split_on_comma_of_next_line() { let original = " Because of canonicalization of regions and inference variables, encountering a cycle doesn't mean that we would get an infinite proof tree. + + Because of canonicalization of regions and + inference variables, encountering a cycle doesn't mean that we would get an infinite proof tree. "; let expected = " Because of canonicalization of regions and inference variables, encountering a cycle doesn't mean that we would get an infinite proof tree. + + Because of canonicalization of regions and inference variables, + encountering a cycle doesn't mean that we would get an infinite proof tree. "; assert_eq!(expected, lengthen_lines(original, 100)) } diff --git a/src/doc/rustc-dev-guide/ci/tests/fakes/gh b/src/doc/rustc-dev-guide/ci/tests/fakes/gh new file mode 100755 index 0000000000000..197b83af250ca --- /dev/null +++ b/src/doc/rustc-dev-guide/ci/tests/fakes/gh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -euo pipefail + +command=$1 +{ + printf '%s' "$command" + shift + printf ' <%s>' "$@" + printf '\n' +} >> "$GH_MOCK_LOG" + +case "$command" in + api) + if [[ ${GH_MOCK_API_FAIL-} == 1 ]]; then + exit 1 + fi + echo "https://github.example/jobs/123" + ;; + issue) + if [[ ${1-} == list && -n ${GH_MOCK_ISSUE_NUMBER-} ]]; then + echo "$GH_MOCK_ISSUE_NUMBER" + fi + ;; +esac diff --git a/src/doc/rustc-dev-guide/ci/tests/report-scheduled-linkcheck-failure.sh b/src/doc/rustc-dev-guide/ci/tests/report-scheduled-linkcheck-failure.sh new file mode 100755 index 0000000000000..96526ef0ea350 --- /dev/null +++ b/src/doc/rustc-dev-guide/ci/tests/report-scheduled-linkcheck-failure.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) +script="$repo_root/ci/report-scheduled-linkcheck-failure.sh" +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT + +export PATH="$repo_root/ci/tests/fakes:$PATH" +export GH_MOCK_LOG="$tmp/gh.log" +export GITHUB_REPOSITORY="rust-lang/rustc-dev-guide" +export GITHUB_RUN_ID=123 +export GITHUB_RUN_NUMBER=456 +export GITHUB_SERVER_URL="https://github.com" +export GITHUB_SHA=0123456789abcdef +export GH_TOKEN=test-token + +fail() { + echo "error: $*" >&2 + exit 1 +} + +assert_log_contains() { + grep -F -- "$1" "$GH_MOCK_LOG" >/dev/null || fail "gh log does not contain: $1" +} + +assert_log_excludes() { + if grep -F -- "$1" "$GH_MOCK_LOG" >/dev/null; then + fail "gh log unexpectedly contains: $1" + fi +} + +# A first failure creates the issue with all labels and links to the failed job. +: > "$GH_MOCK_LOG" +unset GH_MOCK_ISSUE_NUMBER GH_MOCK_API_FAIL +"$script" >/dev/null +assert_log_contains 'issue <--repo> <--state> ' +assert_log_contains '<--label> <--label> <--label> ' +assert_log_contains '<--json> <--jq> <[.[] | select(.title == "[automation] Dead links found")][0].number // empty>' +assert_log_contains 'issue ' +assert_log_contains '<--title> <[automation] Dead links found>' +assert_log_contains '<--label> <--label> <--label> ' +assert_log_contains '[CI run #456](https://github.example/jobs/123)' +assert_log_excludes 'issue ' + +# A later failure comments on the existing issue instead of creating another. +: > "$GH_MOCK_LOG" +export GH_MOCK_ISSUE_NUMBER=42 +"$script" >/dev/null +assert_log_contains 'issue <42>' +assert_log_contains 'failed again in [CI run #456](https://github.example/jobs/123)' +assert_log_excludes 'issue ' + +# GitHub API errors and missing required environment variables remain fatal. +: > "$GH_MOCK_LOG" +export GH_MOCK_API_FAIL=1 +if "$script" >/dev/null 2>&1; then + fail "script succeeded after gh api failed" +fi +unset GH_MOCK_API_FAIL + +for variable in GITHUB_REPOSITORY GITHUB_RUN_ID GITHUB_RUN_NUMBER GITHUB_SERVER_URL GITHUB_SHA GH_TOKEN; do + if env -u "$variable" "$script" >/dev/null 2>&1; then + fail "script succeeded without $variable" + fi +done + +echo "report-scheduled-linkcheck-failure tests passed" diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 18fea436747c7..1c3d6e0224718 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -32d94cc9be3f6e6c3fa1deaea9e0ab93c4980dba +420ed2a0c3d7225b1744266fd884d431b4d8cfe0 diff --git a/src/doc/rustc-dev-guide/src/backend/monomorph.md b/src/doc/rustc-dev-guide/src/backend/monomorph.md index 670614fe51379..f25211b23e602 100644 --- a/src/doc/rustc-dev-guide/src/backend/monomorph.md +++ b/src/doc/rustc-dev-guide/src/backend/monomorph.md @@ -33,7 +33,7 @@ Take this example: ```rust fn banana() { - peach::(); + peach::(); } fn main() { @@ -67,9 +67,9 @@ or more modules in Crate B. | Crate A function | Behavior | | - | - | -| Non-generic function | Crate A function doesn't appear in any codegen units of Crate B | -| Non-generic `#[inline]` function | Crate A function appears within a single CGU of Crate B, and exists even after post-inlining stage| -| Generic function | Regardless of inlining, all monomorphized (specialized) functions
from Crate A appear within a single codegen unit for Crate B.
The codegen unit exists even after the post inlining stage.| +| Non-generic function | Crate A function doesn't appear in any codegen units of Crate B. | +| Non-generic `#[inline]` function | Crate A function appears within a single CGU of Crate B.
The codegen unit exists even after the post inlining stage. | +| Generic function | Regardless of inlining, all monomorphized (specialized) functions
from Crate A appear within a single codegen unit for Crate B.
The codegen unit exists even after the post inlining stage. | | Generic `#[inline]` function | - same - | For more details about the partitioner read the module level [documentation]. diff --git a/src/doc/rustc-dev-guide/src/bug-fix-procedure.md b/src/doc/rustc-dev-guide/src/bug-fix-procedure.md index 0db0e07d4e7be..5416e361e5d24 100644 --- a/src/doc/rustc-dev-guide/src/bug-fix-procedure.md +++ b/src/doc/rustc-dev-guide/src/bug-fix-procedure.md @@ -101,7 +101,7 @@ declare_lint! { }, } -// 2. Add a decidacted lint pass for it. +// 2. Add a dedicated lint pass for it. // This step can be skipped if you emit the lint as part of an existing pass. #[derive(Default)] diff --git a/src/doc/rustc-dev-guide/src/closure.md b/src/doc/rustc-dev-guide/src/closure.md index 427919cd57995..81bddd73caba7 100644 --- a/src/doc/rustc-dev-guide/src/closure.md +++ b/src/doc/rustc-dev-guide/src/closure.md @@ -1,13 +1,12 @@ # Closure Capture Inference -This section describes how rustc handles closures. Closures in Rust are -effectively "desugared" into structs that contain the values they use (or -references to the values they use) from their creator's stack frame. rustc has -the job of figuring out which values a closure uses and how, so it can decide -whether to capture a given variable by shared reference, mutable reference, or -by move. rustc also has to figure out which of the closure traits ([`Fn`][fn], -[`FnMut`][fn_mut], or [`FnOnce`][fn_once]) a closure is capable of -implementing. +This section describes how rustc handles closures. +Closures in Rust are effectively "desugared" into structs that contain the values they use (or +references to the values they use) from their creator's stack frame. +rustc has the job of figuring out which values a closure uses and how, so it can decide +whether to capture a given variable by shared reference, mutable reference, or by move. +rustc also has to figure out which of the closure traits ([`Fn`][fn], +[`FnMut`][fn_mut], or [`FnOnce`][fn_once]) a closure is capable of implementing. [fn]: https://doc.rust-lang.org/std/ops/trait.Fn.html [fn_mut]:https://doc.rust-lang.org/std/ops/trait.FnMut.html @@ -31,9 +30,11 @@ fn main() { } ``` -Let's say the above is the content of a file called `immut.rs`. If we compile -`immut.rs` using the following command. The [`-Z dump-mir=all`][dump-mir] flag will cause -`rustc` to generate and dump the [MIR][mir] to a directory called `mir_dump`. +Let's say the above is the content of a file called `immut.rs`. +If we compile `immut.rs` using the following command, +the [`-Z dump-mir=all`][dump-mir] flag will cause +`rustc` to generate and dump the [MIR] to a directory called `mir_dump`. + ```console > rustc +stage1 immut.rs -Z dump-mir=all ``` @@ -43,8 +44,8 @@ Let's say the above is the content of a file called `immut.rs`. If we compile After we run this command, we will see a newly generated directory in our current working directory called `mir_dump`, which will contain several files. -If we look at file `rustc.main.-------.mir_map.0.mir`, we will find, among -other things, it also contains this line: +If we look at file `rustc.main.-------.mir_map.0.mir`, we will find, among other things, +it also contains this line: ```rust,ignore _4 = &_1; @@ -53,9 +54,9 @@ _3 = [closure@immut.rs:7:13: 7:36] { x: move _4 }; Note that in the MIR examples in this chapter, `_1` is `x`. -Here in first line `_4 = &_1;`, the `mir_dump` tells us that `x` was borrowed -as an immutable reference. This is what we would hope as our closure just -reads `x`. +Here in first line `_4 = &_1;`, +the `mir_dump` tells us that `x` was borrowed as an immutable reference. +This is what we would hope as our closure just reads `x`. ### Example 2 @@ -81,7 +82,8 @@ _4 = &mut _1; _3 = [closure@mut.rs:7:13: 10:6] { x: move _4 }; ``` This time along, in the line `_4 = &mut _1;`, we see that the borrow is changed to mutable borrow. -Fair enough! The closure increments `x` by 10. +Fair enough! +The closure increments `x` by 10. ### Example 3 @@ -104,33 +106,39 @@ fn main() { ```rust,ignore _6 = [closure@move.rs:7:13: 9:6] { x: move _1 }; // bb16[3]: scope 1 at move.rs:7:13: 9:6 ``` -Here, `x` is directly moved into the closure and the access to it will not be permitted after the -closure. +Here, `x` is directly moved into the closure, +and the access to it will not be permitted after the closure. ## Inferences in the compiler Now let's dive into rustc code and see how all these inferences are done by the compiler. Let's start with defining a term that we will be using quite a bit in the rest of the discussion - -*upvar*. An **upvar** is a variable that is local to the function where the closure is defined. So, -in the above examples, **x** will be an upvar to the closure. They are also sometimes referred to as -the *free variables* meaning they are not bound to the context of the closure. +*upvar*. +An **upvar** is a variable that is local to the function where the closure is defined. +So, in the above examples, **x** will be an upvar to the closure. +They are also sometimes referred to as the *free variables*, +meaning they are not bound to the context of the closure. [`compiler/rustc_passes/src/upvars.rs`][upvars] defines a query called *upvars_mentioned* for this purpose. [upvars]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_passes/upvars/index.html -Other than lazy invocation, one other thing that distinguishes a closure from a -normal function is that it can use the upvars. It borrows these upvars from its surrounding -context; therefore the compiler has to determine the upvar's borrow type. The compiler starts with -assigning an immutable borrow type and lowers the restriction (that is, changes it from -**immutable** to **mutable** to **move**) as needed, based on the usage. In the Example 1 above, the -closure only uses the variable for printing but does not modify it in any way and therefore, in the -`mir_dump`, we find the borrow type for the upvar `x` to be immutable. In example 2, however, the -closure modifies `x` and increments it by some value. Because of this mutation, the compiler, which +Other than lazy invocation, +one other thing that distinguishes a closure from a normal function is that it can use the upvars. +It borrows these upvars from its surrounding +context; therefore the compiler has to determine the upvar's borrow type. +The compiler starts with assigning an immutable borrow type and lowers the restriction +(that is, changes it from **immutable** to **mutable** to **move**) as needed, based on the usage. +In the Example 1 above, +the closure only uses the variable for printing but does not modify it in any way and therefore, +in the `mir_dump`, we find the borrow type for the upvar `x` to be immutable. +In example 2, however, the closure modifies `x` and increments it by some value. +Because of this mutation, the compiler, which started off assigning `x` as an immutable reference type, has to adjust it as a mutable reference. Likewise in the third example, the closure drops the vector and therefore this requires the variable -`x` to be moved into the closure. Depending on the borrow kind, the closure has to implement the +`x` to be moved into the closure. +Depending on the borrow kind, the closure has to implement the appropriate trait: `Fn` trait for immutable borrow, `FnMut` for mutable borrow, and `FnOnce` for move semantics. @@ -141,17 +149,17 @@ declared in the file [`compiler/rustc_middle/src/ty/mod.rs`][ty]. [upvar]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/upvar/index.html [ty]:https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/index.html -Before we go any further, let's discuss how we can examine the flow of control through the rustc -codebase. For closures specifically, set the `RUSTC_LOG` env variable as below and collect the -output in a file: +Before we go any further, +let's discuss how we can examine the flow of control through the rustc codebase. +For closures specifically, +set the `RUSTC_LOG` env variable as below and collect the output in a file: ```console > RUSTC_LOG=rustc_hir_typeck::upvar rustc +stage1 -Z dump-mir=all \ <.rs file to compile> 2> ``` -This uses the stage1 compiler and enables `debug!` logging for the -`rustc_hir_typeck::upvar` module. +This uses the stage1 compiler and enables `debug!` logging for the `rustc_hir_typeck::upvar` module. The other option is to step through the code using lldb or gdb. @@ -160,8 +168,8 @@ The other option is to step through the code using lldb or gdb. 1. `b upvar.rs:134` // Setting the breakpoint on a certain line in the upvar.rs file 2. `r` // Run the program until it hits the breakpoint -Let's start with [`upvar.rs`][upvar]. This file has something called -the [`euv::ExprUseVisitor`] which walks the source of the closure and +Let's start with [`upvar.rs`][upvar]. +This file has something called the [`euv::ExprUseVisitor`] which walks the source of the closure and invokes a callback for each upvar that is borrowed, mutated, or moved. [`euv::ExprUseVisitor`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/expr_use_visitor/struct.ExprUseVisitor.html @@ -176,32 +184,33 @@ fn main() { } ``` -In the above example, our visitor will be called twice, for the lines marked 1 and 2, once for a -shared borrow and another one for a mutable borrow. It will also tell us what was borrowed. +In the above example, our visitor will be called twice, for the lines marked 1 and 2, +once for a shared borrow and another one for a mutable borrow. +It will also tell us what was borrowed. -The callbacks are defined by implementing the [`Delegate`] trait. The -[`InferBorrowKind`][ibk] type implements `Delegate` and keeps a map that -records for each upvar which mode of capture was required. The modes of capture -can be `ByValue` (moved) or `ByRef` (borrowed). For `ByRef` borrows, the possible -[`BorrowKind`]s are `ImmBorrow`, `UniqueImmBorrow`, `MutBorrow` as defined in the +The callbacks are defined by implementing the [`Delegate`] trait. +The [`InferBorrowKind`][ibk] type implements `Delegate` and keeps a map that +records for each upvar which mode of capture was required. +The modes of capture can be `ByValue` (moved) or `ByRef` (borrowed). +For `ByRef` borrows, the possible [`BorrowKind`]s are `ImmBorrow`, +`UniqueImmBorrow`, `MutBorrow` as defined in the [`compiler/rustc_middle/src/ty/mod.rs`][middle_ty]. [`BorrowKind`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.BorrowKind.html [middle_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/index.html `Delegate` defines a few different methods (the different callbacks): -**consume** for *move* of a variable, **borrow** for a *borrow* of some kind -(shared or mutable), and **mutate** when we see an *assignment* of something. +**consume** for *move* of a variable, **borrow** for a *borrow* of some kind (shared or mutable), +and **mutate** when we see an *assignment* of something. All of these callbacks have a common argument *cmt* which stands for Category, -Mutability and Type and is defined in -[`compiler/rustc_hir_typeck/src/expr_use_visitor.rs`][cmt]. Borrowing from the code -comments, "`cmt` is a complete categorization of a value indicating where it +Mutability and Type and is defined in [`compiler/rustc_hir_typeck/src/expr_use_visitor.rs`][cmt]. +Borrowing from the code comments, "`cmt` is a complete categorization of a value indicating where it originated and how it is located, as well as the mutability of the memory in which the value is stored". Based on the callback (consume, borrow etc.), we -will call the relevant `adjust_upvar_borrow_kind_for_` and pass the -`cmt` along. Once the borrow type is adjusted, we store it in the table, which -basically says what borrows were made for each closure. +will call the relevant `adjust_upvar_borrow_kind_for_` and pass the `cmt` along. +Once the borrow type is adjusted, we store it in the table, +which basically says what borrows were made for each closure. ```rust,ignore self.tables diff --git a/src/doc/rustc-dev-guide/src/offload/installation.md b/src/doc/rustc-dev-guide/src/offload/installation.md index 8422d072bca3b..2bb7ea98f6d5b 100644 --- a/src/doc/rustc-dev-guide/src/offload/installation.md +++ b/src/doc/rustc-dev-guide/src/offload/installation.md @@ -3,9 +3,16 @@ `std::offload` is partly available in nightly builds for users. For now, everyone however still needs to build rustc from source to use all features of it. +## Rustup installation. + +If you are on `x86_64` Linux, you can install the nightly toolchain with: +```console +rustup +nightly component add offload +``` + ## Build instructions -First you need to clone and configure the Rust repository: +Otherwise you need to clone and configure the Rust repository: ```console git clone git@github.com:rust-lang/rust cd rust @@ -14,7 +21,8 @@ cd rust If you would rather reuse an existing clang than build one, drop `--enable-clang` and pass `--enable-llvm-offload-clang-dir=` -instead. It should match the (major version of the) LLVM in `src/llvm-project`. +instead. +It should match the (major version of the) LLVM in `src/llvm-project`. Afterwards you can build rustc using: ```console @@ -27,9 +35,8 @@ rustup toolchain link offload build/host/stage1 rustup toolchain install nightly # enables -Z unstable-options ``` - - ## Build instruction for LLVM itself + ```console git clone git@github.com:llvm/llvm-project cd llvm-project @@ -41,8 +48,8 @@ ninja install ``` This gives you a working LLVM build. - ## Testing + Run this test script for offload-specific tests: ```console ./x test --stage 1 tests/codegen-llvm/gpu_offload diff --git a/src/doc/rustc-dev-guide/src/offload/usage.md b/src/doc/rustc-dev-guide/src/offload/usage.md index 77b8935fab837..9d61a5a87c744 100644 --- a/src/doc/rustc-dev-guide/src/offload/usage.md +++ b/src/doc/rustc-dev-guide/src/offload/usage.md @@ -49,7 +49,7 @@ fn kernel(x: *mut [T; 256], value: T) { fn main() { let mut x = [0.0f64; 256]; core::offload::offload! { - kernel = kernel, + kernel = kernel::, workgroup_dim = [256, 1, 1], args = (&mut x as *mut [f64; 256], 2.5), } @@ -89,38 +89,21 @@ Now we generate the device (GPU) code, passing the manifest: ``` RUSTFLAGS="-Ctarget-cpu=gfx90a --emit=llvm-bc,llvm-ir -Zoffload=Device=/absolute/path/to/offload.manifest -Csave-temps -Zunstable-options" cargo +offload build -Zunstable-options -r -v --target amdgcn-amd-amdhsa -Zbuild-std=core ``` -You might afterwards need to copy your target/release/deps/.bc to lib.bc for now, before the next step. -Now we generate the host (CPU) code. +Next we generate the host (CPU) code. ``` -RUSTFLAGS="--emit=llvm-bc,llvm-ir -Csave-temps -Zoffload=Host=/p/lustre1/drehwald1/prog/offload/r/target/amdgcn-amd-amdhsa/release/deps/device.bin -Zunstable-options" cargo +offload build -r -``` -This call also does a lot of work and generates multiple intermediate files for LLVM offload. -While we integrated most offload steps into rustc by now, one binary invocation still remains for now: - -``` -"clang-linker-wrapper" "--should-extract=gfx90a" "--device-compiler=amdgcn-amd-amdhsa=-g" "--device-compiler=amdgcn-amd-amdhsa=-save-temps=cwd" "--device-linker=amdgcn-amd-amdhsa=-lompdevice" "--host-triple=x86_64-unknown-linux-gnu" "--save-temps" "--linker-path=/ABSOlUTE_PATH_TO/rust/build/x86_64-unknown-linux-gnu/lld/bin/ld.lld" "--hash-style=gnu" "--eh-frame-hdr" "-m" "elf_x86_64" "-pie" "-dynamic-linker" "/lib64/ld-linux-x86-64.so.2" "-o" "main" "/lib/../lib64/Scrt1.o" "/lib/../lib64/crti.o" "/ABSOLUTE_PATH_TO/crtbeginS.o" "-L/ABSOLUTE_PATH_TO/rust/build/x86_64-unknown-linux-gnu/llvm/bin/../lib/x86_64-unknown-linux-gnu" "-L/ABSOLUTE_PATH_TO/rust/build/x86_64-unknown-linux-gnu/llvm/lib/clang/21/lib/x86_64-unknown-linux-gnu" "-L/lib/../lib64" "-L/usr/lib64" "-L/lib" "-L/usr/lib" "target//release/host.o" "-lstdc++" "-lm" "-lomp" "-lomptarget" "-L/ABSOLUTE_PATH_TO/rust/build/x86_64-unknown-linux-gnu/llvm/lib" "-lgcc_s" "-lgcc" "-lpthread" "-lc" "-lgcc_s" "-lgcc" "/ABSOLUTE_PATH_TO/crtendS.o" "/lib/../lib64/crtn.o" -``` - -You can try to find the paths to those files on your system. -However, I recommend to not fix the paths, but rather just re-generate them by copying a bare-mode OpenMP example and compiling it with your clang. -By adding `-###` to your clang invocation, you can see the invidual steps. -It will show multiple steps, just look for the clang-linker-wrapper example. -Make sure to still include the path to the `host.o` file, and not whatever tmp file you got when compiling your c++ example with the following call. -``` -myclang++ -fuse-ld=lld -O3 -fopenmp -fopenmp-offload-mandatory --offload-arch=gfx90a omp_bare.cpp -o main -### +RUSTFLAGS="--emit=llvm-bc,llvm-ir -Csave-temps -Zoffload=Host=$PWD/target/amdgcn-amd-amdhsa/release/deps/device.bin -Zunstable-options" cargo +offload build -r ``` In the final step, you can now run your binary ``` -./main +LD_LIBRARY_PATH=$(rustc +nightly --print sysroot)/lib ./target/x86_64-unknown-linux-gnu/release/binary-name all checks passed! ``` -To receive more information about the memory transfer, you can enable info printing with -``` -LIBOMPTARGET_INFO=-1 ./main -``` +These three steps will soon be wrapped into a single command, once we had more time to test all steps. + +To receive more information about the memory transfer, you can enable info printing by adding `LIBOMPTARGET_INFO=-1` ahead of your binary call. [^list]: https://rocm.docs.amd.com/en/latest/reference/gpu-arch-specs.html or https://developer.nvidia.com/cuda/gpus. Alternatively, check `rustc --print target-cpus`. diff --git a/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md b/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md index 2b06f5b414c1b..19db5016eb073 100644 --- a/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md +++ b/src/doc/rustc-dev-guide/src/solve/sharing-crates-with-rust-analyzer.md @@ -59,7 +59,7 @@ solver. `rustc_next_trait_solver` is intended to depend only on the abstract interfaces defined in `rustc_type_ir`. To support this, the type-system traits in `rustc_type_ir` must expose every interface the solver -requires—for example, [creating a new inference type variable][ir new_infer] +requires—for example, [creating a new inference type variable][ir new_infer] ([rustc][rustc new_infer], [rust-analyzer][r-a new_infer]). For items that do not need compiler-specific representations, `rustc_type_ir` defines them directly as structs or enums parameterized over these traits—for example, [`TraitRef`][ir tr]. @@ -77,7 +77,8 @@ Among its essential responsibilities: instantiates the shared IR, - it provides the context required by the solver (e.g., querying [lang items][ir require_lang_item], enumerating [all blanket impls for a trait][ir for_each_blanket_impl]); -- and it must implement [`IrPrint`][ir irprint] for formatting and tracing. +- and it must implement [`IrPrint`][ir irprint] for formatting and tracing. + In practice, these `IrPrint` impls simply route to existing formatting logic inside rustc or rust-analyzer. @@ -91,7 +92,8 @@ rather than rustc queries. Another notable item in `rustc_type_ir` is the [`inherent` module][ir inherent]. This module provides *forward definitions* of inherent methods—expressed as traits—corresponding to -methods that exist on compiler-specific types such as `Ty` or `GenericArg`. +methods that exist on compiler-specific types such as `Ty` or `GenericArg`. + These definitions allow the generic crates (such as `rustc_next_trait_solver`) to call methods that are implemented differently in rustc and rust-analyzer. @@ -151,9 +153,9 @@ This infrastructure is used by the external fuzzing project: - [`trait Lift` and `Lift_Generic`][lift-trait-macro] - [`trait GenericTypeVisitable`][generictypevisitable] -These traits are used heavily in `rustc_type_ir`, their associated macros -primarily exist to reduce the amount of boilerplate otherwise required to -implement `Lift`, `TypeFoldable`, `TypeVisitable` and `GenericTypeVisitable`. +These traits are used heavily in `rustc_type_ir`, their associated macros +primarily exist to reduce the amount of boilerplate otherwise required to implement `Lift`, +`TypeFoldable`, `TypeVisitable` and `GenericTypeVisitable`. ### `trait TypeVisitable` and `TypeVisitable_Generic` [type-visitable-trait-macro]: #type-visitable-trait-macro @@ -163,8 +165,7 @@ which in turn will transfer control to `TypeVisitor`, this can be [seen in detail here][rustc_typevisitable]. While ostensibly similar due to their names, `TypeVisitable_Generic` and -[`GenericTypeVisitable`][generictypevisitable] they implement two different -visiting systems. +[`GenericTypeVisitable`][generictypevisitable] they implement two different visiting systems. - `TypeVisitable_Generic` means: derive the ordinary `TypeVisitable` trait generically over an `Interner`. @@ -175,50 +176,49 @@ visiting systems. [typevisitable_generic]: #typevisitable_generic It visits the value's fields in declaration order, delegating each field to that -field's own `TypeVisitable` implementation. The traversal can stop early if -the visitor returns a residual result. +field's own `TypeVisitable` implementation. +The traversal can stop early if the visitor returns a residual result. Use `#[type_visitable(ignore)]` to ignore a field; it will not be part of the -traversal and will not need to implement `TypeVisitable`. This should only -be used when the field does not need to be traversed. +traversal and will not need to implement `TypeVisitable`. +This should only be used when the field does not need to be traversed. ### `trait TypeFoldable` and `TypeFoldable_Generic` [type-foldable-trait-macro]: #type-foldable-trait-macro -The trait is implemented by things that need to embed types. This concept is -discussed in detail [here](../ty-fold.md) and can be +The trait is implemented by things that need to embed types. +This concept is discussed in detail [here](../ty-fold.md) and can be [followed in the source][rustc_typefoldable]. -`TypeFoldable_Generic` derives `rustc_type_ir::TypeFoldable` for a struct or -enum. +`TypeFoldable_Generic` derives `rustc_type_ir::TypeFoldable` for a struct or enum. -It consumes a value and reconstructs the same struct or enum variant after -folding its fields. It generates both fallible and infallible folding methods. +It consumes a value and reconstructs the same struct or enum variant after folding its fields. +It generates both fallible and infallible folding methods. -Use `#[type_foldable(identity)]` for a field whose value must be preserved -unchanged. The macro moves that field directly into the reconstructed value -instead of passing it to the folder. Its type therefore does not need to -implement `TypeFoldable`. +Use `#[type_foldable(identity)]` for a field whose value must be preserved unchanged. +The macro moves that field directly into the reconstructed value +instead of passing it to the folder. +Its type therefore does not need to implement `TypeFoldable`. For an enum, the generated match contains one reconstruction arm per variant. ### `trait Lift` and `Lift_Generic` [lift-trait-macro]: #lift-trait-macro -The trait has a method `lift_to_interner(...)`. As the name suggests, it should -'lift' something to the interner. [See here](../memory.md) to read more about -the interner [and here for the source][rustc_lift]. +The trait has a method `lift_to_interner(...)`. +As the name suggests, it should 'lift' something to the interner. +[See here](../memory.md) to read more about the interner [and here for the source][rustc_lift]. -The macro `Lift_Generic` derives `Lift` for a struct or enum, with three -non-obvious considerations: +The macro `Lift_Generic` derives `Lift` for a struct or enum, with three non-obvious considerations: 1. The generic parameters `I` and `J` are reserved for `I: Interner` and `J` being the interner it is being lifted to. -2. `PhantomData` is handled automatically, creating a new `PhantomData`. But it - _has_ to be used in the fully unqualified form -- you cannot use +2. `PhantomData` is handled automatically, creating a new `PhantomData`. + But it _has_ to be used in the fully unqualified form -- you cannot use `std::marker::PhantomData` directly in the field. 3. The bounds are deliberately written as associated type bounds on the `Interner` - trait rather than as `where` clauses on `LiftInto`. Given only `I: LiftInto`, + trait rather than as `where` clauses on `LiftInto`. + Given only `I: LiftInto`, Rust can then treat bounds such as the following as implied: ```rust @@ -228,15 +228,14 @@ I::Const: Lift This allows `Lift_Generic` to emit the bound `I: LiftInto` while still calling `lift_to_interner` on fields of type `I::Ty`, `I::Const`, and the other -declared associated types. It also guarantees that each call produces the -destination field type expected after the derive rewrites `I::Assoc` to -`J::Assoc`. +declared associated types. +It also guarantees that each call produces the +destination field type expected after the derive rewrites `I::Assoc` to `J::Assoc`. Without `declare_lift_into!`, the derive would need to generate a separate bound -for every interner-associated type used by every field. If a new `Interner` -associated type is expected to work with `Lift_Generic`, it needs an appropriate -`Lift` implementation and normally needs to be included in the -`declare_lift_into!` invocation. +for every interner-associated type used by every field. +If a new `Interner` associated type is expected to work with `Lift_Generic`, it needs an appropriate +`Lift` implementation and normally needs to be included in the `declare_lift_into!` invocation. If you want to ignore a field, such as a primitive like a `u32` which can't be lifted you can skip the field with `#[lift(ignore)]`. @@ -245,28 +244,28 @@ lifted you can skip the field with `#[lift(ignore)]`. [generictypevisitable]: #generictypevisitable This a separate more general traversal trait purely used by `rust-analyzer`. -The visitor type is a parameter of the trait rather than a parameter of the -method, and visiting neither returns a result nor supports short-circuiting. +The visitor type is a parameter of the trait rather than a parameter of the method, +and visiting neither returns a result nor supports short-circuiting. -As such a struct or enum can derive both `TypeVisitable_Generic` and -`GenericTypeVisitable` +As such a struct or enum can derive both `TypeVisitable_Generic` and `GenericTypeVisitable` -There is intentionally no ignore attribute. The traversal must visit every -field. This is a soundness requirement for rust-analyzer's use of the traversal +There is intentionally no ignore attribute. +The traversal must visit every field. +This is a soundness requirement for rust-analyzer's use of the traversal when tracing and garbage-collecting interned types. ## Long-term plans for supporting rust-analyzer In general, we aim to support rust-analyzer just as well as rustc in these shared crates—provided -doing so does not substantially harm rustc's performance or maintainability. +doing so does not substantially harm rustc's performance or maintainability. (e.g., [#145377][pr 145377], [#146111][pr 146111], [#146182][pr 146182] and [#147723][pr 147723]) Shared crates that require nightly-only features must guard such code behind a `nightly` feature flag, since rust-analyzer is built with the stable toolchain. Looking forward, we plan to uplift more shared logic into `rustc_type_ir`. -There are still duplicated implementations between rustc and rust-analyzer—such as `ObligationCtxt` -([rustc][rustc oblctxt], [rust-analyzer][r-a oblctxt]) and type coercion logic +There are still duplicated implementations between rustc and rust-analyzer—such as `ObligationCtxt` +([rustc][rustc oblctxt], [rust-analyzer][r-a oblctxt]) and type coercion logic ([rustc][rustc coerce], [rust-analyzer][r-a coerce])—that we would like to unify over time. [rustc-auto-publish]: https://github.com/rust-analyzer/rustc-auto-publish @@ -303,5 +302,5 @@ There are still duplicated implementations between rustc and rust-analyzer—suc [rustc coerce]: https://github.com/rust-lang/rust/blob/63b1db05801271e400954e41b8600a3cf1482363/compiler/rustc_hir_typeck/src/coercion.rs [r-a coerce]: https://github.com/rust-lang/rust-analyzer/blob/34f47d9298c478c12c6c4c0348771d1b05706e09/crates/hir-ty/src/infer/coerce.rs [rustc_lift]: https://github.com/rust-lang/rust/blob/0913b18e489ac1011b580e31fa5559654be12bfc/compiler/rustc_type_ir/src/lift.rs#L18 -[rustc_typevisitable]: https://github.com/rust-lang/rust/blob/0913b18e489ac1011b580e31fa5559654be12bfc/compiler/rustc_type_ir/src/visit.rs#L62 +[rustc_typevisitable]: https://github.com/rust-lang/rust/blob/0913b18e489ac1011b580e31fa5559654be12bfc/compiler/rustc_type_ir/src/visit.rs#L62 [rustc_typefoldable]: https://github.com/rust-lang/rust/blob/0913b18e489ac1011b580e31fa5559654be12bfc/compiler/rustc_type_ir/src/fold.rs#L71 diff --git a/src/doc/rustc-dev-guide/src/test-implementation.md b/src/doc/rustc-dev-guide/src/test-implementation.md index 707ba040609e0..5df7d8d304d22 100644 --- a/src/doc/rustc-dev-guide/src/test-implementation.md +++ b/src/doc/rustc-dev-guide/src/test-implementation.md @@ -1,9 +1,7 @@ # The `#[test]` attribute - - -Many Rust programmers rely on a built-in attribute called `#[test]`. All -you have to do is mark a function and include some asserts like so: +Many Rust programmers rely on a built-in attribute called `#[test]`. +All you have to do is mark a function and include some asserts like so: ```rust,ignore @@ -14,9 +12,9 @@ fn my_test() { ``` When this program is compiled using `rustc --test` or `cargo test`, it will -produce an executable that can run this, and any other test function. This -method of testing allows tests to live alongside code in an organic way. You -can even put tests inside private modules: +produce an executable that can run this, and any other test function. +This method of testing allows tests to live alongside code in an organic way. +You can even put tests inside private modules: ```rust,ignore mod my_priv_mod { @@ -30,22 +28,23 @@ mod my_priv_mod { ``` Private items can thus be easily tested without worrying about how to expose -them to any sort of external testing apparatus. This is key to the -ergonomics of testing in Rust. Semantically, however, it's rather odd. +them to any sort of external testing apparatus. +This is key to the ergonomics of testing in Rust. +Semantically, however, it's rather odd. How does any sort of `main` function invoke these tests if they're not visible? What exactly is `rustc --test` doing? `#[test]` is implemented as a syntactic transformation inside the compiler's -[`rustc_ast`][rustc_ast]. Essentially, it's a fancy [`macro`] that -rewrites the crate in 3 steps: +[`rustc_ast`][rustc_ast]. +Essentially, it's a fancy [`macro`] that rewrites the crate in 3 steps: ## Step 1: Re-Exporting As mentioned earlier, tests can exist inside private modules, so we need a -way of exposing them to the main function, without breaking any existing -code. To that end, [`rustc_ast`][rustc_ast] will create local modules called -`__test_reexports` that recursively reexport tests. This expansion translates -the above example into: +way of exposing them to the main function, without breaking any existing code. +To that end, [`rustc_ast`][rustc_ast] will create local modules called +`__test_reexports` that recursively reexport tests. +This expansion translates the above example into: ```rust,ignore mod my_priv_mod { @@ -61,24 +60,24 @@ mod my_priv_mod { } ``` -Now, our test can be accessed as -`my_priv_mod::__test_reexports::test_priv_func`. For deeper module -structures, `__test_reexports` will reexport modules that contain tests, so a -test at `a::b::my_test` becomes -`a::__test_reexports::b::__test_reexports::my_test`. While this process seems -pretty safe, what happens if there is an existing `__test_reexports` module? +Now, our test can be accessed as `my_priv_mod::__test_reexports::test_priv_func`. +For deeper module structures, `__test_reexports` will reexport modules that contain tests, so a +test at `a::b::my_test` becomes `a::__test_reexports::b::__test_reexports::my_test`. +While this process seems pretty safe, +what happens if there is an existing `__test_reexports` module? The answer: nothing. To explain, we need to understand how Rust's [Abstract Syntax Tree][ast] -represents [identifiers][Ident]. The name of every function, variable, module, -etc. is not stored as a string, but rather as an opaque [Symbol][Symbol] which -is essentially an ID number for each identifier. The compiler keeps a separate -hashtable that allows us to recover the human-readable name of a Symbol when -necessary (such as when printing a syntax error). When the compiler generates -the `__test_reexports` module, it generates a new [Symbol][Symbol] for the -identifier, so while the compiler-generated `__test_reexports` may share a name -with your hand-written one, it will not share a [Symbol][Symbol]. This -technique prevents name collision during code generation and is the foundation +represents [identifiers][Ident]. +The name of every function, variable, module, etc. is not stored as a string, +but rather as an opaque [Symbol] which is essentially an ID number for each identifier. +The compiler keeps a separate hashtable that allows us to recover +the human-readable name of a Symbol when necessary (such as when printing a syntax error). +When the compiler generates the `__test_reexports` module, +it generates a new [Symbol] for the identifier, +so while the compiler-generated `__test_reexports` may share a name +with your hand-written one, it will not share a [Symbol]. +This technique prevents name collision during code generation and is the foundation of Rust's [`macro`] hygiene. ## Step 2: Harness generation @@ -96,20 +95,20 @@ pub fn main() { Here `path::to::test1` is a constant of type [`test::TestDescAndFn`][tdaf]. -While this transformation is simple, it gives us a lot of insight into how -tests are actually run. The tests are aggregated into an array and passed to -a test runner called `test_main_env_args`. We'll come back to exactly what -[`TestDescAndFn`][tdaf] is, but for now, the key takeaway is that there is a crate -called [`test`][test] that is part of Rust core, that implements all of the -runtime for testing. [`test`][test]'s interface is unstable, so the only stable way +While this transformation is simple, it gives us a lot of insight into how tests are actually run. +The tests are aggregated into an array and passed to a test runner called `test_main_env_args`. +We'll come back to exactly what [`TestDescAndFn`][tdaf] is, +but for now, the key takeaway is that there is a crate +called [`test`][test] that is part of Rust core, that implements all of the runtime for testing. +[`test`][test]'s interface is unstable, so the only stable way to interact with it is through the `#[test]` macro. ## Step 3: Test object generation If you've written tests in Rust before, you may be familiar with some of the -optional attributes available on test functions. For example, a test can be -annotated with `#[should_panic]` if we expect the test to cause a panic. It -looks something like this: +optional attributes available on test functions. +For example, a test can be annotated with `#[should_panic]` if we expect the test to cause a panic. +It looks something like this: ```rust,ignore #[test] @@ -120,12 +119,12 @@ fn foo() { ``` This means our tests are more than just simple functions, they have -configuration information as well. `test` encodes this configuration data into -a `struct` called [`TestDesc`]. For each test function in a crate, -[`rustc_ast`][rustc_ast] will parse its attributes and generate a [`TestDesc`] -instance. It then combines the [`TestDesc`] and test function into the -predictably named [`TestDescAndFn`][tdaf] `struct`, that [`test_main_env_args`] -operates on. +configuration information as well. +`test` encodes this configuration data into a `struct` called [`TestDesc`]. +For each test function in a crate, +[`rustc_ast`][rustc_ast] will parse its attributes and generate a [`TestDesc`] instance. +It then combines the [`TestDesc`] and test function into the +predictably named [`TestDescAndFn`][tdaf] `struct`, that [`test_main_env_args`] operates on. For a given test, the generated [`TestDescAndFn`][tdaf] instance looks like so: ```rust,ignore diff --git a/src/doc/rustc-dev-guide/src/tests/compiletest.md b/src/doc/rustc-dev-guide/src/tests/compiletest.md index 4573a04d0281c..ee42cf1fa1d87 100644 --- a/src/doc/rustc-dev-guide/src/tests/compiletest.md +++ b/src/doc/rustc-dev-guide/src/tests/compiletest.md @@ -3,8 +3,8 @@ ## Introduction `compiletest` is the main test harness of the Rust test suite. -It allows test authors to organize large numbers of tests (the Rust compiler has many -thousands), efficient test execution (parallel execution is supported), and +It allows test authors to organize large numbers of tests (the Rust compiler has many thousands), +efficient test execution (parallel execution is supported), and allows the test author to configure behavior and expected results of both individual and groups of tests. @@ -23,14 +23,14 @@ individual and groups of tests. Tests are typically organized as a Rust source file with annotations in comments before and/or within the test code. -These comments serve to direct `compiletest` -on if or how to run the test, what behavior to expect, and more. +These comments serve to direct `compiletest` on if or how to run the test, +what behavior to expect, and more. See [directives](directives.md) and the test suite documentation below for more details on these annotations. See the [Adding new tests](adding.md) and [Best practices](best-practices.md) -chapters for a tutorial on creating a new test and advice on writing a good -test, and the [Running tests](running.md) chapter on how to run the test suite. +chapters for a tutorial on creating a new test and advice on writing a good test, +and the [Running tests](running.md) chapter on how to run the test suite. Arguments can be passed to compiletest using `--test-args` or by placing them after `--`, e.g. - `x test --test-args --force-rerun` @@ -49,8 +49,8 @@ You can use `x test --test-args All of the tests are in the [`tests`] directory. The tests are organized into "suites", with each suite in a separate subdirectory. -Each test suite behaves a -little differently, with different compiler behavior and different checks for correctness. +Each test suite behaves a little differently, +with different compiler behavior and different checks for correctness. For example, the [`tests/incremental`] directory contains tests for incremental compilation. The various suites are defined in [`src/tools/compiletest/src/common.rs`] in the `pub enum Mode` declaration. @@ -114,8 +114,8 @@ The `-Z unpretty` CLI option for `rustc` causes it to translate the input source into various different formats, such as the Rust source after macro expansion. The pretty-printer tests have several [directives](directives.md) described below. -These commands can significantly change the behavior of the test, but the -default behavior without any commands is to: +These commands can significantly change the behavior of the test, +but the default behavior without any commands is to: 1. Run `rustc -Zunpretty=normal` on the source file. 2. Run `rustc -Zunpretty=normal` on the output of the previous step. @@ -133,17 +133,17 @@ The directives for pretty-printing tests are: - `pretty-compare-only` causes a pretty test to only compare the pretty-printed output (stopping after step 3 from above). It will not try to compile the expanded output to type check it. - This is needed for a pretty-mode that does - not expand to valid Rust, or for other situations where the expanded output cannot be compiled. + This is needed for a pretty-mode that does not expand to valid Rust, + or for other situations where the expanded output cannot be compiled. - `pp-exact` is used to ensure a pretty-print test results in specific output. - If specified without a value, then it means the pretty-print output should - match the original source. - If specified with a value, as in `//@ - pp-exact:foo.pp`, it will ensure that the pretty-printed output matches the + If specified without a value, + then it means the pretty-print output should match the original source. + If specified with a value, as in `//@ pp-exact:foo.pp`, + it will ensure that the pretty-printed output matches the contents of the given file. Otherwise, if `pp-exact` is not specified, then - the pretty-printed output will be pretty-printed one more time, and the output - of the two pretty-printing rounds will be compared to ensure that the + the pretty-printed output will be pretty-printed one more time, + and the output of the two pretty-printing rounds will be compared to ensure that the pretty-printed output converges to a steady state. [`tests/pretty`]: https://github.com/rust-lang/rust/tree/HEAD/tests/pretty @@ -166,8 +166,8 @@ Each revision name must start with one of: To make the revisions unique, you should add a suffix like `rpass1` and `rpass2`. -To simulate changing the source, compiletest also passes a `--cfg` flag with the -current revision name. +To simulate changing the source, +compiletest also passes a `--cfg` flag with the current revision name. For example, this will run twice, simulating changing a function: @@ -221,9 +221,10 @@ A simple example of a test using `rustc_clean` is the [hello_world test]. > opt-in. For further context, see: > [Stabilizing the state of the debuginfo test suite](https://github.com/rust-lang/compiler-team/issues/1012) -The tests in [`tests/debuginfo`] test how debuginfo is interpreted by the supported debuggers, and -confirm our visualizers still work as expected. They build a program, launch a debugger, and issue -commands to the debugger. A single test can work with cdb, gdb, and lldb. +The tests in [`tests/debuginfo`] test how debuginfo is interpreted by the supported debuggers, +and confirm our visualizers still work as expected. +They build a program, launch a debugger, and issue commands to the debugger. +A single test can work with cdb, gdb, and lldb. Most tests should have the `//@ compile-flags: -g` directive or something similar to generate the appropriate debuginfo. @@ -245,8 +246,8 @@ The debugger values can be: The command to check the output are of the form `//@ $DEBUGGER-check:$OUTPUT` where `$OUTPUT` is the output to expect. -For example, the following will build the test, start the debugger, set a -breakpoint, launch the program, inspect a value, and check what the debugger prints: +For example, the following will build the test, start the debugger, set a breakpoint, +launch the program, inspect a value, and check what the debugger prints: ```rust,ignore //@ compile-flags: -g @@ -265,7 +266,8 @@ fn b() {} Additionally, there is a special command, `//@ $DEBUGGER-repr:$VAR_NAME` intended to verify variables (and their visualizers) with more granularity than can be achieved with simple string -comparison. This directive should be preferred over the `-command`/`-check` whenever possible. +comparison. +This directive should be preferred over the `-command`/`-check` whenever possible. > [!NOTE] > At time of writing (July 2026) this command is limited to LLDB, with an implementation coming soon @@ -279,8 +281,8 @@ This command effectivly desugars into: ``` The `repr $VAR_NAME` command is intercepted by special logic that uses the debuggers' API to inspect -data that isn't reflected in the variable's printed output. The variable in memory is compared -against input data stored in +data that isn't reflected in the variable's printed output. +The variable in memory is compared against input data stored in `tests/debuginfo//input/_input/.json` and provides detailed error messages on failure. @@ -305,8 +307,9 @@ the debugger currently being used: - `min-apple-lldb-version: 1703.0.236.21`/`min-llvm-lldb-version: 21.1.0` — ignores the test if the version of lldb is below the given version. Note: Apple's fork of LLDB (distributed with Xcode) uses a different versioning scheme that is not - easily mappable to LLVM's LLDB version numbers. As such, the version gates are specified by - vendor. Further info on manually checking version equivalence is available [here](../debuginfo/testing.md#lldb-versioning) + easily mappable to LLVM's LLDB version numbers. + As such, the version gates are specified by vendor. + Further info on manually checking version equivalence is available [here](../debuginfo/testing.md#lldb-versioning) - `rust-lldb` — ignores the test if lldb is not contain the Rust plugin. NOTE: The "Rust" version of LLDB doesn't exist anymore, so this will always be ignored. This should probably be removed. @@ -361,14 +364,14 @@ See the [FileCheck] documentation for a tutorial and more information. See also the [assembly tests](#assembly-tests) for a similar set of tests. By default, codegen tests will have `//@ needs-target-std` *implied* (that the -target needs to support std), *unless* the `#![no_std]`/`#![no_core]` attribute -was specified in the test source. +target needs to support std), +*unless* the `#![no_std]`/`#![no_core]` attribute was specified in the test source. You can override this behavior and explicitly -write `//@ needs-target-std` to only run the test when target supports std, even -if the test is `#![no_std]`/`#![no_core]`. +write `//@ needs-target-std` to only run the test when target supports std, +even if the test is `#![no_std]`/`#![no_core]`. -If you need to work with `#![no_std]` cross-compiling tests, consult the -[`minicore` test auxiliary](./minicore.md) chapter. +If you need to work with `#![no_std]` cross-compiling tests, +consult the [`minicore` test auxiliary](./minicore.md) chapter. [`tests/codegen-llvm`]: https://github.com/rust-lang/rust/tree/HEAD/tests/codegen-llvm [FileCheck]: https://llvm.org/docs/CommandGuide/FileCheck.html @@ -388,8 +391,8 @@ See the [FileCheck] documentation for a tutorial and more information. See also the [codegen tests](#codegen-tests) for a similar set of tests. -If you need to work with `#![no_std]` cross-compiling tests, consult the -[`minicore` test auxiliary](./minicore.md) chapter. +If you need to work with `#![no_std]` cross-compiling tests, +consult the [`minicore` test auxiliary](./minicore.md) chapter. [`tests/assembly-llvm`]: https://github.com/rust-lang/rust/tree/HEAD/tests/assembly-llvm @@ -444,10 +447,10 @@ There are several forms the `EMIT_MIR` comment can take: interested in the final state after an optimization. Some rare cases may want to use the "before" file for completeness. -- `// EMIT_MIR $MIR_PATH.diff` — where `$MIR_PATH` is the filename of the MIR - dump, such as `my_test_name.my_function.EarlyOtherwiseBranch`. - Compiletest will diff the `.before.mir` and `.after.mir` files, and compare the diff - output to the expected `.diff` file from the `EMIT_MIR` comment. +- `// EMIT_MIR $MIR_PATH.diff` — where `$MIR_PATH` is the filename of the MIR dump, + such as `my_test_name.my_function.EarlyOtherwiseBranch`. + Compiletest will diff the `.before.mir` and `.after.mir` files, + and compare the diff output to the expected `.diff` file from the `EMIT_MIR` comment. This is useful if you want to see how an optimization changes the MIR. @@ -457,8 +460,8 @@ There are several forms the `EMIT_MIR` comment can take: By default 32 bit and 64 bit targets use the same dump files, which can be problematic in the presence of pointers in constants or other bit width dependent things. -In that case you can add `// EMIT_MIR_FOR_EACH_BIT_WIDTH` to -your test, causing separate files to be generated for 32bit and 64bit systems. +In that case you can add `// EMIT_MIR_FOR_EACH_BIT_WIDTH` to your test, +causing separate files to be generated for 32bit and 64bit systems. [`tests/mir-opt`]: https://github.com/rust-lang/rust/tree/HEAD/tests/mir-opt @@ -491,8 +494,8 @@ Each test should be in a separate directory with a `rmake.rs` Rust program, called the *recipe*. A recipe will be compiled and executed by compiletest with the `run_make_support` library linked in. -If you need new utilities or functionality, consider extending and improving the -[`run_make_support`] library. +If you need new utilities or functionality, +consider extending and improving the [`run_make_support`] library. Compiletest directives like `//@ only-` or `//@ ignore-` are supported in `rmake.rs`, like in UI tests. @@ -524,11 +527,11 @@ Of course, some tests will not successfully *run* in this way. #### Using rust-analyzer with `rmake.rs` -Like other test programs, the `rmake.rs` scripts used by run-make tests do not -have rust-analyzer integration by default. +Like other test programs, +the `rmake.rs` scripts used by run-make tests do not have rust-analyzer integration by default. -To work around this when working on a particular test, temporarily create a -`Cargo.toml` file in the test's directory +To work around this when working on a particular test, +temporarily create a `Cargo.toml` file in the test's directory (e.g. `tests/run-make/sysroot-crates-are-unstable/Cargo.toml`) with these contents: @@ -589,8 +592,8 @@ Each mode also has an alias to run the coverage tests in just that mode: ./x test coverage-map -- tests/coverage/if.rs # runs the specified test in "coverage-map" mode only ``` -If a particular test should not be run in one of the coverage test modes for -some reason, use the `//@ ignore-coverage-map` or `//@ ignore-coverage-run` directives. +If a particular test should not be run in one of the coverage test modes for some reason, +use the `//@ ignore-coverage-map` or `//@ ignore-coverage-run` directives. #### `coverage-map` suite @@ -598,11 +601,11 @@ In `coverage-map` mode, these tests verify the mappings between source code regions and coverage counters that are emitted by LLVM. They compile the test with `--emit llvm-ir`, then use a custom tool ([`src/tools/coverage-dump`]) to extract and pretty-print the coverage mappings embedded in the IR. -These tests don't require the profiler runtime, so they run in PR CI jobs and are easy to -run/bless locally. +These tests don't require the profiler runtime, +so they run in PR CI jobs and are easy to run/bless locally. -These coverage map tests can be sensitive to changes in MIR lowering or MIR -optimizations, producing mappings that are different but produce identical coverage reports. +These coverage map tests can be sensitive to changes in MIR lowering or MIR optimizations, +producing mappings that are different but produce identical coverage reports. As a rule of thumb, any PR that doesn't change coverage-specific code should **feel free to re-bless** the `coverage-map` tests as necessary, without @@ -612,19 +615,19 @@ worrying about the actual changes, as long as the `coverage-run` tests still pas In `coverage-run` mode, these tests perform an end-to-end test of coverage reporting. They compile a test program with coverage instrumentation, run that -program to produce raw coverage data, and then use LLVM tools to process that -data into a human-readable code coverage report. +program to produce raw coverage data, +and then use LLVM tools to process that data into a human-readable code coverage report. -Instrumented binaries need to be linked against the LLVM profiler runtime, so -`coverage-run` tests are **automatically skipped** unless the profiler runtime +Instrumented binaries need to be linked against the LLVM profiler runtime, +so `coverage-run` tests are **automatically skipped** unless the profiler runtime is enabled in `bootstrap.toml`: ```toml build.profiler = true ``` -This also means that they typically don't run in PR CI jobs, though they do run -as part of the full set of CI jobs used for merging. +This also means that they typically don't run in PR CI jobs, +though they do run as part of the full set of CI jobs used for merging. #### `coverage-run-rustdoc` suite @@ -638,8 +641,8 @@ This avoids having to build rustdoc when only running the main `coverage` suite. ### Crash tests -[`tests/crashes`] serve as a collection of tests that are expected to cause the -compiler to ICE, panic or crash in some other way, so that accidental fixes are tracked. +[`tests/crashes`] serve as a collection of tests that are expected to cause the compiler to ICE, +panic, or crash in some other way, so that accidental fixes are tracked. Formerly, this was done at but doing it inside the rust-lang/rust testsuite is more convenient. @@ -659,8 +662,8 @@ When you do so, each issue number should be noted in the file name (`12345.rs` should suffice) and also inside the file by means of a `//@ known-bug: #12345` directive. Please [label][labeling] the relevant issues with `S-bug-has-test` once your PR is merged. -If you happen to fix one of the crashes, please move it to a fitting -subdirectory in `tests/ui` and give it a meaningful name. +If you happen to fix one of the crashes, +please move it to a fitting subdirectory in `tests/ui` and give it a meaningful name. Please add a doc comment at the top of the file explaining why this test exists. Even better will be if you can briefly explain how the example caused rustc to crash previously, and what was done to fix it. @@ -711,8 +714,8 @@ The `-L` flag is used to find the extern crates. `aux-crate` is very similar to `aux-build`. However, it uses the `--extern` flag to link to the extern crate to make the crate be available as an extern prelude. -That allows you to specify the additional syntax of the `--extern` flag, such as -renaming a dependency. +That allows you to specify the additional syntax of the `--extern` flag, +such as renaming a dependency. For example, `//@ aux-crate: foo=bar.rs` will compile `auxiliary/bar.rs` and make it available under the name `foo` within the test. This is similar to how Cargo does dependency renaming. @@ -722,8 +725,8 @@ For example, `//@ aux-crate: noprelude:foo=bar.rs`. `aux-bin` is similar to `aux-build` but will build a binary instead of a library. The binary will be available in `auxiliary/bin` relative to the working directory of the test. -`aux-codegen-backend` is similar to `aux-build`, but will then pass the compiled -dylib to `-Zcodegen-backend` when building the main file. +`aux-codegen-backend` is similar to `aux-build`, +but will then pass the compiled dylib to `-Zcodegen-backend` when building the main file. This will only work for tests in `tests/ui-fulldeps`, since it requires the use of compiler crates. ### Auxiliary proc-macro @@ -740,8 +743,8 @@ preset behavior compared to `aux-build` for the proc-macro test auxiliary: to produce a dylib for the aux crate. 3. The aux crate is made available to the test file via extern prelude with `--extern `. - Note that since UI tests default to edition - 2015, you still need to specify `extern ` unless the main + Note that since UI tests default to edition 2015, + you still need to specify `extern ` unless the main test file is using an edition that is 2018 or newer if you want to use the aux crate name in a `use` import. 4. The `proc_macro` crate is made available as an extern prelude module. @@ -794,8 +797,8 @@ This is done by adding a special directive at the top of the file: //@ revisions: foo bar baz ``` -This will result in the test being compiled (and tested) three times, once with -`--cfg foo`, once with `--cfg bar`, and once with `--cfg baz`. +This will result in the test being compiled (and tested) three times, once with `--cfg foo`, +once with `--cfg bar`, and once with `--cfg baz`. You can therefore use `#[cfg(foo)]` etc within the test to tweak each of these results. You can also customize directives and expected error messages to a particular revision. @@ -814,8 +817,8 @@ fn test_foo() { Multiple revisions can be specified in a comma-separated list, such as `//[foo,bar,baz]~^`. -In test suites that use the LLVM [FileCheck] tool, the current revision name is -also registered as an additional prefix for FileCheck directives: +In test suites that use the LLVM [FileCheck] tool, +the current revision name is also registered as an additional prefix for FileCheck directives: ```rust,ignore //@ revisions: NORMAL COVERAGE @@ -848,8 +851,8 @@ Normally, revision names mentioned in other directives and error annotations must correspond to an actual revision declared in a `revisions` directive. This is enforced by an `./x test tidy` check. -If a revision name needs to be temporarily removed from the revision list for -some reason, the above check can be suppressed by adding the revision name to an +If a revision name needs to be temporarily removed from the revision list for some reason, +the above check can be suppressed by adding the revision name to an `//@ unused-revision-names:` header instead. Specifying an unused name of `*` (i.e. `//@ unused-revision-names: *`) will @@ -857,10 +860,10 @@ permit any unused revision name to be mentioned. ## Compare modes -Compiletest can be run in different modes, called _compare modes_, which can be -used to compare the behavior of all tests with different compiler flags enabled. -This can help highlight what differences might appear with certain flags, and -check for any problems that might arise. +Compiletest can be run in different modes, called _compare modes_, +which can be used to compare the behavior of all tests with different compiler flags enabled. +This can help highlight what differences might appear with certain flags, +and check for any problems that might arise. To run the tests in a different mode, you need to pass the `--compare-mode` CLI flag: @@ -885,8 +888,8 @@ In CI, compare modes are only used in one Linux builder, and only with the follo This helps ensure that none of the debuginfo tests are affected when enabling split-DWARF. Note that compare modes are separate to [revisions](#revisions). -All revisions are tested when running `./x test tests/ui`, however compare-modes must be -manually run individually via the `--compare-mode` flag. +All revisions are tested when running `./x test tests/ui`, +however compare-modes must be manually run individually via the `--compare-mode` flag. ## Parallel frontend diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md index 3f274464c6e63..ca7a9f482f1ae 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/fuchsia.md @@ -14,12 +14,11 @@ Please contact the [fuchsia][fuchsia-ping] ping group and ask them for help. ## Building Fuchsia in CI -Fuchsia builds as part of the suite of bors tests that run before a pull request -is merged. +Fuchsia builds as part of the suite of bors tests that run before a pull request is merged. If you are worried that a pull request might break the Fuchsia builder and want -to test it out before submitting it to the bors queue, simply ask bors to run -the try job that builds the Fuchsia integration: +to test it out before submitting it to the bors queue, +simply ask bors to run the try job that builds the Fuchsia integration: ```text @bors try jobs=test-x86_64-fuchsia @@ -27,13 +26,13 @@ the try job that builds the Fuchsia integration: ## Building Fuchsia locally -Because Fuchsia uses languages other than Rust, it does not use Cargo as a build -system. It also requires the toolchain build to be configured in a [certain -way][build-toolchain]. +Because Fuchsia uses languages other than Rust, it does not use Cargo as a build system. +It also requires the toolchain build to be configured in a [certain way][build-toolchain]. The recommended way to build Fuchsia is to use the Docker scripts that check out -and run a Fuchsia build for you. If you've run Docker tests before, you can -simply run this command from your Rust checkout to download and build Fuchsia +and run a Fuchsia build for you. +If you've run Docker tests before, +you can simply run this command from your Rust checkout to download and build Fuchsia using your local Rust toolchain. ``` @@ -44,20 +43,21 @@ See the [Testing with Docker](../docker.md) chapter for more details on how to r and debug jobs with Docker. Note that a Fuchsia checkout is *large* – as of this writing, a checkout and -build takes over 67G of space – and as you might imagine, it takes a while to -complete. +build takes over 67G of space – and as you might imagine, it takes a while to complete. ### Modifying the Fuchsia checkout The main reason you would want to build Fuchsia locally is because you need to -investigate a regression. After running a Docker build, you'll find the Fuchsia -checkout inside the `obj/test-x86_64-fuchsia/fuchsia` directory of your Rust -checkout. If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] -script to `KEEP_CHECKOUT=1`, you can change the checkout as needed and rerun -the build command above. This will reuse all the build results from before. +investigate a regression. +After running a Docker build, +you'll find the Fuchsia checkout inside the `obj/test-x86_64-fuchsia/fuchsia` directory of your Rust +checkout. +If you modify the `KEEP_CHECKOUT` line in the [build-fuchsia.sh] script to `KEEP_CHECKOUT=1`, +you can change the checkout as needed and rerun +the build command above. +This will reuse all the build results from before. -You can find more options to customize the Fuchsia checkout in the -[build-fuchsia.sh] script. +You can find more options to customize the Fuchsia checkout in the [build-fuchsia.sh] script. ### Customizing the Fuchsia build @@ -73,14 +73,15 @@ to add this to your `$PATH` for some workflows. There are a few `fx` subcommands that are relevant, including: -- `fx set` accepts build arguments, writes them to `out/default/args.gn`, and - runs GN. -- `fx build` builds the Fuchsia project using Ninja. It will automatically pick - up changes to build arguments and rerun GN. By default it builds everything, +- `fx set` accepts build arguments, writes them to `out/default/args.gn`, and runs GN. +- `fx build` builds the Fuchsia project using Ninja. + It will automatically pick up changes to build arguments and rerun GN. + By default, it builds everything, but it also accepts target paths to build specific targets (see below). -- `fx clippy` runs Clippy on specific Rust targets (or all of them). We use this - in the Rust CI build to avoid running codegen on most Rust targets. Underneath - it invokes Ninja, just like `fx build`. The clippy results are saved in json +- `fx clippy` runs Clippy on specific Rust targets (or all of them). + We use this in the Rust CI build to avoid running codegen on most Rust targets. + Underneath, it invokes Ninja, just like `fx build`. + The clippy results are saved in json files inside the build output directory before being printed. #### Target paths @@ -91,20 +92,20 @@ GN uses paths like the following to identify build targets: //src/starnix/kernel:starnix_core ``` -The initial `//` means the root of the checkout, and the remaining slashes are -directory names. The string after `:` is the _target name_ of a target defined +The initial `//` means the root of the checkout, and the remaining slashes are directory names. +The string after `:` is the _target name_ of a target defined in the `BUILD.gn` file of that directory. -The target name can be omitted if it is the same as the directory name. In other -words, `//src/starnix/kernel` is the same as `//src/starnix/kernel:kernel`. +The target name can be omitted if it is the same as the directory name. +In other words, `//src/starnix/kernel` is the same as `//src/starnix/kernel:kernel`. These target paths are used inside `BUILD.gn` files to reference dependencies, and can also be used in `fx build`. #### Modifying compiler flags -You can put custom compiler flags inside a GN `config` that is added to a -target. As a simple example: +You can put custom compiler flags inside a GN `config` that is added to a target. +As a simple example: ``` config("everybody_loops") { @@ -118,20 +119,20 @@ rustc_binary("example") { } ``` -This will add the flag `-Zeverybody-loops` to rustc when building the `example` -target. Note that you can also use [`public_configs`] for a config to be added +This will add the flag `-Zeverybody-loops` to rustc when building the `example` target. +Note that you can also use [`public_configs`] for a config to be added to every target that depends on that target. -If you want to add a flag to every Rust target in the build, you can add -rustflags to the [`//build/config:compiler`] config or to the OS-specific -configs referenced in that file. Note that `cflags` and `ldflags` are ignored on -Rust targets. +If you want to add a flag to every Rust target in the build, +you can add rustflags to the [`//build/config:compiler`] config or to the OS-specific +configs referenced in that file. +Note that `cflags` and `ldflags` are ignored on Rust targets. #### Running ninja and rustc commands directly -Going down one layer, `fx build` invokes `ninja`, which in turn eventually -invokes `rustc`. All build actions are run inside the out directory, which is -usually `out/default` inside the Fuchsia checkout. +Going down one layer, `fx build` invokes `ninja`, which in turn eventually invokes `rustc`. +All build actions are run inside the out directory, +which is usually `out/default` inside the Fuchsia checkout. You can get ninja to print the actual command it invokes by forcing that command to fail, e.g. by adding a syntax error to one of the source files of the target. @@ -139,26 +140,25 @@ Once you have the command, you can run it from inside the output directory. After changing the toolchain itself, the build setting `rustc_version_string` in `out/default/args.gn` needs to be changed so that `fx build` or `ninja` will -rebuild all the Rust targets. This can be done in a text editor and the contents -of the string do not matter, as long as it changes from one build to the next. -[build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain -directory. +rebuild all the Rust targets. +This can be done in a text editor, and the contents of the string do not matter, +as long as it changes from one build to the next. +[build_fuchsia_from_rust_ci.sh] does this for you by hashing the toolchain directory. The Fuchsia website has more detailed documentation of the [build system]. #### Other tips and tricks When using `build_fuchsia_from_rust_ci.sh` you can comment out the `fx set` -command after the initial run so it won't rerun GN each time. If you do this you -can also comment out the version_string line to save a couple seconds. +command after the initial run so it won't rerun GN each time. +If you do this, you can also comment out the version_string line to save a couple seconds. -`export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the -initial build. +`export NINJA_PERSISTENT_MODE=1` to get faster ninja startup times after the initial build. ## Fuchsia target support -To learn more about Fuchsia target support, see the Fuchsia chapter in [the -rustc book][platform-support]. +To learn more about Fuchsia target support, +see the Fuchsia chapter in [the rustc book][platform-support]. [regressions]: https://gist.github.com/tmandry/7103eba4bd6a6fb0c439b5a90ae355fa [build-toolchain]: https://fuchsia.dev/fuchsia-src/development/build/rust_toolchain @@ -173,5 +173,5 @@ rustc book][platform-support]. [fuchsia-ping]: ../../notification-groups/fuchsia.md [^loc]: As of June 2024, Fuchsia had about 2 million lines of first-party Rust -code and a roughly equal amount of third-party code, as counted by tokei -(excluding comments and blanks). +code and a roughly equal amount of third-party code, +as counted by tokei (excluding comments and blanks). diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/rust-for-linux.md b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/rust-for-linux.md index a6a7374b811be..e2d3bec5d86f7 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/rust-for-linux.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem-test-jobs/rust-for-linux.md @@ -7,23 +7,22 @@ support for the Rust programming language into the Linux kernel. If a PR breaks the Rust for Linux CI job, then: -- If the breakage was unintentional and seems spurious, then let [RfL][rfl-ping] - know and retry. - - If the PR is urgent and retrying doesn't fix it, then disable the CI job - temporarily (comment out the `image: x86_64-rust-for-linux` job in +- If the breakage was unintentional and seems spurious, then let [RfL][rfl-ping] know and retry. + - If the PR is urgent and retrying doesn't fix it, + then disable the CI job temporarily (comment out the `image: x86_64-rust-for-linux` job in `src/ci/github-actions/jobs.yml`). - If the breakage was unintentional, then change the PR to resolve the breakage. -- If the breakage was intentional, then let [RfL][rfl-ping] know and discuss - what will the kernel need to change. +- If the breakage was intentional, + then let [RfL][rfl-ping] know and discuss what will the kernel need to change. - If the PR is urgent, then disable the CI job temporarily (comment out the `image: x86_64-rust-for-linux` job in `src/ci/github-actions/jobs.yml`). - If the PR can wait a few days, then wait for RfL maintainers to provide a - new Linux kernel commit hash with the needed changes done, and apply it to - the PR, which would confirm the changes work (update the `LINUX_VERSION` + new Linux kernel commit hash with the needed changes done, and apply it to the PR, + which would confirm the changes work (update the `LINUX_VERSION` environment variable in `src/ci/docker/scripts/rfl-build.sh`). -If you need to contact the RfL developers, you can ping the [Rust for Linux][rfl-ping] -ping group to ask for help: +If you need to contact the RfL developers, +you can ping the [Rust for Linux][rfl-ping] ping group to ask for help: ```text @rustbot ping rfl @@ -31,17 +30,17 @@ ping group to ask for help: ## Building Rust for Linux in CI -Rust for Linux builds as part of the suite of bors tests that run before a pull -request is merged. +Rust for Linux builds as part of the suite of bors tests that run before a pull request is merged. -The workflow builds a stage1 sysroot of the Rust compiler, downloads the Linux -kernel, and tries to compile several Rust for Linux drivers and examples using -this sysroot. RfL uses several unstable compiler/language features, therefore -this workflow notifies us if a given compiler change would break it. +The workflow builds a stage1 sysroot of the Rust compiler, downloads the Linux kernel, +and tries to compile several Rust for Linux drivers and examples using +this sysroot. +RfL uses several unstable compiler/language features, +therefore this workflow notifies us if a given compiler change would break it. If you are worried that a pull request might break the Rust for Linux builder -and want to test it out before submitting it to the bors queue, simply ask -bors to run the try job that builds the Rust for Linux integration: +and want to test it out before submitting it to the bors queue, +simply ask bors to run the try job that builds the Rust for Linux integration: `@bors try jobs=x86_64-rust-for-linux`. [rfl-ping]: ../../notification-groups/rust-for-linux.md diff --git a/src/doc/rustc-dev-guide/src/tests/ecosystem.md b/src/doc/rustc-dev-guide/src/tests/ecosystem.md index 9e5b3a1e1c11d..ff9aca59ebe49 100644 --- a/src/doc/rustc-dev-guide/src/tests/ecosystem.md +++ b/src/doc/rustc-dev-guide/src/tests/ecosystem.md @@ -7,9 +7,10 @@ regressions and make informed decisions about the evolution of the language. ### Crater -Crater is a tool which runs tests on many thousands of public projects. This -tool has its own separate infrastructure for running, and is not run as part of -CI. See the [Crater chapter](crater.md) for more details. +Crater is a tool which runs tests on many thousands of public projects. +This tool has its own separate infrastructure for running, and is not run as part of +CI. +See the [Crater chapter](crater.md) for more details. ### `cargotest` @@ -23,8 +24,8 @@ there aren't any significant regressions: ### Large OSS Project builders -We have CI jobs that build large open-source Rust projects that are used as -regression tests in CI. Our integration jobs build the following projects: +We have CI jobs that build large open-source Rust projects that are used as regression tests in CI. +Our integration jobs build the following projects: - [Fuchsia](./ecosystem-test-jobs/fuchsia.md) - [Rust for Linux](./ecosystem-test-jobs/rust-for-linux.md) diff --git a/src/doc/rustc-dev-guide/src/tests/intro.md b/src/doc/rustc-dev-guide/src/tests/intro.md index 82fb9597cb500..162f8b836fadb 100644 --- a/src/doc/rustc-dev-guide/src/tests/intro.md +++ b/src/doc/rustc-dev-guide/src/tests/intro.md @@ -1,9 +1,9 @@ # Testing the compiler -The Rust project runs a wide variety of different tests, orchestrated by the -build system (`./x test`). This section gives a brief overview of the different -testing tools. Subsequent chapters dive into [running tests](running.md) and -[adding new tests](adding.md). +The Rust project runs a wide variety of different tests, +orchestrated by the build system (`./x test`). +This section gives a brief overview of the different testing tools. +Subsequent chapters dive into [running tests](running.md) and [adding new tests](adding.md). ## Kinds of tests @@ -12,11 +12,10 @@ Almost all of them are driven by `./x test`, with some exceptions noted below. ### Compiletest -The main test harness for testing the compiler itself is a tool called -[compiletest]. +The main test harness for testing the compiler itself is a tool called [compiletest]. -[compiletest] supports running different styles of tests, organized into *test -suites*. A *test mode* may provide common presets/behavior for a set of *test +[compiletest] supports running different styles of tests, +organized into *test suites*. A *test mode* may provide common presets/behavior for a set of *test suites*. [compiletest]-supported tests are located in the [`tests`] directory. The [Compiletest chapter][compiletest] goes into detail on how to use this tool. @@ -28,9 +27,9 @@ The [Compiletest chapter][compiletest] goes into detail on how to use this tool. ### Package tests -The standard library and many of the compiler packages include typical Rust -`#[test]` unit tests, integration tests, and documentation tests. You can pass a -path to `./x test` for almost any package in the `library/` or `compiler/` +The standard library and many of the compiler packages include typical Rust `#[test]` unit tests, +integration tests, and documentation tests. +You can pass a path to `./x test` for almost any package in the `library/` or `compiler/` directory, and `x` will essentially run `cargo test` on that package. Examples: @@ -41,13 +40,14 @@ Examples: | `./x test library/core` | Runs tests on `core` only | | `./x test compiler/rustc_data_structures` | Runs tests on `rustc_data_structures` | -The standard library relies very heavily on documentation tests to cover its -functionality. However, unit tests and integration tests can also be used as -needed. Almost all of the compiler packages have doctests disabled. +The standard library relies very heavily on documentation tests to cover its functionality. +However, unit tests and integration tests can also be used as needed. +Almost all of the compiler packages have doctests disabled. All standard library and compiler unit tests are placed in separate `tests` file -(which is enforced in [tidy][tidy-unit-tests]). This ensures that when the test -file is changed, the crate does not need to be recompiled. For example: +(which is enforced in [tidy][tidy-unit-tests]). +This ensures that when the test file is changed, the crate does not need to be recompiled. +For example: ```rust,ignore #[cfg(test)] @@ -55,11 +55,9 @@ mod tests; ``` If it wasn't done this way, and you were working on something like `core`, that -would require recompiling the entire standard library, and the entirety of -`rustc`. +would require recompiling the entire standard library, and the entirety of `rustc`. -`./x test` includes some CLI options for controlling the behavior with these -package tests: +`./x test` includes some CLI options for controlling the behavior with these package tests: * `--doc` — Only runs documentation tests in the package. * `--all-targets` — Run all tests *except* documentation tests. @@ -69,8 +67,9 @@ package tests: ### Tidy -Tidy is a custom tool used for validating source code style and formatting -conventions, such as rejecting long lines. There is more information in the +Tidy is a custom tool used for validating source code style and formatting conventions, +such as rejecting long lines. +There is more information in the [section on coding conventions](../conventions.md#formatting) or the [Tidy Readme]. > Examples: `./x test tidy` @@ -80,9 +79,8 @@ conventions, such as rejecting long lines. There is more information in the ### Formatting -Rustfmt is integrated with the build system to enforce uniform style across the -compiler. The formatting check is automatically run by the Tidy tool mentioned -above. +Rustfmt is integrated with the build system to enforce uniform style across the compiler. +The formatting check is automatically run by the Tidy tool mentioned above. Examples: @@ -94,10 +92,10 @@ Examples: ### Book documentation tests -All of the books that are published have their own tests, primarily for -validating that the Rust code examples pass. Under the hood, these are -essentially using `rustdoc --test` on the markdown files. The tests can be run -by passing a path to a book to `./x test`. +All of the books that are published have their own tests, +primarily for validating that the Rust code examples pass. +Under the hood, these are essentially using `rustdoc --test` on the markdown files. +The tests can be run by passing a path to a book to `./x test`. > Example: `./x test src/doc/book` @@ -114,8 +112,8 @@ This requires building all of the documentation, which might take a while. ### `distcheck` -`distcheck` verifies that the source distribution tarball created by the build -system will unpack, build, and run all tests. +`distcheck` verifies that the source distribution tarball created by the build system will unpack, +build, and run all tests. ```console ./x test distcheck @@ -123,25 +121,24 @@ system will unpack, build, and run all tests. ### Tool tests -Packages that are included with Rust have all of their tests run as well. This -includes things such as cargo, clippy, rustfmt, miri, bootstrap (testing the +Packages that are included with Rust have all of their tests run as well. +This includes things such as cargo, clippy, rustfmt, miri, bootstrap (testing the Rust build system itself), etc. -Most of the tools are located in the [`src/tools`] directory. To run the tool's -tests, just pass its path to `./x test`. +Most of the tools are located in the [`src/tools`] directory. +To run the tool's tests, just pass its path to `./x test`. > Example: `./x test src/tools/cargo` Usually these tools involve running `cargo test` within the tool's directory. -If you want to run only a specified set of tests, append `--test-args -FILTER_NAME` to the command. +If you want to run only a specified set of tests, append `--test-args FILTER_NAME` to the command. > Example: `./x test src/tools/miri --test-args padding` -In CI, some tools are allowed to fail. Failures send notifications to the -corresponding teams, and is tracked on the [toolstate website]. More information -can be found in the [toolstate documentation]. +In CI, some tools are allowed to fail. +Failures send notifications to the corresponding teams, and is tracked on the [toolstate website]. +More information can be found in the [toolstate documentation]. [`src/tools`]: https://github.com/rust-lang/rust/tree/HEAD/src/tools/ [toolstate documentation]: https://forge.rust-lang.org/infra/toolstate.html @@ -150,14 +147,14 @@ can be found in the [toolstate documentation]. ### Ecosystem testing Rust tests integration with real-world code to catch regressions and make -informed decisions about the evolution of the language. There are several kinds -of ecosystem tests, including Crater. See the [Ecosystem testing -chapter](ecosystem.md) for more details. +informed decisions about the evolution of the language. +There are several kinds of ecosystem tests, including Crater. +See the [Ecosystem testing chapter](ecosystem.md) for more details. ### Performance testing -A separate infrastructure is used for testing and tracking performance of the -compiler. See the [Performance testing chapter](perf.md) for more details. +A separate infrastructure is used for testing and tracking performance of the compiler. +See the [Performance testing chapter](perf.md) for more details. ### Codegen backend testing diff --git a/src/doc/rustc-dev-guide/src/thir.md b/src/doc/rustc-dev-guide/src/thir.md index 1ba30d86a6913..8b80d0be16874 100644 --- a/src/doc/rustc-dev-guide/src/thir.md +++ b/src/doc/rustc-dev-guide/src/thir.md @@ -136,7 +136,7 @@ Thir { kind: Scope { region_scope: Node(5), hir_id: HirId(DefId(0:3 ~ main[26fd]::main).5), - // reference to expression 0 above + // reference to expression 2 above value: e2, }, ty: i32, diff --git a/src/etc/lldb_lookup.py b/src/etc/lldb_lookup.py index 365816dc8489a..94ed47af1891f 100644 --- a/src/etc/lldb_lookup.py +++ b/src/etc/lldb_lookup.py @@ -40,6 +40,7 @@ ClangEncodedEnumSummaryProvider, StructSummaryProvider, f16SummaryProvider, + f128SummaryProvider, # re-exports get_template_args as get_template_args, resolve_msvc_template_arg as resolve_msvc_template_arg, @@ -181,6 +182,17 @@ def register_providers_compatibility(): DEFAULT_TYPE_OPTIONS | lldb.eTypeOptionHideChildren, ) + if LLDBFeature.Float128 in FEATURE_FLAGS: + # Force f128 summary on windows-msvc since most Windows debuggers don't support PDB f128 + register_summary( + f128SummaryProvider, + lldb.SBTypeNameSpecifier( + MOD_PREFIX + is_msvc_f128.__name__, + lldb.eFormatterMatchCallback, + ), + DEFAULT_TYPE_OPTIONS | lldb.eTypeOptionHideChildren, + ) + # Tuple-structs register_synth( TupleSyntheticProvider, @@ -501,6 +513,11 @@ def is_msvc_f16(type: lldb.SBType, _dict: LLDBOpaque) -> bool: return type.GetName() == "f16" and type.IsAggregateType() +def is_msvc_f128(type: lldb.SBType, _dict: LLDBOpaque) -> bool: + # Most Windows debuggers don't support PDB f128. + return type.GetName() == "f128" and type.IsAggregateType() + + def classify_rust_type(type: lldb.SBType, is_msvc: bool) -> RustType: if type.IsPointerType(): return RustType.Indirection diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py index 2791dae3600b0..a3a424ac1abce 100644 --- a/src/etc/lldb_providers.py +++ b/src/etc/lldb_providers.py @@ -542,6 +542,12 @@ def f16SummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: ) +def f128SummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: + from lldb import eBasicTypeFloat128 + + return valobj.Cast(valobj.GetTarget().GetBasicType(eBasicTypeFloat128)).GetValue() + + def sequence_formatter(output: str, valobj: SBValue, _dict: LLDBOpaque): length: int = valobj.GetNumChildren() diff --git a/src/etc/natvis/intrinsic.natvis b/src/etc/natvis/intrinsic.natvis index 49e0ce319efac..ac9bf1c427957 100644 --- a/src/etc/natvis/intrinsic.natvis +++ b/src/etc/natvis/intrinsic.natvis @@ -59,6 +59,118 @@ {(float) (sign() * (raw_significand() + 1.0) * two_pow_exponent())} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {sign()}inf + NaN + + {sign()}0x0p+0 + + {sign()}0x1{subnormal_hex()}p{-16382 - subnormal_shift(),d} + {sign()}0x1{normal_hex()}p{normal_exponent_sign()}{normal_exponent(),d} + + + "0x" + hex128(high_bits, low_bits, 128) + + () diff --git a/src/gcc b/src/gcc index 6f155cc3f5a2d..badf78d09d16e 160000 --- a/src/gcc +++ b/src/gcc @@ -1 +1 @@ -Subproject commit 6f155cc3f5a2dff33afe6cc3ed6c2e0e605ae6a3 +Subproject commit badf78d09d16e66f4ca07971c51aa6a227558d4f diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 744c7eb288dfa..1b5968e20e4b4 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -576,7 +576,7 @@ impl Item { } pub(crate) fn links(&self, cx: &Context<'_>) -> Vec { - use crate::html::format::{href, link_tooltip}; + use crate::html::format::{href_with_path_check, link_tooltip}; let Some(links) = cx.cache().intra_doc_links.get(&self.item_or_reexport_id()) else { return vec![]; @@ -585,7 +585,7 @@ impl Item { .iter() .filter_map(|ItemLink { link: s, link_text, page_id: id, fragment }| { debug!(?id); - if let Ok(HrefInfo { mut url, .. }) = href(*id, cx) { + if let Ok(HrefInfo { mut url, .. }) = href_with_path_check(*id, cx, link_text) { debug!(?url); match fragment { Some(UrlFragment::Item(def_id)) => { @@ -601,7 +601,7 @@ impl Item { Some(RenderedLink { original_text: s.clone(), new_text: link_text.clone(), - tooltip: link_tooltip(*id, fragment, cx).to_string(), + tooltip: link_tooltip(*id, fragment, cx, Some(link_text)).to_string(), href: url, }) } else { diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index ccee062584e01..c89c3dbd3fbf5 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -18,6 +18,45 @@ use crate::formats::item_type::ItemType; use crate::html::render::{IndexItem, IndexItemInfo}; use crate::visit_lib::RustdocEffectiveVisibilities; +pub(crate) struct PathInfo { + /// Parts of the fully qualified path. So in `foo::bar::bib`, it will + /// be `["foo", "bar", "bib"]`. + pub(crate) parts: Vec, + pub(crate) ty: ItemType, + /// When a reexport inline an item, we can end up with the same `DefId` with multiple local + /// targets. So in case like: + /// + /// ``` + /// /// Link to [`a2`]. + /// pub use std::ffi::os_str::OsString as a1; + /// /// Link to [`a1`]. + /// pub use std::ffi::os_str::OsString as a2; + /// /// Link to [`a2`]. + /// pub use std::ffi::os_str::OsString as a3; + /// ``` + /// + /// To ensure that `a1` and `a2` links to `a1` and `a2` which have the same `DefId`, we need + /// to store both `a1` and `a2` paths. + /// + /// The path stored in `parts` is not present in `alternatives`. + pub(crate) alternatives: Vec>, +} + +impl PathInfo { + pub(crate) fn get_preferred_path(&self, preferred_name: Option<&str>) -> &[Symbol] { + if let Some(preferred_name) = preferred_name + && let Some(alternative_path) = self + .alternatives + .iter() + .find(|path| path.last().is_some_and(|last| last.as_str() == preferred_name)) + { + alternative_path + } else { + &self.parts + } + } +} + /// This cache is used to store information about the [`clean::Crate`] being /// rendered in order to provide more useful documentation. This contains /// information like all implementors of a trait, all traits a type implements, @@ -42,7 +81,7 @@ pub(crate) struct Cache { /// URLs when a type is being linked to. External paths are not located in /// this map because the `External` type itself has all the information /// necessary. - pub(crate) paths: FxIndexMap, ItemType)>, + pub(crate) paths: FxIndexMap, /// Similar to `paths`, but only holds external paths. This is only used for /// generating explicit hyperlinks to other crates. @@ -358,7 +397,8 @@ impl DocFolder for CacheBuilder<'_, '_> { | clean::ForeignTypeItem | clean::MacroItem(..) | clean::ProcMacroItem(..) - | clean::VariantItem(..) => { + | clean::VariantItem(..) + | clean::PrimitiveItem(..) => { use rustc_data_structures::fx::IndexEntry as Entry; let skip_because_unstable = matches!( @@ -376,21 +416,31 @@ impl DocFolder for CacheBuilder<'_, '_> { let item_def_id = item.item_id.expect_def_id(); match self.cache.paths.entry(item_def_id) { Entry::Vacant(entry) => { - entry.insert((self.cache.stack.clone(), item.type_())); + entry.insert(PathInfo { + parts: self.cache.stack.clone(), + ty: item.type_(), + alternatives: Vec::new(), + }); } Entry::Occupied(mut entry) => { - if entry.get().0.len() > self.cache.stack.len() { - entry.insert((self.cache.stack.clone(), item.type_())); + // Shorter paths are preferred by default. + if entry.get().parts.len() > self.cache.stack.len() { + let old_parts = std::mem::replace( + &mut entry.get_mut().parts, + self.cache.stack.clone(), + ); + // We only keep the old path if it's a different (final) name. + if old_parts.last() != self.cache.stack.last() { + entry.get_mut().alternatives.push(old_parts); + } + } + if !entry.get().alternatives.contains(&self.cache.stack) { + entry.get_mut().alternatives.push(self.cache.stack.clone()); } } } } } - clean::PrimitiveItem(..) => { - self.cache - .paths - .insert(item.item_id.expect_def_id(), (self.cache.stack.clone(), item.type_())); - } clean::ExternCrateItem { .. } | clean::ImportItem(..) @@ -570,7 +620,7 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It // in a field of the cache whose elements are added to the search index later, // after cache building is complete (see `handle_orphan_impl_child`). match cache.paths.get(&parent_did) { - Some((fqp, _)) => (Some(parent_did), &fqp[..fqp.len() - 1]), + Some(info) => (Some(parent_did), &info.parts[..info.parts.len() - 1]), None => { handle_orphan_impl_child(cache, item, parent_did); return; diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 54ff94ed1d614..0ca2782ed2151 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -543,6 +543,7 @@ pub(crate) fn href_with_root_path( original_did: DefId, cx: &Context<'_>, root_path: Option<&str>, + preferred_name: Option<&str>, ) -> Result { let tcx = cx.tcx(); let def_kind = tcx.def_kind(original_did); @@ -553,7 +554,9 @@ pub(crate) fn href_with_root_path( } // If this a constructor, we get the parent (either a struct or a variant) and then // generate the link for this item. - DefKind::Ctor(..) => return href_with_root_path(tcx.parent(original_did), cx, root_path), + DefKind::Ctor(..) => { + return href_with_root_path(tcx.parent(original_did), cx, root_path, preferred_name); + } DefKind::ExternCrate => { // Link to the crate itself, not the `extern crate` item. if let Some(local_did) = original_did.as_local() { @@ -564,7 +567,7 @@ pub(crate) fn href_with_root_path( } _ => original_did, }; - if is_unnamable(cx.tcx(), did) { + if is_unnamable(tcx, did) { return Err(HrefError::UnnamableItem); } let cache = cx.cache(); @@ -586,12 +589,12 @@ pub(crate) fn href_with_root_path( } let (fqp, shortty, url_parts, is_absolute) = match cache.paths.get(&did) { - Some(&(ref fqp, shortty)) => ( - fqp, - shortty, + Some(info) => ( + info.get_preferred_path(preferred_name), + info.ty, { - let module_fqp = to_module_fqp(shortty, fqp.as_slice()); - debug!(?fqp, ?shortty, ?module_fqp); + let module_fqp = to_module_fqp(info.ty, info.parts.as_slice()); + debug!(?info.parts, ?info.ty, ?module_fqp); href_relative_parts(module_fqp, relative_to) }, false, @@ -604,7 +607,7 @@ pub(crate) fn href_with_root_path( if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&def_id_to_get) { let module_fqp = to_module_fqp(shortty, fqp); let (parts, is_absolute) = url_parts(cache, did, module_fqp, relative_to)?; - (fqp, shortty, parts, is_absolute) + (fqp.as_slice(), shortty, parts, is_absolute) } else if matches!(def_kind, DefKind::Macro(_)) { return generate_macro_def_id_path(did, cx, root_path); } else if did.is_local() { @@ -617,12 +620,20 @@ pub(crate) fn href_with_root_path( Ok(HrefInfo { url: make_href(root_path, shortty, url_parts, fqp, is_absolute), kind: shortty, - rust_path: fqp.clone(), + rust_path: fqp.to_vec(), }) } pub(crate) fn href(did: DefId, cx: &Context<'_>) -> Result { - href_with_root_path(did, cx, None) + href_with_root_path(did, cx, None, None) +} + +pub(crate) fn href_with_path_check( + did: DefId, + cx: &Context<'_>, + text: &str, +) -> Result { + href_with_root_path(did, cx, None, Some(text)) } /// Both paths should only be modules. @@ -660,14 +671,21 @@ pub(crate) fn link_tooltip( did: DefId, fragment: &Option, cx: &Context<'_>, + preferred_name: Option<&str>, ) -> impl fmt::Display { fmt::from_fn(move |f| { let cache = cx.cache(); - let Some((fqp, shortty)) = cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) + let Some((fqp, shortty)) = cache + .paths + .get(&did) + .map(|info| (info.get_preferred_path(preferred_name), info.ty)) + .or_else(|| { + cache.external_paths.get(&did).map(|(fqp, shortty)| (fqp.as_slice(), *shortty)) + }) else { return Ok(()); }; - let fqp = if *shortty == ItemType::Primitive { + let fqp = if shortty == ItemType::Primitive { // primitives are documented in a crate, but not actually part of it slice::from_ref(fqp.last().unwrap()) } else { @@ -679,7 +697,7 @@ pub(crate) fn link_tooltip( for component in fqp { write!(f, "{component}::")?; } - if *shortty == ItemType::Enum && tcx.def_kind(id) == DefKind::Field { + if shortty == ItemType::Enum && tcx.def_kind(id) == DefKind::Field { write!(f, "{}::", tcx.item_name(tcx.parent(id)))?; } write!(f, "{}", tcx.item_name(id))?; diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 89d50680c3a3b..9c73e3b4ad687 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -1406,23 +1406,30 @@ fn generate_link_to_def( LinkFromSrc::Local(span) => { context.href_from_span_relative(*span, &href_context.current_href) } - LinkFromSrc::External(def_id) => { - format::href_with_root_path(*def_id, context, Some(href_context.root_path)) - .ok() - .map(|HrefInfo { url, .. }| url) - } + LinkFromSrc::External(def_id) => format::href_with_root_path( + *def_id, + context, + Some(href_context.root_path), + None, + ) + .ok() + .map(|HrefInfo { url, .. }| url), LinkFromSrc::Primitive(prim) => format::href_with_root_path( PrimitiveType::primitive_locations(context.tcx())[prim], context, Some(href_context.root_path), + None, + ) + .ok() + .map(|HrefInfo { url, .. }| url), + LinkFromSrc::Doc(def_id) => format::href_with_root_path( + *def_id, + context, + Some(href_context.root_path), + None, ) .ok() .map(|HrefInfo { url, .. }| url), - LinkFromSrc::Doc(def_id) => { - format::href_with_root_path(*def_id, context, Some(href_context.root_path)) - .ok() - .map(|HrefInfo { url, .. }| url) - } } }) { diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 56dd665177a93..d00b705d3766c 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -296,19 +296,19 @@ impl<'tcx> Context<'tcx> { &self.shared.style_files, ) } else { - if let Some(&(ref names, ty)) = self.cache().paths.get(&it.item_id.expect_def_id()) - && (self.current.len() + 1 != names.len() - || self.current.iter().zip(names.iter()).any(|(a, b)| a != b)) + if let Some(info) = self.cache().paths.get(&it.item_id.expect_def_id()) + && (self.current.len() + 1 != info.parts.len() + || self.current.iter().zip(info.parts.iter()).any(|(a, b)| a != b)) { // We checked that the redirection isn't pointing to the current file, // preventing an infinite redirection loop in the generated // documentation. let path = fmt::from_fn(|f| { - for name in &names[..names.len() - 1] { + for name in &info.parts[..info.parts.len() - 1] { write!(f, "{name}/")?; } - write!(f, "{}", print_ty_path(ty, names.last().unwrap().as_str())) + write!(f, "{}", print_ty_path(info.ty, info.parts.last().unwrap().as_str())) }); match self.shared.redirections { Some(ref redirections) => { @@ -320,7 +320,7 @@ impl<'tcx> Context<'tcx> { let _ = write!( current_path, "{}", - print_ty_path(ty, names.last().unwrap().as_str()) + print_ty_path(info.ty, info.parts.last().unwrap().as_str()) ); redirections.borrow_mut().insert(current_path, path.to_string()); } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 6f66dcf9eae83..46dba0253766a 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1455,12 +1455,12 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) -> // [^115718]: https://github.com/rust-lang/rust/issues/115718 let cache = &cx.shared.cache; if let Some(target_did) = t.type_.def_id(cache) - && let get_extern = { || cache.external_paths.get(&target_did) } - && let Some(&(ref target_fqp, target_type)) = - cache.paths.get(&target_did).or_else(get_extern) + && let get_extern = { || cache.external_paths.get(&target_did).map(|(fqp, shortty)| (fqp, *shortty)) } + && let Some((target_fqp, target_type)) = + cache.paths.get(&target_did).map(|info| (&info.parts, info.ty)).or_else(get_extern) && target_type.is_adt() // primitives cannot be inlined && let Some(self_did) = it.item_id.as_def_id() - && let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) } + && let get_local = { || cache.paths.get(&self_did).map(|info| &info.parts) } && let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) { let mut js_src_path: UrlPartsBuilder = diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index 4c93e632ab467..b459ead9481cb 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -1279,7 +1279,7 @@ pub(crate) fn build_index( for &OrphanImplItem { impl_id, parent, trait_parent, ref item, ref impl_generics } in &cache.orphan_impl_items { - if let Some((fqp, _)) = cache.paths.get(&parent) { + if let Some(path_info) = cache.paths.get(&parent) { let info = IndexItemInfo::new( tcx, cache, @@ -1291,7 +1291,7 @@ pub(crate) fn build_index( search_index.push(IndexItem { defid: item.item_id.as_def_id(), name: item.name.unwrap(), - module_path: fqp[..fqp.len() - 1].to_vec(), + module_path: path_info.parts[..path_info.parts.len() - 1].to_vec(), parent: Some(parent), parent_idx: None, trait_parent, @@ -1418,8 +1418,15 @@ pub(crate) fn build_index( cache .paths .get(&defid) - .or_else(|| check_external.then(|| cache.external_paths.get(&defid)).flatten()) - .map(|&(ref fqp, ty)| { + .map(|info| (&info.parts, info.ty)) + .or_else(|| { + check_external + .then(|| { + cache.external_paths.get(&defid).map(|(parts, ty)| (parts, *ty)) + }) + .flatten() + }) + .map(|(fqp, ty)| { let pathid = serialized_index.names.len(); match serialized_index.crate_paths_index.entry((ty, fqp.clone())) { Entry::Occupied(entry) => *entry.get(), @@ -1661,8 +1668,10 @@ pub(crate) fn build_index( used_in_function_signature, )), RenderTypeId::DefId(defid) => { - if let Some(&(ref fqp, item_type)) = - paths.get(&defid).or_else(|| external_paths.get(&defid)) + if let Some((fqp, item_type)) = paths + .get(&defid) + .map(|info| (&info.parts, info.ty)) + .or_else(|| external_paths.get(&defid).map(|(parts, ty)| (parts, *ty))) { if tcx.lang_items().fn_mut_trait() == Some(defid) || tcx.lang_items().fn_once_trait() == Some(defid) @@ -1974,8 +1983,11 @@ pub(crate) fn get_function_type_for_search( let impl_or_trait_generics = impl_generics.or_else(|| { if let Some(def_id) = parent && let Some(trait_) = cache.traits.get(&def_id) - && let Some((path, _)) = - cache.paths.get(&def_id).or_else(|| cache.external_paths.get(&def_id)) + && let Some((path, _)) = cache + .paths + .get(&def_id) + .map(|info| (&info.parts, info.ty)) + .or_else(|| cache.external_paths.get(&def_id).map(|(parts, ty)| (parts, *ty))) { let path = clean::Path { res: rustc_hir::def::Res::Def(rustc_hir::def::DefKind::Trait, def_id), diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 5c963afd3a221..258970bbb3b8f 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -834,12 +834,17 @@ impl TraitAliasPart { // FIXME: this is a vague explanation for why this can't be a `get`, in // theory it should be... let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) { - Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) { + Some(p) => match cache + .paths + .get(&did) + .map(|info| (&info.parts, info.ty)) + .or_else(|| cache.external_paths.get(&did).map(|(parts, ty)| (parts, *ty))) + { Some((_, t)) => (p, t), None => continue, }, None => match cache.external_paths.get(&did) { - Some((p, t)) => (p, t), + Some((p, t)) => (p, *t), None => continue, }, }; @@ -987,8 +992,10 @@ impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> { return; } let Some(target_did) = t.type_.def_id(cache) else { return }; - let get_extern = { || cache.external_paths.get(&target_did) }; - let Some(&(ref target_fqp, target_type)) = cache.paths.get(&target_did).or_else(get_extern) + let get_extern = + { || cache.external_paths.get(&target_did).map(|(parts, ty)| (parts, *ty)) }; + let Some((target_fqp, target_type)) = + cache.paths.get(&target_did).map(|info| (&info.parts, info.ty)).or_else(get_extern) else { return; }; @@ -1004,7 +1011,7 @@ impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> { .collect(); AliasedType { target_fqp: &target_fqp[..], target_type, impl_ } }); - let get_local = { || cache.paths.get(&self_did).map(|(p, _)| p) }; + let get_local = { || cache.paths.get(&self_did).map(|info| &info.parts) }; let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else { return; }; diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index f161b31f94dcb..bdd2b7d416d80 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -113,8 +113,9 @@ impl<'tcx> JsonRenderer<'tcx> { .cache .paths .iter() - .chain(&self.cache.external_paths) - .map(|(&k, &(ref path, kind))| { + .map(|(k, info)| (k, (&info.parts, info.ty))) + .chain(self.cache.external_paths.iter().map(|(k, (parts, ty))| (k, (parts, *ty)))) + .map(|(&k, (path, kind))| { ( self.id_from_item_default(k.into()), types::ItemSummary { @@ -195,8 +196,9 @@ impl<'tcx> JsonRenderer<'tcx> { self.cache .paths .get(&item_id) - .or_else(|| self.cache.external_paths.get(&item_id)) - .map(|(path, _)| path.iter().map(|name| name.to_string()).collect()) + .map(|info| &info.parts) + .or_else(|| self.cache.external_paths.get(&item_id).map(|(parts, _)| parts)) + .map(|path| path.iter().map(|name| name.to_string()).collect()) } } diff --git a/src/tools/miri/tests/panic/mir-validation.stderr b/src/tools/miri/tests/panic/mir-validation.stderr index 115820510dd89..1d40c93d709e6 100644 --- a/src/tools/miri/tests/panic/mir-validation.stderr +++ b/src/tools/miri/tests/panic/mir-validation.stderr @@ -7,12 +7,9 @@ LL | *(tuple.0) = 1; thread 'rustc' ($TID) panicked at compiler/rustc_mir_transform/src/validate.rs:LL:CC: -broken MIR in Item(DefId) (after phase change to runtime-optimized) at bb0[1]: -place (*(_2.0: *mut i32)) has deref as a later projection (it is only permitted as the first projection) +Box stack backtrace: -error: the compiler unexpectedly panicked. This is a bug - diff --git a/tests/assembly-llvm/asm/amdgpu-vec-types.rs b/tests/assembly-llvm/asm/amdgpu-vec-types.rs index 1613e603fca49..8ff4208636bc6 100644 --- a/tests/assembly-llvm/asm/amdgpu-vec-types.rs +++ b/tests/assembly-llvm/asm/amdgpu-vec-types.rs @@ -14,11 +14,12 @@ #![allow( asm_sub_register, improper_gpu_kernel_arg, - improper_ctypes_definitions, non_camel_case_types, unused_assignments, unused_variables )] +#![deny(unfulfilled_lint_expectations)] +#![expect(improper_ctypes_definitions)] extern crate minicore; use minicore::simd::*; diff --git a/tests/assembly-llvm/naked-functions/wasm32.rs b/tests/assembly-llvm/naked-functions/wasm32.rs index e2a2ab94c8a33..b2754029021bf 100644 --- a/tests/assembly-llvm/naked-functions/wasm32.rs +++ b/tests/assembly-llvm/naked-functions/wasm32.rs @@ -99,7 +99,6 @@ extern "C" fn fn_i64_i64(num: i64) -> i64 { // wasm32-unknown: .functype fn_i128_i128 (i32, i64, i64) -> () // wasm32-wasip1: .functype fn_i128_i128 (i32, i64, i64) -> () // wasm64-unknown: .functype fn_i128_i128 (i64, i64, i64) -> () -#[allow(improper_ctypes_definitions)] #[no_mangle] #[unsafe(naked)] extern "C" fn fn_i128_i128(num: i128) -> i128 { diff --git a/tests/assembly-llvm/reg-struct-return.rs b/tests/assembly-llvm/reg-struct-return.rs index d364954abe30d..59e40c9e6fb12 100644 --- a/tests/assembly-llvm/reg-struct-return.rs +++ b/tests/assembly-llvm/reg-struct-return.rs @@ -23,6 +23,7 @@ use minicore::*; // Verifies ABI changes for small structs, where both fields fit into one register. // WITH is expected to use register return, WITHOUT should use hidden pointer. mod Small { + #[repr(C)] struct SmallStruct { a: i8, b: i8, @@ -66,6 +67,7 @@ mod Small { // WITH is expected to still use register return, WITHOUT should use hidden // pointer. mod Pivot { + #[repr(C)] struct PivotStruct { a: i32, b: i32, @@ -109,6 +111,7 @@ mod Pivot { // maximum size for reg-struct-return (8 bytes). // Here, the hidden pointer convention should be used even when `-Zreg-struct-return` is set. mod Large { + #[repr(C)] struct LargeStruct { a: i32, b: i32, diff --git a/tests/codegen-llvm/abi-x86_64_sysv.rs b/tests/codegen-llvm/abi-x86_64_sysv.rs index 09909f994d652..b8912c91ebaee 100644 --- a/tests/codegen-llvm/abi-x86_64_sysv.rs +++ b/tests/codegen-llvm/abi-x86_64_sysv.rs @@ -4,12 +4,14 @@ #![crate_type = "lib"] +#[repr(C)] pub struct S24 { a: i8, b: i8, c: i8, } +#[repr(C)] pub struct S48 { a: i16, b: i16, diff --git a/tests/codegen-llvm/bpf-abi/indirect-return.rs b/tests/codegen-llvm/bpf-abi/indirect-return.rs index c285bd9431c58..437f4ab0f3abc 100644 --- a/tests/codegen-llvm/bpf-abi/indirect-return.rs +++ b/tests/codegen-llvm/bpf-abi/indirect-return.rs @@ -11,6 +11,7 @@ extern crate minicore; +#[repr(C)] struct Big { a: [u16; 32], b: u64, diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index 4ba7ecad764f2..c900c3debd504 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -103,7 +103,8 @@ #![feature(no_core, lang_items, f16, f128)] #![no_core] -#![allow(improper_ctypes)] // only Complex<{float}> is guaranteed to be ABI-compatible for now +// only Complex<{float}> is guaranteed to be ABI-compatible for now +#![expect(improper_ctypes_definitions)] #![crate_type = "lib"] extern crate minicore; diff --git a/tests/codegen-llvm/regparm-inreg.rs b/tests/codegen-llvm/regparm-inreg.rs index 77d4c206071e7..bf6bc4dd7bf0e 100644 --- a/tests/codegen-llvm/regparm-inreg.rs +++ b/tests/codegen-llvm/regparm-inreg.rs @@ -52,14 +52,15 @@ pub mod tests { #[no_mangle] pub extern "thiscall" fn f6(_: i32, _: i32, _: i32) {} + #[repr(C)] struct S1 { x1: i32, } - // regparm0: @f7(i32 noundef %_1, i32 noundef %_2, i32 noundef %_3, i32 noundef %_4) - // regparm1: @f7(i32 inreg noundef %_1, i32 noundef %_2, i32 noundef %_3, i32 noundef %_4) - // regparm2: @f7(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 noundef %_3, i32 noundef %_4) - // regparm3: @f7(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 inreg noundef %_3, - // regparm3-SAME: i32 noundef %_4) + // regparm0: @f7(i32 noundef %_1, i32 noundef %_2, ptr {{.*}} byval([4 x i8]) {{.*}} %_3, i32 noundef %_4) + // regparm1: @f7(i32 inreg noundef %_1, i32 noundef %_2, ptr {{.*}} byval([4 x i8]) {{.*}} %_3, i32 noundef %_4) + // regparm2: @f7(i32 inreg noundef %_1, i32 inreg noundef %_2, ptr {{.*}} byval([4 x i8]) {{.*}} %_3, i32 noundef %_4) + // regparm3: @f7(i32 inreg noundef %_1, i32 inreg noundef %_2, ptr {{.*}} byval([4 x i8]) {{.*}} %_3, + // regparm3-SAME: i32 inreg noundef %_4) #[no_mangle] pub extern "C" fn f7(_: i32, _: i32, _: S1, _: i32) {} diff --git a/tests/codegen-llvm/riscv-abi/riscv64-lp64-lp64f-lp64d-abi.rs b/tests/codegen-llvm/riscv-abi/riscv64-lp64-lp64f-lp64d-abi.rs index 0f5a449ead133..ae806aa64f513 100644 --- a/tests/codegen-llvm/riscv-abi/riscv64-lp64-lp64f-lp64d-abi.rs +++ b/tests/codegen-llvm/riscv-abi/riscv64-lp64-lp64f-lp64d-abi.rs @@ -5,7 +5,7 @@ #![crate_type = "lib"] #![no_core] #![feature(no_core, lang_items)] -#![allow(improper_ctypes)] +#![deny(unfulfilled_lint_expectations, improper_ctypes_definitions)] extern crate minicore; use minicore::*; @@ -59,6 +59,7 @@ pub extern "C" fn f_fp_scalar_2(x: f64) -> f64 { pub struct Empty {} // CHECK: define void @f_agg_empty_struct() +#[expect(improper_ctypes_definitions)] #[no_mangle] pub extern "C" fn f_agg_empty_struct(e: Empty) -> Empty { e diff --git a/tests/codegen-llvm/scalable-vectors/memcpy.rs b/tests/codegen-llvm/scalable-vectors/memcpy.rs index 859f50b5b1178..30d4b0d17ebe1 100644 --- a/tests/codegen-llvm/scalable-vectors/memcpy.rs +++ b/tests/codegen-llvm/scalable-vectors/memcpy.rs @@ -5,12 +5,13 @@ #![crate_type = "lib"] #![feature(simd_ffi)] #![feature(stdarch_aarch64_sve)] +#![deny(unfulfilled_lint_expectations)] // Test that `vscale * size` is generated for `memcpy` of scalable vector types use std::arch::aarch64::*; -#[allow(improper_ctypes)] +#[expect(improper_ctypes)] unsafe extern "C" { fn svcreate2_s16_wrapper(__dst: *mut svint16x2_t, x0: *const svint16_t, x1: *const svint16_t); fn svcreate3_s16_wrapper( diff --git a/tests/debuginfo/basic-types-globals-metadata.rs b/tests/debuginfo/basic-types-globals-metadata.rs index 3f1d9fd5de278..f7f3504ae26fa 100644 --- a/tests/debuginfo/basic-types-globals-metadata.rs +++ b/tests/debuginfo/basic-types-globals-metadata.rs @@ -33,11 +33,12 @@ //@ gdb-check:type = f32 //@ gdb-command:whatis basic_types_globals_metadata::F64 //@ gdb-check:type = f64 +// FIXME(f128): gdb doesn't support Rust `f128` yet. //@ gdb-command:continue #![allow(unused_variables)] #![allow(dead_code)] -#![feature(f16)] +#![feature(f16, f128)] // N.B. These are `mut` only so they don't constant fold away. static mut B: bool = false; @@ -55,13 +56,14 @@ static mut U64: u64 = 64; static mut F16: f16 = 1.5; static mut F32: f32 = 2.5; static mut F64: f64 = 3.5; +static mut F128: f128 = 4.5; fn main() { _zzz(); // #break - let a = unsafe { (B, I, C, I8, I16, I32, I64, U, U8, U16, U32, U64, F32, F64) }; - // FIXME: Including f16 and f32 in the same tuple emits `__gnu_h2f_ieee`, which - // does not exist on some targets like PowerPC. + let a = unsafe { (B, I, C, I8, I16, I32, I64, U, U8, U16, U32, U64, F32, F64, F128) }; + // FIXME(f16): Including f16 and f32 in the same tuple emits `__gnu_h2f_ieee`, which + // does not exist on some targets like PowerPC (fixed in llvm22). // See https://github.com/llvm/llvm-project/issues/97981 and // https://github.com/rust-lang/compiler-builtins/issues/655 let b = unsafe { F16 }; diff --git a/tests/debuginfo/basic-types-globals.rs b/tests/debuginfo/basic-types-globals.rs index 044b757aaf470..3bc9d3becdade 100644 --- a/tests/debuginfo/basic-types-globals.rs +++ b/tests/debuginfo/basic-types-globals.rs @@ -1,11 +1,20 @@ -//@ revisions: lto no-lto +//@ revisions: lto no-lto lto-apple no-lto-apple //@ compile-flags:-g --crate-name=basic_types_globals //@ disable-gdb-pretty-printers +// FIXME(f128): Merge `-apple` revisions once Apple releases Xcode with LLVM 22. +//@ [lto] ignore-apple +//@ [no-lto] ignore-apple +//@ [lto-apple] only-apple +//@ [no-lto-apple] only-apple //@ [lto] compile-flags:-C lto //@ [lto] no-prefer-dynamic +//@ [lto-apple] compile-flags:-C lto +//@ [lto-apple] no-prefer-dynamic //@ ignore-backends: gcc +// `f128` support was added to `lldb` in version 22. +//@ min-llvm-lldb-version: 22 //@ lldb-command:run //@ lldb-command:v basic_types_globals::B @@ -38,6 +47,9 @@ //@ lldb-check:[...]basic_types_globals::F32 = 2.5 //@ lldb-command:v basic_types_globals::F64 //@ lldb-check:[...]basic_types_globals::F64 = 3.5 +//@ lldb-command:v basic_types_globals::F128 +//@[no-lto] lldb-check:[...]basic_types_globals::F128 = 4.5 +//@[lto] lldb-check:[...]basic_types_globals::F128 = 4.5 //@ gdb-command:run //@ gdb-command:print B @@ -70,10 +82,11 @@ //@ gdb-check:$14 = 2.5 //@ gdb-command:print F64 //@ gdb-check:$15 = 3.5 +// FIXME(f128): gdb doesn't support Rust `f128` yet. //@ gdb-command:continue #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] // N.B. These are `mut` only so they don't constant fold away. static mut B: bool = false; @@ -91,13 +104,14 @@ static mut U64: u64 = 64; static mut F16: f16 = 1.5; static mut F32: f32 = 2.5; static mut F64: f64 = 3.5; +static mut F128: f128 = 4.5; fn main() { _zzz(); // #break - let a = unsafe { (B, I, C, I8, I16, I32, I64, U, U8, U16, U32, U64, F32, F64) }; - // FIXME: Including f16 and f32 in the same tuple emits `__gnu_h2f_ieee`, which - // does not exist on some targets like PowerPC. + let a = unsafe { (B, I, C, I8, I16, I32, I64, U, U8, U16, U32, U64, F32, F64, F128) }; + // FIXME(f16): Including f16 and f32 in the same tuple emits `__gnu_h2f_ieee`, which + // does not exist on some targets like PowerPC (fixed in llvm22). // See https://github.com/llvm/llvm-project/issues/97981 and // https://github.com/rust-lang/compiler-builtins/issues/655 let b = unsafe { F16 }; diff --git a/tests/debuginfo/basic-types-metadata.rs b/tests/debuginfo/basic-types-metadata.rs index d3a3d03ef7424..7171840077265 100644 --- a/tests/debuginfo/basic-types-metadata.rs +++ b/tests/debuginfo/basic-types-metadata.rs @@ -35,6 +35,7 @@ //@ gdb-check:type = f32 //@ gdb-command:whatis f64 //@ gdb-check:type = f64 +// FIXME(f128): gdb doesn't support Rust `f128` yet. //@ gdb-command:whatis fnptr //@ gdb-check:type = *mut fn () //@ gdb-command:info functions _yyy @@ -54,7 +55,7 @@ //@ gdb-command:continue #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] fn main() { let unit: () = (); @@ -73,6 +74,7 @@ fn main() { let f16: f16 = 1.5; let f32: f32 = 2.5; let f64: f64 = 3.5; + let f128: f128 = 4.5; let fnptr : fn() = _zzz; let closure_0 = || {}; let closure_1 = || { b; }; diff --git a/tests/debuginfo/basic-types-mut-globals.rs b/tests/debuginfo/basic-types-mut-globals.rs index c3cc7be549d47..3f59da2a5d2e0 100644 --- a/tests/debuginfo/basic-types-mut-globals.rs +++ b/tests/debuginfo/basic-types-mut-globals.rs @@ -1,13 +1,14 @@ -// Caveats - gdb prints any 8-bit value (meaning rust I8 and u8 values) -// as its numerical value along with its associated ASCII char, there -// doesn't seem to be any way around this. Also, gdb doesn't know -// about UTF-32 character encoding and will print a rust char as only -// its numerical value. - -//@ compile-flags:-g +//@ compile-flags:-g --crate-name=basic_types_mut_globals //@ disable-gdb-pretty-printers //@ ignore-backends: gcc +// FIXME(f128): Merge `apple` revision once Apple releases Xcode with LLVM 22. +//@ revisions: not-apple apple +//@[not-apple] ignore-apple +//@[apple] only-apple +// `f128` support was added to `lldb` in version 22. +//@ min-llvm-lldb-version: 22 + //@ gdb-command:run // Check initializers @@ -41,6 +42,7 @@ //@ gdb-check:$14 = 2.5 //@ gdb-command:print F64 //@ gdb-check:$15 = 3.5 +// FIXME(f128): gdb doesn't support Rust `f128` yet. //@ gdb-command:continue // Check new values @@ -50,7 +52,7 @@ //@ gdb-check:$17 = 2 //@ gdb-command:print C //@ gdb-check:$18 = 102 'f' -//@ gdb-command:print/d I8 +//@ gdb-command:print I8 //@ gdb-check:$19 = 78 //@ gdb-command:print I16 //@ gdb-check:$20 = -26 @@ -60,7 +62,7 @@ //@ gdb-check:$22 = -54 //@ gdb-command:print U //@ gdb-check:$23 = 5 -//@ gdb-command:print/d U8 +//@ gdb-command:print U8 //@ gdb-check:$24 = 20 //@ gdb-command:print U16 //@ gdb-check:$25 = 32 @@ -74,9 +76,81 @@ //@ gdb-check:$29 = 5.75 //@ gdb-command:print F64 //@ gdb-check:$30 = 9.25 +// FIXME(f128): gdb doesn't support Rust `f128` yet. + +//@ lldb-command:run + +// Check initializers +//@ lldb-command:v basic_types_mut_globals::B +//@ lldb-check:[...]basic_types_mut_globals::B = false +//@ lldb-command:v basic_types_mut_globals::I +//@ lldb-check:[...]basic_types_mut_globals::I = -1 +//@ lldb-command:v basic_types_mut_globals::C +//@ lldb-check:[...]basic_types_mut_globals::C = U+0x00000061 U'a' +//@ lldb-command:v/d basic_types_mut_globals::I8 +//@ lldb-check:[...]basic_types_mut_globals::I8 = 68 +//@ lldb-command:v basic_types_mut_globals::I16 +//@ lldb-check:[...]basic_types_mut_globals::I16 = -16 +//@ lldb-command:v basic_types_mut_globals::I32 +//@ lldb-check:[...]basic_types_mut_globals::I32 = -32 +//@ lldb-command:v basic_types_mut_globals::I64 +//@ lldb-check:[...]basic_types_mut_globals::I64 = -64 +//@ lldb-command:v basic_types_mut_globals::U +//@ lldb-check:[...]basic_types_mut_globals::U = 1 +//@ lldb-command:v/d basic_types_mut_globals::U8 +//@ lldb-check:[...]basic_types_mut_globals::U8 = 100 +//@ lldb-command:v basic_types_mut_globals::U16 +//@ lldb-check:[...]basic_types_mut_globals::U16 = 16 +//@ lldb-command:v basic_types_mut_globals::U32 +//@ lldb-check:[...]basic_types_mut_globals::U32 = 32 +//@ lldb-command:v basic_types_mut_globals::U64 +//@ lldb-check:[...]basic_types_mut_globals::U64 = 64 +//@ lldb-command:v basic_types_mut_globals::F16 +//@ lldb-check:[...]basic_types_mut_globals::F16 = 1.5 +//@ lldb-command:v basic_types_mut_globals::F32 +//@ lldb-check:[...]basic_types_mut_globals::F32 = 2.5 +//@ lldb-command:v basic_types_mut_globals::F64 +//@ lldb-check:[...]basic_types_mut_globals::F64 = 3.5 +//@ lldb-command:v basic_types_mut_globals::F128 +//@[not-apple] lldb-check:[...]basic_types_mut_globals::F128 = 4.5 +//@ lldb-command:continue + +// Check new values +//@ lldb-command:v basic_types_mut_globals::B +//@ lldb-check:[...]basic_types_mut_globals::B = true +//@ lldb-command:v basic_types_mut_globals::I +//@ lldb-check:[...]basic_types_mut_globals::I = 2 +//@ lldb-command:v basic_types_mut_globals::C +//@ lldb-check:[...]basic_types_mut_globals::C = U+0x00000066 U'f' +//@ lldb-command:v/d basic_types_mut_globals::I8 +//@ lldb-check:[...]basic_types_mut_globals::I8 = 78 +//@ lldb-command:v basic_types_mut_globals::I16 +//@ lldb-check:[...]basic_types_mut_globals::I16 = -26 +//@ lldb-command:v basic_types_mut_globals::I32 +//@ lldb-check:[...]basic_types_mut_globals::I32 = -12 +//@ lldb-command:v basic_types_mut_globals::I64 +//@ lldb-check:[...]basic_types_mut_globals::I64 = -54 +//@ lldb-command:v basic_types_mut_globals::U +//@ lldb-check:[...]basic_types_mut_globals::U = 5 +//@ lldb-command:v/d basic_types_mut_globals::U8 +//@ lldb-check:[...]basic_types_mut_globals::U8 = 20 +//@ lldb-command:v basic_types_mut_globals::U16 +//@ lldb-check:[...]basic_types_mut_globals::U16 = 32 +//@ lldb-command:v basic_types_mut_globals::U32 +//@ lldb-check:[...]basic_types_mut_globals::U32 = 16 +//@ lldb-command:v basic_types_mut_globals::U64 +//@ lldb-check:[...]basic_types_mut_globals::U64 = 128 +//@ lldb-command:v basic_types_mut_globals::F16 +//@ lldb-check:[...]basic_types_mut_globals::F16 = 2.25 +//@ lldb-command:v basic_types_mut_globals::F32 +//@ lldb-check:[...]basic_types_mut_globals::F32 = 5.75 +//@ lldb-command:v basic_types_mut_globals::F64 +//@ lldb-check:[...]basic_types_mut_globals::F64 = 9.25 +//@ lldb-command:v basic_types_mut_globals::F128 +//@[not-apple] lldb-check:[...]basic_types_mut_globals::F128 = 12.75 #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] static mut B: bool = false; static mut I: isize = -1; @@ -93,6 +167,7 @@ static mut U64: u64 = 64; static mut F16: f16 = 1.5; static mut F32: f32 = 2.5; static mut F64: f64 = 3.5; +static mut F128: f128 = 4.5; fn main() { _zzz(); // #break @@ -113,6 +188,7 @@ fn main() { F16 = 2.25; F32 = 5.75; F64 = 9.25; + F128 = 12.75; } _zzz(); // #break diff --git a/tests/debuginfo/basic-types/main.rs b/tests/debuginfo/basic-types/main.rs index 9f61862c0dfd8..d01e51036f201 100644 --- a/tests/debuginfo/basic-types/main.rs +++ b/tests/debuginfo/basic-types/main.rs @@ -1,9 +1,3 @@ -// Caveats - gdb prints any 8-bit value (meaning rust i8 and u8 values) -// as its numerical value along with its associated ASCII char, there -// doesn't seem to be any way around this. Also, gdb doesn't know -// about UTF-32 character encoding and will print a rust char as only -// its numerical value. - //@ compile-flags:-g //@ disable-gdb-pretty-printers //@ ignore-backends: gcc @@ -32,6 +26,7 @@ //@ gdb-repr:f16 //@ gdb-repr:f32 //@ gdb-repr:f64 +// FIXME(f128): gdb doesn't support Rust `f128` yet. //@ gdb-repr:s // === LLDB TESTS ================================================================================== @@ -85,13 +80,16 @@ //@ cdb-check:f32 : 2.500000 [Type: float] //@ cdb-command:dx f64 //@ cdb-check:f64 : 3.500000 [Type: double] +//@ cdb-command:dx f128 +//@ cdb-check:f128 : 0x1.2p+2 [Type: f128] +//@ cdb-check:bits : 0x40012000000000000000000000000000 //@ cdb-command:.enable_unicode 1 // FIXME(#88840): The latest version of the Windows SDK broke the visualizer for str. //@ cdb-command:dx s //@ cdb-check:s : [...] [Type: ref$] #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] fn main() { let b: bool = false; @@ -109,6 +107,7 @@ fn main() { let f16: f16 = 1.5; let f32: f32 = 2.5; let f64: f64 = 3.5; + let f128: f128 = 4.5; let s: &str = "Hello, World!"; _zzz(); // #break } diff --git a/tests/debuginfo/borrowed-basic.rs b/tests/debuginfo/borrowed-basic.rs index f7b7d2cbd810c..2872bc65fac3f 100644 --- a/tests/debuginfo/borrowed-basic.rs +++ b/tests/debuginfo/borrowed-basic.rs @@ -2,6 +2,13 @@ //@ disable-gdb-pretty-printers //@ ignore-backends: gcc +// FIXME(f128): Merge `apple` revision once Apple releases Xcode with LLVM 22. +//@ revisions: not-apple apple +//@[not-apple] ignore-apple +//@[apple] only-apple +// `f128` support was added to `lldb` in version 22. +//@ min-llvm-lldb-version: 22 + // === GDB TESTS =================================================================================== //@ gdb-command:run @@ -50,6 +57,8 @@ //@ gdb-command:print *f64_ref //@ gdb-check:$15 = 3.5 +// FIXME(f128): gdb doesn't support Rust `f128` yet. + // === LLDB TESTS ================================================================================== @@ -99,8 +108,11 @@ //@ lldb-command:v *f64_ref //@ lldb-check:[...] 3.5 +//@ lldb-command:v *f128_ref +//@[not-apple] lldb-check:[...] 4.5 + #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] fn main() { let bool_val: bool = true; @@ -148,6 +160,9 @@ fn main() { let f64_val: f64 = 3.5; let f64_ref: &f64 = &f64_val; + let f128_val: f128 = 4.5; + let f128_ref: &f128 = &f128_val; + zzz(); // #break } diff --git a/tests/debuginfo/borrowed-unique-basic.rs b/tests/debuginfo/borrowed-unique-basic.rs index 17939239c0dea..0d1fdec0f58d1 100644 --- a/tests/debuginfo/borrowed-unique-basic.rs +++ b/tests/debuginfo/borrowed-unique-basic.rs @@ -2,6 +2,13 @@ //@ disable-gdb-pretty-printers //@ ignore-backends: gcc +// FIXME(f128): Merge `apple` revision once Apple releases Xcode with LLVM 22. +//@ revisions: not-apple apple +//@[not-apple] ignore-apple +//@[apple] only-apple +// `f128` support was added to `lldb` in version 22. +//@ min-llvm-lldb-version: 22 + // === GDB TESTS =================================================================================== //@ gdb-command:run @@ -51,6 +58,8 @@ //@ gdb-command:print *f64_ref //@ gdb-check:$15 = 3.5 +// FIXME(f128): gdb doesn't support Rust `f128` yet. + // === LLDB TESTS ================================================================================== @@ -102,8 +111,11 @@ //@ lldb-command:v *f64_ref //@ lldb-check:[...] 3.5 +//@ lldb-command:v *f128_ref +//@[not-apple] lldb-check:[...] 4.5 + #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] fn main() { let bool_box: Box = Box::new(true); @@ -151,6 +163,9 @@ fn main() { let f64_box: Box = Box::new(3.5); let f64_ref: &f64 = &*f64_box; + let f128_box: Box = Box::new(4.5); + let f128_ref: &f128 = &*f128_box; + zzz(); // #break } diff --git a/tests/debuginfo/f128-natvis.rs b/tests/debuginfo/f128-natvis.rs new file mode 100644 index 0000000000000..5f91014cfe5db --- /dev/null +++ b/tests/debuginfo/f128-natvis.rs @@ -0,0 +1,92 @@ +//@ compile-flags: -g +//@ only-msvc + +// This tests the `f128` Natvis visualiser. +//@ cdb-command:g +//@ cdb-command:dx v0_0 +//@ cdb-check:v0_0 : 0x0p+0 [Type: f128] +//@ cdb-check:bits : 0x00000000000000000000000000000000 +//@ cdb-command:dx neg_0_0 +//@ cdb-check:neg_0_0 : -0x0p+0 [Type: f128] +//@ cdb-check:bits : 0x80000000000000000000000000000000 +//@ cdb-command:dx v1_0 +//@ cdb-check:v1_0 : 0x1p+0 [Type: f128] +//@ cdb-check:bits : 0x3fff0000000000000000000000000000 +//@ cdb-command:dx v1_5 +//@ cdb-check:v1_5 : 0x1.8p+0 [Type: f128] +//@ cdb-check:bits : 0x3fff8000000000000000000000000000 +//@ cdb-command:dx v72_3 +//@ cdb-check:v72_3 : 0x1.2133333333333333333333333333p+6 [Type: f128] +//@ cdb-check:bits : 0x40052133333333333333333333333333 +//@ cdb-command:dx neg_0_126 +//@ cdb-check:neg_0_126 : -0x1.020c49ba5e353f7ced916872b021p-3 [Type: f128] +//@ cdb-check:bits : 0xbffc020c49ba5e353f7ced916872b021 +//@ cdb-command:dx v0_00003 +//@ cdb-check:v0_00003 : 0x1.f75104d551d68c692f6e82949a56p-16 [Type: f128] +//@ cdb-check:bits : 0x3feff75104d551d68c692f6e82949a56 +//@ cdb-command:dx neg_0_00004 +//@ cdb-check:neg_0_00004 : -0x1.4f8b588e368f08461f9f01b866e4p-15 [Type: f128] +//@ cdb-check:bits : 0xbff04f8b588e368f08461f9f01b866e4 +//@ cdb-command:dx very_small +//@ cdb-check:very_small : 0x1p-16494 [Type: f128] +//@ cdb-check:bits : 0x00000000000000000000000000000001 +//@ cdb-command:dx not_quite_as_small +//@ cdb-check:not_quite_as_small : 0x1.8p-16385 [Type: f128] +//@ cdb-check:bits : 0x00003000000000000000000000000000 +//@ cdb-command:dx smallest_pos_normal +//@ cdb-check:smallest_pos_normal : 0x1p-16382 [Type: f128] +//@ cdb-check:bits : 0x00010000000000000000000000000000 +//@ cdb-command:dx smallest_subnormal +//@ cdb-check:smallest_subnormal : -0x1.fffffffffffffffffffffffffffep-16383 [Type: f128] +//@ cdb-check:bits : 0x8000ffffffffffffffffffffffffffff +//@ cdb-command:dx just_above +//@ cdb-check:just_above : -0x1.ffffffffffffffffffffffffff8p-1 [Type: f128] +//@ cdb-check:bits : 0xbffeffffffffffffffffffffffffff80 +//@ cdb-command:dx max +//@ cdb-check:max : 0x1.ffffffffffffffffffffffffffffp+16383 [Type: f128] +//@ cdb-check:bits : 0x7ffeffffffffffffffffffffffffffff +//@ cdb-command:dx min +//@ cdb-check:min : -0x1.ffffffffffffffffffffffffffffp+16383 [Type: f128] +//@ cdb-check:bits : 0xfffeffffffffffffffffffffffffffff +//@ cdb-command:dx inf +//@ cdb-check:inf : inf [Type: f128] +//@ cdb-check:bits : 0x7fff0000000000000000000000000000 +//@ cdb-command:dx neg_inf +//@ cdb-check:neg_inf : -inf [Type: f128] +//@ cdb-check:bits : 0xffff0000000000000000000000000000 +//@ cdb-command:dx nan +//@ cdb-check:nan : NaN [Type: f128] +//@ cdb-check:bits : 0x7fff8000000000000000000000000000 +//@ cdb-command:dx other_nan +//@ cdb-check:other_nan : NaN [Type: f128] +//@ cdb-check:bits : 0xffff123456789abcdef123456789abcd + +#![feature(f128)] + +fn main() { + let v0_0 = 0.0_f128; + let neg_0_0 = -0.0_f128; + let v1_0 = 1.0_f128; + let v1_5 = 1.5_f128; + let v72_3 = 72.3_f128; + let neg_0_126 = -0.126_f128; + let v0_00003 = 0.00003_f128; + let neg_0_00004 = -0.00004_f128; + let very_small = 0.0_f128.next_up(); + let not_quite_as_small = const { f128::MIN_POSITIVE / 8.0 + f128::MIN_POSITIVE / 16.0 }; + let smallest_pos_normal = f128::MIN_POSITIVE; + let smallest_subnormal = (-f128::MIN_POSITIVE).next_up(); + let just_above = const { -1.0 + f128::EPSILON * 64.0 }; + let max = f128::MAX; + let min = f128::MIN; + let inf = f128::INFINITY; + let neg_inf = f128::NEG_INFINITY; + let nan = f128::NAN; + let other_nan = f128::from_bits(0xffff_1234_5678_9abc_def1_2345_6789_abcd); + + _zzz(); // #break +} + +fn _zzz() { + () +} diff --git a/tests/debuginfo/reference-debuginfo.rs b/tests/debuginfo/reference-debuginfo.rs index 518e1dac2885e..495dde379da7e 100644 --- a/tests/debuginfo/reference-debuginfo.rs +++ b/tests/debuginfo/reference-debuginfo.rs @@ -2,10 +2,18 @@ // That pass replaces debuginfo for `a => _x` where `_x = &b` to be `a => &b`, // and leaves codegen to create a ladder of allocations so as `*a == b`. // +// FIXME: Currently emits warning: MIR pass `ConstDebugInfo` is unknown and will be ignored //@ compile-flags:-g -Zmir-enable-passes=+ReferencePropagation,-ConstDebugInfo //@ disable-gdb-pretty-printers //@ ignore-backends: gcc +// FIXME(f128): Merge `apple` revision once Apple releases Xcode with LLVM 22. +//@ revisions: not-apple apple +//@[not-apple] ignore-apple +//@[apple] only-apple +// `f128` support was added to `lldb` in version 22. +//@ min-llvm-lldb-version: 22 + // === GDB TESTS =================================================================================== //@ gdb-command:run @@ -54,6 +62,8 @@ //@ gdb-command:print *f64_ref //@ gdb-check:$15 = 3.5 +// FIXME(f128): gdb doesn't support Rust `f128` yet. + //@ gdb-command:print *f64_double_ref //@ gdb-check:$16 = 3.5 @@ -106,11 +116,14 @@ //@ lldb-command:v *f64_ref //@ lldb-check:[...] 3.5 +//@ lldb-command:v *f128_ref +//@[not-apple] lldb-check:[...] 4.5 + //@ lldb-command:v *f64_double_ref //@ lldb-check:[...] 3.5 #![allow(unused_variables)] -#![feature(f16)] +#![feature(f16, f128)] fn main() { let bool_val: bool = true; @@ -159,6 +172,9 @@ fn main() { let f64_ref: &f64 = &f64_val; let f64_double_ref: &f64 = &f64_ref; + let f128_val: f128 = 4.5; + let f128_ref: &f128 = &f128_val; + zzz(); // #break } diff --git a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs index 37110e75f4779..3f9698afe2d6c 100644 --- a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs +++ b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs @@ -1,5 +1,5 @@ #![crate_type = "staticlib"] -#![feature(c_variadic_int128, c_variadic_experimental_arch)] +#![feature(c_variadic_int128, c_variadic_experimental_arch, f128)] use core::ffi::{CStr, VaList, c_char, c_double, c_int, c_long, c_longlong}; @@ -100,6 +100,59 @@ pub unsafe extern "C" fn check_list_i128(mut ap: VaList) -> usize { } } +cfg_select! { + any( + all(target_arch = "x86_64", not(target_vendor = "apple"), not(target_env = "msvc")), + all(target_arch = "x86", not(target_vendor = "apple"), not(target_env = "msvc")), + all(target_arch = "powerpc64", target_feature = "vsx"), + all( + not(windows), + not(target_vendor = "apple"), + any( + target_arch = "aarch64", + target_arch = "loongarch32", + target_arch = "loongarch64", + target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "wasm32", + target_arch = "wasm64", + ), + ), + ) => { + #[unsafe(no_mangle)] + pub static RUST_HAS_F128: c_int = 1; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn check_list_f128(mut ap: VaList) -> usize { + continue_if!(ap.next_arg::() == -42.0); + // use a 32-bit value here to test the alignment logic. + continue_if!(ap.next_arg::() == 0xAAAA_AAAAu32.cast_signed()); + continue_if!(ap.next_arg::() == f128::MAX); + + return 0; + } + } + _ => { + #[unsafe(no_mangle)] + pub static RUST_HAS_F128: c_int = 0; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn check_list_f128(_: VaList) -> usize { + // This function was called a platform where rustc does not implement + // VaArgSafe for f128 but clang does define _Float128. + // + // This occurs on powerpc64 where f128 support depends on a target feature. + // + // Otherwise, rustc should add the implementation if this comes up. + 0xFF + } + } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn check_varargs_0(_: c_int, mut ap: ...) -> usize { continue_if!(ap.next_arg::() == 42); diff --git a/tests/run-make/c-link-to-rust-va-list-fn/test.c b/tests/run-make/c-link-to-rust-va-list-fn/test.c index c7510a29445a5..14c507cd6cccd 100644 --- a/tests/run-make/c-link-to-rust-va-list-fn/test.c +++ b/tests/run-make/c-link-to-rust-va-list-fn/test.c @@ -3,12 +3,14 @@ #include #include #include +#include extern size_t check_list_0(va_list ap); extern size_t check_list_1(va_list ap); extern size_t check_list_2(va_list ap); extern size_t check_list_copy_0(va_list ap); extern size_t check_list_i128(va_list ap); +extern size_t check_list_f128(va_list ap); extern size_t check_varargs_0(int fixed, ...); extern size_t check_varargs_1(int fixed, ...); extern size_t check_varargs_2(int fixed, ...); @@ -21,6 +23,9 @@ extern size_t run_test_va_list_by_value(); extern size_t run_test_va_list_by_pointer(); extern size_t run_test_va_list_by_pointer_pointer(); +// Was the rust side compiled with f128 support? +extern const int RUST_HAS_F128; + int test_rust(size_t (*fn)(va_list), ...) { size_t ret = 0; va_list ap; @@ -40,9 +45,43 @@ int main(int argc, char* argv[]) { assert(test_rust(check_list_copy_0, 6.28, 16, 'A', "Skip Me!", "Correct") == 0); #if defined(__SIZEOF_INT128__) + assert(test_rust(check_list_i128, (__int128)-42, 0xAAAAAAAA, (unsigned __int128)-1) == 0); #endif + // Run the f128 test when __float128/_Float128 is defined or long double is IEEE f128. + // Use #define instead of typedef so that `#ifdef` can detect it. +#if defined(__LDBL_MANT_DIG__) && __LDBL_MANT_DIG__ == 113 +#define f128 long double +#elif defined(__SIZEOF_FLOAT128__) +#ifdef __clang__ +#define f128 __float128 +#else +#define f128 _Float128 +#endif +#endif + +#ifdef f128 + // construct f128::MAX. + union cvt128 { + struct { +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + uint64_t hi, lo; +#else + uint64_t lo, hi; +#endif + } i; + f128 f; + }; + union cvt128 f128_max; + f128_max.i.hi = 0x7ffeffffffffffff; + f128_max.i.lo = 0xffffffffffffffff; + + if (RUST_HAS_F128) { + assert(test_rust(check_list_f128, (f128)-42.0, 0xAAAAAAAA, f128_max.f) == 0); + } +#endif + assert(check_varargs_0(0, 42, "Hello, World!") == 0); assert(check_varargs_1(0, 3.14, 12l, 'A', 0x1LL) == 0); diff --git a/tests/run-make/comment-section/rmake.rs b/tests/run-make/comment-section/rmake.rs index ccfc38e870d88..22b0e2b3b3fb9 100644 --- a/tests/run-make/comment-section/rmake.rs +++ b/tests/run-make/comment-section/rmake.rs @@ -6,6 +6,8 @@ //@ only-linux // FIXME(jieyouxu): check cross-compile setup //@ ignore-cross-compile +// FIXME: remove the ignore gcc once fixed. +//@ ignore-backends: gcc use run_make_support::{cwd, env_var, llvm_readobj, rfs, rustc}; diff --git a/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs b/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs index 942b667814a91..f6a337513909d 100644 --- a/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs +++ b/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs @@ -1,4 +1,6 @@ //@ only-x86_64-unknown-linux-gnu +// FIXME: remove the ignore gcc once fixed. +//@ ignore-backends: gcc // Regression test for the incremental bug in . // diff --git a/tests/run-make/emit/rmake.rs b/tests/run-make/emit/rmake.rs index 8b3ddb66f9238..5cf13e9bd4879 100644 --- a/tests/run-make/emit/rmake.rs +++ b/tests/run-make/emit/rmake.rs @@ -4,6 +4,7 @@ // See https://github.com/rust-lang/rust/pull/30452 //@ ignore-cross-compile +//@ ignore-backends: gcc use run_make_support::{run, rustc}; diff --git a/tests/run-make/extra-filename-with-temp-outputs/rmake.rs b/tests/run-make/extra-filename-with-temp-outputs/rmake.rs index f93a3ecc8d1b5..2b59459006c23 100644 --- a/tests/run-make/extra-filename-with-temp-outputs/rmake.rs +++ b/tests/run-make/extra-filename-with-temp-outputs/rmake.rs @@ -7,6 +7,8 @@ // See https://github.com/rust-lang/rust/pull/15686 //@ ignore-cross-compile (relocations in generic ELF against `arm-unknown-linux-gnueabihf`) +// FIXME: remove the ignore gcc once fixed. +//@ ignore-backends: gcc use run_make_support::{bin_name, cwd, has_prefix, has_suffix, rfs, rustc, shallow_find_files}; diff --git a/tests/run-make/parallel-reproducible-build/rmake.rs b/tests/run-make/parallel-reproducible-build/rmake.rs index f35d15de07b85..caaee1b47c583 100644 --- a/tests/run-make/parallel-reproducible-build/rmake.rs +++ b/tests/run-make/parallel-reproducible-build/rmake.rs @@ -1,5 +1,7 @@ //@ needs-target-std //@ ignore-cross-compile +// FIXME: remove the ignore gcc once fixed. +//@ ignore-backends: gcc //@ ignore-windows-gnu // GNU Linker for Windows is non-deterministic. (from `reproducible-build-2` test in this suite) diff --git a/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs b/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs index 62ad5a46af860..5f4be53c6edf5 100644 --- a/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs +++ b/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs @@ -1,5 +1,7 @@ //@ needs-target-std //@ ignore-cross-compile +// FIXME: remove the ignore gcc once fixed. +//@ ignore-backends: gcc //@ ignore-windows-gnu // GNU Linker for Windows is non-deterministic. (from `reproducible-build-2` test in this suite) diff --git a/tests/rustdoc-html/intra-doc/inline-same-item-with-different-names.rs b/tests/rustdoc-html/intra-doc/inline-same-item-with-different-names.rs new file mode 100644 index 0000000000000..c7801da5c98c2 --- /dev/null +++ b/tests/rustdoc-html/intra-doc/inline-same-item-with-different-names.rs @@ -0,0 +1,43 @@ +// This test ensures that when a same item is inlined with different names, the intra +// doc links generate the correct href/title. +// Regression test for . + +#![crate_name = "foo"] + +// We check that the macros and structs are correctly generated. +//@ has 'foo/macro.d1.html' +//@ has 'foo/macro.d2.html' +//@ has 'foo/macro.d3.html' +//@ has 'foo/struct.a1.html' +//@ has 'foo/struct.a2.html' +//@ has 'foo/struct.a3.html' + +//@ has 'foo/index.html' + +//@ has - '//dd/a[@href="macro.d1.html"]' 'd1' +//@ has - '//dd/a[@title="macro foo::d1"]' 'd1' +//@ has - '//dd/a[@href="macro.d2.html"]' 'd2' +//@ has - '//dd/a[@title="macro foo::d2"]' 'd2' +//@ has - '//dd/a[@href="macro.d3.html"]' 'd3' +//@ has - '//dd/a[@title="macro foo::d3"]' 'd3' + +/// Link to [`d3`]. +pub use std::debug_assert as d1; +/// Link to [`d1`]. +pub use std::debug_assert as d2; +/// Link to [`d2`]. +pub use std::debug_assert as d3; + +//@ has - '//dd/a[@href="struct.a1.html"]' 'a1' +//@ has - '//dd/a[@title="struct foo::a1"]' 'a1' +//@ has - '//dd/a[@href="struct.a2.html"]' 'a2' +//@ has - '//dd/a[@title="struct foo::a2"]' 'a2' +//@ has - '//dd/a[@href="struct.a3.html"]' 'a3' +//@ has - '//dd/a[@title="struct foo::a3"]' 'a3' + +/// Link to [`a3`]. +pub use std::ffi::os_str::OsString as a1; +/// Link to [`a1`]. +pub use std::ffi::os_str::OsString as a2; +/// Link to [`a2`]. +pub use std::ffi::os_str::OsString as a3; diff --git a/tests/ui/abi/abi-sysv64-arg-passing.rs b/tests/ui/abi/abi-sysv64-arg-passing.rs index 362a1862f9d4c..e21ca94aa6e8f 100644 --- a/tests/ui/abi/abi-sysv64-arg-passing.rs +++ b/tests/ui/abi/abi-sysv64-arg-passing.rs @@ -34,7 +34,6 @@ // the sysv64 ABI on Windows. #[allow(dead_code)] -#[allow(improper_ctypes)] #[cfg(target_arch = "x86_64")] mod tests { @@ -87,6 +86,7 @@ mod tests { #[derive(Copy, Clone)] pub struct Quad { a: u64, b: u64, c: u64, d: u64 } + #[repr(C)] #[derive(Copy, Clone)] pub struct QuadFloats { a: f32, b: f32, c: f32, d: f32 } @@ -113,6 +113,7 @@ mod tests { pub fn rust_dbg_extern_identity_u32(v: u32) -> u32; pub fn rust_dbg_extern_identity_u64(v: u64) -> u64; pub fn rust_dbg_extern_identity_double(v: f64) -> f64; + #[expect(improper_ctypes)] pub fn rust_dbg_extern_empty_struct(v1: ManyInts, e: Empty, v2: ManyInts); pub fn rust_dbg_extern_identity_TwoU8s(v: TwoU8s) -> TwoU8s; pub fn rust_dbg_extern_identity_TwoU16s(v: TwoU16s) -> TwoU16s; diff --git a/tests/ui/abi/abi-sysv64-register-usage.rs b/tests/ui/abi/abi-sysv64-register-usage.rs index cf1620db6f7be..576af8e696438 100644 --- a/tests/ui/abi/abi-sysv64-register-usage.rs +++ b/tests/ui/abi/abi-sysv64-register-usage.rs @@ -45,11 +45,11 @@ pub extern "sysv64" fn all_the_registers( // this struct contains 8 i64's, while only 6 can be passed in registers. #[cfg(target_arch = "x86_64")] #[derive(PartialEq, Eq, Debug)] +#[repr(C)] pub struct LargeStruct(i64, i64, i64, i64, i64, i64, i64, i64); #[cfg(target_arch = "x86_64")] #[inline(never)] -#[allow(improper_ctypes_definitions)] pub extern "sysv64" fn large_struct_by_val(mut foo: LargeStruct) -> LargeStruct { foo.0 *= 1; foo.1 *= 2; diff --git a/tests/ui/abi/arm-unadjusted-intrinsic.rs b/tests/ui/abi/arm-unadjusted-intrinsic.rs index f20fda4c61534..4340369d5ecc7 100644 --- a/tests/ui/abi/arm-unadjusted-intrinsic.rs +++ b/tests/ui/abi/arm-unadjusted-intrinsic.rs @@ -28,7 +28,7 @@ impl Copy for int8x16x4_t {} #[target_feature(enable = "neon")] #[cfg_attr(target_arch = "arm", target_feature(enable = "v7"))] pub unsafe fn vld1q_s8_x4(a: *const i8) -> int8x16x4_t { - #[allow(improper_ctypes)] + #[expect(improper_ctypes)] extern "llvm-intrinsic" { #[cfg_attr(target_arch = "arm", link_name = "llvm.arm.neon.vld1x4.v16i8.p0i8")] #[cfg_attr(target_arch = "aarch64", link_name = "llvm.aarch64.neon.ld1x4.v16i8.p0i8")] diff --git a/tests/ui/abi/compatibility.rs b/tests/ui/abi/compatibility.rs index e2496726f4b3f..c0a124208b21e 100644 --- a/tests/ui/abi/compatibility.rs +++ b/tests/ui/abi/compatibility.rs @@ -89,7 +89,7 @@ #![feature(no_core, rustc_attrs, lang_items)] #![feature(unsized_fn_params, transparent_unions)] #![no_core] -#![allow(unused, improper_ctypes_definitions, internal_features)] +#![expect(unused, improper_ctypes_definitions, internal_features)] // FIXME: some targets are broken in various ways. // Hence there are `cfg` throughout this test to disable parts of it on those targets. diff --git a/tests/ui/abi/extern-c-two-doubles-x86_64-5754.rs b/tests/ui/abi/extern-c-two-doubles-x86_64-5754.rs index 7f44ef9c68535..5b278f2506a24 100644 --- a/tests/ui/abi/extern-c-two-doubles-x86_64-5754.rs +++ b/tests/ui/abi/extern-c-two-doubles-x86_64-5754.rs @@ -1,8 +1,8 @@ // https://github.com/rust-lang/rust/issues/5754 //@ build-pass #![allow(dead_code)] -#![allow(improper_ctypes)] +#[repr(C)] struct TwoDoubles { r: f64, i: f64 diff --git a/tests/ui/abi/extern/extern-c-method-return-struct.rs b/tests/ui/abi/extern/extern-c-method-return-struct.rs index 679f0b37758ab..08cfe495ba2a3 100644 --- a/tests/ui/abi/extern/extern-c-method-return-struct.rs +++ b/tests/ui/abi/extern/extern-c-method-return-struct.rs @@ -2,13 +2,14 @@ //@ build-pass #![allow(dead_code)] + +#[repr(C)] pub struct Foo { x: isize, y: isize } impl Foo { - #[allow(improper_ctypes_definitions)] pub extern "C" fn foo_new() -> Foo { Foo { x: 21, y: 33 } } diff --git a/tests/ui/abi/extern/extern-pass-FiveU16s.rs b/tests/ui/abi/extern/extern-pass-FiveU16s.rs index 5f1307beb28e6..48edb8d4a5a92 100644 --- a/tests/ui/abi/extern/extern-pass-FiveU16s.rs +++ b/tests/ui/abi/extern/extern-pass-FiveU16s.rs @@ -1,5 +1,4 @@ //@ run-pass -#![allow(improper_ctypes)] // Test a foreign function that accepts and returns a struct by value. @@ -8,6 +7,7 @@ // sizes, causing there to be padding in the last element. #[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] pub struct FiveU16s { one: u16, two: u16, diff --git a/tests/ui/abi/extern/extern-pass-TwoU16s.rs b/tests/ui/abi/extern/extern-pass-TwoU16s.rs index 8bde553050a40..47dd0674ae439 100644 --- a/tests/ui/abi/extern/extern-pass-TwoU16s.rs +++ b/tests/ui/abi/extern/extern-pass-TwoU16s.rs @@ -1,10 +1,10 @@ //@ run-pass -#![allow(improper_ctypes)] // Test a foreign function that accepts and returns a struct // by value. #[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] pub struct TwoU16s { one: u16, two: u16, diff --git a/tests/ui/abi/extern/extern-pass-TwoU32s.rs b/tests/ui/abi/extern/extern-pass-TwoU32s.rs index fc90eb6945c7e..f7d9459e8f82f 100644 --- a/tests/ui/abi/extern/extern-pass-TwoU32s.rs +++ b/tests/ui/abi/extern/extern-pass-TwoU32s.rs @@ -1,10 +1,10 @@ //@ run-pass -#![allow(improper_ctypes)] // Test a foreign function that accepts and returns a struct // by value. #[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] pub struct TwoU32s { one: u32, two: u32, diff --git a/tests/ui/abi/extern/extern-pass-TwoU64s.rs b/tests/ui/abi/extern/extern-pass-TwoU64s.rs index 603de2e49ab2d..dbce8690d78cd 100644 --- a/tests/ui/abi/extern/extern-pass-TwoU64s.rs +++ b/tests/ui/abi/extern/extern-pass-TwoU64s.rs @@ -1,10 +1,10 @@ //@ run-pass -#![allow(improper_ctypes)] // Test a foreign function that accepts and returns a struct // by value. #[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] pub struct TwoU64s { one: u64, two: u64, diff --git a/tests/ui/abi/extern/extern-pass-TwoU8s.rs b/tests/ui/abi/extern/extern-pass-TwoU8s.rs index a712d79a98dd2..56db34dcefea7 100644 --- a/tests/ui/abi/extern/extern-pass-TwoU8s.rs +++ b/tests/ui/abi/extern/extern-pass-TwoU8s.rs @@ -1,10 +1,10 @@ //@ run-pass -#![allow(improper_ctypes)] // Test a foreign function that accepts and returns a struct // by value. #[derive(Copy, Clone, PartialEq, Debug)] +#[repr(C)] pub struct TwoU8s { one: u8, two: u8, diff --git a/tests/ui/abi/extern/extern-pass-empty.rs b/tests/ui/abi/extern/extern-pass-empty.rs index 1ad52b128ad93..707c1944cdd63 100644 --- a/tests/ui/abi/extern/extern-pass-empty.rs +++ b/tests/ui/abi/extern/extern-pass-empty.rs @@ -1,5 +1,5 @@ //@ run-pass -#![allow(improper_ctypes)] // FIXME: this test is inherently not FFI-safe. +#![expect(improper_ctypes)] // FIXME: this test is inherently not FFI-safe. // Test a foreign function that accepts empty struct. diff --git a/tests/ui/abi/extern/extern-return-FiveU16s.rs b/tests/ui/abi/extern/extern-return-FiveU16s.rs index d8ae8b2661c5a..d0566de1e6975 100644 --- a/tests/ui/abi/extern/extern-return-FiveU16s.rs +++ b/tests/ui/abi/extern/extern-return-FiveU16s.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] +#[repr(C)] pub struct FiveU16s { one: u16, two: u16, diff --git a/tests/ui/abi/extern/extern-return-TwoU16s.rs b/tests/ui/abi/extern/extern-return-TwoU16s.rs index bf909a8db24c7..601daf35b4ea9 100644 --- a/tests/ui/abi/extern/extern-return-TwoU16s.rs +++ b/tests/ui/abi/extern/extern-return-TwoU16s.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] +#[repr(C)] pub struct TwoU16s { one: u16, two: u16, diff --git a/tests/ui/abi/extern/extern-return-TwoU32s.rs b/tests/ui/abi/extern/extern-return-TwoU32s.rs index c528da8cfc464..9f81286009959 100644 --- a/tests/ui/abi/extern/extern-return-TwoU32s.rs +++ b/tests/ui/abi/extern/extern-return-TwoU32s.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] +#[repr(C)] pub struct TwoU32s { one: u32, two: u32, diff --git a/tests/ui/abi/extern/extern-return-TwoU64s.rs b/tests/ui/abi/extern/extern-return-TwoU64s.rs index d4f9540ec7b35..a7dd142ffbc0d 100644 --- a/tests/ui/abi/extern/extern-return-TwoU64s.rs +++ b/tests/ui/abi/extern/extern-return-TwoU64s.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] +#[repr(C)] pub struct TwoU64s { one: u64, two: u64, diff --git a/tests/ui/abi/extern/extern-return-TwoU8s.rs b/tests/ui/abi/extern/extern-return-TwoU8s.rs index 228b27396249b..5f2d0418fd166 100644 --- a/tests/ui/abi/extern/extern-return-TwoU8s.rs +++ b/tests/ui/abi/extern/extern-return-TwoU8s.rs @@ -1,6 +1,6 @@ //@ run-pass -#![allow(improper_ctypes)] +#[repr(C)] pub struct TwoU8s { one: u8, two: u8, diff --git a/tests/ui/abi/foreign/foreign-fn-with-byval.rs b/tests/ui/abi/foreign/foreign-fn-with-byval.rs index 9908ec2d2c01a..d5f87c1a804b5 100644 --- a/tests/ui/abi/foreign/foreign-fn-with-byval.rs +++ b/tests/ui/abi/foreign/foreign-fn-with-byval.rs @@ -1,7 +1,7 @@ //@ run-pass -#![allow(improper_ctypes, improper_ctypes_definitions)] #[derive(Copy, Clone)] +#[repr(C)] pub struct S { x: u64, y: u64, diff --git a/tests/ui/abi/issue-28676.rs b/tests/ui/abi/issue-28676.rs index 2abb4ce52b3b3..616ed6bde24b1 100644 --- a/tests/ui/abi/issue-28676.rs +++ b/tests/ui/abi/issue-28676.rs @@ -2,9 +2,9 @@ //@ ignore-backends: gcc #![allow(dead_code)] -#![allow(improper_ctypes)] #[derive(Copy, Clone)] +#[repr(C)] pub struct Quad { a: u64, b: u64, diff --git a/tests/ui/abi/issues/issue-62350-sysv-neg-reg-counts.rs b/tests/ui/abi/issues/issue-62350-sysv-neg-reg-counts.rs index 314db42280d99..578381dcd32d1 100644 --- a/tests/ui/abi/issues/issue-62350-sysv-neg-reg-counts.rs +++ b/tests/ui/abi/issues/issue-62350-sysv-neg-reg-counts.rs @@ -1,8 +1,8 @@ //@ run-pass #![allow(dead_code)] -#![allow(improper_ctypes)] #[derive(Copy, Clone)] +#[repr(C)] pub struct QuadFloats { a: f32, b: f32, diff --git a/tests/ui/abi/issues/issue-97463-broken-abi-leaked-uninit-data.rs b/tests/ui/abi/issues/issue-97463-broken-abi-leaked-uninit-data.rs index f694205174889..fa8ac0bfe594a 100644 --- a/tests/ui/abi/issues/issue-97463-broken-abi-leaked-uninit-data.rs +++ b/tests/ui/abi/issues/issue-97463-broken-abi-leaked-uninit-data.rs @@ -1,6 +1,4 @@ //@ run-pass -#![allow(dead_code)] -#![allow(improper_ctypes)] #[link(name = "rust_test_helpers", kind = "static")] extern "C" { diff --git a/tests/ui/abi/large-byval-align.rs b/tests/ui/abi/large-byval-align.rs index 69418e1cbc7b8..7e640fb42a3cb 100644 --- a/tests/ui/abi/large-byval-align.rs +++ b/tests/ui/abi/large-byval-align.rs @@ -3,10 +3,9 @@ //@ build-pass //@ ignore-backends: gcc -#[repr(align(536870912))] +#[repr(C, align(536870912))] pub struct A(i64); -#[allow(improper_ctypes_definitions)] pub extern "C" fn foo(x: A) {} fn main() { diff --git a/tests/ui/abi/non-rustic-unsized.rs b/tests/ui/abi/non-rustic-unsized.rs index d26c4af72ccaf..fa22f91afeb67 100644 --- a/tests/ui/abi/non-rustic-unsized.rs +++ b/tests/ui/abi/non-rustic-unsized.rs @@ -3,7 +3,7 @@ #![no_core] #![crate_type = "lib"] #![feature(no_core, unsized_fn_params)] -#![allow(improper_ctypes_definitions, improper_ctypes)] +#![expect(improper_ctypes_definitions, improper_ctypes)] extern crate minicore; use minicore::*; diff --git a/tests/ui/abi/simd-abi-checks-avx.rs b/tests/ui/abi/simd-abi-checks-avx.rs index 7432381d15b72..e47346d02ea59 100644 --- a/tests/ui/abi/simd-abi-checks-avx.rs +++ b/tests/ui/abi/simd-abi-checks-avx.rs @@ -4,7 +4,7 @@ #![feature(portable_simd)] #![feature(simd_ffi)] -#![allow(improper_ctypes_definitions)] +#![expect(improper_ctypes_definitions)] use std::arch::x86_64::*; diff --git a/tests/ui/abi/simd-abi-checks-s390x.rs b/tests/ui/abi/simd-abi-checks-s390x.rs index 8ca3d2f457899..95028b8286d55 100644 --- a/tests/ui/abi/simd-abi-checks-s390x.rs +++ b/tests/ui/abi/simd-abi-checks-s390x.rs @@ -15,7 +15,7 @@ #![feature(no_core)] #![no_core] #![crate_type = "lib"] -#![allow(non_camel_case_types, improper_ctypes_definitions)] +#![expect(improper_ctypes_definitions)] extern crate minicore; use minicore::simd::*; diff --git a/tests/ui/abi/simd-abi-checks-sse.rs b/tests/ui/abi/simd-abi-checks-sse.rs index 906b6ec65610e..4ed4b78166f9e 100644 --- a/tests/ui/abi/simd-abi-checks-sse.rs +++ b/tests/ui/abi/simd-abi-checks-sse.rs @@ -9,7 +9,7 @@ //@ dont-require-annotations: NOTE #![feature(no_core)] #![no_core] -#![allow(improper_ctypes_definitions)] +#![expect(improper_ctypes_definitions)] extern crate minicore; use minicore::simd::Simd; diff --git a/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs b/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs index 7d21307e1b2d9..f0f6bb3f765b2 100644 --- a/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs +++ b/tests/ui/abi/unsized-args-in-c-abi-issues-94223-115845.rs @@ -1,5 +1,5 @@ //@ check-pass -#![allow(improper_ctypes_definitions)] +#![expect(improper_ctypes_definitions)] #![feature(unsized_fn_params)] #![crate_type = "lib"] diff --git a/tests/ui/c-variadic/roundtrip.rs b/tests/ui/c-variadic/roundtrip.rs index 9fa55c36e2441..1fb96ab7cfad7 100644 --- a/tests/ui/c-variadic/roundtrip.rs +++ b/tests/ui/c-variadic/roundtrip.rs @@ -5,7 +5,8 @@ c_variadic_va_arg_safe, c_variadic_int128, const_destruct, - const_raw_ptr_comparison + const_raw_ptr_comparison, + f128 )] #![allow(unused_features)] // c_variadic_int128 is only used on 64-bit targets. @@ -113,7 +114,38 @@ fn main() { roundtrip!(i128, -1, -2); roundtrip!(u128, 1, 2); } - _ => {} + _ => { /* unsupported */ } + } + + cfg_select! { + any( + all( + any(target_arch = "x86_64", target_arch = "x86"), + not(target_vendor = "apple"), + not(target_env = "msvc") + ), + all(target_arch = "powerpc64", target_feature = "vsx"), + all( + not(windows), + not(target_vendor = "apple"), + any( + target_arch = "aarch64", + target_arch = "loongarch32", + target_arch = "loongarch64", + target_arch = "mips64", + target_arch = "mips64r6", + target_arch = "riscv64", + target_arch = "s390x", + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "wasm32", + target_arch = "wasm64", + ), + ), + ) => { + roundtrip!(f128, -1.0, f128::MAX); + } + _ => { /* unsupported */ } } } } diff --git a/tests/ui/const-generics/mgca/inherent-alias-default.rs b/tests/ui/const-generics/mgca/inherent-alias-default.rs new file mode 100644 index 0000000000000..9ba6d1d15e856 --- /dev/null +++ b/tests/ui/const-generics/mgca/inherent-alias-default.rs @@ -0,0 +1,15 @@ +//@ check-pass +//! rustc_hir_analysis::check_item_type does type_of() on the default value. This is wonky, because +//! the generic args are in Self format at that point, not in impl format, so the result can't be +//! used with the Self-format args. However, it does not instantiate the result, it just does +//! ensure_ok(). This test just makes sure that codepath is hit in tests. +#![feature(min_generic_const_args, inherent_associated_types)] + +struct Struct(T1, T2, T3); +impl Struct { + const INHERENT: usize = core::direct_const_arg!(2); +} + +struct WithDefault::INHERENT) }>; + +fn main() {} diff --git a/tests/ui/ffi/ffi-struct-size-alignment.rs b/tests/ui/ffi/ffi-struct-size-alignment.rs index 287ae7cad2b6d..00f25f0673c5c 100644 --- a/tests/ui/ffi/ffi-struct-size-alignment.rs +++ b/tests/ui/ffi/ffi-struct-size-alignment.rs @@ -1,12 +1,12 @@ //@ run-pass #![allow(dead_code)] -#![allow(improper_ctypes)] // Issue #3656 // Incorrect struct size computation in the FFI, because of not taking // the alignment of elements into account. use std::ffi::{c_uint, c_void}; +#[repr(C)] pub struct KEYGEN { hash_algorithm: [c_uint; 2], count: u32, diff --git a/tests/ui/intrinsics/not-overridden.stderr b/tests/ui/intrinsics/not-overridden.stderr index ae5586b2f0a85..45c5c37318b89 100644 --- a/tests/ui/intrinsics/not-overridden.stderr +++ b/tests/ui/intrinsics/not-overridden.stderr @@ -5,8 +5,6 @@ LL | unsafe { const_deallocate(std::ptr::null_mut(), 0, 0) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: the compiler unexpectedly panicked. This is a bug - query stack during panic: end of query stack error: aborting due to 1 previous error diff --git a/tests/ui/resolve/multiple_definitions_attribute_merging.stderr b/tests/ui/resolve/multiple_definitions_attribute_merging.stderr index 63bdcfddf3ca2..b8b33e3417bf7 100644 --- a/tests/ui/resolve/multiple_definitions_attribute_merging.stderr +++ b/tests/ui/resolve/multiple_definitions_attribute_merging.stderr @@ -17,9 +17,7 @@ LL | struct Dealigned(u8, T); | ^ -builtin derive created an unaligned reference -error: the compiler unexpectedly panicked. This is a bug - +Box query stack during panic: #0 [mir_built] building MIR for `::eq` #1 [check_unsafety] unsafety-checking `::eq` diff --git a/tests/ui/resolve/proc_macro_generated_packed.stderr b/tests/ui/resolve/proc_macro_generated_packed.stderr index 3e63abb4b9e6b..d8e160d0c6a03 100644 --- a/tests/ui/resolve/proc_macro_generated_packed.stderr +++ b/tests/ui/resolve/proc_macro_generated_packed.stderr @@ -8,9 +8,7 @@ LL | struct Dealigned(u8, T); | ^ -builtin derive created an unaligned reference -error: the compiler unexpectedly panicked. This is a bug - +Box query stack during panic: #0 [mir_built] building MIR for `::eq` #1 [check_unsafety] unsafety-checking `::eq` diff --git a/tests/ui/thir-print/str-patterns.stdout b/tests/ui/thir-print/str-patterns.stdout index da1f86b8fc591..61bcbaef5029a 100644 --- a/tests/ui/thir-print/str-patterns.stdout +++ b/tests/ui/thir-print/str-patterns.stdout @@ -46,7 +46,9 @@ Thir { extra: Some( PatExtra { expanded_const: Some( - DefId(0:4 ~ str_patterns[fc71]::CONSTANT), + Free { + def_id: DefId(0:4 ~ str_patterns[fc71]::CONSTANT), + }, ), ascriptions: [], }, diff --git a/tests/ui/track-diagnostics/track7.rs b/tests/ui/track-diagnostics/track7.rs new file mode 100644 index 0000000000000..f43c1e1a81c23 --- /dev/null +++ b/tests/ui/track-diagnostics/track7.rs @@ -0,0 +1,40 @@ +// This test checks that -Ztrack-diagnostics reports the correct source locations for an ICE +// triggered with `span_bug!`. +// +//@ compile-flags: -Zvalidate-mir -Ztrack-diagnostics +//@ rustc-env:RUST_BACKTRACE=0 +//@ failure-status: 101 +// +// Normalize the emitted location so this doesn't need +// updating everytime someone adds or removes a line. +//@ normalize-stderr: ".rs:\d+:\d+" -> ".rs:LL:CC" +//@ normalize-stderr: "note: rustc .+ running on .+" -> "note: rustc $$VERSION running on $$TARGET" +//@ normalize-stderr: "/rustc(?:-dev)?/[a-z0-9.]+/" -> "" +//@ normalize-stderr: "track7\[....\]" -> "track7[HASH]" +// The test becomes too flaky if we care about exact args. If `-Z ui-testing` +// from compiletest and `-Z track-diagnostics` from `// compile-flags` at the +// top of this file are present, then assume all args are present. +//@ normalize-stderr: "note: compiler flags: .*-Z ui-testing.*-Z track-diagnostics" -> "note: compiler flags: ... -Z ui-testing ... -Z track-diagnostics" + +#![feature(custom_mir, core_intrinsics)] +extern crate core; +use core::intrinsics::mir::*; + +fn bar(_x: i32) {} + +// Use of `mir!` here is just because it's an easy way to trigger a `span_bug!`. +#[custom_mir(dialect = "built")] +pub fn main() { + mir! { + let a: (i32, i32); + { + a = (1, 2); + Call(RET = bar(Move(a.0)), ReturnTo(retblock), UnwindContinue()) + //~^ ERROR broken MIR in + //~| ERROR encountered `Move` of a non-local, non-box place in `Call` terminator + } + retblock = { + Return() + } + } +} diff --git a/tests/ui/track-diagnostics/track7.stderr b/tests/ui/track-diagnostics/track7.stderr new file mode 100644 index 0000000000000..61a615f782b8e --- /dev/null +++ b/tests/ui/track-diagnostics/track7.stderr @@ -0,0 +1,26 @@ +error: internal compiler error: compiler/rustc_mir_transform/src/validate.rs:LL:CC: broken MIR in Item(DefId(0:6 ~ track7[HASH]::main)) (after pass LintAndRemoveUninhabited) at bb0[1]: + encountered `Move` of a non-local, non-box place in `Call` terminator: _0 = bar(move (_1.0: i32)) -> [return: bb1, unwind continue] + --> $DIR/track7.rs:LL:CC + | +LL | Call(RET = bar(Move(a.0)), ReturnTo(retblock), UnwindContinue()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: -Ztrack-diagnostics: created at compiler/rustc_mir_transform/src/validate.rs:LL:CC + + +thread 'rustc' ($TID) panicked at compiler/rustc_mir_transform/src/validate.rs:LL:CC: +Box +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +note: using internal features is not supported and expected to cause internal compiler errors when used incorrectly + +note: rustc $VERSION running on $TARGET + +note: compiler flags: ... -Z ui-testing ... -Z track-diagnostics + +query stack during panic: +#0 [mir_built] building MIR for `main` +#1 [has_ffi_unwind_calls] checking if `main` contains FFI-unwind calls +... and 3 other queries... use `env RUST_BACKTRACE=1` to see the full query stack +error: aborting due to 1 previous error + diff --git a/tests/ui/traits/block-scoped-trait-import-issue-134146.rs b/tests/ui/traits/block-scoped-trait-import-issue-134146.rs new file mode 100644 index 0000000000000..0a9874c281d67 --- /dev/null +++ b/tests/ui/traits/block-scoped-trait-import-issue-134146.rs @@ -0,0 +1,59 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/134146. +//! Traits declared inside bodies cannot be imported through their enclosing function or const. + +#![allow(dead_code)] + +fn main() { + { + trait Hello { + fn hello(&self) { + println!("hello world"); + } + } + impl Hello for T {} + } + + ().hello(); + //~^ ERROR no method named `hello` found +} + +fn nested_module() { + mod inner { + pub trait Nested { + fn nested(&self) {} + } + impl Nested for () {} + } +} + +fn use_nested() { + ().nested(); + //~^ ERROR no method named `nested` found +} + +const _: () = { + trait InConst { + fn in_const(&self) {} + } + impl InConst for () {} +}; + +fn use_const() { + ().in_const(); + //~^ ERROR no method named `in_const` found +} + +fn multiple_candidates() { + { + trait First { + fn several(&self) {} + } + trait Second { + fn several(&self) {} + } + impl First for () {} + impl Second for () {} + } + ().several(); + //~^ ERROR no method named `several` found +} diff --git a/tests/ui/traits/block-scoped-trait-import-issue-134146.stderr b/tests/ui/traits/block-scoped-trait-import-issue-134146.stderr new file mode 100644 index 0000000000000..41827953bb796 --- /dev/null +++ b/tests/ui/traits/block-scoped-trait-import-issue-134146.stderr @@ -0,0 +1,50 @@ +error[E0599]: no method named `hello` found for unit type `()` in the current scope + --> $DIR/block-scoped-trait-import-issue-134146.rs:16:8 + | +LL | fn hello(&self) { + | ----- the method is available for `()` here +... +LL | ().hello(); + | ^^^^^ method not found in `()` + | + = help: items from traits can only be used if the trait is in scope + = help: trait `main::Hello` which provides `hello` is implemented but not reachable + +error[E0599]: no method named `nested` found for unit type `()` in the current scope + --> $DIR/block-scoped-trait-import-issue-134146.rs:30:8 + | +LL | fn nested(&self) {} + | ------ the method is available for `()` here +... +LL | ().nested(); + | ^^^^^^ method not found in `()` + | + = help: items from traits can only be used if the trait is in scope + = help: trait `nested_module::inner::Nested` which provides `nested` is implemented but not reachable + +error[E0599]: no method named `in_const` found for unit type `()` in the current scope + --> $DIR/block-scoped-trait-import-issue-134146.rs:42:8 + | +LL | fn in_const(&self) {} + | -------- the method is available for `()` here +... +LL | ().in_const(); + | ^^^^^^^^ method not found in `()` + | + = help: items from traits can only be used if the trait is in scope + = help: trait `_::InConst` which provides `in_const` is implemented but not reachable + +error[E0599]: no method named `several` found for unit type `()` in the current scope + --> $DIR/block-scoped-trait-import-issue-134146.rs:57:8 + | +LL | ().several(); + | ^^^^^^^ method not found in `()` + | + = help: items from traits can only be used if the trait is in scope + = help: the following traits which provide `several` are implemented but not reachable: + multiple_candidates::First + multiple_candidates::Second + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0599`. diff --git a/triagebot.toml b/triagebot.toml index 4f2d0a262fdc1..475cf6e044f9b 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -850,7 +850,7 @@ zulip_stream = 542373 # #t-libs/backports topic = "#{number}: beta-nominated" message_on_add = [ """\ -@*T-libs-fcp* PR #{number} "{title}" has been nominated for beta backport. +@*T-libs-ping* PR #{number} "{title}" has been nominated for beta backport. """, """\ /poll Should #{number} be beta backported? @@ -874,7 +874,7 @@ zulip_stream = 542373 # #t-libs/backports topic = "#{number}: stable-nominated" message_on_add = [ """\ -@*T-libs-fcp* PR #{number} "{title}" has been nominated for stable backport. +@*T-libs-ping* PR #{number} "{title}" has been nominated for stable backport. """, """\ /poll Approve stable backport of #{number}? diff --git a/yarn.lock b/yarn.lock index 55c862d1e75dd..e49c9209142f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -154,10 +154,10 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browser-ui-test@^0.25.0: - version "0.25.1" - resolved "https://registry.yarnpkg.com/browser-ui-test/-/browser-ui-test-0.25.1.tgz#c7f22a5e2b9e51be4ba34df3adf7bd7a9249bce6" - integrity sha512-woRwKU1dPBIwYmCI6npox8qlPO0WQ8GZH2YbL39mNkiWymByebiB4EK0PlaGMbmEja0MEqfMQD+d33LCW4S2AA== +browser-ui-test@^0.25.2: + version "0.25.2" + resolved "https://registry.yarnpkg.com/browser-ui-test/-/browser-ui-test-0.25.2.tgz#31db7386497b3eef4d79e236bd2091c1162148a9" + integrity sha512-74njL1/xjg5UumbhQblWa6oAn/PcaOnlJmoSe9FabJaXrLsI2f8DJ3B8vunvCJwZa90V4Jm94xyg/JPi+l+zZQ== dependencies: css-unit-converter "^1.1.2" pngjs "^3.4.0"