diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index ba28720afecdd..0758e5d045673 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -3,7 +3,7 @@ use std::iter; use rustc_index::IndexVec; use rustc_index::bit_set::DenseBitSet; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::mir::{UnwindTerminateReason, traversal}; +use rustc_middle::mir::{Local, UnwindTerminateReason, traversal}; use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, HasTypingEnv, TyAndLayout}; use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeFoldable, TypeVisitableExt}; use rustc_middle::{bug, mir, span_bug}; @@ -240,7 +240,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( let local_values = { let args = arg_local_refs(&mut start_bx, &mut fx, &memory_locals); - let mut allocate_local = |local| { + let mut allocate_local = |local: Local| { let decl = &mir.local_decls[local]; let layout = start_bx.layout_of(fx.monomorphize(decl.ty)); assert!(!layout.ty.has_erasable_regions()); diff --git a/compiler/rustc_data_structures/src/sorted_map/index_map.rs b/compiler/rustc_data_structures/src/sorted_map/index_map.rs index 1654867739f0d..b38b09d60ebf0 100644 --- a/compiler/rustc_data_structures/src/sorted_map/index_map.rs +++ b/compiler/rustc_data_structures/src/sorted_map/index_map.rs @@ -147,7 +147,7 @@ impl FromIterator<(K, V)> for SortedIndexMultiMap { where J: IntoIterator, { - let items = IndexVec::from_iter(iter); + let items = IndexVec::::from_iter(iter); let mut idx_sorted_by_item_key: Vec<_> = items.indices().collect(); // `sort_by_key` is stable, so insertion order is preserved for duplicate items. diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs index 358bc389bd138..f6298adf2ebb7 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs @@ -22,6 +22,12 @@ impl ExpectedIdx { } } +impl ProvidedIdx { + pub(crate) fn to_expected_idx(self) -> ExpectedIdx { + ExpectedIdx::from_u32(self.as_u32()) + } +} + // An issue that might be found in the compatibility matrix #[derive(Debug)] enum Issue { diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 120a43576e814..edd740d8d8f24 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -775,7 +775,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // First, check if we just need to wrap some arguments in a tuple. if let Some((mismatch_idx, terr)) = - compatibility_diagonal.iter().enumerate().find_map(|(i, c)| { + compatibility_diagonal.iter_enumerated().find_map(|(i, c)| { if let Compatibility::Incompatible(Some(terr)) = c { Some((i, *terr)) } else { @@ -787,24 +787,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Do we have as many extra provided arguments as the tuple's length? // If so, we might have just forgotten to wrap some args in a tuple. if let Some(ty::Tuple(tys)) = - formal_and_expected_inputs.get(mismatch_idx.into()).map(|tys| tys.1.kind()) + formal_and_expected_inputs.get(mismatch_idx.to_expected_idx()).map(|tys| tys.1.kind()) // If the tuple is unit, we're not actually wrapping any arguments. && !tys.is_empty() && provided_arg_tys.len() == formal_and_expected_inputs.len() - 1 + tys.len() { // Wrap up the N provided arguments starting at this position in a tuple. - let provided_as_tuple = Ty::new_tup_from_iter( - tcx, - provided_arg_tys.iter().map(|(ty, _)| *ty).skip(mismatch_idx).take(tys.len()), - ); + let provided_args_to_tuple = &provided_arg_tys[mismatch_idx..]; + let (provided_args_to_tuple, provided_args_after_tuple) = + provided_args_to_tuple.split_at(tys.len()); + let provided_as_tuple = + Ty::new_tup_from_iter(tcx, provided_args_to_tuple.iter().map(|&(ty, _)| ty)); let mut satisfied = true; // Check if the newly wrapped tuple + rest of the arguments are compatible. for ((_, expected_ty), provided_ty) in std::iter::zip( - formal_and_expected_inputs.iter().skip(mismatch_idx), - [provided_as_tuple].into_iter().chain( - provided_arg_tys.iter().map(|(ty, _)| *ty).skip(mismatch_idx + tys.len()), - ), + formal_and_expected_inputs[mismatch_idx.to_expected_idx()..].iter(), + [provided_as_tuple] + .into_iter() + .chain(provided_args_after_tuple.iter().map(|&(ty, _)| ty)), ) { if !self.may_coerce(provided_ty, *expected_ty) { satisfied = false; @@ -816,10 +817,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Take some care with spans, so we don't suggest wrapping a macro's // innards in parenthesis, for example. if satisfied - && let Some((_, lo)) = - provided_arg_tys.get(ProvidedIdx::from_usize(mismatch_idx)) - && let Some((_, hi)) = - provided_arg_tys.get(ProvidedIdx::from_usize(mismatch_idx + tys.len() - 1)) + && let &[(_, hi @ lo)] | &[(_, lo), .., (_, hi)] = provided_args_to_tuple { let mut err; if tys.len() == 1 { @@ -827,9 +825,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // so don't do anything special here. err = self.err_ctxt().report_and_explain_type_error( mk_trace( - *lo, - formal_and_expected_inputs[mismatch_idx.into()], - provided_arg_tys[mismatch_idx.into()].0, + lo, + formal_and_expected_inputs[mismatch_idx.to_expected_idx()], + provided_arg_tys[mismatch_idx].0, ), self.param_env, terr, @@ -868,7 +866,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { callee_ty, call_expr, None, - Some(mismatch_idx), + Some(mismatch_idx.as_usize()), &matched_inputs, &formal_and_expected_inputs, is_method, @@ -2615,7 +2613,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } let expected_display_type = self - .resolve_vars_if_possible(formal_and_expected_inputs[idx.into()].1) + .resolve_vars_if_possible(formal_and_expected_inputs[idx].1) .sort_string(self.tcx); let label = if idxs_matched == params_with_generics.len() - 1 { format!( diff --git a/compiler/rustc_index/src/idx.rs b/compiler/rustc_index/src/idx.rs index b85160540d872..33f406e21137d 100644 --- a/compiler/rustc_index/src/idx.rs +++ b/compiler/rustc_index/src/idx.rs @@ -1,5 +1,7 @@ use std::fmt::Debug; use std::hash::Hash; +use std::ops; +use std::slice::SliceIndex; /// Represents some newtyped `usize` wrapper. /// @@ -43,3 +45,92 @@ impl Idx for u32 { self as usize } } + +/// Helper trait for indexing operations with a custom index type. +pub trait IntoSliceIdx { + type Output: SliceIndex; + fn into_slice_idx(self) -> Self::Output; +} + +impl IntoSliceIdx for I { + type Output = usize; + #[inline] + fn into_slice_idx(self) -> Self::Output { + self.index() + } +} + +impl IntoSliceIdx for ops::RangeFull { + type Output = ops::RangeFull; + #[inline] + fn into_slice_idx(self) -> Self::Output { + self + } +} + +impl IntoSliceIdx for ops::Range { + type Output = ops::Range; + #[inline] + fn into_slice_idx(self) -> Self::Output { + ops::Range { start: self.start.index(), end: self.end.index() } + } +} + +impl IntoSliceIdx for ops::RangeFrom { + type Output = ops::RangeFrom; + #[inline] + fn into_slice_idx(self) -> Self::Output { + ops::RangeFrom { start: self.start.index() } + } +} + +impl IntoSliceIdx for ops::RangeTo { + type Output = ops::RangeTo; + #[inline] + fn into_slice_idx(self) -> Self::Output { + ..self.end.index() + } +} + +impl IntoSliceIdx for ops::RangeInclusive { + type Output = ops::RangeInclusive; + #[inline] + fn into_slice_idx(self) -> Self::Output { + ops::RangeInclusive::new(self.start().index(), self.end().index()) + } +} + +impl IntoSliceIdx for ops::RangeToInclusive { + type Output = ops::RangeToInclusive; + #[inline] + fn into_slice_idx(self) -> Self::Output { + ..=self.end.index() + } +} + +#[cfg(feature = "nightly")] +impl IntoSliceIdx for core::range::Range { + type Output = core::range::Range; + #[inline] + fn into_slice_idx(self) -> Self::Output { + core::range::Range { start: self.start.index(), end: self.end.index() } + } +} + +#[cfg(feature = "nightly")] +impl IntoSliceIdx for core::range::RangeFrom { + type Output = core::range::RangeFrom; + #[inline] + fn into_slice_idx(self) -> Self::Output { + core::range::RangeFrom { start: self.start.index() } + } +} + +#[cfg(feature = "nightly")] +impl IntoSliceIdx for core::range::RangeInclusive { + type Output = core::range::RangeInclusive; + #[inline] + fn into_slice_idx(self) -> Self::Output { + core::range::RangeInclusive { start: self.start.index(), end: self.end.index() } + } +} diff --git a/compiler/rustc_index/src/lib.rs b/compiler/rustc_index/src/lib.rs index cae55230b0679..3441a5f65c785 100644 --- a/compiler/rustc_index/src/lib.rs +++ b/compiler/rustc_index/src/lib.rs @@ -2,6 +2,7 @@ #![cfg_attr(all(feature = "nightly", test), feature(stmt_expr_attributes))] #![cfg_attr(feature = "nightly", allow(internal_features))] #![cfg_attr(feature = "nightly", feature(extend_one, step_trait, test))] +#![cfg_attr(feature = "nightly", feature(new_range_api))] #![cfg_attr(feature = "nightly", feature(new_zeroed_alloc))] #![warn(unreachable_pub)] // tidy-alphabetical-end @@ -14,7 +15,7 @@ mod idx; mod slice; mod vec; -pub use idx::Idx; +pub use idx::{Idx, IntoSliceIdx}; pub use rustc_index_macros::newtype_index; pub use slice::IndexSlice; #[doc(no_inline)] diff --git a/compiler/rustc_index/src/slice.rs b/compiler/rustc_index/src/slice.rs index 4636f294f13bb..f17ea9e4b59ad 100644 --- a/compiler/rustc_index/src/slice.rs +++ b/compiler/rustc_index/src/slice.rs @@ -1,8 +1,9 @@ +use std::fmt; use std::marker::PhantomData; use std::ops::{Index, IndexMut}; -use std::{fmt, slice}; +use std::slice::{self, SliceIndex}; -use crate::{Idx, IndexVec}; +use crate::{Idx, IndexVec, IntoSliceIdx}; /// A view into contiguous `T`s, indexed by `I` rather than by `usize`. /// @@ -97,13 +98,19 @@ impl IndexSlice { } #[inline] - pub fn get(&self, index: I) -> Option<&T> { - self.raw.get(index.index()) + pub fn get>( + &self, + index: R, + ) -> Option<&>::Output> { + self.raw.get(index.into_slice_idx()) } #[inline] - pub fn get_mut(&mut self, index: I) -> Option<&mut T> { - self.raw.get_mut(index.index()) + pub fn get_mut>( + &mut self, + index: R, + ) -> Option<&mut >::Output> { + self.raw.get_mut(index.into_slice_idx()) } /// Returns mutable references to two distinct elements, `a` and `b`. @@ -184,19 +191,19 @@ impl fmt::Debug for IndexSlice { } } -impl Index for IndexSlice { - type Output = T; +impl> Index for IndexSlice { + type Output = >::Output; #[inline] - fn index(&self, index: I) -> &T { - &self.raw[index.index()] + fn index(&self, index: R) -> &Self::Output { + &self.raw[index.into_slice_idx()] } } -impl IndexMut for IndexSlice { +impl> IndexMut for IndexSlice { #[inline] - fn index_mut(&mut self, index: I) -> &mut T { - &mut self.raw[index.index()] + fn index_mut(&mut self, index: R) -> &mut Self::Output { + &mut self.raw[index.into_slice_idx()] } } diff --git a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs index e454a88e847fd..03c4614af139b 100644 --- a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs +++ b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs @@ -160,7 +160,7 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { /// empty region. The `expansion` phase will grow this larger. fn construct_var_data(&self) -> LexicalRegionResolutions<'tcx> { LexicalRegionResolutions { - values: IndexVec::from_fn_n( + values: IndexVec::::from_fn_n( |vid| { let vid_universe = self.var_infos[vid].universe; VarValue::Empty(vid_universe) diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index cf90df1b198b8..ea0bb5feb1220 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -96,6 +96,17 @@ impl<'tcx> HasLocalDecls<'tcx> for Body<'tcx> { } impl MirPhase { + pub fn name(&self) -> &'static str { + match *self { + MirPhase::Built => "built", + MirPhase::Analysis(AnalysisPhase::Initial) => "analysis", + MirPhase::Analysis(AnalysisPhase::PostCleanup) => "analysis-post-cleanup", + MirPhase::Runtime(RuntimePhase::Initial) => "runtime", + MirPhase::Runtime(RuntimePhase::PostCleanup) => "runtime-post-cleanup", + MirPhase::Runtime(RuntimePhase::Optimized) => "runtime-optimized", + } + } + /// Gets the (dialect, phase) index of the current `MirPhase`. Both numbers /// are 1-indexed. pub fn index(&self) -> (usize, usize) { diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs index 595a5e548b011..f05a798949b7e 100644 --- a/compiler/rustc_middle/src/mir/statement.rs +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -25,6 +25,26 @@ impl Statement<'_> { } impl<'tcx> StatementKind<'tcx> { + /// Returns a simple string representation of a `StatementKind` variant, independent of any + /// values it might hold (e.g. `StatementKind::Assign` always returns `"Assign"`). + pub const fn name(&self) -> &'static str { + match self { + StatementKind::Assign(..) => "Assign", + StatementKind::FakeRead(..) => "FakeRead", + StatementKind::SetDiscriminant { .. } => "SetDiscriminant", + StatementKind::Deinit(..) => "Deinit", + StatementKind::StorageLive(..) => "StorageLive", + StatementKind::StorageDead(..) => "StorageDead", + StatementKind::Retag(..) => "Retag", + StatementKind::PlaceMention(..) => "PlaceMention", + StatementKind::AscribeUserType(..) => "AscribeUserType", + StatementKind::Coverage(..) => "Coverage", + StatementKind::Intrinsic(..) => "Intrinsic", + StatementKind::ConstEvalCounter => "ConstEvalCounter", + StatementKind::Nop => "Nop", + StatementKind::BackwardIncompatibleDropHint { .. } => "BackwardIncompatibleDropHint", + } + } pub fn as_assign_mut(&mut self) -> Option<&mut (Place<'tcx>, Rvalue<'tcx>)> { match self { StatementKind::Assign(x) => Some(x), @@ -862,3 +882,40 @@ impl<'tcx> BinOp { }) } } + +impl From for RawPtrKind { + fn from(other: Mutability) -> Self { + match other { + Mutability::Mut => RawPtrKind::Mut, + Mutability::Not => RawPtrKind::Const, + } + } +} + +impl RawPtrKind { + pub fn is_fake(self) -> bool { + match self { + RawPtrKind::Mut | RawPtrKind::Const => false, + RawPtrKind::FakeForPtrMetadata => true, + } + } + + pub fn to_mutbl_lossy(self) -> Mutability { + match self { + RawPtrKind::Mut => Mutability::Mut, + RawPtrKind::Const => Mutability::Not, + + // We have no type corresponding to a fake borrow, so use + // `*const` as an approximation. + RawPtrKind::FakeForPtrMetadata => Mutability::Not, + } + } + + pub fn ptr_str(self) -> &'static str { + match self { + RawPtrKind::Mut => "mut", + RawPtrKind::Const => "const", + RawPtrKind::FakeForPtrMetadata => "const (fake)", + } + } +} diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index af6f0e4c55183..4f86703e95376 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -97,19 +97,6 @@ pub enum MirPhase { Runtime(RuntimePhase), } -impl MirPhase { - pub fn name(&self) -> &'static str { - match *self { - MirPhase::Built => "built", - MirPhase::Analysis(AnalysisPhase::Initial) => "analysis", - MirPhase::Analysis(AnalysisPhase::PostCleanup) => "analysis-post-cleanup", - MirPhase::Runtime(RuntimePhase::Initial) => "runtime", - MirPhase::Runtime(RuntimePhase::PostCleanup) => "runtime-post-cleanup", - MirPhase::Runtime(RuntimePhase::Optimized) => "runtime-optimized", - } - } -} - /// See [`MirPhase::Analysis`]. #[derive(Copy, Clone, TyEncodable, TyDecodable, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(HashStable)] @@ -206,43 +193,6 @@ pub enum RawPtrKind { FakeForPtrMetadata, } -impl From for RawPtrKind { - fn from(other: Mutability) -> Self { - match other { - Mutability::Mut => RawPtrKind::Mut, - Mutability::Not => RawPtrKind::Const, - } - } -} - -impl RawPtrKind { - pub fn is_fake(self) -> bool { - match self { - RawPtrKind::Mut | RawPtrKind::Const => false, - RawPtrKind::FakeForPtrMetadata => true, - } - } - - pub fn to_mutbl_lossy(self) -> Mutability { - match self { - RawPtrKind::Mut => Mutability::Mut, - RawPtrKind::Const => Mutability::Not, - - // We have no type corresponding to a fake borrow, so use - // `*const` as an approximation. - RawPtrKind::FakeForPtrMetadata => Mutability::Not, - } - } - - pub fn ptr_str(self) -> &'static str { - match self { - RawPtrKind::Mut => "mut", - RawPtrKind::Const => "const", - RawPtrKind::FakeForPtrMetadata => "const (fake)", - } - } -} - #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, TyEncodable, TyDecodable)] #[derive(Hash, HashStable)] pub enum MutBorrowKind { @@ -515,29 +465,6 @@ pub enum StatementKind<'tcx> { }, } -impl StatementKind<'_> { - /// Returns a simple string representation of a `StatementKind` variant, independent of any - /// values it might hold (e.g. `StatementKind::Assign` always returns `"Assign"`). - pub const fn name(&self) -> &'static str { - match self { - StatementKind::Assign(..) => "Assign", - StatementKind::FakeRead(..) => "FakeRead", - StatementKind::SetDiscriminant { .. } => "SetDiscriminant", - StatementKind::Deinit(..) => "Deinit", - StatementKind::StorageLive(..) => "StorageLive", - StatementKind::StorageDead(..) => "StorageDead", - StatementKind::Retag(..) => "Retag", - StatementKind::PlaceMention(..) => "PlaceMention", - StatementKind::AscribeUserType(..) => "AscribeUserType", - StatementKind::Coverage(..) => "Coverage", - StatementKind::Intrinsic(..) => "Intrinsic", - StatementKind::ConstEvalCounter => "ConstEvalCounter", - StatementKind::Nop => "Nop", - StatementKind::BackwardIncompatibleDropHint { .. } => "BackwardIncompatibleDropHint", - } - } -} - #[derive( Clone, TyEncodable, @@ -673,12 +600,6 @@ pub enum CallSource { Normal, } -impl CallSource { - pub fn from_hir_call(self) -> bool { - matches!(self, CallSource::Normal) - } -} - #[derive(Clone, Copy, Debug, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)] #[derive(TypeFoldable, TypeVisitable)] /// The macro that an inline assembly block was created by @@ -689,15 +610,6 @@ pub enum InlineAsmMacro { NakedAsm, } -impl InlineAsmMacro { - pub const fn diverges(self, options: InlineAsmOptions) -> bool { - match self { - InlineAsmMacro::Asm => options.contains(InlineAsmOptions::NORETURN), - InlineAsmMacro::NakedAsm => true, - } - } -} - /////////////////////////////////////////////////////////////////////////// // Terminators @@ -999,30 +911,6 @@ pub enum BackwardIncompatibleDropReason { Edition2024, } -impl TerminatorKind<'_> { - /// Returns a simple string representation of a `TerminatorKind` variant, independent of any - /// values it might hold (e.g. `TerminatorKind::Call` always returns `"Call"`). - pub const fn name(&self) -> &'static str { - match self { - TerminatorKind::Goto { .. } => "Goto", - TerminatorKind::SwitchInt { .. } => "SwitchInt", - TerminatorKind::UnwindResume => "UnwindResume", - TerminatorKind::UnwindTerminate(_) => "UnwindTerminate", - TerminatorKind::Return => "Return", - TerminatorKind::Unreachable => "Unreachable", - TerminatorKind::Drop { .. } => "Drop", - TerminatorKind::Call { .. } => "Call", - TerminatorKind::TailCall { .. } => "TailCall", - TerminatorKind::Assert { .. } => "Assert", - TerminatorKind::Yield { .. } => "Yield", - TerminatorKind::CoroutineDrop => "CoroutineDrop", - TerminatorKind::FalseEdge { .. } => "FalseEdge", - TerminatorKind::FalseUnwind { .. } => "FalseUnwind", - TerminatorKind::InlineAsm { .. } => "InlineAsm", - } - } -} - #[derive(Debug, Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)] pub struct SwitchTargets { /// Possible values. For each value, the location to branch to is found in diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index fdfcb128778a7..b887370fd699a 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -2,12 +2,13 @@ use std::slice; +use rustc_ast::InlineAsmOptions; use rustc_data_structures::packed::Pu128; use rustc_hir::LangItem; use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable}; use smallvec::{SmallVec, smallvec}; -use super::{TerminatorKind, *}; +use super::*; impl SwitchTargets { /// Creates switch targets from an iterator of values and target blocks. @@ -414,6 +415,28 @@ impl<'tcx> Terminator<'tcx> { } impl<'tcx> TerminatorKind<'tcx> { + /// Returns a simple string representation of a `TerminatorKind` variant, independent of any + /// values it might hold (e.g. `TerminatorKind::Call` always returns `"Call"`). + pub const fn name(&self) -> &'static str { + match self { + TerminatorKind::Goto { .. } => "Goto", + TerminatorKind::SwitchInt { .. } => "SwitchInt", + TerminatorKind::UnwindResume => "UnwindResume", + TerminatorKind::UnwindTerminate(_) => "UnwindTerminate", + TerminatorKind::Return => "Return", + TerminatorKind::Unreachable => "Unreachable", + TerminatorKind::Drop { .. } => "Drop", + TerminatorKind::Call { .. } => "Call", + TerminatorKind::TailCall { .. } => "TailCall", + TerminatorKind::Assert { .. } => "Assert", + TerminatorKind::Yield { .. } => "Yield", + TerminatorKind::CoroutineDrop => "CoroutineDrop", + TerminatorKind::FalseEdge { .. } => "FalseEdge", + TerminatorKind::FalseUnwind { .. } => "FalseUnwind", + TerminatorKind::InlineAsm { .. } => "InlineAsm", + } + } + #[inline] pub fn if_(cond: Operand<'tcx>, t: BasicBlock, f: BasicBlock) -> TerminatorKind<'tcx> { TerminatorKind::SwitchInt { discr: cond, targets: SwitchTargets::static_if(0, f, t) } @@ -698,3 +721,18 @@ impl<'tcx> TerminatorKind<'tcx> { } } } + +impl CallSource { + pub fn from_hir_call(self) -> bool { + matches!(self, CallSource::Normal) + } +} + +impl InlineAsmMacro { + pub const fn diverges(self, options: InlineAsmOptions) -> bool { + match self { + InlineAsmMacro::Asm => options.contains(InlineAsmOptions::NORETURN), + InlineAsmMacro::NakedAsm => true, + } + } +} diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 8ed5a118093f3..dbbbdc606bbb4 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -47,7 +47,7 @@ pub use rustc_session::lint::RegisteredTools; use rustc_span::hygiene::MacroKind; use rustc_span::{ExpnId, ExpnKind, Ident, Span, Symbol, kw, sym}; pub use rustc_type_ir::relate::VarianceDiagInfo; -pub use rustc_type_ir::{Movability, Mutability, *}; +pub use rustc_type_ir::*; use tracing::{debug, instrument}; pub use vtable::*; use {rustc_ast as ast, rustc_attr_parsing as attr, rustc_hir as hir}; diff --git a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs index ee331713736e7..10b43390eb288 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use rustc_middle::mir::*; -use rustc_middle::thir::{self, *}; +use rustc_middle::thir::*; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; use crate::builder::Builder; @@ -134,7 +134,7 @@ impl<'tcx> MatchPairTree<'tcx> { PatKind::Constant { value } => TestCase::Constant { value }, PatKind::AscribeUserType { - ascription: thir::Ascription { ref annotation, variance }, + ascription: Ascription { ref annotation, variance }, ref subpattern, .. } => { diff --git a/compiler/rustc_mir_transform/src/check_pointers.rs b/compiler/rustc_mir_transform/src/check_pointers.rs index d693f739180f4..2d04b62193585 100644 --- a/compiler/rustc_mir_transform/src/check_pointers.rs +++ b/compiler/rustc_mir_transform/src/check_pointers.rs @@ -71,8 +71,7 @@ pub(crate) fn check_pointers<'tcx, F>( // statements/blocks after. Iterating or visiting the MIR in order would require updating // our current location after every insertion. By iterating backwards, we dodge this issue: // The only Locations that an insertion changes have already been handled. - for block in (0..basic_blocks.len()).rev() { - let block = block.into(); + for block in basic_blocks.indices().rev() { for statement_index in (0..basic_blocks[block].statements.len()).rev() { let location = Location { block, statement_index }; let statement = &basic_blocks[block].statements[statement_index]; diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index 039f346495be0..5568d42ab8f3c 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -50,7 +50,7 @@ fn make_node_flow_priority_list( // A "reloop" node has exactly one out-edge, which jumps back to the top // of an enclosing loop. Reloop nodes are typically visited more times // than loop-exit nodes, so try to avoid giving them physical counters. - let is_reloop_node = IndexVec::from_fn_n( + let is_reloop_node = IndexVec::::from_fn_n( |node| match graph.successors[node].as_slice() { &[succ] => graph.dominates(succ, node), _ => false, diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index 09384defea86f..dcc7c5b91d76a 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -42,7 +42,7 @@ impl CoverageGraph { // `SwitchInt` to have multiple targets to the same destination `BasicBlock`, so // de-duplication is required. This is done without reordering the successors. - let successors = IndexVec::from_fn_n( + let successors = IndexVec::::from_fn_n( |bcb| { let mut seen_bcbs = FxHashSet::default(); let terminator = mir_body[bcbs[bcb].last_bb()].terminator(); diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 2f8a305019999..29521d4272010 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1259,7 +1259,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { let layout = self.ecx.layout_of(lhs_ty).ok()?; - let as_bits = |value| { + let as_bits = |value: VnIndex| { let constant = self.evaluated[value].as_ref()?; if layout.backend_repr.is_scalar() { let scalar = self.ecx.read_scalar(constant).discard_err()?; diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 74221e2168751..7d99aaa173143 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -1352,11 +1352,11 @@ macro_rules! int_impl { /// /// Basic usage: /// ``` - /// #![feature(unbounded_shifts)] #[doc = concat!("assert_eq!(0x1", stringify!($SelfT), ".unbounded_shl(4), 0x10);")] #[doc = concat!("assert_eq!(0x1", stringify!($SelfT), ".unbounded_shl(129), 0);")] /// ``` - #[unstable(feature = "unbounded_shifts", issue = "129375")] + #[stable(feature = "unbounded_shifts", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "unbounded_shifts", since = "CURRENT_RUSTC_VERSION")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -1474,12 +1474,12 @@ macro_rules! int_impl { /// /// Basic usage: /// ``` - /// #![feature(unbounded_shifts)] #[doc = concat!("assert_eq!(0x10", stringify!($SelfT), ".unbounded_shr(4), 0x1);")] #[doc = concat!("assert_eq!(0x10", stringify!($SelfT), ".unbounded_shr(129), 0);")] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MIN.unbounded_shr(129), -1);")] /// ``` - #[unstable(feature = "unbounded_shifts", issue = "129375")] + #[stable(feature = "unbounded_shifts", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "unbounded_shifts", since = "CURRENT_RUSTC_VERSION")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 2682273b7f185..405c71121caad 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1613,11 +1613,11 @@ macro_rules! uint_impl { /// /// Basic usage: /// ``` - /// #![feature(unbounded_shifts)] #[doc = concat!("assert_eq!(0x1", stringify!($SelfT), ".unbounded_shl(4), 0x10);")] #[doc = concat!("assert_eq!(0x1", stringify!($SelfT), ".unbounded_shl(129), 0);")] /// ``` - #[unstable(feature = "unbounded_shifts", issue = "129375")] + #[stable(feature = "unbounded_shifts", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "unbounded_shifts", since = "CURRENT_RUSTC_VERSION")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -1734,11 +1734,11 @@ macro_rules! uint_impl { /// /// Basic usage: /// ``` - /// #![feature(unbounded_shifts)] #[doc = concat!("assert_eq!(0x10", stringify!($SelfT), ".unbounded_shr(4), 0x1);")] #[doc = concat!("assert_eq!(0x10", stringify!($SelfT), ".unbounded_shr(129), 0);")] /// ``` - #[unstable(feature = "unbounded_shifts", issue = "129375")] + #[stable(feature = "unbounded_shifts", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "unbounded_shifts", since = "CURRENT_RUSTC_VERSION")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] diff --git a/library/core/src/ops/control_flow.rs b/library/core/src/ops/control_flow.rs index c8fcee5c140f5..8993e14fcd379 100644 --- a/library/core/src/ops/control_flow.rs +++ b/library/core/src/ops/control_flow.rs @@ -229,6 +229,27 @@ impl ControlFlow { } } +impl ControlFlow { + /// Extracts the value `T` that is wrapped by `ControlFlow`. + /// + /// # Examples + /// + /// ``` + /// #![feature(control_flow_into_value)] + /// use std::ops::ControlFlow; + /// + /// assert_eq!(ControlFlow::::Break(1024).into_value(), 1024); + /// assert_eq!(ControlFlow::::Continue(512).into_value(), 512); + /// ``` + #[unstable(feature = "control_flow_into_value", issue = "137461")] + #[rustc_allow_const_fn_unstable(const_precise_live_drops)] + pub const fn into_value(self) -> T { + match self { + ControlFlow::Continue(x) | ControlFlow::Break(x) => x, + } + } +} + /// These are used only as part of implementing the iterator adapters. /// They have mediocre names and non-obvious semantics, so aren't /// currently on a path to potential stabilization. diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 471150cfd9671..d47af93c16017 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -2928,10 +2928,17 @@ impl [T] { /// This sort is unstable (i.e., may reorder equal elements), in-place (i.e., does not /// allocate), and *O*(*n* \* log(*n*)) worst-case. /// - /// If the implementation of [`Ord`] for `T` does not implement a [total order] the resulting - /// order of elements in the slice is unspecified. All original elements will remain in the - /// slice and any possible modifications via interior mutability are observed in the input. Same - /// is true if the implementation of [`Ord`] for `T` panics. + /// If the implementation of [`Ord`] for `T` does not implement a [total order], the function + /// may panic; even if the function exits normally, the resulting order of elements in the slice + /// is unspecified. See also the note on panicking below. + /// + /// For example `|a, b| (a - b).cmp(a)` is a comparison function that is neither transitive nor + /// reflexive nor total, `a < b < c < a` with `a = 1, b = 2, c = 3`. For more information and + /// examples see the [`Ord`] documentation. + /// + /// + /// All original elements will remain in the slice and any possible modifications via interior + /// mutability are observed in the input. Same is true if the implementation of [`Ord`] for `T` panics. /// /// Sorting types that only implement [`PartialOrd`] such as [`f32`] and [`f64`] require /// additional precautions. For example, `f32::NAN != f32::NAN`, which doesn't fulfill the @@ -2954,7 +2961,8 @@ impl [T] { /// /// # Panics /// - /// May panic if the implementation of [`Ord`] for `T` does not implement a [total order]. + /// May panic if the implementation of [`Ord`] for `T` does not implement a [total order], or if + /// the [`Ord`] implementation panics. /// /// # Examples /// @@ -2982,15 +2990,17 @@ impl [T] { /// This sort is unstable (i.e., may reorder equal elements), in-place (i.e., does not /// allocate), and *O*(*n* \* log(*n*)) worst-case. /// - /// If the comparison function `compare` does not implement a [total order] the resulting order - /// of elements in the slice is unspecified. All original elements will remain in the slice and - /// any possible modifications via interior mutability are observed in the input. Same is true - /// if `compare` panics. + /// If the comparison function `compare` does not implement a [total order], the function + /// may panic; even if the function exits normally, the resulting order of elements in the slice + /// is unspecified. See also the note on panicking below. /// /// For example `|a, b| (a - b).cmp(a)` is a comparison function that is neither transitive nor /// reflexive nor total, `a < b < c < a` with `a = 1, b = 2, c = 3`. For more information and /// examples see the [`Ord`] documentation. /// + /// All original elements will remain in the slice and any possible modifications via interior + /// mutability are observed in the input. Same is true if `compare` panics. + /// /// # Current implementation /// /// The current implementation is based on [ipnsort] by Lukas Bergdoll and Orson Peters, which @@ -3003,7 +3013,8 @@ impl [T] { /// /// # Panics /// - /// May panic if `compare` does not implement a [total order]. + /// May panic if the `compare` does not implement a [total order], or if + /// the `compare` itself panics. /// /// # Examples /// @@ -3034,10 +3045,16 @@ impl [T] { /// This sort is unstable (i.e., may reorder equal elements), in-place (i.e., does not /// allocate), and *O*(*n* \* log(*n*)) worst-case. /// - /// If the implementation of [`Ord`] for `K` does not implement a [total order] the resulting - /// order of elements in the slice is unspecified. All original elements will remain in the - /// slice and any possible modifications via interior mutability are observed in the input. Same - /// is true if the implementation of [`Ord`] for `K` panics. + /// If the implementation of [`Ord`] for `K` does not implement a [total order], the function + /// may panic; even if the function exits normally, the resulting order of elements in the slice + /// is unspecified. See also the note on panicking below. + /// + /// For example `|a, b| (a - b).cmp(a)` is a comparison function that is neither transitive nor + /// reflexive nor total, `a < b < c < a` with `a = 1, b = 2, c = 3`. For more information and + /// examples see the [`Ord`] documentation. + /// + /// All original elements will remain in the slice and any possible modifications via interior + /// mutability are observed in the input. Same is true if the implementation of [`Ord`] for `K` panics. /// /// # Current implementation /// @@ -3051,7 +3068,8 @@ impl [T] { /// /// # Panics /// - /// May panic if the implementation of [`Ord`] for `K` does not implement a [total order]. + /// May panic if the implementation of [`Ord`] for `K` does not implement a [total order], or if + /// the [`Ord`] implementation panics. /// /// # Examples /// diff --git a/library/coretests/tests/num/int_macros.rs b/library/coretests/tests/num/int_macros.rs index 392704e7509ac..bbf19d2b444f9 100644 --- a/library/coretests/tests/num/int_macros.rs +++ b/library/coretests/tests/num/int_macros.rs @@ -512,5 +512,169 @@ macro_rules! int_module { assert_eq_const_safe!(<$T>::midpoint(6, <$T>::MAX), <$T>::MAX / 2 + 3); } } + + // test_unbounded_sh* constants + const SHIFT_AMOUNT_OVERFLOW: u32 = <$T>::BITS; + const SHIFT_AMOUNT_OVERFLOW2: u32 = <$T>::BITS + 3; + const SHIFT_AMOUNT_OVERFLOW3: u32 = <$T>::BITS << 2; + + const SHIFT_AMOUNT_TEST_ONE: u32 = <$T>::BITS >> 1; + const SHIFT_AMOUNT_TEST_TWO: u32 = <$T>::BITS >> 3; + const SHIFT_AMOUNT_TEST_THREE: u32 = (<$T>::BITS >> 1) - 1; + const SHIFT_AMOUNT_TEST_FOUR: u32 = <$T>::BITS - 1; + + test_runtime_and_compiletime! { + fn test_unbounded_shl() { + // <$T>::MIN + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_TEST_ONE), (<$T>::MIN << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_TEST_TWO), (<$T>::MIN << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_TEST_THREE), (<$T>::MIN << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_TEST_FOUR), (<$T>::MIN << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, 1), (<$T>::MIN << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, 3), (<$T>::MIN << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, 5), (<$T>::MIN << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW3), 0); + + // <$T>::MAX + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_TEST_ONE), (<$T>::MAX << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_TEST_TWO), (<$T>::MAX << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_TEST_THREE), (<$T>::MAX << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_TEST_FOUR), (<$T>::MAX << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, 1), (<$T>::MAX << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, 3), (<$T>::MAX << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, 5), (<$T>::MAX << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 1 + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_TEST_ONE), (1 << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_TEST_TWO), (1 << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_TEST_THREE), (1 << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_TEST_FOUR), (1 << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, 1), (1 << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, 3), (1 << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, 5), (1 << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_OVERFLOW3), 0); + + // -1 + assert_eq_const_safe!(<$T>::unbounded_shl(-1, SHIFT_AMOUNT_TEST_ONE), (-1 << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(-1, SHIFT_AMOUNT_TEST_TWO), (-1 << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(-1, SHIFT_AMOUNT_TEST_THREE), (-1 << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(-1, SHIFT_AMOUNT_TEST_FOUR), (-1 << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(-1, 1), (-1 << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(-1, 3), (-1 << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(-1, 5), (-1 << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(-1, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(-1, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(-1, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(-1, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 8 + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_TEST_ONE), (8 << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_TEST_TWO), (8 << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_TEST_THREE), (8 << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_TEST_FOUR), (8 << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, 1), (8 << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, 3), (8 << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, 5), (8 << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 17 + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_TEST_ONE), (17 << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_TEST_TWO), (17 << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_TEST_THREE), (17 << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_TEST_FOUR), (17 << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, 1), (17 << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, 3), (17 << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, 5), (17 << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_OVERFLOW3), 0); + } + + fn test_unbounded_shr() { + // <$T>::MIN + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_TEST_ONE), (<$T>::MIN >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_TEST_TWO), (<$T>::MIN >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_TEST_THREE), (<$T>::MIN >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_TEST_FOUR), (<$T>::MIN >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, 1), (<$T>::MIN >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, 3), (<$T>::MIN >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, 5), (<$T>::MIN >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW), -1); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW2), -1); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW3), -1); + + // <$T>::MAX + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_TEST_ONE), (<$T>::MAX >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_TEST_TWO), (<$T>::MAX >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_TEST_THREE), (<$T>::MAX >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_TEST_FOUR), (<$T>::MAX >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, 1), (<$T>::MAX >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, 3), (<$T>::MAX >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, 5), (<$T>::MAX >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 1 + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_TEST_ONE), (1 >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_TEST_TWO), (1 >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_TEST_THREE), (1 >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_TEST_FOUR), (1 >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, 1), (1 >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, 3), (1 >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, 5), (1 >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_OVERFLOW3), 0); + + // -1 + assert_eq_const_safe!(<$T>::unbounded_shr(-1, SHIFT_AMOUNT_TEST_ONE), (-1 >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(-1, SHIFT_AMOUNT_TEST_TWO), (-1 >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(-1, SHIFT_AMOUNT_TEST_THREE), (-1 >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(-1, SHIFT_AMOUNT_TEST_FOUR), (-1 >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(-1, 1), (-1 >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(-1, 3), (-1 >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(-1, 5), (-1 >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(-1, SHIFT_AMOUNT_OVERFLOW), -1); + assert_eq_const_safe!(<$T>::unbounded_shr(-1, SHIFT_AMOUNT_OVERFLOW), -1); + assert_eq_const_safe!(<$T>::unbounded_shr(-1, SHIFT_AMOUNT_OVERFLOW2), -1); + assert_eq_const_safe!(<$T>::unbounded_shr(-1, SHIFT_AMOUNT_OVERFLOW3), -1); + + // 8 + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_TEST_ONE), (8 >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_TEST_TWO), (8 >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_TEST_THREE), (8 >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_TEST_FOUR), (8 >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, 1), (8 >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, 3), (8 >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, 5), (8 >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 17 + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_TEST_ONE), (17 >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_TEST_TWO), (17 >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_TEST_THREE), (17 >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_TEST_FOUR), (17 >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, 1), (17 >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, 3), (17 >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, 5), (17 >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_OVERFLOW3), 0); + } + } }; } diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index c085ba5a249af..d09eb97b17e06 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -351,5 +351,169 @@ macro_rules! uint_module { assert_eq_const_safe!(<$T>::midpoint(6, <$T>::MAX), (<$T>::MAX - <$T>::MIN) / 2 + 3); } } + + // test_unbounded_sh* constants + const SHIFT_AMOUNT_OVERFLOW: u32 = <$T>::BITS; + const SHIFT_AMOUNT_OVERFLOW2: u32 = <$T>::BITS + 3; + const SHIFT_AMOUNT_OVERFLOW3: u32 = <$T>::BITS << 2; + + const SHIFT_AMOUNT_TEST_ONE: u32 = <$T>::BITS >> 1; + const SHIFT_AMOUNT_TEST_TWO: u32 = <$T>::BITS >> 3; + const SHIFT_AMOUNT_TEST_THREE: u32 = (<$T>::BITS >> 1) - 1; + const SHIFT_AMOUNT_TEST_FOUR: u32 = <$T>::BITS - 1; + + test_runtime_and_compiletime! { + fn test_unbounded_shl() { + // <$T>::MIN + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_TEST_ONE), (<$T>::MIN << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_TEST_TWO), (<$T>::MIN << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_TEST_THREE), (<$T>::MIN << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_TEST_FOUR), (<$T>::MIN << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, 1), (<$T>::MIN << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, 3), (<$T>::MIN << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, 5), (<$T>::MIN << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW3), 0); + + // <$T>::MAX + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_TEST_ONE), (<$T>::MAX << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_TEST_TWO), (<$T>::MAX << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_TEST_THREE), (<$T>::MAX << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_TEST_FOUR), (<$T>::MAX << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, 1), (<$T>::MAX << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, 3), (<$T>::MAX << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, 5), (<$T>::MAX << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 1 + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_TEST_ONE), (1 << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_TEST_TWO), (1 << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_TEST_THREE), (1 << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_TEST_FOUR), (1 << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, 1), (1 << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, 3), (1 << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, 5), (1 << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(1, SHIFT_AMOUNT_OVERFLOW3), 0); + + // !0 + assert_eq_const_safe!(<$T>::unbounded_shl(!0, SHIFT_AMOUNT_TEST_ONE), (!0 << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(!0, SHIFT_AMOUNT_TEST_TWO), (!0 << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(!0, SHIFT_AMOUNT_TEST_THREE), (!0 << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(!0, SHIFT_AMOUNT_TEST_FOUR), (!0 << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(!0, 1), (!0 << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(!0, 3), (!0 << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(!0, 5), (!0 << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(!0, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(!0, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(!0, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(!0, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 8 + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_TEST_ONE), (8 << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_TEST_TWO), (8 << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_TEST_THREE), (8 << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_TEST_FOUR), (8 << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, 1), (8 << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, 3), (8 << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, 5), (8 << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(8, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 17 + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_TEST_ONE), (17 << SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_TEST_TWO), (17 << SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_TEST_THREE), (17 << SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_TEST_FOUR), (17 << SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, 1), (17 << 1)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, 3), (17 << 3)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, 5), (17 << 5)); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shl(17, SHIFT_AMOUNT_OVERFLOW3), 0); + } + + fn test_unbounded_shr() { + // <$T>::MIN + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_TEST_ONE), (<$T>::MIN >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_TEST_TWO), (<$T>::MIN >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_TEST_THREE), (<$T>::MIN >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_TEST_FOUR), (<$T>::MIN >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, 1), (<$T>::MIN >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, 3), (<$T>::MIN >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, 5), (<$T>::MIN >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MIN, SHIFT_AMOUNT_OVERFLOW3), 0); + + // <$T>::MAX + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_TEST_ONE), (<$T>::MAX >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_TEST_TWO), (<$T>::MAX >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_TEST_THREE), (<$T>::MAX >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_TEST_FOUR), (<$T>::MAX >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, 1), (<$T>::MAX >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, 3), (<$T>::MAX >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, 5), (<$T>::MAX >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(<$T>::MAX, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 1 + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_TEST_ONE), (1 >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_TEST_TWO), (1 >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_TEST_THREE), (1 >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_TEST_FOUR), (1 >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, 1), (1 >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, 3), (1 >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, 5), (1 >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(1, SHIFT_AMOUNT_OVERFLOW3), 0); + + // !0 + assert_eq_const_safe!(<$T>::unbounded_shr(!0, SHIFT_AMOUNT_TEST_ONE), (!0 >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(!0, SHIFT_AMOUNT_TEST_TWO), (!0 >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(!0, SHIFT_AMOUNT_TEST_THREE), (!0 >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(!0, SHIFT_AMOUNT_TEST_FOUR), (!0 >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(!0, 1), (!0 >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(!0, 3), (!0 >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(!0, 5), (!0 >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(!0, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(!0, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(!0, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(!0, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 8 + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_TEST_ONE), (8 >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_TEST_TWO), (8 >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_TEST_THREE), (8 >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_TEST_FOUR), (8 >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, 1), (8 >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, 3), (8 >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, 5), (8 >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(8, SHIFT_AMOUNT_OVERFLOW3), 0); + + // 17 + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_TEST_ONE), (17 >> SHIFT_AMOUNT_TEST_ONE)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_TEST_TWO), (17 >> SHIFT_AMOUNT_TEST_TWO)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_TEST_THREE), (17 >> SHIFT_AMOUNT_TEST_THREE)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_TEST_FOUR), (17 >> SHIFT_AMOUNT_TEST_FOUR)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, 1), (17 >> 1)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, 3), (17 >> 3)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, 5), (17 >> 5)); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_OVERFLOW), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_OVERFLOW2), 0); + assert_eq_const_safe!(<$T>::unbounded_shr(17, SHIFT_AMOUNT_OVERFLOW3), 0); + } + } }; } diff --git a/library/std/src/rt.rs b/library/std/src/rt.rs index 3a22a16cb165e..8f17e5de430ad 100644 --- a/library/std/src/rt.rs +++ b/library/std/src/rt.rs @@ -122,6 +122,7 @@ pub(crate) fn thread_cleanup() { // print a nice message. panic::catch_unwind(|| { crate::thread::drop_current(); + crate::sys::thread_cleanup(); }) .unwrap_or_else(handle_rt_panic); } diff --git a/library/std/src/sys/pal/hermit/mod.rs b/library/std/src/sys/pal/hermit/mod.rs index 21cbac643bbec..b00da7c92d2e6 100644 --- a/library/std/src/sys/pal/hermit/mod.rs +++ b/library/std/src/sys/pal/hermit/mod.rs @@ -67,6 +67,8 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) { } } +pub fn thread_cleanup() {} + // SAFETY: must be called only once during runtime cleanup. // NOTE: this is not guaranteed to run, for example when the program aborts. pub unsafe fn cleanup() {} diff --git a/library/std/src/sys/pal/sgx/mod.rs b/library/std/src/sys/pal/sgx/mod.rs index 37ca6b08c950b..8b4f907c9797e 100644 --- a/library/std/src/sys/pal/sgx/mod.rs +++ b/library/std/src/sys/pal/sgx/mod.rs @@ -34,6 +34,8 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) { } } +pub fn thread_cleanup() {} + // SAFETY: must be called only once during runtime cleanup. // NOTE: this is not guaranteed to run, for example when the program aborts. pub unsafe fn cleanup() {} diff --git a/library/std/src/sys/pal/solid/mod.rs b/library/std/src/sys/pal/solid/mod.rs index 06af7bfade059..940c7bb9c8460 100644 --- a/library/std/src/sys/pal/solid/mod.rs +++ b/library/std/src/sys/pal/solid/mod.rs @@ -36,6 +36,8 @@ pub mod time; // NOTE: this is not guaranteed to run, for example when Rust code is called externally. pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {} +pub fn thread_cleanup() {} + // SAFETY: must be called only once during runtime cleanup. pub unsafe fn cleanup() {} diff --git a/library/std/src/sys/pal/teeos/mod.rs b/library/std/src/sys/pal/teeos/mod.rs index 3632524157db9..4472a9e02ace1 100644 --- a/library/std/src/sys/pal/teeos/mod.rs +++ b/library/std/src/sys/pal/teeos/mod.rs @@ -42,6 +42,8 @@ pub fn abort_internal() -> ! { // so this should never be called. pub fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {} +pub fn thread_cleanup() {} + // SAFETY: must be called only once during runtime cleanup. // this is not guaranteed to run, for example when the program aborts. pub unsafe fn cleanup() { diff --git a/library/std/src/sys/pal/uefi/mod.rs b/library/std/src/sys/pal/uefi/mod.rs index 6a03e240c6bd4..239819b41e259 100644 --- a/library/std/src/sys/pal/uefi/mod.rs +++ b/library/std/src/sys/pal/uefi/mod.rs @@ -72,6 +72,8 @@ pub(crate) unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) { } } +pub fn thread_cleanup() {} + /// # SAFETY /// this is not guaranteed to run, for example when the program aborts. /// - must be called only once during runtime cleanup. diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index c0b56d8d2b28a..a776ee421bba5 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -48,7 +48,7 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { // behavior. reset_sigpipe(sigpipe); - stack_overflow::init(); + stack_overflow::protect(true); args::init(argc, argv); // Normally, `thread::spawn` will call `Thread::set_name` but since this thread @@ -222,12 +222,14 @@ pub(crate) fn on_broken_pipe_flag_used() -> bool { ON_BROKEN_PIPE_FLAG_USED.load(crate::sync::atomic::Ordering::Relaxed) } -// SAFETY: must be called only once during runtime cleanup. -// NOTE: this is not guaranteed to run, for example when the program aborts. -pub unsafe fn cleanup() { +pub fn thread_cleanup() { stack_overflow::cleanup(); } +// SAFETY: must be called only once during runtime cleanup. +// NOTE: this is not guaranteed to run, for example when the program aborts. +pub unsafe fn cleanup() {} + #[allow(unused_imports)] pub use libc::signal; diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs index 43ece63457fe6..467823e6c02f5 100644 --- a/library/std/src/sys/pal/unix/stack_overflow.rs +++ b/library/std/src/sys/pal/unix/stack_overflow.rs @@ -1,29 +1,6 @@ #![cfg_attr(test, allow(dead_code))] -pub use self::imp::{cleanup, init}; -use self::imp::{drop_handler, make_handler}; - -pub struct Handler { - data: *mut libc::c_void, -} - -impl Handler { - pub unsafe fn new() -> Handler { - make_handler(false) - } - - fn null() -> Handler { - Handler { data: crate::ptr::null_mut() } - } -} - -impl Drop for Handler { - fn drop(&mut self) { - unsafe { - drop_handler(self.data); - } - } -} +pub use self::imp::{cleanup, protect}; #[cfg(any( target_os = "linux", @@ -45,12 +22,11 @@ mod imp { #[cfg(all(target_os = "linux", target_env = "gnu"))] use libc::{mmap64, mprotect, munmap}; - use super::Handler; - use crate::cell::Cell; use crate::ops::Range; use crate::sync::OnceLock; - use crate::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}; + use crate::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use crate::sys::pal::unix::os; + use crate::sys::thread_local::{guard, local_pointer}; use crate::{io, mem, ptr, thread}; // We use a TLS variable to store the address of the guard page. While TLS @@ -58,9 +34,11 @@ mod imp { // since we make sure to write to the variable before the signal stack is // installed, thereby ensuring that the variable is always allocated when // the signal handler is called. - thread_local! { - // FIXME: use `Range` once that implements `Copy`. - static GUARD: Cell<(usize, usize)> = const { Cell::new((0, 0)) }; + local_pointer! { + static GUARD_START; + static GUARD_END; + + static SIGALTSTACK; } // Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages @@ -93,7 +71,9 @@ mod imp { info: *mut libc::siginfo_t, _data: *mut libc::c_void, ) { - let (start, end) = GUARD.get(); + let start = GUARD_START.get().addr(); + let end = GUARD_END.get().addr(); + // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. let addr = unsafe { (*info).si_addr().addr() }; @@ -119,51 +99,72 @@ mod imp { } static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0); - static MAIN_ALTSTACK: AtomicPtr = AtomicPtr::new(ptr::null_mut()); static NEED_ALTSTACK: AtomicBool = AtomicBool::new(false); + /// Set up stack overflow protection for the current thread + /// /// # Safety - /// Must be called only once + /// May only be called once per thread. #[forbid(unsafe_op_in_unsafe_fn)] - pub unsafe fn init() { - PAGE_SIZE.store(os::page_size(), Ordering::Relaxed); - - // Always write to GUARD to ensure the TLS variable is allocated. - let guard = unsafe { install_main_guard().unwrap_or(0..0) }; - GUARD.set((guard.start, guard.end)); - - // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" - let mut action: sigaction = unsafe { mem::zeroed() }; - for &signal in &[SIGSEGV, SIGBUS] { - // SAFETY: just fetches the current signal handler into action - unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; - // Configure our signal handler if one is not already set. - if action.sa_sigaction == SIG_DFL { - if !NEED_ALTSTACK.load(Ordering::Relaxed) { - // haven't set up our sigaltstack yet - NEED_ALTSTACK.store(true, Ordering::Release); - let handler = unsafe { make_handler(true) }; - MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); - mem::forget(handler); + pub unsafe fn protect(main_thread: bool) { + if main_thread { + PAGE_SIZE.store(os::page_size(), Ordering::Relaxed); + // Use acquire ordering to observe the page size store above, + // which is propagated by a release store to NEED_ALTSTACK. + } else if !NEED_ALTSTACK.load(Ordering::Acquire) { + return; + } + + let guard = if main_thread { + unsafe { install_main_guard().unwrap_or(0..0) } + } else { + unsafe { current_guard().unwrap_or(0..0) } + }; + + // Always store the guard range to ensure the TLS variables are allocated. + GUARD_START.set(ptr::without_provenance_mut(guard.start)); + GUARD_END.set(ptr::without_provenance_mut(guard.end)); + + if main_thread { + // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" + let mut action: sigaction = unsafe { mem::zeroed() }; + for &signal in &[SIGSEGV, SIGBUS] { + // SAFETY: just fetches the current signal handler into action + unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; + // Configure our signal handler if one is not already set. + if action.sa_sigaction == SIG_DFL { + if !NEED_ALTSTACK.load(Ordering::Relaxed) { + // Set up the signal stack and tell other threads to set + // up their own. This uses a release store to propagate + // the store to PAGE_SIZE above. + NEED_ALTSTACK.store(true, Ordering::Release); + unsafe { setup_sigaltstack() }; + } + + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + action.sa_sigaction = signal_handler as sighandler_t; + // SAFETY: only overriding signals if the default is set + unsafe { sigaction(signal, &action, ptr::null_mut()) }; } - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - action.sa_sigaction = signal_handler as sighandler_t; - // SAFETY: only overriding signals if the default is set - unsafe { sigaction(signal, &action, ptr::null_mut()) }; } + } else { + unsafe { setup_sigaltstack() }; } } /// # Safety - /// Must be called only once + /// Mutates the alternate signal stack #[forbid(unsafe_op_in_unsafe_fn)] - pub unsafe fn cleanup() { - // FIXME: I probably cause more bugs than I'm worth! - // see https://github.com/rust-lang/rust/issues/111272 - unsafe { drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed)) }; - } + unsafe fn setup_sigaltstack() { + // SAFETY: assuming stack_t is zero-initializable + let mut stack = unsafe { mem::zeroed() }; + // SAFETY: reads current stack_t into stack + unsafe { sigaltstack(ptr::null(), &mut stack) }; + // Do not overwrite the stack if one is already set. + if stack.ss_flags & SS_DISABLE == 0 { + return; + } - unsafe fn get_stack() -> libc::stack_t { // OpenBSD requires this flag for stack mapping // otherwise the said mapping will fail as a no-op on most systems // and has a different meaning on FreeBSD @@ -185,82 +186,60 @@ mod imp { let sigstack_size = sigstack_size(); let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let stackp = mmap64( - ptr::null_mut(), - sigstack_size + page_size, - PROT_READ | PROT_WRITE, - flags, - -1, - 0, - ); - if stackp == MAP_FAILED { + let allocation = unsafe { + mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) + }; + if allocation == MAP_FAILED { panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); } - let guard_result = libc::mprotect(stackp, page_size, PROT_NONE); + + let guard_result = unsafe { libc::mprotect(allocation, page_size, PROT_NONE) }; if guard_result != 0 { panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); } - let stackp = stackp.add(page_size); - libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } + let stack = libc::stack_t { + // Reserve a guard page at the bottom of the allocation. + ss_sp: unsafe { allocation.add(page_size) }, + ss_flags: 0, + ss_size: sigstack_size, + }; + // SAFETY: We warned our caller this would happen! + unsafe { + sigaltstack(&stack, ptr::null_mut()); + } + + // Ensure that `rt::thread_cleanup` gets called, which will in turn call + // cleanup, where this signal stack will be freed. + guard::enable(); + SIGALTSTACK.set(allocation.cast()); } - /// # Safety - /// Mutates the alternate signal stack - #[forbid(unsafe_op_in_unsafe_fn)] - pub unsafe fn make_handler(main_thread: bool) -> Handler { - if !NEED_ALTSTACK.load(Ordering::Acquire) { - return Handler::null(); + pub fn cleanup() { + let allocation = SIGALTSTACK.get(); + if allocation.is_null() { + return; } - if !main_thread { - // Always write to GUARD to ensure the TLS variable is allocated. - let guard = unsafe { current_guard() }.unwrap_or(0..0); - GUARD.set((guard.start, guard.end)); - } + SIGALTSTACK.set(ptr::null_mut()); - // SAFETY: assuming stack_t is zero-initializable - let mut stack = unsafe { mem::zeroed() }; - // SAFETY: reads current stack_t into stack - unsafe { sigaltstack(ptr::null(), &mut stack) }; - // Configure alternate signal stack, if one is not already set. - if stack.ss_flags & SS_DISABLE != 0 { - // SAFETY: We warned our caller this would happen! - unsafe { - stack = get_stack(); - sigaltstack(&stack, ptr::null_mut()); - } - Handler { data: stack.ss_sp as *mut libc::c_void } - } else { - Handler::null() - } - } + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); - /// # Safety - /// Must be called - /// - only with our handler or nullptr - /// - only when done with our altstack - /// This disables the alternate signal stack! - #[forbid(unsafe_op_in_unsafe_fn)] - pub unsafe fn drop_handler(data: *mut libc::c_void) { - if !data.is_null() { - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let disabling_stack = libc::stack_t { - ss_sp: ptr::null_mut(), - ss_flags: SS_DISABLE, - // Workaround for bug in macOS implementation of sigaltstack - // UNIX2003 which returns ENOMEM when disabling a stack while - // passing ss_size smaller than MINSIGSTKSZ. According to POSIX - // both ss_sp and ss_size should be ignored in this case. - ss_size: sigstack_size, - }; - // SAFETY: we warned the caller this disables the alternate signal stack! - unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; - // SAFETY: We know from `get_stackp` that the alternate stack we installed is part of - // a mapping that started one page earlier, so walk back a page and unmap from there. - unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; - } + let disabling_stack = libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: SS_DISABLE, + // Workaround for bug in macOS implementation of sigaltstack + // UNIX2003 which returns ENOMEM when disabling a stack while + // passing ss_size smaller than MINSIGSTKSZ. According to POSIX + // both ss_sp and ss_size should be ignored in this case. + ss_size: sigstack_size, + }; + unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; + + // SAFETY: we created this mapping in `setup_sigaltstack` above with + // this exact size. + unsafe { munmap(allocation.cast(), sigstack_size + page_size) }; } /// Modern kernels on modern hardware can have dynamic signal stack sizes. @@ -587,13 +566,6 @@ mod imp { target_os = "illumos", )))] mod imp { - pub unsafe fn init() {} - - pub unsafe fn cleanup() {} - - pub unsafe fn make_handler(_main_thread: bool) -> super::Handler { - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} + pub unsafe fn protect(_main_thread: bool) {} + pub fn cleanup() {} } diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs index 37d5e01790a0b..31d6d51ce8e9a 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/pal/unix/thread.rs @@ -102,10 +102,9 @@ impl Thread { extern "C" fn thread_start(main: *mut libc::c_void) -> *mut libc::c_void { unsafe { - // Next, set up our stack overflow handler which may get triggered if we run - // out of stack. - let _handler = stack_overflow::Handler::new(); - // Finally, let's run some code. + // Protect this thread against stack overflows + stack_overflow::protect(false); + // and run its main function. Box::from_raw(main as *mut Box)(); } ptr::null_mut() diff --git a/library/std/src/sys/pal/unsupported/common.rs b/library/std/src/sys/pal/unsupported/common.rs index 34a766683830d..ec046e226659a 100644 --- a/library/std/src/sys/pal/unsupported/common.rs +++ b/library/std/src/sys/pal/unsupported/common.rs @@ -4,6 +4,8 @@ use crate::io as std_io; // NOTE: this is not guaranteed to run, for example when Rust code is called externally. pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {} +pub fn thread_cleanup() {} + // SAFETY: must be called only once during runtime cleanup. // NOTE: this is not guaranteed to run, for example when the program aborts. pub unsafe fn cleanup() {} diff --git a/library/std/src/sys/pal/windows/fs.rs b/library/std/src/sys/pal/windows/fs.rs index 0ddce30cf8e44..dce5a429cb0d4 100644 --- a/library/std/src/sys/pal/windows/fs.rs +++ b/library/std/src/sys/pal/windows/fs.rs @@ -1,4 +1,4 @@ -use super::api::{self, WinError}; +use super::api::{self, WinError, set_file_information_by_handle}; use super::{IoResult, to_u16s}; use crate::alloc::{alloc, handle_alloc_error}; use crate::borrow::Cow; @@ -319,31 +319,17 @@ impl File { && creation == c::OPEN_ALWAYS && api::get_last_error() == WinError::ALREADY_EXISTS { - unsafe { - // This first tries `FileAllocationInfo` but falls back to - // `FileEndOfFileInfo` in order to support WINE. - // If WINE gains support for FileAllocationInfo, we should - // remove the fallback. - let alloc = c::FILE_ALLOCATION_INFO { AllocationSize: 0 }; - let result = c::SetFileInformationByHandle( - handle.as_raw_handle(), - c::FileAllocationInfo, - (&raw const alloc).cast::(), - mem::size_of::() as u32, - ); - if result == 0 { + // This first tries `FileAllocationInfo` but falls back to + // `FileEndOfFileInfo` in order to support WINE. + // If WINE gains support for FileAllocationInfo, we should + // remove the fallback. + let alloc = c::FILE_ALLOCATION_INFO { AllocationSize: 0 }; + set_file_information_by_handle(handle.as_raw_handle(), &alloc) + .or_else(|_| { let eof = c::FILE_END_OF_FILE_INFO { EndOfFile: 0 }; - let result = c::SetFileInformationByHandle( - handle.as_raw_handle(), - c::FileEndOfFileInfo, - (&raw const eof).cast::(), - mem::size_of::() as u32, - ); - if result == 0 { - return Err(io::Error::last_os_error()); - } - } - } + set_file_information_by_handle(handle.as_raw_handle(), &eof) + }) + .io_result()?; } Ok(File { handle: Handle::from_inner(handle) }) } else { diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs index 1eca346b76c2b..57e24d68981e6 100644 --- a/library/std/src/sys/pal/windows/mod.rs +++ b/library/std/src/sys/pal/windows/mod.rs @@ -58,6 +58,8 @@ pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) { } } +pub fn thread_cleanup() {} + // SAFETY: must be called only once during runtime cleanup. // NOTE: this is not guaranteed to run, for example when the program aborts. pub unsafe fn cleanup() { diff --git a/library/std/src/sys/pal/zkvm/mod.rs b/library/std/src/sys/pal/zkvm/mod.rs index 054c867f90d8e..46a59823b5bc5 100644 --- a/library/std/src/sys/pal/zkvm/mod.rs +++ b/library/std/src/sys/pal/zkvm/mod.rs @@ -33,6 +33,8 @@ use crate::io as std_io; // NOTE: this is not guaranteed to run, for example when Rust code is called externally. pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {} +pub fn thread_cleanup() {} + // SAFETY: must be called only once during runtime cleanup. // NOTE: this is not guaranteed to run, for example when the program aborts. pub unsafe fn cleanup() {} diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 4f5f8f92264c1..0ea4d8f1e3914 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -41,6 +41,7 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ --font-family: "Source Serif 4", NanumBarunGothic, serif; --font-family-code: "Source Code Pro", monospace; --line-number-padding: 4px; + --line-number-right-margin: 20px; /* scraped examples icons (34x33px) */ --prev-arrow-image: url('data:image/svg+xml, .example-line-numbers, -.rustdoc .scraped-example .src-line-numbers, -.rustdoc .scraped-example .src-line-numbers > pre { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.rustdoc .example-wrap > .example-line-numbers + pre, -.rustdoc .scraped-example .rust { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - .rustdoc .scraped-example { position: relative; } @@ -908,45 +893,31 @@ both the code example and the line numbers, so we need to remove the radius in t overflow: auto; } -.rustdoc .example-wrap pre.example-line-numbers, -.rustdoc .example-wrap .src-line-numbers { - min-width: fit-content; /* prevent collapsing into nothing in truncated scraped examples */ - flex-grow: 0; - text-align: right; - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - padding: 14px 8px; - padding-right: 2px; - color: var(--src-line-numbers-span-color); -} - -.example-wrap.digits-1 [data-nosnippet] { +.example-wrap.digits-1:not(.hide-lines) [data-nosnippet] { width: calc(1ch + var(--line-number-padding) * 2); } -.example-wrap.digits-2 [data-nosnippet] { +.example-wrap.digits-2:not(.hide-lines) [data-nosnippet] { width: calc(2ch + var(--line-number-padding) * 2); } -.example-wrap.digits-3 [data-nosnippet] { +.example-wrap.digits-3:not(.hide-lines) [data-nosnippet] { width: calc(3ch + var(--line-number-padding) * 2); } -.example-wrap.digits-4 [data-nosnippet] { +.example-wrap.digits-4:not(.hide-lines) [data-nosnippet] { width: calc(4ch + var(--line-number-padding) * 2); } -.example-wrap.digits-5 [data-nosnippet] { +.example-wrap.digits-5:not(.hide-lines) [data-nosnippet] { width: calc(5ch + var(--line-number-padding) * 2); } -.example-wrap.digits-6 [data-nosnippet] { +.example-wrap.digits-6:not(.hide-lines) [data-nosnippet] { width: calc(6ch + var(--line-number-padding) * 2); } -.example-wrap.digits-7 [data-nosnippet] { +.example-wrap.digits-7:not(.hide-lines) [data-nosnippet] { width: calc(7ch + var(--line-number-padding) * 2); } -.example-wrap.digits-8 [data-nosnippet] { +.example-wrap.digits-8:not(.hide-lines) [data-nosnippet] { width: calc(8ch + var(--line-number-padding) * 2); } -.example-wrap.digits-9 [data-nosnippet] { +.example-wrap.digits-9:not(.hide-lines) [data-nosnippet] { width: calc(9ch + var(--line-number-padding) * 2); } @@ -954,12 +925,12 @@ both the code example and the line numbers, so we need to remove the radius in t color: var(--src-line-numbers-span-color); text-align: right; display: inline-block; - margin-right: 20px; + margin-right: var(--line-number-right-margin); -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; user-select: none; - padding: 0 4px; + padding: 0 var(--line-number-padding); } .example-wrap [data-nosnippet]:target { border-right: none; @@ -967,6 +938,60 @@ both the code example and the line numbers, so we need to remove the radius in t .example-wrap .line-highlighted[data-nosnippet] { background-color: var(--src-line-number-highlighted-background-color); } +:root.word-wrap-source-code .example-wrap [data-nosnippet] { + position: absolute; + left: 0; +} +.word-wrap-source-code .example-wrap pre > code { + position: relative; + word-break: break-all; +} +:root.word-wrap-source-code .example-wrap pre > code { + display: block; + white-space: pre-wrap; +} +:root.word-wrap-source-code .example-wrap pre > code * { + word-break: break-all; +} +:root.word-wrap-source-code .example-wrap.digits-1 pre > code { + padding-left: calc( + 1ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +} +:root.word-wrap-source-code .example-wrap.digits-2 pre > code { + padding-left: calc( + 2ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +} +:root.word-wrap-source-code .example-wrap.digits-3 pre > code { + padding-left: calc( + 3ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +} +:root.word-wrap-source-code .example-wrap.digits-4 pre > code { + padding-left: calc( + 4ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +} +:root.word-wrap-source-code .example-wrap.digits-5 pre > code { + padding-left: calc( + 5ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +} +:root.word-wrap-source-code .example-wrap.digits-6 pre > code { + padding-left: calc( + 6ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +} +:root.word-wrap-source-code .example-wrap.digits-7 pre > code { + padding-left: calc( + 7ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +} +:root.word-wrap-source-code .example-wrap.digits-8 pre > code { + padding-left: calc( + 8ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +} +:root.word-wrap-source-code .example-wrap.digits-9 pre > code { + padding-left: calc( + 9ch + var(--line-number-padding) * 2 + var(--line-number-right-margin)); +} +.example-wrap.hide-lines [data-nosnippet] { + display: none; +} .search-loading { text-align: center; diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index e46cc1897e9e0..edfcc1291b976 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -1112,35 +1112,32 @@ function preLoadCss(cssUrl) { // @ts-expect-error window.rustdoc_add_line_numbers_to_examples = () => { - if (document.querySelector(".rustdoc.src")) { - // We are in the source code page, nothing to be done here! - return; + // @ts-expect-error + function generateLine(nb) { + return `${nb}`; } + onEachLazy(document.querySelectorAll( - ":not(.scraped-example) > .example-wrap > pre:not(.example-line-numbers)", - ), x => { - const parent = x.parentNode; - const line_numbers = parent.querySelectorAll(".example-line-numbers"); - if (line_numbers.length > 0) { + ".rustdoc:not(.src) :not(.scraped-example) > .example-wrap > pre > code", + ), code => { + if (hasClass(code.parentElement.parentElement, "hide-lines")) { + removeClass(code.parentElement.parentElement, "hide-lines"); return; } - const count = x.textContent.split("\n").length; - const elems = []; - for (let i = 0; i < count; ++i) { - elems.push(i + 1); - } - const node = document.createElement("pre"); - addClass(node, "example-line-numbers"); - node.innerHTML = elems.join("\n"); - parent.insertBefore(node, x); + const lines = code.innerHTML.split("\n"); + const digits = (lines.length + "").length; + // @ts-expect-error + code.innerHTML = lines.map((line, index) => generateLine(index + 1) + line).join("\n"); + addClass(code.parentElement.parentElement, `digits-${digits}`); }); }; // @ts-expect-error window.rustdoc_remove_line_numbers_from_examples = () => { - onEachLazy(document.querySelectorAll(".example-wrap > .example-line-numbers"), x => { - x.parentNode.removeChild(x); - }); + onEachLazy( + document.querySelectorAll(".rustdoc:not(.src) :not(.scraped-example) > .example-wrap"), + x => addClass(x, "hide-lines"), + ); }; if (getSettingValue("line-numbers") === "true") { diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index bf33e0f17e5fe..5f1bbd27328cb 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -59,6 +59,14 @@ } else { removeClass(document.documentElement, "sans-serif"); } + break; + case "word-wrap-source-code": + if (value === true) { + addClass(document.documentElement, "word-wrap-source-code"); + } else { + removeClass(document.documentElement, "word-wrap-source-code"); + } + break; } } @@ -246,6 +254,11 @@ "js_name": "sans-serif-fonts", "default": false, }, + { + "name": "Word wrap source code", + "js_name": "word-wrap-source-code", + "default": false, + }, ]; // Then we build the DOM. diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index 3042373fb096f..425b915b5f94e 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -286,6 +286,9 @@ if (getSettingValue("hide-modnav") === "true") { if (getSettingValue("sans-serif-fonts") === "true") { addClass(document.documentElement, "sans-serif"); } +if (getSettingValue("word-wrap-source-code") === "true") { + addClass(document.documentElement, "word-wrap-source-code"); +} function updateSidebarWidth() { const desktopSidebarWidth = getSettingValue("desktop-sidebar-width"); if (desktopSidebarWidth && desktopSidebarWidth !== "null") { diff --git a/tests/codegen/issues/str-to-string-128690.rs b/tests/codegen/issues/str-to-string-128690.rs index 8b416306ba66e..d9e69764be28e 100644 --- a/tests/codegen/issues/str-to-string-128690.rs +++ b/tests/codegen/issues/str-to-string-128690.rs @@ -2,25 +2,27 @@ #![crate_type = "lib"] //! Make sure str::to_string is specialized not to use fmt machinery. +//! +//! Note that the `CHECK-NOT`s here try to match on calls to functions under `core::fmt`. // CHECK-LABEL: define {{(dso_local )?}}void @one_ref #[no_mangle] pub fn one_ref(input: &str) -> String { - // CHECK-NOT: {{(call|invoke).*}}fmt + // CHECK-NOT: {{(call|invoke)}}{{.*}}@{{.*}}core{{.*}}fmt{{.*}} input.to_string() } // CHECK-LABEL: define {{(dso_local )?}}void @two_ref #[no_mangle] pub fn two_ref(input: &&str) -> String { - // CHECK-NOT: {{(call|invoke).*}}fmt + // CHECK-NOT: {{(call|invoke)}}{{.*}}@{{.*}}core{{.*}}fmt{{.*}} input.to_string() } // CHECK-LABEL: define {{(dso_local )?}}void @thirteen_ref #[no_mangle] pub fn thirteen_ref(input: &&&&&&&&&&&&&str) -> String { - // CHECK-NOT: {{(call|invoke).*}}fmt + // CHECK-NOT: {{(call|invoke)}}{{.*}}@{{.*}}core{{.*}}fmt{{.*}} input.to_string() } @@ -31,6 +33,6 @@ pub fn thirteen_ref(input: &&&&&&&&&&&&&str) -> String { // CHECK-LABEL: define {{(dso_local )?}}void @fourteen_ref #[no_mangle] pub fn fourteen_ref(input: &&&&&&&&&&&&&&str) -> String { - // CHECK: {{(call|invoke).*}}fmt + // CHECK: {{(call|invoke)}}{{.*}}@{{.*}}core{{.*}}fmt{{.*}} input.to_string() } diff --git a/tests/rustdoc-gui/docblock-code-block-line-number.goml b/tests/rustdoc-gui/docblock-code-block-line-number.goml index 032746a6bdf44..97273ceb195fa 100644 --- a/tests/rustdoc-gui/docblock-code-block-line-number.goml +++ b/tests/rustdoc-gui/docblock-code-block-line-number.goml @@ -21,7 +21,7 @@ assert-css: ( set-local-storage: {"rustdoc-line-numbers": "true"} reload: // We wait for the line numbers to be added into the DOM by the JS... -wait-for: "pre.example-line-numbers" +wait-for: ".digits-1 pre" // Otherwise, we can't check text color show-text: true @@ -35,30 +35,21 @@ define-function: ( call-function: ("switch-theme", {"theme": |theme|}) // If the test didn't fail, it means that it was found! assert-css: ( - "pre.example-line-numbers", + ".digits-1 pre [data-nosnippet]", { "color": |color|, - "margin": "0px", - "padding-top": "14px", - "padding-bottom": "14px", - "padding-left": "8px", - "padding-right": "2px", + "margin-top": "0px", + "margin-bottom": "0px", + "margin-left": "0px", + "margin-right": "20px", + "padding-top": "0px", + "padding-bottom": "0px", + "padding-left": "4px", + "padding-right": "4px", "text-align": "right", - // There should not be a radius on the right of the line numbers. - "border-top-left-radius": "6px", - "border-bottom-left-radius": "6px", - "border-top-right-radius": "0px", - "border-bottom-right-radius": "0px", }, ALL, ) - // There should not be a radius on the left of the line numbers. - assert-css: ("pre.example-line-numbers + .rust", { - "border-top-left-radius": "0px", - "border-bottom-left-radius": "0px", - "border-top-right-radius": "6px", - "border-bottom-right-radius": "6px", - }) }, ) call-function: ("check-colors", { @@ -74,9 +65,6 @@ call-function: ("check-colors", { "color": "#c67e2d", }) -// The first code block has two lines so let's check its `
` elements lists both of them.
-assert-text: ("pre.example-line-numbers", "1\n2")
-
 // Now, try changing the setting dynamically. We'll turn it off, using the settings menu,
 // and make sure it goes away.
 
@@ -87,42 +75,17 @@ assert-css: ("#settings", {"display": "block"})
 
 // Then, click the toggle button.
 click: "input#line-numbers"
-wait-for-false: "pre.example-line-numbers"
+wait-for: ".digits-1.hide-lines"
 assert-local-storage: {"rustdoc-line-numbers": "false" }
 
-// Check that the rounded corners are back.
-assert-css: (
-    ".example-wrap .rust",
-    {
-        "border-top-left-radius": "6px",
-        "border-bottom-left-radius": "6px",
-        "border-top-right-radius": "6px",
-        "border-bottom-right-radius": "6px",
-    },
-    ALL,
-)
-
 // Finally, turn it on again.
 click: "input#line-numbers"
-wait-for: "pre.example-line-numbers"
+wait-for: ".digits-1:not(.hide-lines)"
 assert-local-storage: {"rustdoc-line-numbers": "true" }
-wait-for: "pre.example-line-numbers"
 
 // Same check with scraped examples line numbers.
 go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html"
 
-assert-css: (
-    ".scraped-example .rust",
-    {
-        // There should not be a radius on the left of the code.
-        "border-top-left-radius": "0px",
-        "border-bottom-left-radius": "0px",
-        "border-top-right-radius": "6px",
-        "border-bottom-right-radius": "6px",
-    },
-    ALL,
-)
-
 define-function: (
     "check-padding",
     [path, padding_bottom],
@@ -203,17 +166,18 @@ assert-local-storage: {"rustdoc-line-numbers": "true" }
 assert: ".example-wrap > pre.language-txt"
 assert: ".example-wrap > pre.rust"
 assert-count: (".example-wrap", 2)
-assert-count: (".example-wrap > pre.example-line-numbers", 2)
+assert-count: (".example-wrap.digits-1", 2)
 
 click: "#settings-menu"
 wait-for: "#settings"
 
 // Then, click the toggle button.
 click: "input#line-numbers"
-wait-for-count: (".example-wrap > pre.example-line-numbers", 0)
+wait-for-count: (".example-wrap.digits-1.hide-lines", 2)
 assert-local-storage-false: {"rustdoc-line-numbers": "true" }
 
 // Now turning off the setting.
 click: "input#line-numbers"
-wait-for-count: (".example-wrap > pre.example-line-numbers", 2)
+wait-for-count: (".example-wrap.digits-1", 2)
+wait-for-count: (".example-wrap.digits-1.hide-lines", 0)
 assert-local-storage: {"rustdoc-line-numbers": "true" }
diff --git a/tests/rustdoc-gui/source-code-wrapping.goml b/tests/rustdoc-gui/source-code-wrapping.goml
new file mode 100644
index 0000000000000..cb2fd3052cdac
--- /dev/null
+++ b/tests/rustdoc-gui/source-code-wrapping.goml
@@ -0,0 +1,47 @@
+// Checks that the interactions with the source code pages are working as expected.
+go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
+show-text: true
+set-window-size: (1000, 1000)
+
+define-function: (
+    "click-code-wrapping",
+    [expected],
+    block {
+        click: "#word-wrap-source-code"
+        wait-for-local-storage: {"rustdoc-word-wrap-source-code": |expected|}
+    },
+)
+
+store-size: (".rust code", {"width": width, "height": height})
+click: "#settings-menu"
+wait-for: "#settings"
+call-function: ("click-code-wrapping", {"expected": "true"})
+wait-for-size-false: (".rust code", {"width": |width|, "height": |height|})
+store-size: (".rust code", {"width": new_width, "height": new_height})
+// The width should now be smaller than the window's and the height
+// should have increased.
+assert: |width| > |new_width| && |height| < |new_height|
+
+// Switching back to the original setting.
+call-function: ("click-code-wrapping", {"expected": "false"})
+assert-size: (".rust code", {"width": |width|, "height": |height|})
+
+// Now let's check in docs code examples.
+go-to: "file://" + |DOC_PATH| + "/test_docs/trait_bounds/index.html"
+click: "#settings-menu"
+wait-for: "#settings"
+
+store-size: (".example-wrap .rust code", {"width": rust_width, "height": rust_height})
+store-size: (".example-wrap .language-text code", {"width": txt_width, "height": txt_height})
+call-function: ("click-code-wrapping", {"expected": "true"})
+wait-for-size-false: (".example-wrap .rust code", {"width": |rust_width|, "height": |rust_height|})
+
+store-size: (".example-wrap .rust code", {"width": new_rust_width, "height": new_rust_height})
+store-size: (".example-wrap .language-text code", {"width": new_txt_width, "height": new_txt_height})
+
+assert: |rust_width| > |new_rust_width| && |rust_height| < |new_rust_height|
+assert: |txt_width| > |new_txt_width| && |txt_height| < |new_txt_height|
+
+call-function: ("click-code-wrapping", {"expected": "false"})
+wait-for-size: (".example-wrap .rust code", {"width": |rust_width|, "height": |rust_height|})
+assert-size: (".example-wrap .language-text code", {"width": |txt_width|, "height": |txt_height|})
diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs
index f9c20ab22b89d..31f6b7f09b7d0 100644
--- a/tests/rustdoc-gui/src/test_docs/lib.rs
+++ b/tests/rustdoc-gui/src/test_docs/lib.rs
@@ -615,6 +615,15 @@ pub mod private {
     }
 }
 
+/// ```
+/// fn super_long_function_name_because_i_need_to_hit_the_limit_and_break_beyond_it() {
+/// }
+/// ```
+///
+/// ```text
+/// fn super_long_function_name_because_i_need_to_hit_the_limit_and_break_beyond_it_v2() {
+/// }
+/// ```
 pub mod trait_bounds {
     pub trait OneBound: Sized {}
     pub trait TwoBounds: Sized + Copy {}
diff --git a/tests/ui/runtime/out-of-stack.rs b/tests/ui/runtime/out-of-stack.rs
index 6be34afb56035..90da5132505a2 100644
--- a/tests/ui/runtime/out-of-stack.rs
+++ b/tests/ui/runtime/out-of-stack.rs
@@ -20,6 +20,7 @@ use std::env;
 use std::hint::black_box;
 use std::process::Command;
 use std::thread;
+use std::cell::Cell;
 
 fn silent_recurse() {
     let buf = [0u8; 1000];
@@ -33,12 +34,30 @@ fn loud_recurse() {
     black_box(()); // don't optimize this into a tail call. please.
 }
 
+fn in_tls_destructor(f: impl FnOnce() + 'static) {
+    struct RunOnDrop(Cell>>);
+    impl Drop for RunOnDrop {
+        fn drop(&mut self) {
+            self.0.take().unwrap()()
+        }
+    }
+
+    thread_local! {
+        static RUN: RunOnDrop = RunOnDrop(Cell::new(None));
+    }
+
+    RUN.with(|run| run.0.set(Some(Box::new(f))));
+}
+
 #[cfg(unix)]
 fn check_status(status: std::process::ExitStatus)
 {
     use std::os::unix::process::ExitStatusExt;
 
     assert!(!status.success());
+    // Apple's libc has a bug where calling abort in a TLS destructor on a thread
+    // other than the main thread doesn't always result in SIGTRAP.
+    #[cfg(not(target_vendor = "apple"))]
     assert_eq!(status.signal(), Some(libc::SIGABRT));
 }
 
@@ -48,40 +67,51 @@ fn check_status(status: std::process::ExitStatus)
     assert!(!status.success());
 }
 
-
 fn main() {
     let args: Vec = env::args().collect();
-    if args.len() > 1 && args[1] == "silent" {
-        silent_recurse();
-    } else if args.len() > 1 && args[1] == "loud" {
-        loud_recurse();
-    } else if args.len() > 1 && args[1] == "silent-thread" {
-        thread::spawn(silent_recurse).join();
-    } else if args.len() > 1 && args[1] == "loud-thread" {
-        thread::spawn(loud_recurse).join();
-    } else {
-        let mut modes = vec![
-            "silent-thread",
-            "loud-thread",
-        ];
-
-        // On linux it looks like the main thread can sometimes grow its stack
-        // basically without bounds, so we only test the child thread cases
-        // there.
-        if !cfg!(target_os = "linux") {
-            modes.push("silent");
-            modes.push("loud");
+    match args.get(1).map(String::as_str) {
+        Some("silent") => silent_recurse(),
+        Some("loud") => loud_recurse(),
+        Some("silent-thread") => thread::spawn(silent_recurse).join().unwrap(),
+        Some("loud-thread") => thread::spawn(loud_recurse).join().unwrap(),
+        Some("silent-tls") => in_tls_destructor(silent_recurse),
+        Some("loud-tls") => in_tls_destructor(loud_recurse),
+        Some("silent-thread-tls") => {
+            thread::spawn(|| in_tls_destructor(silent_recurse)).join().unwrap();
+        }
+        Some("loud-thread-tls") => {
+            thread::spawn(|| in_tls_destructor(loud_recurse)).join().unwrap();
         }
-        for mode in modes {
-            println!("testing: {}", mode);
+        _ => {
+            let mut modes = vec![
+                "silent-thread",
+                "loud-thread",
+                "silent-thread-tls",
+                "loud-thread-tls",
+            ];
+
+            // On linux it looks like the main thread can sometimes grow its stack
+            // basically without bounds, so we only test the child thread cases
+            // there.
+            if !cfg!(target_os = "linux") {
+                modes.extend(["silent", "loud", "silent-tls", "loud-tls"]);
+            }
+
+            for mode in modes {
+                println!("testing: {}", mode);
 
-            let silent = Command::new(&args[0]).arg(mode).output().unwrap();
+                let silent = Command::new(&args[0]).arg(mode).output().unwrap();
 
-            check_status(silent.status);
+                let error = String::from_utf8_lossy(&silent.stderr);
+                assert!(error.contains("has overflowed its stack"),
+                        "missing overflow message: {}", error);
 
-            let error = String::from_utf8_lossy(&silent.stderr);
-            assert!(error.contains("has overflowed its stack"),
-                    "missing overflow message: {}", error);
+                // Stack overflows in TLS destructors do not result in the error
+                // code being changed on Windows.
+                if !(cfg!(target_os = "windows") && mode.contains("tls")) {
+                    check_status(silent.status);
+                }
+            }
         }
     }
 }