-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
[MIR] Implement overflow checking #33255
Conversation
Add, Sub, Mul, Shl, and Shr are checked using a new Rvalue: CheckedBinaryOp, while Div, Rem and Neg are handled with explicit checks in the MIR.
Factors out the common pattern across the several places that do arithmetic checks
(rust_highfive has picked a reviewer for you, use r? to override) |
This currently breaks documentation generation for libc, as it makes the panic lang item required during MIR construction. /cc @alexcrichton |
ast::IntTy::I64 => ConstInt::I64(std::i64::MIN), | ||
ast::IntTy::Is => { | ||
let int_ty = self.hir.tcx().sess.target.int_type; | ||
let min = match int_ty { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can generate the correct ConstIsize
variant directly, without casting to i64
and going through ConstIsize::new
.
let val = match int_ty {
ast::IntTy::I32 => ConstIsize::Is32(std::i32::MIN),
ast::IntTy::I64 => ConstIsize::Is64(std::i64::MIN),
_ => bug!(),
}
Ah that's fine, I can fix that at some point upstream for libc |
let after = f(self, then_block); | ||
|
||
// If the returned block isn't terminated, add a branch to the "else" | ||
// block |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of this hack? Does it always result in correct code in case of bugs (when somebody forgets to set a terminator)? If not, then lack of the terminator should just stay a bug, as it currently is.
Have you considered a
? Well, I guess there isn’t much difference, the current approach also looks fine. |
This is simpler to work with than `with_cond`.
This branch shouldn't be hit so if it is, it's probably a mistake.
}); | ||
|
||
block.and(Rvalue::Use(Operand::Consume(val))) | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn’t this branch ignore the check_overflow
? Are we checking for division unconditionally in current trans?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me it seems like it would be cleaner to have something like
if !self.is_overflow_checked() || !op.is_checkable() || !ty.is_integral() { return block.and(Rvalue::BinaryOp(...) }
at the top, and then only do the branching based on the operator elsewhere in this function.
Does this generate the LLVM intrinsics or explicit checks? The LLVM documentation for frontends warns against using the intrinsics unless necessary. |
@drbo it does indeed emit intrinsics. I do not see how one would check for overflow after the fact without intrinsics, and I’m doubtful implementing overflow detection for, say multiplication would be easy without intrinsics or inline assembly. Any ideas? |
@drbo that's almost certainly referring to the fact that you shouldn't use the intrinsics unless you actually need to check the result, which is the case here. If we don't check the result, we don't use the intrinsics, it's not like we emit the intrinsics for all arithmetic, just checked arithmetic. Also, the impact on optimisation isn't a strong factor here, since most of the time chceked arithmetic is used because optimisations haven't been enabled. |
|
||
let (of_block, ok_block) = this.build_cond_br(block, expr_span, | ||
Operand::Consume(is_min)); | ||
this.panic(of_block, "attempted to negate with overflow", expr_span); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine we might want some more structured form of terminators here for these special checks (but that needn't derail this PR).
@Aatch ok I reviewed the PR. The code all makes sense and basically looks good to me. I have one question though, I thought you had mentioned some kind of change to MIR to make something analogous to LLVM's "extract-value"? I didn't see anything like that? Otherwise, it seems like we have to settle the question of what to do about inlined code. I keep going back and forth. Finally, I suspect we are going to want mildly higher-level terminators that are clearly "automatically inserted" so that things like constant propagation and so forth can recognize them, but I'm not sure about that. |
Ah, just saw this comment saying that the codegen part was deferred. |
The libs team discussed the ramifications of this on the standard library today, and the conclusion was generally positive. We felt that if the standard library wants overflow checks it should write those overflow checks (e.g. with @eddyb, could you clarify where this is relied on for We did have some hesitation, however, at the consideration of this for the I personally interpret that as expecting code like this to panic: fn add<T: Add>(a: T, b: T) -> T {
a + b
}
fn main() {
add(200_u8, 200_u8);
} I think with this PR, however, it will not panic? That'll get dispatched to the |
@alexcrichton |
Debugopt builds are a thing, you know. I would not expect anyone to run their big testsuite on a non-opt build. The We may want a |
@alexcrichton Well, the comments indicate that the behavior of @arielb1 The "short-cutting" only works when the types are known, though. If trait selection picks the impls in |
Comments notwithstanding, I feel like having the ability to have a On Thu, May 05, 2016 at 09:13:37AM -0700, Eduard-Mihai Burtescu wrote:
|
☔ The latest upstream changes (presumably #33130) made this pull request unmergeable. Please resolve the merge conflicts. |
@Aatch Have you tried rebasing? Do you want me to do it? |
@eddyb what's the story with this branch? I think we achieved a rough consensus that we should maintain existing semantics -- incoherent as they are -- and revisit later? |
I'm going to close this PR pending @eddyb's rebase etc. |
[MIR] Implement overflow checking The initial set of changes is from @Aatch's #33255 PR, rebased on master, plus: Added an `Assert` terminator to MIR, to simplify working with overflow and bounds checks. With this terminator, error cases can be accounted for directly, instead of looking for lang item calls. It also keeps the MIR slimmer, with no extra explicit blocks for the actual panic calls. Warnings can be produced when the `Assert` is known to always panic at runtime, e.g.: ```rust warning: index out of bounds: the len is 1 but the index is 3 --> <anon>:1:14 1 |> fn main() { &[std::io::stdout()][3]; } |> ^^^^^^^^^^^^^^^^^^^^^^ ``` Generalized the `OperandValue::FatPtr` optimization to any aggregate pair of immediates. This allows us to generate the same IR for overflow checks as old trans, not something worse. For example, addition on `i16` calls `llvm.sadd.with.overflow.i16`, which returns `{i16, i1}`. However, the Rust type `(i16, bool)`, has to be `{i16, i8}`, only an immediate `bool` is `i1`. But if we split the pair into an `i16` and an `i1`, we can pass them around as such for free. The latest addition is a rebase of #34054, updated to work for pairs too. Closes #34054, fixes #33873. Last but not least, the `#[rustc_inherit_overflow_checks]` attribute was introduced to control the overflow checking behavior of generic or `#[inline]` functions, when translated in another crate. It is **not** intended to be used by crates other than `libcore`, which is in the unusual position of being distributed as only an optimized build with no checks, even when used from debug mode. Before MIR-based translation, this worked out fine, as the decision for overflow was made at translation time, in the crate being compiled, but MIR stored in `rlib` has to contain the checks. To avoid always generating the checks and slowing everything down, a decision was made to use an attribute in the few spots of `libcore` that need it (see #33255 for previous discussion): * `core::ops::{Add, Sub, Mul, Neg, Shl, Shr}` implementations for integers, which have `#[inline]` methods and can be used in generic abstractions from other crates * `core::ops::{Add, Sub, Mul, Neg, Shl, Shr}Assign` same as above, for augmented assignment * `pow` and `abs` methods on integers, which intentionally piggy-back on built-in multiplication and negation, respectively, to get overflow checks * `core::iter::{Iterator, Chain, Peek}::count` and `core::iter::Enumerate::{next, nth}`, also documented as panicking on overflow, from addition, counting elements of an iterator in an `usize`
Implements overflow checking in the MIR. Also adds checking for
x/0
,x%0
,MIN/-1
andMIN%-1
.Fixes #29769