Skip to content

Make saturating_add and saturating_sub const functions #58246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 12, 2019
113 changes: 93 additions & 20 deletions src/libcore/num/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,17 +882,38 @@ $EndFeature, "
```"),
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[cfg(stage0)]
pub fn saturating_add(self, rhs: Self) -> Self {
#[cfg(stage0)]
match self.checked_add(rhs) {
Some(x) => x,
None if rhs >= 0 => Self::max_value(),
None => Self::min_value(),
}
#[cfg(not(stage0))]
{
intrinsics::saturating_add(self, rhs)
}
}

}

doc_comment! {
concat!("Saturating integer addition. Computes `self + rhs`, saturating at the numeric
bounds instead of overflowing.

# Examples

Basic usage:

```
", $Feature, "assert_eq!(100", stringify!($SelfT), ".saturating_add(1), 101);
assert_eq!(", stringify!($SelfT), "::max_value().saturating_add(100), ", stringify!($SelfT),
"::max_value());",
$EndFeature, "
```"),

#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_saturating_int_methods")]
#[inline]
#[cfg(not(stage0))]
pub const fn saturating_add(self, rhs: Self) -> Self {
intrinsics::saturating_add(self, rhs)
}
}

Expand All @@ -912,17 +933,36 @@ $EndFeature, "
```"),
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[cfg(stage0)]
pub fn saturating_sub(self, rhs: Self) -> Self {
#[cfg(stage0)]
match self.checked_sub(rhs) {
Some(x) => x,
None if rhs >= 0 => Self::min_value(),
None => Self::max_value(),
}
#[cfg(not(stage0))]
{
intrinsics::saturating_sub(self, rhs)
}
}
}

doc_comment! {
concat!("Saturating integer subtraction. Computes `self - rhs`, saturating at the
numeric bounds instead of overflowing.

# Examples

Basic usage:

```
", $Feature, "assert_eq!(100", stringify!($SelfT), ".saturating_sub(127), -27);
assert_eq!(", stringify!($SelfT), "::min_value().saturating_sub(100), ", stringify!($SelfT),
"::min_value());",
$EndFeature, "
```"),
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_saturating_int_methods")]
#[inline]
#[cfg(not(stage0))]
pub const fn saturating_sub(self, rhs: Self) -> Self {
intrinsics::saturating_sub(self, rhs)
}
}

Expand Down Expand Up @@ -2753,16 +2793,33 @@ assert_eq!(200u8.saturating_add(127), 255);", $EndFeature, "
```"),
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[cfg(stage0)]
pub fn saturating_add(self, rhs: Self) -> Self {
#[cfg(stage0)]
match self.checked_add(rhs) {
Some(x) => x,
None => Self::max_value(),
}
#[cfg(not(stage0))]
{
intrinsics::saturating_add(self, rhs)
}
}
}

doc_comment! {
concat!("Saturating integer addition. Computes `self + rhs`, saturating at
the numeric bounds instead of overflowing.

# Examples

Basic usage:

```
", $Feature, "assert_eq!(100", stringify!($SelfT), ".saturating_add(1), 101);
assert_eq!(200u8.saturating_add(127), 255);", $EndFeature, "
```"),

#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[cfg(not(stage0))]
pub const fn saturating_add(self, rhs: Self) -> Self {
intrinsics::saturating_add(self, rhs)
}
}

Expand All @@ -2780,16 +2837,32 @@ assert_eq!(13", stringify!($SelfT), ".saturating_sub(127), 0);", $EndFeature, "
```"),
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[cfg(stage0)]
pub fn saturating_sub(self, rhs: Self) -> Self {
#[cfg(stage0)]
match self.checked_sub(rhs) {
Some(x) => x,
None => Self::min_value(),
}
#[cfg(not(stage0))]
{
intrinsics::saturating_sub(self, rhs)
}
}
}

doc_comment! {
concat!("Saturating integer subtraction. Computes `self - rhs`, saturating
at the numeric bounds instead of overflowing.

# Examples

Basic usage:

```
", $Feature, "assert_eq!(100", stringify!($SelfT), ".saturating_sub(27), 73);
assert_eq!(13", stringify!($SelfT), ".saturating_sub(127), 0);", $EndFeature, "
```"),
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[cfg(not(stage0))]
pub const fn saturating_sub(self, rhs: Self) -> Self {
intrinsics::saturating_sub(self, rhs)
}
}

Expand Down
38 changes: 37 additions & 1 deletion src/librustc_mir/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use syntax::symbol::Symbol;
use rustc::ty;
use rustc::ty::layout::{LayoutOf, Primitive};
use rustc::ty::layout::{LayoutOf, Primitive, Size};
use rustc::mir::BinOp;
use rustc::mir::interpret::{
EvalResult, EvalErrorKind, Scalar,
Expand Down Expand Up @@ -122,6 +122,42 @@ impl<'a, 'mir, 'tcx, M: Machine<'a, 'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M>
self.binop_with_overflow(bin_op, lhs, rhs, dest)?;
}
}
"saturating_add" | "saturating_sub" => {
let l = self.read_immediate(args[0])?;
let r = self.read_immediate(args[1])?;
let is_add = intrinsic_name == "saturating_add";
let (val, overflowed) = self.binary_op_imm(if is_add {
BinOp::Add
} else {
BinOp::Sub
}, l, r)?;
let val = if overflowed {
// For signed ints the saturated value depends on the
// sign of the first term
let first_term: u128 = l.to_scalar()?.to_bits(l.layout.size)?;
let num_bits = l.layout.size.bits();
if l.layout.abi.is_signed() {
if first_term & (1 << (num_bits-1)) == 0 { // first term is positive
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it cannot overflow on the negative side because the smallest possible things we could reach here (for u8, similar for other types) are "0 - 127" and "0 + -128", and both are fine. Correct?

Scalar::from_uint((1u128 << (num_bits - 1)) - 1, // max positive
Size::from_bits(num_bits))
} else { // first term is negative
// max negative
Scalar::from_uint(1u128 << (num_bits - 1), Size::from_bits(num_bits))
}
} else { // unsigned
if is_add {
// max unsigned
Scalar::from_uint(u128::max_value() >> (128 - num_bits),
Size::from_bits(num_bits))
} else { // underflow to 0
Scalar::from_uint(0u128, Size::from_bits(num_bits))
}
}
} else {
val
};
self.write_scalar(val, dest)?;
}
"unchecked_shl" | "unchecked_shr" => {
let l = self.read_immediate(args[0])?;
let r = self.read_immediate(args[1])?;
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_mir/transform/qualify_consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,8 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
| "add_with_overflow"
| "sub_with_overflow"
| "mul_with_overflow"
| "saturating_add"
| "saturating_sub"
// no need to check feature gates, intrinsics are only callable
// from the libstd or with forever unstable feature gates
=> is_const_fn = true,
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_mir/transform/qualify_min_const_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ fn is_intrinsic_whitelisted(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> bool
| "overflowing_add" // ~> .wrapping_add
| "overflowing_sub" // ~> .wrapping_sub
| "overflowing_mul" // ~> .wrapping_mul
| "saturating_add" // ~> .saturating_add
| "saturating_sub" // ~> .saturating_sub
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @rust-lang/lang

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm also

| "unchecked_shl" // ~> .wrapping_shl
| "unchecked_shr" // ~> .wrapping_shr
| "rotate_left" // ~> .rotate_left
Expand Down
33 changes: 33 additions & 0 deletions src/test/run-pass/const-int-saturating-arith.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#![feature(const_saturating_int_methods)]

const INT_U32_NO: u32 = (42 as u32).saturating_add(2);
const INT_U32: u32 = u32::max_value().saturating_add(1);
const INT_U128: u128 = u128::max_value().saturating_add(1);
const INT_I128: i128 = i128::max_value().saturating_add(1);
const INT_I128_NEG: i128 = i128::min_value().saturating_add(-1);

const INT_U32_NO_SUB: u32 = (42 as u32).saturating_sub(2);
const INT_U32_SUB: u32 = (1 as u32).saturating_sub(2);
const INT_I32_NO_SUB: i32 = (-42 as i32).saturating_sub(2);
const INT_I32_NEG_SUB: i32 = i32::min_value().saturating_sub(1);
const INT_I32_POS_SUB: i32 = i32::max_value().saturating_sub(-1);
const INT_U128_SUB: u128 = (0 as u128).saturating_sub(1);
const INT_I128_NEG_SUB: i128 = i128::min_value().saturating_sub(1);
const INT_I128_POS_SUB: i128 = i128::max_value().saturating_sub(-1);

fn main() {
assert_eq!(INT_U32_NO, 44);
assert_eq!(INT_U32, u32::max_value());
assert_eq!(INT_U128, u128::max_value());
assert_eq!(INT_I128, i128::max_value());
assert_eq!(INT_I128_NEG, i128::min_value());

assert_eq!(INT_U32_NO_SUB, 40);
assert_eq!(INT_U32_SUB, 0);
assert_eq!(INT_I32_NO_SUB, -44);
assert_eq!(INT_I32_NEG_SUB, i32::min_value());
assert_eq!(INT_I32_POS_SUB, i32::max_value());
assert_eq!(INT_U128_SUB, 0);
assert_eq!(INT_I128_NEG_SUB, i128::min_value());
assert_eq!(INT_I128_POS_SUB, i128::max_value());
}