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 e5dbae16d07d4..7d07139a9077e 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 c65e9bdbd211e..8256ef6442057 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 572200dbd7634..7121b4b654cfe 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_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_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/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 1664ce83aef72..6fac5cc11e1e4 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -515,8 +515,13 @@ macro_rules! uint_impl { /// /// # Panics /// - /// This function will panic if `n` is greater than or equal to the number of - /// bits in `self`. + /// ## Overflow behavior + /// + /// If overflow checks are enabled (default in debug mode), this function will panic if `n` + /// is greater than or equal to the number of bits in `self`. If overflow checks are + /// disabled (default in release mode), there is no panic; instead, the value is shifted + /// by `n % Self::BITS`. + // FIXME(wrapping_funnel_shifts): link to `wrapping_funnel_shl` when stable. /// /// # Examples /// @@ -543,21 +548,31 @@ macro_rules! uint_impl { /// /// ```should_panic /// #![feature(funnel_shifts)] + /// # #![feature(cfg_overflow_checks)] + /// # #[cfg(overflow_checks)] { /// #[doc = concat!("let a = ", stringify!($SelfT), "::MAX;")] /// // Okay #[doc = concat!("let _ = a.rotate_left(", stringify!($SelfT), "::BITS);")] - /// // Panics + /// // Panics (only when overflow checks are enabled) #[doc = concat!("let _ = a.funnel_shl(a, ", stringify!($SelfT), "::BITS);")] + /// # } + /// # #[cfg(not(overflow_checks))] panic!("fulfill should_panic"); /// ``` #[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] #[unstable(feature = "funnel_shifts", issue = "145686")] #[must_use = "this returns the result of the operation, without modifying the original"] #[inline(always)] + #[rustc_inherit_overflow_checks] pub const fn funnel_shl(self, right: Self, n: u32) -> Self { - assert!(n < Self::BITS, "attempt to funnel shift left with overflow"); - // SAFETY: just checked that `shift` is in-range - unsafe { self.unchecked_funnel_shl(right, n) } + if intrinsics::overflow_checks() { + assert!(n < Self::BITS, "attempt to funnel shift left with overflow"); + } + // SAFETY: `n` is wrapped to within range + unsafe { + let n = n & (Self::BITS - 1); + self.unchecked_funnel_shl(right, n) + } } /// Performs a right funnel shift. @@ -571,8 +586,13 @@ macro_rules! uint_impl { /// /// # Panics /// - /// This function will panic if `n` is greater than or equal to the number of - /// bits in `self`. + /// ## Overflow behavior + /// + /// If overflow checks are enabled (default in debug mode), this function will panic if `n` + /// is greater than or equal to the number of bits in `self`. If overflow checks are + /// disabled (default in release mode), there is no panic; instead, the value is shifted + /// by `n % Self::BITS`. + // FIXME(wrapping_funnel_shifts): link to `wrapping_funnel_shr` when stable. /// /// # Examples /// @@ -599,21 +619,31 @@ macro_rules! uint_impl { /// /// ```should_panic /// #![feature(funnel_shifts)] + /// # #![feature(cfg_overflow_checks)] + /// # #[cfg(overflow_checks)] { /// #[doc = concat!("let a = ", stringify!($SelfT), "::MAX;")] /// // Okay #[doc = concat!("let _ = a.rotate_right(", stringify!($SelfT), "::BITS);")] - /// // Panics + /// // Panics (only when overflow checks are enabled) #[doc = concat!("let _ = a.funnel_shr(a, ", stringify!($SelfT), "::BITS);")] + /// # } + /// # #[cfg(not(overflow_checks))] panic!("fulfill should_panic"); /// ``` #[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] #[unstable(feature = "funnel_shifts", issue = "145686")] #[must_use = "this returns the result of the operation, without modifying the original"] #[inline(always)] + #[rustc_inherit_overflow_checks] pub const fn funnel_shr(self, right: Self, n: u32) -> Self { - assert!(n < Self::BITS, "attempt to funnel shift right with overflow"); - // SAFETY: just checked that `shift` is in-range - unsafe { self.unchecked_funnel_shr(right, n) } + if intrinsics::overflow_checks() { + assert!(n < Self::BITS, "attempt to funnel shift right with overflow"); + } + // SAFETY: `n` is wrapped to within range + unsafe { + let n = n & (Self::BITS - 1); + self.unchecked_funnel_shr(right, n) + } } /// Unchecked funnel shift left. 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/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 1f629f01f38dd..10d760cd04ad2 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -11,6 +11,7 @@ #![feature(borrowed_buf_init)] #![feature(bstr)] #![feature(casefold)] +#![feature(cfg_overflow_checks)] #![feature(cfg_target_has_reliable_f16_f128)] #![feature(char_internals)] #![feature(clone_to_uninit)] diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index 8189776807915..37ad8a6b74185 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -216,17 +216,31 @@ macro_rules! uint_module { } #[test] + #[cfg(overflow_checks)] #[should_panic = "attempt to funnel shift left with overflow"] fn test_funnel_shl_overflow() { let _ = <$T>::funnel_shl(A, B, $T::BITS); } #[test] + #[cfg(overflow_checks)] #[should_panic = "attempt to funnel shift right with overflow"] fn test_funnel_shr_overflow() { let _ = <$T>::funnel_shr(A, B, $T::BITS); } + #[test] + #[cfg(not(overflow_checks))] + fn test_funnel_shl_overflow() { + let _ = <$T>::funnel_shl(A, B, A); + } + + #[test] + #[cfg(not(overflow_checks))] + fn test_funnel_shr_overflow() { + let _ = <$T>::funnel_shr(A, B, B); + } + #[test] fn test_funnel_shifts_runtime() { for i in 0..$T::BITS - 1 { 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 9816981c7fc88..1ae0da23fe5e1 100644 --- a/library/std/src/sys/thread/mod.rs +++ b/library/std/src/sys/thread/mod.rs @@ -73,6 +73,7 @@ cfg_select! { target_os = "vxworks", target_os = "wasi", target_vendor = "apple", + target_os = "fuchsia", ))] pub use unix::sleep_until; #[expect(dead_code)] @@ -134,7 +135,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 2dbb0314cb271..0de15cc27934f 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -650,6 +650,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", @@ -672,7 +692,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( @@ -700,7 +720,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. @@ -778,6 +798,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 7634052c93f33..d745b836e822b 100644 --- a/library/unwind/src/types.rs +++ b/library/unwind/src/types.rs @@ -42,6 +42,12 @@ pub const unwinder_private_data_size: usize = cfg_select! { }; #[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 c008ff5090a9c..fcf8be30c9dd1 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 f5c28c8e0445e..7b621a2ecc834 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 a8359ad34fa50..456019fc96977 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/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/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/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/std/overflow-check-ops.rs b/tests/ui/std/overflow-check-ops.rs new file mode 100644 index 0000000000000..458c1b121664e --- /dev/null +++ b/tests/ui/std/overflow-check-ops.rs @@ -0,0 +1,45 @@ +//! Verify the behavior differences between enabling and disabling overflow checks. + +//@ run-pass +//@ needs-unwind +//@ revisions: ERROR WRAP +//@[ERROR] compile-flags: -C overflow-checks=true +//@[WRAP] compile-flags: -C overflow-checks=false + +#![feature(cfg_overflow_checks)] +#![feature(funnel_shifts)] + +use std::hint::black_box as bb; +use std::{assert_matches, fmt, panic}; + +#[track_caller] +fn check(func: fn() -> T, wrapping_res: T, name: &str) { + let type_name = std::any::type_name::(); + let res = panic::catch_unwind(func); + if cfg!(overflow_checks) { + assert_matches!(res, Err(_), "{type_name} {name}"); + } else { + assert_eq!(res.unwrap(), wrapping_res, "{type_name} {name}"); + } +} + +fn main() { + check(|| bb(u32::MAX) + bb(1), 0, "add"); + check(|| bb(0u32) - bb(1), u32::MAX, "sub"); + check(|| bb(u32::MAX) * bb(2), u32::MAX << 1, "mul"); + check(|| bb(1u32) << bb(32), 1, "shl"); + check(|| bb(u32::MAX) >> bb(32), u32::MAX, "shr"); + check(|| bb(1234u32).funnel_shl(4567, bb(32)), 1234, "funnel_shl"); + check(|| bb(1234u32).funnel_shr(4567, bb(32)), 4567, "funnel_shr"); + check(|| bb(u32::MAX).pow(bb(2)), 1, "pow"); + check(|| bb(u32::MAX).next_power_of_two(), 0, "next_power_of_two"); + + check(|| bb(i32::MAX) + bb(1), i32::MIN, "add"); + check(|| bb(i32::MIN) - bb(1), i32::MAX, "sub"); + check(|| bb(i32::MAX) * bb(2), i32::MAX << 1, "mul"); + check(|| -bb(i32::MIN), i32::MIN, "neg"); + check(|| bb(i32::MIN).abs(), i32::MIN, "abs"); + check(|| bb(1) << bb(32), 1, "shl"); + check(|| bb(i32::MAX) >> bb(32), i32::MAX, "shr"); + check(|| bb(i32::MAX).pow(bb(2)), 1, "pow"); +} 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/triagebot.toml b/triagebot.toml index a842b2a07c15f..4990c72ffce6c 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1581,6 +1581,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"