Skip to content

Commit eeeb2cc

Browse files
committed
Allow for better optimizations of iterators for zero-sized types
Using regular pointer arithmetic to iterate collections of zero-sized types doesn't work, because we'd get the same pointer all the time. Our current solution is to convert the pointer to an integer, add an offset and then convert back, but this inhibits certain optimizations. What we should do instead is to convert the pointer to one that points to an i8*, and then use a LLVM GEP instructions without the inbounds flag to perform the pointer arithmetic. This allows to generate pointers that point outside allocated objects without causing UB (as long as you don't dereference them), and it wraps around using two's complement, i.e. it behaves exactly like the wrapping_* operations we're currently using, with the added benefit of LLVM being able to better optimize the resulting IR.
1 parent 579e319 commit eeeb2cc

File tree

6 files changed

+219
-46
lines changed

6 files changed

+219
-46
lines changed

src/libcollections/btree/node.rs

+68
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub use self::TraversalItem::*;
1919
use core::prelude::*;
2020

2121
use core::cmp::Ordering::{Greater, Less, Equal};
22+
#[cfg(not(stage0))]
23+
use core::intrinsics::arith_offset;
2224
use core::iter::Zip;
2325
use core::marker::PhantomData;
2426
use core::ops::{Deref, DerefMut, Index, IndexMut};
@@ -205,6 +207,7 @@ impl<T> RawItems<T> {
205207
RawItems::from_parts(slice.as_ptr(), slice.len())
206208
}
207209

210+
#[cfg(stage0)]
208211
unsafe fn from_parts(ptr: *const T, len: usize) -> RawItems<T> {
209212
if mem::size_of::<T>() == 0 {
210213
RawItems {
@@ -219,6 +222,22 @@ impl<T> RawItems<T> {
219222
}
220223
}
221224

225+
#[cfg(not(stage0))]
226+
unsafe fn from_parts(ptr: *const T, len: usize) -> RawItems<T> {
227+
if mem::size_of::<T>() == 0 {
228+
RawItems {
229+
head: ptr,
230+
tail: arith_offset(ptr as *const i8, len as isize) as *const T,
231+
}
232+
} else {
233+
RawItems {
234+
head: ptr,
235+
tail: ptr.offset(len as isize),
236+
}
237+
}
238+
}
239+
240+
#[cfg(stage0)]
222241
unsafe fn push(&mut self, val: T) {
223242
ptr::write(self.tail as *mut T, val);
224243

@@ -228,11 +247,23 @@ impl<T> RawItems<T> {
228247
self.tail = self.tail.offset(1);
229248
}
230249
}
250+
251+
#[cfg(not(stage0))]
252+
unsafe fn push(&mut self, val: T) {
253+
ptr::write(self.tail as *mut T, val);
254+
255+
if mem::size_of::<T>() == 0 {
256+
self.tail = arith_offset(self.tail as *const i8, 1) as *const T;
257+
} else {
258+
self.tail = self.tail.offset(1);
259+
}
260+
}
231261
}
232262

233263
impl<T> Iterator for RawItems<T> {
234264
type Item = T;
235265

266+
#[cfg(stage0)]
236267
fn next(&mut self) -> Option<T> {
237268
if self.head == self.tail {
238269
None
@@ -250,9 +281,29 @@ impl<T> Iterator for RawItems<T> {
250281
}
251282
}
252283
}
284+
285+
#[cfg(not(stage0))]
286+
fn next(&mut self) -> Option<T> {
287+
if self.head == self.tail {
288+
None
289+
} else {
290+
unsafe {
291+
let ret = Some(ptr::read(self.head));
292+
293+
if mem::size_of::<T>() == 0 {
294+
self.head = arith_offset(self.head as *const i8, 1) as *const T;
295+
} else {
296+
self.head = self.head.offset(1);
297+
}
298+
299+
ret
300+
}
301+
}
302+
}
253303
}
254304

255305
impl<T> DoubleEndedIterator for RawItems<T> {
306+
#[cfg(stage0)]
256307
fn next_back(&mut self) -> Option<T> {
257308
if self.head == self.tail {
258309
None
@@ -268,6 +319,23 @@ impl<T> DoubleEndedIterator for RawItems<T> {
268319
}
269320
}
270321
}
322+
323+
#[cfg(not(stage0))]
324+
fn next_back(&mut self) -> Option<T> {
325+
if self.head == self.tail {
326+
None
327+
} else {
328+
unsafe {
329+
if mem::size_of::<T>() == 0 {
330+
self.tail = arith_offset(self.tail as *const i8, -1) as *const T;
331+
} else {
332+
self.tail = self.tail.offset(-1);
333+
}
334+
335+
Some(ptr::read(self.tail))
336+
}
337+
}
338+
}
271339
}
272340

273341
impl<T> Drop for RawItems<T> {

src/libcollections/vec.rs

+70
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ use core::cmp::Ordering;
6666
use core::fmt;
6767
use core::hash::{self, Hash};
6868
use core::intrinsics::assume;
69+
#[cfg(not(stage0))]
70+
use core::intrinsics::arith_offset;
6971
use core::iter::{repeat, FromIterator};
7072
use core::marker::PhantomData;
7173
use core::mem;
@@ -1527,6 +1529,7 @@ impl<T> IntoIterator for Vec<T> {
15271529
/// }
15281530
/// ```
15291531
#[inline]
1532+
#[cfg(stage0)]
15301533
fn into_iter(self) -> IntoIter<T> {
15311534
unsafe {
15321535
let ptr = *self.ptr;
@@ -1542,6 +1545,24 @@ impl<T> IntoIterator for Vec<T> {
15421545
IntoIter { allocation: ptr, cap: cap, ptr: begin, end: end }
15431546
}
15441547
}
1548+
1549+
#[inline]
1550+
#[cfg(not(stage0))]
1551+
fn into_iter(self) -> IntoIter<T> {
1552+
unsafe {
1553+
let ptr = *self.ptr;
1554+
assume(!ptr.is_null());
1555+
let cap = self.cap;
1556+
let begin = ptr as *const T;
1557+
let end = if mem::size_of::<T>() == 0 {
1558+
arith_offset(ptr as *const i8, self.len() as isize) as *const T
1559+
} else {
1560+
ptr.offset(self.len() as isize) as *const T
1561+
};
1562+
mem::forget(self);
1563+
IntoIter { allocation: ptr, cap: cap, ptr: begin, end: end }
1564+
}
1565+
}
15451566
}
15461567

15471568
#[stable(feature = "rust1", since = "1.0.0")]
@@ -1746,6 +1767,7 @@ impl<T> Iterator for IntoIter<T> {
17461767
type Item = T;
17471768

17481769
#[inline]
1770+
#[cfg(stage0)]
17491771
fn next(&mut self) -> Option<T> {
17501772
unsafe {
17511773
if self.ptr == self.end {
@@ -1769,6 +1791,31 @@ impl<T> Iterator for IntoIter<T> {
17691791
}
17701792
}
17711793

1794+
#[inline]
1795+
#[cfg(not(stage0))]
1796+
fn next(&mut self) -> Option<T> {
1797+
unsafe {
1798+
if self.ptr == self.end {
1799+
None
1800+
} else {
1801+
if mem::size_of::<T>() == 0 {
1802+
// purposefully don't use 'ptr.offset' because for
1803+
// vectors with 0-size elements this would return the
1804+
// same pointer.
1805+
self.ptr = arith_offset(self.ptr as *const i8, 1) as *const T;
1806+
1807+
// Use a non-null pointer value
1808+
Some(ptr::read(EMPTY as *mut T))
1809+
} else {
1810+
let old = self.ptr;
1811+
self.ptr = self.ptr.offset(1);
1812+
1813+
Some(ptr::read(old))
1814+
}
1815+
}
1816+
}
1817+
}
1818+
17721819
#[inline]
17731820
fn size_hint(&self) -> (usize, Option<usize>) {
17741821
let diff = (self.end as usize) - (self.ptr as usize);
@@ -1786,6 +1833,7 @@ impl<T> Iterator for IntoIter<T> {
17861833
#[stable(feature = "rust1", since = "1.0.0")]
17871834
impl<T> DoubleEndedIterator for IntoIter<T> {
17881835
#[inline]
1836+
#[cfg(stage0)]
17891837
fn next_back(&mut self) -> Option<T> {
17901838
unsafe {
17911839
if self.end == self.ptr {
@@ -1805,6 +1853,28 @@ impl<T> DoubleEndedIterator for IntoIter<T> {
18051853
}
18061854
}
18071855
}
1856+
1857+
#[inline]
1858+
#[cfg(not(stage0))]
1859+
fn next_back(&mut self) -> Option<T> {
1860+
unsafe {
1861+
if self.end == self.ptr {
1862+
None
1863+
} else {
1864+
if mem::size_of::<T>() == 0 {
1865+
// See above for why 'ptr.offset' isn't used
1866+
self.end = arith_offset(self.end as *const i8, -1) as *const T;
1867+
1868+
// Use a non-null pointer value
1869+
Some(ptr::read(EMPTY as *mut T))
1870+
} else {
1871+
self.end = self.end.offset(-1);
1872+
1873+
Some(ptr::read(mem::transmute(self.end)))
1874+
}
1875+
}
1876+
}
1877+
}
18081878
}
18091879

18101880
#[stable(feature = "rust1", since = "1.0.0")]

src/libcore/intrinsics.rs

+14
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,20 @@ extern "rust-intrinsic" {
283283
/// returned value will result in undefined behavior.
284284
pub fn offset<T>(dst: *const T, offset: isize) -> *const T;
285285

286+
/// Calculates the offset from a pointer, potentially wrapping.
287+
///
288+
/// This is implemented as an intrinsic to avoid converting to and from an
289+
/// integer, since the conversion inhibits certain optimizations.
290+
///
291+
/// # Safety
292+
///
293+
/// Unlike the `offset` intrinsic, this intrinsic does not restrict the
294+
/// resulting pointer to point into or one byte past the end of an allocated
295+
/// object, and it wraps with two's complement arithmetic. The resulting
296+
/// value is not necessarily valid to be used to actually access memory.
297+
#[cfg(not(stage0))]
298+
pub fn arith_offset<T>(dst: *const T, offset: isize) -> *const T;
299+
286300
/// Copies `count * size_of<T>` bytes from `src` to `dst`. The source
287301
/// and destination may *not* overlap.
288302
///

0 commit comments

Comments
 (0)