From d9fa6d2a7feb49413fd6e2cb5bf4e33e2ac70ed9 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Fri, 10 Apr 2026 09:24:30 -0600 Subject: [PATCH 1/4] test that Range loops optimize away but RangeInclusive loops do not --- tests/codegen-llvm/range-iter-loop-opts.rs | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/codegen-llvm/range-iter-loop-opts.rs diff --git a/tests/codegen-llvm/range-iter-loop-opts.rs b/tests/codegen-llvm/range-iter-loop-opts.rs new file mode 100644 index 0000000000000..528da65b87616 --- /dev/null +++ b/tests/codegen-llvm/range-iter-loop-opts.rs @@ -0,0 +1,60 @@ +// This test ensures that Range iterators are optimizable, to +// the point that some loops can be entirely optimized out. + +//@ compile-flags: -Copt-level=3 + +#![crate_type = "lib"] + +use std::ops::{Range, RangeInclusive}; + +// CHECK-LABEL: @range_noop_loop( +#[no_mangle] +pub unsafe fn range_noop_loop() { + // CHECK-NEXT: start: + // CHECK-NEXT: ret void + + // This loop should be optimized out entirely. + for _ in 0_u8..100 { + () + } +} + +// CHECK-LABEL: @range_count( +#[no_mangle] +pub unsafe fn range_count(s: u8, e: u8) -> usize { + // CHECK-NOT: br {{.*}} + // CHECK: ret i64 + + // This loop should be optimized to arithmetic. + let mut count = 0; + for _ in s..e { + count += 1; + } + count +} + +// RangeInclusive currently cannot optimize the same way. + +// CHECK-LABEL: @rangeinclusive_noop_loop( +#[no_mangle] +pub unsafe fn rangeinclusive_noop_loop() { + // CHECK: br {{.*}} + // CHECK: ret void + + for _ in 0_u8..=100 { + () + } +} + +// CHECK-LABEL: @rangeinclusive_count( +#[no_mangle] +pub unsafe fn rangeinclusive_count(s: u8, e: u8) -> usize { + // CHECK: br {{.*}} + // CHECK: ret i64 + + let mut count = 0; + for _ in s..=e { + count += 1; + } + count +} From c83fca5993c98f6e9d2e9c1184fb8882f2673ea9 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Fri, 10 Apr 2026 08:45:35 -0600 Subject: [PATCH 2/4] introduce Step::forward/backward_overflowing and optimize the RangeInclusive iterator implementation with them This changes the `exhausted` field to represent an overflow flag for the bounds, essentially acting as an extra bit for `Idx`. This was found to enable optimizations previously only applicable to the exclusive-ended Range type. - change end_bound to return Excluded(start) when exhausted - add contains to tests - make into_bounds panic when exhausted matches From> for RangeInclusive --- compiler/rustc_abi/src/lib.rs | 14 + compiler/rustc_index_macros/src/newtype.rs | 14 + library/core/src/iter/range.rs | 330 ++++++++++++------ library/core/src/ops/range.rs | 40 ++- library/coretests/tests/iter/range.rs | 58 ++- library/coretests/tests/ops.rs | 7 +- tests/codegen-llvm/range-iter-loop-opts.rs | 42 ++- ...op.runtime-optimized.after.panic-abort.mir | 4 +- ...p.runtime-optimized.after.panic-unwind.mir | 4 +- ...xt.runtime-optimized.after.panic-abort.mir | 4 +- ...t.runtime-optimized.after.panic-unwind.mir | 4 +- tests/ui/impl-trait/example-calendar.rs | 8 + 12 files changed, 377 insertions(+), 152 deletions(-) diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index 166c8bea6f354..980e5eb02352f 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -990,6 +990,13 @@ impl Step for Size { u64::forward_checked(start.bytes(), count).map(Self::from_bytes) } + #[inline] + #[cfg(not(bootstrap))] + fn forward_overflowing(start: Self, count: usize) -> (Self, bool) { + let (s, o) = u64::forward_overflowing(start.bytes(), count); + (Self::from_bytes(s), o) + } + #[inline] fn forward(start: Self, count: usize) -> Self { Self::from_bytes(u64::forward(start.bytes(), count)) @@ -1005,6 +1012,13 @@ impl Step for Size { u64::backward_checked(start.bytes(), count).map(Self::from_bytes) } + #[inline] + #[cfg(not(bootstrap))] + fn backward_overflowing(start: Self, count: usize) -> (Self, bool) { + let (s, o) = u64::backward_overflowing(start.bytes(), count); + (Self::from_bytes(s), o) + } + #[inline] fn backward(start: Self, count: usize) -> Self { Self::from_bytes(u64::backward(start.bytes(), count)) diff --git a/compiler/rustc_index_macros/src/newtype.rs b/compiler/rustc_index_macros/src/newtype.rs index 4553cf026bb08..14fd147f96780 100644 --- a/compiler/rustc_index_macros/src/newtype.rs +++ b/compiler/rustc_index_macros/src/newtype.rs @@ -136,6 +136,20 @@ impl Parse for Newtype { fn backward_checked(start: Self, u: usize) -> Option { Self::index(start).checked_sub(u).map(Self::from_usize) } + + #[inline] + #[cfg(not(bootstrap))] + fn forward_overflowing(start: Self, u: usize) -> (Self, bool) { + let (s, o) = Self::index(start).overflowing_add(u); + (Self::from_usize(s), o) + } + + #[inline] + #[cfg(not(bootstrap))] + fn backward_overflowing(start: Self, u: usize) -> (Self, bool) { + let (s, o) = Self::index(start).overflowing_sub(u); + (Self::from_usize(s), o) + } } impl ::std::cmp::Ord for #name { fn cmp(&self, other: &Self) -> std::cmp::Ordering { diff --git a/library/core/src/iter/range.rs b/library/core/src/iter/range.rs index 949295a77bca6..041aa8f75dbc4 100644 --- a/library/core/src/iter/range.rs +++ b/library/core/src/iter/range.rs @@ -66,6 +66,31 @@ pub const trait Step: [const] Clone + [const] PartialOrd + Sized { /// * Corollary: `Step::forward_checked(a, 0) == Some(a)` fn forward_checked(start: Self, count: usize) -> Option; + /// Returns the value that would be obtained by taking the *successor* + /// of `self` `count` times along with a boolean tracking whether overflow + /// occurred. + /// + /// If this would overflow the range of values supported by `Self`, the + /// value returned is unspecified and should not be relied on, though + /// typically wrapping (modular arithmetic) is the most effective + /// implementation to enable optimizations. + /// + /// # Invariants + /// + /// For any `a`, `n`, and `m`, where no overflow occurs: + /// + /// * `Step::forward_overflowing(Step::forward_overflowing(a, n).0, m) == Step::forward_overflowing(a, n + m)` + /// + /// For any `a` and `n`, where no overflow occurs: + /// + /// * `Step::forward_overflowing(a, n) == (Step::forward_checked(a, n).unwrap(), false)` + /// + /// For any `a` and `n`: + /// + /// * `Step::forward_overflowing(a, n) == (0..n).fold((a, false), |(x, y), _| { let (s, o) = Step::forward_overflowing(x, 1); (s, y || o) })` + /// * Corollary: `Step::forward_overflowing(a, 0) == (a, false)` + fn forward_overflowing(start: Self, count: usize) -> (Self, bool); + /// Returns the value that would be obtained by taking the *successor* /// of `self` `count` times. /// @@ -136,6 +161,31 @@ pub const trait Step: [const] Clone + [const] PartialOrd + Sized { /// * Corollary: `Step::backward_checked(a, 0) == Some(a)` fn backward_checked(start: Self, count: usize) -> Option; + /// Returns the value that would be obtained by taking the *successor* + /// of `self` `count` times along with a boolean tracking whether overflow + /// occurred. + /// + /// If this would overflow the range of values supported by `Self`, the + /// value returned is unspecified and should not be relied on, though + /// typically wrapping (modular arithmetic) is the most effective + /// implementation to enable optimizations. + /// + /// # Invariants + /// + /// For any `a`, `n`, and `m`, where no overflow occurs: + /// + /// * `Step::backward_overflowing(Step::backward_overflowing(a, n).0, m) == Step::backward_overflowing(a, n + m)` + /// + /// For any `a` and `n`, where no overflow occurs: + /// + /// * `Step::backward_overflowing(a, n) == (Step::backward_checked(a, n).unwrap(), false)` + /// + /// For any `a` and `n`: + /// + /// * `Step::backward_overflowing(a, n) == (0..n).fold((a, false), |(x, y), _| { let (s, o) = Step::backward_overflowing(x, 1); (s, y || o) })` + /// * Corollary: `Step::backward_overflowing(a, 0) == (a, false)` + fn backward_overflowing(start: Self, count: usize) -> (Self, bool); + /// Returns the value that would be obtained by taking the *predecessor* /// of `self` `count` times. /// @@ -293,6 +343,24 @@ macro_rules! step_integer_impls { Err(_) => None, // if n is out of range, `unsigned_start - n` is too } } + + #[inline] + fn forward_overflowing(start: Self, n: usize) -> (Self, bool) { + match Self::try_from(n) { + Ok(n) => start.overflowing_add(n), + // if n is out of range, `start + n` must overflow + Err(_) => (start.wrapping_add(n as Self), true), + } + } + + #[inline] + fn backward_overflowing(start: Self, n: usize) -> (Self, bool) { + match Self::try_from(n) { + Ok(n) => start.overflowing_sub(n), + // if n is out of range, `start - n` must overflow + Err(_) => (start.wrapping_add(n as Self), true), + } + } } #[allow(unreachable_patterns)] @@ -358,6 +426,28 @@ macro_rules! step_integer_impls { Err(_) => None, } } + + #[inline] + fn forward_overflowing(start: Self, n: usize) -> (Self, bool) { + match $u_narrower::try_from(n) { + Ok(n) => start.overflowing_add_unsigned(n), + // If n is out of range of e.g. u8, + // then it is bigger than the entire range for i8 is wide + // so `any_i8 + n` necessarily overflows i8. + Err(_) => (start.wrapping_add(n as Self), true), + } + } + + #[inline] + fn backward_overflowing(start: Self, n: usize) -> (Self, bool) { + match $u_narrower::try_from(n) { + Ok(n) => start.overflowing_sub_unsigned(n), + // If n is out of range of e.g. u8, + // then it is bigger than the entire range for i8 is wide + // so `any_i8 - n` necessarily overflows i8. + Err(_) => (start.wrapping_add(n as Self), true), + } + } } )+ @@ -391,6 +481,16 @@ macro_rules! step_integer_impls { fn backward_checked(start: Self, n: usize) -> Option { start.checked_sub(n as Self) } + + #[inline] + fn forward_overflowing(start: Self, n: usize) -> (Self, bool) { + start.overflowing_add(n as Self) + } + + #[inline] + fn backward_overflowing(start: Self, n: usize) -> (Self, bool) { + start.overflowing_sub(n as Self) + } } #[allow(unreachable_patterns)] @@ -429,6 +529,16 @@ macro_rules! step_integer_impls { fn backward_checked(start: Self, n: usize) -> Option { start.checked_sub(n as Self) } + + #[inline] + fn forward_overflowing(start: Self, n: usize) -> (Self, bool) { + start.overflowing_add_unsigned(n as $u_wider) + } + + #[inline] + fn backward_overflowing(start: Self, n: usize) -> (Self, bool) { + start.overflowing_add_unsigned(n as $u_wider) + } } )+ }; @@ -490,6 +600,31 @@ macro_rules! step_nonzero_identical_methods { Self::new(start.get().saturating_sub(n as $int)).unwrap_or(Self::MIN) } + // Note: These NonZero overflowing implementations were chosen for + // code simplicity. Many alternative impls were examined, and some + // yielded marginally simpler assembly, but none resulted in the same + // loop -> arithmetic optimizations seen with the bare integers. + + #[inline] + fn forward_overflowing(start: Self, n: usize) -> (Self, bool) { + // Wrapping to Zero causes UB, so saturate to MAX instead. + if let Some(s) = Step::forward_checked(start, n) { + (s, false) + } else { + (Self::MAX, true) + } + } + + #[inline] + fn backward_overflowing(start: Self, n: usize) -> (Self, bool) { + // Subtracting to Zero causes UB, so saturate to MIN instead. + if let Some(s) = Step::backward_checked(start, n) { + (s, false) + } else { + (Self::MIN, true) + } + } + #[inline] fn steps_between(start: &Self, end: &Self) -> (usize, Option) { if *start <= *end { @@ -627,6 +762,29 @@ const impl Step for char { Some(unsafe { char::from_u32_unchecked(res) }) } + // Note: These char overflowing implementations were chosen for + // code simplicity. Alternative impls were examined, and some + // yielded marginally simpler assembly, but none resulted in the same + // loop -> arithmetic optimizations seen with the bare integers. + + #[inline] + fn forward_overflowing(start: Self, count: usize) -> (Self, bool) { + if let Some(c) = Step::forward_checked(start, count) { + (c, false) + } else { + (Self::MAX, true) + } + } + + #[inline] + fn backward_overflowing(start: Self, count: usize) -> (Self, bool) { + if let Some(c) = Step::backward_checked(start, count) { + (c, false) + } else { + (Self::MIN, true) + } + } + #[inline] unsafe fn forward_unchecked(start: char, count: usize) -> char { let start = start as u32; @@ -682,6 +840,24 @@ const impl Step for AsciiChar { Some(unsafe { AsciiChar::from_u8_unchecked(end) }) } + #[inline] + fn forward_overflowing(start: Self, count: usize) -> (Self, bool) { + let (s, o) = (start as usize).overflowing_add(count); + let ret = s & (AsciiChar::MAX as usize); + + // SAFETY: Clamped to [0, MAX], must be valid ASCII + (unsafe { AsciiChar::from_u8_unchecked(ret as u8) }, o || ret < s) + } + + #[inline] + fn backward_overflowing(start: Self, count: usize) -> (Self, bool) { + let (s, o) = (start as usize).overflowing_sub(count); + let ret = s & (AsciiChar::MAX as usize); + + // SAFETY: Clamped to [0, MAX], must be valid ASCII + (unsafe { AsciiChar::from_u8_unchecked(ret as u8) }, o || ret < s) + } + #[inline] unsafe fn forward_unchecked(start: AsciiChar, count: usize) -> AsciiChar { // SAFETY: Caller asserts that result is a valid ASCII character, @@ -721,6 +897,18 @@ const impl Step for Ipv4Addr { u32::backward_checked(start.to_bits(), count).map(Ipv4Addr::from_bits) } + #[inline] + fn forward_overflowing(start: Self, count: usize) -> (Self, bool) { + let (s, o) = u32::forward_overflowing(start.to_bits(), count); + (Ipv4Addr::from_bits(s), o) + } + + #[inline] + fn backward_overflowing(start: Self, count: usize) -> (Self, bool) { + let (s, o) = u32::backward_overflowing(start.to_bits(), count); + (Ipv4Addr::from_bits(s), o) + } + #[inline] unsafe fn forward_unchecked(start: Ipv4Addr, count: usize) -> Ipv4Addr { // SAFETY: Since u32 and Ipv4Addr are losslessly convertible, @@ -754,6 +942,18 @@ const impl Step for Ipv6Addr { u128::backward_checked(start.to_bits(), count).map(Ipv6Addr::from_bits) } + #[inline] + fn forward_overflowing(start: Self, count: usize) -> (Self, bool) { + let (s, o) = u128::forward_overflowing(start.to_bits(), count); + (Ipv6Addr::from_bits(s), o) + } + + #[inline] + fn backward_overflowing(start: Self, count: usize) -> (Self, bool) { + let (s, o) = u128::backward_overflowing(start.to_bits(), count); + (Ipv6Addr::from_bits(s), o) + } + #[inline] unsafe fn forward_unchecked(start: Ipv6Addr, count: usize) -> Ipv6Addr { // SAFETY: Since u128 and Ipv6Addr are losslessly convertible, @@ -1186,7 +1386,6 @@ trait RangeInclusiveIteratorImpl { type Item; // Iterator - fn spec_next(&mut self) -> Option; fn spec_try_fold(&mut self, init: B, f: F) -> R where Self: Sized, @@ -1194,7 +1393,6 @@ trait RangeInclusiveIteratorImpl { R: Try; // DoubleEndedIterator - fn spec_next_back(&mut self) -> Option; fn spec_try_rfold(&mut self, init: B, f: F) -> R where Self: Sized, @@ -1205,22 +1403,6 @@ trait RangeInclusiveIteratorImpl { impl RangeInclusiveIteratorImpl for ops::RangeInclusive { type Item = A; - #[inline] - default fn spec_next(&mut self) -> Option { - if self.is_empty() { - return None; - } - let is_iterating = self.start < self.end; - Some(if is_iterating { - let n = - Step::forward_checked(self.start.clone(), 1).expect("`Step` invariants not upheld"); - mem::replace(&mut self.start, n) - } else { - self.exhausted = true; - self.start.clone() - }) - } - #[inline] default fn spec_try_fold(&mut self, init: B, mut f: F) -> R where @@ -1250,22 +1432,6 @@ impl RangeInclusiveIteratorImpl for ops::RangeInclusive { try { accum } } - #[inline] - default fn spec_next_back(&mut self) -> Option { - if self.is_empty() { - return None; - } - let is_iterating = self.start < self.end; - Some(if is_iterating { - let n = - Step::backward_checked(self.end.clone(), 1).expect("`Step` invariants not upheld"); - mem::replace(&mut self.end, n) - } else { - self.exhausted = true; - self.end.clone() - }) - } - #[inline] default fn spec_try_rfold(&mut self, init: B, mut f: F) -> R where @@ -1297,22 +1463,6 @@ impl RangeInclusiveIteratorImpl for ops::RangeInclusive { } impl RangeInclusiveIteratorImpl for ops::RangeInclusive { - #[inline] - fn spec_next(&mut self) -> Option { - if self.is_empty() { - return None; - } - let is_iterating = self.start < self.end; - Some(if is_iterating { - // SAFETY: just checked precondition - let n = unsafe { Step::forward_unchecked(self.start, 1) }; - mem::replace(&mut self.start, n) - } else { - self.exhausted = true; - self.start - }) - } - #[inline] fn spec_try_fold(&mut self, init: B, mut f: F) -> R where @@ -1342,22 +1492,6 @@ impl RangeInclusiveIteratorImpl for ops::RangeInclusive { try { accum } } - #[inline] - fn spec_next_back(&mut self) -> Option { - if self.is_empty() { - return None; - } - let is_iterating = self.start < self.end; - Some(if is_iterating { - // SAFETY: just checked precondition - let n = unsafe { Step::backward_unchecked(self.end, 1) }; - mem::replace(&mut self.end, n) - } else { - self.exhausted = true; - self.end - }) - } - #[inline] fn spec_try_rfold(&mut self, init: B, mut f: F) -> R where @@ -1394,7 +1528,14 @@ impl Iterator for ops::RangeInclusive { #[inline] fn next(&mut self) -> Option { - self.spec_next() + if self.is_empty() { + return None; + } + + let (n, o) = Step::forward_overflowing(self.start.clone(), 1); + + self.exhausted = o; + Some(mem::replace(&mut self.start, n)) } #[inline] @@ -1425,26 +1566,13 @@ impl Iterator for ops::RangeInclusive { return None; } - if let Some(plus_n) = Step::forward_checked(self.start.clone(), n) { - use crate::cmp::Ordering::*; + let (plus_n, on) = Step::forward_overflowing(self.start.clone(), n); + let (plus_1, o1) = Step::forward_overflowing(plus_n.clone(), 1); - match plus_n.partial_cmp(&self.end) { - Some(Less) => { - self.start = Step::forward(plus_n.clone(), 1); - return Some(plus_n); - } - Some(Equal) => { - self.start = plus_n.clone(); - self.exhausted = true; - return Some(plus_n); - } - _ => {} - } - } + self.start = plus_1; + self.exhausted = on | o1; - self.start = self.end.clone(); - self.exhausted = true; - None + if !on && plus_n <= self.end { Some(plus_n) } else { None } } #[inline] @@ -1490,7 +1618,14 @@ impl Iterator for ops::RangeInclusive { impl DoubleEndedIterator for ops::RangeInclusive { #[inline] fn next_back(&mut self) -> Option { - self.spec_next_back() + if self.is_empty() { + return None; + } + + let (n, o) = Step::backward_overflowing(self.end.clone(), 1); + + self.exhausted = o; + Some(mem::replace(&mut self.end, n)) } #[inline] @@ -1499,26 +1634,13 @@ impl DoubleEndedIterator for ops::RangeInclusive { return None; } - if let Some(minus_n) = Step::backward_checked(self.end.clone(), n) { - use crate::cmp::Ordering::*; + let (minus_n, on) = Step::backward_overflowing(self.end.clone(), n); + let (minus_1, o1) = Step::backward_overflowing(minus_n.clone(), 1); - match minus_n.partial_cmp(&self.start) { - Some(Greater) => { - self.end = Step::backward(minus_n.clone(), 1); - return Some(minus_n); - } - Some(Equal) => { - self.end = minus_n.clone(); - self.exhausted = true; - return Some(minus_n); - } - _ => {} - } - } + self.end = minus_1; + self.exhausted = on | o1; - self.end = self.start.clone(); - self.exhausted = true; - None + if !on && minus_n >= self.start { Some(minus_n) } else { None } } #[inline] diff --git a/library/core/src/ops/range.rs b/library/core/src/ops/range.rs index 1875aca947098..ebb6c3ddb938c 100644 --- a/library/core/src/ops/range.rs +++ b/library/core/src/ops/range.rs @@ -363,10 +363,15 @@ pub struct RangeInclusive { pub(crate) start: Idx, pub(crate) end: Idx, - // This field is: + // This field represents an overflow flag for either bound (start or end): // - `false` upon construction - // - `false` when iteration has yielded an element and the iterator is not exhausted - // - `true` when iteration has been used to exhaust the iterator + // - `false` when iteration has yielded an element and + // neither bound has overflowed the valid range of `Idx` + // - `true` when iteration has caused either bound to + // overflow the valid range of `Idx` + // + // When this is true, `start` or `end` may be left in an unspecified state, + // often wrapping (modular arithmetic) around at the boundary of `Idx`. // // This is required to support PartialEq and Hash without a PartialOrd bound or specialization. pub(crate) exhausted: bool, @@ -464,6 +469,13 @@ impl RangeInclusive { /// The caller is responsible for dealing with `end == usize::MAX`. #[inline] pub(crate) const fn into_slice_range(self) -> Range { + // Typically users should not be indexing with exhausted instances, + // but this heuristic should apply to most cases. This doesn't + // handle reverse iteration well (`next_back` and `nth_back` can + // cause `end` to wrap around to values at or near `usize::MAX`), + // but using an exhausted `RangeInclusive` after reverse iteration + // is an exceedingly rare case. + // If we're not exhausted, we want to simply slice `start..end + 1`. // If we are exhausted, then slicing with `end + 1..end + 1` gives us an // empty range that is still subject to bounds-checks for that endpoint. @@ -1127,9 +1139,11 @@ const impl RangeBounds for RangeInclusive { } fn end_bound(&self) -> Bound<&T> { if self.exhausted { - // When the iterator is exhausted, we usually have start == end, + // When the iterator is exhausted, it might have overflowed, // but we want the range to appear empty, containing nothing. - Excluded(&self.end) + // So in that case, we return bounds which are always empty: + // Included(start)..Excluded(start) + Excluded(&self.start) } else { Included(&self.end) } @@ -1140,16 +1154,12 @@ const impl RangeBounds for RangeInclusive { #[rustc_const_unstable(feature = "const_range", issue = "none")] const impl IntoBounds for RangeInclusive { fn into_bounds(self) -> (Bound, Bound) { - ( - Included(self.start), - if self.exhausted { - // When the iterator is exhausted, we usually have start == end, - // but we want the range to appear empty, containing nothing. - Excluded(self.end) - } else { - Included(self.end) - }, - ) + assert!( + !self.exhausted, + "attempted to convert from an exhausted `RangeInclusive` (unspecified behavior)" + ); + + (Included(self.start), Included(self.end)) } } diff --git a/library/coretests/tests/iter/range.rs b/library/coretests/tests/iter/range.rs index 4a00e6f96bda8..c9904f08c4240 100644 --- a/library/coretests/tests/iter/range.rs +++ b/library/coretests/tests/iter/range.rs @@ -93,9 +93,26 @@ fn test_range_inclusive_exhaustion() { assert_eq!(r.next(), None); assert_eq!(r.next(), None); - assert_eq!(*r.start(), 10); + assert_eq!(*r.start(), 11); assert_eq!(*r.end(), 10); - assert_ne!(r, 10..=10); + assert_eq!(r.contains(&11), false); + assert_eq!(r.contains(&10), false); + assert_eq!(r, 11..=10); + assert_eq!(r, r); + + let mut r = 255..=255_u8; + assert_eq!(r.next(), Some(255)); + assert!(r.is_empty()); + assert_eq!(r.next(), None); + assert_eq!(r.next(), None); + + assert_eq!(*r.start(), 0); + assert_eq!(*r.end(), 255); + assert_eq!(r.contains(&0), false); + assert_eq!(r.contains(&255), false); + assert_ne!(r, 255..=255); + assert_ne!(r, 0..=255); + assert_eq!(r, r); let mut r = 10..=10; assert_eq!(r.next_back(), Some(10)); @@ -103,8 +120,23 @@ fn test_range_inclusive_exhaustion() { assert_eq!(r.next_back(), None); assert_eq!(*r.start(), 10); - assert_eq!(*r.end(), 10); - assert_ne!(r, 10..=10); + assert_eq!(*r.end(), 9); + assert_eq!(r.contains(&10), false); + assert_eq!(r.contains(&9), false); + assert_eq!(r, 10..=9); + + let mut r = 0..=0_u8; + assert_eq!(r.next_back(), Some(0)); + assert!(r.is_empty()); + assert_eq!(r.next_back(), None); + + assert_eq!(*r.start(), 0); + assert_eq!(*r.end(), 255); + assert_eq!(r.contains(&0), false); + assert_eq!(r.contains(&255), false); + assert_ne!(r, 0..=0); + assert_ne!(r, 0..=255); + assert_eq!(r, r); let mut r = 10..=12; assert_eq!(r.next(), Some(10)); @@ -221,9 +253,6 @@ fn test_range_inclusive_nth() { assert_eq!((10..=15).nth(5), Some(15)); assert_eq!((10..=15).nth(6), None); - let mut exhausted_via_next = 10_u8..=20; - while exhausted_via_next.next().is_some() {} - let mut r = 10_u8..=20; assert_eq!(r.nth(2), Some(12)); assert_eq!(r, 13..=20); @@ -233,7 +262,11 @@ fn test_range_inclusive_nth() { assert_eq!(ExactSizeIterator::is_empty(&r), false); assert_eq!(r.nth(10), None); assert_eq!(r.is_empty(), true); - assert_eq!(r, exhausted_via_next); + assert_eq!(*r.start(), 27); + assert_eq!(*r.end(), 20); + assert_eq!(r.contains(&27), false); + assert_eq!(r.contains(&20), false); + assert_eq!(r, r); assert_eq!(ExactSizeIterator::is_empty(&r), true); } @@ -245,9 +278,6 @@ fn test_range_inclusive_nth_back() { assert_eq!((10..=15).nth_back(6), None); assert_eq!((-120..=80_i8).nth_back(200), Some(-120)); - let mut exhausted_via_next_back = 10_u8..=20; - while exhausted_via_next_back.next_back().is_some() {} - let mut r = 10_u8..=20; assert_eq!(r.nth_back(2), Some(18)); assert_eq!(r, 10..=17); @@ -257,7 +287,11 @@ fn test_range_inclusive_nth_back() { assert_eq!(ExactSizeIterator::is_empty(&r), false); assert_eq!(r.nth_back(10), None); assert_eq!(r.is_empty(), true); - assert_eq!(r, exhausted_via_next_back); + assert_eq!(*r.start(), 10); + assert_eq!(*r.end(), 3); + assert_eq!(r.contains(&10), false); + assert_eq!(r.contains(&3), false); + assert_eq!(r, r); assert_eq!(ExactSizeIterator::is_empty(&r), true); } diff --git a/library/coretests/tests/ops.rs b/library/coretests/tests/ops.rs index 121718f2167e2..1cbc625959886 100644 --- a/library/coretests/tests/ops.rs +++ b/library/coretests/tests/ops.rs @@ -305,7 +305,12 @@ fn test_fmt() { let mut r = 1..=1; assert_eq!(format!("{:?}", r), "1..=1"); r.next().unwrap(); - assert_eq!(format!("{:?}", r), "1..=1 (exhausted)"); + assert_eq!(format!("{:?}", r), "2..=1"); + + let mut r = 255_u8..=255; + assert_eq!(format!("{:?}", r), "255..=255"); + r.next().unwrap(); + assert_eq!(format!("{:?}", r), "0..=255 (exhausted)"); assert_eq!(format!("{:?}", 1..1), "1..1"); assert_eq!(format!("{:?}", 1..), "1.."); diff --git a/tests/codegen-llvm/range-iter-loop-opts.rs b/tests/codegen-llvm/range-iter-loop-opts.rs index 528da65b87616..c51c7ad42709a 100644 --- a/tests/codegen-llvm/range-iter-loop-opts.rs +++ b/tests/codegen-llvm/range-iter-loop-opts.rs @@ -5,8 +5,12 @@ #![crate_type = "lib"] +use std::num::NonZeroU8; use std::ops::{Range, RangeInclusive}; +// CHECK-LABEL: @rangeinclusive_noop_loop = unnamed_addr alias void (), ptr @range_noop_loop +// CHECK-LABEL: @rangeinclusive_nz_noop_loop = unnamed_addr alias void (), ptr @range_noop_loop + // CHECK-LABEL: @range_noop_loop( #[no_mangle] pub unsafe fn range_noop_loop() { @@ -23,7 +27,7 @@ pub unsafe fn range_noop_loop() { #[no_mangle] pub unsafe fn range_count(s: u8, e: u8) -> usize { // CHECK-NOT: br {{.*}} - // CHECK: ret i64 + // CHECK: ret i{{8|16|32|64}} // This loop should be optimized to arithmetic. let mut count = 0; @@ -33,14 +37,10 @@ pub unsafe fn range_count(s: u8, e: u8) -> usize { count } -// RangeInclusive currently cannot optimize the same way. - -// CHECK-LABEL: @rangeinclusive_noop_loop( +// Deduplicated to alias of range_noop_loop, checked above #[no_mangle] pub unsafe fn rangeinclusive_noop_loop() { - // CHECK: br {{.*}} - // CHECK: ret void - + // This loop should be optimized out entirely. for _ in 0_u8..=100 { () } @@ -49,9 +49,35 @@ pub unsafe fn rangeinclusive_noop_loop() { // CHECK-LABEL: @rangeinclusive_count( #[no_mangle] pub unsafe fn rangeinclusive_count(s: u8, e: u8) -> usize { + // CHECK-NOT: br {{.*}} + // CHECK: ret i{{8|16|32|64}} + + // This loop should be optimized to arithmetic. + let mut count = 0; + for _ in s..=e { + count += 1; + } + count +} + +// Deduplicated to alias of range_noop_loop, checked above +#[no_mangle] +pub unsafe fn rangeinclusive_nz_noop_loop() { + // This loop should be optimized out entirely. + for _ in NonZeroU8::new(1).unwrap()..=NonZeroU8::new(100).unwrap() { + () + } +} + +// CHECK-LABEL: @rangeinclusive_nz_count( +#[no_mangle] +pub unsafe fn rangeinclusive_nz_count(s: NonZeroU8, e: NonZeroU8) -> usize { // CHECK: br {{.*}} - // CHECK: ret i64 + // CHECK: ret i{{8|16|32|64}} + // RangeInclusive cannot optimize the same way + // because Step::forward_overflowing on NonZero cannot + // be allowed to wrap to 0. let mut count = 0; for _ in s..=e { count += 1; diff --git a/tests/mir-opt/pre-codegen/range_iter.inclusive_loop.runtime-optimized.after.panic-abort.mir b/tests/mir-opt/pre-codegen/range_iter.inclusive_loop.runtime-optimized.after.panic-abort.mir index 5e1291f71663d..8e89ffefe8ee7 100644 --- a/tests/mir-opt/pre-codegen/range_iter.inclusive_loop.runtime-optimized.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/range_iter.inclusive_loop.runtime-optimized.after.panic-abort.mir @@ -19,8 +19,6 @@ fn inclusive_loop(_1: u32, _2: u32, _3: impl Fn(u32)) -> () { scope 2 { debug x => _9; } - scope 5 (inlined iter::range::>::next) { - } } scope 3 (inlined std::ops::RangeInclusive::::new) { } @@ -38,7 +36,7 @@ fn inclusive_loop(_1: u32, _2: u32, _3: impl Fn(u32)) -> () { StorageLive(_7); StorageLive(_6); _6 = &mut _5; - _7 = as iter::range::RangeInclusiveIteratorImpl>::spec_next(move _6) -> [return: bb2, unwind unreachable]; + _7 = as Iterator>::next(move _6) -> [return: bb2, unwind unreachable]; } bb2: { diff --git a/tests/mir-opt/pre-codegen/range_iter.inclusive_loop.runtime-optimized.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/range_iter.inclusive_loop.runtime-optimized.after.panic-unwind.mir index 2e82343e44431..084708b53c933 100644 --- a/tests/mir-opt/pre-codegen/range_iter.inclusive_loop.runtime-optimized.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/range_iter.inclusive_loop.runtime-optimized.after.panic-unwind.mir @@ -19,8 +19,6 @@ fn inclusive_loop(_1: u32, _2: u32, _3: impl Fn(u32)) -> () { scope 2 { debug x => _9; } - scope 5 (inlined iter::range::>::next) { - } } scope 3 (inlined std::ops::RangeInclusive::::new) { } @@ -38,7 +36,7 @@ fn inclusive_loop(_1: u32, _2: u32, _3: impl Fn(u32)) -> () { StorageLive(_7); StorageLive(_6); _6 = &mut _5; - _7 = as iter::range::RangeInclusiveIteratorImpl>::spec_next(move _6) -> [return: bb2, unwind: bb8]; + _7 = as Iterator>::next(move _6) -> [return: bb2, unwind: bb8]; } bb2: { diff --git a/tests/mir-opt/pre-codegen/range_iter.range_inclusive_iter_next.runtime-optimized.after.panic-abort.mir b/tests/mir-opt/pre-codegen/range_iter.range_inclusive_iter_next.runtime-optimized.after.panic-abort.mir index aaf54ccf7e9f7..361c4ab8ca156 100644 --- a/tests/mir-opt/pre-codegen/range_iter.range_inclusive_iter_next.runtime-optimized.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/range_iter.range_inclusive_iter_next.runtime-optimized.after.panic-abort.mir @@ -3,11 +3,9 @@ fn range_inclusive_iter_next(_1: &mut std::ops::RangeInclusive) -> Option { debug it => _1; let mut _0: std::option::Option; - scope 1 (inlined iter::range::>::next) { - } bb0: { - _0 = as iter::range::RangeInclusiveIteratorImpl>::spec_next(move _1) -> [return: bb1, unwind unreachable]; + _0 = as Iterator>::next(move _1) -> [return: bb1, unwind unreachable]; } bb1: { diff --git a/tests/mir-opt/pre-codegen/range_iter.range_inclusive_iter_next.runtime-optimized.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/range_iter.range_inclusive_iter_next.runtime-optimized.after.panic-unwind.mir index 127912b37da63..fc788dba68952 100644 --- a/tests/mir-opt/pre-codegen/range_iter.range_inclusive_iter_next.runtime-optimized.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/range_iter.range_inclusive_iter_next.runtime-optimized.after.panic-unwind.mir @@ -3,11 +3,9 @@ fn range_inclusive_iter_next(_1: &mut std::ops::RangeInclusive) -> Option { debug it => _1; let mut _0: std::option::Option; - scope 1 (inlined iter::range::>::next) { - } bb0: { - _0 = as iter::range::RangeInclusiveIteratorImpl>::spec_next(move _1) -> [return: bb1, unwind continue]; + _0 = as Iterator>::next(move _1) -> [return: bb1, unwind continue]; } bb1: { diff --git a/tests/ui/impl-trait/example-calendar.rs b/tests/ui/impl-trait/example-calendar.rs index 972ee5f4b63b4..a385cd9def1ab 100644 --- a/tests/ui/impl-trait/example-calendar.rs +++ b/tests/ui/impl-trait/example-calendar.rs @@ -163,9 +163,17 @@ impl std::iter::Step for NaiveDate { Some((0..n).fold(start, |x, _| x.succ())) } + fn forward_overflowing(start: Self, n: usize) -> (Self, bool) { + (Self::forward_checked(start, n).unwrap(), false) + } + fn backward_checked(_: Self, _: usize) -> Option { unimplemented!() } + + fn backward_overflowing(_: Self, _: usize) -> (Self, bool) { + unimplemented!() + } } #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] From 2270de748cd101f817037d6a768b7bf14e582bd1 Mon Sep 17 00:00:00 2001 From: SangHun Kim Date: Mon, 22 Jun 2026 18:06:28 +0900 Subject: [PATCH 3/4] Include AtomicU128/AtomicI128 in docs for any target --- library/core/src/sync/atomic.rs | 180 +++++++++++++++++++------------- 1 file changed, 109 insertions(+), 71 deletions(-) diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index e8865823deb98..5530aebe8789d 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs @@ -269,7 +269,7 @@ mod private { #[cfg(target_has_atomic_load_store = "64")] #[repr(C, align(8))] pub struct Align8(T); - #[cfg(target_has_atomic_load_store = "128")] + #[cfg(any(target_has_atomic_load_store = "128", doc))] #[repr(C, align(16))] pub struct Align16(T); } @@ -302,7 +302,7 @@ macro impl_atomic_primitive( reason = "implementation detail which may disappear or be replaced at any time", issue = "none" )] - #[cfg(target_has_atomic_load_store = $size)] + #[cfg(any(target_has_atomic_load_store = $size, doc))] unsafe impl $(<$T>)? AtomicPrimitive for $Primitive { type Storage = private::$Storage<$Operand>; } @@ -2535,7 +2535,8 @@ macro_rules! if_8_bit { #[cfg(target_has_atomic_load_store)] macro_rules! atomic_int { - ($cfg_cas:meta, + ($cfg_base:meta, + $cfg_cas:meta, $cfg_align:meta, $stable:meta, $stable_cxchg:meta, @@ -2611,7 +2612,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_base, doc = "```")] + #[cfg_attr(not($cfg_base), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::", stringify!($atomic_type), ";")] /// #[doc = concat!("let atomic_forty_two = ", stringify!($atomic_type), "::new(42);")] @@ -2630,7 +2632,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_base, doc = "```rust")] + #[cfg_attr(not($cfg_base), doc = "```rust,compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{self, ", stringify!($atomic_type), "};")] /// /// // Get a pointer to an allocated value @@ -2692,7 +2695,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_base, doc = "```")] + #[cfg_attr(not($cfg_base), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let mut some_var = ", stringify!($atomic_type), "::new(10);")] @@ -2720,7 +2724,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_align, doc = "```rust")] + #[cfg_attr(not($cfg_align), doc = "```rust,compile_fail")] /// #![feature(atomic_from_mut)] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// @@ -2731,7 +2736,7 @@ macro_rules! atomic_int { /// ``` /// #[inline] - #[$cfg_align] + #[cfg(any($cfg_align, doc))] #[unstable(feature = "atomic_from_mut", issue = "76314")] pub fn from_mut(v: &mut $int_type) -> &mut Self { let [] = [(); align_of::() - align_of::<$int_type>()]; @@ -2749,7 +2754,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ```ignore-wasm + #[cfg_attr($cfg_base, doc = "```ignore-wasm")] + #[cfg_attr(not($cfg_base), doc = "```ignore-wasm,compile_fail")] /// #![feature(atomic_from_mut)] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// @@ -2790,7 +2796,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ```ignore-wasm + #[cfg_attr($cfg_align, doc = "```ignore-wasm")] + #[cfg_attr(not($cfg_align), doc = "```ignore-wasm,compile_fail")] /// #![feature(atomic_from_mut)] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// @@ -2806,7 +2813,7 @@ macro_rules! atomic_int { /// } /// ``` #[inline] - #[$cfg_align] + #[cfg(any($cfg_align, doc))] #[unstable(feature = "atomic_from_mut", issue = "76314")] pub fn from_mut_slice(v: &mut [$int_type]) -> &mut [Self] { let [] = [(); align_of::() - align_of::<$int_type>()]; @@ -2824,7 +2831,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_base, doc = "```")] + #[cfg_attr(not($cfg_base), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::", stringify!($atomic_type), ";")] /// #[doc = concat!("let some_var = ", stringify!($atomic_type), "::new(5);")] @@ -2850,7 +2858,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_base, doc = "```")] + #[cfg_attr(not($cfg_base), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let some_var = ", stringify!($atomic_type), "::new(5);")] @@ -2876,7 +2885,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_base, doc = "```")] + #[cfg_attr(not($cfg_base), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let some_var = ", stringify!($atomic_type), "::new(5);")] @@ -2905,7 +2915,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let some_var = ", stringify!($atomic_type), "::new(5);")] @@ -2914,7 +2925,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[$stable] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn swap(&self, val: $int_type, order: Ordering) -> $int_type { @@ -2962,7 +2973,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let some_var = ", stringify!($atomic_type), "::new(5);")] @@ -2979,7 +2991,7 @@ macro_rules! atomic_int { since = "1.50.0", note = "Use `compare_exchange` or `compare_exchange_weak` instead") ] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn compare_and_swap(&self, @@ -3015,7 +3027,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let some_var = ", stringify!($atomic_type), "::new(5);")] @@ -3048,7 +3061,7 @@ macro_rules! atomic_int { /// [compare-and-swap operation]: https://en.wikipedia.org/wiki/Compare-and-swap #[inline] #[$stable_cxchg] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn compare_exchange(&self, @@ -3082,7 +3095,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let val = ", stringify!($atomic_type), "::new(4);")] @@ -3112,7 +3126,7 @@ macro_rules! atomic_int { /// [compare-and-swap operation]: https://en.wikipedia.org/wiki/Compare-and-swap #[inline] #[$stable_cxchg] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn compare_exchange_weak(&self, @@ -3140,7 +3154,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let foo = ", stringify!($atomic_type), "::new(0);")] @@ -3149,7 +3164,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[$stable] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn fetch_add(&self, val: $int_type, order: Ordering) -> $int_type { @@ -3171,7 +3186,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let foo = ", stringify!($atomic_type), "::new(20);")] @@ -3180,7 +3196,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[$stable] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn fetch_sub(&self, val: $int_type, order: Ordering) -> $int_type { @@ -3205,7 +3221,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let foo = ", stringify!($atomic_type), "::new(0b101101);")] @@ -3214,7 +3231,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[$stable] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn fetch_and(&self, val: $int_type, order: Ordering) -> $int_type { @@ -3239,7 +3256,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let foo = ", stringify!($atomic_type), "::new(0x13);")] @@ -3248,7 +3266,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[$stable_nand] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn fetch_nand(&self, val: $int_type, order: Ordering) -> $int_type { @@ -3273,7 +3291,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let foo = ", stringify!($atomic_type), "::new(0b101101);")] @@ -3282,7 +3301,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[$stable] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn fetch_or(&self, val: $int_type, order: Ordering) -> $int_type { @@ -3307,7 +3326,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let foo = ", stringify!($atomic_type), "::new(0b101101);")] @@ -3316,7 +3336,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[$stable] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn fetch_xor(&self, val: $int_type, order: Ordering) -> $int_type { @@ -3329,7 +3349,7 @@ macro_rules! atomic_int { /// . #[inline] #[stable(feature = "no_more_cas", since = "1.45.0")] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] #[deprecated( @@ -3383,7 +3403,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ```rust + #[cfg_attr($cfg_cas, doc = "```rust")] + #[cfg_attr(not($cfg_cas), doc = "```rust,compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let x = ", stringify!($atomic_type), "::new(7);")] @@ -3394,7 +3415,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[stable(feature = "atomic_try_update", since = "1.95.0")] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn try_update( @@ -3450,7 +3471,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ```rust + #[cfg_attr($cfg_cas, doc = "```rust")] + #[cfg_attr(not($cfg_cas), doc = "```rust,compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let x = ", stringify!($atomic_type), "::new(7);")] @@ -3460,7 +3482,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[stable(feature = "atomic_try_update", since = "1.95.0")] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn update( @@ -3495,7 +3517,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let foo = ", stringify!($atomic_type), "::new(23);")] @@ -3505,7 +3528,8 @@ macro_rules! atomic_int { /// /// If you want to obtain the maximum value in one step, you can use the following: /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let foo = ", stringify!($atomic_type), "::new(23);")] @@ -3515,7 +3539,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[stable(feature = "atomic_min_max", since = "1.45.0")] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn fetch_max(&self, val: $int_type, order: Ordering) -> $int_type { @@ -3540,7 +3564,8 @@ macro_rules! atomic_int { /// /// # Examples /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let foo = ", stringify!($atomic_type), "::new(23);")] @@ -3552,7 +3577,8 @@ macro_rules! atomic_int { /// /// If you want to obtain the minimum value in one step, you can use the following: /// - /// ``` + #[cfg_attr($cfg_cas, doc = "```")] + #[cfg_attr(not($cfg_cas), doc = "```compile_fail")] #[doc = concat!($extra_feature, "use std::sync::atomic::{", stringify!($atomic_type), ", Ordering};")] /// #[doc = concat!("let foo = ", stringify!($atomic_type), "::new(23);")] @@ -3562,7 +3588,7 @@ macro_rules! atomic_int { /// ``` #[inline] #[stable(feature = "atomic_min_max", since = "1.45.0")] - #[$cfg_cas] + #[cfg(any($cfg_cas, doc))] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces #[rustc_should_not_be_called_on_const_items] pub fn fetch_min(&self, val: $int_type, order: Ordering) -> $int_type { @@ -3615,8 +3641,9 @@ macro_rules! atomic_int { #[cfg(target_has_atomic_load_store = "8")] atomic_int! { - cfg(target_has_atomic = "8"), - cfg(target_has_atomic_primitive_alignment = "8"), + target_has_atomic_load_store = "8", + target_has_atomic = "8", + target_has_atomic_primitive_alignment = "8", stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), @@ -3633,8 +3660,9 @@ atomic_int! { } #[cfg(target_has_atomic_load_store = "8")] atomic_int! { - cfg(target_has_atomic = "8"), - cfg(target_has_atomic_primitive_alignment = "8"), + target_has_atomic_load_store = "8", + target_has_atomic = "8", + target_has_atomic_primitive_alignment = "8", stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), @@ -3651,8 +3679,9 @@ atomic_int! { } #[cfg(target_has_atomic_load_store = "16")] atomic_int! { - cfg(target_has_atomic = "16"), - cfg(target_has_atomic_primitive_alignment = "16"), + target_has_atomic_load_store = "16", + target_has_atomic = "16", + target_has_atomic_primitive_alignment = "16", stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), @@ -3669,8 +3698,9 @@ atomic_int! { } #[cfg(target_has_atomic_load_store = "16")] atomic_int! { - cfg(target_has_atomic = "16"), - cfg(target_has_atomic_primitive_alignment = "16"), + target_has_atomic_load_store = "16", + target_has_atomic = "16", + target_has_atomic_primitive_alignment = "16", stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), @@ -3687,8 +3717,9 @@ atomic_int! { } #[cfg(target_has_atomic_load_store = "32")] atomic_int! { - cfg(target_has_atomic = "32"), - cfg(target_has_atomic_primitive_alignment = "32"), + target_has_atomic_load_store = "32", + target_has_atomic = "32", + target_has_atomic_primitive_alignment = "32", stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), @@ -3705,8 +3736,9 @@ atomic_int! { } #[cfg(target_has_atomic_load_store = "32")] atomic_int! { - cfg(target_has_atomic = "32"), - cfg(target_has_atomic_primitive_alignment = "32"), + target_has_atomic_load_store = "32", + target_has_atomic = "32", + target_has_atomic_primitive_alignment = "32", stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), @@ -3723,8 +3755,9 @@ atomic_int! { } #[cfg(target_has_atomic_load_store = "64")] atomic_int! { - cfg(target_has_atomic = "64"), - cfg(target_has_atomic_primitive_alignment = "64"), + target_has_atomic_load_store = "64", + target_has_atomic = "64", + target_has_atomic_primitive_alignment = "64", stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), @@ -3741,8 +3774,9 @@ atomic_int! { } #[cfg(target_has_atomic_load_store = "64")] atomic_int! { - cfg(target_has_atomic = "64"), - cfg(target_has_atomic_primitive_alignment = "64"), + target_has_atomic_load_store = "64", + target_has_atomic = "64", + target_has_atomic_primitive_alignment = "64", stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), @@ -3757,10 +3791,11 @@ atomic_int! { 8, u64 AtomicU64 } -#[cfg(target_has_atomic_load_store = "128")] +#[cfg(any(target_has_atomic_load_store = "128", doc))] atomic_int! { - cfg(target_has_atomic = "128"), - cfg(target_has_atomic_primitive_alignment = "128"), + target_has_atomic_load_store = "128", + target_has_atomic = "128", + target_has_atomic_primitive_alignment = "128", unstable(feature = "integer_atomics", issue = "99069"), unstable(feature = "integer_atomics", issue = "99069"), unstable(feature = "integer_atomics", issue = "99069"), @@ -3775,10 +3810,11 @@ atomic_int! { 16, i128 AtomicI128 } -#[cfg(target_has_atomic_load_store = "128")] +#[cfg(any(target_has_atomic_load_store = "128", doc))] atomic_int! { - cfg(target_has_atomic = "128"), - cfg(target_has_atomic_primitive_alignment = "128"), + target_has_atomic_load_store = "128", + target_has_atomic = "128", + target_has_atomic_primitive_alignment = "128", unstable(feature = "integer_atomics", issue = "99069"), unstable(feature = "integer_atomics", issue = "99069"), unstable(feature = "integer_atomics", issue = "99069"), @@ -3799,8 +3835,9 @@ macro_rules! atomic_int_ptr_sized { ( $($target_pointer_width:literal $align:literal)* ) => { $( #[cfg(target_pointer_width = $target_pointer_width)] atomic_int! { - cfg(target_has_atomic = "ptr"), - cfg(target_has_atomic_primitive_alignment = "ptr"), + target_has_atomic_load_store = "ptr", + target_has_atomic = "ptr", + target_has_atomic_primitive_alignment = "ptr", stable(feature = "rust1", since = "1.0.0"), stable(feature = "extended_compare_and_swap", since = "1.10.0"), stable(feature = "atomic_debug", since = "1.3.0"), @@ -3817,8 +3854,9 @@ macro_rules! atomic_int_ptr_sized { } #[cfg(target_pointer_width = $target_pointer_width)] atomic_int! { - cfg(target_has_atomic = "ptr"), - cfg(target_has_atomic_primitive_alignment = "ptr"), + target_has_atomic_load_store = "ptr", + target_has_atomic = "ptr", + target_has_atomic_primitive_alignment = "ptr", stable(feature = "rust1", since = "1.0.0"), stable(feature = "extended_compare_and_swap", since = "1.10.0"), stable(feature = "atomic_debug", since = "1.3.0"), From 5a417e253530581d9cb82d4e9bef76008044b7dd Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Sun, 28 Jun 2026 21:19:35 +0000 Subject: [PATCH 4/4] Implement `Metadata:from_statx` for Linux `MetadataExt` trait * Implement Metadata:from_statx for Linux MetadataExt trait * Added STATX_MASK constant and included documentation for it * Refactored shared code from try_statx and Metadata::from_statx into a function with FileAttr::from_statx --- library/std/src/os/linux/fs.rs | 72 ++++++++++++++++++++++++++ library/std/src/sys/fs/mod.rs | 2 + library/std/src/sys/fs/unix.rs | 94 +++++++++++++++++++++------------- 3 files changed, 133 insertions(+), 35 deletions(-) diff --git a/library/std/src/os/linux/fs.rs b/library/std/src/os/linux/fs.rs index e52a63bc798ea..00c57794abb69 100644 --- a/library/std/src/os/linux/fs.rs +++ b/library/std/src/os/linux/fs.rs @@ -7,7 +7,24 @@ use crate::fs::Metadata; #[allow(deprecated)] use crate::os::linux::raw; +use crate::os::raw::{c_uint, c_void}; use crate::sys::AsInner; +use crate::sys::fs::cfg_has_statx; +cfg_has_statx! {{ + use crate::sys::fs::FileAttr; + use crate::sys::FromInner; +} else { + use crate::sys::unsupported; +}} + +/// This is the [`statx`] mask expected by [`Metadata::from_statx`], which sets both +/// `STATX_BASIC_STATS` and `STATX_BTIME`. See the [Linux man page] for statx for more +/// details. +/// +/// [`statx`]: https://docs.rs/libc/latest/libc/struct.statx.html +/// [Linux man page]: https://man7.org/linux/man-pages/man2/statx.2.html +#[unstable(feature = "metadata_statx", issue = "156268")] +pub const STATX_MASK: c_uint = libc::STATX_BASIC_STATS | libc::STATX_BTIME; /// OS-specific extensions to [`fs::Metadata`]. /// @@ -41,6 +58,53 @@ pub trait MetadataExt { #[allow(deprecated)] fn as_raw_stat(&self) -> &raw::stat; + /// Creates a [`Metadata`] from a const void pointer populated by the [`statx`] syscall. + /// + /// Currently [`Metadata::from_statx`] is only supported on Linux platforms with a target + /// environment of GNU. + /// + /// # Safety + /// + /// The caller must take care to provide a valid const void pointer containing information + /// populated by the [`statx`] syscall. In particular, the provided pointer should contain + /// statx information pertaining to the mask [`STATX_MASK`], so that there will be no + /// uninitialized data encountered in constructing [`Metadata`]. + /// + /// Note that the relevant information is copied out of the structure and the pointer is + /// not retained past the call. + /// + /// [`Metadata`]: crate::fs::Metadata + /// [`statx`]: https://docs.rs/libc/latest/libc/struct.statx.html + /// + /// ```no_run + /// #![feature(metadata_statx)] + /// use libc::statx; + /// use std::ffi::c_void; + /// use std::fs::{write, Metadata}; + /// use std::io; + /// use std::os::linux::fs::{MetadataExt, STATX_MASK}; + /// + /// fn main() -> io::Result<()> { + /// write("hello.txt", "Hello World!")?; + /// let mut buf = Box::::new_uninit(); + /// unsafe { + /// libc::statx( + /// libc::AT_FDCWD, + /// "hello.txt".as_ptr().cast(), + /// libc::AT_STATX_SYNC_AS_STAT, + /// STATX_MASK, + /// buf.as_mut_ptr().cast() + /// ); + /// } + /// let statxbuf: Box = unsafe { buf.assume_init() }; + /// let metadata = unsafe { Metadata::from_statx(&*statxbuf as *const statx as *const c_void) }; + /// assert_eq!(metadata.len(), 12); // "Hello World!" is 12 bytes + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "metadata_statx", issue = "156268")] + unsafe fn from_statx(statxbuf: *const c_void) -> Self; + /// Returns the device ID on which this file resides. /// /// # Examples @@ -337,6 +401,14 @@ impl MetadataExt for Metadata { &*(self.as_inner().as_inner() as *const libc::stat64 as *const raw::stat) } } + cfg_has_statx! {{ + unsafe fn from_statx(statxbuf: *const c_void) -> Metadata { + Metadata::from_inner(FileAttr::from_statx(*(statxbuf as *const libc::statx))) + }} else { + unsafe fn from_statx(statxbuf: *const c_void) -> Self { + unsupported(); + } + }} fn st_dev(&self) -> u64 { self.as_inner().as_inner().st_dev as u64 } diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 0c297c5766b82..789d40299bcaa 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -18,6 +18,8 @@ cfg_select! { #[cfg(any(target_os = "linux", target_os = "android"))] pub(super) use unix::CachedFileMetadata; use crate::sys::helpers::run_path_with_cstr as with_native_path; + #[cfg(all(target_os = "linux", target_env = "gnu"))] + pub(crate) use unix::cfg_has_statx; } target_os = "windows" => { mod windows; diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 3152a22534f6c..2fd6203b7021d 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -125,6 +125,9 @@ macro_rules! cfg_has_statx { }; } +#[cfg(all(target_os = "linux", target_env = "gnu"))] +pub(crate) use cfg_has_statx; + cfg_has_statx! {{ #[derive(Clone)] pub struct FileAttr { @@ -133,7 +136,7 @@ cfg_has_statx! {{ } #[derive(Clone)] - struct StatxExtraFields { + pub struct StatxExtraFields { // This is needed to check if btime is supported by the filesystem. stx_mask: u32, stx_btime: libc::statx_timestamp, @@ -212,40 +215,7 @@ cfg_has_statx! {{ STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed); } - // We cannot fill `stat64` exhaustively because of private padding fields. - let mut stat: stat64 = mem::zeroed(); - // `c_ulong` on gnu-mips, `dev_t` otherwise - stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _; - stat.st_ino = buf.stx_ino as libc::ino64_t; - stat.st_nlink = buf.stx_nlink as libc::nlink_t; - stat.st_mode = buf.stx_mode as libc::mode_t; - stat.st_uid = buf.stx_uid as libc::uid_t; - stat.st_gid = buf.stx_gid as libc::gid_t; - stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _; - stat.st_size = buf.stx_size as off64_t; - stat.st_blksize = buf.stx_blksize as libc::blksize_t; - stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t; - stat.st_atime = buf.stx_atime.tv_sec as libc::time_t; - // `i64` on gnu-x86_64-x32, `c_ulong` otherwise. - stat.st_atime_nsec = buf.stx_atime.tv_nsec as _; - stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t; - stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _; - stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t; - stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _; - - let extra = StatxExtraFields { - stx_mask: buf.stx_mask, - stx_btime: buf.stx_btime, - // Store full times to avoid 32-bit `time_t` truncation. - #[cfg(target_pointer_width = "32")] - stx_atime: buf.stx_atime, - #[cfg(target_pointer_width = "32")] - stx_ctime: buf.stx_ctime, - #[cfg(target_pointer_width = "32")] - stx_mtime: buf.stx_mtime, - }; - - Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) })) + Some(Ok(FileAttr::from_statx(buf))) } } else { @@ -525,11 +495,65 @@ pub struct DirBuilder { struct Mode(mode_t); cfg_has_statx! {{ + impl StatxExtraFields { + /// Constructs `StatxExtraFields` from a given `libc::statx` struct + /// + /// SAFETY: + /// The caller must take care to provide a `libc::statx` buffer that is + /// populated by the statx syscall with the flags `STATX_BASIC_STATS` + /// and `STATX_BTIME` enabled + pub unsafe fn from_statx(buf: libc::statx) -> Self { + StatxExtraFields { + stx_mask: buf.stx_mask, + stx_btime: buf.stx_btime, + // Store full times to avoid 32-bit `time_t` truncation. + #[cfg(target_pointer_width = "32")] + stx_atime: buf.stx_atime, + #[cfg(target_pointer_width = "32")] + stx_ctime: buf.stx_ctime, + #[cfg(target_pointer_width = "32")] + stx_mtime: buf.stx_mtime, + } + } + } impl FileAttr { fn from_stat64(stat: stat64) -> Self { Self { stat, statx_extra_fields: None } } + /// Constructs `FileAttr` from a given `libc::statx` struct + /// + /// SAFETY: + /// The caller must take care to provide a `libc::statx` buffer that is + /// populated by the statx syscall with the flags `STATX_BASIC_STATS` + /// and `STATX_BTIME` enabled + pub unsafe fn from_statx(buf: libc::statx) -> Self { + // We cannot fill `stat64` exhaustively because of private padding fields. + let mut stat: libc::stat64 = mem::zeroed(); + // `c_ulong` on gnu-mips, `dev_t` otherwise + stat.st_dev = libc::makedev((buf).stx_dev_major, (buf).stx_dev_minor) as _; + stat.st_ino = buf.stx_ino as libc::ino64_t; + stat.st_nlink = buf.stx_nlink as libc::nlink_t; + stat.st_mode = buf.stx_mode as libc::mode_t; + stat.st_uid = buf.stx_uid as libc::uid_t; + stat.st_gid = buf.stx_gid as libc::gid_t; + stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _; + stat.st_size = buf.stx_size as libc::off64_t; + stat.st_blksize = buf.stx_blksize as libc::blksize_t; + stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t; + stat.st_atime = buf.stx_atime.tv_sec as libc::time_t; + // `i64` on gnu-x86_64-x32, `c_ulong` otherwise. + stat.st_atime_nsec = buf.stx_atime.tv_nsec as _; + stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t; + stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _; + stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t; + stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _; + + let statx_extra_fields = Some(StatxExtraFields::from_statx(buf)); + + Self {stat, statx_extra_fields} + } + #[cfg(target_pointer_width = "32")] pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> { if let Some(ext) = &self.statx_extra_fields {