diff --git a/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs b/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs index 3d0bb6fcc48fd..d20a73e8e6825 100644 --- a/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs +++ b/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs @@ -197,6 +197,20 @@ fn generate_launcher<'ll>(cx: &CodegenCx<'ll, '_>) -> (&'ll llvm::Value, &'ll ll (tgt_decl, tgt_fn_ty) } +/// Declares the `omp_get_num_devices` runtime function and returns the +/// declaration together with its type. +pub(crate) fn declare_omp_get_num_devices<'ll>( + cx: &CodegenCx<'ll, '_>, +) -> (&'ll llvm::Value, &'ll llvm::Type) { + let ti32 = cx.type_i32(); + let tgt_fn_ty = cx.type_func(&[], ti32); + let name = "omp_get_num_devices"; + let tgt_decl = declare_offload_fn(&cx, name, tgt_fn_ty); + let nounwind = llvm::AttributeKind::NoUnwind.create_attr(cx.llcx); + attributes::apply_to_llfn(tgt_decl, Function, &[nounwind]); + (tgt_decl, tgt_fn_ty) +} + // What is our @1 here? A magic global, used in our data_{begin/update/end}_mapper: // @0 = private unnamed_addr constant [23 x i8] c";unknown;unknown;0;0;;\00", align 1 // @1 = private unnamed_addr constant %struct.ident_t { i32 0, i32 2, i32 0, i32 22, ptr @0 }, align 8 @@ -591,6 +605,7 @@ pub(crate) fn gen_call_handling<'ll, 'tcx>( offload_globals: &OffloadGlobals<'ll>, offload_dims: &OffloadKernelDims<'ll>, dyn_cache: &'ll Value, + device_id: &'ll Value, ) { let cx = builder.cx; let OffloadKernelGlobals { @@ -775,15 +790,8 @@ pub(crate) fn gen_call_handling<'ll, 'tcx>( builder.store(value.2, ptr, value.0); } - let args = vec![ - s_ident_t, - // FIXME(offload) give users a way to select which GPU to use. - cx.get_const_i64(u64::MAX), // MAX == -1. - num_workgroups, - threads_per_block, - region_id, - a5, - ]; + let device_id = builder.sext(device_id, cx.type_i64()); + let args = vec![s_ident_t, device_id, num_workgroups, threads_per_block, region_id, a5]; builder.call(tgt_target_kernel_ty, None, None, tgt_decl, &args, None, None); // %41 = call i32 @__tgt_target_kernel(ptr @1, i64 -1, i32 2097152, i32 256, ptr @.kernel_1.region_id, ptr %kernel_args) diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index ba11ef29fb536..a5bb595d9b2c6 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -37,7 +37,7 @@ use crate::abi::FnAbiLlvmExt; use crate::builder::Builder; use crate::builder::autodiff::{adjust_activity_to_abi, generate_enzyme_call}; use crate::builder::gpu_offload::{ - OffloadKernelDims, gen_call_handling, gen_define_handling, register_offload, + self, OffloadKernelDims, declare_omp_get_num_devices, register_offload, }; use crate::context::CodegenCx; use crate::declare::declare_raw_fn; @@ -241,6 +241,13 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { // offload *has* a return type, but somehow works without mentioning the place return IntrinsicResult::WroteIntoPlace; } + sym::offload_get_num_devices => { + let (fn_decl, fn_ty) = declare_omp_get_num_devices(self.cx); + + let llval = self.call(fn_ty, None, None, fn_decl, &[], None, None); + + return IntrinsicResult::Operand(OperandValue::Immediate(llval)); + }, sym::is_val_statically_known => { if let OperandValue::Immediate(imm) = args[0].val { self.call_intrinsic( @@ -1851,7 +1858,11 @@ fn codegen_offload<'ll, 'tcx>( OperandValue::Immediate(val) => val, _ => panic!("unparsable"), }; - let args = get_args_from_tuple(bx, args[4], fn_target); + let device_id = match args[4].val { + OperandValue::Immediate(val) => val, + _ => panic!("unparsable"), + }; + let args = get_args_from_tuple(bx, args[5], fn_target); let target_symbol = mangle_offload_export(tcx, fn_target); let sig = tcx.fn_sig(fn_target.def_id()).instantiate(tcx, fn_target.args).skip_norm_wip(); @@ -1882,8 +1893,9 @@ fn codegen_offload<'ll, 'tcx>( } }; register_offload(cx); - let offload_data = gen_define_handling(&cx, &metadata, target_symbol, offload_globals); - gen_call_handling( + let offload_data = + gpu_offload::gen_define_handling(&cx, &metadata, target_symbol, offload_globals); + gpu_offload::gen_call_handling( bx, &offload_data, &args, @@ -1892,6 +1904,7 @@ fn codegen_offload<'ll, 'tcx>( offload_globals, &offload_dims, &dyn_cache, + &device_id, ); } diff --git a/compiler/rustc_codegen_ssa/src/back/archive.rs b/compiler/rustc_codegen_ssa/src/back/archive.rs index c4107b4a60f27..6c4575caebd8e 100644 --- a/compiler/rustc_codegen_ssa/src/back/archive.rs +++ b/compiler/rustc_codegen_ssa/src/back/archive.rs @@ -623,8 +623,9 @@ impl<'a> ArArchiveBuilder<'a> { io::Error::new( io::ErrorKind::InvalidData, format!( - "archive member at offset {start} with size {} \ + "archive member of {} at offset {start} with size {} \ exceeds archive size {} in `{}`", + src_archive.0.display(), file_range.1, archive_data.len(), src_archive.0.display(), @@ -642,11 +643,18 @@ impl<'a> ArArchiveBuilder<'a> { } } ArchiveEntrySource::File(file) => unsafe { - let mmap = Mmap::map( - File::open(file) - .map_err(|err| io_error_context("failed to open object file", err))?, - ) - .map_err(|err| io_error_context("failed to map object file", err))?; + let mmap = Mmap::map(File::open(&file).map_err(|err| { + io_error_context( + &format!("failed to open object file {}", file.display()), + err, + ) + })?) + .map_err(|err| { + io_error_context( + &format!("failed to map object file {}", file.display()), + err, + ) + })?; if entry.kind == ArchiveEntryKind::RustObj && let Some(sym) = &symbols { diff --git a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs index d0820ad79902c..ed572b3774567 100644 --- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs @@ -135,6 +135,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { | sym::atomic_fence | sym::atomic_singlethreadfence | sym::caller_location + | sym::offload_get_num_devices | sym::return_address => {} _ => { span_bug!( diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 7648bf4eb241d..b1efe62e57a04 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -49,69 +49,11 @@ pub(crate) struct Qualifs<'mir, 'tcx> { } impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { - /// Returns `true` if `local` is `NeedsDrop` at the given `Location`. - /// - /// Only updates the cursor if absolutely necessary - pub(crate) fn needs_drop( - &mut self, - ccx: &'mir ConstCx<'mir, 'tcx>, - local: Local, - location: Location, - ) -> bool { - let ty = ccx.body.local_decls[local].ty; - // Peeking into opaque types causes cycles if the current function declares said opaque - // type. Thus we avoid short circuiting on the type and instead run the more expensive - // analysis that looks at the actual usage within this function - if !ty.has_opaque_types() && !NeedsDrop::in_any_value_of_ty(ccx, ty) { - return false; - } - - let needs_drop = self.needs_drop.get_or_insert_with(|| { - let ConstCx { tcx, body, .. } = *ccx; - - FlowSensitiveAnalysis::new(NeedsDrop, ccx) - .iterate_to_fixpoint(tcx, body, None) - .into_results_cursor(body) - }); - - needs_drop.seek_before_primary_effect(location); - needs_drop.get().contains(local) - } - - /// Returns `true` if `local` is `NeedsNonConstDrop` at the given `Location`. - /// - /// Only updates the cursor if absolutely necessary - pub(crate) fn needs_non_const_drop( - &mut self, - ccx: &'mir ConstCx<'mir, 'tcx>, - local: Local, - location: Location, - ) -> bool { - let ty = ccx.body.local_decls[local].ty; - // Peeking into opaque types causes cycles if the current function declares said opaque - // type. Thus we avoid short circuiting on the type and instead run the more expensive - // analysis that looks at the actual usage within this function - if !ty.has_opaque_types() && !NeedsNonConstDrop::in_any_value_of_ty(ccx, ty) { - return false; - } - - let needs_non_const_drop = self.needs_non_const_drop.get_or_insert_with(|| { - let ConstCx { tcx, body, .. } = *ccx; - - FlowSensitiveAnalysis::new(NeedsNonConstDrop, ccx) - .iterate_to_fixpoint(tcx, body, None) - .into_results_cursor(body) - }); - - needs_non_const_drop.seek_before_primary_effect(location); - needs_non_const_drop.get().contains(local) - } - - /// Returns `true` if `local` is `HasMutInterior` at the given `Location`. + /// Does `Q` hold for the `local` at the given `Location`? /// /// Only updates the cursor if absolutely necessary. - fn has_mut_interior( - &mut self, + fn in_local( + qualif_results: &mut Option>, ccx: &'mir ConstCx<'mir, 'tcx>, local: Local, location: Location, @@ -119,21 +61,21 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { let ty = ccx.body.local_decls[local].ty; // Peeking into opaque types causes cycles if the current function declares said opaque // type. Thus we avoid short circuiting on the type and instead run the more expensive - // analysis that looks at the actual usage within this function - if !ty.has_opaque_types() && !HasMutInterior::in_any_value_of_ty(ccx, ty) { + // analysis that looks at the actual usage within this function. + if !ty.has_opaque_types() && !Q::in_any_value_of_ty(ccx, ty) { return false; } - let has_mut_interior = self.has_mut_interior.get_or_insert_with(|| { + let qualif_results = qualif_results.get_or_insert_with(|| { let ConstCx { tcx, body, .. } = *ccx; - FlowSensitiveAnalysis::new(HasMutInterior, ccx) + FlowSensitiveAnalysis::new(ccx) .iterate_to_fixpoint(tcx, body, None) .into_results_cursor(body) }); - has_mut_interior.seek_before_primary_effect(location); - has_mut_interior.get().contains(local) + qualif_results.seek_before_primary_effect(location); + qualif_results.get().contains(local) } fn in_return_place( @@ -161,9 +103,19 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { let return_loc = ccx.body.terminator_loc(return_block); ConstQualifs { - needs_drop: self.needs_drop(ccx, RETURN_PLACE, return_loc), - needs_non_const_drop: self.needs_non_const_drop(ccx, RETURN_PLACE, return_loc), - has_mut_interior: self.has_mut_interior(ccx, RETURN_PLACE, return_loc), + needs_drop: Self::in_local(&mut self.needs_drop, ccx, RETURN_PLACE, return_loc), + needs_non_const_drop: Self::in_local( + &mut self.needs_non_const_drop, + ccx, + RETURN_PLACE, + return_loc, + ), + has_mut_interior: Self::in_local( + &mut self.has_mut_interior, + ccx, + RETURN_PLACE, + return_loc, + ), tainted_by_errors, } } @@ -435,7 +387,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { let ty_of_dropped_place = dropped_place.ty(self.body, self.tcx).ty; let needs_drop = if let Some(local) = dropped_place.as_local() { - self.qualifs.needs_drop(self.ccx, local, location) + Qualifs::in_local(&mut self.qualifs.needs_drop, self.ccx, local, location) } else { qualifs::NeedsDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place) }; @@ -448,7 +400,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { let needs_non_const_drop = if let Some(local) = dropped_place.as_local() { // Use the span where the local was declared as the span of the drop error. err_span = self.body.local_decls[local].source_info.span; - self.qualifs.needs_non_const_drop(self.ccx, local, location) + Qualifs::in_local(&mut self.qualifs.needs_non_const_drop, self.ccx, local, location) } else { qualifs::NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place) }; @@ -602,7 +554,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { | Rvalue::RawPtr(RawPtrKind::Const, place) => { let borrowed_place_has_mut_interior = qualifs::in_place::( self.ccx, - &mut |local| self.qualifs.has_mut_interior(self.ccx, local, location), + &mut |local| { + Qualifs::in_local( + &mut self.qualifs.has_mut_interior, + self.ccx, + local, + location, + ) + }, place.as_ref(), ); diff --git a/compiler/rustc_const_eval/src/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/check_consts/qualifs.rs index b2b8a567860e0..daac2d5176258 100644 --- a/compiler/rustc_const_eval/src/check_consts/qualifs.rs +++ b/compiler/rustc_const_eval/src/check_consts/qualifs.rs @@ -45,10 +45,10 @@ pub trait Qualif { const ANALYSIS_NAME: &'static str; /// Whether this `Qualif` is cleared when a local is moved from. - const IS_CLEARED_ON_MOVE: bool = false; + const IS_CLEARED_ON_MOVE: bool; /// Whether this `Qualif` might be evaluated after the promotion and can encounter a promoted. - const ALLOW_PROMOTED: bool = false; + const ALLOW_PROMOTED: bool; /// Extracts the field of `ConstQualifs` that corresponds to this `Qualif`. fn in_qualifs(qualifs: &ConstQualifs) -> bool; @@ -79,6 +79,8 @@ pub struct HasMutInterior; impl Qualif for HasMutInterior { const ANALYSIS_NAME: &'static str = "flow_has_mut_interior"; + const IS_CLEARED_ON_MOVE: bool = false; + const ALLOW_PROMOTED: bool = false; fn in_qualifs(qualifs: &ConstQualifs) -> bool { qualifs.has_mut_interior diff --git a/compiler/rustc_const_eval/src/check_consts/resolver.rs b/compiler/rustc_const_eval/src/check_consts/resolver.rs index 29b6e26d950d5..4fc8c62ccf80a 100644 --- a/compiler/rustc_const_eval/src/check_consts/resolver.rs +++ b/compiler/rustc_const_eval/src/check_consts/resolver.rs @@ -247,7 +247,7 @@ impl<'mir, 'tcx, Q> FlowSensitiveAnalysis<'mir, 'tcx, Q> where Q: Qualif, { - pub(super) fn new(_: Q, ccx: &'mir ConstCx<'mir, 'tcx>) -> Self { + pub(super) fn new(ccx: &'mir ConstCx<'mir, 'tcx>) -> Self { FlowSensitiveAnalysis { ccx, _qualif: PhantomData } } @@ -309,7 +309,7 @@ impl DebugWithContext for State { if self.borrow != old.borrow { f.write_str("borrow: ")?; - self.qualif.fmt_diff_with(&old.borrow, ctxt, f)?; + self.borrow.fmt_diff_with(&old.borrow, ctxt, f)?; f.write_str("\n")?; } diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 4dd8a4e3fe51d..0d7ff905300cc 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -168,6 +168,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::needs_drop | sym::non_exhaustive | sym::offload + | sym::offload_get_num_devices | sym::offset_of | sym::overflow_checks | sym::powf16 @@ -384,10 +385,12 @@ pub(crate) fn check_intrinsic_type( Ty::new_array_with_const_len(tcx, tcx.types.u32, Const::from_target_usize(tcx, 3)), Ty::new_array_with_const_len(tcx, tcx.types.u32, Const::from_target_usize(tcx, 3)), tcx.types.u32, + tcx.types.i32, param(1), ], param(2), ), + sym::offload_get_num_devices => (0, 0, vec![], tcx.types.i32), sym::offset => (2, 0, vec![param(0), param(1)], param(0)), sym::arith_offset => ( 1, diff --git a/compiler/rustc_hir_analysis/src/collect/dump.rs b/compiler/rustc_hir_analysis/src/collect/dump.rs index b1b8b513f3b35..772bbe4a6b579 100644 --- a/compiler/rustc_hir_analysis/src/collect/dump.rs +++ b/compiler/rustc_hir_analysis/src/collect/dump.rs @@ -3,6 +3,7 @@ use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; use rustc_hir::{find_attr, intravisit}; use rustc_middle::hir::nested_filter; +use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault; use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt, Unnormalized}; use rustc_span::sym; @@ -25,6 +26,30 @@ pub(crate) fn generics(tcx: TyCtxt<'_>) { } } +pub(crate) fn object_lifetime_defaults(tcx: TyCtxt<'_>) { + for def_id in tcx.hir_crate_items(()).definitions() { + if def_id == hir::def_id::CRATE_DEF_ID { + continue; + } + + if !find_attr!(tcx, def_id, RustcDumpObjectLifetimeDefaults) { + continue; + } + + for param in &tcx.generics_of(def_id).own_params { + let ty::GenericParamDefKind::Type { .. } = param.kind else { continue }; + let default = tcx.object_lifetime_default(param.def_id); + let repr = match default { + ObjectLifetimeDefault::Empty => "Empty".to_owned(), + ObjectLifetimeDefault::Static => "'static".to_owned(), + ObjectLifetimeDefault::Param(def_id) => tcx.item_name(def_id).to_string(), + ObjectLifetimeDefault::Ambiguous => "Ambiguous".to_owned(), + }; + tcx.dcx().span_err(tcx.def_span(param.def_id), repr); + } + } +} + pub(crate) fn opaque_hidden_types(tcx: TyCtxt<'_>) { if !find_attr!(tcx, crate, RustcDumpHiddenTypeOfOpaques) { return; diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 0bedff530212e..dc108c41cf787 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -2055,3 +2055,21 @@ fn assoc_tag_str(assoc_tag: ty::AssocTag) -> &'static str { ty::AssocTag::Type => "type", } } + +/// Computes the `pat.between(ty)` span for the "use `=`" suggestion on `let pat: ty`. +/// Returns `None` if `pat` and `ty` are in incompatible macro contexts (e.g. `pat` is a +/// metavariable from the call site while `ty` lives in the macro body), in which case no +/// suggestion is emitted. +pub(crate) fn eq_ctxt_suggestion_span(pat: Span, ty: Span) -> Option { + if let Some(ty2) = ty.find_ancestor_in_same_ctxt(pat) + && pat.hi() <= ty2.lo() + { + return Some(pat.between(ty2)); + } + if let Some(pat2) = pat.find_ancestor_in_same_ctxt(ty) + && pat2.hi() <= ty.lo() + { + return Some(pat2.between(ty)); + } + None +} 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 c2cf7136bd4a0..ea5c47ff6be77 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -56,7 +56,9 @@ use tracing::{debug, instrument}; use crate::check::check_abi; use crate::check_c_variadic_abi; use crate::diagnostics::{self, BadReturnTypeNotation, NoFieldOnType, NoVariantNamed}; -use crate::hir_ty_lowering::errors::{GenericsArgsErrExtend, prohibit_assoc_item_constraint}; +use crate::hir_ty_lowering::errors::{ + GenericsArgsErrExtend, eq_ctxt_suggestion_span, prohibit_assoc_item_constraint, +}; use crate::hir_ty_lowering::generics::{check_generic_arg_count, lower_generic_args}; use crate::middle::resolve_bound_vars as rbv; @@ -3302,18 +3304,18 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .next() { // `let x: S::new(valid_in_ty_ctxt);` -> `let x = S::new(valid_in_ty_ctxt);` - let err = tcx - .dcx() - .struct_span_err( - hir_ty.span, - "expected type, found associated function call", - ) - .with_span_suggestion_verbose( - stmt.pat.span.between(hir_ty.span), + let mut err = tcx.dcx().struct_span_err( + hir_ty.span, + "expected type, found associated function call", + ); + if let Some(between) = eq_ctxt_suggestion_span(stmt.pat.span, hir_ty.span) { + err.span_suggestion_verbose( + between, "use `=` if you meant to assign", - " = ".to_string(), + " = ", Applicability::MaybeIncorrect, ); + } self.dcx().try_steal_replace_and_emit_err( hir_ty.span, StashKey::ReturnTypeNotation, @@ -3328,18 +3330,18 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { { // `let x: i32::something(valid_in_ty_ctxt);` -> `let x = i32::something(valid_in_ty_ctxt);` // FIXME: Check that `something` is a valid function in `i32`. - let err = tcx - .dcx() - .struct_span_err( - hir_ty.span, - "expected type, found associated function call", - ) - .with_span_suggestion_verbose( - stmt.pat.span.between(hir_ty.span), + let mut err = tcx.dcx().struct_span_err( + hir_ty.span, + "expected type, found associated function call", + ); + if let Some(between) = eq_ctxt_suggestion_span(stmt.pat.span, hir_ty.span) { + err.span_suggestion_verbose( + between, "use `=` if you meant to assign", - " = ".to_string(), + " = ", Applicability::MaybeIncorrect, ); + } self.dcx().try_steal_replace_and_emit_err( hir_ty.span, StashKey::ReturnTypeNotation, diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs index dc0b895ea1aa9..1f342f10f8b17 100644 --- a/compiler/rustc_hir_analysis/src/lib.rs +++ b/compiler/rustc_hir_analysis/src/lib.rs @@ -203,13 +203,16 @@ pub fn check_crate(tcx: TyCtxt<'_>) { if tcx.features().rustc_attrs() { tcx.sess.time("dumping_rustc_attr_data", || { - outlives::dump::inferred_outlives(tcx); - variance::dump::variances(tcx); - collect::dump::generics(tcx); - collect::dump::opaque_hidden_types(tcx); + // tidy-alphabetical-start collect::dump::clauses_and_item_bounds(tcx); collect::dump::def_parents(tcx); + collect::dump::generics(tcx); + collect::dump::object_lifetime_defaults(tcx); + collect::dump::opaque_hidden_types(tcx); collect::dump::vtables(tcx); + outlives::dump::inferred_outlives(tcx); + variance::dump::variances(tcx); + // tidy-alphabetical-end }); } diff --git a/compiler/rustc_mir_dataflow/src/drop_flag_effects.rs b/compiler/rustc_mir_dataflow/src/drop_flag_effects.rs index b6fc1219a8503..7d7491dd643b0 100644 --- a/compiler/rustc_mir_dataflow/src/drop_flag_effects.rs +++ b/compiler/rustc_mir_dataflow/src/drop_flag_effects.rs @@ -87,6 +87,8 @@ pub fn on_all_children_bits<'tcx, F>( on_all_children_bits(move_data, move_path_index, &mut each_child); } +/// Calls `callback` for each child move path of the function's arguments. Note the move paths' +/// `DropFlagState` argument to the callback will always be `DropFlagState::Present`. pub fn drop_flag_effects_for_function_entry<'tcx, F>( body: &Body<'tcx>, move_data: &MoveData<'tcx>, diff --git a/compiler/rustc_mir_dataflow/src/impls/initialized.rs b/compiler/rustc_mir_dataflow/src/impls/initialized.rs index 7356a0815a8fc..2d69998f42ad9 100644 --- a/compiler/rustc_mir_dataflow/src/impls/initialized.rs +++ b/compiler/rustc_mir_dataflow/src/impls/initialized.rs @@ -154,7 +154,9 @@ impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> { skip_unreachable_unwind: false, } } +} +impl<'tcx> MaybeInitializedPlaces<'_, 'tcx> { /// Ensures definitely inactive variants are excluded from the set of initialized places for /// blocks reached through an `otherwise` edge. pub fn exclude_inactive_in_otherwise(mut self) -> Self { @@ -182,143 +184,7 @@ impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> { false } } -} - -impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> { - fn move_data(&self) -> &MoveData<'tcx> { - self.move_data - } -} - -/// `MaybeUninitializedPlaces` tracks all places that might be -/// uninitialized upon reaching a particular point in the control flow -/// for a function. -/// -/// For example, in code like the following, we have corresponding -/// dataflow information shown in the right-hand comments. -/// -/// ```rust -/// struct S; -/// #[rustfmt::skip] -/// fn foo(p: bool) { // maybe-uninit: -/// // {a, b, c, d} -/// let a = S; let mut b = S; let c; let d; // { c, d} -/// -/// if p { -/// drop(a); // {a, c, d} -/// b = S; // {a, c, d} -/// -/// } else { -/// drop(b); // { b, c, d} -/// d = S; // { b, c } -/// -/// } // {a, b, c, d} -/// -/// c = S; // {a, b, d} -/// } -/// ``` -/// -/// To determine whether a place is *definitely* uninitialized at a -/// particular control-flow point, one can take the set-complement -/// of the data from `MaybeInitializedPlaces` at the corresponding -/// control-flow point. -/// -/// Similarly, at a given `drop` statement, the set-intersection -/// between this data and `MaybeInitializedPlaces` yields the set of -/// places that would require a dynamic drop-flag at that statement. -pub struct MaybeUninitializedPlaces<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - body: &'a Body<'tcx>, - move_data: &'a MoveData<'tcx>, - - mark_inactive_variants_as_uninit: bool, - skip_unreachable_unwind: DenseBitSet, -} - -impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> { - pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>) -> Self { - MaybeUninitializedPlaces { - tcx, - body, - move_data, - mark_inactive_variants_as_uninit: false, - skip_unreachable_unwind: DenseBitSet::new_empty(body.basic_blocks.len()), - } - } - - /// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an - /// enum discriminant. - /// - /// This is correct in a vacuum but is not the default because it causes problems in the borrow - /// checker, where this information gets propagated along `FakeEdge`s. - pub fn mark_inactive_variants_as_uninit(mut self) -> Self { - self.mark_inactive_variants_as_uninit = true; - self - } - - pub fn skipping_unreachable_unwind( - mut self, - unreachable_unwind: DenseBitSet, - ) -> Self { - self.skip_unreachable_unwind = unreachable_unwind; - self - } -} - -impl<'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { - fn move_data(&self) -> &MoveData<'tcx> { - self.move_data - } -} - -/// `EverInitializedPlaces` tracks all initializations of locals that may have -/// occurred upon reaching a particular point in the control flow for a -/// function, without an intervening `StorageDead`. -/// -/// This dataflow is used to determine if an immutable local variable may -/// be assigned to. -/// -/// For example, in code like the following, we have corresponding -/// dataflow information shown in the right-hand comments. -/// -/// ```rust -/// struct S; -/// #[rustfmt::skip] -/// fn foo(p: bool) { // ever-init: -/// // {p, } -/// let a = S; let mut b = S; let c; let d; // {p, a, b, } -/// -/// if p { -/// drop(a); // {p, a, b, } -/// b = S; // {p, a, b, } -/// -/// } else { -/// drop(b); // {p, a, b, } -/// d = S; // {p, a, b, d} -/// -/// } // {p, a, b, d} -/// -/// c = S; // {p, a, b, c, d} -/// } -/// ``` -pub struct EverInitializedPlaces<'a, 'tcx> { - body: &'a Body<'tcx>, - move_data: &'a MoveData<'tcx>, -} - -impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> { - pub fn new(body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>) -> Self { - EverInitializedPlaces { body, move_data } - } -} - -impl<'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'_, 'tcx> { - fn move_data(&self) -> &MoveData<'tcx> { - self.move_data - } -} -impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> { fn update_bits( state: &mut >::Domain, path: MovePathIndex, @@ -331,16 +197,9 @@ impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> { } } -impl<'tcx> MaybeUninitializedPlaces<'_, 'tcx> { - fn update_bits( - state: &mut >::Domain, - path: MovePathIndex, - dfstate: DropFlagState, - ) { - match dfstate { - DropFlagState::Absent => state.gen_(path), - DropFlagState::Present => state.kill(path), - } +impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> { + fn move_data(&self) -> &MoveData<'tcx> { + self.move_data } } @@ -362,7 +221,7 @@ impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> { *state = MaybeReachable::Reachable(MixedBitSet::new_empty(self.move_data().move_paths.len())); drop_flag_effects_for_function_entry(self.body, self.move_data, |path, s| { - assert!(s == DropFlagState::Present); + debug_assert!(s == DropFlagState::Present); state.gen_(path); }); } @@ -482,6 +341,100 @@ impl<'tcx> Analysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> { } } +/// `MaybeUninitializedPlaces` tracks all places that might be +/// uninitialized upon reaching a particular point in the control flow +/// for a function. +/// +/// For example, in code like the following, we have corresponding +/// dataflow information shown in the right-hand comments. +/// +/// ```rust +/// struct S; +/// #[rustfmt::skip] +/// fn foo(p: bool) { // maybe-uninit: +/// // {a, b, c, d} +/// let a = S; let mut b = S; let c; let d; // { c, d} +/// +/// if p { +/// drop(a); // {a, c, d} +/// b = S; // {a, c, d} +/// +/// } else { +/// drop(b); // { b, c, d} +/// d = S; // { b, c } +/// +/// } // {a, b, c, d} +/// +/// c = S; // {a, b, d} +/// } +/// ``` +/// +/// To determine whether a place is *definitely* uninitialized at a +/// particular control-flow point, one can take the set-complement +/// of the data from `MaybeInitializedPlaces` at the corresponding +/// control-flow point. +/// +/// Similarly, at a given `drop` statement, the set-intersection +/// between this data and `MaybeInitializedPlaces` yields the set of +/// places that would require a dynamic drop-flag at that statement. +pub struct MaybeUninitializedPlaces<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + move_data: &'a MoveData<'tcx>, + + mark_inactive_variants_as_uninit: bool, + skip_unreachable_unwind: DenseBitSet, +} + +impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>) -> Self { + MaybeUninitializedPlaces { + tcx, + body, + move_data, + mark_inactive_variants_as_uninit: false, + skip_unreachable_unwind: DenseBitSet::new_empty(body.basic_blocks.len()), + } + } +} + +impl<'tcx> MaybeUninitializedPlaces<'_, 'tcx> { + /// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an + /// enum discriminant. + /// + /// This is correct in a vacuum but is not the default because it causes problems in the borrow + /// checker, where this information gets propagated along `FakeEdge`s. + pub fn mark_inactive_variants_as_uninit(mut self) -> Self { + self.mark_inactive_variants_as_uninit = true; + self + } + + pub fn skipping_unreachable_unwind( + mut self, + unreachable_unwind: DenseBitSet, + ) -> Self { + self.skip_unreachable_unwind = unreachable_unwind; + self + } + + fn update_bits( + state: &mut >::Domain, + path: MovePathIndex, + dfstate: DropFlagState, + ) { + match dfstate { + DropFlagState::Absent => state.gen_(path), + DropFlagState::Present => state.kill(path), + } + } +} + +impl<'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { + fn move_data(&self) -> &MoveData<'tcx> { + self.move_data + } +} + /// There can be many more `MovePathIndex` than there are locals in a MIR body. /// We use a mixed bitset to avoid paying too high a memory footprint. pub type MaybeUninitializedPlacesDomain = MixedBitSet; @@ -504,7 +457,7 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { state.insert_all(); drop_flag_effects_for_function_entry(self.body, self.move_data, |path, s| { - assert!(s == DropFlagState::Present); + debug_assert!(s == DropFlagState::Present); state.remove(path); }); } @@ -609,6 +562,115 @@ impl<'tcx> Analysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { } } +/// `EverInitializedPlaces` tracks all initializations of locals that may have +/// occurred upon reaching a particular point in the control flow for a +/// function, without an intervening `StorageDead`. +/// +/// This dataflow is used to determine if an immutable local variable may +/// be assigned to. +/// +/// For example, in code like the following, we have corresponding +/// dataflow information shown in the right-hand comments. +/// +/// ```rust +/// struct S; +/// #[rustfmt::skip] +/// fn foo(p: bool) { // ever-init: +/// // {p, } +/// let a = S; let mut b = S; let c; let d; // {p, a, b, } +/// +/// if p { +/// drop(a); // {p, a, b, } +/// b = S; // {p, a, b, } +/// +/// } else { +/// drop(b); // {p, a, b, } +/// d = S; // {p, a, b, d} +/// +/// } // {p, a, b, d} +/// +/// c = S; // {p, a, b, c, d} +/// } +/// ``` +pub struct EverInitializedPlaces<'a, 'tcx> { + body: &'a Body<'tcx>, + move_data: &'a MoveData<'tcx>, +} + +impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> { + pub fn new(body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>) -> Self { + EverInitializedPlaces { body, move_data } + } +} + +impl EverInitializedPlaces<'_, '_> { + /// Whether the init of `local` at `init` can reach `target` via a path that doesn't pass + /// through a `StorageDead(local)`. Mirrors the gen/kill structure of `EverInitializedPlaces`. + pub fn init_reaches_location( + body: &Body<'_>, + local: Local, + init: Init, + target: Location, + ) -> bool { + let init_loc = match init.location { + // Arguments are initialized on entry, and `StorageDead` is never emitted for them, so + // they reach every location. + InitLocation::Argument(_) => return true, + InitLocation::Statement(init_loc) => init_loc, + }; + + // Worklist of locations to walk forward from, seeded with the location(s) following `init`. + let mut queue = vec![]; + + let basic_blocks = &body.basic_blocks; + let init_block_data = &basic_blocks[init_loc.block]; + if init_loc.statement_index < init_block_data.statements.len() { + // This case mirrors `apply_primary_statement_effect`. + queue.push(init_loc.successor_within_block()); + } else if init.kind == InitKind::NonPanicPathOnly { + // This case mirrors `apply_call_return_effect`. + let TerminatorEdges::AssignOnReturn { return_, .. } = + init_block_data.terminator().edges() + else { + bug!("`NonPanicPathOnly` should only be seen on terminators with return edges"); + }; + queue.extend(return_.into_iter().map(BasicBlock::start_location)); + } else { + // This case mirrors `apply_primary_terminator_effect`. + queue.extend(init_block_data.terminator().successors().map(BasicBlock::start_location)); + } + + let mut visited = FxIndexSet::default(); + 'outer: while let Some(loc) = queue.pop() { + if !visited.insert(loc) { + continue; + } + // Walk from `loc` to the end of its block, looking for `target` or a kill. + let block_data = &basic_blocks[loc.block]; + for statement_index in loc.statement_index..=block_data.statements.len() { + if target == (Location { block: loc.block, statement_index }) { + return true; + } + if let Some(stmt) = block_data.statements.get(statement_index) + && let StatementKind::StorageDead(dead) = stmt.kind + && dead == local + { + continue 'outer; + } + } + + queue.extend(block_data.terminator().successors().map(BasicBlock::start_location)); + } + false + } +} + +impl<'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'_, 'tcx> { + fn move_data(&self) -> &MoveData<'tcx> { + self.move_data + } +} + pub type EverInitializedPlacesDomain = DenseBitSet; impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> { @@ -692,65 +754,3 @@ impl<'tcx> Analysis<'tcx> for EverInitializedPlaces<'_, 'tcx> { })); } } - -impl EverInitializedPlaces<'_, '_> { - /// Whether the init of `local` at `init` can reach `target` via a path that doesn't pass - /// through a `StorageDead(local)`. Mirrors the gen/kill structure of `EverInitializedPlaces`. - pub fn init_reaches_location( - body: &Body<'_>, - local: Local, - init: Init, - target: Location, - ) -> bool { - let init_loc = match init.location { - // Arguments are initialized on entry, and `StorageDead` is never emitted for them, so - // they reach every location. - InitLocation::Argument(_) => return true, - InitLocation::Statement(init_loc) => init_loc, - }; - - // Worklist of locations to walk forward from, seeded with the location(s) following `init`. - let mut queue = vec![]; - - let basic_blocks = &body.basic_blocks; - let init_block_data = &basic_blocks[init_loc.block]; - if init_loc.statement_index < init_block_data.statements.len() { - // This case mirrors `apply_primary_statement_effect`. - queue.push(init_loc.successor_within_block()); - } else if init.kind == InitKind::NonPanicPathOnly { - // This case mirrors `apply_call_return_effect`. - let TerminatorEdges::AssignOnReturn { return_, .. } = - init_block_data.terminator().edges() - else { - bug!("`NonPanicPathOnly` should only be seen on terminators with return edges"); - }; - queue.extend(return_.into_iter().map(BasicBlock::start_location)); - } else { - // This case mirrors `apply_primary_terminator_effect`. - queue.extend(init_block_data.terminator().successors().map(BasicBlock::start_location)); - } - - let mut visited = FxIndexSet::default(); - 'outer: while let Some(loc) = queue.pop() { - if !visited.insert(loc) { - continue; - } - // Walk from `loc` to the end of its block, looking for `target` or a kill. - let block_data = &basic_blocks[loc.block]; - for statement_index in loc.statement_index..=block_data.statements.len() { - if target == (Location { block: loc.block, statement_index }) { - return true; - } - if let Some(stmt) = block_data.statements.get(statement_index) - && let StatementKind::StorageDead(dead) = stmt.kind - && dead == local - { - continue 'outer; - } - } - - queue.extend(block_data.terminator().successors().map(BasicBlock::start_location)); - } - false - } -} diff --git a/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs b/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs index 6421ba6d7c866..ece5bc634d2bf 100644 --- a/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs +++ b/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs @@ -140,7 +140,7 @@ impl<'tcx> crate::MirPass<'tcx> for AbortUnwindingCalls { super::simplify::remove_dead_blocks(body); } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Implements part of MIR semantics, turning effectively implicit aborts into explicit // ones. PassPolicy::Required diff --git a/compiler/rustc_mir_transform/src/add_call_guards.rs b/compiler/rustc_mir_transform/src/add_call_guards.rs index 55d8493d55c3c..b73cb453752e1 100644 --- a/compiler/rustc_mir_transform/src/add_call_guards.rs +++ b/compiler/rustc_mir_transform/src/add_call_guards.rs @@ -129,7 +129,7 @@ impl<'tcx> crate::MirPass<'tcx> for AddCallGuards { basic_blocks.extend(new_blocks); } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Breaks critical edges so codegen can place edge-specific actions without affecting // other control-flow edges. PassPolicy::Required diff --git a/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs b/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs index 378a5618f0faa..6b74d53801594 100644 --- a/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs +++ b/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs @@ -70,7 +70,7 @@ impl<'tcx> crate::MirPass<'tcx> for AddMovesForPackedDrops { patch.apply(body); } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Implements part of MIR semantics by making implicit packed-drop handling explicit. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/add_subtyping_projections.rs b/compiler/rustc_mir_transform/src/add_subtyping_projections.rs index 08c4b0a0dc5e9..cab927d152196 100644 --- a/compiler/rustc_mir_transform/src/add_subtyping_projections.rs +++ b/compiler/rustc_mir_transform/src/add_subtyping_projections.rs @@ -66,7 +66,7 @@ impl<'tcx> crate::MirPass<'tcx> for Subtyper { checker.patcher.apply(body); } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Later MIR phases expect all subtyping to be explicit. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/check_alignment.rs b/compiler/rustc_mir_transform/src/check_alignment.rs index eeb96d109545e..56fae176360a1 100644 --- a/compiler/rustc_mir_transform/src/check_alignment.rs +++ b/compiler/rustc_mir_transform/src/check_alignment.rs @@ -5,7 +5,6 @@ use rustc_middle::mir::interpret::Scalar; use rustc_middle::mir::visit::PlaceContext; use rustc_middle::mir::*; use rustc_middle::ty::{Ty, TyCtxt}; -use rustc_session::Session; use crate::PassPolicy; use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_pointers}; @@ -13,9 +12,9 @@ use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_poi pub(super) struct CheckAlignment; impl<'tcx> crate::MirPass<'tcx> for CheckAlignment { - fn policy(&self, sess: &Session) -> PassPolicy { + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { // When UB checks are enabled this is part of their semantics, not an optimization. - PassPolicy::optional_non_optimization(sess.ub_checks()) + PassPolicy::optional(ctx.ub_checks()) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/check_enums.rs b/compiler/rustc_mir_transform/src/check_enums.rs index 3233d7022c520..1f5667ebcf116 100644 --- a/compiler/rustc_mir_transform/src/check_enums.rs +++ b/compiler/rustc_mir_transform/src/check_enums.rs @@ -7,7 +7,6 @@ use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; use rustc_middle::ty::layout::PrimitiveExt; use rustc_middle::ty::{self, Ty, TyCtxt, TypingEnv}; -use rustc_session::Session; use tracing::debug; use crate::PassPolicy; @@ -18,9 +17,9 @@ use crate::PassPolicy; pub(super) struct CheckEnums; impl<'tcx> crate::MirPass<'tcx> for CheckEnums { - fn policy(&self, sess: &Session) -> PassPolicy { + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { // When UB checks are enabled this is part of their semantics, not an optimization. - PassPolicy::optional_non_optimization(sess.ub_checks()) + PassPolicy::optional(ctx.ub_checks()) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/check_null.rs b/compiler/rustc_mir_transform/src/check_null.rs index 7b4ccb11c7cf4..ab2c48f0eb1e8 100644 --- a/compiler/rustc_mir_transform/src/check_null.rs +++ b/compiler/rustc_mir_transform/src/check_null.rs @@ -3,7 +3,6 @@ use rustc_index::IndexVec; use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext}; use rustc_middle::mir::*; use rustc_middle::ty::{Ty, TyCtxt}; -use rustc_session::Session; use crate::PassPolicy; use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_pointers}; @@ -11,9 +10,9 @@ use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_poi pub(super) struct CheckNull; impl<'tcx> crate::MirPass<'tcx> for CheckNull { - fn policy(&self, sess: &Session) -> PassPolicy { + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { // When UB checks are enabled this is part of their semantics, not an optimization. - PassPolicy::optional_non_optimization(sess.ub_checks()) + PassPolicy::optional(ctx.ub_checks()) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/cleanup_post_borrowck.rs b/compiler/rustc_mir_transform/src/cleanup_post_borrowck.rs index fa034119fb57c..a27d98c3e31f5 100644 --- a/compiler/rustc_mir_transform/src/cleanup_post_borrowck.rs +++ b/compiler/rustc_mir_transform/src/cleanup_post_borrowck.rs @@ -87,7 +87,7 @@ impl<'tcx> crate::MirPass<'tcx> for CleanupPostBorrowck { } } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Removes administrative MIR instructions that later passes must never see. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/copy_prop.rs b/compiler/rustc_mir_transform/src/copy_prop.rs index 6ba520f0431a2..d22a5c5cc4eef 100644 --- a/compiler/rustc_mir_transform/src/copy_prop.rs +++ b/compiler/rustc_mir_transform/src/copy_prop.rs @@ -22,8 +22,8 @@ use crate::ssa::{MaybeUninitializedLocals, SsaLocals}; pub(super) struct CopyProp; impl<'tcx> crate::MirPass<'tcx> for CopyProp { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() >= 1) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 1) } #[instrument(level = "trace", skip(self, tcx, body))] diff --git a/compiler/rustc_mir_transform/src/coroutine/mod.rs b/compiler/rustc_mir_transform/src/coroutine/mod.rs index c5c65553dae8c..ad91fda4df37d 100644 --- a/compiler/rustc_mir_transform/src/coroutine/mod.rs +++ b/compiler/rustc_mir_transform/src/coroutine/mod.rs @@ -1219,7 +1219,7 @@ impl<'tcx> crate::MirPass<'tcx> for StateTransform { create_coroutine_resume_function(tcx, transform, body, can_return, can_unwind); } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Implements coroutine semantics by lowering the coroutine body to a state machine. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index a9d9a593a0f26..6d87b54a1f4e8 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -25,8 +25,8 @@ mod tests; pub(super) struct InstrumentCoverage; impl<'tcx> crate::MirPass<'tcx> for InstrumentCoverage { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optional_non_optimization(sess.instrument_coverage()) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.instrument_coverage()) } fn run_pass(&self, tcx: TyCtxt<'tcx>, mir_body: &mut mir::Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/cross_crate_inline.rs b/compiler/rustc_mir_transform/src/cross_crate_inline.rs index 13b3304fda8f2..f516b91e1ded5 100644 --- a/compiler/rustc_mir_transform/src/cross_crate_inline.rs +++ b/compiler/rustc_mir_transform/src/cross_crate_inline.rs @@ -83,8 +83,9 @@ fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { // Don't do any inference if codegen optimizations are disabled and also MIR inlining is not // enabled. This ensures that we do inference even if someone only passes -Zinline-mir, // which is less confusing than having to also enable -Copt-level=1. - let inliner_will_run = pm::should_run_pass(tcx, &inline::Inline, pm::Optimizations::Allowed) - || inline::ForceInline::should_run_pass_for_callee(tcx, def_id.to_def_id()); + let inliner_will_run = + pm::should_run_pass(&inline::Inline, &pm::PassCtx::for_body(tcx, def_id.to_def_id())) + || inline::ForceInline::should_run_pass_for_callee(tcx, def_id.to_def_id()); if matches!(tcx.sess.opts.optimize, OptLevel::No) && !inliner_will_run { return false; } diff --git a/compiler/rustc_mir_transform/src/ctfe_limit.rs b/compiler/rustc_mir_transform/src/ctfe_limit.rs index f9334590c1e42..e69b5c4060321 100644 --- a/compiler/rustc_mir_transform/src/ctfe_limit.rs +++ b/compiler/rustc_mir_transform/src/ctfe_limit.rs @@ -39,9 +39,9 @@ impl<'tcx> crate::MirPass<'tcx> for CtfeLimit { } } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // This is part of CTFE diagnostics rather than an optimization. - PassPolicy::optional_non_optimization(true) + PassPolicy::optional(true) } } diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index 2af68a9046e5a..9a94b73748f54 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -36,8 +36,8 @@ const PLACE_LIMIT: usize = 100; pub(super) struct DataflowConstProp; impl<'tcx> crate::MirPass<'tcx> for DataflowConstProp { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() >= 3) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 3) } #[instrument(skip_all level = "debug")] diff --git a/compiler/rustc_mir_transform/src/dead_store_elimination.rs b/compiler/rustc_mir_transform/src/dead_store_elimination.rs index 879335d15dd98..3faceb6be295d 100644 --- a/compiler/rustc_mir_transform/src/dead_store_elimination.rs +++ b/compiler/rustc_mir_transform/src/dead_store_elimination.rs @@ -141,8 +141,8 @@ impl<'tcx> crate::MirPass<'tcx> for DeadStoreElimination { } } - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() >= 2) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 2) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/deref_separator.rs b/compiler/rustc_mir_transform/src/deref_separator.rs index ef5f8931ac600..3ca2a670a2f98 100644 --- a/compiler/rustc_mir_transform/src/deref_separator.rs +++ b/compiler/rustc_mir_transform/src/deref_separator.rs @@ -102,7 +102,7 @@ impl<'tcx> crate::MirPass<'tcx> for Derefer { deref_finder(tcx, body, true); } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Later MIR stages expect derefs to only appear as the first place projection. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/dest_prop.rs b/compiler/rustc_mir_transform/src/dest_prop.rs index 924125404a07a..7f9bba03ff345 100644 --- a/compiler/rustc_mir_transform/src/dest_prop.rs +++ b/compiler/rustc_mir_transform/src/dest_prop.rs @@ -154,8 +154,8 @@ use crate::PassPolicy; pub(super) struct DestinationPropagation; impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() >= 2) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 2) } #[tracing::instrument(level = "trace", skip(self, tcx, body))] diff --git a/compiler/rustc_mir_transform/src/early_otherwise_branch.rs b/compiler/rustc_mir_transform/src/early_otherwise_branch.rs index 28c7e7facc578..b13a4fa16e21d 100644 --- a/compiler/rustc_mir_transform/src/early_otherwise_branch.rs +++ b/compiler/rustc_mir_transform/src/early_otherwise_branch.rs @@ -95,8 +95,8 @@ use crate::patch::MirPatch; pub(super) struct EarlyOtherwiseBranch; impl<'tcx> crate::MirPass<'tcx> for EarlyOtherwiseBranch { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() >= 2) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 2) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/elaborate_box_derefs.rs b/compiler/rustc_mir_transform/src/elaborate_box_derefs.rs index 6ce39aac6a0db..fecd3865b3b32 100644 --- a/compiler/rustc_mir_transform/src/elaborate_box_derefs.rs +++ b/compiler/rustc_mir_transform/src/elaborate_box_derefs.rs @@ -150,7 +150,7 @@ impl<'tcx> crate::MirPass<'tcx> for ElaborateBoxDerefs { } } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Implements Box dereference semantics so backends and Miri do not have to handle them. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/elaborate_drops.rs b/compiler/rustc_mir_transform/src/elaborate_drops.rs index 84c9c044ae6a6..a19ae86542d25 100644 --- a/compiler/rustc_mir_transform/src/elaborate_drops.rs +++ b/compiler/rustc_mir_transform/src/elaborate_drops.rs @@ -88,7 +88,7 @@ impl<'tcx> crate::MirPass<'tcx> for ElaborateDrops { elaborate_patch.apply(body); } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Implements MIR drop semantics. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/erase_deref_temps.rs b/compiler/rustc_mir_transform/src/erase_deref_temps.rs index a0c3dd930ccf6..20dde292d1413 100644 --- a/compiler/rustc_mir_transform/src/erase_deref_temps.rs +++ b/compiler/rustc_mir_transform/src/erase_deref_temps.rs @@ -39,7 +39,7 @@ impl<'tcx> crate::MirPass<'tcx> for EraseDerefTemps { EraseDerefTempsVisitor { tcx }.visit_body_preserves_cfg(body); } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Later MIR stages assume that CopyForDeref is gone. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 9d751a7cc5bd0..81534b057641f 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -128,8 +128,8 @@ use crate::ssa::{MaybeUninitializedLocals, SsaLocals}; pub(super) struct GVN; impl<'tcx> crate::MirPass<'tcx> for GVN { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() >= 2) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 2) } #[instrument(level = "trace", skip(self, tcx, body))] diff --git a/compiler/rustc_mir_transform/src/impossible_clauses.rs b/compiler/rustc_mir_transform/src/impossible_clauses.rs index bb6644716bf05..f802de87ca11f 100644 --- a/compiler/rustc_mir_transform/src/impossible_clauses.rs +++ b/compiler/rustc_mir_transform/src/impossible_clauses.rs @@ -114,8 +114,8 @@ impl<'tcx> MirPass<'tcx> for ImpossibleClauses { } } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // This can only replace code proven unreachable with immediate UB, so it cannot remove UB. - PassPolicy::optional_non_optimization(true) + PassPolicy::optional(true) } } diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index ae20d21ea665f..47f2fe51f5487 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -18,7 +18,7 @@ use rustc_middle::mir::*; use rustc_middle::ty::{ self, Instance, InstanceKind, ShimKind, Ty, TyCtxt, TypeFlags, TypeVisitableExt, Unnormalized, }; -use rustc_session::config::{DebugInfo, OptLevel}; +use rustc_session::config::DebugInfo; use rustc_span::Spanned; use tracing::{debug, instrument, trace, trace_span}; @@ -45,18 +45,16 @@ struct CallSite<'tcx> { pub struct Inline; impl<'tcx> crate::MirPass<'tcx> for Inline { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - let enabled_by_default = - sess.opts.unstable_opts.inline_mir.unwrap_or_else(|| match sess.mir_opt_level() { + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + match ctx.opts.unstable_opts.inline_mir { + Some(enabled) => PassPolicy::optional(enabled), + None => PassPolicy::optional(match ctx.mir_opt_level() { 0 | 1 => false, - 2 => { - (sess.opts.optimize == OptLevel::More - || sess.opts.optimize == OptLevel::Aggressive) - && sess.opts.incremental == None - } + // Inlining reduces incremental effectiveness + 2 => ctx.opts.incremental.is_none(), _ => true, - }); - PassPolicy::optimization(enabled_by_default) + }), + } } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { @@ -78,7 +76,7 @@ impl ForceInline { } impl<'tcx> crate::MirPass<'tcx> for ForceInline { - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Forced inlining is part of MIR semantics. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/inline/cycle.rs b/compiler/rustc_mir_transform/src/inline/cycle.rs index b974cb656379d..17151661d9d8a 100644 --- a/compiler/rustc_mir_transform/src/inline/cycle.rs +++ b/compiler/rustc_mir_transform/src/inline/cycle.rs @@ -51,8 +51,10 @@ fn should_recurse<'tcx>(tcx: TyCtxt<'tcx>, callee: ty::Instance<'tcx>) -> bool { } } - crate::pm::should_run_pass(tcx, &crate::inline::Inline, crate::pm::Optimizations::Allowed) - || crate::inline::ForceInline::should_run_pass_for_callee(tcx, callee.def.def_id()) + crate::pm::should_run_pass( + &crate::inline::Inline, + &crate::pm::PassCtx::for_body(tcx, callee.def_id()), + ) || crate::inline::ForceInline::should_run_pass_for_callee(tcx, callee.def.def_id()) } #[instrument( diff --git a/compiler/rustc_mir_transform/src/instsimplify.rs b/compiler/rustc_mir_transform/src/instsimplify.rs index 8f8b4aec7cbbc..71b27ebde8459 100644 --- a/compiler/rustc_mir_transform/src/instsimplify.rs +++ b/compiler/rustc_mir_transform/src/instsimplify.rs @@ -27,8 +27,8 @@ impl<'tcx> crate::MirPass<'tcx> for InstSimplify { } } - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() > 0) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 1) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/jump_threading.rs b/compiler/rustc_mir_transform/src/jump_threading.rs index b704c39044ac9..69b91eac31b06 100644 --- a/compiler/rustc_mir_transform/src/jump_threading.rs +++ b/compiler/rustc_mir_transform/src/jump_threading.rs @@ -76,18 +76,13 @@ pub(super) struct JumpThreading; const MAX_COST: u8 = 100; impl<'tcx> crate::MirPass<'tcx> for JumpThreading { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - let enabled_by_default = if sess.target.is_like_gpu { - // Jump threading can duplicate calls in control-flow. - // This leads to incorrect code when done for so called "convergent" operations on GPU - // targets, similar to how inline assembly cannot be duplicated on all targets. - // Conservatively prevent this by disabling the pass. - // See also issue #137086. - false - } else { - sess.mir_opt_level() >= 2 - }; - PassPolicy::optimization(enabled_by_default) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + // Jump threading can duplicate calls in control-flow. + // This leads to incorrect code when done for so called "convergent" operations on GPU + // targets, similar to how inline assembly cannot be duplicated on all targets. + // Conservatively prevent this by disabling the pass. + // See also issue #137086. + PassPolicy::optional(ctx.mir_opt_level() >= 2 && !ctx.target.is_like_gpu) } #[instrument(skip_all level = "debug")] diff --git a/compiler/rustc_mir_transform/src/large_enums.rs b/compiler/rustc_mir_transform/src/large_enums.rs index 43cd4198b2621..1fdf827c14044 100644 --- a/compiler/rustc_mir_transform/src/large_enums.rs +++ b/compiler/rustc_mir_transform/src/large_enums.rs @@ -5,7 +5,6 @@ use rustc_middle::mir::interpret::AllocId; use rustc_middle::mir::*; use rustc_middle::ty::util::IntTypeExt; use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt}; -use rustc_session::Session; use crate::PassPolicy; use crate::patch::MirPatch; @@ -32,13 +31,11 @@ pub(super) struct EnumSizeOpt { } impl<'tcx> crate::MirPass<'tcx> for EnumSizeOpt { - fn policy(&self, sess: &Session) -> PassPolicy { + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { // There are some differences in behavior on wasm and ARM that are not properly // understood, so we conservatively treat this optimization as unsound: // https://github.com/rust-lang/rust/issues/154413 - PassPolicy::optimization( - sess.opts.unstable_opts.unsound_mir_opts && sess.mir_opt_level() >= 3, - ) + PassPolicy::optional(ctx.mir_opt_level() >= 3 && ctx.opts.unstable_opts.unsound_mir_opts) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index d2dd77c986318..6fdccad1505a5 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -35,7 +35,7 @@ mod pass_manager; use std::sync::LazyLock; -use pass_manager::{self as pm, Lint, MirLint, MirPass, PassPolicy, WithMinOptLevel}; +use pass_manager::{self as pm, Lint, MirLint, MirPass, PassCtx, PassPolicy, WithMinOptLevel}; mod check_pointers; mod cost_checker; @@ -549,7 +549,7 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> & let is_fn_like = tcx.def_kind(def).is_fn_like(); if is_fn_like { // Do not compute the mir call graph without said call graph actually being used. - if pm::should_run_pass(tcx, &inline::Inline, pm::Optimizations::Allowed) + if pm::should_run_pass(&inline::Inline, &pm::PassCtx::for_body(tcx, def.to_def_id())) || inline::ForceInline::should_run_pass_for_callee(tcx, def.to_def_id()) { tcx.ensure_done().mir_inliner_callees(ty::InstanceKind::Item(def.to_def_id())); diff --git a/compiler/rustc_mir_transform/src/lint_and_remove_uninhabited.rs b/compiler/rustc_mir_transform/src/lint_and_remove_uninhabited.rs index a69b69ef94d59..767ab0972b800 100644 --- a/compiler/rustc_mir_transform/src/lint_and_remove_uninhabited.rs +++ b/compiler/rustc_mir_transform/src/lint_and_remove_uninhabited.rs @@ -83,7 +83,7 @@ impl<'tcx> crate::MirPass<'tcx> for LintAndRemoveUninhabited { } } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Removing visibly uninhabited return edges determines the control flow seen by MIR checks. // Cannot remove UB: removing the return edge would *introduce* UB if the call actually returned. PassPolicy::Required diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index 6126560949c79..1d5d02e4d88ee 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -339,7 +339,7 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics { } } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Implements intrinsic semantics by lowering intrinsic calls to ordinary MIR operations. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/lower_slice_len.rs b/compiler/rustc_mir_transform/src/lower_slice_len.rs index b157cf3d53d40..1dfba7d6c5200 100644 --- a/compiler/rustc_mir_transform/src/lower_slice_len.rs +++ b/compiler/rustc_mir_transform/src/lower_slice_len.rs @@ -10,8 +10,8 @@ use crate::PassPolicy; pub(super) struct LowerSliceLenCalls; impl<'tcx> crate::MirPass<'tcx> for LowerSliceLenCalls { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() > 0) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 1) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/match_branches.rs b/compiler/rustc_mir_transform/src/match_branches.rs index 36eed06fcadda..894f209f9b473 100644 --- a/compiler/rustc_mir_transform/src/match_branches.rs +++ b/compiler/rustc_mir_transform/src/match_branches.rs @@ -14,9 +14,9 @@ use crate::unreachable_prop::remove_successors_from_switch; pub(super) struct MatchBranchSimplification; impl<'tcx> crate::MirPass<'tcx> for MatchBranchSimplification { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { // Enable only under -Zmir-opt-level=2 as this can make programs less debuggable. - PassPolicy::optimization(sess.mir_opt_level() >= 2) + PassPolicy::optional(ctx.mir_opt_level() >= 2) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/mentioned_items.rs b/compiler/rustc_mir_transform/src/mentioned_items.rs index 89146f5ce8548..e3cf9f246e669 100644 --- a/compiler/rustc_mir_transform/src/mentioned_items.rs +++ b/compiler/rustc_mir_transform/src/mentioned_items.rs @@ -2,7 +2,6 @@ use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::{self, Location, MentionedItem}; use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::{self, TyCtxt}; -use rustc_session::Session; use rustc_span::Spanned; use crate::PassPolicy; @@ -16,7 +15,7 @@ struct MentionedItemsVisitor<'a, 'tcx> { } impl<'tcx> crate::MirPass<'tcx> for MentionedItems { - fn policy(&self, _sess: &Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // If this pass is skipped the collector assume that nothing got mentioned! We could // potentially skip it in opt-level 0 if we are sure that opt-level will never *remove* uses // of anything, but that still seems fragile. Furthermore, even debug builds use level 1, so diff --git a/compiler/rustc_mir_transform/src/multiple_return_terminators.rs b/compiler/rustc_mir_transform/src/multiple_return_terminators.rs index 8f2709e71dfcf..280a57e474237 100644 --- a/compiler/rustc_mir_transform/src/multiple_return_terminators.rs +++ b/compiler/rustc_mir_transform/src/multiple_return_terminators.rs @@ -10,8 +10,8 @@ use crate::{PassPolicy, simplify}; pub(super) struct MultipleReturnTerminators; impl<'tcx> crate::MirPass<'tcx> for MultipleReturnTerminators { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() >= 4) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 4) } fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/pass_manager.rs b/compiler/rustc_mir_transform/src/pass_manager.rs index 798f69d3cc883..6ebf4637adc3b 100644 --- a/compiler/rustc_mir_transform/src/pass_manager.rs +++ b/compiler/rustc_mir_transform/src/pass_manager.rs @@ -3,9 +3,12 @@ use std::collections::hash_map::Entry; use std::sync::atomic::Ordering; use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; +use rustc_hir::def_id::DefId; +use rustc_middle::bug; use rustc_middle::mir::{Body, MirDumper, MirPhase, RuntimePhase}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; +use rustc_session::config::OptLevel; use tracing::trace; use crate::lint::lint_body; @@ -89,39 +92,16 @@ pub(crate) enum PassPolicy { Required, /// An optional pass that may be configured by `-Zmir-enable-passes`. Optional { - /// Whether this pass should be enabled by default in this session in the absence of - /// an explicit `-Zmir-enable-passes` or `#[optimize(none)]`. - generally_enabled: bool, - /// Whether this is an optimization pass. `#[optimize(none)]` only disables optimization - /// passes. - /// A pass may be optional without being an optimization pass, - /// e.g. if it just adds extra debug checks that one can turn off. - optimization: bool, + /// Whether this pass should be enabled in the absence of an explicit + /// `-Zmir-enable-passes` override. + enabled_by_default: bool, }, } impl PassPolicy { - fn and_enabled(self, enabled: bool) -> Self { - match self { - PassPolicy::Required => PassPolicy::Required, - PassPolicy::Optional { generally_enabled: enabled_by_default, optimization } => { - PassPolicy::Optional { - generally_enabled: enabled_by_default && enabled, - optimization, - } - } - } - } - - /// Create a [`PassPolicy::Optional`] that is not an optimization, - /// enabled by default under the given condition. - pub(crate) fn optional_non_optimization(condition: bool) -> Self { - Self::Optional { generally_enabled: condition, optimization: false } - } - - /// Create a [`PassPolicy::Optional`] optimization, enabled by default under the given condition. - pub(crate) fn optimization(condition: bool) -> Self { - Self::Optional { generally_enabled: condition, optimization: true } + /// Create a [`PassPolicy::Optional`] enabled by default under the given condition. + pub(crate) fn optional(enabled_by_default: bool) -> Self { + Self::Optional { enabled_by_default } } } @@ -138,7 +118,7 @@ pub(super) trait MirPass<'tcx> { } /// Describes how this pass is enabled and which mechanisms may disable it. - fn policy(&self, sess: &Session) -> PassPolicy; + fn policy(&self, ctx: &PassCtx<'_>) -> PassPolicy; fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>); @@ -147,6 +127,40 @@ pub(super) trait MirPass<'tcx> { } } +#[derive(Copy, Clone)] +pub(super) struct PassCtx<'sess> { + /// Prefer [`Self::mir_opt_level`] to [`Session::mir_opt_level`] to account for overrides. + session: &'sess Session, + /// The MIR optimization level for this body; may be overridden by `#[optimize]`. + body_mir_opt_level: usize, +} + +impl<'sess> PassCtx<'sess> { + pub(super) fn for_body(tcx: TyCtxt<'sess>, def_id: DefId) -> Self { + let body_mir_opt_level = if tcx.def_kind(def_id).has_codegen_attrs() + && tcx.codegen_fn_attrs(def_id).optimize.do_not_optimize() + { + OptLevel::No.mir_opt_level() + } else { + tcx.sess.mir_opt_level() + }; + Self { session: tcx.sess, body_mir_opt_level } + } + + /// The effective MIR optimization level for this body, including `#[optimize]` overrides. + pub(super) fn mir_opt_level(&self) -> usize { + self.body_mir_opt_level + } +} + +impl std::ops::Deref for PassCtx<'_> { + type Target = Session; + + fn deref(&self) -> &Self::Target { + self.session + } +} + /// Just like `MirPass`, except it cannot mutate `Body`, and MIR dumping is /// disabled (via the `Lint` adapter). pub(super) trait MirLint<'tcx> { @@ -177,12 +191,12 @@ where false } - fn policy(&self, _sess: &Session) -> PassPolicy { - PassPolicy::optional_non_optimization(true) + fn policy(&self, _ctx: &PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(true) } } -pub(super) struct WithMinOptLevel(pub u32, pub T); +pub(super) struct WithMinOptLevel(pub usize, pub T); impl<'tcx, T> MirPass<'tcx> for WithMinOptLevel where @@ -196,22 +210,17 @@ where self.1.run_pass(tcx, body) } - fn policy(&self, sess: &Session) -> PassPolicy { - self.1.policy(sess).and_enabled(sess.mir_opt_level() >= self.0 as usize) + fn policy(&self, ctx: &PassCtx<'_>) -> PassPolicy { + let policy = self.1.policy(ctx); + match policy { + PassPolicy::Required => bug!("required pass cannot be gated by an opt level"), + PassPolicy::Optional { enabled_by_default } => PassPolicy::Optional { + enabled_by_default: enabled_by_default && ctx.mir_opt_level() >= self.0, + }, + } } } -/// Whether to allow [optimization passes]. -/// -/// [optimization passes]: PassPolicy::Optional::optimization -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub(crate) enum Optimizations { - /// The current function has `#[optimize(none)]`. - Suppressed, - /// Normal optimizations may run. - Allowed, -} - /// Run the sequence of passes without validating the MIR after each pass. The MIR is still /// validated at the end. pub(super) fn run_passes_no_validate<'tcx>( @@ -233,18 +242,13 @@ pub(super) fn run_passes<'tcx>( run_passes_inner(tcx, body, passes, phase_change, true); } -pub(super) fn should_run_pass<'tcx, P>( - tcx: TyCtxt<'tcx>, - pass: &P, - optimizations: Optimizations, -) -> bool +pub(super) fn should_run_pass<'tcx, P>(pass: &P, ctx: &PassCtx<'_>) -> bool where P: MirPass<'tcx> + ?Sized, { let name = pass.name(); let pass_override = || { - tcx.sess - .opts + ctx.opts .unstable_opts .mir_enable_passes .iter() @@ -252,9 +256,9 @@ where .find_map(|(name_, polarity)| if name == name_ { Some(*polarity) } else { None }) }; - match pass.policy(tcx.sess) { + match pass.policy(ctx) { PassPolicy::Required => true, - PassPolicy::Optional { generally_enabled: enabled_by_default, optimization } => { + PassPolicy::Optional { enabled_by_default } => { if let Some(o) = pass_override() { trace!( pass = %name, @@ -262,9 +266,6 @@ where if o { "Running" } else { "Not running" } ); o - } else if optimization && optimizations == Optimizations::Suppressed { - trace!(pass = %name, "Not running as requested by `#[optimize(none)]`"); - false } else { enabled_by_default } @@ -319,23 +320,16 @@ fn run_passes_inner<'tcx>( let validate = validate_each & tcx.sess.opts.unstable_opts.validate_mir; let lint = tcx.sess.opts.unstable_opts.lint_mir; - let def_id = body.source.def_id(); - let optimizations = if tcx.def_kind(def_id).has_codegen_attrs() - && tcx.codegen_fn_attrs(def_id).optimize.do_not_optimize() - { - Optimizations::Suppressed - } else { - Optimizations::Allowed - }; + let ctx = PassCtx::for_body(tcx, body.source.def_id()); for pass in passes { let pass_name = pass.name(); - if !should_run_pass(tcx, *pass, optimizations) { + if !should_run_pass(*pass, &ctx) { continue; }; - if is_optimization_stage(body, phase_change, optimizations) + if is_optimization_stage(body, phase_change) && let Some(limit) = &tcx.sess.opts.unstable_opts.mir_opt_bisect_limit && limited_by_opt_bisect( tcx, @@ -419,13 +413,8 @@ pub(super) fn dump_mir_for_phase_change<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tc } } -fn is_optimization_stage( - body: &Body<'_>, - phase_change: Option, - optimizations: Optimizations, -) -> bool { - optimizations == Optimizations::Allowed - && body.phase == MirPhase::Runtime(RuntimePhase::PostCleanup) +fn is_optimization_stage(body: &Body<'_>, phase_change: Option) -> bool { + body.phase == MirPhase::Runtime(RuntimePhase::PostCleanup) && phase_change == Some(MirPhase::Runtime(RuntimePhase::Optimized)) } diff --git a/compiler/rustc_mir_transform/src/post_analysis_normalize.rs b/compiler/rustc_mir_transform/src/post_analysis_normalize.rs index 532e1097b5546..78f97c2eb1448 100644 --- a/compiler/rustc_mir_transform/src/post_analysis_normalize.rs +++ b/compiler/rustc_mir_transform/src/post_analysis_normalize.rs @@ -18,7 +18,7 @@ impl<'tcx> crate::MirPass<'tcx> for PostAnalysisNormalize { PostAnalysisNormalizeVisitor { tcx, typing_env }.visit_body_preserves_cfg(body); } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Reveals opaque types and normalizes MIR while transitioning to the runtime dialect. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/prettify.rs b/compiler/rustc_mir_transform/src/prettify.rs index ea1988c0b5c5a..f86ad4d49e769 100644 --- a/compiler/rustc_mir_transform/src/prettify.rs +++ b/compiler/rustc_mir_transform/src/prettify.rs @@ -9,7 +9,6 @@ use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; -use rustc_session::Session; use crate::PassPolicy; @@ -20,8 +19,8 @@ use crate::PassPolicy; pub(super) struct ReorderBasicBlocks; impl<'tcx> crate::MirPass<'tcx> for ReorderBasicBlocks { - fn policy(&self, _session: &Session) -> PassPolicy { - PassPolicy::optional_non_optimization(false) + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(false) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { @@ -48,8 +47,8 @@ impl<'tcx> crate::MirPass<'tcx> for ReorderBasicBlocks { pub(super) struct ReorderLocals; impl<'tcx> crate::MirPass<'tcx> for ReorderLocals { - fn policy(&self, _session: &Session) -> PassPolicy { - PassPolicy::optional_non_optimization(false) + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(false) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index ae2028f1c62ea..ccd9969c3692e 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -64,7 +64,7 @@ impl<'tcx> crate::MirPass<'tcx> for PromoteTemps<'tcx> { self.promoted_fragments.set(promoted); } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Implements promotion by extracting eligible values into separate constant MIR bodies. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/ref_prop.rs b/compiler/rustc_mir_transform/src/ref_prop.rs index 1db9c36bee184..566f1851ff49e 100644 --- a/compiler/rustc_mir_transform/src/ref_prop.rs +++ b/compiler/rustc_mir_transform/src/ref_prop.rs @@ -73,8 +73,8 @@ use crate::ssa::{SsaLocals, StorageLiveLocals}; pub(super) struct ReferencePropagation; impl<'tcx> crate::MirPass<'tcx> for ReferencePropagation { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() >= 2) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 2) } #[instrument(level = "trace", skip(self, tcx, body))] diff --git a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs index 7d55756a9a694..c25404ed7ab99 100644 --- a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs +++ b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs @@ -12,10 +12,10 @@ use crate::patch::MirPatch; pub(super) struct RemoveNoopLandingPads; impl<'tcx> crate::MirPass<'tcx> for RemoveNoopLandingPads { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - // FIXME: isn't this an optimization? Or is the LLVM code so terrible we want this even with + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + // FIXME: Should this really run on opt-level 0? Or is the LLVM code so terrible we want this even with // "no" optimizations? - PassPolicy::optional_non_optimization(sess.panic_strategy().unwinds()) + PassPolicy::optional(ctx.panic_strategy().unwinds()) } #[instrument(level = "debug", skip(self, _tcx, body))] diff --git a/compiler/rustc_mir_transform/src/remove_place_mention.rs b/compiler/rustc_mir_transform/src/remove_place_mention.rs index bec46896a8d55..5123b19e1c0a1 100644 --- a/compiler/rustc_mir_transform/src/remove_place_mention.rs +++ b/compiler/rustc_mir_transform/src/remove_place_mention.rs @@ -9,8 +9,8 @@ use crate::PassPolicy; pub(super) struct RemovePlaceMention; impl<'tcx> crate::MirPass<'tcx> for RemovePlaceMention { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optional_non_optimization(!sess.opts.unstable_opts.mir_preserve_ub) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(!ctx.opts.unstable_opts.mir_preserve_ub) } fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/remove_storage_markers.rs b/compiler/rustc_mir_transform/src/remove_storage_markers.rs index 47fcbf2420164..833d281366adc 100644 --- a/compiler/rustc_mir_transform/src/remove_storage_markers.rs +++ b/compiler/rustc_mir_transform/src/remove_storage_markers.rs @@ -9,10 +9,8 @@ use crate::PassPolicy; pub(super) struct RemoveStorageMarkers; impl<'tcx> crate::MirPass<'tcx> for RemoveStorageMarkers { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optional_non_optimization( - sess.mir_opt_level() > 0 && !sess.emit_lifetime_markers(), - ) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 1 && !ctx.emit_lifetime_markers()) } fn run_pass(&self, _tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/remove_uninit_drops.rs b/compiler/rustc_mir_transform/src/remove_uninit_drops.rs index 19ac267b62836..c5f462f714dd8 100644 --- a/compiler/rustc_mir_transform/src/remove_uninit_drops.rs +++ b/compiler/rustc_mir_transform/src/remove_uninit_drops.rs @@ -66,7 +66,7 @@ impl<'tcx> crate::MirPass<'tcx> for RemoveUninitDrops { } } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { // Const checking relies on uninitialized drops being removed before drop elaboration. PassPolicy::Required } diff --git a/compiler/rustc_mir_transform/src/remove_unneeded_drops.rs b/compiler/rustc_mir_transform/src/remove_unneeded_drops.rs index f36423a8c8e73..45a5c5626f98a 100644 --- a/compiler/rustc_mir_transform/src/remove_unneeded_drops.rs +++ b/compiler/rustc_mir_transform/src/remove_unneeded_drops.rs @@ -40,7 +40,7 @@ impl<'tcx> crate::MirPass<'tcx> for RemoveUnneededDrops { } } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optional_non_optimization(true) + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(true) } } diff --git a/compiler/rustc_mir_transform/src/remove_zsts.rs b/compiler/rustc_mir_transform/src/remove_zsts.rs index 6dddf4838a6c3..09379fc252072 100644 --- a/compiler/rustc_mir_transform/src/remove_zsts.rs +++ b/compiler/rustc_mir_transform/src/remove_zsts.rs @@ -9,8 +9,8 @@ use crate::PassPolicy; pub(super) struct RemoveZsts; impl<'tcx> crate::MirPass<'tcx> for RemoveZsts { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optional_non_optimization(sess.mir_opt_level() > 0) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 1) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/simplify.rs b/compiler/rustc_mir_transform/src/simplify.rs index 47d31f4e0d04b..32504fc72fecd 100644 --- a/compiler/rustc_mir_transform/src/simplify.rs +++ b/compiler/rustc_mir_transform/src/simplify.rs @@ -95,8 +95,8 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyCfg { self.name() } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(true) + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(true) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { @@ -429,8 +429,8 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyLocals { } } - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() > 0) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 1) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/simplify_branches.rs b/compiler/rustc_mir_transform/src/simplify_branches.rs index ceea038444e4d..58a3f1b571e59 100644 --- a/compiler/rustc_mir_transform/src/simplify_branches.rs +++ b/compiler/rustc_mir_transform/src/simplify_branches.rs @@ -23,8 +23,8 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyConstCondition { } } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(true) + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(true) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/simplify_comparison_integral.rs b/compiler/rustc_mir_transform/src/simplify_comparison_integral.rs index a0ce3932a0952..68a8920c2d014 100644 --- a/compiler/rustc_mir_transform/src/simplify_comparison_integral.rs +++ b/compiler/rustc_mir_transform/src/simplify_comparison_integral.rs @@ -27,8 +27,8 @@ use crate::ssa::SsaLocals; pub(super) struct SimplifyComparisonIntegral; impl<'tcx> crate::MirPass<'tcx> for SimplifyComparisonIntegral { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() > 1) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 2) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/single_use_consts.rs b/compiler/rustc_mir_transform/src/single_use_consts.rs index 21ba434acc6ad..6a3d4ae416425 100644 --- a/compiler/rustc_mir_transform/src/single_use_consts.rs +++ b/compiler/rustc_mir_transform/src/single_use_consts.rs @@ -25,8 +25,8 @@ use crate::strip_debuginfo::drop_invalid_debuginfos; pub(super) struct SingleUseConsts; impl<'tcx> crate::MirPass<'tcx> for SingleUseConsts { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() > 0) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 1) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/sroa.rs b/compiler/rustc_mir_transform/src/sroa.rs index c115889205878..8ae130753be1d 100644 --- a/compiler/rustc_mir_transform/src/sroa.rs +++ b/compiler/rustc_mir_transform/src/sroa.rs @@ -16,8 +16,8 @@ use crate::patch::MirPatch; pub(super) struct ScalarReplacementOfAggregates; impl<'tcx> crate::MirPass<'tcx> for ScalarReplacementOfAggregates { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() >= 2) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 2) } #[instrument(level = "debug", skip(self, tcx, body))] diff --git a/compiler/rustc_mir_transform/src/ssa_range_prop.rs b/compiler/rustc_mir_transform/src/ssa_range_prop.rs index 0492398fd7bcb..79c6868bf87c2 100644 --- a/compiler/rustc_mir_transform/src/ssa_range_prop.rs +++ b/compiler/rustc_mir_transform/src/ssa_range_prop.rs @@ -25,8 +25,8 @@ use crate::ssa::SsaLocals; pub(super) struct SsaRangePropagation; impl<'tcx> crate::MirPass<'tcx> for SsaRangePropagation { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() > 1) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 2) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/strip_debuginfo.rs b/compiler/rustc_mir_transform/src/strip_debuginfo.rs index 7535ab166c757..46c53b53e49c9 100644 --- a/compiler/rustc_mir_transform/src/strip_debuginfo.rs +++ b/compiler/rustc_mir_transform/src/strip_debuginfo.rs @@ -12,10 +12,8 @@ use crate::PassPolicy; pub(super) struct StripDebugInfo; impl<'tcx> crate::MirPass<'tcx> for StripDebugInfo { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optional_non_optimization( - sess.opts.unstable_opts.mir_strip_debuginfo != MirStripDebugInfo::None, - ) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.opts.unstable_opts.mir_strip_debuginfo != MirStripDebugInfo::None) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/unreachable_enum_branching.rs b/compiler/rustc_mir_transform/src/unreachable_enum_branching.rs index 0bb6d87379caa..a7e35e5c4bf6b 100644 --- a/compiler/rustc_mir_transform/src/unreachable_enum_branching.rs +++ b/compiler/rustc_mir_transform/src/unreachable_enum_branching.rs @@ -78,8 +78,8 @@ fn variant_discriminants<'tcx>( } impl<'tcx> crate::MirPass<'tcx> for UnreachableEnumBranching { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optimization(sess.mir_opt_level() > 0) + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(ctx.mir_opt_level() >= 1) } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/unreachable_prop.rs b/compiler/rustc_mir_transform/src/unreachable_prop.rs index 3c9ae691c885c..84d6c39396248 100644 --- a/compiler/rustc_mir_transform/src/unreachable_prop.rs +++ b/compiler/rustc_mir_transform/src/unreachable_prop.rs @@ -15,9 +15,9 @@ use crate::patch::MirPatch; pub(super) struct UnreachablePropagation; impl crate::MirPass<'_> for UnreachablePropagation { - fn policy(&self, sess: &rustc_session::Session) -> PassPolicy { + fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { // Enable only under -Zmir-opt-level=2 as this can make programs less debuggable. - PassPolicy::optimization(sess.mir_opt_level() >= 2) + PassPolicy::optional(ctx.mir_opt_level() >= 2) } fn run_pass<'tcx>(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index b9c55439f0597..65c6adc5430e4 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -98,8 +98,8 @@ impl<'tcx> crate::MirPass<'tcx> for Validator { } } - fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy { - PassPolicy::optional_non_optimization(true) + fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy { + PassPolicy::optional(true) } } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index d9099cadaece8..1fe16d294433c 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -30,7 +30,6 @@ use rustc_hir::{ }; use rustc_macros::Diagnostic; use rustc_middle::hir::nested_filter; -use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault; use rustc_middle::query::Providers; use rustc_middle::traits::ObligationCause; use rustc_middle::ty::error::{ExpectedFound, TypeError}; @@ -195,9 +194,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::Deprecated { span: attr_span, .. } => { self.check_deprecated(hir_id, *attr_span, target) } - AttributeKind::RustcDumpObjectLifetimeDefaults => { - self.check_dump_object_lifetime_defaults(hir_id); - } AttributeKind::Naked(..) => self.check_naked(hir_id, target), AttributeKind::NonExhaustive(attr_span) => { self.check_non_exhaustive(*attr_span, span, target, item) @@ -337,6 +333,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcDumpInferredOutlives => (), AttributeKind::RustcDumpItemBounds => (), AttributeKind::RustcDumpLayout(..) => (), + AttributeKind::RustcDumpObjectLifetimeDefaults => (), AttributeKind::RustcDumpSymbolName(..) => (), AttributeKind::RustcDumpUserArgs => (), AttributeKind::RustcDumpVariances => (), @@ -785,23 +782,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - /// Debugging aid for the `object_lifetime_default` query. - fn check_dump_object_lifetime_defaults(&self, hir_id: HirId) { - let tcx = self.tcx; - let Some(owner_id) = hir_id.as_owner() else { return }; - for param in &tcx.generics_of(owner_id.def_id).own_params { - let ty::GenericParamDefKind::Type { .. } = param.kind else { continue }; - let default = tcx.object_lifetime_default(param.def_id); - let repr = match default { - ObjectLifetimeDefault::Empty => "Empty".to_owned(), - ObjectLifetimeDefault::Static => "'static".to_owned(), - ObjectLifetimeDefault::Param(def_id) => tcx.item_name(def_id).to_string(), - ObjectLifetimeDefault::Ambiguous => "Ambiguous".to_owned(), - }; - tcx.dcx().span_err(tcx.def_span(param.def_id), repr); - } - } - /// Checks if the `#[non_exhaustive]` attribute on an `item` is valid. fn check_non_exhaustive( &self, diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 266a5adb2f29c..eed84ce69169c 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -108,6 +108,17 @@ pub enum OptLevel { SizeMin, } +impl OptLevel { + /// Infers an MIR opt-level (if not otherwise specified) from general opt-level. + /// Produces `1` at opt-level 0, and `2` at all other levels. + pub fn mir_opt_level(&self) -> usize { + match self { + OptLevel::No => 1, + _ => 2, + } + } +} + /// This is what the `LtoCli` values get mapped to after resolving defaults and /// and taking other command line options into account. /// diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 99ff5d4b691ae..ece4bfab89029 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -37,7 +37,7 @@ pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, Varian use crate::config::{ self, BranchProtection, Cfg, CheckCfg, CoverageLevel, CoverageOptions, CrateType, DebugInfo, ErrorOutputType, FunctionReturn, Input, InstrumentCoverage, InstrumentMcount, NATIVE_CPU, - OptLevel, OutFileName, OutputType, PAuthKey, PointerAuthOption, SwitchWithOptPath, + OutFileName, OutputType, PAuthKey, PointerAuthOption, SwitchWithOptPath, }; use crate::filesearch::FileSearch; use crate::lint::LintId; @@ -829,10 +829,7 @@ impl Session { } pub fn mir_opt_level(&self) -> usize { - self.opts - .unstable_opts - .mir_opt_level - .unwrap_or_else(|| if self.opts.optimize != OptLevel::No { 2 } else { 1 }) + self.opts.unstable_opts.mir_opt_level.unwrap_or_else(|| self.opts.optimize.mir_opt_level()) } /// Calculates the flavor of LTO to use for this compilation. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b67f0633fb772..693cdadec99fe 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1471,6 +1471,7 @@ symbols! { of, off, offload, + offload_get_num_devices, offload_kernel, offset, offset_of, diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index f5fab1e8614b5..2316fc4318918 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3808,13 +3808,15 @@ pub const fn autodiff(f: F, df: G, args: T) -> /// - `f`: The kernel function to offload. /// - `workgroup_dim`: A 3D size specifying the number of workgroups to launch. /// - `thread_dim`: A 3D size specifying the number of threads per workgroup. +/// - `dyn_cache`: The amount of dynamic shared memory to request for the kernel. +/// - `device_id`: The device to offload to. Use `-1` to select the default device. /// - `args`: A tuple of arguments forwarded to `f`. /// /// Example usage (pseudocode): /// /// ```rust,ignore (pseudocode) /// fn kernel(x: *mut [f64; 128]) { -/// core::intrinsics::offload(kernel_1, [256, 1, 1], [32, 1, 1], (x,)) +/// core::intrinsics::offload(kernel_1, [256, 1, 1], [32, 1, 1], 0, -1, (x,)) /// } /// /// #[cfg(target_os = "linux")] @@ -3838,9 +3840,20 @@ pub const fn offload( workgroup_dim: [u32; 3], thread_dim: [u32; 3], dyn_cache: u32, + device_id: i32, args: T, ) -> R; +/// Returns the number of offload devices available on the system. +/// +/// Use this to discover which `device_id` values are valid to pass to +/// [`offload`]. Devices are numbered from `0` to the returned value minus one. +/// +/// Returns `0` if no offloading devices are present. +#[rustc_nounwind] +#[rustc_intrinsic] +pub const fn offload_get_num_devices() -> i32; + /// Inform Miri that a given pointer definitely has a certain alignment. #[cfg(miri)] #[rustc_allow_const_fn_unstable(const_eval_select)] diff --git a/library/core/src/offload.md b/library/core/src/offload.md index 985a93a4294fa..726d0c7af1928 100644 --- a/library/core/src/offload.md +++ b/library/core/src/offload.md @@ -21,7 +21,8 @@ fn kernel(x: *mut [f64; 256]) { ``` To launch an offloaded kernel, use the `offload!` macro. It lets you specify the kernel, the -workgroup and thread dimensions, and the arguments to forward to the device. +workgroup and thread dimensions, the device to offload to, and the arguments to forward to the +device. ```rust,ignore (optional component) let mut x = [0.0f64; 256]; diff --git a/library/core/src/offload/mod.rs b/library/core/src/offload/mod.rs index 17ff74f0bbfbb..3d85621361209 100644 --- a/library/core/src/offload/mod.rs +++ b/library/core/src/offload/mod.rs @@ -19,6 +19,9 @@ pub use crate::offload; /// Defaults to `[1, 1, 1]`. /// - `dyn_cache`: The amount of dynamic shared memory, in bytes, to allocate for the kernel. /// Defaults to `0`. +/// - `device`: The index of the device to offload to. Must be `>= 0`. If omitted, the +/// default device is used. Use [`crate::intrinsics::offload_get_num_devices`] to discover +/// which device ids are valid. /// /// Each argument may only be specified once. /// @@ -43,61 +46,82 @@ macro_rules! offload { workgroup_dim = ([1, 1, 1]); thread_dim = ([1, 1, 1]); dyn_cache = (0); + device = NONE; args = NONE ) }; - (@munch [kernel = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = NONE; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; args = $a:tt) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = (SOME $val); workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; args = $a) + (@munch [kernel = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = NONE; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { + $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = (SOME $val); workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; device = $device; args = $a) }; - (@munch [kernel = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = (SOME $old:expr); workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; args = $a:tt) => { + (@munch [kernel = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = (SOME $old:expr); workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { compile_error!("duplicate field `kernel`") }; - (@munch [workgroup_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = ([1, 1, 1]); thread_dim = $t:tt; dyn_cache = $d:tt; args = $a:tt) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = (SOME $val); thread_dim = $t; dyn_cache = $d; args = $a) + (@munch [workgroup_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = ([1, 1, 1]); thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { + $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = (SOME $val); thread_dim = $t; dyn_cache = $d; device = $device; args = $a) }; - (@munch [workgroup_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = (SOME $old:expr); thread_dim = $t:tt; dyn_cache = $d:tt; args = $a:tt) => { + (@munch [workgroup_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = (SOME $old:expr); thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { compile_error!("duplicate field `workgroup_dim`") }; - (@munch [thread_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = ([1, 1, 1]); dyn_cache = $d:tt; args = $a:tt) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = (SOME $val); dyn_cache = $d; args = $a) + (@munch [thread_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = ([1, 1, 1]); dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { + $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = (SOME $val); dyn_cache = $d; device = $device; args = $a) }; - (@munch [thread_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = (SOME $old:expr); dyn_cache = $d:tt; args = $a:tt) => { + (@munch [thread_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = (SOME $old:expr); dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { compile_error!("duplicate field `thread_dim`") }; - (@munch [dyn_cache = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = (0); args = $a:tt) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = (SOME $val); args = $a) + (@munch [dyn_cache = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = (0); device = $device:tt; args = $a:tt) => { + $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = (SOME $val); device = $device; args = $a) }; - (@munch [dyn_cache = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = (SOME $old:expr); args = $a:tt) => { + (@munch [dyn_cache = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = (SOME $old:expr); device = $device:tt; args = $a:tt) => { compile_error!("duplicate field `dyn_cache`") }; - (@munch [args = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; args = NONE) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; args = (SOME $val)) + (@munch [device = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = NONE; args = $a:tt) => { + $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; device = (SOME $val); args = $a) }; - (@munch [args = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; args = (SOME $old:expr)) => { + (@munch [device = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = (SOME $old:expr); args = $a:tt) => { + compile_error!("duplicate field `device`") + }; + (@munch [args = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = NONE) => { + $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; device = $device; args = (SOME $val)) + }; + (@munch [args = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = (SOME $old:expr)) => { compile_error!("duplicate field `args`") }; - (@munch [$invalid:ident = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; args = $a:tt) => { + (@munch [$invalid:ident = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { compile_error!(concat!("unknown field `", stringify!($invalid), "`")) }; - (@munch []; kernel = NONE; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; args = $a:tt) => { + (@munch []; kernel = NONE; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { compile_error!("missing `kernel`") }; - (@munch []; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; args = NONE) => { + (@munch []; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = NONE) => { compile_error!("missing `args`") }; - (@munch []; kernel = (SOME $kernel:expr); workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; args = (SOME $args:expr)) => { + (@munch []; kernel = (SOME $kernel:expr); workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = (SOME $args:expr)) => { $crate::intrinsics::offload::<_, _, ()>( $kernel, $crate::offload!(@value $w), $crate::offload!(@value $t), $crate::offload!(@value $d), + $crate::offload!(@device $device), $args, ) }; (@value (SOME $val:expr)) => { $val }; (@value ($val:expr)) => { $val }; + + // if `device` is omitted (`NONE), we use the OpenMP default device (`-1`) + (@device NONE) => { -1 }; + (@device (SOME $val:expr)) => { { + const { $crate::assert!($val >= 0, "offload device must be non-negative; omit `device` to use the default device") }; + let device: i32 = $val; + $crate::assert!( + device < $crate::intrinsics::offload_get_num_devices(), + "offload device {} is not available", + device, + ); + device + } }; } diff --git a/library/std/src/os/windows/process.rs b/library/std/src/os/windows/process.rs index 41dcb70c59c9f..3949907888dd6 100644 --- a/library/std/src/os/windows/process.rs +++ b/library/std/src/os/windows/process.rs @@ -174,6 +174,15 @@ pub impl(self) trait CommandExt { #[stable(feature = "windows_process_extensions", since = "1.16.0")] fn creation_flags(&mut self, flags: u32) -> &mut process::Command; + /// Places the child process on the desktop named `desktop` by setting the + /// `lpDesktop` field of the [STARTUPINFO][1] passed to `CreateProcess`. + /// + /// The name may be a desktop or a `window-station\desktop` path. + /// + /// [1]: + #[unstable(feature = "windows_process_extensions_desktop", issue = "158852")] + fn desktop>(&mut self, desktop: S) -> &mut process::Command; + /// Sets the field `wShowWindow` of [STARTUPINFO][1] that is passed to `CreateProcess`. /// Allowed values are the ones listed in /// @@ -383,6 +392,11 @@ impl CommandExt for process::Command { self } + fn desktop>(&mut self, desktop: S) -> &mut process::Command { + self.as_inner_mut().desktop(desktop.as_ref()); + self + } + fn show_window(&mut self, cmd_show: u16) -> &mut process::Command { self.as_inner_mut().show_window(Some(cmd_show)); self diff --git a/library/std/src/sys/pal/unix/fuchsia.rs b/library/std/src/sys/pal/unix/fuchsia.rs index c118dee624764..f9dfd52a610bb 100644 --- a/library/std/src/sys/pal/unix/fuchsia.rs +++ b/library/std/src/sys/pal/unix/fuchsia.rs @@ -9,12 +9,13 @@ use crate::io; // Time // ////////// -pub type zx_time_t = i64; +pub type zx_instant_mono_t = i64; -pub const ZX_TIME_INFINITE: zx_time_t = i64::MAX; +pub const ZX_TIME_INFINITE: zx_instant_mono_t = i64::MAX; unsafe extern "C" { - pub safe fn zx_clock_get_monotonic() -> zx_time_t; + pub safe fn zx_clock_get_monotonic() -> zx_instant_mono_t; + pub safe fn zx_nanosleep(deadline: zx_instant_mono_t) -> zx_status_t; } ///////////// @@ -62,7 +63,7 @@ unsafe extern "C" { pub fn zx_object_wait_one( handle: zx_handle_t, signals: zx_signals_t, - timeout: zx_time_t, + deadline: zx_instant_mono_t, pending: *mut zx_signals_t, ) -> zx_status_t; @@ -70,7 +71,7 @@ unsafe extern "C" { value_ptr: *const zx_futex_t, current_value: zx_futex_t, new_futex_owner: zx_handle_t, - deadline: zx_time_t, + deadline: zx_instant_mono_t, ) -> zx_status_t; pub fn zx_futex_wake(value_ptr: *const zx_futex_t, wake_count: u32) -> zx_status_t; pub fn zx_futex_wake_single_owner(value_ptr: *const zx_futex_t) -> zx_status_t; @@ -117,7 +118,7 @@ pub type zx_info_process_flags_t = u32; #[repr(C)] pub struct zx_info_process_t { pub return_code: i64, - pub start_time: zx_time_t, + pub start_time: zx_instant_mono_t, pub flags: zx_info_process_flags_t, pub reserved1: u32, } diff --git a/library/std/src/sys/process/windows.rs b/library/std/src/sys/process/windows.rs index 0cff0fb7945c4..f095f829c4c67 100644 --- a/library/std/src/sys/process/windows.rs +++ b/library/std/src/sys/process/windows.rs @@ -162,6 +162,7 @@ pub struct Command { startupinfo_untrusted_source: bool, startupinfo_force_feedback: Option, inherit_handles: bool, + desktop: Option>, } pub enum Stdio { @@ -191,6 +192,7 @@ impl Command { startupinfo_untrusted_source: false, startupinfo_force_feedback: None, inherit_handles: true, + desktop: None, } } @@ -215,6 +217,7 @@ impl Command { pub fn creation_flags(&mut self, flags: u32) { self.flags = flags; } + pub fn show_window(&mut self, cmd_show: Option) { self.show_window = cmd_show; } @@ -239,6 +242,10 @@ impl Command { self.startupinfo_force_feedback = enabled; } + pub fn desktop(&mut self, desktop: &OsStr) { + self.desktop = Some(desktop.encode_wide().chain([0]).collect()); + } + pub fn get_program(&self) -> &OsStr { &self.program } @@ -391,6 +398,10 @@ impl Command { None => {} } + if let Some(desktop) = &mut self.desktop { + si.lpDesktop = desktop.as_mut_ptr(); + } + let si_ptr: *mut c::STARTUPINFOW; let mut si_ex; diff --git a/library/std/src/sys/thread/mod.rs b/library/std/src/sys/thread/mod.rs index fb5d65150395d..dfbfc2bc8e0c2 100644 --- a/library/std/src/sys/thread/mod.rs +++ b/library/std/src/sys/thread/mod.rs @@ -72,6 +72,7 @@ cfg_select! { target_os = "vxworks", target_os = "wasi", target_vendor = "apple", + target_os = "fuchsia", ))] pub use unix::sleep_until; pub use unix::{ @@ -147,7 +148,8 @@ cfg_select! { target_os = "wasi", target_vendor = "apple", target_os = "motor", - target_os = "vexos" + target_os = "vexos", + target_os = "fuchsia", )))] pub fn sleep_until(deadline: crate::time::Instant) { use crate::time::Instant; diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index abcdfe89476e1..12b644bce45e9 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -668,6 +668,26 @@ pub fn sleep(dur: Duration) { pub fn sleep_until(deadline: crate::time::Instant) { use crate::time::Instant; + let timespec = deadline.into_inner().into_timespec(); + if timespec.tv_sec < 0 { + // `clock_nanosleep` fails with EINVAL if + // > The tp argument to clock_settime() is outside the range for the + // > given clock ID. + // + // This specification allows *any* clock range, which means we'd + // theoretically have to detect whether the time point is in the + // future (and block indefinitely) or the past (and return immediately) + // when encountering `EINVAL`. But since all existing implementations + // interpret this as saying that negative `tv_sec` values are unsupported, + // we can just test that and return – given that POSIX specifies that + // `CLOCK_MONOTONIC` measures the time "since an unspecified amount + // in the past" negative values are definitely in the past. If you + // observe any platform returning `EINVAL` for more cases, please + // file a bug; we'd need to add logic handling `EINVAL` when it + // occurs. + return; + } + #[cfg(all( target_os = "linux", target_env = "gnu", @@ -690,7 +710,7 @@ pub fn sleep_until(deadline: crate::time::Instant) { } if let Some(clock_nanosleep) = __clock_nanosleep_time64.get() { - let ts = deadline.into_inner().into_timespec().to_timespec64(); + let ts = timespec.to_timespec64(); loop { let r = unsafe { clock_nanosleep( @@ -718,7 +738,7 @@ pub fn sleep_until(deadline: crate::time::Instant) { } } - let Some(ts) = deadline.into_inner().into_timespec().to_timespec() else { + let Some(ts) = timespec.to_timespec() else { // The deadline is further in the future then can be passed to // clock_nanosleep. We have to use Self::sleep instead. This might // happen on 32 bit platforms, especially closer to 2038. @@ -796,6 +816,16 @@ pub fn sleep_until(deadline: crate::time::Instant) { } } +#[cfg(target_os = "fuchsia")] +pub fn sleep_until(deadline: crate::time::Instant) { + use crate::sys::pal::fuchsia::{zx_cvt, zx_nanosleep}; + + let deadline = deadline.into_inner().into_deadline(); + if let Err(error) = zx_cvt(zx_nanosleep(deadline)) { + panic!("zx_nanosleep failed: {error}"); + } +} + pub fn yield_now() { let ret = unsafe { libc::sched_yield() }; debug_assert_eq!(ret, 0); diff --git a/library/std/src/sys/time/unix.rs b/library/std/src/sys/time/unix.rs index 944cb552cad9e..d84256df0cd53 100644 --- a/library/std/src/sys/time/unix.rs +++ b/library/std/src/sys/time/unix.rs @@ -123,6 +123,11 @@ impl Instant { // 126 bits. Some((nanos * u128::from(timebase.denom)).div_ceil(u128::from(timebase.numer))) } + + #[cfg(target_os = "fuchsia")] + pub fn into_deadline(self) -> crate::sys::pal::fuchsia::zx_instant_mono_t { + self.t.tv_sec.saturating_mul(1_000_000_000).saturating_add(self.t.tv_nsec.as_inner().into()) + } } impl AsInner for Instant { diff --git a/library/std/src/thread/functions.rs b/library/std/src/thread/functions.rs index 355a00c2a95ad..19e830facd5ec 100644 --- a/library/std/src/thread/functions.rs +++ b/library/std/src/thread/functions.rs @@ -295,9 +295,10 @@ pub fn sleep(dur: Duration) { /// Puts the current thread to sleep until the specified deadline has passed. /// -/// The thread may still be asleep after the deadline specified due to -/// scheduling specifics or platform-dependent functionality. It will never -/// wake before. +/// If the deadline has already passed at the time this function is called, it +/// will return immediately. Note that the thread may still be asleep after the +/// deadline specified due to scheduling specifics or platform-dependent +/// functionality. It will never wake before. /// /// This function is blocking, and should not be used in `async` functions. /// @@ -313,19 +314,21 @@ pub fn sleep(dur: Duration) { /// /// | Platform | System call | /// |-----------|----------------------------------------------------------------------| -/// | Linux | [clock_nanosleep] (Monotonic Clock) | -/// | BSD except OpenBSD | [clock_nanosleep] (Monotonic Clock) | -/// | Android | [clock_nanosleep] (Monotonic Clock) | -/// | Solaris | [clock_nanosleep] (Monotonic Clock) | -/// | Illumos | [clock_nanosleep] (Monotonic Clock) | -/// | Dragonfly | [clock_nanosleep] (Monotonic Clock) | -/// | Hurd | [clock_nanosleep] (Monotonic Clock) | -/// | Vxworks | [clock_nanosleep] (Monotonic Clock) | +/// | Linux | [`clock_nanosleep`] (Monotonic Clock) | +/// | BSD except OpenBSD | [`clock_nanosleep`] (Monotonic Clock) | +/// | Android | [`clock_nanosleep`] (Monotonic Clock) | +/// | Solaris | [`clock_nanosleep`] (Monotonic Clock) | +/// | Illumos | [`clock_nanosleep`] (Monotonic Clock) | +/// | Dragonfly | [`clock_nanosleep`] (Monotonic Clock) | +/// | Hurd | [`clock_nanosleep`] (Monotonic Clock) | +/// | Vxworks | [`clock_nanosleep`] (Monotonic Clock) | /// | Apple | `mach_wait_until` | +/// | Fuchsia | [`zx_nanosleep`] | /// | Other | `sleep_until` uses [`sleep`] and does not issue a syscall itself | /// /// [currently]: crate::io#platform-specific-behavior -/// [clock_nanosleep]: https://linux.die.net/man/3/clock_nanosleep +/// [`clock_nanosleep`]: https://linux.die.net/man/3/clock_nanosleep +/// [`zx_nanosleep`]: https://fuchsia.dev/reference/syscalls/nanosleep /// /// **Disclaimer:** These system calls might change over time. /// diff --git a/library/std/src/thread/tests.rs b/library/std/src/thread/tests.rs index 78b6f7c35e8db..e88ca92218dc8 100644 --- a/library/std/src/thread/tests.rs +++ b/library/std/src/thread/tests.rs @@ -333,6 +333,15 @@ fn sleep_ms_smoke() { thread::sleep(Duration::from_millis(2)); } +#[test] +fn sleep_until_elapsed() { + // UNIX's `clock_nanosleep` doesn't like timeouts that are too far back. + // Test that `sleep_until` returns immediately instead of panicking. + // Going 10 years back should be enough to trigger any errors. + let earlier = Instant::now() - Duration::from_secs(10 * 365 * 24 * 3600); + thread::sleep_until(earlier); +} + #[test] fn test_size_of_option_thread_id() { assert_eq!(size_of::>(), size_of::()); diff --git a/library/unwind/src/types.rs b/library/unwind/src/types.rs index f7aa2554f6ee8..d299d8e7e2252 100644 --- a/library/unwind/src/types.rs +++ b/library/unwind/src/types.rs @@ -43,6 +43,12 @@ pub const unwinder_private_data_size: usize = }; #[repr(C)] +// The Itanium C++ ABI requires this type to have "double-word" alignment, +// which libunwind and libgcc interpret as the maximum alignment of any +// scalar type on the current target. +#[cfg_attr(target_pointer_width = "16", repr(align(4)))] +#[cfg_attr(target_pointer_width = "32", repr(align(8)))] +#[cfg_attr(target_pointer_width = "64", repr(align(16)))] pub struct _Unwind_Exception { pub exception_class: _Unwind_Exception_Class, pub exception_cleanup: _Unwind_Exception_Cleanup_Fn, diff --git a/src/bootstrap/src/cli_main.rs b/src/bootstrap/src/cli_main.rs index c11e1478f4d42..8a74b2e598283 100644 --- a/src/bootstrap/src/cli_main.rs +++ b/src/bootstrap/src/cli_main.rs @@ -16,11 +16,12 @@ use std::{env, process}; use crate::core::builder::StepStack; use crate::core::config::flags::{Flags, Subcommand}; use crate::core::config::{ChangeId, Config}; +use crate::core::session::Build; +use crate::debug; use crate::utils::change_tracker::{ CONFIG_CHANGE_HISTORY, find_recent_config_change_ids, human_readable_changes, }; use crate::utils::helpers::t; -use crate::{Build, debug}; fn is_tracing_enabled() -> bool { cfg!(feature = "tracing") diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 265999711eb87..4a75cdbb1562f 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -3,7 +3,6 @@ use std::fs; use std::path::{Path, PathBuf}; -use crate::Mode; use crate::core::backend::CodegenBackendKind; use crate::core::build_steps::compile::{ ArtifactKeepMode, add_to_sysroot, run_cargo, rustc_cargo, rustc_cargo_env, std_cargo, @@ -20,6 +19,7 @@ use crate::core::builder::{ }; use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; +use crate::core::session::Mode; use crate::utils::build_stamp::{self, BuildStamp}; use crate::utils::helpers::t; diff --git a/src/bootstrap/src/core/build_steps/clean.rs b/src/bootstrap/src/core/build_steps/clean.rs index a5c7398d11302..23f12bbb63e72 100644 --- a/src/bootstrap/src/core/build_steps/clean.rs +++ b/src/bootstrap/src/core/build_steps/clean.rs @@ -14,9 +14,9 @@ use crate::core::builder::{ }; use crate::core::compiler::Compiler; use crate::core::config::flags::Subcommand; +use crate::core::session::{Build, Mode}; use crate::utils::build_stamp::BuildStamp; use crate::utils::helpers::t; -use crate::{Build, Mode}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct CleanAll {} diff --git a/src/bootstrap/src/core/build_steps/clippy.rs b/src/bootstrap/src/core/build_steps/clippy.rs index 2ca775f92d683..dc3e3efb80ee5 100644 --- a/src/bootstrap/src/core/build_steps/clippy.rs +++ b/src/bootstrap/src/core/build_steps/clippy.rs @@ -14,7 +14,6 @@ //! (as usual) a massive undertaking/refactoring. use super::tool::{SourceType, prepare_tool_cargo}; -use crate::Mode; use crate::core::build_steps::check::{CompilerForCheck, prepare_compiler_for_check}; use crate::core::build_steps::compile::{ ArtifactKeepMode, run_cargo, rustc_cargo, std_cargo, std_crates_for_make_run, @@ -26,6 +25,7 @@ use crate::core::builder::{ use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; use crate::core::config::flags::Subcommand; +use crate::core::session::Mode; use crate::utils::build_stamp::{self, BuildStamp}; use crate::utils::helpers; diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index ddb1fde9f9ee2..55a8170061724 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -33,13 +33,14 @@ use crate::core::config::toml::target::DefaultLinuxLinkerOverride; use crate::core::config::{ Allocator, CompilerBuiltins, DebuginfoLevel, LlvmLibunwind, RustcLto, TargetSelection, }; +use crate::core::session::{CLang, DependencyType, FileType, GitRepo, Mode}; use crate::utils::build_stamp; use crate::utils::build_stamp::BuildStamp; use crate::utils::exec::command; use crate::utils::helpers::{ self, exe, get_clang_cl_resource_dir, is_debug_info, is_dylib, symlink_dir, t, up_to_date, }; -use crate::{CLang, DependencyType, FileType, GitRepo, Mode, debug, trace}; +use crate::{debug, trace}; /// Build a standard library for the given `target` using the given `build_compiler`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index b5118281fab74..5c86d117767c8 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -38,6 +38,8 @@ use crate::core::builder::{ }; use crate::core::compiler::Compiler; use crate::core::config::{GccCiMode, TargetSelection}; +use crate::core::session::{DependencyType, FileType, Mode}; +use crate::trace; use crate::utils::build_stamp::{self, BuildStamp}; use crate::utils::channel::{self, Info}; use crate::utils::exec::{BootstrapCommand, command}; @@ -45,7 +47,6 @@ use crate::utils::helpers::{ exe, is_dylib, move_file, t, target_supports_cranelift_backend, timeit, }; use crate::utils::tarball::{GeneratedTarball, OverlayKind, Tarball}; -use crate::{DependencyType, FileType, Mode, trace}; pub(crate) const LLVM_TOOLS: &[&str] = &[ "llvm-cov", // used to generate coverage report diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index adff654fe88e8..b80a0b0ba27c8 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -21,8 +21,8 @@ use crate::core::builder::{ }; use crate::core::compiler::Compiler; use crate::core::config::{Config, TargetSelection}; +use crate::core::session::{FileType, Mode}; use crate::utils::helpers::{submodule_path_of, symlink_dir, t, up_to_date}; -use crate::{FileType, Mode}; macro_rules! book { ($($name:ident, $path:expr, $book_name:expr, $lang:expr ;)+) => { diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 323cce1da51ab..5d188bcd25570 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -21,12 +21,13 @@ use crate::core::builder::{ Builder, CommandLineStep, Kind, RunConfig, ShouldRun, Step, StepMetadata, }; use crate::core::config::{Config, LlvmCiMode, LlvmPgoGenerationMode, TargetSelection}; +use crate::core::session::{CLang, GitRepo}; +use crate::trace; use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash}; use crate::utils::exec::command; use crate::utils::helpers::{ self, exe, get_clang_cl_resource_dir, libdir, t, unhashed_basename, up_to_date, }; -use crate::{CLang, GitRepo, trace}; /// Path where a file containing the link type (dynamic or static) is stored in the LLVM CI tarball. pub const LLVM_CI_LINK_TYPE_PATH: &str = "link-type.txt"; diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs index 82a132d0b5288..243b09acaa308 100644 --- a/src/bootstrap/src/core/build_steps/run.rs +++ b/src/bootstrap/src/core/build_steps/run.rs @@ -8,7 +8,6 @@ use std::path::PathBuf; use build_helper::git::get_git_untracked_files; use clap_complete::{Generator, shells}; -use crate::Mode; use crate::core::build_steps::dist::distdir; use crate::core::build_steps::test; use crate::core::build_steps::tool::{self, RustcPrivateCompilers, SourceType, Tool}; @@ -16,6 +15,7 @@ use crate::core::build_steps::vendor::{VENDOR_DIR, Vendor, default_paths_to_vend use crate::core::builder::{Builder, CommandLineStep, Kind, RunConfig, ShouldRun, StepMetadata}; use crate::core::config::TargetSelection; use crate::core::config::flags::{get_completion, top_level_help}; +use crate::core::session::Mode; use crate::utils::exec::command; use crate::utils::helpers::{self, t}; diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index a4eefe51c420e..be3dc2954086c 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -38,6 +38,7 @@ use crate::core::builder::{ use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; use crate::core::config::flags::{Subcommand, get_completion, top_level_help}; +use crate::core::session::{CLang, GitRepo, Mode}; use crate::core::{android, debuggers}; use crate::utils::build_stamp::{self, BuildStamp}; use crate::utils::exec::{BootstrapCommand, command}; @@ -47,7 +48,6 @@ use crate::utils::helpers::{ target_supports_cranelift_backend, up_to_date, }; use crate::utils::render_tests::{add_flags_and_try_run_tests, try_run_tests}; -use crate::{CLang, GitRepo, Mode}; mod compiletest; pub mod failed_tests; diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs index b67d1b1bd49e7..4e94a422fd153 100644 --- a/src/bootstrap/src/core/build_steps/tool.rs +++ b/src/bootstrap/src/core/build_steps/tool.rs @@ -23,9 +23,9 @@ use crate::core::builder::{ }; use crate::core::compiler::Compiler; use crate::core::config::{Allocator, DebuginfoLevel, RustcLto, TargetSelection}; +use crate::core::session::{FileType, Mode}; use crate::utils::exec::{BootstrapCommand, command}; use crate::utils::helpers::{self, add_dylib_path, exe, t}; -use crate::{FileType, Mode}; #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum SourceType { diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 67abbe4faf2a1..93ec4a11f9083 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -11,10 +11,10 @@ use crate::core::compiler::Compiler; use crate::core::config::flags::{Color, Subcommand}; use crate::core::config::toml::pgo::PgoConfig; use crate::core::config::{CompressDebuginfo, Config, DryRun, SplitDebuginfo, TargetSelection}; +use crate::core::session::{CLang, GitRepo, Mode, RemapScheme}; use crate::utils::build_stamp; use crate::utils::exec::{BootstrapCommand, command}; use crate::utils::helpers::{self, LldThreads, check_cfg_arg, envify, linker_flags, t}; -use crate::{CLang, GitRepo, Mode, RemapScheme}; /// Extra `--check-cfg` to add when building the compiler or tools /// (Mode restriction, config name, config values (if any)) diff --git a/src/bootstrap/src/core/builder/cli_paths/tests.rs b/src/bootstrap/src/core/builder/cli_paths/tests.rs index e18a274c75f49..3a92bf37bdf0a 100644 --- a/src/bootstrap/src/core/builder/cli_paths/tests.rs +++ b/src/bootstrap/src/core/builder/cli_paths/tests.rs @@ -2,8 +2,8 @@ use std::collections::{BTreeSet, HashSet}; use std::path::PathBuf; use std::sync::{Arc, Mutex}; -use crate::Build; use crate::core::builder::{Builder, CommandLineStepDescription}; +use crate::core::session::Build; use crate::utils::tests::TestCtx; fn render_steps_for_cli_args(args_str: &str) -> String { diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 6f30f3b56f8b8..98fceeae9df5c 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -25,12 +25,13 @@ use crate::core::compiler::Compiler; use crate::core::config::flags::Subcommand; use crate::core::config::{DryRun, TargetSelection}; use crate::core::metadata::Crate; +use crate::core::session::Build; +use crate::trace; use crate::utils::build_stamp::BuildStamp; use crate::utils::cache::Cache; use crate::utils::exec::{BootstrapCommand, ExecutionContext, command}; use crate::utils::helpers::{self, LldThreads, add_dylib_path, exe, libdir, linker_args, t}; use crate::utils::tracing::format_location; -use crate::{Build, trace}; mod cargo; mod cli_paths; @@ -40,7 +41,7 @@ mod tests; /// Builds and performs different [`Self::kind`]s of stuff and actions, taking /// into account build configuration from e.g. bootstrap.toml. -pub struct Builder<'a> { +pub(crate) struct Builder<'a> { /// Build configuration from e.g. bootstrap.toml. pub build: &'a Build, diff --git a/src/bootstrap/src/core/compiler.rs b/src/bootstrap/src/core/compiler.rs index a57c60465f24c..5602e8ffd1efd 100644 --- a/src/bootstrap/src/core/compiler.rs +++ b/src/bootstrap/src/core/compiler.rs @@ -1,7 +1,7 @@ use std::hash::{Hash, Hasher}; -use crate::Build; use crate::core::config::TargetSelection; +use crate::core::session::Build; /// A structure representing a Rust compiler. /// diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 8ca1c74b929e3..f74def6a61d8b 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -1845,7 +1845,7 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to /// /// This *does not* update the submodule if `bootstrap.toml` explicitly says /// not to, or if we're not in a git repository (like a plain source - /// tarball). Typically [`crate::Build::require_submodule`] should be + /// tarball). Typically [`crate::core::session::Build::require_submodule`] should be /// used instead to provide a nice error to the user if the submodule is /// missing. #[cfg_attr( diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index 56c2541161cec..da479251c68ab 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -10,7 +10,6 @@ use clap_complete::Generator; #[cfg(feature = "tracing")] use tracing::instrument; -use crate::Build; use crate::core::backend::CodegenBackendKind; use crate::core::build_steps::perf::PerfArgs; use crate::core::build_steps::setup::Profile; @@ -18,6 +17,7 @@ use crate::core::build_steps::test::TestTarget; use crate::core::builder::{Builder, Kind}; use crate::core::config::Config; use crate::core::config::target_selection::{TargetSelectionList, target_selection_list}; +use crate::core::session::Build; use crate::utils::helpers; #[derive(Copy, Clone, Default, Debug, ValueEnum)] diff --git a/src/bootstrap/src/core/metadata.rs b/src/bootstrap/src/core/metadata.rs index a3b52e1071d24..5e88277008971 100644 --- a/src/bootstrap/src/core/metadata.rs +++ b/src/bootstrap/src/core/metadata.rs @@ -11,7 +11,7 @@ use std::path::PathBuf; use serde_derive::Deserialize; -use crate::Build; +use crate::core::session::Build; use crate::utils::exec::command; use crate::utils::helpers::t; diff --git a/src/bootstrap/src/core/mod.rs b/src/bootstrap/src/core/mod.rs index d6db6c701cc35..c130051a8c7c4 100644 --- a/src/bootstrap/src/core/mod.rs +++ b/src/bootstrap/src/core/mod.rs @@ -8,3 +8,4 @@ pub(crate) mod debuggers; pub(crate) mod download; pub(crate) mod metadata; pub(crate) mod sanity; +pub(crate) mod session; diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index 234e2c13de59e..148f2ac1212c0 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -14,11 +14,11 @@ use std::ffi::{OsStr, OsString}; use std::path::PathBuf; use std::{env, fs}; -use crate::Build; use crate::core::build_steps::tool; use crate::core::builder::Builder; use crate::core::config::flags::Subcommand; use crate::core::config::{CompilerBuiltins, DebuggerPath, Target}; +use crate::core::session::Build; use crate::utils::exec::command; use crate::utils::helpers::{self, t}; diff --git a/src/bootstrap/src/core/session.rs b/src/bootstrap/src/core/session.rs new file mode 100644 index 0000000000000..3e6668258c641 --- /dev/null +++ b/src/bootstrap/src/core/session.rs @@ -0,0 +1,1878 @@ +use std::cell::Cell; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::Display; +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; +use std::time::{Instant, SystemTime}; +use std::{env, fs, io, str}; + +use build_helper::ci::gha; +use termcolor::{ColorChoice, StandardStream, WriteColor}; +#[cfg(feature = "tracing")] +use tracing::{instrument, span}; + +use crate::core::build_steps::format::InternalRustfmt; +use crate::core::build_steps::test::TestTarget; +use crate::core::build_steps::vendor::VENDOR_DIR; +use crate::core::builder::{Builder, Kind}; +use crate::core::compiler::Compiler; +use crate::core::config::flags::{self, Subcommand}; +use crate::core::config::{BootstrapOverrideLld, Config, DryRun, LlvmLibunwind, TargetSelection}; +use crate::core::metadata::Crate; +#[cfg(feature = "tracing")] +use crate::trace_io; +use crate::utils::build_stamp::BuildStamp; +use crate::utils::channel::GitInfo; +use crate::utils::exec::{BootstrapCommand, ExecutionContext, command}; +use crate::utils::helpers::{ + self, dir_is_empty, exe, libdir, set_file_times, split_debuginfo, symlink_dir, t, +}; +use crate::{debug, trace}; + +pub(crate) enum GitRepo { + Rustc, + Llvm, +} + +/// Global configuration for the build system. +/// +/// This structure transitively contains all configuration for the build system. +/// All filesystem-encoded configuration is in `config`, all flags are in +/// `flags`, and then parsed or probed information is listed in the keys below. +/// +/// This structure is a parameter of almost all methods in the build system, +/// although most functions are implemented as free functions rather than +/// methods specifically on this structure itself (to make it easier to +/// organize). +pub(crate) struct Build { + /// User-specified configuration from `bootstrap.toml`. + pub(crate) config: Config, + + // Version information + pub(crate) version: String, + + // Properties derived from the above configuration + pub(crate) src: PathBuf, + pub(crate) out: PathBuf, + pub(crate) bootstrap_out: PathBuf, + pub(crate) cargo_info: GitInfo, + pub(crate) rust_analyzer_info: GitInfo, + pub(crate) clippy_info: GitInfo, + pub(crate) miri_info: GitInfo, + pub(crate) rustfmt_info: GitInfo, + pub(crate) enzyme_info: GitInfo, + pub(crate) in_tree_llvm_info: GitInfo, + pub(crate) in_tree_gcc_info: GitInfo, + pub(crate) local_rebuild: bool, + pub(crate) fail_fast: bool, + pub(crate) test_target: TestTarget, + pub(crate) verbosity: usize, + + /// Build triple for the pre-compiled snapshot compiler. + pub(crate) host_target: TargetSelection, + /// Which triples to produce a compiler toolchain for. + pub(crate) hosts: Vec, + /// Which triples to build libraries (core/alloc/std/test/proc_macro) for. + pub(crate) targets: Vec, + + pub(crate) initial_rustc: PathBuf, + pub(crate) initial_rustdoc: PathBuf, + pub(crate) initial_cargo: PathBuf, + pub(crate) initial_lld: PathBuf, + pub(crate) initial_relative_libdir: PathBuf, + pub(crate) initial_sysroot: PathBuf, + + // Runtime state filled in later on + // C/C++ compilers and archiver for all targets + pub(crate) cc: HashMap, + pub(crate) cxx: HashMap, + pub(crate) ar: HashMap, + pub(crate) ranlib: HashMap, + pub(crate) wasi_sdk_path: Option, + + // Miscellaneous + // allow bidirectional lookups: both name -> path and path -> name + pub(crate) crates: HashMap, + pub(crate) crate_paths: HashMap, + pub(crate) is_sudo: bool, + pub(crate) prerelease_version: Cell>, + + #[cfg(feature = "build-metrics")] + pub(crate) metrics: crate::utils::metrics::BuildMetrics, + + #[cfg(feature = "tracing")] + pub(crate) step_graph: std::cell::RefCell, +} + +/// When building Rust various objects are handled differently. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum DependencyType { + /// Libraries originating from proc-macros. + Host, + /// Typical Rust libraries. + Target, + /// Non Rust libraries and objects shipped to ease usage of certain targets. + TargetSelfContained, +} + +/// The various "modes" of invoking Cargo. +/// +/// These entries currently correspond to the various output directories of the +/// build system, with each mod generating output in a different directory. +#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Mode { + /// Build the standard library, placing output in the "stageN-std" directory. + Std, + + /// Build librustc, and compiler libraries, placing output in the "stageN-rustc" directory. + Rustc, + + /// Build a codegen backend for rustc, placing the output in the "stageN-codegen" directory. + Codegen, + + /// Build a tool, placing output in the "bootstrap-tools" + /// directory. This is for miscellaneous sets of tools that extend + /// bootstrap. + /// + /// These tools are intended to be only executed on the host system that + /// invokes bootstrap, and they thus cannot be cross-compiled. + /// + /// They are always built using the stage0 compiler, and they + /// can be compiled with stable Rust. + /// + /// These tools also essentially do not participate in staging. + ToolBootstrap, + + /// Build a cross-compilable helper tool. These tools do not depend on unstable features or + /// compiler internals, but they might be cross-compilable (so we cannot build them using the + /// stage0 compiler, unlike `ToolBootstrap`). + /// + /// Some of these tools are also shipped in our `dist` archives. + /// While we could compile them using the stage0 compiler when not cross-compiling, we instead + /// use the in-tree compiler (and std) to build them, so that we can ship e.g. std security + /// fixes and avoid depending fully on stage0 for the artifacts that we ship. + /// + /// This mode is used e.g. for linkers and linker tools invoked by rustc on its host target. + ToolTarget, + + /// Build a tool which uses the locally built std, placing output in the + /// "stageN-tools" directory. Its usage is quite rare; historically it was + /// needed by compiletest, but now it is mainly used by `test-float-parse`. + ToolStd, + + /// Build a tool which uses the `rustc_private` mechanism, and thus + /// the locally built rustc rlib artifacts, + /// placing the output in the "stageN-tools" directory. This is used for + /// everything that links to rustc as a library, such as rustdoc, clippy, + /// rustfmt, miri, etc. + ToolRustcPrivate, +} + +impl Mode { + pub(crate) fn must_support_dlopen(&self) -> bool { + match self { + Mode::Std | Mode::Codegen => true, + Mode::ToolBootstrap + | Mode::ToolRustcPrivate + | Mode::ToolStd + | Mode::ToolTarget + | Mode::Rustc => false, + } + } +} + +/// When `rust.rust_remap_debuginfo` is requested, the compiler needs to know how to +/// opportunistically unremap compiler vs non-compiler sources. We use two schemes, +/// [`RemapScheme::Compiler`] and [`RemapScheme::NonCompiler`]. +pub(crate) enum RemapScheme { + /// The [`RemapScheme::Compiler`] scheme will remap to `/rustc-dev/{hash}`. + Compiler, + /// The [`RemapScheme::NonCompiler`] scheme will remap to `/rustc/{hash}`. + NonCompiler, +} + +#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] +pub(crate) enum CLang { + C, + Cxx, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum FileType { + /// An executable binary file (like a `.exe`). + Executable, + /// A native, binary library file (like a `.so`, `.dll`, `.a`, `.lib` or `.o`). + NativeLibrary, + /// An executable (non-binary) script file (like a `.py` or `.sh`). + Script, + /// Any other regular file that is non-executable. + Regular, +} + +impl FileType { + /// Get Unix permissions appropriate for this file type. + pub(crate) fn perms(self) -> u32 { + match self { + FileType::Executable | FileType::Script => 0o755, + FileType::Regular | FileType::NativeLibrary => 0o644, + } + } + + pub(crate) fn could_have_split_debuginfo(self) -> bool { + match self { + FileType::Executable | FileType::NativeLibrary => true, + FileType::Script | FileType::Regular => false, + } + } +} + +macro_rules! forward { + ( $( $fn:ident( $($param:ident: $ty:ty),* ) $( -> $ret:ty)? ),+ $(,)? ) => { + impl Build { + $( + pub(crate) fn $fn(&self, $($param: $ty),* ) $( -> $ret)? { + self.config.$fn( $($param),* ) + } + )+ + } + } +} + +forward! { + do_if_verbose(f: impl Fn()), + is_verbose() -> bool, + create(path: &Path, s: &str), + remove(f: &Path), + tempdir() -> PathBuf, + download_rustc() -> bool, +} + +/// An alternative way of specifying what target and stage is involved in some bootstrap activity. +/// Ideally using a `Compiler` directly should be preferred. +pub(crate) struct TargetAndStage { + target: TargetSelection, + stage: u32, +} + +impl From<(TargetSelection, u32)> for TargetAndStage { + fn from((target, stage): (TargetSelection, u32)) -> Self { + Self { target, stage } + } +} + +impl From for TargetAndStage { + fn from(compiler: Compiler) -> Self { + Self { target: compiler.host, stage: compiler.stage } + } +} + +impl Build { + /// Creates a new set of build configuration from the `flags` on the command + /// line and the filesystem `config`. + /// + /// By default all build output will be placed in the current directory. + pub(crate) fn new(mut config: Config) -> Build { + let src = config.src.clone(); + let out = config.out.clone(); + + #[cfg(unix)] + // keep this consistent with the equivalent check in x.py: + // https://github.com/rust-lang/rust/blob/a8a33cf27166d3eabaffc58ed3799e054af3b0c6/src/bootstrap/bootstrap.py#L796-L797 + let is_sudo = match env::var_os("SUDO_USER") { + Some(_sudo_user) => { + // SAFETY: getuid() system call is always successful and no return value is reserved + // to indicate an error. + // + // For more context, see https://man7.org/linux/man-pages/man2/geteuid.2.html + let uid = unsafe { libc::getuid() }; + uid == 0 + } + None => false, + }; + #[cfg(not(unix))] + let is_sudo = false; + + let rust_info = config.rust_info.clone(); + let cargo_info = config.cargo_info.clone(); + let rust_analyzer_info = config.rust_analyzer_info.clone(); + let clippy_info = config.clippy_info.clone(); + let miri_info = config.miri_info.clone(); + let rustfmt_info = config.rustfmt_info.clone(); + let enzyme_info = config.enzyme_info.clone(); + let in_tree_llvm_info = config.in_tree_llvm_info.clone(); + let in_tree_gcc_info = config.in_tree_gcc_info.clone(); + + let initial_target_libdir = command(&config.initial_rustc) + .run_in_dry_run() + .args(["--print", "target-libdir"]) + .run_capture_stdout(&config) + .stdout() + .trim() + .to_owned(); + + let initial_target_dir = Path::new(&initial_target_libdir) + .parent() + .unwrap_or_else(|| panic!("{initial_target_libdir} has no parent")); + + let initial_lld = initial_target_dir.join("bin").join("rust-lld"); + + let initial_relative_libdir = if cfg!(test) { + // On tests, bootstrap uses the shim rustc, not the one from the stage0 toolchain. + PathBuf::default() + } else { + let ancestor = initial_target_dir.ancestors().nth(2).unwrap_or_else(|| { + panic!("Not enough ancestors for {}", initial_target_dir.display()) + }); + + ancestor + .strip_prefix(&config.initial_sysroot) + .unwrap_or_else(|_| { + panic!( + "Couldn’t resolve the initial relative libdir from {}", + initial_target_dir.display() + ) + }) + .to_path_buf() + }; + + let version = std::fs::read_to_string(src.join("src").join("version")) + .expect("failed to read src/version"); + let version = version.trim(); + + let mut bootstrap_out = std::env::current_exe() + .expect("could not determine path to running process") + .parent() + .unwrap() + .to_path_buf(); + // Since bootstrap is hardlink to deps/bootstrap-*, Solaris can sometimes give + // path with deps/ which is bad and needs to be avoided. + if bootstrap_out.ends_with("deps") { + bootstrap_out.pop(); + } + if !bootstrap_out.join(exe("rustc", config.host_target)).exists() && !cfg!(test) { + // this restriction can be lifted whenever https://github.com/rust-lang/rfcs/pull/3028 is implemented + panic!( + "`rustc` not found in {}, run `cargo build --bins` before `cargo run`", + bootstrap_out.display() + ) + } + + if rust_info.is_from_tarball() && config.description.is_none() { + config.description = Some("built from a source tarball".to_owned()); + } + + let mut build = Build { + initial_lld, + initial_relative_libdir, + initial_rustc: config.initial_rustc.clone(), + initial_rustdoc: config.initial_rustdoc.clone(), + initial_cargo: config.initial_cargo.clone(), + initial_sysroot: config.initial_sysroot.clone(), + local_rebuild: config.local_rebuild, + fail_fast: config.cmd.fail_fast(), + test_target: config.cmd.test_target(), + verbosity: config.exec_ctx.verbosity as usize, + + host_target: config.host_target, + hosts: config.hosts.clone(), + targets: config.targets.clone(), + + config, + version: version.to_string(), + src, + out, + bootstrap_out, + + cargo_info, + rust_analyzer_info, + clippy_info, + miri_info, + rustfmt_info, + enzyme_info, + in_tree_llvm_info, + in_tree_gcc_info, + cc: HashMap::new(), + cxx: HashMap::new(), + ar: HashMap::new(), + ranlib: HashMap::new(), + wasi_sdk_path: env::var_os("WASI_SDK_PATH").map(PathBuf::from), + crates: HashMap::new(), + crate_paths: HashMap::new(), + is_sudo, + prerelease_version: Cell::new(None), + + #[cfg(feature = "build-metrics")] + metrics: crate::utils::metrics::BuildMetrics::init(), + + #[cfg(feature = "tracing")] + step_graph: std::cell::RefCell::new(crate::utils::step_graph::StepGraph::default()), + }; + + // If local-rust is the same major.minor as the current version, then force a + // local-rebuild + let local_version_verbose = command(&build.initial_rustc) + .run_in_dry_run() + .args(["--version", "--verbose"]) + .run_capture_stdout(&build) + .stdout(); + let local_release = local_version_verbose + .lines() + .filter_map(|x| x.strip_prefix("release:")) + .next() + .unwrap() + .trim(); + if local_release.split('.').take(2).eq(version.split('.').take(2)) { + build.do_if_verbose(|| println!("auto-detected local-rebuild {local_release}")); + build.local_rebuild = true; + } + + build.do_if_verbose(|| println!("finding compilers")); + crate::utils::cc_detect::fill_compilers(&mut build); + // When running `setup`, the profile is about to change, so any requirements we have now may + // be different on the next invocation. Don't check for them until the next time x.py is + // run. This is ok because `setup` never runs any build commands, so it won't fail if commands are missing. + // + // Similarly, for `setup` we don't actually need submodules or cargo metadata. + if !matches!(build.config.cmd, Subcommand::Setup { .. }) { + build.do_if_verbose(|| println!("running sanity check")); + crate::core::sanity::check(&mut build); + + // Make sure we update these before gathering metadata so we don't get an error about missing + // Cargo.toml files. + let rust_submodules = ["library/backtrace"]; + for s in rust_submodules { + build.require_submodule( + s, + Some( + "The submodule is required for the standard library \ + and the main Cargo workspace.", + ), + ); + } + // Now, update all existing submodules. + build.update_existing_submodules(); + + build.do_if_verbose(|| println!("learning about cargo")); + crate::core::metadata::build(&mut build); + } + + // Create symbolic link to use host sysroot from a consistent path (e.g., in the rust-analyzer config file). + let build_triple = build.out.join(build.host_target); + t!(fs::create_dir_all(&build_triple)); + let host = build.out.join("host"); + if host.is_symlink() { + // Left over from a previous build; overwrite it. + // This matters if `build.build` has changed between invocations. + #[cfg(windows)] + t!(fs::remove_dir(&host)); + #[cfg(not(windows))] + t!(fs::remove_file(&host)); + } + t!( + symlink_dir(&build.config, &build_triple, &host), + format!("symlink_dir({} => {}) failed", host.display(), build_triple.display()) + ); + + build + } + + /// Updates a submodule, and exits with a failure if submodule management + /// is disabled and the submodule does not exist. + /// + /// The given submodule name should be its path relative to the root of + /// the main repository. + /// + /// The given `err_hint` will be shown to the user if the submodule is not + /// checked out and submodule management is disabled. + #[cfg_attr( + feature = "tracing", + instrument( + level = "trace", + name = "Build::require_submodule", + skip_all, + fields(submodule = submodule), + ), + )] + pub(crate) fn require_submodule(&self, submodule: &str, err_hint: Option<&str>) { + if self.rust_info().is_from_tarball() { + return; + } + + if self.config.dry_run() { + return; + } + + // When testing bootstrap itself, it is much faster to ignore + // submodules. Almost all Steps work fine without their submodules. + if cfg!(test) && !self.config.submodules() { + return; + } + self.config.update_submodule(submodule); + let absolute_path = self.config.src.join(submodule); + if !absolute_path.exists() || dir_is_empty(&absolute_path) { + let maybe_enable = if !self.config.submodules() + && self.config.rust_info.is_managed_git_subrepository() + { + "\nConsider setting `build.submodules = true` or manually initializing the submodules." + } else { + "" + }; + let err_hint = err_hint.map_or_else(String::new, |e| format!("\n{e}")); + eprintln!( + "submodule {submodule} does not appear to be checked out, \ + but it is required for this step{maybe_enable}{err_hint}" + ); + helpers::exit_process(1); + } + } + + /// If any submodule has been initialized already, sync it unconditionally. + /// This avoids contributors checking in a submodule change by accident. + pub(crate) fn update_existing_submodules(&self) { + // Avoid running git when there isn't a git checkout, or the user has + // explicitly disabled submodules in `bootstrap.toml`. + if !self.config.submodules() { + return; + } + let output = helpers::git(Some(&self.src)) + .args(["config", "--file"]) + .arg(".gitmodules") + .args(["--get-regexp", "path"]) + .run_capture(self) + .stdout(); + std::thread::scope(|s| { + // Look for `submodule.$name.path = $path` + // Sample output: `submodule.src/rust-installer.path src/tools/rust-installer` + for line in output.lines() { + let submodule = line.split_once(' ').unwrap().1; + let config = self.config.clone(); + s.spawn(move || { + Self::update_existing_submodule(&config, submodule); + }); + } + }); + } + + /// Updates the given submodule only if it's initialized already; nothing happens otherwise. + pub(crate) fn update_existing_submodule(config: &Config, submodule: &str) { + // Avoid running git when there isn't a git checkout. + if !config.submodules() { + return; + } + + if config.git_info(false, Path::new(submodule)).is_managed_git_subrepository() { + config.update_submodule(submodule); + } + } + + /// Executes the entire build, as configured by the flags and configuration. + #[cfg_attr(feature = "tracing", instrument(level = "debug", name = "Build::build", skip_all))] + pub(crate) fn build(&mut self) { + trace!("setting up job management"); + unsafe { + crate::utils::job::setup(self); + } + + // Handle hard-coded subcommands. + { + #[cfg(feature = "tracing")] + let _hardcoded_span = + span!(tracing::Level::DEBUG, "handling hardcoded subcommands (Format, Perf)") + .entered(); + + match &self.config.cmd { + Subcommand::Format { check, all } => { + let builder = Builder::new(self); + let rustfmt_path = builder.ensure(InternalRustfmt).unwrap_or_else(|| { + eprintln!("fmt error: `x fmt` is not supported on this channel"); + helpers::exit_process(1); + }); + return crate::core::build_steps::format::format( + &builder, + rustfmt_path, + *check, + *all, + &self.config.paths, + ); + } + Subcommand::Perf(args) => { + return crate::core::build_steps::perf::perf(&Builder::new(self), args); + } + _cmd => { + debug!(cmd = ?_cmd, "not a hardcoded subcommand; returning to normal handling"); + } + } + + debug!("handling subcommand normally"); + } + + if !self.config.dry_run() { + #[cfg(feature = "tracing")] + let _real_run_span = span!(tracing::Level::DEBUG, "executing real run").entered(); + + // We first do a dry-run. This is a sanity-check to ensure that + // steps don't do anything expensive in the dry-run. + { + #[cfg(feature = "tracing")] + let _sanity_check_span = + span!(tracing::Level::DEBUG, "(1) executing dry-run sanity-check").entered(); + self.config.set_dry_run(DryRun::SelfCheck); + let builder = Builder::new(self); + builder.execute_cli(); + } + + // Actual run. + { + #[cfg(feature = "tracing")] + let _actual_run_span = + span!(tracing::Level::DEBUG, "(2) executing actual run").entered(); + self.config.set_dry_run(DryRun::Disabled); + let builder = Builder::new(self); + builder.execute_cli(); + } + } else { + #[cfg(feature = "tracing")] + let _dry_run_span = span!(tracing::Level::DEBUG, "executing dry run").entered(); + + let builder = Builder::new(self); + builder.execute_cli(); + } + + #[cfg(feature = "tracing")] + debug!("checking for postponed test failures from `test --no-fail-fast`"); + + // Check for postponed failures from `test --no-fail-fast`. + self.config.exec_ctx().report_failures_and_exit(); + + #[cfg(feature = "build-metrics")] + self.metrics.persist(self); + } + + pub(crate) fn rust_info(&self) -> &GitInfo { + &self.config.rust_info + } + + /// Gets the space-separated set of activated features for the standard library. + /// This can be configured with the `std-features` key in bootstrap.toml. + pub(crate) fn std_features(&self, target: TargetSelection) -> String { + let mut features: BTreeSet<&str> = + self.config.rust_std_features.iter().map(|s| s.as_str()).collect(); + + match self.config.llvm_libunwind(target) { + LlvmLibunwind::InTree => features.insert("llvm-libunwind"), + LlvmLibunwind::System => features.insert("system-llvm-libunwind"), + LlvmLibunwind::No => false, + }; + + if self.config.backtrace { + features.insert("backtrace"); + } + + if self.config.profiler_enabled(target) { + features.insert("profiler"); + } + + // If zkvm target, generate memcpy, etc. + if target.contains("zkvm") { + features.insert("compiler-builtins-mem"); + } + + features.into_iter().collect::>().join(" ") + } + + /// Gets the space-separated set of activated features for the compiler. + pub(crate) fn rustc_features( + &self, + kind: Kind, + target: TargetSelection, + crates: &[String], + ) -> String { + let possible_features_by_crates: HashSet<_> = crates + .iter() + .flat_map(|krate| &self.crates[krate].features) + .map(std::ops::Deref::deref) + .collect(); + let check = |feature: &str| -> bool { + crates.is_empty() || possible_features_by_crates.contains(feature) + }; + let mut features = vec![]; + + if let Some(allocator_feature_name) = self.config.allocator(target).feature_name() + && check(allocator_feature_name) + { + features.push(allocator_feature_name); + } + if (self.config.llvm_enabled(target) || kind == Kind::Check) && check("llvm") { + features.push("llvm"); + } + if self.config.llvm_offload { + features.push("llvm_offload"); + } + // keep in sync with `bootstrap/compile.rs:rustc_cargo_env` + if self.config.rust_randomize_layout && check("rustc_randomized_layouts") { + features.push("rustc_randomized_layouts"); + } + if self.config.compile_time_deps && kind == Kind::Check { + features.push("check_only"); + } + + if crates.iter().any(|c| c == "rustc_transmute") { + // for `x test rustc_transmute`, this feature isn't enabled automatically by a + // dependent crate. + features.push("rustc"); + } + + // If debug logging is on, then we want the default for tracing: + // https://github.com/tokio-rs/tracing/blob/3dd5c03d907afdf2c39444a29931833335171554/tracing/src/level_filters.rs#L26 + // which is everything (including debug/trace/etc.) + // if its unset, if debug_assertions is on, then debug_logging will also be on + // as well as tracing *ignoring* this feature when debug_assertions is on + if !self.config.rust_debug_logging && check("max_level_info") { + features.push("max_level_info"); + } + + features.join(" ") + } + + /// Component directory that Cargo will produce output into (e.g. + /// release/debug) + pub(crate) fn cargo_dir(&self, mode: Mode) -> &'static str { + match (mode, self.config.rust_optimize.is_release()) { + (Mode::Std, _) => "dist", + (_, true) => "release", + (_, false) => "debug", + } + } + + pub(crate) fn tools_dir(&self, build_compiler: Compiler) -> PathBuf { + let out = self + .out + .join(build_compiler.host) + .join(format!("stage{}-tools-bin", build_compiler.stage + 1)); + t!(fs::create_dir_all(&out)); + out + } + + /// Returns the root directory for all output generated in a particular + /// stage when being built with a particular build compiler. + /// + /// The mode indicates what the root directory is for. + pub(crate) fn stage_out(&self, build_compiler: Compiler, mode: Mode) -> PathBuf { + use std::fmt::Write; + + fn bootstrap_tool() -> (Option, &'static str) { + (None, "bootstrap-tools") + } + fn staged_tool(build_compiler: Compiler) -> (Option, &'static str) { + (Some(build_compiler.stage + 1), "tools") + } + + let (stage, suffix) = match mode { + // Std is special, stage N std is built with stage N rustc + Mode::Std => (Some(build_compiler.stage), "std"), + // The rest of things are built with stage N-1 rustc + Mode::Rustc => (Some(build_compiler.stage + 1), "rustc"), + Mode::Codegen => (Some(build_compiler.stage + 1), "codegen"), + Mode::ToolBootstrap => bootstrap_tool(), + Mode::ToolStd | Mode::ToolRustcPrivate => (Some(build_compiler.stage + 1), "tools"), + Mode::ToolTarget => { + // If we're not cross-compiling (the common case), share the target directory with + // bootstrap tools to reuse the build cache. + if build_compiler.stage == 0 { + bootstrap_tool() + } else { + staged_tool(build_compiler) + } + } + }; + let path = self.out.join(build_compiler.host); + let mut dir_name = String::new(); + if let Some(stage) = stage { + write!(dir_name, "stage{stage}-").unwrap(); + } + dir_name.push_str(suffix); + path.join(dir_name) + } + + /// Returns the root output directory for all Cargo output in a given stage, + /// running a particular compiler, whether or not we're building the + /// standard library, and targeting the specified architecture. + pub(crate) fn cargo_out( + &self, + build_compiler: Compiler, + mode: Mode, + target: TargetSelection, + ) -> PathBuf { + self.stage_out(build_compiler, mode).join(target).join(self.cargo_dir(mode)) + } + + /// Output directory for all documentation for a target + pub(crate) fn doc_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(target).join("doc") + } + + /// Output directory for all JSON-formatted documentation for a target + pub(crate) fn json_doc_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(target).join("json-doc") + } + + pub(crate) fn test_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(target).join("test") + } + + /// Output directory for all documentation for a target + pub(crate) fn compiler_doc_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(target).join("compiler-doc") + } + + /// Output directory for some generated md crate documentation for a target (temporary) + pub(crate) fn md_doc_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(target).join("md-doc") + } + + /// Path to the vendored Rust crates. + pub(crate) fn vendored_crates_path(&self) -> Option { + if self.config.vendor { Some(self.src.join(VENDOR_DIR)) } else { None } + } + + /// Directory for libraries built from C/C++ code and shared between stages. + pub(crate) fn native_dir(&self, target: TargetSelection) -> PathBuf { + self.out.join(target).join("native") + } + + /// Root output directory for rust_test_helpers library compiled for + /// `target` + pub(crate) fn test_helpers_out(&self, target: TargetSelection) -> PathBuf { + self.native_dir(target).join("rust-test-helpers") + } + + /// Adds the `RUST_TEST_THREADS` env var if necessary + pub(crate) fn add_rust_test_threads(&self, cmd: &mut BootstrapCommand) { + if env::var_os("RUST_TEST_THREADS").is_none() { + cmd.env("RUST_TEST_THREADS", self.jobs().to_string()); + } + } + + /// Returns the libdir of the snapshot compiler. + pub(crate) fn rustc_snapshot_libdir(&self) -> PathBuf { + self.rustc_snapshot_sysroot().join(libdir(self.config.host_target)) + } + + /// Returns the sysroot of the snapshot compiler. + pub(crate) fn rustc_snapshot_sysroot(&self) -> &Path { + static SYSROOT_CACHE: OnceLock = OnceLock::new(); + SYSROOT_CACHE.get_or_init(|| { + command(&self.initial_rustc) + .run_in_dry_run() + .args(["--print", "sysroot"]) + .run_capture_stdout(self) + .stdout() + .trim() + .to_owned() + .into() + }) + } + + pub(crate) fn info(&self, msg: &str) { + match self.config.get_dry_run() { + DryRun::SelfCheck => (), + DryRun::Disabled | DryRun::UserSelected => { + println!("{msg}"); + } + } + } + + /// Return a `Group` guard for a [`Step`] that: + /// - Performs `action` + /// - If the action is `Kind::Test`, use [`Build::msg_test`] instead. + /// - On `what` + /// - Where `what` possibly corresponds to a `mode` + /// - `action` is performed with/on the given compiler (`target_and_stage`). + /// - Since for some steps it is not possible to pass a single compiler here, it is also + /// possible to pass the host and stage explicitly. + /// - With a given `target`. + /// + /// [`Step`]: crate::core::builder::Step + #[must_use = "Groups should not be dropped until the Step finishes running"] + #[track_caller] + pub(crate) fn msg( + &self, + action: impl Into, + what: impl Display, + mode: impl Into>, + target_and_stage: impl Into, + target: impl Into>, + ) -> Option { + let target_and_stage = target_and_stage.into(); + let action = action.into(); + assert!( + action != Kind::Test, + "Please use `Build::msg_test` instead of `Build::msg(Kind::Test)`" + ); + + let actual_stage = match mode.into() { + // Std has the same stage as the compiler that builds it + Some(Mode::Std) => target_and_stage.stage, + // Other things have stage corresponding to their build compiler + 1 + Some( + Mode::Rustc + | Mode::Codegen + | Mode::ToolBootstrap + | Mode::ToolTarget + | Mode::ToolStd + | Mode::ToolRustcPrivate, + ) + | None => target_and_stage.stage + 1, + }; + + let action = action.description(); + let what = what.to_string(); + let msg = |fmt| { + let space = if !what.is_empty() { " " } else { "" }; + format!("{action} stage{actual_stage} {what}{space}{fmt}") + }; + let msg = if let Some(target) = target.into() { + let build_stage = target_and_stage.stage; + let host = target_and_stage.target; + if host == target { + msg(format_args!("(stage{build_stage} -> stage{actual_stage}, {target})")) + } else { + msg(format_args!("(stage{build_stage}:{host} -> stage{actual_stage}:{target})")) + } + } else { + msg(format_args!("")) + }; + self.group(&msg) + } + + /// Return a `Group` guard for a [`Step`] that tests `what` with the given `stage` and `target`. + /// Use this instead of [`Build::msg`] for test steps, because for them it is not always clear + /// what exactly is a build compiler. + /// + /// [`Step`]: crate::core::builder::Step + #[must_use = "Groups should not be dropped until the Step finishes running"] + #[track_caller] + pub(crate) fn msg_test( + &self, + what: impl Display, + target: TargetSelection, + stage: u32, + ) -> Option { + let action = Kind::Test.description(); + let msg = format!("{action} stage{stage} {what} ({target})"); + self.group(&msg) + } + + /// Return a `Group` guard for a [`Step`] that is only built once and isn't affected by `--stage`. + /// + /// [`Step`]: crate::core::builder::Step + #[must_use = "Groups should not be dropped until the Step finishes running"] + #[track_caller] + pub(crate) fn msg_unstaged( + &self, + action: impl Into, + what: impl Display, + target: TargetSelection, + ) -> Option { + let action = action.into().description(); + let msg = format!("{action} {what} for {target}"); + self.group(&msg) + } + + #[track_caller] + pub(crate) fn group(&self, msg: &str) -> Option { + match self.config.get_dry_run() { + DryRun::SelfCheck => None, + DryRun::Disabled | DryRun::UserSelected => Some(gha::group(msg)), + } + } + + /// Returns the number of parallel jobs that have been configured for this + /// build. + pub(crate) fn jobs(&self) -> u32 { + self.config.jobs.unwrap_or_else(|| { + std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32 + }) + } + + pub(crate) fn debuginfo_map_to( + &self, + which: GitRepo, + remap_scheme: RemapScheme, + ) -> Option { + if !self.config.rust_remap_debuginfo { + return None; + } + + match which { + GitRepo::Rustc => { + let sha = self.rust_sha().unwrap_or(&self.version); + + match remap_scheme { + RemapScheme::Compiler => { + // For compiler sources, remap via `/rustc-dev/{sha}` to allow + // distinguishing between compiler sources vs library sources, since + // `rustc-dev` dist component places them under + // `$sysroot/lib/rustlib/rustc-src/rust` as opposed to `rust-src`'s + // `$sysroot/lib/rustlib/src/rust`. + // + // Keep this scheme in sync with `rustc_metadata::rmeta::decoder`'s + // `try_to_translate_virtual_to_real`. + Some(format!("/rustc-dev/{sha}")) + } + RemapScheme::NonCompiler => { + // For non-compiler sources, use `/rustc/{sha}` remapping scheme. + Some(format!("/rustc/{sha}")) + } + } + } + GitRepo::Llvm => Some(String::from("/rustc/llvm")), + } + } + + /// Returns the path to the C compiler for the target specified. + pub(crate) fn cc(&self, target: TargetSelection) -> PathBuf { + if self.config.dry_run() { + return PathBuf::new(); + } + self.cc[&target].path().into() + } + + /// Returns the internal `cc::Tool` for the C compiler. + pub(crate) fn cc_tool(&self, target: TargetSelection) -> cc::Tool { + self.cc[&target].clone() + } + + /// Returns the internal `cc::Tool` for the C++ compiler. + pub(crate) fn cxx_tool(&self, target: TargetSelection) -> cc::Tool { + self.cxx[&target].clone() + } + + /// Returns C flags that `cc-rs` thinks should be enabled for the + /// specified target by default. + pub(crate) fn cc_handled_cflags(&self, target: TargetSelection, c: CLang) -> Vec { + if self.config.dry_run() { + return Vec::new(); + } + let base = match c { + CLang::C => self.cc[&target].clone(), + CLang::Cxx => self.cxx[&target].clone(), + }; + + // Filter out -O and /O (the optimization flags) that we picked up + // from cc-rs, that's up to the caller to figure out. + base.args() + .iter() + .map(|s| s.to_string_lossy().into_owned()) + .filter(|s| !s.starts_with("-O") && !s.starts_with("/O")) + .collect::>() + } + + /// Returns extra C flags that `cc-rs` doesn't handle. + pub(crate) fn cc_unhandled_cflags( + &self, + target: TargetSelection, + which: GitRepo, + c: CLang, + ) -> Vec { + let mut base = Vec::new(); + + // If we're compiling C++ on macOS then we add a flag indicating that + // we want libc++ (more filled out than libstdc++), ensuring that + // LLVM/etc are all properly compiled. + if matches!(c, CLang::Cxx) && target.contains("apple-darwin") { + base.push("-stdlib=libc++".into()); + } + + // Work around an apparently bad MinGW / GCC optimization, + // See: https://lists.llvm.org/pipermail/cfe-dev/2016-December/051980.html + // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78936 + if &*target.triple == "i686-pc-windows-gnu" { + base.push("-fno-omit-frame-pointer".into()); + } + + if let Some(map_to) = self.debuginfo_map_to(which, RemapScheme::NonCompiler) { + let map = format!("{}={}", self.src.display(), map_to); + let cc = self.cc_tool(target); + if cc.is_like_clang() || cc.is_like_gnu() { + base.push(format!("-fdebug-prefix-map={map}")); + } else if cc.is_like_clang_cl() { + base.push("-Xclang".into()); + base.push(format!("-fdebug-prefix-map={map}")); + } + } + base + } + + /// Returns the path to the `ar` archive utility for the target specified. + pub(crate) fn ar(&self, target: TargetSelection) -> Option { + if self.config.dry_run() { + return None; + } + self.ar.get(&target).cloned() + } + + /// Returns the path to the `ranlib` utility for the target specified. + pub(crate) fn ranlib(&self, target: TargetSelection) -> Option { + if self.config.dry_run() { + return None; + } + self.ranlib.get(&target).cloned() + } + + /// Returns the path to the C++ compiler for the target specified. + pub(crate) fn cxx(&self, target: TargetSelection) -> Result { + if self.config.dry_run() { + return Ok(PathBuf::new()); + } + match self.cxx.get(&target) { + Some(p) => Ok(p.path().into()), + None => Err(format!("target `{target}` is not configured as a host, only as a target")), + } + } + + /// Returns the path to the linker for the given target if it needs to be overridden. + pub(crate) fn linker(&self, target: TargetSelection) -> Option { + if self.config.dry_run() { + return Some(PathBuf::new()); + } + if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.clone()) + { + Some(linker) + } else if target.contains("vxworks") { + // need to use CXX compiler as linker to resolve the exception functions + // that are only existed in CXX libraries + Some(self.cxx[&target].path().into()) + } else if !self.config.is_host_target(target) + && helpers::use_host_linker(target) + && !target.is_msvc() + { + Some(self.cc(target)) + } else if self.config.bootstrap_override_lld.is_used() + && self.is_lld_direct_linker(target) + && self.host_target == target + { + match self.config.bootstrap_override_lld { + BootstrapOverrideLld::SelfContained => Some(self.initial_lld.clone()), + BootstrapOverrideLld::External => Some("lld".into()), + BootstrapOverrideLld::None => None, + } + } else { + None + } + } + + // Is LLD configured directly through `-Clinker`? + // Only MSVC targets use LLD directly at the moment. + pub(crate) fn is_lld_direct_linker(&self, target: TargetSelection) -> bool { + target.is_msvc() + } + + /// Returns if this target should statically link the C runtime, if specified + pub(crate) fn crt_static(&self, target: TargetSelection) -> Option { + if target.contains("pc-windows-msvc") { + Some(true) + } else { + self.config.target_config.get(&target).and_then(|t| t.crt_static) + } + } + + /// Returns the "musl root" for this `target`, if defined. + /// + /// If this is a native target (host is also musl) and no musl-root is given, + /// it falls back to the system toolchain in /usr. + pub(crate) fn musl_root(&self, target: TargetSelection) -> Option<&Path> { + let configured_root = self + .config + .target_config + .get(&target) + .and_then(|t| t.musl_root.as_ref()) + .or(self.config.musl_root.as_ref()) + .map(|p| &**p); + + if self.config.is_host_target(target) && configured_root.is_none() { + Some(Path::new("/usr")) + } else { + configured_root + } + } + + /// Returns the "musl libdir" for this `target`. + pub(crate) fn musl_libdir(&self, target: TargetSelection) -> Option { + self.config + .target_config + .get(&target) + .and_then(|t| t.musl_libdir.clone()) + .or_else(|| self.musl_root(target).map(|root| root.join("lib"))) + } + + /// Returns the `lib` directory for the WASI target specified, if + /// configured. + /// + /// This first consults `wasi-root` as configured in per-target + /// configuration, and failing that it assumes that `$WASI_SDK_PATH` is + /// set in the environment, and failing that `None` is returned. + pub(crate) fn wasi_libdir(&self, target: TargetSelection) -> Option { + let configured = + self.config.target_config.get(&target).and_then(|t| t.wasi_root.as_ref()).map(|p| &**p); + if let Some(path) = configured { + return Some(path.join("lib").join(target.to_string())); + } + let mut env_root = self.wasi_sdk_path.clone()?; + env_root.push("share"); + env_root.push("wasi-sysroot"); + env_root.push("lib"); + env_root.push(target.to_string()); + Some(env_root) + } + + /// Returns `true` if this is a no-std `target`, if defined + pub(crate) fn no_std(&self, target: TargetSelection) -> Option { + self.config.target_config.get(&target).map(|t| t.no_std) + } + + /// Returns `true` if the target will be tested using the `remote-test-client` + /// and `remote-test-server` binaries. + pub(crate) fn remote_tested(&self, target: TargetSelection) -> bool { + self.qemu_rootfs(target).is_some() + || target.contains("android") + || env::var_os("TEST_DEVICE_ADDR").is_some() + } + + /// Returns an optional "runner" to pass to `compiletest` when executing + /// test binaries. + /// + /// An example of this would be a WebAssembly runtime when testing the wasm + /// targets. + pub(crate) fn runner(&self, target: TargetSelection) -> Option { + let configured_runner = + self.config.target_config.get(&target).and_then(|t| t.runner.as_ref()).map(|p| &**p); + if let Some(runner) = configured_runner { + return Some(runner.to_owned()); + } + + if target.starts_with("wasm") && target.contains("wasi") { + self.default_wasi_runner(target) + } else { + None + } + } + + /// When a `runner` configuration is not provided and a WASI-looking target + /// is being tested this is consulted to prove the environment to see if + /// there's a runtime already lying around that seems reasonable to use. + fn default_wasi_runner(&self, target: TargetSelection) -> Option { + let mut finder = crate::core::sanity::Finder::new(); + + // Look for Wasmtime, and for its default options be sure to disable + // its caching system since we're executing quite a lot of tests and + // ideally shouldn't pollute the cache too much. + if let Some(path) = finder.maybe_have("wasmtime") + && let Ok(mut path) = path.into_os_string().into_string() + { + path.push_str(" run -Wexceptions -C cache=n --dir ."); + // Make sure that tests have access to RUSTC_BOOTSTRAP. This (for example) is + // required for libtest to work on beta/stable channels. + // + // NB: with Wasmtime 20 this can change to `-S inherit-env` to + // inherit the entire environment rather than just this single + // environment variable. + path.push_str(" --env RUSTC_BOOTSTRAP"); + + if target.contains("wasip2") { + path.push_str(" --wasi inherit-network --wasi allow-ip-name-lookup"); + } + + return Some(path); + } + + None + } + + /// Returns whether the specified tool is configured as part of this build. + /// + /// This requires that both the `extended` key is set and the `tools` key is + /// either unset or specifically contains the specified tool. + pub(crate) fn tool_enabled(&self, tool: &str) -> bool { + if !self.config.extended { + return false; + } + match &self.config.tools { + Some(set) => set.contains(tool), + None => true, + } + } + + /// Returns the root of the "rootfs" image that this target will be using, + /// if one was configured. + /// + /// If `Some` is returned then that means that tests for this target are + /// emulated with QEMU and binaries will need to be shipped to the emulator. + pub(crate) fn qemu_rootfs(&self, target: TargetSelection) -> Option<&Path> { + self.config.target_config.get(&target).and_then(|t| t.qemu_rootfs.as_ref()).map(|p| &**p) + } + + /// Temporary directory that extended error information is emitted to. + pub(crate) fn extended_error_dir(&self) -> PathBuf { + self.out.join("tmp/extended-error-metadata") + } + + /// Tests whether the `compiler` compiling for `target` should be forced to + /// use a stage1 compiler instead. + /// + /// Currently, by default, the build system does not perform a "full + /// bootstrap" by default where we compile the compiler three times. + /// Instead, we compile the compiler two times. The final stage (stage2) + /// just copies the libraries from the previous stage, which is what this + /// method detects. + /// + /// Here we return `true` if: + /// + /// * The build isn't performing a full bootstrap + /// * The `compiler` is in the final stage, 2 + /// * We're not cross-compiling, so the artifacts are already available in + /// stage1 + /// + /// When all of these conditions are met the build will lift artifacts from + /// the previous stage forward. + pub(crate) fn force_use_stage1(&self, stage: u32, target: TargetSelection) -> bool { + !self.config.full_bootstrap + && !self.config.download_rustc() + && stage >= 2 + && (self.hosts.contains(&target) || target == self.host_target) + } + + /// Checks whether the `compiler` compiling for `target` should be forced to + /// use a stage2 compiler instead. + /// + /// When we download the pre-compiled version of rustc and compiler stage is >= 2, + /// it should be forced to use a stage2 compiler. + pub(crate) fn force_use_stage2(&self, stage: u32) -> bool { + self.config.download_rustc() && stage >= 2 + } + + /// Given `num` in the form "a.b.c" return a "release string" which + /// describes the release version number. + /// + /// For example on nightly this returns "a.b.c-nightly", on beta it returns + /// "a.b.c-beta.1" and on stable it just returns "a.b.c". + pub(crate) fn release(&self, num: &str) -> String { + match &self.config.channel[..] { + "stable" => num.to_string(), + "beta" => { + if !self.config.omit_git_hash { + format!("{}-beta.{}", num, self.beta_prerelease_version()) + } else { + format!("{num}-beta") + } + } + "nightly" => format!("{num}-nightly"), + _ => format!("{num}-dev"), + } + } + + fn beta_prerelease_version(&self) -> u32 { + fn extract_beta_rev_from_file>(version_file: P) -> Option { + let version = fs::read_to_string(version_file).ok()?; + + helpers::extract_beta_rev(&version) + } + + if let Some(s) = self.prerelease_version.get() { + return s; + } + + // First check if there is a version file available. + // If available, we read the beta revision from that file. + // This only happens when building from a source tarball when Git should not be used. + let count = extract_beta_rev_from_file(self.src.join("version")).unwrap_or_else(|| { + // Figure out how many merge commits happened since we branched off main. + // That's our beta number! + // (Note that we use a `..` range, not the `...` symmetric difference.) + helpers::git(Some(&self.src)) + .arg("rev-list") + .arg("--count") + .arg("--merges") + .arg(format!( + "refs/remotes/origin/{}..HEAD", + self.config.stage0_metadata.config.nightly_branch + )) + .run_in_dry_run() + .run_capture(self) + .stdout() + }); + let n = count.trim().parse().unwrap(); + self.prerelease_version.set(Some(n)); + n + } + + /// Returns the value of `release` above for Rust itself. + pub(crate) fn rust_release(&self) -> String { + self.release(&self.version) + } + + /// Returns the "package version" for a component. + /// + /// The package version is typically what shows up in the names of tarballs. + /// For channels like beta/nightly it's just the channel name, otherwise it's the release + /// version. + pub(crate) fn rust_package_vers(&self) -> String { + match &self.config.channel[..] { + "stable" => self.version.to_string(), + "beta" => "beta".to_string(), + "nightly" => "nightly".to_string(), + _ => format!("{}-dev", self.version), + } + } + + /// Returns the `version` string associated with this compiler for Rust + /// itself. + /// + /// Note that this is a descriptive string which includes the commit date, + /// sha, version, etc. + pub(crate) fn rust_version(&self) -> String { + let mut version = self.rust_info().version(self, &self.version); + if let Some(ref s) = self.config.description + && !s.is_empty() + { + version.push_str(" ("); + version.push_str(s); + version.push(')'); + } + version + } + + /// Returns the full commit hash. + pub(crate) fn rust_sha(&self) -> Option<&str> { + self.rust_info().sha() + } + + /// Returns the `a.b.c` version that the given package is at. + pub(crate) fn release_num(&self, package: &str) -> String { + if self.config.dry_run() { + return "0.0.0 (dry-run)".into(); + } + let toml_file_name = self.src.join(format!("src/tools/{package}/Cargo.toml")); + let toml = t!(fs::read_to_string(toml_file_name)); + for line in toml.lines() { + if let Some(stripped) = + line.strip_prefix("version = \"").and_then(|s| s.strip_suffix('"')) + { + return stripped.to_owned(); + } + } + + panic!("failed to find version in {package}'s Cargo.toml") + } + + /// Returns `true` if unstable features should be enabled for the compiler + /// we're building. + pub(crate) fn unstable_features(&self) -> bool { + !matches!(&self.config.channel[..], "stable" | "beta") + } + + /// Returns a Vec of all the dependencies of the given root crate, + /// including transitive dependencies and the root itself. Only includes + /// "local" crates (those in the local source tree, not from a registry). + pub(crate) fn in_tree_crates( + &self, + root: &str, + target: Option, + ) -> Vec<&Crate> { + let mut ret = Vec::new(); + let mut list = vec![root.to_owned()]; + let mut visited = HashSet::new(); + while let Some(krate) = list.pop() { + let krate = self + .crates + .get(&krate) + .unwrap_or_else(|| panic!("metadata missing for {krate}: {:?}", self.crates)); + ret.push(krate); + for dep in &krate.deps { + if !self.crates.contains_key(dep) { + // Ignore non-workspace members. + continue; + } + // Don't include optional deps if their features are not + // enabled. Ideally this would be computed from `cargo + // metadata --features …`, but that is somewhat slow. In + // the future, we may want to consider just filtering all + // build and dev dependencies in metadata::build. + if visited.insert(dep) + && (dep != "profiler_builtins" + || target + .map(|t| self.config.profiler_enabled(t)) + .unwrap_or_else(|| self.config.any_profiler_enabled())) + && (dep != "rustc_codegen_llvm" + || self.config.hosts.iter().any(|host| self.config.llvm_enabled(*host))) + { + list.push(dep.clone()); + } + } + } + + // Sort the crates so that bootstrap unit tests can assume a deterministic order. + ret.sort_unstable_by(|a, b| Ord::cmp(&a.name, &b.name)); + ret + } + + pub(crate) fn read_stamp_file(&self, stamp: &BuildStamp) -> Vec<(PathBuf, DependencyType)> { + if self.config.dry_run() { + return Vec::new(); + } + + if !stamp.path().exists() { + eprintln!( + "ERROR: Unable to find the stamp file {}, did you try to keep a nonexistent build stage?", + stamp.path().display() + ); + helpers::exit_process(1); + } + + let mut paths = Vec::new(); + let contents = t!(fs::read(stamp.path()), stamp.path()); + // This is the method we use for extracting paths from the stamp file passed to us. See + // run_cargo for more information (in compile.rs). + for part in contents.split(|b| *b == 0) { + if part.is_empty() { + continue; + } + let dependency_type = match part[0] as char { + 'h' => DependencyType::Host, + 's' => DependencyType::TargetSelfContained, + 't' => DependencyType::Target, + _ => unreachable!(), + }; + let path = PathBuf::from(t!(str::from_utf8(&part[1..]))); + paths.push((path, dependency_type)); + } + paths + } + + /// Copies a file from `src` to `dst`. + /// + /// If `src` is a symlink, `src` will be resolved to the actual path + /// and copied to `dst` instead of the symlink itself. + #[track_caller] + pub(crate) fn resolve_symlink_and_copy(&self, src: &Path, dst: &Path) { + self.copy_link_internal(src, dst, true); + } + + /// Links a file from `src` to `dst`. + /// Attempts to use hard links if possible, falling back to copying. + /// You can neither rely on this being a copy nor it being a link, + /// so do not write to dst. + #[track_caller] + pub(crate) fn copy_link(&self, src: &Path, dst: &Path, file_type: FileType) { + self.copy_link_internal(src, dst, false); + + if file_type.could_have_split_debuginfo() + && let Some(dbg_file) = split_debuginfo(src) + { + self.copy_link_internal( + &dbg_file, + &dst.with_extension(dbg_file.extension().unwrap()), + false, + ); + } + } + + #[track_caller] + fn copy_link_internal(&self, src: &Path, dst: &Path, dereference_symlinks: bool) { + if self.config.dry_run() { + return; + } + if src == dst { + return; + } + + #[cfg(feature = "tracing")] + let _span = trace_io!("file-copy-link", ?src, ?dst); + + if let Err(e) = fs::remove_file(dst) + && cfg!(windows) + && e.kind() != io::ErrorKind::NotFound + { + // workaround for https://github.com/rust-lang/rust/issues/127126 + // if removing the file fails, attempt to rename it instead. + let now = t!(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)); + let _ = fs::rename(dst, format!("{}-{}", dst.display(), now.as_nanos())); + } + let mut metadata = t!(src.symlink_metadata(), format!("src = {}", src.display())); + let mut src = src.to_path_buf(); + if metadata.file_type().is_symlink() { + if dereference_symlinks { + src = t!(fs::canonicalize(src)); + metadata = t!(fs::metadata(&src), format!("target = {}", src.display())); + } else { + let link = t!(fs::read_link(src)); + t!(self.symlink_file(link, dst)); + return; + } + } + if let Ok(()) = fs::hard_link(&src, dst) { + // Attempt to "easy copy" by creating a hard link (symlinks are privileged on windows), + // but if that fails just fall back to a slow `copy` operation. + } else { + if let Err(e) = fs::copy(&src, dst) { + panic!("failed to copy `{}` to `{}`: {}", src.display(), dst.display(), e) + } + t!(fs::set_permissions(dst, metadata.permissions())); + + // Restore file times because changing permissions on e.g. Linux using `chmod` can cause + // file access time to change. + let file_times = fs::FileTimes::new() + .set_accessed(t!(metadata.accessed())) + .set_modified(t!(metadata.modified())); + t!(set_file_times(dst, file_times)); + } + } + + /// Links the `src` directory recursively to `dst`. Both are assumed to exist + /// when this function is called. + /// Will attempt to use hard links if possible and fall back to copying. + #[track_caller] + pub(crate) fn cp_link_r(&self, src: &Path, dst: &Path) { + if self.config.dry_run() { + return; + } + for f in self.read_dir(src) { + let path = f.path(); + let name = path.file_name().unwrap(); + let dst = dst.join(name); + if t!(f.file_type()).is_dir() { + t!(fs::create_dir_all(&dst)); + self.cp_link_r(&path, &dst); + } else { + self.copy_link(&path, &dst, FileType::Regular); + } + } + } + + /// Copies the `src` directory recursively to `dst`. Both are assumed to exist + /// when this function is called. + /// Will attempt to use hard links if possible and fall back to copying. + /// Unwanted files or directories can be skipped + /// by returning `false` from the filter function. + #[track_caller] + pub(crate) fn cp_link_filtered(&self, src: &Path, dst: &Path, filter: &dyn Fn(&Path) -> bool) { + // Immediately recurse with an empty relative path + self.cp_link_filtered_recurse(src, dst, Path::new(""), filter) + } + + // Inner function does the actual work + #[track_caller] + fn cp_link_filtered_recurse( + &self, + src: &Path, + dst: &Path, + relative: &Path, + filter: &dyn Fn(&Path) -> bool, + ) { + for f in self.read_dir(src) { + let path = f.path(); + let name = path.file_name().unwrap(); + let dst = dst.join(name); + let relative = relative.join(name); + // Only copy file or directory if the filter function returns true + if filter(&relative) { + if t!(f.file_type()).is_dir() { + let _ = fs::remove_dir_all(&dst); + self.create_dir(&dst); + self.cp_link_filtered_recurse(&path, &dst, &relative, filter); + } else { + self.copy_link(&path, &dst, FileType::Regular); + } + } + } + } + + pub(crate) fn copy_link_to_folder(&self, src: &Path, dest_folder: &Path) { + let file_name = src.file_name().unwrap(); + let dest = dest_folder.join(file_name); + self.copy_link(src, &dest, FileType::Regular); + } + + pub(crate) fn install(&self, src: &Path, dstdir: &Path, file_type: FileType) { + if self.config.dry_run() { + return; + } + let dst = dstdir.join(src.file_name().unwrap()); + + #[cfg(feature = "tracing")] + let _span = trace_io!("install", ?src, ?dst); + + t!(fs::create_dir_all(dstdir)); + if !src.exists() { + panic!("ERROR: File \"{}\" not found!", src.display()); + } + + self.copy_link_internal(src, &dst, true); + chmod(&dst, file_type.perms()); + + // If this file can have debuginfo, look for split debuginfo and install it too. + if file_type.could_have_split_debuginfo() + && let Some(dbg_file) = split_debuginfo(src) + { + self.install(&dbg_file, dstdir, FileType::Regular); + } + } + + pub(crate) fn read(&self, path: &Path) -> String { + if self.config.dry_run() { + return String::new(); + } + t!(fs::read_to_string(path)) + } + + #[track_caller] + pub(crate) fn create_dir(&self, dir: &Path) { + if self.config.dry_run() { + return; + } + + #[cfg(feature = "tracing")] + let _span = trace_io!("dir-create", ?dir); + + t!(fs::create_dir_all(dir)) + } + + pub(crate) fn remove_dir(&self, dir: &Path) { + if self.config.dry_run() { + return; + } + + #[cfg(feature = "tracing")] + let _span = trace_io!("dir-remove", ?dir); + + t!(fs::remove_dir_all(dir)) + } + + /// Make sure that `dir` will be an empty existing directory after this function ends. + /// If it existed before, it will be first deleted. + pub(crate) fn clear_dir(&self, dir: &Path) { + if self.config.dry_run() { + return; + } + + #[cfg(feature = "tracing")] + let _span = trace_io!("dir-clear", ?dir); + + let _ = std::fs::remove_dir_all(dir); + self.create_dir(dir); + } + + pub(crate) fn read_dir(&self, dir: &Path) -> impl Iterator { + let iter = match fs::read_dir(dir) { + Ok(v) => v, + Err(_) if self.config.dry_run() => return vec![].into_iter(), + Err(err) => panic!("could not read dir {dir:?}: {err:?}"), + }; + iter.map(|e| t!(e)).collect::>().into_iter() + } + + pub(crate) fn symlink_file, Q: AsRef>( + &self, + src: P, + link: Q, + ) -> io::Result<()> { + #[cfg(unix)] + use std::os::unix::fs::symlink as symlink_file; + #[cfg(windows)] + use std::os::windows::fs::symlink_file; + if !self.config.dry_run() { symlink_file(src.as_ref(), link.as_ref()) } else { Ok(()) } + } + + /// Returns if config.ninja is enabled, and checks for ninja existence, + /// exiting with a nicer error message if not. + pub(crate) fn ninja(&self) -> bool { + let mut cmd_finder = crate::core::sanity::Finder::new(); + + if self.config.ninja_in_file { + // Some Linux distros rename `ninja` to `ninja-build`. + // CMake can work with either binary name. + if cmd_finder.maybe_have("ninja-build").is_none() + && cmd_finder.maybe_have("ninja").is_none() + { + eprintln!( + " +Couldn't find required command: ninja (or ninja-build) + +You should install ninja as described at +, +or set `ninja = false` in the `[llvm]` section of `bootstrap.toml`. +Alternatively, set `download-ci-llvm = true` in that `[llvm]` section +to download LLVM rather than building it. +" + ); + helpers::exit_process(1); + } + } + + // If ninja isn't enabled but we're building for MSVC then we try + // doubly hard to enable it. It was realized in #43767 that the msbuild + // CMake generator for MSVC doesn't respect configuration options like + // disabling LLVM assertions, which can often be quite important! + // + // In these cases we automatically enable Ninja if we find it in the + // environment. + if !self.config.ninja_in_file + && self.config.host_target.is_msvc() + && cmd_finder.maybe_have("ninja").is_some() + { + return true; + } + + self.config.ninja_in_file + } + + pub(crate) fn colored_stdout R>(&self, f: F) -> R { + self.colored_stream_inner(StandardStream::stdout, self.config.stdout_is_tty, f) + } + + #[expect(dead_code, reason = "symmetric with `colored_stdout`")] + pub(crate) fn colored_stderr R>(&self, f: F) -> R { + self.colored_stream_inner(StandardStream::stderr, self.config.stderr_is_tty, f) + } + + fn colored_stream_inner(&self, constructor: C, is_tty: bool, f: F) -> R + where + C: Fn(ColorChoice) -> StandardStream, + F: FnOnce(&mut dyn WriteColor) -> R, + { + let choice = match self.config.color { + flags::Color::Always => ColorChoice::Always, + flags::Color::Never => ColorChoice::Never, + flags::Color::Auto if !is_tty => ColorChoice::Never, + flags::Color::Auto => ColorChoice::Auto, + }; + let mut stream = constructor(choice); + let result = f(&mut stream); + stream.reset().unwrap(); + result + } + + #[cfg_attr(not(feature = "tracing"), expect(dead_code))] + pub(crate) fn report_summary(&self, path: &Path, start_time: Instant) { + self.config.exec_ctx.profiler().report_summary(path, start_time); + } + + #[cfg(feature = "tracing")] + pub(crate) fn report_step_graph(self, directory: &Path) { + self.step_graph.into_inner().store_to_dot_files(directory); + } +} + +impl AsRef for Build { + fn as_ref(&self) -> &ExecutionContext { + &self.config.exec_ctx + } +} + +#[cfg(unix)] +fn chmod(path: &Path, perms: u32) { + use std::os::unix::fs::*; + t!(fs::set_permissions(path, fs::Permissions::from_mode(perms))); +} +#[cfg(windows)] +fn chmod(_path: &Path, _perms: u32) {} diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 92e36155ffa22..cafe81af4d56e 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -21,1856 +21,6 @@ #![allow(clippy::map_clone, reason = "false positive for `|x: &&Foo| Foo::clone(x)`")] // tidy-alphabetical-end -use std::cell::Cell; -use std::collections::{BTreeSet, HashMap, HashSet}; -use std::fmt::Display; -use std::path::{Path, PathBuf}; -use std::sync::OnceLock; -use std::time::{Instant, SystemTime}; -use std::{env, fs, io, str}; - -use build_helper::ci::gha; -use termcolor::{ColorChoice, StandardStream, WriteColor}; -#[cfg(feature = "tracing")] -use tracing::{instrument, span}; - -use crate::core::build_steps::format::InternalRustfmt; -use crate::core::build_steps::test::TestTarget; -use crate::core::build_steps::vendor::VENDOR_DIR; -use crate::core::builder::{Builder, Kind}; -use crate::core::compiler::Compiler; -use crate::core::config::flags::{self, Subcommand}; -use crate::core::config::{BootstrapOverrideLld, Config, DryRun, LlvmLibunwind, TargetSelection}; -use crate::core::metadata::Crate; -use crate::utils::build_stamp::BuildStamp; -use crate::utils::channel::GitInfo; -use crate::utils::exec::{BootstrapCommand, ExecutionContext, command}; -use crate::utils::helpers::{ - self, dir_is_empty, exe, libdir, set_file_times, split_debuginfo, symlink_dir, t, -}; - pub mod cli_main; mod core; mod utils; - -pub enum GitRepo { - Rustc, - Llvm, -} - -/// Global configuration for the build system. -/// -/// This structure transitively contains all configuration for the build system. -/// All filesystem-encoded configuration is in `config`, all flags are in -/// `flags`, and then parsed or probed information is listed in the keys below. -/// -/// This structure is a parameter of almost all methods in the build system, -/// although most functions are implemented as free functions rather than -/// methods specifically on this structure itself (to make it easier to -/// organize). -pub struct Build { - /// User-specified configuration from `bootstrap.toml`. - config: Config, - - // Version information - version: String, - - // Properties derived from the above configuration - src: PathBuf, - out: PathBuf, - bootstrap_out: PathBuf, - cargo_info: GitInfo, - rust_analyzer_info: GitInfo, - clippy_info: GitInfo, - miri_info: GitInfo, - rustfmt_info: GitInfo, - enzyme_info: GitInfo, - in_tree_llvm_info: GitInfo, - in_tree_gcc_info: GitInfo, - local_rebuild: bool, - fail_fast: bool, - test_target: TestTarget, - verbosity: usize, - - /// Build triple for the pre-compiled snapshot compiler. - host_target: TargetSelection, - /// Which triples to produce a compiler toolchain for. - hosts: Vec, - /// Which triples to build libraries (core/alloc/std/test/proc_macro) for. - targets: Vec, - - initial_rustc: PathBuf, - initial_rustdoc: PathBuf, - initial_cargo: PathBuf, - initial_lld: PathBuf, - initial_relative_libdir: PathBuf, - initial_sysroot: PathBuf, - - // Runtime state filled in later on - // C/C++ compilers and archiver for all targets - cc: HashMap, - cxx: HashMap, - ar: HashMap, - ranlib: HashMap, - wasi_sdk_path: Option, - - // Miscellaneous - // allow bidirectional lookups: both name -> path and path -> name - crates: HashMap, - crate_paths: HashMap, - is_sudo: bool, - prerelease_version: Cell>, - - #[cfg(feature = "build-metrics")] - metrics: crate::utils::metrics::BuildMetrics, - - #[cfg(feature = "tracing")] - step_graph: std::cell::RefCell, -} - -/// When building Rust various objects are handled differently. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum DependencyType { - /// Libraries originating from proc-macros. - Host, - /// Typical Rust libraries. - Target, - /// Non Rust libraries and objects shipped to ease usage of certain targets. - TargetSelfContained, -} - -/// The various "modes" of invoking Cargo. -/// -/// These entries currently correspond to the various output directories of the -/// build system, with each mod generating output in a different directory. -#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] -pub enum Mode { - /// Build the standard library, placing output in the "stageN-std" directory. - Std, - - /// Build librustc, and compiler libraries, placing output in the "stageN-rustc" directory. - Rustc, - - /// Build a codegen backend for rustc, placing the output in the "stageN-codegen" directory. - Codegen, - - /// Build a tool, placing output in the "bootstrap-tools" - /// directory. This is for miscellaneous sets of tools that extend - /// bootstrap. - /// - /// These tools are intended to be only executed on the host system that - /// invokes bootstrap, and they thus cannot be cross-compiled. - /// - /// They are always built using the stage0 compiler, and they - /// can be compiled with stable Rust. - /// - /// These tools also essentially do not participate in staging. - ToolBootstrap, - - /// Build a cross-compilable helper tool. These tools do not depend on unstable features or - /// compiler internals, but they might be cross-compilable (so we cannot build them using the - /// stage0 compiler, unlike `ToolBootstrap`). - /// - /// Some of these tools are also shipped in our `dist` archives. - /// While we could compile them using the stage0 compiler when not cross-compiling, we instead - /// use the in-tree compiler (and std) to build them, so that we can ship e.g. std security - /// fixes and avoid depending fully on stage0 for the artifacts that we ship. - /// - /// This mode is used e.g. for linkers and linker tools invoked by rustc on its host target. - ToolTarget, - - /// Build a tool which uses the locally built std, placing output in the - /// "stageN-tools" directory. Its usage is quite rare; historically it was - /// needed by compiletest, but now it is mainly used by `test-float-parse`. - ToolStd, - - /// Build a tool which uses the `rustc_private` mechanism, and thus - /// the locally built rustc rlib artifacts, - /// placing the output in the "stageN-tools" directory. This is used for - /// everything that links to rustc as a library, such as rustdoc, clippy, - /// rustfmt, miri, etc. - ToolRustcPrivate, -} - -impl Mode { - pub fn must_support_dlopen(&self) -> bool { - match self { - Mode::Std | Mode::Codegen => true, - Mode::ToolBootstrap - | Mode::ToolRustcPrivate - | Mode::ToolStd - | Mode::ToolTarget - | Mode::Rustc => false, - } - } -} - -/// When `rust.rust_remap_debuginfo` is requested, the compiler needs to know how to -/// opportunistically unremap compiler vs non-compiler sources. We use two schemes, -/// [`RemapScheme::Compiler`] and [`RemapScheme::NonCompiler`]. -pub enum RemapScheme { - /// The [`RemapScheme::Compiler`] scheme will remap to `/rustc-dev/{hash}`. - Compiler, - /// The [`RemapScheme::NonCompiler`] scheme will remap to `/rustc/{hash}`. - NonCompiler, -} - -#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] -pub enum CLang { - C, - Cxx, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FileType { - /// An executable binary file (like a `.exe`). - Executable, - /// A native, binary library file (like a `.so`, `.dll`, `.a`, `.lib` or `.o`). - NativeLibrary, - /// An executable (non-binary) script file (like a `.py` or `.sh`). - Script, - /// Any other regular file that is non-executable. - Regular, -} - -impl FileType { - /// Get Unix permissions appropriate for this file type. - pub fn perms(self) -> u32 { - match self { - FileType::Executable | FileType::Script => 0o755, - FileType::Regular | FileType::NativeLibrary => 0o644, - } - } - - pub fn could_have_split_debuginfo(self) -> bool { - match self { - FileType::Executable | FileType::NativeLibrary => true, - FileType::Script | FileType::Regular => false, - } - } -} - -macro_rules! forward { - ( $( $fn:ident( $($param:ident: $ty:ty),* ) $( -> $ret:ty)? ),+ $(,)? ) => { - impl Build { - $( fn $fn(&self, $($param: $ty),* ) $( -> $ret)? { - self.config.$fn( $($param),* ) - } )+ - } - } -} - -forward! { - do_if_verbose(f: impl Fn()), - is_verbose() -> bool, - create(path: &Path, s: &str), - remove(f: &Path), - tempdir() -> PathBuf, - download_rustc() -> bool, -} - -/// An alternative way of specifying what target and stage is involved in some bootstrap activity. -/// Ideally using a `Compiler` directly should be preferred. -struct TargetAndStage { - target: TargetSelection, - stage: u32, -} - -impl From<(TargetSelection, u32)> for TargetAndStage { - fn from((target, stage): (TargetSelection, u32)) -> Self { - Self { target, stage } - } -} - -impl From for TargetAndStage { - fn from(compiler: Compiler) -> Self { - Self { target: compiler.host, stage: compiler.stage } - } -} - -impl Build { - /// Creates a new set of build configuration from the `flags` on the command - /// line and the filesystem `config`. - /// - /// By default all build output will be placed in the current directory. - pub(crate) fn new(mut config: Config) -> Build { - let src = config.src.clone(); - let out = config.out.clone(); - - #[cfg(unix)] - // keep this consistent with the equivalent check in x.py: - // https://github.com/rust-lang/rust/blob/a8a33cf27166d3eabaffc58ed3799e054af3b0c6/src/bootstrap/bootstrap.py#L796-L797 - let is_sudo = match env::var_os("SUDO_USER") { - Some(_sudo_user) => { - // SAFETY: getuid() system call is always successful and no return value is reserved - // to indicate an error. - // - // For more context, see https://man7.org/linux/man-pages/man2/geteuid.2.html - let uid = unsafe { libc::getuid() }; - uid == 0 - } - None => false, - }; - #[cfg(not(unix))] - let is_sudo = false; - - let rust_info = config.rust_info.clone(); - let cargo_info = config.cargo_info.clone(); - let rust_analyzer_info = config.rust_analyzer_info.clone(); - let clippy_info = config.clippy_info.clone(); - let miri_info = config.miri_info.clone(); - let rustfmt_info = config.rustfmt_info.clone(); - let enzyme_info = config.enzyme_info.clone(); - let in_tree_llvm_info = config.in_tree_llvm_info.clone(); - let in_tree_gcc_info = config.in_tree_gcc_info.clone(); - - let initial_target_libdir = command(&config.initial_rustc) - .run_in_dry_run() - .args(["--print", "target-libdir"]) - .run_capture_stdout(&config) - .stdout() - .trim() - .to_owned(); - - let initial_target_dir = Path::new(&initial_target_libdir) - .parent() - .unwrap_or_else(|| panic!("{initial_target_libdir} has no parent")); - - let initial_lld = initial_target_dir.join("bin").join("rust-lld"); - - let initial_relative_libdir = if cfg!(test) { - // On tests, bootstrap uses the shim rustc, not the one from the stage0 toolchain. - PathBuf::default() - } else { - let ancestor = initial_target_dir.ancestors().nth(2).unwrap_or_else(|| { - panic!("Not enough ancestors for {}", initial_target_dir.display()) - }); - - ancestor - .strip_prefix(&config.initial_sysroot) - .unwrap_or_else(|_| { - panic!( - "Couldn’t resolve the initial relative libdir from {}", - initial_target_dir.display() - ) - }) - .to_path_buf() - }; - - let version = std::fs::read_to_string(src.join("src").join("version")) - .expect("failed to read src/version"); - let version = version.trim(); - - let mut bootstrap_out = std::env::current_exe() - .expect("could not determine path to running process") - .parent() - .unwrap() - .to_path_buf(); - // Since bootstrap is hardlink to deps/bootstrap-*, Solaris can sometimes give - // path with deps/ which is bad and needs to be avoided. - if bootstrap_out.ends_with("deps") { - bootstrap_out.pop(); - } - if !bootstrap_out.join(exe("rustc", config.host_target)).exists() && !cfg!(test) { - // this restriction can be lifted whenever https://github.com/rust-lang/rfcs/pull/3028 is implemented - panic!( - "`rustc` not found in {}, run `cargo build --bins` before `cargo run`", - bootstrap_out.display() - ) - } - - if rust_info.is_from_tarball() && config.description.is_none() { - config.description = Some("built from a source tarball".to_owned()); - } - - let mut build = Build { - initial_lld, - initial_relative_libdir, - initial_rustc: config.initial_rustc.clone(), - initial_rustdoc: config.initial_rustdoc.clone(), - initial_cargo: config.initial_cargo.clone(), - initial_sysroot: config.initial_sysroot.clone(), - local_rebuild: config.local_rebuild, - fail_fast: config.cmd.fail_fast(), - test_target: config.cmd.test_target(), - verbosity: config.exec_ctx.verbosity as usize, - - host_target: config.host_target, - hosts: config.hosts.clone(), - targets: config.targets.clone(), - - config, - version: version.to_string(), - src, - out, - bootstrap_out, - - cargo_info, - rust_analyzer_info, - clippy_info, - miri_info, - rustfmt_info, - enzyme_info, - in_tree_llvm_info, - in_tree_gcc_info, - cc: HashMap::new(), - cxx: HashMap::new(), - ar: HashMap::new(), - ranlib: HashMap::new(), - wasi_sdk_path: env::var_os("WASI_SDK_PATH").map(PathBuf::from), - crates: HashMap::new(), - crate_paths: HashMap::new(), - is_sudo, - prerelease_version: Cell::new(None), - - #[cfg(feature = "build-metrics")] - metrics: crate::utils::metrics::BuildMetrics::init(), - - #[cfg(feature = "tracing")] - step_graph: std::cell::RefCell::new(crate::utils::step_graph::StepGraph::default()), - }; - - // If local-rust is the same major.minor as the current version, then force a - // local-rebuild - let local_version_verbose = command(&build.initial_rustc) - .run_in_dry_run() - .args(["--version", "--verbose"]) - .run_capture_stdout(&build) - .stdout(); - let local_release = local_version_verbose - .lines() - .filter_map(|x| x.strip_prefix("release:")) - .next() - .unwrap() - .trim(); - if local_release.split('.').take(2).eq(version.split('.').take(2)) { - build.do_if_verbose(|| println!("auto-detected local-rebuild {local_release}")); - build.local_rebuild = true; - } - - build.do_if_verbose(|| println!("finding compilers")); - crate::utils::cc_detect::fill_compilers(&mut build); - // When running `setup`, the profile is about to change, so any requirements we have now may - // be different on the next invocation. Don't check for them until the next time x.py is - // run. This is ok because `setup` never runs any build commands, so it won't fail if commands are missing. - // - // Similarly, for `setup` we don't actually need submodules or cargo metadata. - if !matches!(build.config.cmd, Subcommand::Setup { .. }) { - build.do_if_verbose(|| println!("running sanity check")); - crate::core::sanity::check(&mut build); - - // Make sure we update these before gathering metadata so we don't get an error about missing - // Cargo.toml files. - let rust_submodules = ["library/backtrace"]; - for s in rust_submodules { - build.require_submodule( - s, - Some( - "The submodule is required for the standard library \ - and the main Cargo workspace.", - ), - ); - } - // Now, update all existing submodules. - build.update_existing_submodules(); - - build.do_if_verbose(|| println!("learning about cargo")); - crate::core::metadata::build(&mut build); - } - - // Create symbolic link to use host sysroot from a consistent path (e.g., in the rust-analyzer config file). - let build_triple = build.out.join(build.host_target); - t!(fs::create_dir_all(&build_triple)); - let host = build.out.join("host"); - if host.is_symlink() { - // Left over from a previous build; overwrite it. - // This matters if `build.build` has changed between invocations. - #[cfg(windows)] - t!(fs::remove_dir(&host)); - #[cfg(not(windows))] - t!(fs::remove_file(&host)); - } - t!( - symlink_dir(&build.config, &build_triple, &host), - format!("symlink_dir({} => {}) failed", host.display(), build_triple.display()) - ); - - build - } - - /// Updates a submodule, and exits with a failure if submodule management - /// is disabled and the submodule does not exist. - /// - /// The given submodule name should be its path relative to the root of - /// the main repository. - /// - /// The given `err_hint` will be shown to the user if the submodule is not - /// checked out and submodule management is disabled. - #[cfg_attr( - feature = "tracing", - instrument( - level = "trace", - name = "Build::require_submodule", - skip_all, - fields(submodule = submodule), - ), - )] - pub fn require_submodule(&self, submodule: &str, err_hint: Option<&str>) { - if self.rust_info().is_from_tarball() { - return; - } - - if self.config.dry_run() { - return; - } - - // When testing bootstrap itself, it is much faster to ignore - // submodules. Almost all Steps work fine without their submodules. - if cfg!(test) && !self.config.submodules() { - return; - } - self.config.update_submodule(submodule); - let absolute_path = self.config.src.join(submodule); - if !absolute_path.exists() || dir_is_empty(&absolute_path) { - let maybe_enable = if !self.config.submodules() - && self.config.rust_info.is_managed_git_subrepository() - { - "\nConsider setting `build.submodules = true` or manually initializing the submodules." - } else { - "" - }; - let err_hint = err_hint.map_or_else(String::new, |e| format!("\n{e}")); - eprintln!( - "submodule {submodule} does not appear to be checked out, \ - but it is required for this step{maybe_enable}{err_hint}" - ); - helpers::exit_process(1); - } - } - - /// If any submodule has been initialized already, sync it unconditionally. - /// This avoids contributors checking in a submodule change by accident. - fn update_existing_submodules(&self) { - // Avoid running git when there isn't a git checkout, or the user has - // explicitly disabled submodules in `bootstrap.toml`. - if !self.config.submodules() { - return; - } - let output = helpers::git(Some(&self.src)) - .args(["config", "--file"]) - .arg(".gitmodules") - .args(["--get-regexp", "path"]) - .run_capture(self) - .stdout(); - std::thread::scope(|s| { - // Look for `submodule.$name.path = $path` - // Sample output: `submodule.src/rust-installer.path src/tools/rust-installer` - for line in output.lines() { - let submodule = line.split_once(' ').unwrap().1; - let config = self.config.clone(); - s.spawn(move || { - Self::update_existing_submodule(&config, submodule); - }); - } - }); - } - - /// Updates the given submodule only if it's initialized already; nothing happens otherwise. - pub(crate) fn update_existing_submodule(config: &Config, submodule: &str) { - // Avoid running git when there isn't a git checkout. - if !config.submodules() { - return; - } - - if config.git_info(false, Path::new(submodule)).is_managed_git_subrepository() { - config.update_submodule(submodule); - } - } - - /// Executes the entire build, as configured by the flags and configuration. - #[cfg_attr(feature = "tracing", instrument(level = "debug", name = "Build::build", skip_all))] - pub fn build(&mut self) { - trace!("setting up job management"); - unsafe { - crate::utils::job::setup(self); - } - - // Handle hard-coded subcommands. - { - #[cfg(feature = "tracing")] - let _hardcoded_span = - span!(tracing::Level::DEBUG, "handling hardcoded subcommands (Format, Perf)") - .entered(); - - match &self.config.cmd { - Subcommand::Format { check, all } => { - let builder = Builder::new(self); - let rustfmt_path = builder.ensure(InternalRustfmt).unwrap_or_else(|| { - eprintln!("fmt error: `x fmt` is not supported on this channel"); - helpers::exit_process(1); - }); - return crate::core::build_steps::format::format( - &builder, - rustfmt_path, - *check, - *all, - &self.config.paths, - ); - } - Subcommand::Perf(args) => { - return crate::core::build_steps::perf::perf(&Builder::new(self), args); - } - _cmd => { - debug!(cmd = ?_cmd, "not a hardcoded subcommand; returning to normal handling"); - } - } - - debug!("handling subcommand normally"); - } - - if !self.config.dry_run() { - #[cfg(feature = "tracing")] - let _real_run_span = span!(tracing::Level::DEBUG, "executing real run").entered(); - - // We first do a dry-run. This is a sanity-check to ensure that - // steps don't do anything expensive in the dry-run. - { - #[cfg(feature = "tracing")] - let _sanity_check_span = - span!(tracing::Level::DEBUG, "(1) executing dry-run sanity-check").entered(); - self.config.set_dry_run(DryRun::SelfCheck); - let builder = Builder::new(self); - builder.execute_cli(); - } - - // Actual run. - { - #[cfg(feature = "tracing")] - let _actual_run_span = - span!(tracing::Level::DEBUG, "(2) executing actual run").entered(); - self.config.set_dry_run(DryRun::Disabled); - let builder = Builder::new(self); - builder.execute_cli(); - } - } else { - #[cfg(feature = "tracing")] - let _dry_run_span = span!(tracing::Level::DEBUG, "executing dry run").entered(); - - let builder = Builder::new(self); - builder.execute_cli(); - } - - #[cfg(feature = "tracing")] - debug!("checking for postponed test failures from `test --no-fail-fast`"); - - // Check for postponed failures from `test --no-fail-fast`. - self.config.exec_ctx().report_failures_and_exit(); - - #[cfg(feature = "build-metrics")] - self.metrics.persist(self); - } - - fn rust_info(&self) -> &GitInfo { - &self.config.rust_info - } - - /// Gets the space-separated set of activated features for the standard library. - /// This can be configured with the `std-features` key in bootstrap.toml. - fn std_features(&self, target: TargetSelection) -> String { - let mut features: BTreeSet<&str> = - self.config.rust_std_features.iter().map(|s| s.as_str()).collect(); - - match self.config.llvm_libunwind(target) { - LlvmLibunwind::InTree => features.insert("llvm-libunwind"), - LlvmLibunwind::System => features.insert("system-llvm-libunwind"), - LlvmLibunwind::No => false, - }; - - if self.config.backtrace { - features.insert("backtrace"); - } - - if self.config.profiler_enabled(target) { - features.insert("profiler"); - } - - // If zkvm target, generate memcpy, etc. - if target.contains("zkvm") { - features.insert("compiler-builtins-mem"); - } - - features.into_iter().collect::>().join(" ") - } - - /// Gets the space-separated set of activated features for the compiler. - fn rustc_features(&self, kind: Kind, target: TargetSelection, crates: &[String]) -> String { - let possible_features_by_crates: HashSet<_> = crates - .iter() - .flat_map(|krate| &self.crates[krate].features) - .map(std::ops::Deref::deref) - .collect(); - let check = |feature: &str| -> bool { - crates.is_empty() || possible_features_by_crates.contains(feature) - }; - let mut features = vec![]; - - if let Some(allocator_feature_name) = self.config.allocator(target).feature_name() - && check(allocator_feature_name) - { - features.push(allocator_feature_name); - } - if (self.config.llvm_enabled(target) || kind == Kind::Check) && check("llvm") { - features.push("llvm"); - } - if self.config.llvm_offload { - features.push("llvm_offload"); - } - // keep in sync with `bootstrap/compile.rs:rustc_cargo_env` - if self.config.rust_randomize_layout && check("rustc_randomized_layouts") { - features.push("rustc_randomized_layouts"); - } - if self.config.compile_time_deps && kind == Kind::Check { - features.push("check_only"); - } - - if crates.iter().any(|c| c == "rustc_transmute") { - // for `x test rustc_transmute`, this feature isn't enabled automatically by a - // dependent crate. - features.push("rustc"); - } - - // If debug logging is on, then we want the default for tracing: - // https://github.com/tokio-rs/tracing/blob/3dd5c03d907afdf2c39444a29931833335171554/tracing/src/level_filters.rs#L26 - // which is everything (including debug/trace/etc.) - // if its unset, if debug_assertions is on, then debug_logging will also be on - // as well as tracing *ignoring* this feature when debug_assertions is on - if !self.config.rust_debug_logging && check("max_level_info") { - features.push("max_level_info"); - } - - features.join(" ") - } - - /// Component directory that Cargo will produce output into (e.g. - /// release/debug) - fn cargo_dir(&self, mode: Mode) -> &'static str { - match (mode, self.config.rust_optimize.is_release()) { - (Mode::Std, _) => "dist", - (_, true) => "release", - (_, false) => "debug", - } - } - - fn tools_dir(&self, build_compiler: Compiler) -> PathBuf { - let out = self - .out - .join(build_compiler.host) - .join(format!("stage{}-tools-bin", build_compiler.stage + 1)); - t!(fs::create_dir_all(&out)); - out - } - - /// Returns the root directory for all output generated in a particular - /// stage when being built with a particular build compiler. - /// - /// The mode indicates what the root directory is for. - fn stage_out(&self, build_compiler: Compiler, mode: Mode) -> PathBuf { - use std::fmt::Write; - - fn bootstrap_tool() -> (Option, &'static str) { - (None, "bootstrap-tools") - } - fn staged_tool(build_compiler: Compiler) -> (Option, &'static str) { - (Some(build_compiler.stage + 1), "tools") - } - - let (stage, suffix) = match mode { - // Std is special, stage N std is built with stage N rustc - Mode::Std => (Some(build_compiler.stage), "std"), - // The rest of things are built with stage N-1 rustc - Mode::Rustc => (Some(build_compiler.stage + 1), "rustc"), - Mode::Codegen => (Some(build_compiler.stage + 1), "codegen"), - Mode::ToolBootstrap => bootstrap_tool(), - Mode::ToolStd | Mode::ToolRustcPrivate => (Some(build_compiler.stage + 1), "tools"), - Mode::ToolTarget => { - // If we're not cross-compiling (the common case), share the target directory with - // bootstrap tools to reuse the build cache. - if build_compiler.stage == 0 { - bootstrap_tool() - } else { - staged_tool(build_compiler) - } - } - }; - let path = self.out.join(build_compiler.host); - let mut dir_name = String::new(); - if let Some(stage) = stage { - write!(dir_name, "stage{stage}-").unwrap(); - } - dir_name.push_str(suffix); - path.join(dir_name) - } - - /// Returns the root output directory for all Cargo output in a given stage, - /// running a particular compiler, whether or not we're building the - /// standard library, and targeting the specified architecture. - fn cargo_out(&self, build_compiler: Compiler, mode: Mode, target: TargetSelection) -> PathBuf { - self.stage_out(build_compiler, mode).join(target).join(self.cargo_dir(mode)) - } - - /// Output directory for all documentation for a target - fn doc_out(&self, target: TargetSelection) -> PathBuf { - self.out.join(target).join("doc") - } - - /// Output directory for all JSON-formatted documentation for a target - fn json_doc_out(&self, target: TargetSelection) -> PathBuf { - self.out.join(target).join("json-doc") - } - - fn test_out(&self, target: TargetSelection) -> PathBuf { - self.out.join(target).join("test") - } - - /// Output directory for all documentation for a target - fn compiler_doc_out(&self, target: TargetSelection) -> PathBuf { - self.out.join(target).join("compiler-doc") - } - - /// Output directory for some generated md crate documentation for a target (temporary) - fn md_doc_out(&self, target: TargetSelection) -> PathBuf { - self.out.join(target).join("md-doc") - } - - /// Path to the vendored Rust crates. - fn vendored_crates_path(&self) -> Option { - if self.config.vendor { Some(self.src.join(VENDOR_DIR)) } else { None } - } - - /// Directory for libraries built from C/C++ code and shared between stages. - fn native_dir(&self, target: TargetSelection) -> PathBuf { - self.out.join(target).join("native") - } - - /// Root output directory for rust_test_helpers library compiled for - /// `target` - fn test_helpers_out(&self, target: TargetSelection) -> PathBuf { - self.native_dir(target).join("rust-test-helpers") - } - - /// Adds the `RUST_TEST_THREADS` env var if necessary - fn add_rust_test_threads(&self, cmd: &mut BootstrapCommand) { - if env::var_os("RUST_TEST_THREADS").is_none() { - cmd.env("RUST_TEST_THREADS", self.jobs().to_string()); - } - } - - /// Returns the libdir of the snapshot compiler. - fn rustc_snapshot_libdir(&self) -> PathBuf { - self.rustc_snapshot_sysroot().join(libdir(self.config.host_target)) - } - - /// Returns the sysroot of the snapshot compiler. - fn rustc_snapshot_sysroot(&self) -> &Path { - static SYSROOT_CACHE: OnceLock = OnceLock::new(); - SYSROOT_CACHE.get_or_init(|| { - command(&self.initial_rustc) - .run_in_dry_run() - .args(["--print", "sysroot"]) - .run_capture_stdout(self) - .stdout() - .trim() - .to_owned() - .into() - }) - } - - fn info(&self, msg: &str) { - match self.config.get_dry_run() { - DryRun::SelfCheck => (), - DryRun::Disabled | DryRun::UserSelected => { - println!("{msg}"); - } - } - } - - /// Return a `Group` guard for a [`Step`] that: - /// - Performs `action` - /// - If the action is `Kind::Test`, use [`Build::msg_test`] instead. - /// - On `what` - /// - Where `what` possibly corresponds to a `mode` - /// - `action` is performed with/on the given compiler (`target_and_stage`). - /// - Since for some steps it is not possible to pass a single compiler here, it is also - /// possible to pass the host and stage explicitly. - /// - With a given `target`. - /// - /// [`Step`]: crate::core::builder::Step - #[must_use = "Groups should not be dropped until the Step finishes running"] - #[track_caller] - fn msg( - &self, - action: impl Into, - what: impl Display, - mode: impl Into>, - target_and_stage: impl Into, - target: impl Into>, - ) -> Option { - let target_and_stage = target_and_stage.into(); - let action = action.into(); - assert!( - action != Kind::Test, - "Please use `Build::msg_test` instead of `Build::msg(Kind::Test)`" - ); - - let actual_stage = match mode.into() { - // Std has the same stage as the compiler that builds it - Some(Mode::Std) => target_and_stage.stage, - // Other things have stage corresponding to their build compiler + 1 - Some( - Mode::Rustc - | Mode::Codegen - | Mode::ToolBootstrap - | Mode::ToolTarget - | Mode::ToolStd - | Mode::ToolRustcPrivate, - ) - | None => target_and_stage.stage + 1, - }; - - let action = action.description(); - let what = what.to_string(); - let msg = |fmt| { - let space = if !what.is_empty() { " " } else { "" }; - format!("{action} stage{actual_stage} {what}{space}{fmt}") - }; - let msg = if let Some(target) = target.into() { - let build_stage = target_and_stage.stage; - let host = target_and_stage.target; - if host == target { - msg(format_args!("(stage{build_stage} -> stage{actual_stage}, {target})")) - } else { - msg(format_args!("(stage{build_stage}:{host} -> stage{actual_stage}:{target})")) - } - } else { - msg(format_args!("")) - }; - self.group(&msg) - } - - /// Return a `Group` guard for a [`Step`] that tests `what` with the given `stage` and `target`. - /// Use this instead of [`Build::msg`] for test steps, because for them it is not always clear - /// what exactly is a build compiler. - /// - /// [`Step`]: crate::core::builder::Step - #[must_use = "Groups should not be dropped until the Step finishes running"] - #[track_caller] - fn msg_test( - &self, - what: impl Display, - target: TargetSelection, - stage: u32, - ) -> Option { - let action = Kind::Test.description(); - let msg = format!("{action} stage{stage} {what} ({target})"); - self.group(&msg) - } - - /// Return a `Group` guard for a [`Step`] that is only built once and isn't affected by `--stage`. - /// - /// [`Step`]: crate::core::builder::Step - #[must_use = "Groups should not be dropped until the Step finishes running"] - #[track_caller] - fn msg_unstaged( - &self, - action: impl Into, - what: impl Display, - target: TargetSelection, - ) -> Option { - let action = action.into().description(); - let msg = format!("{action} {what} for {target}"); - self.group(&msg) - } - - #[track_caller] - fn group(&self, msg: &str) -> Option { - match self.config.get_dry_run() { - DryRun::SelfCheck => None, - DryRun::Disabled | DryRun::UserSelected => Some(gha::group(msg)), - } - } - - /// Returns the number of parallel jobs that have been configured for this - /// build. - fn jobs(&self) -> u32 { - self.config.jobs.unwrap_or_else(|| { - std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32 - }) - } - - fn debuginfo_map_to(&self, which: GitRepo, remap_scheme: RemapScheme) -> Option { - if !self.config.rust_remap_debuginfo { - return None; - } - - match which { - GitRepo::Rustc => { - let sha = self.rust_sha().unwrap_or(&self.version); - - match remap_scheme { - RemapScheme::Compiler => { - // For compiler sources, remap via `/rustc-dev/{sha}` to allow - // distinguishing between compiler sources vs library sources, since - // `rustc-dev` dist component places them under - // `$sysroot/lib/rustlib/rustc-src/rust` as opposed to `rust-src`'s - // `$sysroot/lib/rustlib/src/rust`. - // - // Keep this scheme in sync with `rustc_metadata::rmeta::decoder`'s - // `try_to_translate_virtual_to_real`. - Some(format!("/rustc-dev/{sha}")) - } - RemapScheme::NonCompiler => { - // For non-compiler sources, use `/rustc/{sha}` remapping scheme. - Some(format!("/rustc/{sha}")) - } - } - } - GitRepo::Llvm => Some(String::from("/rustc/llvm")), - } - } - - /// Returns the path to the C compiler for the target specified. - fn cc(&self, target: TargetSelection) -> PathBuf { - if self.config.dry_run() { - return PathBuf::new(); - } - self.cc[&target].path().into() - } - - /// Returns the internal `cc::Tool` for the C compiler. - fn cc_tool(&self, target: TargetSelection) -> cc::Tool { - self.cc[&target].clone() - } - - /// Returns the internal `cc::Tool` for the C++ compiler. - fn cxx_tool(&self, target: TargetSelection) -> cc::Tool { - self.cxx[&target].clone() - } - - /// Returns C flags that `cc-rs` thinks should be enabled for the - /// specified target by default. - fn cc_handled_cflags(&self, target: TargetSelection, c: CLang) -> Vec { - if self.config.dry_run() { - return Vec::new(); - } - let base = match c { - CLang::C => self.cc[&target].clone(), - CLang::Cxx => self.cxx[&target].clone(), - }; - - // Filter out -O and /O (the optimization flags) that we picked up - // from cc-rs, that's up to the caller to figure out. - base.args() - .iter() - .map(|s| s.to_string_lossy().into_owned()) - .filter(|s| !s.starts_with("-O") && !s.starts_with("/O")) - .collect::>() - } - - /// Returns extra C flags that `cc-rs` doesn't handle. - fn cc_unhandled_cflags( - &self, - target: TargetSelection, - which: GitRepo, - c: CLang, - ) -> Vec { - let mut base = Vec::new(); - - // If we're compiling C++ on macOS then we add a flag indicating that - // we want libc++ (more filled out than libstdc++), ensuring that - // LLVM/etc are all properly compiled. - if matches!(c, CLang::Cxx) && target.contains("apple-darwin") { - base.push("-stdlib=libc++".into()); - } - - // Work around an apparently bad MinGW / GCC optimization, - // See: https://lists.llvm.org/pipermail/cfe-dev/2016-December/051980.html - // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78936 - if &*target.triple == "i686-pc-windows-gnu" { - base.push("-fno-omit-frame-pointer".into()); - } - - if let Some(map_to) = self.debuginfo_map_to(which, RemapScheme::NonCompiler) { - let map = format!("{}={}", self.src.display(), map_to); - let cc = self.cc_tool(target); - if cc.is_like_clang() || cc.is_like_gnu() { - base.push(format!("-fdebug-prefix-map={map}")); - } else if cc.is_like_clang_cl() { - base.push("-Xclang".into()); - base.push(format!("-fdebug-prefix-map={map}")); - } - } - base - } - - /// Returns the path to the `ar` archive utility for the target specified. - fn ar(&self, target: TargetSelection) -> Option { - if self.config.dry_run() { - return None; - } - self.ar.get(&target).cloned() - } - - /// Returns the path to the `ranlib` utility for the target specified. - fn ranlib(&self, target: TargetSelection) -> Option { - if self.config.dry_run() { - return None; - } - self.ranlib.get(&target).cloned() - } - - /// Returns the path to the C++ compiler for the target specified. - fn cxx(&self, target: TargetSelection) -> Result { - if self.config.dry_run() { - return Ok(PathBuf::new()); - } - match self.cxx.get(&target) { - Some(p) => Ok(p.path().into()), - None => Err(format!("target `{target}` is not configured as a host, only as a target")), - } - } - - /// Returns the path to the linker for the given target if it needs to be overridden. - fn linker(&self, target: TargetSelection) -> Option { - if self.config.dry_run() { - return Some(PathBuf::new()); - } - if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.clone()) - { - Some(linker) - } else if target.contains("vxworks") { - // need to use CXX compiler as linker to resolve the exception functions - // that are only existed in CXX libraries - Some(self.cxx[&target].path().into()) - } else if !self.config.is_host_target(target) - && helpers::use_host_linker(target) - && !target.is_msvc() - { - Some(self.cc(target)) - } else if self.config.bootstrap_override_lld.is_used() - && self.is_lld_direct_linker(target) - && self.host_target == target - { - match self.config.bootstrap_override_lld { - BootstrapOverrideLld::SelfContained => Some(self.initial_lld.clone()), - BootstrapOverrideLld::External => Some("lld".into()), - BootstrapOverrideLld::None => None, - } - } else { - None - } - } - - // Is LLD configured directly through `-Clinker`? - // Only MSVC targets use LLD directly at the moment. - fn is_lld_direct_linker(&self, target: TargetSelection) -> bool { - target.is_msvc() - } - - /// Returns if this target should statically link the C runtime, if specified - fn crt_static(&self, target: TargetSelection) -> Option { - if target.contains("pc-windows-msvc") { - Some(true) - } else { - self.config.target_config.get(&target).and_then(|t| t.crt_static) - } - } - - /// Returns the "musl root" for this `target`, if defined. - /// - /// If this is a native target (host is also musl) and no musl-root is given, - /// it falls back to the system toolchain in /usr. - fn musl_root(&self, target: TargetSelection) -> Option<&Path> { - let configured_root = self - .config - .target_config - .get(&target) - .and_then(|t| t.musl_root.as_ref()) - .or(self.config.musl_root.as_ref()) - .map(|p| &**p); - - if self.config.is_host_target(target) && configured_root.is_none() { - Some(Path::new("/usr")) - } else { - configured_root - } - } - - /// Returns the "musl libdir" for this `target`. - fn musl_libdir(&self, target: TargetSelection) -> Option { - self.config - .target_config - .get(&target) - .and_then(|t| t.musl_libdir.clone()) - .or_else(|| self.musl_root(target).map(|root| root.join("lib"))) - } - - /// Returns the `lib` directory for the WASI target specified, if - /// configured. - /// - /// This first consults `wasi-root` as configured in per-target - /// configuration, and failing that it assumes that `$WASI_SDK_PATH` is - /// set in the environment, and failing that `None` is returned. - fn wasi_libdir(&self, target: TargetSelection) -> Option { - let configured = - self.config.target_config.get(&target).and_then(|t| t.wasi_root.as_ref()).map(|p| &**p); - if let Some(path) = configured { - return Some(path.join("lib").join(target.to_string())); - } - let mut env_root = self.wasi_sdk_path.clone()?; - env_root.push("share"); - env_root.push("wasi-sysroot"); - env_root.push("lib"); - env_root.push(target.to_string()); - Some(env_root) - } - - /// Returns `true` if this is a no-std `target`, if defined - fn no_std(&self, target: TargetSelection) -> Option { - self.config.target_config.get(&target).map(|t| t.no_std) - } - - /// Returns `true` if the target will be tested using the `remote-test-client` - /// and `remote-test-server` binaries. - fn remote_tested(&self, target: TargetSelection) -> bool { - self.qemu_rootfs(target).is_some() - || target.contains("android") - || env::var_os("TEST_DEVICE_ADDR").is_some() - } - - /// Returns an optional "runner" to pass to `compiletest` when executing - /// test binaries. - /// - /// An example of this would be a WebAssembly runtime when testing the wasm - /// targets. - fn runner(&self, target: TargetSelection) -> Option { - let configured_runner = - self.config.target_config.get(&target).and_then(|t| t.runner.as_ref()).map(|p| &**p); - if let Some(runner) = configured_runner { - return Some(runner.to_owned()); - } - - if target.starts_with("wasm") && target.contains("wasi") { - self.default_wasi_runner(target) - } else { - None - } - } - - /// When a `runner` configuration is not provided and a WASI-looking target - /// is being tested this is consulted to prove the environment to see if - /// there's a runtime already lying around that seems reasonable to use. - fn default_wasi_runner(&self, target: TargetSelection) -> Option { - let mut finder = crate::core::sanity::Finder::new(); - - // Look for Wasmtime, and for its default options be sure to disable - // its caching system since we're executing quite a lot of tests and - // ideally shouldn't pollute the cache too much. - if let Some(path) = finder.maybe_have("wasmtime") - && let Ok(mut path) = path.into_os_string().into_string() - { - path.push_str(" run -Wexceptions -C cache=n --dir ."); - // Make sure that tests have access to RUSTC_BOOTSTRAP. This (for example) is - // required for libtest to work on beta/stable channels. - // - // NB: with Wasmtime 20 this can change to `-S inherit-env` to - // inherit the entire environment rather than just this single - // environment variable. - path.push_str(" --env RUSTC_BOOTSTRAP"); - - if target.contains("wasip2") { - path.push_str(" --wasi inherit-network --wasi allow-ip-name-lookup"); - } - - return Some(path); - } - - None - } - - /// Returns whether the specified tool is configured as part of this build. - /// - /// This requires that both the `extended` key is set and the `tools` key is - /// either unset or specifically contains the specified tool. - fn tool_enabled(&self, tool: &str) -> bool { - if !self.config.extended { - return false; - } - match &self.config.tools { - Some(set) => set.contains(tool), - None => true, - } - } - - /// Returns the root of the "rootfs" image that this target will be using, - /// if one was configured. - /// - /// If `Some` is returned then that means that tests for this target are - /// emulated with QEMU and binaries will need to be shipped to the emulator. - fn qemu_rootfs(&self, target: TargetSelection) -> Option<&Path> { - self.config.target_config.get(&target).and_then(|t| t.qemu_rootfs.as_ref()).map(|p| &**p) - } - - /// Temporary directory that extended error information is emitted to. - fn extended_error_dir(&self) -> PathBuf { - self.out.join("tmp/extended-error-metadata") - } - - /// Tests whether the `compiler` compiling for `target` should be forced to - /// use a stage1 compiler instead. - /// - /// Currently, by default, the build system does not perform a "full - /// bootstrap" by default where we compile the compiler three times. - /// Instead, we compile the compiler two times. The final stage (stage2) - /// just copies the libraries from the previous stage, which is what this - /// method detects. - /// - /// Here we return `true` if: - /// - /// * The build isn't performing a full bootstrap - /// * The `compiler` is in the final stage, 2 - /// * We're not cross-compiling, so the artifacts are already available in - /// stage1 - /// - /// When all of these conditions are met the build will lift artifacts from - /// the previous stage forward. - fn force_use_stage1(&self, stage: u32, target: TargetSelection) -> bool { - !self.config.full_bootstrap - && !self.config.download_rustc() - && stage >= 2 - && (self.hosts.contains(&target) || target == self.host_target) - } - - /// Checks whether the `compiler` compiling for `target` should be forced to - /// use a stage2 compiler instead. - /// - /// When we download the pre-compiled version of rustc and compiler stage is >= 2, - /// it should be forced to use a stage2 compiler. - fn force_use_stage2(&self, stage: u32) -> bool { - self.config.download_rustc() && stage >= 2 - } - - /// Given `num` in the form "a.b.c" return a "release string" which - /// describes the release version number. - /// - /// For example on nightly this returns "a.b.c-nightly", on beta it returns - /// "a.b.c-beta.1" and on stable it just returns "a.b.c". - fn release(&self, num: &str) -> String { - match &self.config.channel[..] { - "stable" => num.to_string(), - "beta" => { - if !self.config.omit_git_hash { - format!("{}-beta.{}", num, self.beta_prerelease_version()) - } else { - format!("{num}-beta") - } - } - "nightly" => format!("{num}-nightly"), - _ => format!("{num}-dev"), - } - } - - fn beta_prerelease_version(&self) -> u32 { - fn extract_beta_rev_from_file>(version_file: P) -> Option { - let version = fs::read_to_string(version_file).ok()?; - - helpers::extract_beta_rev(&version) - } - - if let Some(s) = self.prerelease_version.get() { - return s; - } - - // First check if there is a version file available. - // If available, we read the beta revision from that file. - // This only happens when building from a source tarball when Git should not be used. - let count = extract_beta_rev_from_file(self.src.join("version")).unwrap_or_else(|| { - // Figure out how many merge commits happened since we branched off main. - // That's our beta number! - // (Note that we use a `..` range, not the `...` symmetric difference.) - helpers::git(Some(&self.src)) - .arg("rev-list") - .arg("--count") - .arg("--merges") - .arg(format!( - "refs/remotes/origin/{}..HEAD", - self.config.stage0_metadata.config.nightly_branch - )) - .run_in_dry_run() - .run_capture(self) - .stdout() - }); - let n = count.trim().parse().unwrap(); - self.prerelease_version.set(Some(n)); - n - } - - /// Returns the value of `release` above for Rust itself. - fn rust_release(&self) -> String { - self.release(&self.version) - } - - /// Returns the "package version" for a component. - /// - /// The package version is typically what shows up in the names of tarballs. - /// For channels like beta/nightly it's just the channel name, otherwise it's the release - /// version. - fn rust_package_vers(&self) -> String { - match &self.config.channel[..] { - "stable" => self.version.to_string(), - "beta" => "beta".to_string(), - "nightly" => "nightly".to_string(), - _ => format!("{}-dev", self.version), - } - } - - /// Returns the `version` string associated with this compiler for Rust - /// itself. - /// - /// Note that this is a descriptive string which includes the commit date, - /// sha, version, etc. - fn rust_version(&self) -> String { - let mut version = self.rust_info().version(self, &self.version); - if let Some(ref s) = self.config.description - && !s.is_empty() - { - version.push_str(" ("); - version.push_str(s); - version.push(')'); - } - version - } - - /// Returns the full commit hash. - fn rust_sha(&self) -> Option<&str> { - self.rust_info().sha() - } - - /// Returns the `a.b.c` version that the given package is at. - fn release_num(&self, package: &str) -> String { - if self.config.dry_run() { - return "0.0.0 (dry-run)".into(); - } - let toml_file_name = self.src.join(format!("src/tools/{package}/Cargo.toml")); - let toml = t!(fs::read_to_string(toml_file_name)); - for line in toml.lines() { - if let Some(stripped) = - line.strip_prefix("version = \"").and_then(|s| s.strip_suffix('"')) - { - return stripped.to_owned(); - } - } - - panic!("failed to find version in {package}'s Cargo.toml") - } - - /// Returns `true` if unstable features should be enabled for the compiler - /// we're building. - fn unstable_features(&self) -> bool { - !matches!(&self.config.channel[..], "stable" | "beta") - } - - /// Returns a Vec of all the dependencies of the given root crate, - /// including transitive dependencies and the root itself. Only includes - /// "local" crates (those in the local source tree, not from a registry). - fn in_tree_crates(&self, root: &str, target: Option) -> Vec<&Crate> { - let mut ret = Vec::new(); - let mut list = vec![root.to_owned()]; - let mut visited = HashSet::new(); - while let Some(krate) = list.pop() { - let krate = self - .crates - .get(&krate) - .unwrap_or_else(|| panic!("metadata missing for {krate}: {:?}", self.crates)); - ret.push(krate); - for dep in &krate.deps { - if !self.crates.contains_key(dep) { - // Ignore non-workspace members. - continue; - } - // Don't include optional deps if their features are not - // enabled. Ideally this would be computed from `cargo - // metadata --features …`, but that is somewhat slow. In - // the future, we may want to consider just filtering all - // build and dev dependencies in metadata::build. - if visited.insert(dep) - && (dep != "profiler_builtins" - || target - .map(|t| self.config.profiler_enabled(t)) - .unwrap_or_else(|| self.config.any_profiler_enabled())) - && (dep != "rustc_codegen_llvm" - || self.config.hosts.iter().any(|host| self.config.llvm_enabled(*host))) - { - list.push(dep.clone()); - } - } - } - - // Sort the crates so that bootstrap unit tests can assume a deterministic order. - ret.sort_unstable_by(|a, b| Ord::cmp(&a.name, &b.name)); - ret - } - - fn read_stamp_file(&self, stamp: &BuildStamp) -> Vec<(PathBuf, DependencyType)> { - if self.config.dry_run() { - return Vec::new(); - } - - if !stamp.path().exists() { - eprintln!( - "ERROR: Unable to find the stamp file {}, did you try to keep a nonexistent build stage?", - stamp.path().display() - ); - helpers::exit_process(1); - } - - let mut paths = Vec::new(); - let contents = t!(fs::read(stamp.path()), stamp.path()); - // This is the method we use for extracting paths from the stamp file passed to us. See - // run_cargo for more information (in compile.rs). - for part in contents.split(|b| *b == 0) { - if part.is_empty() { - continue; - } - let dependency_type = match part[0] as char { - 'h' => DependencyType::Host, - 's' => DependencyType::TargetSelfContained, - 't' => DependencyType::Target, - _ => unreachable!(), - }; - let path = PathBuf::from(t!(str::from_utf8(&part[1..]))); - paths.push((path, dependency_type)); - } - paths - } - - /// Copies a file from `src` to `dst`. - /// - /// If `src` is a symlink, `src` will be resolved to the actual path - /// and copied to `dst` instead of the symlink itself. - #[track_caller] - pub fn resolve_symlink_and_copy(&self, src: &Path, dst: &Path) { - self.copy_link_internal(src, dst, true); - } - - /// Links a file from `src` to `dst`. - /// Attempts to use hard links if possible, falling back to copying. - /// You can neither rely on this being a copy nor it being a link, - /// so do not write to dst. - #[track_caller] - pub fn copy_link(&self, src: &Path, dst: &Path, file_type: FileType) { - self.copy_link_internal(src, dst, false); - - if file_type.could_have_split_debuginfo() - && let Some(dbg_file) = split_debuginfo(src) - { - self.copy_link_internal( - &dbg_file, - &dst.with_extension(dbg_file.extension().unwrap()), - false, - ); - } - } - - #[track_caller] - fn copy_link_internal(&self, src: &Path, dst: &Path, dereference_symlinks: bool) { - if self.config.dry_run() { - return; - } - if src == dst { - return; - } - - #[cfg(feature = "tracing")] - let _span = trace_io!("file-copy-link", ?src, ?dst); - - if let Err(e) = fs::remove_file(dst) - && cfg!(windows) - && e.kind() != io::ErrorKind::NotFound - { - // workaround for https://github.com/rust-lang/rust/issues/127126 - // if removing the file fails, attempt to rename it instead. - let now = t!(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)); - let _ = fs::rename(dst, format!("{}-{}", dst.display(), now.as_nanos())); - } - let mut metadata = t!(src.symlink_metadata(), format!("src = {}", src.display())); - let mut src = src.to_path_buf(); - if metadata.file_type().is_symlink() { - if dereference_symlinks { - src = t!(fs::canonicalize(src)); - metadata = t!(fs::metadata(&src), format!("target = {}", src.display())); - } else { - let link = t!(fs::read_link(src)); - t!(self.symlink_file(link, dst)); - return; - } - } - if let Ok(()) = fs::hard_link(&src, dst) { - // Attempt to "easy copy" by creating a hard link (symlinks are privileged on windows), - // but if that fails just fall back to a slow `copy` operation. - } else { - if let Err(e) = fs::copy(&src, dst) { - panic!("failed to copy `{}` to `{}`: {}", src.display(), dst.display(), e) - } - t!(fs::set_permissions(dst, metadata.permissions())); - - // Restore file times because changing permissions on e.g. Linux using `chmod` can cause - // file access time to change. - let file_times = fs::FileTimes::new() - .set_accessed(t!(metadata.accessed())) - .set_modified(t!(metadata.modified())); - t!(set_file_times(dst, file_times)); - } - } - - /// Links the `src` directory recursively to `dst`. Both are assumed to exist - /// when this function is called. - /// Will attempt to use hard links if possible and fall back to copying. - #[track_caller] - pub fn cp_link_r(&self, src: &Path, dst: &Path) { - if self.config.dry_run() { - return; - } - for f in self.read_dir(src) { - let path = f.path(); - let name = path.file_name().unwrap(); - let dst = dst.join(name); - if t!(f.file_type()).is_dir() { - t!(fs::create_dir_all(&dst)); - self.cp_link_r(&path, &dst); - } else { - self.copy_link(&path, &dst, FileType::Regular); - } - } - } - - /// Copies the `src` directory recursively to `dst`. Both are assumed to exist - /// when this function is called. - /// Will attempt to use hard links if possible and fall back to copying. - /// Unwanted files or directories can be skipped - /// by returning `false` from the filter function. - #[track_caller] - pub fn cp_link_filtered(&self, src: &Path, dst: &Path, filter: &dyn Fn(&Path) -> bool) { - // Immediately recurse with an empty relative path - self.cp_link_filtered_recurse(src, dst, Path::new(""), filter) - } - - // Inner function does the actual work - #[track_caller] - fn cp_link_filtered_recurse( - &self, - src: &Path, - dst: &Path, - relative: &Path, - filter: &dyn Fn(&Path) -> bool, - ) { - for f in self.read_dir(src) { - let path = f.path(); - let name = path.file_name().unwrap(); - let dst = dst.join(name); - let relative = relative.join(name); - // Only copy file or directory if the filter function returns true - if filter(&relative) { - if t!(f.file_type()).is_dir() { - let _ = fs::remove_dir_all(&dst); - self.create_dir(&dst); - self.cp_link_filtered_recurse(&path, &dst, &relative, filter); - } else { - self.copy_link(&path, &dst, FileType::Regular); - } - } - } - } - - fn copy_link_to_folder(&self, src: &Path, dest_folder: &Path) { - let file_name = src.file_name().unwrap(); - let dest = dest_folder.join(file_name); - self.copy_link(src, &dest, FileType::Regular); - } - - fn install(&self, src: &Path, dstdir: &Path, file_type: FileType) { - if self.config.dry_run() { - return; - } - let dst = dstdir.join(src.file_name().unwrap()); - - #[cfg(feature = "tracing")] - let _span = trace_io!("install", ?src, ?dst); - - t!(fs::create_dir_all(dstdir)); - if !src.exists() { - panic!("ERROR: File \"{}\" not found!", src.display()); - } - - self.copy_link_internal(src, &dst, true); - chmod(&dst, file_type.perms()); - - // If this file can have debuginfo, look for split debuginfo and install it too. - if file_type.could_have_split_debuginfo() - && let Some(dbg_file) = split_debuginfo(src) - { - self.install(&dbg_file, dstdir, FileType::Regular); - } - } - - fn read(&self, path: &Path) -> String { - if self.config.dry_run() { - return String::new(); - } - t!(fs::read_to_string(path)) - } - - #[track_caller] - fn create_dir(&self, dir: &Path) { - if self.config.dry_run() { - return; - } - - #[cfg(feature = "tracing")] - let _span = trace_io!("dir-create", ?dir); - - t!(fs::create_dir_all(dir)) - } - - fn remove_dir(&self, dir: &Path) { - if self.config.dry_run() { - return; - } - - #[cfg(feature = "tracing")] - let _span = trace_io!("dir-remove", ?dir); - - t!(fs::remove_dir_all(dir)) - } - - /// Make sure that `dir` will be an empty existing directory after this function ends. - /// If it existed before, it will be first deleted. - fn clear_dir(&self, dir: &Path) { - if self.config.dry_run() { - return; - } - - #[cfg(feature = "tracing")] - let _span = trace_io!("dir-clear", ?dir); - - let _ = std::fs::remove_dir_all(dir); - self.create_dir(dir); - } - - fn read_dir(&self, dir: &Path) -> impl Iterator { - let iter = match fs::read_dir(dir) { - Ok(v) => v, - Err(_) if self.config.dry_run() => return vec![].into_iter(), - Err(err) => panic!("could not read dir {dir:?}: {err:?}"), - }; - iter.map(|e| t!(e)).collect::>().into_iter() - } - - fn symlink_file, Q: AsRef>(&self, src: P, link: Q) -> io::Result<()> { - #[cfg(unix)] - use std::os::unix::fs::symlink as symlink_file; - #[cfg(windows)] - use std::os::windows::fs::symlink_file; - if !self.config.dry_run() { symlink_file(src.as_ref(), link.as_ref()) } else { Ok(()) } - } - - /// Returns if config.ninja is enabled, and checks for ninja existence, - /// exiting with a nicer error message if not. - fn ninja(&self) -> bool { - let mut cmd_finder = crate::core::sanity::Finder::new(); - - if self.config.ninja_in_file { - // Some Linux distros rename `ninja` to `ninja-build`. - // CMake can work with either binary name. - if cmd_finder.maybe_have("ninja-build").is_none() - && cmd_finder.maybe_have("ninja").is_none() - { - eprintln!( - " -Couldn't find required command: ninja (or ninja-build) - -You should install ninja as described at -, -or set `ninja = false` in the `[llvm]` section of `bootstrap.toml`. -Alternatively, set `download-ci-llvm = true` in that `[llvm]` section -to download LLVM rather than building it. -" - ); - helpers::exit_process(1); - } - } - - // If ninja isn't enabled but we're building for MSVC then we try - // doubly hard to enable it. It was realized in #43767 that the msbuild - // CMake generator for MSVC doesn't respect configuration options like - // disabling LLVM assertions, which can often be quite important! - // - // In these cases we automatically enable Ninja if we find it in the - // environment. - if !self.config.ninja_in_file - && self.config.host_target.is_msvc() - && cmd_finder.maybe_have("ninja").is_some() - { - return true; - } - - self.config.ninja_in_file - } - - pub fn colored_stdout R>(&self, f: F) -> R { - self.colored_stream_inner(StandardStream::stdout, self.config.stdout_is_tty, f) - } - - pub fn colored_stderr R>(&self, f: F) -> R { - self.colored_stream_inner(StandardStream::stderr, self.config.stderr_is_tty, f) - } - - fn colored_stream_inner(&self, constructor: C, is_tty: bool, f: F) -> R - where - C: Fn(ColorChoice) -> StandardStream, - F: FnOnce(&mut dyn WriteColor) -> R, - { - let choice = match self.config.color { - flags::Color::Always => ColorChoice::Always, - flags::Color::Never => ColorChoice::Never, - flags::Color::Auto if !is_tty => ColorChoice::Never, - flags::Color::Auto => ColorChoice::Auto, - }; - let mut stream = constructor(choice); - let result = f(&mut stream); - stream.reset().unwrap(); - result - } - - pub fn report_summary(&self, path: &Path, start_time: Instant) { - self.config.exec_ctx.profiler().report_summary(path, start_time); - } - - #[cfg(feature = "tracing")] - pub fn report_step_graph(self, directory: &Path) { - self.step_graph.into_inner().store_to_dot_files(directory); - } -} - -impl AsRef for Build { - fn as_ref(&self) -> &ExecutionContext { - &self.config.exec_ctx - } -} - -#[cfg(unix)] -fn chmod(path: &Path, perms: u32) { - use std::os::unix::fs::*; - t!(fs::set_permissions(path, fs::Permissions::from_mode(perms))); -} -#[cfg(windows)] -fn chmod(_path: &Path, _perms: u32) {} diff --git a/src/bootstrap/src/utils/build_stamp.rs b/src/bootstrap/src/utils/build_stamp.rs index d27d5fa2cf420..36a3d0772e5ad 100644 --- a/src/bootstrap/src/utils/build_stamp.rs +++ b/src/bootstrap/src/utils/build_stamp.rs @@ -7,11 +7,11 @@ use std::{fs, io}; use sha2::digest::Digest; -use crate::Mode; use crate::core::backend::CodegenBackendKind; use crate::core::builder::Builder; use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; +use crate::core::session::Mode; use crate::utils::helpers::{self, hex_encode, mtime, t}; #[cfg(test)] diff --git a/src/bootstrap/src/utils/cc_detect.rs b/src/bootstrap/src/utils/cc_detect.rs index 977b4a31eadc4..e753ee71683fd 100644 --- a/src/bootstrap/src/utils/cc_detect.rs +++ b/src/bootstrap/src/utils/cc_detect.rs @@ -27,8 +27,8 @@ use std::path::{Path, PathBuf}; use crate::core::config::flags::Subcommand; use crate::core::config::{CompressDebuginfo, TargetSelection}; +use crate::core::session::{Build, CLang, GitRepo}; use crate::utils::exec::{BootstrapCommand, command}; -use crate::{Build, CLang, GitRepo}; /// Creates and configures a new [`cc::Build`] instance for the given target. fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build { diff --git a/src/bootstrap/src/utils/cc_detect/tests.rs b/src/bootstrap/src/utils/cc_detect/tests.rs index a2f35e6a1030d..716407cb0cb1c 100644 --- a/src/bootstrap/src/utils/cc_detect/tests.rs +++ b/src/bootstrap/src/utils/cc_detect/tests.rs @@ -2,8 +2,8 @@ use std::iter; use std::path::PathBuf; use super::*; -use crate::Build; use crate::core::config::{Target, TargetSelection}; +use crate::core::session::Build; use crate::utils::tests::TestCtx; #[test] diff --git a/src/bootstrap/src/utils/channel.rs b/src/bootstrap/src/utils/channel.rs index 21b4257e54d0b..ebb40edf9b262 100644 --- a/src/bootstrap/src/utils/channel.rs +++ b/src/bootstrap/src/utils/channel.rs @@ -10,7 +10,7 @@ use std::path::Path; use super::exec::ExecutionContext; use super::helpers; -use crate::Build; +use crate::core::session::Build; use crate::utils::helpers::t; #[derive(Clone, Default)] diff --git a/src/bootstrap/src/utils/job.rs b/src/bootstrap/src/utils/job.rs index 887deb41ca8bc..942ac6c80e4ee 100644 --- a/src/bootstrap/src/utils/job.rs +++ b/src/bootstrap/src/utils/job.rs @@ -1,11 +1,13 @@ #[cfg(windows)] pub use for_windows::*; +use crate::core::session::Build; + #[cfg(any(target_os = "haiku", target_os = "hermit", not(any(unix, windows))))] -pub unsafe fn setup(_build: &mut crate::Build) {} +pub unsafe fn setup(_build: &mut Build) {} #[cfg(all(unix, not(target_os = "haiku")))] -pub unsafe fn setup(build: &mut crate::Build) { +pub unsafe fn setup(build: &mut Build) { if build.config.low_priority { unsafe { libc::setpriority(libc::PRIO_PGRP as _, 0, 10); @@ -58,9 +60,7 @@ mod for_windows { use windows::Win32::System::Threading::{BELOW_NORMAL_PRIORITY_CLASS, GetCurrentProcess}; use windows::core::PCWSTR; - use crate::Build; - - pub unsafe fn setup(build: &mut Build) { + pub unsafe fn setup(build: &mut super::Build) { // SAFETY: pretty much everything below is unsafe unsafe { // Enable the Windows Error Reporting dialog which msys disables, diff --git a/src/bootstrap/src/utils/metrics.rs b/src/bootstrap/src/utils/metrics.rs index e685c64733c66..a309b1d53b8e9 100644 --- a/src/bootstrap/src/utils/metrics.rs +++ b/src/bootstrap/src/utils/metrics.rs @@ -16,8 +16,8 @@ use build_helper::metrics::{ }; use sysinfo::{CpuRefreshKind, RefreshKind, System}; -use crate::Build; use crate::core::builder::{Builder, Step}; +use crate::core::session::Build; use crate::utils::helpers::t; // Update this number whenever a breaking change is made to the build metrics. diff --git a/src/bootstrap/src/utils/tarball.rs b/src/bootstrap/src/utils/tarball.rs index 41ad6b022ac18..3ba7dbdb984c8 100644 --- a/src/bootstrap/src/utils/tarball.rs +++ b/src/bootstrap/src/utils/tarball.rs @@ -7,10 +7,10 @@ use std::path::{Path, PathBuf}; -use crate::FileType; use crate::core::build_steps::dist::distdir; use crate::core::builder::{Builder, Kind}; use crate::core::config::BUILDER_CONFIG_FILENAME; +use crate::core::session::FileType; use crate::utils::channel; use crate::utils::exec::BootstrapCommand; use crate::utils::helpers::{self, move_file, t}; diff --git a/src/ci/citool/src/jobs.rs b/src/ci/citool/src/jobs.rs index 8b4f66c85761a..9800f14d8fa39 100644 --- a/src/ci/citool/src/jobs.rs +++ b/src/ci/citool/src/jobs.rs @@ -44,7 +44,7 @@ impl Job { } fn is_linux(&self) -> bool { - self.os.contains("ubuntu") + self.os.contains("ubuntu") || self.os.contains("linux") } } @@ -414,7 +414,10 @@ pub fn find_linux_job<'a>(jobs: &'a [Job], name: &str) -> anyhow::Result<&'a Job )); }; if !job.is_linux() { - return Err(anyhow::anyhow!("Only Linux jobs can be executed locally")); + return Err(anyhow::anyhow!( + "Only Linux jobs can be executed locally, os `{}` is not linux", + job.os + )); } Ok(job) diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 3b5bf4ae080dc..ab317ba1048a4 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -812,8 +812,7 @@ Defines which scopes of paths should be remapped by --remap-path-prefix. `rustdoc` (and by extension `rustc`) have a special `documentation` remapping scope, it permits remapping source paths that ends up in the generated documentation. -Currently the scope can only be specified from `rustc`, due to the lack of an equivalent -`--remap-path-scope` flag in `rustc`. +It can specified with `--remap-path-scope=documentation`. ## `#[doc(cfg)]` and `#[doc(auto_cfg)]` diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index e4c05d58004a7..8bfd99f2f7c9f 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -454,7 +454,7 @@ pub(crate) fn rewrite_macro_def( }; let mut header = if def.macro_rules { - let pos = context.snippet_provider.span_after(span, "macro_rules!"); + let pos = context.snippet_provider.span_after(span, "!"); vec![HeaderPart::new("macro_rules!", span.with_hi(pos))] } else { let macro_lo = context.snippet_provider.span_before(span, "macro"); diff --git a/tests/codegen-llvm/gpu_offload/control_flow.rs b/tests/codegen-llvm/gpu_offload/control_flow.rs index da997de53a428..8cafeda3395ce 100644 --- a/tests/codegen-llvm/gpu_offload/control_flow.rs +++ b/tests/codegen-llvm/gpu_offload/control_flow.rs @@ -6,8 +6,8 @@ // contains control flow. #![feature(abi_gpu_kernel)] +#![feature(gpu_offload)] #![feature(rustc_attrs)] -#![feature(core_intrinsics)] #![no_main] // CHECK: @.offload_sizes.[[K:[^ ]*foo]] = private unnamed_addr constant @@ -28,13 +28,12 @@ unsafe fn main() { let A = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0]; for i in 0..100 { - core::intrinsics::offload::<_, _, ()>( - foo, - [256, 1, 1], - [32, 1, 1], - 0, - (A.as_ptr() as *const [f32; 6],), - ); + core::offload::offload! { + kernel = foo, + workgroup_dim = [256, 1, 1], + thread_dim = [32, 1, 1], + args = (A.as_ptr() as *const [f32; 6],), + } } } diff --git a/tests/codegen-llvm/gpu_offload/device_check.rs b/tests/codegen-llvm/gpu_offload/device_check.rs new file mode 100644 index 0000000000000..4eaa324a662a4 --- /dev/null +++ b/tests/codegen-llvm/gpu_offload/device_check.rs @@ -0,0 +1,30 @@ +//@ compile-flags: -Zoffload=Test -Zunstable-options -C opt-level=0 -Clto=fat +//@ no-prefer-dynamic +//@ needs-offload + +// This test verifies that selecting an unavailable `device` in the `offload` macro panics. + +#![feature(gpu_offload)] +#![no_main] + +#[unsafe(no_mangle)] +fn main() { + core::offload::offload! { + kernel = kernel, + device = 99, + args = (), + } +} + +#[unsafe(no_mangle)] +fn kernel() {} + +// CHECK-LABEL: define{{( dso_local)?}} void @main() +// CHECK: store i32 99, ptr %device, align 4 +// CHECK-NEXT: %{{[0-9_]+}} = call i32 @omp_get_num_devices() +// CHECK-NEXT: %{{[0-9_]+}} = load i32, ptr %device, align 4 +// CHECK-NEXT: %{{[0-9_]+}} = icmp slt i32 %{{[0-9_]+}}, %{{[0-9_]+}} +// CHECK-NEXT: br i1 %{{[0-9_]+}}, label %bb{{[0-9]+}}, label %bb{{[0-9]+}} +// CHECK: call void @{{.*}}panic_fmt +// CHECK: unreachable +// CHECK: call i32 @__tgt_target_kernel diff --git a/tests/codegen-llvm/gpu_offload/gpu_host.rs b/tests/codegen-llvm/gpu_offload/gpu_host.rs index 2bfaf89b45590..45fcf6cf5c3ce 100644 --- a/tests/codegen-llvm/gpu_offload/gpu_host.rs +++ b/tests/codegen-llvm/gpu_offload/gpu_host.rs @@ -7,8 +7,8 @@ // Better documentation to what each global or variable means is available in the gpu offload code, // or the LLVM offload documentation. +#![feature(gpu_offload)] #![feature(rustc_attrs)] -#![feature(core_intrinsics)] #![no_main] #[unsafe(no_mangle)] @@ -21,7 +21,12 @@ fn main() { } pub fn kernel_1(x: &mut [f32; 256], y: &[f32; 256]) { - core::intrinsics::offload(_kernel_1, [256, 1, 1], [32, 1, 1], 0, (x, y)) + core::offload::offload! { + kernel = _kernel_1, + workgroup_dim = [256, 1, 1], + thread_dim = [32, 1, 1], + args = (x, y), + } } #[inline(never)] @@ -78,8 +83,10 @@ pub fn _kernel_1(x: &mut [f32; 256], y: &[f32; 256]) { // CHECK-NEXT: [[P32:%[^ ]+]] = getelementptr inbounds nuw i8, ptr %kernel_args, i64 32 // CHECK-NEXT: store ptr @.offload_maptypes.[[K]].kernel, ptr [[P32]], align 8 // CHECK-NEXT: [[P40:%[^ ]+]] = getelementptr inbounds nuw i8, ptr %kernel_args, i64 40 +// CHECK-NEXT: [[P64:%[^ ]+]] = getelementptr inbounds nuw i8, ptr %kernel_args, i64 64 +// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(24) [[P40]], i8 0, i64 24, i1 false) +// CHECK-NEXT: store i64 64, ptr [[P64]], align 8 // CHECK-NEXT: [[P72:%[^ ]+]] = getelementptr inbounds nuw i8, ptr %kernel_args, i64 72 -// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(32) [[P40]], i8 0, i64 32, i1 false) // CHECK-NEXT: store <4 x i32> , ptr [[P72]], align 8 // CHECK-NEXT: [[P88:%[^ ]+]] = getelementptr inbounds nuw i8, ptr %kernel_args, i64 88 // CHECK-NEXT: store i32 1, ptr [[P88]], align 8 @@ -95,17 +102,17 @@ pub fn _kernel_1(x: &mut [f32; 256], y: &[f32; 256]) { // CHECK: declare void @__tgt_register_lib(ptr) local_unnamed_addr // CHECK: declare void @__tgt_unregister_lib(ptr) local_unnamed_addr -// CHECK-LABEL: define internal void @.omp_offloading.descriptor_reg() section ".text.startup" { +// CHECK-LABEL: define internal void @.omp_offloading.descriptor_reg() section ".text.startup" // CHECK-NEXT: entry: -// CHECK-NEXT: call void @__tgt_register_lib(ptr nonnull @.omp_offloading.descriptor) -// CHECK-NEXT: call void @__tgt_init_all_rtls() +// CHECK-NEXT: {{tail }}call void @__tgt_register_lib(ptr nonnull @.omp_offloading.descriptor) +// CHECK-NEXT: {{tail }}call void @__tgt_init_all_rtls() // CHECK-NEXT: %0 = {{tail }}call i32 @atexit(ptr nonnull @.omp_offloading.descriptor_unreg) // CHECK-NEXT: ret void // CHECK-NEXT: } -// CHECK-LABEL: define internal void @.omp_offloading.descriptor_unreg() section ".text.startup" { +// CHECK-LABEL: define internal void @.omp_offloading.descriptor_unreg() section ".text.startup" // CHECK-NEXT: entry: -// CHECK-NEXT: call void @__tgt_unregister_lib(ptr nonnull @.omp_offloading.descriptor) +// CHECK-NEXT: {{tail }}call void @__tgt_unregister_lib(ptr nonnull @.omp_offloading.descriptor) // CHECK-NEXT: ret void // CHECK-NEXT: } diff --git a/tests/codegen-llvm/gpu_offload/scalar_host.rs b/tests/codegen-llvm/gpu_offload/scalar_host.rs index 66c910c439e46..807d08ddf1893 100644 --- a/tests/codegen-llvm/gpu_offload/scalar_host.rs +++ b/tests/codegen-llvm/gpu_offload/scalar_host.rs @@ -6,8 +6,8 @@ // the kernel as i64 #![feature(abi_gpu_kernel)] +#![feature(gpu_offload)] #![feature(rustc_attrs)] -#![feature(core_intrinsics)] #![no_main] // CHECK: define{{( dso_local)?}} void @main() @@ -28,7 +28,10 @@ fn main() { let mut x = 0.0f32; let k = core::hint::black_box(42.0f32); - core::intrinsics::offload::<_, _, ()>(foo, [1, 1, 1], [1, 1, 1], 0, (&mut x as *mut f32, k)); + core::offload::offload! { + kernel = foo, + args = (&mut x as *mut f32, k), + } } unsafe extern "C" { diff --git a/tests/codegen-llvm/gpu_offload/slice_device.rs b/tests/codegen-llvm/gpu_offload/slice_device.rs index 1abe04f8cc429..6e900c21ca7cb 100644 --- a/tests/codegen-llvm/gpu_offload/slice_device.rs +++ b/tests/codegen-llvm/gpu_offload/slice_device.rs @@ -15,7 +15,7 @@ extern crate minicore; // CHECK: ; Function Attrs // nvptx-NEXT: define ptx_kernel void @foo // amdgpu-NEXT: define amdgpu_kernel void @foo -// CHECK-SAME: ptr readnone captures(none) %dyn_ptr +// CHECK-SAME: ptr nofree readnone captures(none) %dyn_ptr // nvptx-SAME: [2 x i64] %0 // amdgpu-SAME: ptr noalias {{.*}} %0, i64 {{.*}} %1 // CHECK-NEXT: entry: diff --git a/tests/codegen-llvm/gpu_offload/slice_host.rs b/tests/codegen-llvm/gpu_offload/slice_host.rs index dfc7ec545630c..ad47d2e76360a 100644 --- a/tests/codegen-llvm/gpu_offload/slice_host.rs +++ b/tests/codegen-llvm/gpu_offload/slice_host.rs @@ -5,8 +5,8 @@ // This test verifies that offload is properly handling slices passing them properly to the device #![feature(abi_gpu_kernel)] +#![feature(gpu_offload)] #![feature(rustc_attrs)] -#![feature(core_intrinsics)] #![no_main] // CHECK: @anon.[[ID:.*]].0 = private unnamed_addr constant [23 x i8] c";unknown;unknown;0;0;;\00", align 1 @@ -27,7 +27,10 @@ #[unsafe(no_mangle)] fn main() { let mut x = [0.0f32, 0.0, 0.0, 0.0]; - core::intrinsics::offload::<_, _, ()>(foo, [1, 1, 1], [1, 1, 1], 0, ((&mut x) as &mut [f32],)); + core::offload::offload! { + kernel = foo, + args = ((&mut x) as &mut [f32],), + } } unsafe extern "C" { diff --git a/tests/mir-opt/optimize_none.rs b/tests/mir-opt/optimize_none.rs index 23245f6cc687e..657cfd0675503 100644 --- a/tests/mir-opt/optimize_none.rs +++ b/tests/mir-opt/optimize_none.rs @@ -13,21 +13,19 @@ pub fn add_noopt() -> i32 { } #[optimize(none)] -pub fn const_branch() -> i32 { - // CHECK-LABEL: fn const_branch( - // CHECK: [[BOOL:_[0-9]+]] = const true; - // CHECK: switchInt(move [[BOOL]]) -> [0: [[BB_FALSE_SHIM:bb[0-9]+]], otherwise: [[BB_TRUE:bb[0-9]+]]]; - // CHECK-NEXT: } - // CHECK: [[BB_FALSE_SHIM]]: { - // CHECK-NEXT: goto -> [[BB_FALSE:bb[0-9]+]] - // CHECK: [[BB_FALSE]]: { - // CHECK-NEXT: _0 = const 0 - // CHECK: [[BB_TRUE]]: { - // CHECK-NEXT: _0 = const 1 - // CHECK-NEXT: goto - // CHECK-NEXT: } +#[allow(unused_assignments)] +pub fn dead_store_noopt(input: i32) -> i32 { + // CHECK-LABEL: fn dead_store_noopt( + // CHECK: debug value => [[VALUE:_[0-9]+]]; + // CHECK: [[VALUE]] = copy _1; + // CHECK-NEXT: [[VALUE]] = const 1_i32; + // CHECK-NEXT: [[VALUE]] = const 2_i32; + // CHECK-NEXT: _0 = copy [[VALUE]]; - if true { 1 } else { 0 } + let mut value = input; + value = 1; + value = 2; + value } fn main() {} diff --git a/tests/run-make/offload-generic-manifest/generic.rs b/tests/run-make/offload-generic-manifest/generic.rs index eb356ad05c574..a6b8a7368858d 100644 --- a/tests/run-make/offload-generic-manifest/generic.rs +++ b/tests/run-make/offload-generic-manifest/generic.rs @@ -1,4 +1,4 @@ -#![feature(core_intrinsics, rustc_attrs)] +#![feature(gpu_offload, rustc_attrs)] #![allow(internal_features)] #![cfg_attr(device, no_main)] @@ -7,6 +7,12 @@ fn kernel(x: T) {} #[cfg(not(device))] fn main() { - core::intrinsics::offload::<_, _, ()>(kernel::, [1, 1, 1], [1, 1, 1], 0, (0.0f32,)); - core::intrinsics::offload::<_, _, ()>(kernel::, [1, 1, 1], [1, 1, 1], 0, (0i32,)); + core::offload::offload! { + kernel = kernel::, + args = (0.0f32,), + } + core::offload::offload! { + kernel = kernel::, + args = (0i32,), + } } diff --git a/tests/rustdoc-gui/notable-trait.goml b/tests/rustdoc-gui/notable-trait.goml new file mode 100644 index 0000000000000..d776021917e8c --- /dev/null +++ b/tests/rustdoc-gui/notable-trait.goml @@ -0,0 +1,256 @@ +// This test checks the position of the `i` for the notable traits. +include: "utils.goml" +go-to: "file://" + |DOC_PATH| + "/test_docs/struct.NotableStructWithLongName.html" +show-text: true + +define-function: ( + "check-notable-tooltip-position", + [x, i_x], + block { + // Checking they have the same y position. + compare-elements-position-near: ( + "//*[@id='method.create_an_iterator_from_read']//a[normalize-space()='NotableStructWithLongName']", + "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']", + {"y": 1}, + ) + // Checking they don't have the same x position. + compare-elements-position-false: ( + "//*[@id='method.create_an_iterator_from_read']//a[normalize-space()='NotableStructWithLongName']", + "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']", + ["x"], + ) + // The `i` should be *after* the type. + assert-position: ( + "//*[@id='method.create_an_iterator_from_read']//a[normalize-space()='NotableStructWithLongName']", + {"x": |x|}, + ) + assert-position: ( + "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']", + {"x": |i_x|}, + ) + }, +) + +define-function: ( + "check-notable-tooltip-position-complete", + [x, i_x, popover_x], + block { + call-function: ("check-notable-tooltip-position", {"x": |x|, "i_x": |i_x|}) + assert-count: ("//*[@class='tooltip popover']", 0) + click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']" + assert-count: ("//*[@class='tooltip popover']", 1) + wait-for-position: ( + "//*[@class='tooltip popover']", + {"x": |popover_x|} + ) + compare-elements-position-near: ( + "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']", + "//*[@class='tooltip popover']", + {"y": 30} + ) + compare-elements-position-false: ( + "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']", + "//*[@class='tooltip popover']", + ["x"] + ) + click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']" + move-cursor-to: "//h1" + assert-count: ("//*[@class='tooltip popover']", 0) + }, +) + +// We start with a wide screen. +set-window-size: (1100, 600) +call-function: ("check-notable-tooltip-position-complete", { + "x": 682, + "i_x": 960, + "popover_x": 468, +}) + +// Now only the `i` should be on the next line. +set-window-size: (1055, 600) +compare-elements-position-false: ( + "//*[@id='method.create_an_iterator_from_read']//a[normalize-space()='NotableStructWithLongName']", + "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']", + ["y", "x"], +) + +// Now both the `i` and the struct name should be on the next line. +set-window-size: (980, 600) +call-function: ("check-notable-tooltip-position", { + "x": 250, + "i_x": 528, +}) + +go-to: "file://" + |DOC_PATH| + "/test_docs/struct.NotableStructWithLongName.html" +// This is needed to ensure that the text color is computed. +show-text: true + +// Now check the colors. +define-function: ( + "check-colors", + [theme, header_color, content_color, type_color, trait_color, link_color], + block { + call-function: ("switch-theme", {"theme": |theme|}) + + assert-css: ( + "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']", + {"color": |content_color|}, + ALL, + ) + + move-cursor-to: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']" + wait-for-count: (".tooltip.popover", 1) + + assert-css: ( + "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']", + {"color": |link_color|}, + ALL, + ) + + assert-css: ( + ".tooltip.popover h3", + {"color": |header_color|}, + ALL, + ) + assert-css: ( + ".tooltip.popover pre", + {"color": |content_color|}, + ALL, + ) + assert-css: ( + ".tooltip.popover pre a.struct", + {"color": |type_color|}, + ALL, + ) + assert-css: ( + ".tooltip.popover pre a.trait", + {"color": |trait_color|}, + ALL, + ) + }, +) + +call-function: ( + "check-colors", + { + "theme": "ayu", + "link_color": "#39afd7", + "content_color": "#e6e1cf", + "header_color": "#fff", + "type_color": "#ffa0a5", + "trait_color": "#39afd7", + }, +) + +call-function: ( + "check-colors", + { + "theme": "dark", + "link_color": "#d2991d", + "content_color": "#ddd", + "header_color": "#ddd", + "type_color": "#2dbfb8", + "trait_color": "#b78cf2", + }, +) + +call-function: ( + "check-colors", + { + "theme": "light", + "link_color": "#3873ad", + "content_color": "black", + "header_color": "black", + "type_color": "#ad378a", + "trait_color": "#6e4fc9", + }, +) + +// Checking on mobile now. +set-window-size: (650, 600) +wait-for-size: ("body", {"width": 650}) +call-function: ("check-notable-tooltip-position-complete", { + "x": 26, + "i_x": 305, + "popover_x": 0, +}) + +reload: + +// Check that pressing escape works +click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']" +move-cursor-to: "//*[@class='tooltip popover']" +assert-count: ("//*[@class='tooltip popover']", 1) +press-key: "Escape" +assert-count: ("//*[@class='tooltip popover']", 0) +assert: "#method\.create_an_iterator_from_read .tooltip:focus" + +// Check that clicking outside works. +click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']" +assert-count: ("//*[@class='tooltip popover']", 1) +click: ".main-heading h1" +assert-count: ("//*[@class='tooltip popover']", 0) +assert-false: "#method\.create_an_iterator_from_read .tooltip:focus" + +// Check that pressing tab over and over works. +click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']" +move-cursor-to: "//*[@class='tooltip popover']" +assert-count: ("//*[@class='tooltip popover']", 1) +press-key: "Tab" +press-key: "Tab" +press-key: "Tab" +press-key: "Tab" +press-key: "Tab" +press-key: "Tab" +press-key: "Tab" +assert-count: ("//*[@class='tooltip popover']", 0) +assert: "#method\.create_an_iterator_from_read .tooltip:focus" + +define-function: ( + "setup-popup", + [], + block { + store-window-property: {"scrollY": scroll} + click: "#method\.create_an_iterator_from_read .fn" + // We ensure that the scroll position changed. + assert-window-property-false: {"scrollY": |scroll|} + // Store the new position. + store-window-property: {"scrollY": scroll} + click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']" + wait-for: "//*[@class='tooltip popover']" + click: ".main-heading h1" + } +) + +// Now we check that the focus isn't given back to the wrong item when opening +// another popover. +call-function: ("setup-popup", {}) +click: ".main-heading h1" +// We ensure we didn't come back to the previous focused item. +assert-window-property-false: {"scrollY": |scroll|} + +// Same but with Escape handling. +call-function: ("setup-popup", {}) +press-key: "Escape" +// We ensure we didn't come back to the previous focused item. +assert-window-property-false: {"scrollY": |scroll|} + +// Opening the mobile sidebar should close the popover. +set-window-size: (650, 600) +click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']" +assert-count: ("//*[@class='tooltip popover']", 1) +click: ".sidebar-menu-toggle" +assert: "//*[@class='sidebar shown']" +assert-count: ("//*[@class='tooltip popover']", 0) +assert-false: "#method\.create_an_iterator_from_read .tooltip:focus" + +// Also check the focus handling for the settings button. +set-window-size: (1100, 600) +reload: +assert-count: ("//*[@class='tooltip popover']", 0) +click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']" +wait-for-count: ("//*[@class='tooltip popover']", 1) +call-function: ("open-settings-menu", {}) +wait-for-count: ("//*[@class='tooltip popover']", 0) +assert-false: "#method\.create_an_iterator_from_read .tooltip:focus" diff --git a/tests/ui-fulldeps/lto-with-rustc-private.rs b/tests/ui-fulldeps/lto-with-rustc-private.rs new file mode 100644 index 0000000000000..7695d5b6ea1cc --- /dev/null +++ b/tests/ui-fulldeps/lto-with-rustc-private.rs @@ -0,0 +1,14 @@ +//! Regression test for . + +//@ build-fail +//@ compile-flags: -Clto +//@ normalize-stderr: "error: crate .* required.*\n( .*\n)*\n" -> "" +//@ normalize-stderr: "aborting due to [0-9]+" -> "aborting due to NUMBER" +//@ dont-require-annotations: ERROR + +#![feature(rustc_private)] + +extern crate rustc_errors; +//~? ERROR crate `rustc_errors` required to be available in rlib format + +fn main() {} diff --git a/tests/ui-fulldeps/lto-with-rustc-private.stderr b/tests/ui-fulldeps/lto-with-rustc-private.stderr new file mode 100644 index 0000000000000..58577ffffb3f6 --- /dev/null +++ b/tests/ui-fulldeps/lto-with-rustc-private.stderr @@ -0,0 +1,2 @@ +error: aborting due to NUMBER previous errors + diff --git a/tests/ui/diagnostic-width/elided-span-with-hard-tabs.rs b/tests/ui/diagnostic-width/elided-span-with-hard-tabs.rs new file mode 100644 index 0000000000000..d455da13fef83 --- /dev/null +++ b/tests/ui/diagnostic-width/elided-span-with-hard-tabs.rs @@ -0,0 +1,11 @@ +//! Regression test for . + +// The panic happens while the JSON emitter fills in its `rendered` field, which is the +// path `cargo` takes, so this has to be checked with the default JSON error format. +//@ compile-flags: --diagnostic-width=30 +// ignore-tidy-file-tab + +fn main() { + let _: &[u8] = [0, 0]; + //~^ ERROR mismatched types +} diff --git a/tests/ui/diagnostic-width/elided-span-with-hard-tabs.stderr b/tests/ui/diagnostic-width/elided-span-with-hard-tabs.stderr new file mode 100644 index 0000000000000..6415675e5a69d --- /dev/null +++ b/tests/ui/diagnostic-width/elided-span-with-hard-tabs.stderr @@ -0,0 +1,16 @@ +error[E0308]: mismatched types + --> $DIR/elided-span-with-hard-tabs.rs:9:20 + | +LL | ..._: &[u8] = [0, ... 0]; + | ----- ^^^^^^^^...^^^^^^^^ expected `&[u8]`, found `[{integer}; 2]` + | | + | expected due to this + | +help: consider borrowing here + | +LL | let _: &[u8] = &[0, 0]; + | + + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/impl-trait/nested-rpit-not-iterator-ice-159559.rs b/tests/ui/impl-trait/nested-rpit-not-iterator-ice-159559.rs new file mode 100644 index 0000000000000..03842bc36db7a --- /dev/null +++ b/tests/ui/impl-trait/nested-rpit-not-iterator-ice-159559.rs @@ -0,0 +1,19 @@ +//! Regression test for . +//! Reporting the `E0277` for the unsatisfied `IntoIterator` bound on the +//! nested opaque type used to ICE ("Normalizing ... without wrapping in a +//! `Binder`") in the RPIT method-chain suggestion when the return type +//! captures a lifetime. + +trait Cap<'a> {} + +impl Cap<'_> for T {} + +fn fail_late_bound<'a>( + a: &u8, + _: &'a u8, +) -> impl IntoIterator + IntoIterator>> { + //~^ ERROR `&u8` is not an iterator + [a] +} + +fn main() {} diff --git a/tests/ui/impl-trait/nested-rpit-not-iterator-ice-159559.stderr b/tests/ui/impl-trait/nested-rpit-not-iterator-ice-159559.stderr new file mode 100644 index 0000000000000..4ba03ed69999c --- /dev/null +++ b/tests/ui/impl-trait/nested-rpit-not-iterator-ice-159559.stderr @@ -0,0 +1,12 @@ +error[E0277]: `&u8` is not an iterator + --> $DIR/nested-rpit-not-iterator-ice-159559.rs:14:31 + | +LL | ) -> impl IntoIterator + IntoIterator>> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `&u8` is not an iterator + | + = help: the trait `Iterator` is not implemented for `&u8` + = note: required for `&u8` to implement `IntoIterator` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/macros/auxiliary/nested-macro-rules-definition.rs b/tests/ui/macros/auxiliary/nested-macro-rules-definition.rs new file mode 100644 index 0000000000000..cb980fd3d9274 --- /dev/null +++ b/tests/ui/macros/auxiliary/nested-macro-rules-definition.rs @@ -0,0 +1,15 @@ +pub struct ProjectileCreated; +pub struct NotificationChannel(std::marker::PhantomData); + +// The inner `macro_rules!` is what later reports a span from this crate while the +// diagnostic is being rendered against the downstream crate's source. +macro_rules! define_trigger_system { + ($(( $field:ident, $ty:ident, $channel:ident )),* $(,)?) => { + #[macro_export] + macro_rules! all_trigger_fields { + ($submacro:ident) => { $submacro!($( ( $field, $ty, $channel ) ),*) } + } + }; +} + +define_trigger_system!((projectile_created, ProjectileCreated, NotificationChannel),); diff --git a/tests/ui/macros/cross-crate-nested-macro-rules-span.rs b/tests/ui/macros/cross-crate-nested-macro-rules-span.rs new file mode 100644 index 0000000000000..9353398093ff8 --- /dev/null +++ b/tests/ui/macros/cross-crate-nested-macro-rules-span.rs @@ -0,0 +1,18 @@ +//! Regression test for . + +//@ aux-build: nested-macro-rules-definition.rs + +extern crate nested_macro_rules_definition; +use nested_macro_rules_definition::*; + +macro_rules! make_event_subscription { + ($(( $field:ident, $ty:ident, $channel:ident )),*) => { + pub struct EventSubscription($($channel::ReaderId),*); + //~^ ERROR ambiguous associated type + }; +} + +all_trigger_fields!(make_event_subscription); +//~^ ERROR macros that expand to items must be delimited with braces or followed by a semicolon + +fn main() {} diff --git a/tests/ui/macros/cross-crate-nested-macro-rules-span.stderr b/tests/ui/macros/cross-crate-nested-macro-rules-span.stderr new file mode 100644 index 0000000000000..edf3d575b972d --- /dev/null +++ b/tests/ui/macros/cross-crate-nested-macro-rules-span.stderr @@ -0,0 +1,27 @@ +error: macros that expand to items must be delimited with braces or followed by a semicolon + --> $DIR/cross-crate-nested-macro-rules-span.rs:15:1 + | +LL | all_trigger_fields!(make_event_subscription); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `all_trigger_fields` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0223]: ambiguous associated type + --> $DIR/cross-crate-nested-macro-rules-span.rs:10:40 + | +LL | pub struct EventSubscription($($channel::ReaderId),*); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | all_trigger_fields!(make_event_subscription); + | -------------------------------------------- in this macro invocation + | + = note: this error originates in the macro `make_event_subscription` which comes from the expansion of the macro `all_trigger_fields` (in Nightly builds, run with -Z macro-backtrace for more info) +help: if there were a trait named `Example` with associated type `ReaderId` implemented for `nested_macro_rules_definition::NotificationChannel`, you could use the fully-qualified path + | +LL - pub struct EventSubscription($($channel::ReaderId),*); +LL + pub struct EventSubscription($( as Example>::ReaderId),*); + | + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0223`. diff --git a/tests/ui/numbers-arithmetic/wrapping-ops-under-mir-opts.rs b/tests/ui/numbers-arithmetic/wrapping-ops-under-mir-opts.rs new file mode 100644 index 0000000000000..ab0e48f51be6f --- /dev/null +++ b/tests/ui/numbers-arithmetic/wrapping-ops-under-mir-opts.rs @@ -0,0 +1,11 @@ +//! Regression test for . + +//@ run-pass +//@ compile-flags: -Zmir-opt-level=2 -Coverflow-checks=on + +fn main() { + assert_eq!(1_u32.wrapping_sub(2), u32::MAX); + assert_eq!(u32::MAX.wrapping_add(2), 1); + assert_eq!(i32::MIN.wrapping_sub(1), i32::MAX); + assert_eq!(2_u32.wrapping_mul(u32::MAX), u32::MAX - 1); +} diff --git a/tests/ui/offload/check_config.rs b/tests/ui/offload/check_config.rs index ff145f420e482..63388ce69ba62 100644 --- a/tests/ui/offload/check_config.rs +++ b/tests/ui/offload/check_config.rs @@ -9,7 +9,7 @@ //[fail]~? ERROR: using the offload feature requires -Z offload= //[fail]~? ERROR: using the offload feature requires -C lto=fat -#![feature(core_intrinsics)] +#![feature(gpu_offload)] fn main() { let mut x = [3.0; 256]; @@ -17,7 +17,10 @@ fn main() { } fn kernel_1(x: &mut [f32; 256]) { - core::intrinsics::offload(_kernel_1, [1, 1, 1], [1, 1, 1], 0, (x,)) + core::offload::offload! { + kernel = _kernel_1, + args = (x,), + } } fn _kernel_1(x: &mut [f32; 256]) {} diff --git a/tests/ui/offload/duplicate_kernel.rs b/tests/ui/offload/duplicate_kernel.rs index abde76137a37c..da667a0c0666a 100644 --- a/tests/ui/offload/duplicate_kernel.rs +++ b/tests/ui/offload/duplicate_kernel.rs @@ -18,5 +18,5 @@ fn kernel(_x: f32) {} fn main() { _RNvC19collision_kernels_a6kernel(0.0); - core::intrinsics::offload::<_, _, ()>(kernel, [1, 1, 1], [1, 1, 1], 0, (0.0f32,)); + core::intrinsics::offload::<_, _, ()>(kernel, [1, 1, 1], [1, 1, 1], 0, -1, (0.0f32,)); } diff --git a/tests/ui/offload/non_tuple_args.rs b/tests/ui/offload/non_tuple_args.rs index 0a07c99a26d34..14de21b2374a2 100644 --- a/tests/ui/offload/non_tuple_args.rs +++ b/tests/ui/offload/non_tuple_args.rs @@ -4,7 +4,7 @@ fn main() { // args_ty is not a tuple - core::intrinsics::offload::<_, _, ()>(kernel_0, [1, 1, 1], [1, 1, 1], 0, 42); + core::intrinsics::offload::<_, _, ()>(kernel_0, [1, 1, 1], [1, 1, 1], 0, -1, 42); //~^ ERROR `{integer}` is not a tuple } diff --git a/tests/ui/offload/non_tuple_args.stderr b/tests/ui/offload/non_tuple_args.stderr index 8b59d6828c6f2..90b0f16bec53e 100644 --- a/tests/ui/offload/non_tuple_args.stderr +++ b/tests/ui/offload/non_tuple_args.stderr @@ -1,7 +1,7 @@ error[E0277]: `{integer}` is not a tuple --> $DIR/non_tuple_args.rs:7:36 | -LL | core::intrinsics::offload::<_, _, ()>(kernel_0, [1, 1, 1], [1, 1, 1], 0, 42); +LL | core::intrinsics::offload::<_, _, ()>(kernel_0, [1, 1, 1], [1, 1, 1], 0, -1, 42); | ^ the nightly-only, unstable trait `std::marker::Tuple` is not implemented for `{integer}` | note: required by a bound in `offload` diff --git a/tests/ui/offload/offload_macro.rs b/tests/ui/offload/offload_macro.rs index 468820f08c291..4480f7dd9c80b 100644 --- a/tests/ui/offload/offload_macro.rs +++ b/tests/ui/offload/offload_macro.rs @@ -26,4 +26,7 @@ fn main() { core::offload::offload! { kernel = kernel, args = (), dyn_cache = 0, dyn_cache = 8 } //~^ ERROR duplicate field `dyn_cache` + + core::offload::offload! { kernel = kernel, args = (), device = 0, device = 1 } + //~^ ERROR duplicate field `device` } diff --git a/tests/ui/offload/offload_macro.stderr b/tests/ui/offload/offload_macro.stderr index cd85afeab373e..e2517a2f8cca1 100644 --- a/tests/ui/offload/offload_macro.stderr +++ b/tests/ui/offload/offload_macro.stderr @@ -62,5 +62,13 @@ LL | core::offload::offload! { kernel = kernel, args = (), dyn_cache = 0, dy | = note: this error originates in the macro `$crate::offload` which comes from the expansion of the macro `core::offload::offload` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 8 previous errors +error: duplicate field `device` + --> $DIR/offload_macro.rs:30:5 + | +LL | core::offload::offload! { kernel = kernel, args = (), device = 0, device = 1 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `$crate::offload` which comes from the expansion of the macro `core::offload::offload` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 9 previous errors diff --git a/tests/ui/offload/offload_negative_device.rs b/tests/ui/offload/offload_negative_device.rs new file mode 100644 index 0000000000000..b47dc5f085315 --- /dev/null +++ b/tests/ui/offload/offload_negative_device.rs @@ -0,0 +1,12 @@ +//@ compile-flags: -Zunstable-options -Zoffload=Test -Clto=fat --emit=llvm-ir -Zdeduplicate-diagnostics=yes +//@ no-prefer-dynamic +//@ needs-offload + +#![feature(gpu_offload)] + +fn kernel() {} + +fn main() { + core::offload::offload! { kernel = kernel, args = (), device = -1 } + //~^ ERROR evaluation panicked: offload device must be non-negative; omit `device` to use the default device +} diff --git a/tests/ui/offload/offload_negative_device.stderr b/tests/ui/offload/offload_negative_device.stderr new file mode 100644 index 0000000000000..4be386dd436da --- /dev/null +++ b/tests/ui/offload/offload_negative_device.stderr @@ -0,0 +1,19 @@ +error[E0080]: evaluation panicked: offload device must be non-negative; omit `device` to use the default device + --> $DIR/offload_negative_device.rs:10:5 + | +LL | core::offload::offload! { kernel = kernel, args = (), device = -1 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `main::{constant#0}` failed here + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `core::offload::offload` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: erroneous constant encountered + --> $DIR/offload_negative_device.rs:10:5 + | +LL | core::offload::offload! { kernel = kernel, args = (), device = -1 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this note originates in the macro `$crate::offload` which comes from the expansion of the macro `core::offload::offload` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/offload/type_mismatch.rs b/tests/ui/offload/type_mismatch.rs index 4079444a0aff1..a75f8358b7359 100644 --- a/tests/ui/offload/type_mismatch.rs +++ b/tests/ui/offload/type_mismatch.rs @@ -5,25 +5,32 @@ fn main() { // kernel_ty is not a function item let not_fn = 42; - core::intrinsics::offload::<_, _, ()>(not_fn, [1, 1, 1], [1, 1, 1], 0, ()); + core::intrinsics::offload::<_, _, ()>(not_fn, [1, 1, 1], [1, 1, 1], 0, -1, ()); //~^ ERROR expected a function item for the offload kernel, found `i32` // argument count mismatch - core::intrinsics::offload::<_, _, ()>(kernel_1, [1, 1, 1], [1, 1, 1], 0, ()); + core::intrinsics::offload::<_, _, ()>(kernel_1, [1, 1, 1], [1, 1, 1], 0, -1, ()); //~^ ERROR offload kernel expects 1 arguments, but 0 arguments were provided // argument type mismatch - core::intrinsics::offload::<_, _, ()>(kernel_1, [1, 1, 1], [1, 1, 1], 0, (42.0f64,)); + core::intrinsics::offload::<_, _, ()>(kernel_1, [1, 1, 1], [1, 1, 1], 0, -1, (42.0f64,)); //~^ ERROR type mismatch in offload kernel argument 0: expected `f32`, found `f64` // return type mismatch - let _: f64 = core::intrinsics::offload::<_, _, f64>(kernel_0, [1, 1, 1], [1, 1, 1], 0, ()); + let _: f64 = core::intrinsics::offload::<_, _, f64>(kernel_0, [1, 1, 1], [1, 1, 1], 0, -1, ()); //~^ ERROR offload kernel return type mismatch: kernel returns `()`, but offload call expects `f64` // multiple argument type mismatch - core::intrinsics::offload::<_, _, ()>(kernel_2, [1, 1, 1], [1, 1, 1], 0, (42.0f64, 42.0f64)); - //~^ ERROR type mismatch in offload kernel argument 0: expected `f32`, found `f64` - //~| ERROR type mismatch in offload kernel argument 1: expected `f32`, found `f64` + core::intrinsics::offload::<_, _, ()>( + //~^ ERROR type mismatch in offload kernel argument 0: expected `f32`, found `f64` + //~| ERROR type mismatch in offload kernel argument 1: expected `f32`, found `f64` + kernel_2, + [1, 1, 1], + [1, 1, 1], + 0, + -1, + (42.0f64, 42.0f64), + ); } fn kernel_0() {} diff --git a/tests/ui/offload/type_mismatch.stderr b/tests/ui/offload/type_mismatch.stderr index 8cf160ca09486..808768e7cd4f3 100644 --- a/tests/ui/offload/type_mismatch.stderr +++ b/tests/ui/offload/type_mismatch.stderr @@ -1,37 +1,37 @@ error: expected a function item for the offload kernel, found `i32` --> $DIR/type_mismatch.rs:8:5 | -LL | core::intrinsics::offload::<_, _, ()>(not_fn, [1, 1, 1], [1, 1, 1], 0, ()); +LL | core::intrinsics::offload::<_, _, ()>(not_fn, [1, 1, 1], [1, 1, 1], 0, -1, ()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: offload kernel expects 1 arguments, but 0 arguments were provided --> $DIR/type_mismatch.rs:12:5 | -LL | core::intrinsics::offload::<_, _, ()>(kernel_1, [1, 1, 1], [1, 1, 1], 0, ()); +LL | core::intrinsics::offload::<_, _, ()>(kernel_1, [1, 1, 1], [1, 1, 1], 0, -1, ()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: type mismatch in offload kernel argument 0: expected `f32`, found `f64` --> $DIR/type_mismatch.rs:16:5 | -LL | core::intrinsics::offload::<_, _, ()>(kernel_1, [1, 1, 1], [1, 1, 1], 0, (42.0f64,)); +LL | core::intrinsics::offload::<_, _, ()>(kernel_1, [1, 1, 1], [1, 1, 1], 0, -1, (42.0f64,)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: offload kernel return type mismatch: kernel returns `()`, but offload call expects `f64` --> $DIR/type_mismatch.rs:20:18 | -LL | let _: f64 = core::intrinsics::offload::<_, _, f64>(kernel_0, [1, 1, 1], [1, 1, 1], 0, ()); +LL | let _: f64 = core::intrinsics::offload::<_, _, f64>(kernel_0, [1, 1, 1], [1, 1, 1], 0, -1, ()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: type mismatch in offload kernel argument 0: expected `f32`, found `f64` --> $DIR/type_mismatch.rs:24:5 | -LL | core::intrinsics::offload::<_, _, ()>(kernel_2, [1, 1, 1], [1, 1, 1], 0, (42.0f64, 42.0f64)); +LL | core::intrinsics::offload::<_, _, ()>( | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: type mismatch in offload kernel argument 1: expected `f32`, found `f64` --> $DIR/type_mismatch.rs:24:5 | -LL | core::intrinsics::offload::<_, _, ()>(kernel_2, [1, 1, 1], [1, 1, 1], 0, (42.0f64, 42.0f64)); +LL | core::intrinsics::offload::<_, _, ()>( | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 6 previous errors diff --git a/tests/ui/proc-macro/auxiliary/panicking-attribute.rs b/tests/ui/proc-macro/auxiliary/panicking-attribute.rs new file mode 100644 index 0000000000000..f5544030b8ec5 --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/panicking-attribute.rs @@ -0,0 +1,8 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn tester(_: TokenStream, _: TokenStream) -> TokenStream { + panic!(); +} diff --git a/tests/ui/proc-macro/panicking-inner-attribute-macro.rs b/tests/ui/proc-macro/panicking-inner-attribute-macro.rs new file mode 100644 index 0000000000000..2fef1cb83135a --- /dev/null +++ b/tests/ui/proc-macro/panicking-inner-attribute-macro.rs @@ -0,0 +1,8 @@ +//! Regression test for . + +//@ proc-macro: panicking-attribute.rs +//@ compile-flags: --crate-type=lib + +#![feature(custom_inner_attributes)] +#![panicking_attribute::tester] +//~^ ERROR custom attribute panicked diff --git a/tests/ui/proc-macro/panicking-inner-attribute-macro.stderr b/tests/ui/proc-macro/panicking-inner-attribute-macro.stderr new file mode 100644 index 0000000000000..cbfb29ad1ef89 --- /dev/null +++ b/tests/ui/proc-macro/panicking-inner-attribute-macro.stderr @@ -0,0 +1,10 @@ +error: custom attribute panicked + --> $DIR/panicking-inner-attribute-macro.rs:7:1 + | +LL | #![panicking_attribute::tester] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: explicit panic + +error: aborting due to 1 previous error + diff --git a/tests/ui/process/win-desktop.rs b/tests/ui/process/win-desktop.rs new file mode 100644 index 0000000000000..f5ffed42212c7 --- /dev/null +++ b/tests/ui/process/win-desktop.rs @@ -0,0 +1,129 @@ +// Tests `desktop` by creating a new desktop, spawning a child process onto it +// and checking that the child reports back the expected desktop name. + +//@ run-pass +//@ only-windows +//@ needs-subprocess +//@ edition: 2024 + +#![feature(windows_process_extensions_desktop)] + +use std::os::windows::process::CommandExt; +use std::process::{Command, Stdio}; +use std::{env, io, process}; + +fn main() { + if env::args().skip(1).any(|s| s == "--child") { + child(); + } else { + parent(); + } +} + +fn parent() { + let exe = env::current_exe().unwrap(); + + // Create a uniquely named desktop on the current window station and keep the + // handle alive so the desktop is not destroyed while the child runs. + let desktop_name = format!("rust-test-desktop-{}", process::id()); + let desktop_name_wide: Vec = desktop_name.encode_utf16().chain([0]).collect(); + let hdesk = unsafe { + CreateDesktopW( + desktop_name_wide.as_ptr(), + core::ptr::null(), + core::ptr::null(), + 0, + GENERIC_ALL, + core::ptr::null(), + ) + }; + assert!(!hdesk.is_null(), "CreateDesktopW failed: {:?}", io::Error::last_os_error()); + + // Spawning with `.desktop` should place the child on our new desktop. + let output = Command::new(&exe) + .arg("--child") + .desktop(&desktop_name) + .stdout(Stdio::piped()) + .output() + .unwrap(); + assert!(output.status.success(), "child failed: {:?}", output); + let reported = String::from_utf8(output.stdout).unwrap(); + assert!( + reported.trim().eq_ignore_ascii_case(&desktop_name), + "child ran on unexpected desktop: expected {:?}, got {:?}", + desktop_name, + reported.trim(), + ); + + // Without `.desktop` the child inherits the parent's desktop, which is + // not the one we just created. + let output = Command::new(&exe).arg("--child").stdout(Stdio::piped()).output().unwrap(); + assert!(output.status.success(), "child failed: {:?}", output); + let reported = String::from_utf8(output.stdout).unwrap(); + assert!( + !reported.trim().eq_ignore_ascii_case(&desktop_name), + "child unexpectedly ran on the created desktop {:?} without being asked to", + desktop_name, + ); + + unsafe { CloseDesktop(hdesk) }; +} + +/// Prints the name of the desktop the current process is running on. +fn child() { + let hdesk = unsafe { GetThreadDesktop(GetCurrentThreadId()) }; + assert!(!hdesk.is_null(), "GetThreadDesktop failed: {:?}", io::Error::last_os_error()); + + let mut buffer = [0u16; 256]; + let mut needed = 0u32; + let ret = unsafe { + GetUserObjectInformationW( + hdesk, + UOI_NAME, + buffer.as_mut_ptr().cast(), + size_of_val(&buffer) as u32, + &raw mut needed, + ) + }; + assert_ne!(ret, 0, "GetUserObjectInformationW failed: {:?}", io::Error::last_os_error()); + + let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len()); + let name = String::from_utf16(&buffer[..len]).unwrap(); + print!("{name}"); +} + +// Windows API +mod winapi { + use std::ffi::c_void; + use std::os::windows::raw::HANDLE; + + pub const GENERIC_ALL: u32 = 0x10000000; + pub const UOI_NAME: i32 = 2; + + #[link(name = "user32")] + unsafe extern "system" { + pub fn CreateDesktopW( + lpszDesktop: *const u16, + lpszDevice: *const u16, + pDevmode: *const c_void, + dwFlags: u32, + dwDesiredAccess: u32, + lpsa: *const c_void, + ) -> HANDLE; + pub fn CloseDesktop(hDesktop: HANDLE) -> i32; + pub fn GetThreadDesktop(dwThreadId: u32) -> HANDLE; + pub fn GetUserObjectInformationW( + hObj: HANDLE, + nIndex: i32, + pvInfo: *mut c_void, + nLength: u32, + lpnLengthNeeded: *mut u32, + ) -> i32; + } + + #[link(name = "kernel32")] + unsafe extern "system" { + pub fn GetCurrentThreadId() -> u32; + } +} +use winapi::*; diff --git a/tests/ui/resolve/ice-inconsistent-resolution-with-import-separators-issue-147208.rs b/tests/ui/resolve/ice-inconsistent-resolution-with-import-separators-issue-147208.rs new file mode 100644 index 0000000000000..e6c76aaa4f1f2 --- /dev/null +++ b/tests/ui/resolve/ice-inconsistent-resolution-with-import-separators-issue-147208.rs @@ -0,0 +1,18 @@ +//@ edition: 2024 + +// Regression test for issue https://github.com/rust-lang/rust/issues/147208 +// Fix by https://github.com/rust-lang/rust/pull/149681 + +use foo::bar::E::*; + //~^ ERROR cannot find module or crate `foo` in this scope +use foo::bar::test_use::io as std_io; + //~^ ERROR cannot find module or crate `foo` in this scope + //~| ERROR unresolved import `foo::bar::test_use::io` +fn main() { + Foo(()); + //~^ ERROR cannot find function, tuple struct or tuple variant `Foo` in this scope + { + use ::std::io as std_io; + use std_io::stdout; + } +} diff --git a/tests/ui/resolve/ice-inconsistent-resolution-with-import-separators-issue-147208.stderr b/tests/ui/resolve/ice-inconsistent-resolution-with-import-separators-issue-147208.stderr new file mode 100644 index 0000000000000..a7c6dc95cee70 --- /dev/null +++ b/tests/ui/resolve/ice-inconsistent-resolution-with-import-separators-issue-147208.stderr @@ -0,0 +1,32 @@ +error[E0433]: cannot find module or crate `foo` in this scope + --> $DIR/ice-inconsistent-resolution-with-import-separators-issue-147208.rs:6:5 + | +LL | use foo::bar::E::*; + | ^^^ use of unresolved module or unlinked crate `foo` + | + = help: you might be missing a crate named `foo` + +error[E0433]: cannot find module or crate `foo` in this scope + --> $DIR/ice-inconsistent-resolution-with-import-separators-issue-147208.rs:8:5 + | +LL | use foo::bar::test_use::io as std_io; + | ^^^ use of unresolved module or unlinked crate `foo` + | + = help: you might be missing a crate named `foo` + +error[E0432]: unresolved import `foo::bar::test_use::io` + --> $DIR/ice-inconsistent-resolution-with-import-separators-issue-147208.rs:8:5 + | +LL | use foo::bar::test_use::io as std_io; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0425]: cannot find function, tuple struct or tuple variant `Foo` in this scope + --> $DIR/ice-inconsistent-resolution-with-import-separators-issue-147208.rs:12:5 + | +LL | Foo(()); + | ^^^ not found in this scope + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0425, E0432, E0433. +For more information about an error, try `rustc --explain E0425`. diff --git a/tests/ui/resolve/ice-inconsistent-resolution-with-mod-issue-147208.rs b/tests/ui/resolve/ice-inconsistent-resolution-with-mod-issue-147208.rs new file mode 100644 index 0000000000000..2797497da80c0 --- /dev/null +++ b/tests/ui/resolve/ice-inconsistent-resolution-with-mod-issue-147208.rs @@ -0,0 +1,13 @@ +//@ edition: 2024 + +// Regression test for issue https://github.com/rust-lang/rust/issues/147208 +// Fix by https://github.com/rust-lang/rust/pull/149681 + +use bar::foo; + //~^ ERROR unresolved import `bar` +use foo::bar; +fn main() { + mod bar; + //~^ ERROR cannot declare a file module inside a block unless it has a path attribute + use bar::foo; +} diff --git a/tests/ui/resolve/ice-inconsistent-resolution-with-mod-issue-147208.stderr b/tests/ui/resolve/ice-inconsistent-resolution-with-mod-issue-147208.stderr new file mode 100644 index 0000000000000..e5d293630cdfa --- /dev/null +++ b/tests/ui/resolve/ice-inconsistent-resolution-with-mod-issue-147208.stderr @@ -0,0 +1,19 @@ +error: cannot declare a file module inside a block unless it has a path attribute + --> $DIR/ice-inconsistent-resolution-with-mod-issue-147208.rs:10:5 + | +LL | mod bar; + | ^^^^^^^^ + | + = note: file modules are usually placed outside of blocks, at the top level of the file + +error[E0432]: unresolved import `bar` + --> $DIR/ice-inconsistent-resolution-with-mod-issue-147208.rs:6:5 + | +LL | use bar::foo; + | ^^^ use of unresolved module or unlinked crate `bar` + | + = help: you might be missing a crate named `bar` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/suggestions/let-binding-init-expr-as-ty.rs b/tests/ui/suggestions/let-binding-init-expr-as-ty.rs index 22240d02d7fd2..b70bf5572a57c 100644 --- a/tests/ui/suggestions/let-binding-init-expr-as-ty.rs +++ b/tests/ui/suggestions/let-binding-init-expr-as-ty.rs @@ -28,6 +28,17 @@ fn main() { //~^ ERROR return type notation is experimental let x: S::new(()); //~ ERROR expected type, found associated function call + // Macros — suggestion must point at user code, not the macro definition (#158492) + let x: vec![]; //~ ERROR expected type, found associated function call + + // When the `let` is inside a macro, no suggestion should be emitted at the call site + macro_rules! make { + ($pat:pat) => { + let $pat: Vec::new(); //~ ERROR expected type, found associated function call + }; + } + make!(_); + // Literals let x: 42; //~ ERROR expected type, found `42` let x: ""; //~ ERROR expected type, found `""` diff --git a/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr b/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr index c096fd8c5556e..35198467409e8 100644 --- a/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr +++ b/tests/ui/suggestions/let-binding-init-expr-as-ty.stderr @@ -1,5 +1,5 @@ error: expected type, found `42` - --> $DIR/let-binding-init-expr-as-ty.rs:32:12 + --> $DIR/let-binding-init-expr-as-ty.rs:43:12 | LL | let x: 42; | - ^^ expected type @@ -13,7 +13,7 @@ LL + let x = 42; | error: expected type, found `""` - --> $DIR/let-binding-init-expr-as-ty.rs:33:12 + --> $DIR/let-binding-init-expr-as-ty.rs:44:12 | LL | let x: ""; | - ^^ expected type @@ -40,7 +40,7 @@ LL + let foo = i32::from_be(num); | error[E0573]: cannot find type `bar` in this scope - --> $DIR/let-binding-init-expr-as-ty.rs:36:12 + --> $DIR/let-binding-init-expr-as-ty.rs:47:12 | LL | let x: bar(); | ^^^ not found in this scope @@ -53,7 +53,7 @@ LL + let x = bar(); | error[E0573]: cannot find type `bar` in this scope - --> $DIR/let-binding-init-expr-as-ty.rs:37:12 + --> $DIR/let-binding-init-expr-as-ty.rs:48:12 | LL | let x: bar; | ^^^ not found in this scope @@ -61,7 +61,7 @@ LL | let x: bar; = note: a function named `bar` exists in another namespace error[E0573]: cannot find type `x` in this scope - --> $DIR/let-binding-init-expr-as-ty.rs:40:12 + --> $DIR/let-binding-init-expr-as-ty.rs:51:12 | LL | struct K(S::new(())); | --------------------- similarly named struct `K` defined here @@ -158,7 +158,30 @@ LL - let x: S::new(()); LL + let x = S::new(()); | -error: aborting due to 13 previous errors +error: expected type, found associated function call + --> $DIR/let-binding-init-expr-as-ty.rs:32:12 + | +LL | let x: vec![]; + | ^^^^^^ + | +help: use `=` if you meant to assign + | +LL - let x: vec![]; +LL + let x = vec![]; + | + +error: expected type, found associated function call + --> $DIR/let-binding-init-expr-as-ty.rs:37:23 + | +LL | let $pat: Vec::new(); + | ^^^^^^^^^^ +... +LL | make!(_); + | -------- in this macro invocation + | + = note: this error originates in the macro `make` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 15 previous errors Some errors have detailed explanations: E0573, E0658. For more information about an error, try `rustc --explain E0573`. diff --git a/tests/ui/traits/non_lifetime_binders/expected-region-found-kind.rs b/tests/ui/traits/non_lifetime_binders/expected-region-found-kind.rs new file mode 100644 index 0000000000000..c6796e9c3cba3 --- /dev/null +++ b/tests/ui/traits/non_lifetime_binders/expected-region-found-kind.rs @@ -0,0 +1,21 @@ +//! Regression test for . +//@ check-fail + +#![feature(non_lifetime_binders)] +trait E<'e> { + type As; +} + +trait F<'a>: for E<'a> + for<'e> E<'e> {} +//~^ ERROR type annotations needed: cannot satisfy `Self: E<'a>` [E0283] + +struct G<'a, T> +where + T: F<'a, As: E<'a>>, + //~^ ERROR type annotations needed: cannot satisfy `T: E<'a>` [E0283] + //~| ERROR ambiguous associated type `As` in bounds of `F` [E0221] +{ + x: &'a T, +} + +fn main() {} diff --git a/tests/ui/traits/non_lifetime_binders/expected-region-found-kind.stderr b/tests/ui/traits/non_lifetime_binders/expected-region-found-kind.stderr new file mode 100644 index 0000000000000..6ebba35fe9e2b --- /dev/null +++ b/tests/ui/traits/non_lifetime_binders/expected-region-found-kind.stderr @@ -0,0 +1,45 @@ +error[E0283]: type annotations needed: cannot satisfy `Self: E<'a>` + --> $DIR/expected-region-found-kind.rs:9:14 + | +LL | trait F<'a>: for E<'a> + for<'e> E<'e> {} + | ^^^^^^^^^^^^ + | +note: multiple `impl`s or `where` clauses satisfying `Self: E<'a>` found + --> $DIR/expected-region-found-kind.rs:9:14 + | +LL | trait F<'a>: for E<'a> + for<'e> E<'e> {} + | ^^^^^^^^^^^^ ^^^^^^^^^^^^^ + +error[E0221]: ambiguous associated type `As` in bounds of `F` + --> $DIR/expected-region-found-kind.rs:14:14 + | +LL | type As; + | ------- + | | + | ambiguous `As` from `for<'e> E<'e>` + | ambiguous `As` from `E<'a>` +... +LL | T: F<'a, As: E<'a>>, + | ^^^^^^^^^ ambiguous associated type `As` + +error[E0283]: type annotations needed: cannot satisfy `T: E<'a>` + --> $DIR/expected-region-found-kind.rs:14:8 + | +LL | T: F<'a, As: E<'a>>, + | ^^^^^^^^^^^^^^^^ + | +note: multiple `impl`s or `where` clauses satisfying `T: E<'a>` found + --> $DIR/expected-region-found-kind.rs:14:8 + | +LL | T: F<'a, As: E<'a>>, + | ^^^^^^^^^^^^^^^^ +note: required by a bound in `F` + --> $DIR/expected-region-found-kind.rs:9:14 + | +LL | trait F<'a>: for E<'a> + for<'e> E<'e> {} + | ^^^^^^^^^^^^ required by this bound in `F` + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0221, E0283. +For more information about an error, try `rustc --explain E0221`. diff --git a/triagebot.toml b/triagebot.toml index 6e8fbbad2651a..47e7f462f30cb 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1588,6 +1588,7 @@ cc = ["@rust-lang/wg-const-eval"] [assign] warn_non_default_branch.enable = true contributing_url = "https://rustc-dev-guide.rust-lang.org/getting-started.html" +llm_policy_url = "https://forge.rust-lang.org/policies/llm-usage.html" [[assign.warn_non_default_branch.exceptions]] title = "[beta"