Skip to content
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

Implement arithmetic overflow changes #20795

Closed
wants to merge 4 commits into from

Conversation

Aatch
Copy link
Contributor

@Aatch Aatch commented Jan 9, 2015

Implements most of rust-lang/rfcs#560. Errors that I encountered from the checks during building were fixed.

The checks for division, remainder and bit-shifting have not been implemented yet.

cc @nikomatsakis

emberian and others added 3 commits January 9, 2015 16:41
Adds overflow checking to integer addition, multiplication, and subtraction
when `-Z force-overflow-checks` is true, or if `--cfg ndebug` is not passed to
the compiler. On overflow, it panics with `arithmatic operation overflowed`.
Also adds `overflowing_add`, `overflowing_sub`, and `overflowing_mul`
intrinsics for doing unchecked arithmatic.

[breaking-change]
Adds a Wrapping<T> wrapper type and a WrappingOps trait for allowing
people to explictly opt-in to wrapping integer arithmetic.

This commit currently disables overflow checking in the build due to
cases of expected overflow triggering the panic.
Many of the core rust libraries have places that rely on integer
wrapping behaviour. These places have been altered to use the wrapping_*
methods:

 * core::hash::sip - A number of macros
 * core::str - The `maximal_suffix` method in `TwoWaySearcher`
 * rustc::util::nodemap - Implementation of FnvHash
 * rustc_back::sha2 - A number of macros and other places
 * rand::isaac - Isaac64Rng, changed to use the Wrapping helper type

Some places had "benign" underflow. This is when underflow or overflow
occurs, but the unspecified value is not used due to other conditions.

 * collections::bit::Bitv - underflow when `self.nbits` is zero.
 * collections::hash::{map,table} - Underflow when searching an empty
   table. Did cause undefined behaviour in this case due to an
   out-of-bounds ptr::offset based on the underflowed index. However the
   resulting pointers would never be read from.
 * syntax::ext::deriving::encodable - Underflow when calculating the
   index of the last field in a variant with no fields.

These cases were altered to avoid the underflow, often by moving the
underflowing operation to a place where underflow could not happen.

There was one case that relied on the fact that unsigned arithmetic and
two's complement arithmetic are identical with wrapping semantics. This
was changed to use the wrapping_* methods.

Finally, the calculation of variant discriminants could overflow if the
preceeding discriminant was `U64_MAX`. The logic in `rustc::middle::ty`
for this was altered to avoid the overflow completely, while the
remaining places were changed to use wrapping methods. This is because
`rustc::middle::ty::enum_variants` now throws an error when the
calculated discriminant value overflows a `u64`.

This behaviour can be triggered by the following code:

```
enum Foo {
  A = U64_MAX,
  B
}
```

This commit also implements the remaining integer operators for
Wrapped<T>.
@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @nick29581 (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. The way Github handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see CONTRIBUTING.md for more information.

During my clean-up of rebase errors, I took the opportunity to implement
parse_opt_bool so that it isn't identical to parse_bool wrapped in
`Some`.

parse_opt_bool considers no value to be true, a value of 'y', 'yes' or
'on' to be true and 'n', 'no' or 'off' to be false. All other values are
an error.
#[cfg(not(stage0))]
extern "rust-intrinsic" {
/// Returns (a + b) mod 2^N, where N is the width of N in bits.
pub fn overflowing_add<T>(a: T, b: T) -> T;
Copy link
Member

Choose a reason for hiding this comment

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

Any reason these are overflowing_foo while the methods are wrapping_foo? I think wrapping_foo is a bit more descriptive, fwiw.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we ought to give them consistent names.

@brson
Copy link
Contributor

brson commented Jan 9, 2015

Thanks @Aatch and @cmr

// error-pattern:thread '<main>' panicked at 'arithmatic operation overflowed'

fn main() {
let x = 200u8 + 4u8;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be multiplication?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably, yes. Blame copy-paste. (And @cmr, since it's his code :P)

@nikomatsakis
Copy link
Contributor

This looks overall really nice. I'll give it a more detailed look soon.

@nrc nrc assigned nikomatsakis and unassigned nrc Jan 11, 2015
@nikomatsakis
Copy link
Contributor

Argh, slipped off my radar! I'm sorry about that. Tomorrow. (Please feel free to ping me.)

pub trait WrappingOps {
fn wrapping_add(self, rhs: Self) -> Self;
fn wrapping_sub(self, rhs: Self) -> Self;
fn wrapping_mul(self, rhs: Self) -> Self;
Copy link
Contributor

Choose a reason for hiding this comment

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

What about wrapping_div and wrapping_mod? (same with the intrinsics)

Copy link
Member

Choose a reason for hiding this comment

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

These two methods only make sense for f32 and f64 (since they will never overflow and already panic for division by 0)… which are not part of the checking scheme.

Copy link
Contributor

Choose a reason for hiding this comment

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

floats don't panic on div by zero. they return inf.

Copy link
Member

Choose a reason for hiding this comment

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

Err, I surely meant that integers’ division panics and never overflows when dividing.

@steveklabnik
Copy link
Member

This doesn't currently merge.

@nikomatsakis
Copy link
Contributor

Now that the RFC has been accepted, we can rebase, updated, and land this PR. @Aatch are you stll interested in pursuing that? Can you talk to me on IRC (I've looked for you a few times but not found you, bad timing I guess)?

@mahkoh
Copy link
Contributor

mahkoh commented Feb 11, 2015

This patch doesn't address atomic operations.

@mahkoh
Copy link
Contributor

mahkoh commented Feb 11, 2015

More precisely: If overflow caused by fetch_add or fetch_sub is detected the process must be aborted. Panic is not possible because other threads might already have consumed the value.

@nikomatsakis
Copy link
Contributor

@mahkoh and I talked briefly about atomics on IRC. The options seem to be:

  1. Just wrap. Document it, perhaps change name of methods.
  2. Abort process as @mahkoh suggests.
  3. Abort the thread, although the observed value may have been seen elsewhere.
  4. Wrap but return some indication of when wrapping occurred.
  5. Fallback to a CAS loop.

Each has its own downsides. The first option is obviously prone to programmer error, particularly if someone is naively using an atomic to achieve some kind of global counter. That said, atomics are fairly specialized, and perhaps it should be expected. (Note: If we don't wrap by default, then clearly we need explicit wrapping options whatever we do.)

The second process is heavy-handed. I am generally reluctant to abort an entire process as it is irrecoverable. However, this is only in debug mode -- but it impacts users who want to use checked overflowing even in optimized code, as aborting the process may not be an option for them.

Aborting the thread is insufficient in general since others may observe the values.

Another option is to wrap but return an enum that indicates if wrapping occurred. This seems to add some overhead and mainly serves to warn users that wrapping can occur, thus encouraging them to design a recovery strategy. Probably not very appealing.

Falling back to a CAS loop gives irregular performance similarly may impact users who wish to turn on checked overflow by default (even outside debug mode).

@nikomatsakis
Copy link
Contributor

Having given the question of atomics some more thought (and talked it over with @aturon), my feeling is that the best option is simply to clearly document the semantics of AtomicInt and friends as wrapping on overflow. Naturally this is something of a tradeoff. As I see it, using atomic ints and the like is somewhat advanced, and always rather isolated: it is much more reasonable to expect users to be aware of the possibility of overflow in that scenario, as compared to general arithmetic throughout the program (atomics are also much easier to audit). Finally, given that the recovery options are typically either drastic or expensive, there isn't really an appealing alternative.

@pnkfelix
Copy link
Member

@nikomatsakis maybe we should at least name them AtomicWrappingInt or something. (but I don't know if they're even marked stable at the moment).

@pnkfelix
Copy link
Member

closing in favor of rebased version here; #22532

@pnkfelix pnkfelix closed this Feb 19, 2015
bors added a commit that referenced this pull request Mar 3, 2015
Rebase and follow-through on work done by @cmr and @Aatch.

Implements most of rust-lang/rfcs#560. Errors encountered from the checks during building were fixed.

The checks for division, remainder and bit-shifting have not been implemented yet.

See also PR #20795

cc @Aatch ; cc @nikomatsakis
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.