Skip to content

Commit 787cb68

Browse files
authored
Rollup merge of #122461 - the8472:fix-step-forward-unchecked, r=Amanieu
fix unsoundness in Step::forward_unchecked for signed integers Fixes #122420 ```rust pub fn foo(a: i8, b: u8) -> i8 { unsafe { a.checked_add_unsigned(b).unwrap_unchecked() } } ``` still compiles down to a single arithmetic instruction ([godbolt](https://rust.godbolt.org/z/qsd3xYWfE)). But we may be losing some loop optimizations if llvm can no longer easily derive that it's a finite counted loop from the no-wrapping flags.
2 parents 2b1cd91 + d3cab9f commit 787cb68

File tree

3 files changed

+34
-3
lines changed

3 files changed

+34
-3
lines changed

library/core/src/iter/range.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,25 @@ pub trait Step: Clone + PartialOrd + Sized {
184184
}
185185
}
186186

187-
// These are still macro-generated because the integer literals resolve to different types.
188-
macro_rules! step_identical_methods {
187+
// Separate impls for signed ranges because the distance within a signed range can be larger
188+
// than the signed::MAX value. Therefore `as` casting to the signed type would be incorrect.
189+
macro_rules! step_signed_methods {
190+
($unsigned: ty) => {
191+
#[inline]
192+
unsafe fn forward_unchecked(start: Self, n: usize) -> Self {
193+
// SAFETY: the caller has to guarantee that `start + n` doesn't overflow.
194+
unsafe { start.checked_add_unsigned(n as $unsigned).unwrap_unchecked() }
195+
}
196+
197+
#[inline]
198+
unsafe fn backward_unchecked(start: Self, n: usize) -> Self {
199+
// SAFETY: the caller has to guarantee that `start - n` doesn't overflow.
200+
unsafe { start.checked_sub_unsigned(n as $unsigned).unwrap_unchecked() }
201+
}
202+
};
203+
}
204+
205+
macro_rules! step_unsigned_methods {
189206
() => {
190207
#[inline]
191208
unsafe fn forward_unchecked(start: Self, n: usize) -> Self {
@@ -198,7 +215,12 @@ macro_rules! step_identical_methods {
198215
// SAFETY: the caller has to guarantee that `start - n` doesn't overflow.
199216
unsafe { start.unchecked_sub(n as Self) }
200217
}
218+
};
219+
}
201220

221+
// These are still macro-generated because the integer literals resolve to different types.
222+
macro_rules! step_identical_methods {
223+
() => {
202224
#[inline]
203225
#[allow(arithmetic_overflow)]
204226
#[rustc_inherit_overflow_checks]
@@ -239,6 +261,7 @@ macro_rules! step_integer_impls {
239261
#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
240262
impl Step for $u_narrower {
241263
step_identical_methods!();
264+
step_unsigned_methods!();
242265

243266
#[inline]
244267
fn steps_between(start: &Self, end: &Self) -> Option<usize> {
@@ -271,6 +294,7 @@ macro_rules! step_integer_impls {
271294
#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
272295
impl Step for $i_narrower {
273296
step_identical_methods!();
297+
step_signed_methods!($u_narrower);
274298

275299
#[inline]
276300
fn steps_between(start: &Self, end: &Self) -> Option<usize> {
@@ -335,6 +359,7 @@ macro_rules! step_integer_impls {
335359
#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
336360
impl Step for $u_wider {
337361
step_identical_methods!();
362+
step_unsigned_methods!();
338363

339364
#[inline]
340365
fn steps_between(start: &Self, end: &Self) -> Option<usize> {
@@ -360,6 +385,7 @@ macro_rules! step_integer_impls {
360385
#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
361386
impl Step for $i_wider {
362387
step_identical_methods!();
388+
step_signed_methods!($u_wider);
363389

364390
#[inline]
365391
fn steps_between(start: &Self, end: &Self) -> Option<usize> {

library/core/tests/iter/range.rs

+5
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,11 @@ fn test_range_advance_by() {
325325
assert_eq!(Ok(()), r.advance_back_by(usize::MAX));
326326

327327
assert_eq!((r.start, r.end), (0u128 + usize::MAX as u128, u128::MAX - usize::MAX as u128));
328+
329+
// issue 122420, Step::forward_unchecked was unsound for signed integers
330+
let mut r = -128i8..127;
331+
assert_eq!(Ok(()), r.advance_by(200));
332+
assert_eq!(r.next(), Some(72));
328333
}
329334

330335
#[test]
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
The loop took around 7s
1+
The loop took around 12s

0 commit comments

Comments
 (0)