From ce850bf8733cf65f9c9c3fb9208edc113fa97e3d Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Wed, 29 Oct 2025 12:50:47 +0000 Subject: [PATCH 1/8] array_chunks: slightly improve docs --- library/core/src/array/mod.rs | 7 ++++--- library/core/src/iter/adapters/array_chunks.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/library/core/src/array/mod.rs b/library/core/src/array/mod.rs index 0dc10758a8560..312c8da107475 100644 --- a/library/core/src/array/mod.rs +++ b/library/core/src/array/mod.rs @@ -907,7 +907,7 @@ where /// All write accesses to this structure are unsafe and must maintain a correct /// count of `initialized` elements. /// -/// To minimize indirection fields are still pub but callers should at least use +/// To minimize indirection, fields are still pub but callers should at least use /// `push_unchecked` to signal that something unsafe is going on. struct Guard<'a, T> { /// The array to be initialized. @@ -925,7 +925,7 @@ impl Guard<'_, T> { #[inline] pub(crate) unsafe fn push_unchecked(&mut self, item: T) { // SAFETY: If `initialized` was correct before and the caller does not - // invoke this method more than N times then writes will be in-bounds + // invoke this method more than N times, then writes will be in-bounds // and slots will not be initialized more than once. unsafe { self.array_mut.get_unchecked_mut(self.initialized).write(item); @@ -954,7 +954,7 @@ impl Drop for Guard<'_, T> { /// `next` at most `N` times, the iterator can still be used afterwards to /// retrieve the remaining items. /// -/// If `iter.next()` panicks, all items already yielded by the iterator are +/// If `iter.next()` panics, all items already yielded by the iterator are /// dropped. /// /// Used for [`Iterator::next_chunk`]. @@ -986,6 +986,7 @@ fn iter_next_chunk_erased( buffer: &mut [MaybeUninit], iter: &mut impl Iterator, ) -> Result<(), usize> { + // if `Iterator::next` panics, this guard will drop already initialized items let mut guard = Guard { array_mut: buffer, initialized: 0 }; while guard.initialized < guard.array_mut.len() { let Some(item) = iter.next() else { diff --git a/library/core/src/iter/adapters/array_chunks.rs b/library/core/src/iter/adapters/array_chunks.rs index 8f1744fc5fbb7..ff83b2010490f 100644 --- a/library/core/src/iter/adapters/array_chunks.rs +++ b/library/core/src/iter/adapters/array_chunks.rs @@ -89,7 +89,7 @@ where match self.iter.next_chunk() { Ok(chunk) => acc = f(acc, chunk)?, Err(remainder) => { - // Make sure to not override `self.remainder` with an empty array + // Make sure to not overwrite `self.remainder` with an empty array // when `next` is called after `ArrayChunks` exhaustion. self.remainder.get_or_insert(remainder); From 786cca82d24e6a29e66e1d945492db911cae0020 Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Wed, 10 Sep 2025 13:33:45 +0000 Subject: [PATCH 2/8] slice iter: replace manual saturating_sub --- library/core/src/slice/iter.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/library/core/src/slice/iter.rs b/library/core/src/slice/iter.rs index 7053ae86e732f..10dd6ada30260 100644 --- a/library/core/src/slice/iter.rs +++ b/library/core/src/slice/iter.rs @@ -2368,10 +2368,7 @@ impl<'a, T> Iterator for RChunks<'a, T> { } else { // Can't underflow because of the check above let end = self.v.len() - end; - let start = match end.checked_sub(self.chunk_size) { - Some(sum) => sum, - None => 0, - }; + let start = end.saturating_sub(self.chunk_size); let nth = &self.v[start..end]; self.v = &self.v[0..start]; Some(nth) @@ -2391,10 +2388,7 @@ impl<'a, T> Iterator for RChunks<'a, T> { unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item { let end = self.v.len() - idx * self.chunk_size; - let start = match end.checked_sub(self.chunk_size) { - None => 0, - Some(start) => start, - }; + let start = end.saturating_sub(self.chunk_size); // SAFETY: mostly identical to `Chunks::__iterator_get_unchecked`. unsafe { from_raw_parts(self.v.as_ptr().add(start), end - start) } } @@ -2571,10 +2565,7 @@ impl<'a, T> Iterator for RChunksMut<'a, T> { unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item { let end = self.v.len() - idx * self.chunk_size; - let start = match end.checked_sub(self.chunk_size) { - None => 0, - Some(start) => start, - }; + let start = end.saturating_sub(self.chunk_size); // SAFETY: see comments for `RChunks::__iterator_get_unchecked` and // `ChunksMut::__iterator_get_unchecked`, `self.v`. unsafe { from_raw_parts_mut(self.v.as_mut_ptr().add(start), end - start) } From c62abb65c773654c9e8f65f5f115a3fce441781c Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Thu, 11 Sep 2025 11:05:03 +0000 Subject: [PATCH 3/8] slice iter: cleanup and make similar Chunks[Mut]::nth --- library/core/src/slice/iter.rs | 44 +++++++++++++++------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/library/core/src/slice/iter.rs b/library/core/src/slice/iter.rs index 10dd6ada30260..8ca78941a8a55 100644 --- a/library/core/src/slice/iter.rs +++ b/library/core/src/slice/iter.rs @@ -1537,13 +1537,13 @@ impl<'a, T> Iterator for Chunks<'a, T> { #[inline] fn nth(&mut self, n: usize) -> Option { - let (start, overflow) = n.overflowing_mul(self.chunk_size); - // min(len) makes a wrong start harmless, but enables optimizing this to brachless code - let chunk_start = &self.v[start.min(self.v.len())..]; - let (nth, remainder) = chunk_start.split_at(self.chunk_size.min(chunk_start.len())); - if !overflow && start < self.v.len() { - self.v = remainder; - Some(nth) + if let Some(start) = n.checked_mul(self.chunk_size) + && start < self.v.len() + { + let rest = &self.v[start..]; + let (chunk, rest) = rest.split_at(self.chunk_size.min(rest.len())); + self.v = rest; + Some(chunk) } else { self.v = &self.v[..0]; // cheaper than &[] None @@ -1613,10 +1613,7 @@ impl<'a, T> DoubleEndedIterator for Chunks<'a, T> { None } else { let start = (len - 1 - n) * self.chunk_size; - let end = match start.checked_add(self.chunk_size) { - Some(res) => cmp::min(self.v.len(), res), - None => self.v.len(), - }; + let end = (start + self.chunk_size).min(self.v.len()); let nth_back = &self.v[start..end]; self.v = &self.v[..start]; Some(nth_back) @@ -1719,22 +1716,19 @@ impl<'a, T> Iterator for ChunksMut<'a, T> { #[inline] fn nth(&mut self, n: usize) -> Option<&'a mut [T]> { - let (start, overflow) = n.overflowing_mul(self.chunk_size); - if start >= self.v.len() || overflow { + if let Some(start) = n.checked_mul(self.chunk_size) + && start < self.v.len() + { + // SAFETY: `start < self.v.len()` ensures this is in bounds + let (_, rest) = unsafe { self.v.split_at_mut(start) }; + // SAFETY: `.min(rest.len()` ensures this is in bounds + let (chunk, rest) = unsafe { rest.split_at_mut(self.chunk_size.min(rest.len())) }; + self.v = rest; + // SAFETY: Nothing else points to or will point to the contents of this slice. + Some(unsafe { &mut *chunk }) + } else { self.v = &mut []; None - } else { - let end = match start.checked_add(self.chunk_size) { - Some(sum) => cmp::min(self.v.len(), sum), - None => self.v.len(), - }; - // SAFETY: The self.v contract ensures that any split_at_mut is valid. - let (head, tail) = unsafe { self.v.split_at_mut(end) }; - // SAFETY: The self.v contract ensures that any split_at_mut is valid. - let (_, nth) = unsafe { head.split_at_mut(start) }; - self.v = tail; - // SAFETY: Nothing else points to or will point to the contents of this slice. - Some(unsafe { &mut *nth }) } } From a7ee7c8cbef695faf8449d09c542d12127a6aba9 Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Thu, 11 Sep 2025 15:09:21 +0000 Subject: [PATCH 4/8] slice iter: more cleanup --- library/core/src/slice/iter.rs | 110 +++++++++++++------------------ library/coretests/tests/slice.rs | 7 ++ 2 files changed, 52 insertions(+), 65 deletions(-) diff --git a/library/core/src/slice/iter.rs b/library/core/src/slice/iter.rs index 8ca78941a8a55..925910400eade 100644 --- a/library/core/src/slice/iter.rs +++ b/library/core/src/slice/iter.rs @@ -1415,26 +1415,21 @@ impl<'a, T> Iterator for Windows<'a, T> { #[stable(feature = "rust1", since = "1.0.0")] impl<'a, T> DoubleEndedIterator for Windows<'a, T> { #[inline] - fn next_back(&mut self) -> Option<&'a [T]> { - if self.size.get() > self.v.len() { - None - } else { - let ret = Some(&self.v[self.v.len() - self.size.get()..]); - self.v = &self.v[..self.v.len() - 1]; - ret - } + fn next_back(&mut self) -> Option { + self.nth_back(0) } #[inline] fn nth_back(&mut self, n: usize) -> Option { - let (end, overflow) = self.v.len().overflowing_sub(n); - if end < self.size.get() || overflow { + if let Some(end) = self.v.len().checked_sub(n) + && let Some(start) = end.checked_sub(self.size.get()) + { + let res = &self.v[start..end]; + self.v = &self.v[..end - 1]; + Some(res) + } else { self.v = &self.v[..0]; // cheaper than &[] None - } else { - let ret = &self.v[end - self.size.get()..end]; - self.v = &self.v[..end - 1]; - Some(ret) } } } @@ -1523,9 +1518,7 @@ impl<'a, T> Iterator for Chunks<'a, T> { if self.v.is_empty() { (0, Some(0)) } else { - let n = self.v.len() / self.chunk_size; - let rem = self.v.len() % self.chunk_size; - let n = if rem > 0 { n + 1 } else { n }; + let n = self.v.len().div_ceil(self.chunk_size); (n, Some(n)) } } @@ -1613,7 +1606,7 @@ impl<'a, T> DoubleEndedIterator for Chunks<'a, T> { None } else { let start = (len - 1 - n) * self.chunk_size; - let end = (start + self.chunk_size).min(self.v.len()); + let end = start + (self.v.len() - start).min(self.chunk_size); let nth_back = &self.v[start..end]; self.v = &self.v[..start]; Some(nth_back) @@ -1702,9 +1695,7 @@ impl<'a, T> Iterator for ChunksMut<'a, T> { if self.v.is_empty() { (0, Some(0)) } else { - let n = self.v.len() / self.chunk_size; - let rem = self.v.len() % self.chunk_size; - let n = if rem > 0 { n + 1 } else { n }; + let n = self.v.len().div_ceil(self.chunk_size); (n, Some(n)) } } @@ -1903,13 +1894,10 @@ impl<'a, T> Iterator for ChunksExact<'a, T> { #[inline] fn next(&mut self) -> Option<&'a [T]> { - if self.v.len() < self.chunk_size { - None - } else { - let (fst, snd) = self.v.split_at(self.chunk_size); - self.v = snd; - Some(fst) - } + self.v.split_at_checked(self.chunk_size).and_then(|(chunk, rest)| { + self.v = rest; + Some(chunk) + }) } #[inline] @@ -1925,14 +1913,14 @@ impl<'a, T> Iterator for ChunksExact<'a, T> { #[inline] fn nth(&mut self, n: usize) -> Option { - let (start, overflow) = n.overflowing_mul(self.chunk_size); - if start >= self.v.len() || overflow { + if let Some(start) = n.checked_mul(self.chunk_size) + && start < self.v.len() + { + self.v = &self.v[start..]; + self.next() + } else { self.v = &self.v[..0]; // cheaper than &[] None - } else { - let (_, snd) = self.v.split_at(start); - self.v = snd; - self.next() } } @@ -2061,15 +2049,11 @@ impl<'a, T> Iterator for ChunksExactMut<'a, T> { #[inline] fn next(&mut self) -> Option<&'a mut [T]> { - if self.v.len() < self.chunk_size { - None - } else { - // SAFETY: self.chunk_size is inbounds because we compared above against self.v.len() - let (head, tail) = unsafe { self.v.split_at_mut(self.chunk_size) }; - self.v = tail; - // SAFETY: Nothing else points to or will point to the contents of this slice. - Some(unsafe { &mut *head }) - } + // SAFETY: we have `&mut self`, so are allowed to temporarily materialize a mut slice + unsafe { &mut *self.v }.split_at_mut_checked(self.chunk_size).and_then(|(chunk, rest)| { + self.v = rest; + Some(chunk) + }) } #[inline] @@ -2085,15 +2069,15 @@ impl<'a, T> Iterator for ChunksExactMut<'a, T> { #[inline] fn nth(&mut self, n: usize) -> Option<&'a mut [T]> { - let (start, overflow) = n.overflowing_mul(self.chunk_size); - if start >= self.v.len() || overflow { + if let Some(start) = n.checked_mul(self.chunk_size) + && start < self.v.len() + { + // SAFETY: `start < self.v.len()` + self.v = unsafe { self.v.split_at_mut(start).1 }; + self.next() + } else { self.v = &mut []; None - } else { - // SAFETY: The self.v contract ensures that any split_at_mut is valid. - let (_, snd) = unsafe { self.v.split_at_mut(start) }; - self.v = snd; - self.next() } } @@ -2341,9 +2325,7 @@ impl<'a, T> Iterator for RChunks<'a, T> { if self.v.is_empty() { (0, Some(0)) } else { - let n = self.v.len() / self.chunk_size; - let rem = self.v.len() % self.chunk_size; - let n = if rem > 0 { n + 1 } else { n }; + let n = self.v.len().div_ceil(self.chunk_size); (n, Some(n)) } } @@ -2355,17 +2337,17 @@ impl<'a, T> Iterator for RChunks<'a, T> { #[inline] fn nth(&mut self, n: usize) -> Option { - let (end, overflow) = n.overflowing_mul(self.chunk_size); - if end >= self.v.len() || overflow { + if let Some(end) = n.checked_mul(self.chunk_size) + && end < self.v.len() + { + let end = self.v.len() - end; + let rest = &self.v[..end]; + let (rest, chunk) = rest.split_at(end.saturating_sub(self.chunk_size)); + self.v = rest; + Some(chunk) + } else { self.v = &self.v[..0]; // cheaper than &[] None - } else { - // Can't underflow because of the check above - let end = self.v.len() - end; - let start = end.saturating_sub(self.chunk_size); - let nth = &self.v[start..end]; - self.v = &self.v[0..start]; - Some(nth) } } @@ -2508,9 +2490,7 @@ impl<'a, T> Iterator for RChunksMut<'a, T> { if self.v.is_empty() { (0, Some(0)) } else { - let n = self.v.len() / self.chunk_size; - let rem = self.v.len() % self.chunk_size; - let n = if rem > 0 { n + 1 } else { n }; + let n = self.v.len().div_ceil(self.chunk_size); (n, Some(n)) } } diff --git a/library/coretests/tests/slice.rs b/library/coretests/tests/slice.rs index 110c4e5f3b406..6f60f71e8a477 100644 --- a/library/coretests/tests/slice.rs +++ b/library/coretests/tests/slice.rs @@ -432,6 +432,13 @@ fn test_chunks_exact_mut_zip_aliasing() { assert_eq!(first, (&mut [0, 1][..], &[6, 7][..])); } +#[test] +fn test_chunks_zst() { + const SIZE: usize = 16; + let mut it = [(); usize::MAX].chunks(SIZE); + assert_eq!(it.nth_back(0), Some(&[(); SIZE - 1][..])); +} + #[test] fn test_rchunks_mut_zip_aliasing() { let v1: &mut [i32] = &mut [0, 1, 2, 3, 4]; From b60788e90534fdb6581ba2d8691ee636baf1981f Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Tue, 4 Nov 2025 07:39:34 +0000 Subject: [PATCH 5/8] slice iter: rm all overflowing_* and switch conditions to mv dependent code close --- library/core/src/slice/iter.rs | 136 ++++++++++++++++----------------- 1 file changed, 64 insertions(+), 72 deletions(-) diff --git a/library/core/src/slice/iter.rs b/library/core/src/slice/iter.rs index 925910400eade..8e972fefbe82f 100644 --- a/library/core/src/slice/iter.rs +++ b/library/core/src/slice/iter.rs @@ -1601,15 +1601,15 @@ impl<'a, T> DoubleEndedIterator for Chunks<'a, T> { #[inline] fn nth_back(&mut self, n: usize) -> Option { let len = self.len(); - if n >= len { - self.v = &self.v[..0]; // cheaper than &[] - None - } else { + if n < len { let start = (len - 1 - n) * self.chunk_size; let end = start + (self.v.len() - start).min(self.chunk_size); let nth_back = &self.v[start..end]; self.v = &self.v[..start]; Some(nth_back) + } else { + self.v = &self.v[..0]; // cheaper than &[] + None } } } @@ -1770,10 +1770,7 @@ impl<'a, T> DoubleEndedIterator for ChunksMut<'a, T> { #[inline] fn nth_back(&mut self, n: usize) -> Option { let len = self.len(); - if n >= len { - self.v = &mut []; - None - } else { + if n < len { let start = (len - 1 - n) * self.chunk_size; let end = match start.checked_add(self.chunk_size) { Some(res) => cmp::min(self.v.len(), res), @@ -1786,6 +1783,9 @@ impl<'a, T> DoubleEndedIterator for ChunksMut<'a, T> { self.v = head; // SAFETY: Nothing else points to or will point to the contents of this slice. Some(unsafe { &mut *nth_back }) + } else { + self.v = &mut []; + None } } } @@ -1952,15 +1952,15 @@ impl<'a, T> DoubleEndedIterator for ChunksExact<'a, T> { #[inline] fn nth_back(&mut self, n: usize) -> Option { let len = self.len(); - if n >= len { - self.v = &self.v[..0]; // cheaper than &[] - None - } else { + if n < len { let start = (len - 1 - n) * self.chunk_size; let end = start + self.chunk_size; let nth_back = &self.v[start..end]; self.v = &self.v[..start]; Some(nth_back) + } else { + self.v = &self.v[..0]; // cheaper than &[] + None } } } @@ -2111,10 +2111,7 @@ impl<'a, T> DoubleEndedIterator for ChunksExactMut<'a, T> { #[inline] fn nth_back(&mut self, n: usize) -> Option { let len = self.len(); - if n >= len { - self.v = &mut []; - None - } else { + if n < len { let start = (len - 1 - n) * self.chunk_size; let end = start + self.chunk_size; // SAFETY: The self.v contract ensures that any split_at_mut is valid. @@ -2124,6 +2121,9 @@ impl<'a, T> DoubleEndedIterator for ChunksExactMut<'a, T> { self.v = head; // SAFETY: Nothing else points to or will point to the contents of this slice. Some(unsafe { &mut *nth_back }) + } else { + self.v = &mut []; + None } } } @@ -2307,16 +2307,12 @@ impl<'a, T> Iterator for RChunks<'a, T> { if self.v.is_empty() { None } else { - let len = self.v.len(); - let chunksz = cmp::min(len, self.chunk_size); - // SAFETY: split_at_unchecked just requires the argument be less - // than the length. This could only happen if the expression `len - - // chunksz` overflows. This could only happen if `chunksz > len`, - // which is impossible as we initialize it as the `min` of `len` and - // `self.chunk_size`. - let (fst, snd) = unsafe { self.v.split_at_unchecked(len - chunksz) }; - self.v = fst; - Some(snd) + let idx = self.v.len().saturating_sub(self.chunk_size); + // SAFETY: self.chunk_size() > 0, so 0 <= idx < self.v.len(). + // Thus `idx` is in-bounds for `self.v` and can be used as a valid argument for `split_at_mut_unchecked`. + let (rest, chunk) = unsafe { self.v.split_at_unchecked(idx) }; + self.v = rest; + Some(chunk) } } @@ -2389,17 +2385,16 @@ impl<'a, T> DoubleEndedIterator for RChunks<'a, T> { #[inline] fn nth_back(&mut self, n: usize) -> Option { let len = self.len(); - if n >= len { - self.v = &self.v[..0]; // cheaper than &[] - None - } else { - // can't underflow because `n < len` + if n < len { let offset_from_end = (len - 1 - n) * self.chunk_size; let end = self.v.len() - offset_from_end; let start = end.saturating_sub(self.chunk_size); let nth_back = &self.v[start..end]; self.v = &self.v[end..]; Some(nth_back) + } else { + self.v = &self.v[..0]; // cheaper than &[] + None } } } @@ -2471,17 +2466,13 @@ impl<'a, T> Iterator for RChunksMut<'a, T> { if self.v.is_empty() { None } else { - let sz = cmp::min(self.v.len(), self.chunk_size); - let len = self.v.len(); - // SAFETY: split_at_mut_unchecked just requires the argument be less - // than the length. This could only happen if the expression - // `len - sz` overflows. This could only happen if `sz > - // len`, which is impossible as we initialize it as the `min` of - // `self.v.len()` (e.g. `len`) and `self.chunk_size`. - let (head, tail) = unsafe { self.v.split_at_mut_unchecked(len - sz) }; - self.v = head; + let idx = self.v.len().saturating_sub(self.chunk_size); + // SAFETY: self.chunk_size() > 0, so 0 <= idx < self.v.len(). + // Thus `idx` is in-bounds for `self.v` and can be used as a valid argument for `split_at_mut_unchecked`. + let (rest, chunk) = unsafe { self.v.split_at_mut_unchecked(idx) }; + self.v = rest; // SAFETY: Nothing else points to or will point to the contents of this slice. - Some(unsafe { &mut *tail }) + Some(unsafe { &mut *chunk }) } } @@ -2502,12 +2493,9 @@ impl<'a, T> Iterator for RChunksMut<'a, T> { #[inline] fn nth(&mut self, n: usize) -> Option<&'a mut [T]> { - let (end, overflow) = n.overflowing_mul(self.chunk_size); - if end >= self.v.len() || overflow { - self.v = &mut []; - None - } else { - // Can't underflow because of the check above + if let Some(end) = n.checked_mul(self.chunk_size) + && end < self.v.len() + { let end = self.v.len() - end; let start = match end.checked_sub(self.chunk_size) { Some(sum) => sum, @@ -2522,6 +2510,9 @@ impl<'a, T> Iterator for RChunksMut<'a, T> { self.v = head; // SAFETY: Nothing else points to or will point to the contents of this slice. Some(unsafe { &mut *nth }) + } else { + self.v = &mut []; + None } } @@ -2566,10 +2557,7 @@ impl<'a, T> DoubleEndedIterator for RChunksMut<'a, T> { #[inline] fn nth_back(&mut self, n: usize) -> Option { let len = self.len(); - if n >= len { - self.v = &mut []; - None - } else { + if n < len { // can't underflow because `n < len` let offset_from_end = (len - 1 - n) * self.chunk_size; let end = self.v.len() - offset_from_end; @@ -2581,6 +2569,9 @@ impl<'a, T> DoubleEndedIterator for RChunksMut<'a, T> { self.v = tail; // SAFETY: Nothing else points to or will point to the contents of this slice. Some(unsafe { &mut *nth_back }) + } else { + self.v = &mut []; + None } } } @@ -2711,14 +2702,14 @@ impl<'a, T> Iterator for RChunksExact<'a, T> { #[inline] fn nth(&mut self, n: usize) -> Option { - let (end, overflow) = n.overflowing_mul(self.chunk_size); - if end >= self.v.len() || overflow { + if let Some(end) = n.checked_mul(self.chunk_size) + && end < self.v.len() + { + self.v = &self.v[..self.v.len() - end]; + self.next() + } else { self.v = &self.v[..0]; // cheaper than &[] None - } else { - let (fst, _) = self.v.split_at(self.v.len() - end); - self.v = fst; - self.next() } } @@ -2751,10 +2742,7 @@ impl<'a, T> DoubleEndedIterator for RChunksExact<'a, T> { #[inline] fn nth_back(&mut self, n: usize) -> Option { let len = self.len(); - if n >= len { - self.v = &self.v[..0]; // cheaper than &[] - None - } else { + if n < len { // now that we know that `n` corresponds to a chunk, // none of these operations can underflow/overflow let offset = (len - n) * self.chunk_size; @@ -2763,6 +2751,9 @@ impl<'a, T> DoubleEndedIterator for RChunksExact<'a, T> { let nth_back = &self.v[start..end]; self.v = &self.v[end..]; Some(nth_back) + } else { + self.v = &self.v[..0]; // cheaper than &[] + None } } } @@ -2875,16 +2866,17 @@ impl<'a, T> Iterator for RChunksExactMut<'a, T> { #[inline] fn nth(&mut self, n: usize) -> Option<&'a mut [T]> { - let (end, overflow) = n.overflowing_mul(self.chunk_size); - if end >= self.v.len() || overflow { - self.v = &mut []; - None - } else { - let len = self.v.len(); + if let Some(end) = n.checked_mul(self.chunk_size) + && end < self.v.len() + { + let idx = self.v.len() - end; // SAFETY: The self.v contract ensures that any split_at_mut is valid. - let (fst, _) = unsafe { self.v.split_at_mut(len - end) }; + let (fst, _) = unsafe { self.v.split_at_mut(idx) }; self.v = fst; self.next() + } else { + self.v = &mut []; + None } } @@ -2919,10 +2911,7 @@ impl<'a, T> DoubleEndedIterator for RChunksExactMut<'a, T> { #[inline] fn nth_back(&mut self, n: usize) -> Option { let len = self.len(); - if n >= len { - self.v = &mut []; - None - } else { + if n < len { // now that we know that `n` corresponds to a chunk, // none of these operations can underflow/overflow let offset = (len - n) * self.chunk_size; @@ -2935,6 +2924,9 @@ impl<'a, T> DoubleEndedIterator for RChunksExactMut<'a, T> { self.v = tail; // SAFETY: Nothing else points to or will point to the contents of this slice. Some(unsafe { &mut *nth_back }) + } else { + self.v = &mut []; + None } } } From 62e0a843c0453233c0ebdbe756939ab7511c1a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Mon, 1 Dec 2025 19:44:21 +0100 Subject: [PATCH 6/8] also introduce Peekable::next_if_map_mut next to next_if_map --- library/core/src/iter/adapters/peekable.rs | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/library/core/src/iter/adapters/peekable.rs b/library/core/src/iter/adapters/peekable.rs index a55de75d56c6e..ee40d2b74c647 100644 --- a/library/core/src/iter/adapters/peekable.rs +++ b/library/core/src/iter/adapters/peekable.rs @@ -331,6 +331,8 @@ impl Peekable { /// If the closure panics, the next value will always be consumed and dropped /// even if the panic is caught, because the closure never returned an `Err` value to put back. /// + /// See also: [`next_if_map_mut`](Self::next_if_map_mut). + /// /// # Examples /// /// Parse the leading decimal number from an iterator of characters. @@ -419,6 +421,44 @@ impl Peekable { self.peeked = Some(unpeek); None } + + /// Gives a mutable reference to the next value of the iterator and applies a function `f` to it, + /// returning the result and advancing the iterator if `f` returns `Some`. + /// + /// Otherwise, if `f` returns `None`, the next value is kept for the next iteration. + /// + /// If `f` panics, the item that is consumed from the iterator as if `Some` was returned from `f`. + /// The value will be dropped. + /// + /// This is similar to [`next_if_map`](Self::next_if_map), except ownership of the item is not given to `f`. + /// This can be preferable if `f` would copy the item anyway. + /// + /// # Examples + /// + /// Parse the leading decimal number from an iterator of characters. + /// ``` + /// #![feature(peekable_next_if_map)] + /// let mut iter = "125 GOTO 10".chars().peekable(); + /// let mut line_num = 0_u32; + /// while let Some(digit) = iter.next_if_map_mut(|c| c.to_digit(10)) { + /// line_num = line_num * 10 + digit; + /// } + /// assert_eq!(line_num, 125); + /// assert_eq!(iter.collect::(), " GOTO 10"); + /// ``` + #[unstable(feature = "peekable_next_if_map", issue = "143702")] + pub fn next_if_map_mut(&mut self, f: impl FnOnce(&mut I::Item) -> Option) -> Option { + let unpeek = if let Some(mut item) = self.next() { + match f(&mut item) { + Some(result) => return Some(result), + None => Some(item), + } + } else { + None + }; + self.peeked = Some(unpeek); + None + } } #[unstable(feature = "trusted_len", issue = "37572")] From 6f8b3a97edc5656187ada642f44287c0fecb4b29 Mon Sep 17 00:00:00 2001 From: beetrees Date: Fri, 4 Jul 2025 17:44:15 +0100 Subject: [PATCH 7/8] Rework `c_variadic` --- .../rustc_codegen_ssa/src/traits/intrinsic.rs | 4 +- library/core/src/ffi/mod.rs | 2 +- library/core/src/ffi/va_list.rs | 183 ++++--------- library/core/src/intrinsics/mod.rs | 8 +- library/std/src/ffi/mod.rs | 2 +- .../src/directives/directive_names.rs | 1 + tests/auxiliary/rust_test_helpers.c | 4 + tests/codegen-llvm/cffi/c-variadic-copy.rs | 4 +- tests/codegen-llvm/cffi/c-variadic-opt.rs | 8 +- tests/codegen-llvm/cffi/c-variadic.rs | 4 +- .../c-link-to-rust-va-list-fn/checkrust.rs | 15 +- tests/ui/abi/variadic-ffi.rs | 32 ++- .../pass-by-value-abi.aarch64.stderr | 80 ++++++ tests/ui/c-variadic/pass-by-value-abi.rs | 46 ++++ .../c-variadic/pass-by-value-abi.win.stderr | 83 ++++++ .../pass-by-value-abi.x86_64.stderr | 240 ++++++++++++++++++ tests/ui/c-variadic/variadic-ffi-4.rs | 19 +- tests/ui/c-variadic/variadic-ffi-4.stderr | 121 ++------- .../macro-dotdotdot-may-not-begin-a-type.rs | 2 +- .../variadic-ffi-semantic-restrictions.rs | 6 +- .../variadic-ffi-semantic-restrictions.stderr | 6 +- tests/ui/thir-print/c-variadic.stdout | 4 +- 22 files changed, 587 insertions(+), 287 deletions(-) create mode 100644 tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr create mode 100644 tests/ui/c-variadic/pass-by-value-abi.rs create mode 100644 tests/ui/c-variadic/pass-by-value-abi.win.stderr create mode 100644 tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr diff --git a/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs b/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs index c5ecf43046c74..187e4b90656ab 100644 --- a/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs @@ -36,10 +36,10 @@ pub trait IntrinsicCallBuilderMethods<'tcx>: BackendTypes { vtable_byte_offset: u64, typeid: Self::Metadata, ) -> Self::Value; - /// Trait method used to inject `va_start` on the "spoofed" `VaListImpl` in + /// Trait method used to inject `va_start` on the "spoofed" `VaList` in /// Rust defined C-variadic functions. fn va_start(&mut self, val: Self::Value) -> Self::Value; - /// Trait method used to inject `va_end` on the "spoofed" `VaListImpl` before + /// Trait method used to inject `va_end` on the "spoofed" `VaList` before /// Rust defined C-variadic functions return. fn va_end(&mut self, val: Self::Value) -> Self::Value; } diff --git a/library/core/src/ffi/mod.rs b/library/core/src/ffi/mod.rs index 1356ca217c9a2..f1b928da7ef3c 100644 --- a/library/core/src/ffi/mod.rs +++ b/library/core/src/ffi/mod.rs @@ -28,7 +28,7 @@ pub mod c_str; issue = "44930", reason = "the `c_variadic` feature has not been properly tested on all supported platforms" )] -pub use self::va_list::{VaArgSafe, VaList, VaListImpl}; +pub use self::va_list::{VaArgSafe, VaList}; #[unstable( feature = "c_variadic", diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 233a2ee3e484e..449e62ac00d3e 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -4,15 +4,15 @@ #[cfg(not(target_arch = "xtensa"))] use crate::ffi::c_void; -#[allow(unused_imports)] use crate::fmt; -use crate::intrinsics::{va_arg, va_copy, va_end}; -use crate::marker::{PhantomData, PhantomInvariantLifetime}; -use crate::ops::{Deref, DerefMut}; +use crate::intrinsics::{va_arg, va_copy}; +use crate::marker::PhantomCovariantLifetime; -// The name is WIP, using `VaListImpl` for now. -// // Most targets explicitly specify the layout of `va_list`, this layout is matched here. +// For `va_list`s which are single-element array in C (and therefore experience array-to-pointer +// decay when passed as arguments in C), the `VaList` struct is annotated with +// `#[rustc_pass_indirectly_in_non_rustic_abis]`. This ensures that the compiler uses the correct +// ABI for functions like `extern "C" fn takes_va_list(va: VaList<'_>)` by passing `va` indirectly. crate::cfg_select! { all( target_arch = "aarch64", @@ -27,66 +27,60 @@ crate::cfg_select! { /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf #[repr(C)] #[derive(Debug)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { - stack: *mut c_void, - gr_top: *mut c_void, - vr_top: *mut c_void, + struct VaListInner { + stack: *const c_void, + gr_top: *const c_void, + vr_top: *const c_void, gr_offs: i32, vr_offs: i32, - _marker: PhantomInvariantLifetime<'f>, } } all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)) => { /// PowerPC ABI implementation of a `va_list`. #[repr(C)] #[derive(Debug)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { + #[rustc_pass_indirectly_in_non_rustic_abis] + struct VaListInner { gpr: u8, fpr: u8, reserved: u16, - overflow_arg_area: *mut c_void, - reg_save_area: *mut c_void, - _marker: PhantomInvariantLifetime<'f>, + overflow_arg_area: *const c_void, + reg_save_area: *const c_void, } } target_arch = "s390x" => { /// s390x ABI implementation of a `va_list`. #[repr(C)] #[derive(Debug)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { + #[rustc_pass_indirectly_in_non_rustic_abis] + struct VaListInner { gpr: i64, fpr: i64, - overflow_arg_area: *mut c_void, - reg_save_area: *mut c_void, - _marker: PhantomInvariantLifetime<'f>, + overflow_arg_area: *const c_void, + reg_save_area: *const c_void, } } all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)) => { /// x86_64 ABI implementation of a `va_list`. #[repr(C)] #[derive(Debug)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { + #[rustc_pass_indirectly_in_non_rustic_abis] + struct VaListInner { gp_offset: i32, fp_offset: i32, - overflow_arg_area: *mut c_void, - reg_save_area: *mut c_void, - _marker: PhantomInvariantLifetime<'f>, + overflow_arg_area: *const c_void, + reg_save_area: *const c_void, } } target_arch = "xtensa" => { /// Xtensa ABI implementation of a `va_list`. #[repr(C)] #[derive(Debug)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { - stk: *mut i32, - reg: *mut i32, + #[rustc_pass_indirectly_in_non_rustic_abis] + struct VaListInner { + stk: *const i32, + reg: *const i32, ndx: i32, - _marker: PhantomInvariantLifetime<'f>, } } @@ -95,94 +89,32 @@ crate::cfg_select! { // - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599) // - windows // - uefi - // - any other target for which we don't specify the `VaListImpl` above + // - any other target for which we don't specify the `VaListInner` above // // In this implementation the `va_list` type is just an alias for an opaque pointer. // That pointer is probably just the next variadic argument on the caller's stack. _ => { /// Basic implementation of a `va_list`. #[repr(transparent)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { - ptr: *mut c_void, - - // Invariant over `'f`, so each `VaListImpl<'f>` object is tied to - // the region of the function it's defined in - _marker: PhantomInvariantLifetime<'f>, - } - - impl<'f> fmt::Debug for VaListImpl<'f> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "va_list* {:p}", self.ptr) - } - } - } -} - -crate::cfg_select! { - all( - any( - target_arch = "aarch64", - target_arch = "powerpc", - target_arch = "s390x", - target_arch = "x86_64" - ), - not(target_arch = "xtensa"), - any(not(target_arch = "aarch64"), not(target_vendor = "apple")), - not(target_family = "wasm"), - not(target_os = "uefi"), - not(windows), - ) => { - /// A wrapper for a `va_list` - #[repr(transparent)] - #[derive(Debug)] - pub struct VaList<'a, 'f: 'a> { - inner: &'a mut VaListImpl<'f>, - _marker: PhantomData<&'a mut VaListImpl<'f>>, - } - - - impl<'f> VaListImpl<'f> { - /// Converts a [`VaListImpl`] into a [`VaList`] that is binary-compatible with C's `va_list`. - #[inline] - pub fn as_va_list<'a>(&'a mut self) -> VaList<'a, 'f> { - VaList { inner: self, _marker: PhantomData } - } - } - } - - _ => { - /// A wrapper for a `va_list` - #[repr(transparent)] #[derive(Debug)] - pub struct VaList<'a, 'f: 'a> { - inner: VaListImpl<'f>, - _marker: PhantomData<&'a mut VaListImpl<'f>>, - } - - impl<'f> VaListImpl<'f> { - /// Converts a [`VaListImpl`] into a [`VaList`] that is binary-compatible with C's `va_list`. - #[inline] - pub fn as_va_list<'a>(&'a mut self) -> VaList<'a, 'f> { - VaList { inner: VaListImpl { ..*self }, _marker: PhantomData } - } + struct VaListInner { + ptr: *const c_void, } } } -impl<'a, 'f: 'a> Deref for VaList<'a, 'f> { - type Target = VaListImpl<'f>; - - #[inline] - fn deref(&self) -> &VaListImpl<'f> { - &self.inner - } +/// A variable argument list, equivalent to `va_list` in C. +#[repr(transparent)] +#[lang = "va_list"] +pub struct VaList<'a> { + inner: VaListInner, + _marker: PhantomCovariantLifetime<'a>, } -impl<'a, 'f: 'a> DerefMut for VaList<'a, 'f> { - #[inline] - fn deref_mut(&mut self) -> &mut VaListImpl<'f> { - &mut self.inner +impl fmt::Debug for VaList<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // No need to include `_marker` in debug output. + f.debug_tuple("VaList").field(&self.inner).finish() } } @@ -203,7 +135,7 @@ mod sealed { impl Sealed for *const T {} } -/// Types that are valid to read using [`VaListImpl::arg`]. +/// Types that are valid to read using [`VaList::arg`]. /// /// # Safety /// @@ -238,7 +170,7 @@ unsafe impl VaArgSafe for f64 {} unsafe impl VaArgSafe for *mut T {} unsafe impl VaArgSafe for *const T {} -impl<'f> VaListImpl<'f> { +impl<'f> VaList<'f> { /// Advance to and read the next variable argument. /// /// # Safety @@ -258,27 +190,13 @@ impl<'f> VaListImpl<'f> { // SAFETY: the caller must uphold the safety contract for `va_arg`. unsafe { va_arg(self) } } - - /// Copies the `va_list` at the current location. - pub unsafe fn with_copy(&self, f: F) -> R - where - F: for<'copy> FnOnce(VaList<'copy, 'f>) -> R, - { - let mut ap = self.clone(); - let ret = f(ap.as_va_list()); - // SAFETY: the caller must uphold the safety contract for `va_end`. - unsafe { - va_end(&mut ap); - } - ret - } } -impl<'f> Clone for VaListImpl<'f> { +impl<'f> Clone for VaList<'f> { #[inline] fn clone(&self) -> Self { let mut dest = crate::mem::MaybeUninit::uninit(); - // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal + // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal. unsafe { va_copy(dest.as_mut_ptr(), self); dest.assume_init() @@ -286,18 +204,11 @@ impl<'f> Clone for VaListImpl<'f> { } } -impl<'f> Drop for VaListImpl<'f> { +impl<'f> Drop for VaList<'f> { fn drop(&mut self) { - // FIXME: this should call `va_end`, but there's no clean way to - // guarantee that `drop` always gets inlined into its caller, - // so the `va_end` would get directly called from the same function as - // the corresponding `va_copy`. `man va_end` states that C requires this, - // and LLVM basically follows the C semantics, so we need to make sure - // that `va_end` is always called from the same function as `va_copy`. - // For more details, see https://github.com/rust-lang/rust/pull/59625 - // and https://llvm.org/docs/LangRef.html#llvm-va-end-intrinsic. - // - // This works for now, since `va_end` is a no-op on all current LLVM targets. + // Rust requires that not calling `va_end` on a `va_list` does not cause undefined behaviour + // (as it is safe to leak values). As `va_end` is a no-op on all current LLVM targets, this + // destructor is empty. } } diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 564a896076b0d..7631ac27fd24d 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -54,7 +54,7 @@ )] #![allow(missing_docs)] -use crate::ffi::va_list::{VaArgSafe, VaListImpl}; +use crate::ffi::va_list::{VaArgSafe, VaList}; use crate::marker::{ConstParamTy, Destruct, DiscriminantKind, PointeeSized, Tuple}; use crate::{mem, ptr}; @@ -3359,7 +3359,7 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_copy<'f>(dest: *mut VaListImpl<'f>, src: &VaListImpl<'f>); +pub unsafe fn va_copy<'f>(dest: *mut VaList<'f>, src: &VaList<'f>); /// Loads an argument of type `T` from the `va_list` `ap` and increment the /// argument `ap` points to. @@ -3377,7 +3377,7 @@ pub unsafe fn va_copy<'f>(dest: *mut VaListImpl<'f>, src: &VaListImpl<'f>); /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_arg(ap: &mut VaListImpl<'_>) -> T; +pub unsafe fn va_arg(ap: &mut VaList<'_>) -> T; /// Destroy the arglist `ap` after initialization with `va_start` or `va_copy`. /// @@ -3387,4 +3387,4 @@ pub unsafe fn va_arg(ap: &mut VaListImpl<'_>) -> T; /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_end(ap: &mut VaListImpl<'_>); +pub unsafe fn va_end(ap: &mut VaList<'_>); diff --git a/library/std/src/ffi/mod.rs b/library/std/src/ffi/mod.rs index f44e12d48addf..999bd5e63dc45 100644 --- a/library/std/src/ffi/mod.rs +++ b/library/std/src/ffi/mod.rs @@ -172,7 +172,7 @@ pub use core::ffi::c_void; all supported platforms", issue = "44930" )] -pub use core::ffi::{VaArgSafe, VaList, VaListImpl}; +pub use core::ffi::{VaArgSafe, VaList}; #[stable(feature = "core_ffi_c", since = "1.64.0")] pub use core::ffi::{ c_char, c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, diff --git a/src/tools/compiletest/src/directives/directive_names.rs b/src/tools/compiletest/src/directives/directive_names.rs index 8d1232a47596b..a11671331d512 100644 --- a/src/tools/compiletest/src/directives/directive_names.rs +++ b/src/tools/compiletest/src/directives/directive_names.rs @@ -117,6 +117,7 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "ignore-thumbv8m.base-none-eabi", "ignore-thumbv8m.main-none-eabi", "ignore-tvos", + "ignore-uefi", "ignore-unix", "ignore-unknown", "ignore-uwp", diff --git a/tests/auxiliary/rust_test_helpers.c b/tests/auxiliary/rust_test_helpers.c index 34cc7fd5dfbed..cd10d6b98ca7b 100644 --- a/tests/auxiliary/rust_test_helpers.c +++ b/tests/auxiliary/rust_test_helpers.c @@ -314,6 +314,10 @@ double rust_interesting_average(uint64_t n, ...) { return sum; } +int32_t rust_va_list_next_i32(va_list* ap) { + return va_arg(*ap, int32_t); +} + int32_t rust_int8_to_int32(int8_t x) { return (int32_t)x; } diff --git a/tests/codegen-llvm/cffi/c-variadic-copy.rs b/tests/codegen-llvm/cffi/c-variadic-copy.rs index 4c61c4fcf68d3..0cbdcb4bbb85c 100644 --- a/tests/codegen-llvm/cffi/c-variadic-copy.rs +++ b/tests/codegen-llvm/cffi/c-variadic-copy.rs @@ -1,4 +1,4 @@ -// Tests that `VaListImpl::clone` gets inlined into a call to `llvm.va_copy` +// Tests that `VaList::clone` gets inlined into a call to `llvm.va_copy` #![crate_type = "lib"] #![feature(c_variadic)] @@ -12,5 +12,5 @@ extern "C" { pub unsafe extern "C" fn clone_variadic(ap: VaList) { let mut ap2 = ap.clone(); // CHECK: call void @llvm.va_copy - foreign_c_variadic_1(ap2.as_va_list(), 42i32); + foreign_c_variadic_1(ap2, 42i32); } diff --git a/tests/codegen-llvm/cffi/c-variadic-opt.rs b/tests/codegen-llvm/cffi/c-variadic-opt.rs index 7e544ee7f37da..3cc0c3e9f9bdd 100644 --- a/tests/codegen-llvm/cffi/c-variadic-opt.rs +++ b/tests/codegen-llvm/cffi/c-variadic-opt.rs @@ -10,21 +10,21 @@ extern "C" { } // Ensure that `va_start` and `va_end` are properly injected even -// when the "spoofed" `VaListImpl` is not used. +// when the "spoofed" `VaList` is not used. #[no_mangle] pub unsafe extern "C" fn c_variadic_no_use(fmt: *const i8, mut ap: ...) -> i32 { // CHECK: call void @llvm.va_start - vprintf(fmt, ap.as_va_list()) + vprintf(fmt, ap) // CHECK: call void @llvm.va_end } -// Check that `VaListImpl::clone` gets inlined into a direct call to `llvm.va_copy` +// Check that `VaList::clone` gets inlined into a direct call to `llvm.va_copy` #[no_mangle] pub unsafe extern "C" fn c_variadic_clone(fmt: *const i8, mut ap: ...) -> i32 { // CHECK: call void @llvm.va_start let mut ap2 = ap.clone(); // CHECK: call void @llvm.va_copy - let res = vprintf(fmt, ap2.as_va_list()); + let res = vprintf(fmt, ap2); res // CHECK: call void @llvm.va_end } diff --git a/tests/codegen-llvm/cffi/c-variadic.rs b/tests/codegen-llvm/cffi/c-variadic.rs index 140d2f37f4693..a9c77e5899cf6 100644 --- a/tests/codegen-llvm/cffi/c-variadic.rs +++ b/tests/codegen-llvm/cffi/c-variadic.rs @@ -1,6 +1,6 @@ //@ needs-unwind //@ compile-flags: -C no-prepopulate-passes -Copt-level=0 -// +//@ min-llvm-version: 21 #![crate_type = "lib"] #![feature(c_variadic)] @@ -25,7 +25,7 @@ pub unsafe extern "C" fn use_foreign_c_variadic_0() { } // Ensure that we do not remove the `va_list` passed to the foreign function when -// removing the "spoofed" `VaListImpl` that is used by Rust defined C-variadics. +// removing the "spoofed" `VaList` that is used by Rust defined C-variadics. pub unsafe extern "C" fn use_foreign_c_variadic_1_0(ap: VaList) { // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %ap) foreign_c_variadic_1(ap); diff --git a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs index 63d8d713d6226..dd2d094709942 100644 --- a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs +++ b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs @@ -2,7 +2,7 @@ #![feature(c_variadic)] #![feature(cfg_select)] -use std::ffi::{CStr, CString, VaList, VaListImpl, c_char, c_double, c_int, c_long, c_longlong}; +use std::ffi::{CStr, CString, VaList, c_char, c_double, c_int, c_long, c_longlong}; macro_rules! continue_if { ($cond:expr) => { @@ -58,11 +58,8 @@ pub unsafe extern "C" fn check_list_copy_0(mut ap: VaList) -> usize { continue_if!(ap.arg::() == 16); continue_if!(ap.arg::() == 'A' as c_int); continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Skip Me!")); - ap.with_copy( - |mut ap| { - if compare_c_str(ap.arg::<*const c_char>(), "Correct") { 0 } else { 0xff } - }, - ) + let mut ap = ap.clone(); + if compare_c_str(ap.arg::<*const c_char>(), "Correct") { 0 } else { 0xff } } #[unsafe(no_mangle)] @@ -153,8 +150,8 @@ pub unsafe extern "C" fn check_varargs_5(_: c_int, mut ap: ...) -> usize { unsafe extern "C" { fn test_variadic(_: c_int, ...) -> usize; fn test_va_list_by_value(_: VaList) -> usize; - fn test_va_list_by_pointer(_: *mut VaListImpl) -> usize; - fn test_va_list_by_pointer_pointer(_: *mut *mut VaListImpl) -> usize; + fn test_va_list_by_pointer(_: *mut VaList) -> usize; + fn test_va_list_by_pointer_pointer(_: *mut *mut VaList) -> usize; } #[unsafe(no_mangle)] @@ -165,7 +162,7 @@ extern "C" fn run_test_variadic() -> usize { #[unsafe(no_mangle)] extern "C" fn run_test_va_list_by_value() -> usize { unsafe extern "C" fn helper(mut ap: ...) -> usize { - unsafe { test_va_list_by_value(ap.as_va_list()) } + unsafe { test_va_list_by_value(ap) } } unsafe { helper(1 as c_longlong, 2 as c_int, 3 as c_longlong) } diff --git a/tests/ui/abi/variadic-ffi.rs b/tests/ui/abi/variadic-ffi.rs index dfdbff33264be..3ffa0bea0ecf8 100644 --- a/tests/ui/abi/variadic-ffi.rs +++ b/tests/ui/abi/variadic-ffi.rs @@ -10,37 +10,45 @@ extern "C" { fn rust_interesting_average(_: u64, ...) -> f64; fn rust_valist_interesting_average(_: u64, _: VaList) -> f64; + + fn rust_va_list_next_i32(_: *mut VaList<'_>) -> i32; } -pub unsafe extern "C" fn test_valist_forward(n: u64, mut ap: ...) -> f64 { - rust_valist_interesting_average(n, ap.as_va_list()) +pub unsafe extern "C" fn test_valist_forward(n: u64, ap: ...) -> f64 { + rust_valist_interesting_average(n, ap) } -pub unsafe extern "C-unwind" fn c_unwind_can_forward(n: u64, mut ap: ...) -> f64 { - rust_valist_interesting_average(n, ap.as_va_list()) +pub unsafe extern "C-unwind" fn c_unwind_can_forward(n: u64, ap: ...) -> f64 { + rust_valist_interesting_average(n, ap) } pub unsafe extern "C" fn test_va_copy(_: u64, mut ap: ...) { - let mut ap2 = ap.clone(); - assert_eq!(rust_valist_interesting_average(2, ap2.as_va_list()) as i64, 30); + let ap2 = ap.clone(); + assert_eq!(rust_valist_interesting_average(2, ap2) as i64, 30); // Advance one pair in the copy before checking let mut ap2 = ap.clone(); let _ = ap2.arg::(); let _ = ap2.arg::(); - assert_eq!(rust_valist_interesting_average(2, ap2.as_va_list()) as i64, 50); + assert_eq!(rust_valist_interesting_average(2, ap2) as i64, 50); // Advance one pair in the original let _ = ap.arg::(); let _ = ap.arg::(); - let mut ap2 = ap.clone(); - assert_eq!(rust_valist_interesting_average(2, ap2.as_va_list()) as i64, 50); + let ap2 = ap.clone(); + assert_eq!(rust_valist_interesting_average(2, ap2) as i64, 50); let mut ap2 = ap.clone(); let _ = ap2.arg::(); let _ = ap2.arg::(); - assert_eq!(rust_valist_interesting_average(2, ap2.as_va_list()) as i64, 70); + assert_eq!(rust_valist_interesting_average(2, ap2) as i64, 70); +} + +pub unsafe extern "C" fn test_ref(mut ap: ...) { + assert_eq!(rust_va_list_next_i32(&mut ap), 2); + assert_eq!(rust_va_list_next_i32(&mut ap), 4); + assert_eq!(rust_va_list_next_i32(&mut ap), 8); } pub fn main() { @@ -85,4 +93,8 @@ pub fn main() { unsafe { test_va_copy(4, 10i64, 10f64, 20i64, 20f64, 30i64, 30f64, 40i64, 40f64); } + + unsafe { + test_ref(2, 4, 8); + } } diff --git a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr new file mode 100644 index 0000000000000..fe11c42886188 --- /dev/null +++ b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr @@ -0,0 +1,80 @@ +error: fn_abi_of(take_va_list) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: VaList<'_>, + layout: Layout { + size: Size(32 bytes), + align: AbiAlign { + abi: Align(8 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: $OFFSETS, + memory_index: $MEMORY_INDEX, + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + randomization_seed: $SEED, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(32 bytes), + pointee_align: Some( + Align(8 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/pass-by-value-abi.rs:26:1 + | +LL | pub extern "C" fn take_va_list(_: VaList<'_>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/c-variadic/pass-by-value-abi.rs b/tests/ui/c-variadic/pass-by-value-abi.rs new file mode 100644 index 0000000000000..b65442af24727 --- /dev/null +++ b/tests/ui/c-variadic/pass-by-value-abi.rs @@ -0,0 +1,46 @@ +//@ check-fail +//@ normalize-stderr: "randomization_seed: \d+" -> "randomization_seed: $$SEED" +//@ normalize-stderr: "valid_range: 0\.\.=\d+" -> "valid_range: 0..=$$MAX" +//@ normalize-stderr: "memory_index: \[[^\]]+\]" -> "memory_index: $$MEMORY_INDEX" +//@ normalize-stderr: "offsets: \[[^\]]+\]" -> "offsets: $$OFFSETS" +//@ revisions: x86_64 aarch64 win +//@ compile-flags: -O +//@ [x86_64] only-x86_64 +//@ [x86_64] ignore-windows +//@ [x86_64] ignore-uefi +//@ [aarch64] only-aarch64 +//@ [aarch64] ignore-windows +//@ [aarch64] ignore-apple +//@ [aarch64] ignore-uefi +// Windows dosen't use `#[rustc_pass_indirectly_in_non_rustic_abis]` and is tested in CI, so is here +// for comparison. +//@ [win] only-windows + +#![feature(rustc_attrs, c_variadic)] +#![crate_type = "lib"] + +// Can't use `minicore` here as this is testing the implementation in `core::ffi` specifically. +use std::ffi::VaList; + +#[rustc_abi(debug)] +pub extern "C" fn take_va_list(_: VaList<'_>) {} +//~^ ERROR fn_abi_of(take_va_list) = FnAbi { +//[x86_64]~^^ ERROR mode: Indirect { +//[x86_64]~^^^ ERROR on_stack: false, +//[aarch64]~^^^^ ERROR mode: Indirect { +//[aarch64]~^^^^^ ERROR on_stack: false, +//[win]~^^^^^^ ERROR mode: Direct( + +#[cfg(all(target_arch = "x86_64", not(windows)))] +#[rustc_abi(debug)] +pub extern "sysv64" fn take_va_list_sysv64(_: VaList<'_>) {} +//[x86_64]~^ ERROR fn_abi_of(take_va_list_sysv64) = FnAbi { +//[x86_64]~^^ ERROR mode: Indirect { +//[x86_64]~^^^ ERROR on_stack: false, + +#[cfg(all(target_arch = "x86_64", not(windows)))] +#[rustc_abi(debug)] +pub extern "win64" fn take_va_list_win64(_: VaList<'_>) {} +//[x86_64]~^ ERROR: fn_abi_of(take_va_list_win64) = FnAbi { +//[x86_64]~^^ ERROR mode: Indirect { +//[x86_64]~^^^ ERROR on_stack: false, diff --git a/tests/ui/c-variadic/pass-by-value-abi.win.stderr b/tests/ui/c-variadic/pass-by-value-abi.win.stderr new file mode 100644 index 0000000000000..e84430859e029 --- /dev/null +++ b/tests/ui/c-variadic/pass-by-value-abi.win.stderr @@ -0,0 +1,83 @@ +error: fn_abi_of(take_va_list) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: VaList<'_>, + layout: Layout { + size: Size(8 bytes), + align: AbiAlign { + abi: Align(8 bytes), + }, + backend_repr: Scalar( + Initialized { + value: Pointer( + AddressSpace( + 0, + ), + ), + valid_range: 0..=$MAX, + }, + ), + fields: Arbitrary { + offsets: $OFFSETS, + memory_index: $MEMORY_INDEX, + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + randomization_seed: $SEED, + }, + }, + mode: Direct( + ArgAttributes { + regular: NoUndef, + arg_ext: None, + pointee_size: Size(0 bytes), + pointee_align: None, + }, + ), + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/pass-by-value-abi.rs:26:1 + | +LL | pub extern "C" fn take_va_list(_: VaList<'_>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr new file mode 100644 index 0000000000000..73f1ccd5992a9 --- /dev/null +++ b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr @@ -0,0 +1,240 @@ +error: fn_abi_of(take_va_list) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: VaList<'_>, + layout: Layout { + size: Size(24 bytes), + align: AbiAlign { + abi: Align(8 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: $OFFSETS, + memory_index: $MEMORY_INDEX, + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + randomization_seed: $SEED, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(24 bytes), + pointee_align: Some( + Align(8 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/pass-by-value-abi.rs:26:1 + | +LL | pub extern "C" fn take_va_list(_: VaList<'_>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: fn_abi_of(take_va_list_sysv64) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: VaList<'_>, + layout: Layout { + size: Size(24 bytes), + align: AbiAlign { + abi: Align(8 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: $OFFSETS, + memory_index: $MEMORY_INDEX, + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + randomization_seed: $SEED, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(24 bytes), + pointee_align: Some( + Align(8 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: X86( + SysV64, + ), + can_unwind: false, + } + --> $DIR/pass-by-value-abi.rs:36:1 + | +LL | pub extern "sysv64" fn take_va_list_sysv64(_: VaList<'_>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: fn_abi_of(take_va_list_win64) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: VaList<'_>, + layout: Layout { + size: Size(24 bytes), + align: AbiAlign { + abi: Align(8 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: $OFFSETS, + memory_index: $MEMORY_INDEX, + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + randomization_seed: $SEED, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(24 bytes), + pointee_align: Some( + Align(8 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: X86( + Win64, + ), + can_unwind: false, + } + --> $DIR/pass-by-value-abi.rs:43:1 + | +LL | pub extern "win64" fn take_va_list_win64(_: VaList<'_>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/c-variadic/variadic-ffi-4.rs b/tests/ui/c-variadic/variadic-ffi-4.rs index 8064037942259..d9e2e617ce3a1 100644 --- a/tests/ui/c-variadic/variadic-ffi-4.rs +++ b/tests/ui/c-variadic/variadic-ffi-4.rs @@ -2,37 +2,30 @@ #![no_std] #![feature(c_variadic)] -use core::ffi::{VaList, VaListImpl}; +use core::ffi::VaList; -pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaListImpl<'f> { +pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaList<'f> { ap //~^ ERROR: lifetime may not live long enough - //~| ERROR: lifetime may not live long enough } -pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaListImpl<'static> { +pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> { ap //~ ERROR: lifetime may not live long enough } -pub unsafe extern "C" fn no_escape2(_: usize, ap: ...) { - let _ = ap.with_copy(|ap| ap); //~ ERROR: lifetime may not live long enough -} - -pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { +pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) { *ap0 = ap1; //~^ ERROR: lifetime may not live long enough - //~| ERROR: lifetime may not live long enough } -pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { +pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { ap0 = &mut ap1; //~^ ERROR: `ap1` does not live long enough //~| ERROR: lifetime may not live long enough //~| ERROR: lifetime may not live long enough } -pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { +pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaList, mut ap1: ...) { *ap0 = ap1.clone(); //~^ ERROR: lifetime may not live long enough - //~| ERROR: lifetime may not live long enough } diff --git a/tests/ui/c-variadic/variadic-ffi-4.stderr b/tests/ui/c-variadic/variadic-ffi-4.stderr index fc9f8036083a6..a230bb6f5861e 100644 --- a/tests/ui/c-variadic/variadic-ffi-4.stderr +++ b/tests/ui/c-variadic/variadic-ffi-4.stderr @@ -1,113 +1,64 @@ error: lifetime may not live long enough --> $DIR/variadic-ffi-4.rs:8:5 | -LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaListImpl<'f> { - | -- -- has type `VaListImpl<'1>` - | | - | lifetime `'f` defined here -LL | ap - | ^^ function was supposed to return data with lifetime `'1` but it is returning data with lifetime `'f` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance - -error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:8:5 - | -LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaListImpl<'f> { - | -- -- has type `VaListImpl<'1>` +LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaList<'f> { + | -- -- has type `VaList<'1>` | | | lifetime `'f` defined here LL | ap | ^^ function was supposed to return data with lifetime `'f` but it is returning data with lifetime `'1` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:14:5 + --> $DIR/variadic-ffi-4.rs:13:5 | -LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaListImpl<'static> { - | -- has type `VaListImpl<'1>` +LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> { + | -- has type `VaList<'1>` LL | ap | ^^ returning this value requires that `'1` must outlive `'static` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:18:31 + --> $DIR/variadic-ffi-4.rs:17:5 | -LL | let _ = ap.with_copy(|ap| ap); - | --- ^^ returning this value requires that `'1` must outlive `'2` - | | | - | | return type of closure is VaList<'2, '_> - | has type `VaList<'1, '_>` - -error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:22:5 - | -LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` +LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) { + | ------- ------- has type `VaList<'1>` | | - | has type `&mut VaListImpl<'1>` + | has type `&mut VaList<'2>` LL | *ap0 = ap1; | ^^^^ assignment requires that `'1` must outlive `'2` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance error: lifetime may not live long enough --> $DIR/variadic-ffi-4.rs:22:5 | -LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` +LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { + | ------- ------- has type `VaList<'2>` | | - | has type `&mut VaListImpl<'1>` -LL | *ap0 = ap1; - | ^^^^ assignment requires that `'2` must outlive `'1` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance - -error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:28:5 - | -LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` - | | - | has type `&mut VaListImpl<'1>` + | has type `&mut VaList<'1>` LL | ap0 = &mut ap1; | ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2` | - = note: requirement occurs because of a mutable reference to `VaListImpl<'_>` + = note: requirement occurs because of a mutable reference to `VaList<'_>` = note: mutable references are invariant over their type parameter = help: see for more information about variance error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:28:5 + --> $DIR/variadic-ffi-4.rs:22:5 | -LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` +LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { + | ------- ------- has type `VaList<'2>` | | - | has type `&mut VaListImpl<'1>` + | has type `&mut VaList<'1>` LL | ap0 = &mut ap1; | ^^^^^^^^^^^^^^ assignment requires that `'2` must outlive `'1` | - = note: requirement occurs because of a mutable reference to `VaListImpl<'_>` + = note: requirement occurs because of a mutable reference to `VaList<'_>` = note: mutable references are invariant over their type parameter = help: see for more information about variance error[E0597]: `ap1` does not live long enough - --> $DIR/variadic-ffi-4.rs:28:11 + --> $DIR/variadic-ffi-4.rs:22:11 | -LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | - ------- binding `ap1` declared here +LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { + | - ------- binding `ap1` declared here | | | let's call the lifetime of this reference `'3` LL | ap0 = &mut ap1; @@ -120,33 +71,15 @@ LL | } | - `ap1` dropped here while still borrowed error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:35:5 - | -LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` - | | - | has type `&mut VaListImpl<'1>` -LL | *ap0 = ap1.clone(); - | ^^^^ assignment requires that `'2` must outlive `'1` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance - -error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:35:12 + --> $DIR/variadic-ffi-4.rs:29:5 | -LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` +LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaList, mut ap1: ...) { + | ------- ------- has type `VaList<'1>` | | - | has type `&mut VaListImpl<'1>` + | has type `&mut VaList<'2>` LL | *ap0 = ap1.clone(); - | ^^^^^^^^^^^ argument requires that `'1` must outlive `'2` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance + | ^^^^ assignment requires that `'1` must outlive `'2` -error: aborting due to 11 previous errors +error: aborting due to 7 previous errors For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/parser/macro/macro-dotdotdot-may-not-begin-a-type.rs b/tests/ui/parser/macro/macro-dotdotdot-may-not-begin-a-type.rs index 8be99f22d2ee0..b29f6915ae3d6 100644 --- a/tests/ui/parser/macro/macro-dotdotdot-may-not-begin-a-type.rs +++ b/tests/ui/parser/macro/macro-dotdotdot-may-not-begin-a-type.rs @@ -1,4 +1,4 @@ -// A bare `...` represents `CVarArgs` (`VaListImpl<'_>`) in function argument type +// A bare `...` represents `CVarArgs` (`VaList<'_>`) in function argument type // position without being a proper type syntactically. // This test ensures that we do not regress certain MBE calls would we ever promote // `...` to a proper type syntactically. diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs index 025c0e3ecaca4..6f61425a8bd6c 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs @@ -32,12 +32,12 @@ extern "C" fn f3_3(_: ..., x: isize) {} const unsafe extern "C" fn f4_1(x: isize, _: ...) {} //~^ ERROR functions cannot be both `const` and C-variadic -//~| ERROR destructor of `VaListImpl<'_>` cannot be evaluated at compile-time +//~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time const extern "C" fn f4_2(x: isize, _: ...) {} //~^ ERROR functions cannot be both `const` and C-variadic //~| ERROR functions with a C variable argument list must be unsafe -//~| ERROR destructor of `VaListImpl<'_>` cannot be evaluated at compile-time +//~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} //~^ ERROR functions cannot be both `const` and C-variadic @@ -65,7 +65,7 @@ impl X { const fn i_f5(x: isize, _: ...) {} //~^ ERROR `...` is not supported for non-extern functions //~| ERROR functions cannot be both `const` and C-variadic - //~| ERROR destructor of `VaListImpl<'_>` cannot be evaluated at compile-time + //~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time } trait T { diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr index 0e02d4434233e..318015737fa1b 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr @@ -236,7 +236,7 @@ error: `...` must be the last argument of a C-variadic function LL | fn t_f6(_: ..., x: isize); | ^^^^^^ -error[E0493]: destructor of `VaListImpl<'_>` cannot be evaluated at compile-time +error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time --> $DIR/variadic-ffi-semantic-restrictions.rs:33:43 | LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} @@ -244,7 +244,7 @@ LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} | | | the destructor for this type cannot be evaluated in constant functions -error[E0493]: destructor of `VaListImpl<'_>` cannot be evaluated at compile-time +error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time --> $DIR/variadic-ffi-semantic-restrictions.rs:37:36 | LL | const extern "C" fn f4_2(x: isize, _: ...) {} @@ -252,7 +252,7 @@ LL | const extern "C" fn f4_2(x: isize, _: ...) {} | | | the destructor for this type cannot be evaluated in constant functions -error[E0493]: destructor of `VaListImpl<'_>` cannot be evaluated at compile-time +error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time --> $DIR/variadic-ffi-semantic-restrictions.rs:65:29 | LL | const fn i_f5(x: isize, _: ...) {} diff --git a/tests/ui/thir-print/c-variadic.stdout b/tests/ui/thir-print/c-variadic.stdout index d64b2b9aa9d1d..f1905e04f72bf 100644 --- a/tests/ui/thir-print/c-variadic.stdout +++ b/tests/ui/thir-print/c-variadic.stdout @@ -16,13 +16,13 @@ params: [ ) } Param { - ty: std::ffi::VaListImpl<'{erased}> + ty: std::ffi::VaList<'{erased}> ty_span: None self_kind: None hir_id: Some(HirId(DefId(0:3 ~ c_variadic[a5de]::foo).3)) param: Some( Pat: { - ty: std::ffi::VaListImpl<'{erased}> + ty: std::ffi::VaList<'{erased}> span: $DIR/c-variadic.rs:7:34: 7:37 (#0) kind: PatKind { Missing From 2e394b2c3c8a3a6e059e2a3ec0f85b8e322bdc76 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 4 Nov 2025 19:32:06 +0100 Subject: [PATCH 8/8] document `VaList` ABI for more targets --- library/core/src/ffi/va_list.rs | 57 ++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 449e62ac00d3e..4c59ea0cc5328 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -8,11 +8,29 @@ use crate::fmt; use crate::intrinsics::{va_arg, va_copy}; use crate::marker::PhantomCovariantLifetime; -// Most targets explicitly specify the layout of `va_list`, this layout is matched here. -// For `va_list`s which are single-element array in C (and therefore experience array-to-pointer -// decay when passed as arguments in C), the `VaList` struct is annotated with -// `#[rustc_pass_indirectly_in_non_rustic_abis]`. This ensures that the compiler uses the correct -// ABI for functions like `extern "C" fn takes_va_list(va: VaList<'_>)` by passing `va` indirectly. +// There are currently three flavors of how a C `va_list` is implemented for +// targets that Rust supports: +// +// - `va_list` is an opaque pointer +// - `va_list` is a struct +// - `va_list` is a single-element array, containing a struct +// +// The opaque pointer approach is the simplest to implement: the pointer just +// points to an array of arguments on the caller's stack. +// +// The struct and single-element array variants are more complex, but +// potentially more efficient because the additional state makes it +// possible to pass variadic arguments via registers. +// +// The Rust `VaList` type is ABI-compatible with the C `va_list`. +// The struct and pointer cases straightforwardly map to their Rust equivalents, +// but the single-element array case is special: in C, this type is subject to +// array-to-pointer decay. +// +// The `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute is used to match +// the pointer decay behavior in Rust, while otherwise matching Rust semantics. +// This attribute ensures that the compiler uses the correct ABI for functions +// like `extern "C" fn takes_va_list(va: VaList<'_>)` by passing `va` indirectly. crate::cfg_select! { all( target_arch = "aarch64", @@ -20,8 +38,9 @@ crate::cfg_select! { not(target_os = "uefi"), not(windows), ) => { - /// AArch64 ABI implementation of a `va_list`. See the - /// [AArch64 Procedure Call Standard] for more details. + /// AArch64 ABI implementation of a `va_list`. + /// + /// See the [AArch64 Procedure Call Standard] for more details. /// /// [AArch64 Procedure Call Standard]: /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf @@ -37,6 +56,12 @@ crate::cfg_select! { } all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)) => { /// PowerPC ABI implementation of a `va_list`. + /// + /// See the [LLVM source] and [GCC header] for more details. + /// + /// [LLVM source]: + /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L4089-L4111 + /// [GCC header]: https://web.mit.edu/darwin/src/modules/gcc/gcc/ginclude/va-ppc.h #[repr(C)] #[derive(Debug)] #[rustc_pass_indirectly_in_non_rustic_abis] @@ -50,6 +75,11 @@ crate::cfg_select! { } target_arch = "s390x" => { /// s390x ABI implementation of a `va_list`. + /// + /// See the [S/390x ELF Application Binary Interface Supplement] for more details. + /// + /// [S/390x ELF Application Binary Interface Supplement]: + /// https://docs.google.com/gview?embedded=true&url=https://github.com/IBM/s390x-abi/releases/download/v1.7/lzsabi_s390x.pdf #[repr(C)] #[derive(Debug)] #[rustc_pass_indirectly_in_non_rustic_abis] @@ -61,7 +91,12 @@ crate::cfg_select! { } } all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)) => { - /// x86_64 ABI implementation of a `va_list`. + /// x86_64 System V ABI implementation of a `va_list`. + /// + /// See the [System V AMD64 ABI] for more details. + /// + /// [System V AMD64 ABI]: + /// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf #[repr(C)] #[derive(Debug)] #[rustc_pass_indirectly_in_non_rustic_abis] @@ -74,6 +109,11 @@ crate::cfg_select! { } target_arch = "xtensa" => { /// Xtensa ABI implementation of a `va_list`. + /// + /// See the [LLVM source] for more details. + /// + /// [LLVM source]: + /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1211-L1215 #[repr(C)] #[derive(Debug)] #[rustc_pass_indirectly_in_non_rustic_abis] @@ -88,6 +128,7 @@ crate::cfg_select! { // // - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599) // - windows + // - powerpc64 & powerpc64le // - uefi // - any other target for which we don't specify the `VaListInner` above //