|
10 | 10 |
|
11 | 11 | //! See docs in build/expr/mod.rs
|
12 | 12 |
|
| 13 | +use std; |
| 14 | + |
13 | 15 | use rustc_data_structures::fnv::FnvHashMap;
|
14 | 16 |
|
15 | 17 | use build::{BlockAnd, BlockAndExtension, Builder};
|
16 | 18 | use build::expr::category::{Category, RvalueFunc};
|
17 | 19 | use hair::*;
|
| 20 | +use rustc_const_math::{ConstInt, ConstIsize}; |
| 21 | +use rustc::middle::const_val::ConstVal; |
| 22 | +use rustc::ty; |
18 | 23 | use rustc::mir::repr::*;
|
| 24 | +use syntax::ast; |
| 25 | +use syntax::codemap::Span; |
19 | 26 |
|
20 | 27 | impl<'a,'tcx> Builder<'a,'tcx> {
|
21 | 28 | /// Compile `expr`, yielding an rvalue.
|
@@ -66,10 +73,34 @@ impl<'a,'tcx> Builder<'a,'tcx> {
|
66 | 73 | ExprKind::Binary { op, lhs, rhs } => {
|
67 | 74 | let lhs = unpack!(block = this.as_operand(block, lhs));
|
68 | 75 | let rhs = unpack!(block = this.as_operand(block, rhs));
|
69 |
| - block.and(Rvalue::BinaryOp(op, lhs, rhs)) |
| 76 | + this.build_binary_op(block, op, expr_span, expr.ty, |
| 77 | + lhs, rhs) |
70 | 78 | }
|
71 | 79 | ExprKind::Unary { op, arg } => {
|
72 | 80 | let arg = unpack!(block = this.as_operand(block, arg));
|
| 81 | + // Check for -MIN on signed integers |
| 82 | + if op == UnOp::Neg && this.check_overflow() && expr.ty.is_signed() { |
| 83 | + let bool_ty = this.hir.bool_ty(); |
| 84 | + |
| 85 | + let minval = this.minval_literal(expr_span, expr.ty); |
| 86 | + let is_min = this.temp(bool_ty); |
| 87 | + |
| 88 | + this.cfg.push_assign(block, scope_id, expr_span, &is_min, |
| 89 | + Rvalue::BinaryOp(BinOp::Eq, arg.clone(), minval)); |
| 90 | + |
| 91 | + let of_block = this.cfg.start_new_block(); |
| 92 | + let ok_block = this.cfg.start_new_block(); |
| 93 | + |
| 94 | + this.cfg.terminate(block, scope_id, expr_span, |
| 95 | + TerminatorKind::If { |
| 96 | + cond: Operand::Consume(is_min), |
| 97 | + targets: (of_block, ok_block) |
| 98 | + }); |
| 99 | + |
| 100 | + this.panic(of_block, "attempted to negate with overflow", expr_span); |
| 101 | + |
| 102 | + block = ok_block; |
| 103 | + } |
73 | 104 | block.and(Rvalue::UnaryOp(op, arg))
|
74 | 105 | }
|
75 | 106 | ExprKind::Box { value, value_extents } => {
|
@@ -221,4 +252,166 @@ impl<'a,'tcx> Builder<'a,'tcx> {
|
221 | 252 | }
|
222 | 253 | }
|
223 | 254 | }
|
| 255 | + |
| 256 | + pub fn build_binary_op(&mut self, mut block: BasicBlock, op: BinOp, span: Span, ty: ty::Ty<'tcx>, |
| 257 | + lhs: Operand<'tcx>, rhs: Operand<'tcx>) -> BlockAnd<Rvalue<'tcx>> { |
| 258 | + let scope_id = self.innermost_scope_id(); |
| 259 | + let bool_ty = self.hir.bool_ty(); |
| 260 | + if self.check_overflow() && op.is_checkable() && ty.is_integral() { |
| 261 | + let result_tup = self.hir.tcx().mk_tup(vec![ty, bool_ty]); |
| 262 | + let result_value = self.temp(result_tup); |
| 263 | + |
| 264 | + self.cfg.push_assign(block, scope_id, span, |
| 265 | + &result_value, Rvalue::CheckedBinaryOp(op, |
| 266 | + lhs, |
| 267 | + rhs)); |
| 268 | + let val_fld = Field::new(0); |
| 269 | + let of_fld = Field::new(1); |
| 270 | + |
| 271 | + let val = result_value.clone().field(val_fld, ty); |
| 272 | + let of = result_value.field(of_fld, bool_ty); |
| 273 | + |
| 274 | + let success = self.cfg.start_new_block(); |
| 275 | + let failure = self.cfg.start_new_block(); |
| 276 | + |
| 277 | + self.cfg.terminate(block, scope_id, span, |
| 278 | + TerminatorKind::If { |
| 279 | + cond: Operand::Consume(of), |
| 280 | + targets: (failure, success) |
| 281 | + }); |
| 282 | + let msg = if op == BinOp::Shl || op == BinOp::Shr { |
| 283 | + "shift operation overflowed" |
| 284 | + } else { |
| 285 | + "arithmetic operation overflowed" |
| 286 | + }; |
| 287 | + self.panic(failure, msg, span); |
| 288 | + success.and(Rvalue::Use(Operand::Consume(val))) |
| 289 | + } else { |
| 290 | + if ty.is_integral() && (op == BinOp::Div || op == BinOp::Rem) { |
| 291 | + // Checking division and remainder is more complex, since we 1. always check |
| 292 | + // and 2. there are two possible failure cases, divide-by-zero and overflow. |
| 293 | + |
| 294 | + let (zero_msg, overflow_msg) = if op == BinOp::Div { |
| 295 | + ("attempted to divide by zero", |
| 296 | + "attempted to divide with overflow") |
| 297 | + } else { |
| 298 | + ("attempted remainder with a divisor of zero", |
| 299 | + "attempted remainder with overflow") |
| 300 | + }; |
| 301 | + |
| 302 | + // Check for / 0 |
| 303 | + let is_zero = self.temp(bool_ty); |
| 304 | + let zero = self.zero_literal(span, ty); |
| 305 | + self.cfg.push_assign(block, scope_id, span, &is_zero, |
| 306 | + Rvalue::BinaryOp(BinOp::Eq, rhs.clone(), zero)); |
| 307 | + |
| 308 | + let zero_block = self.cfg.start_new_block(); |
| 309 | + let ok_block = self.cfg.start_new_block(); |
| 310 | + |
| 311 | + self.cfg.terminate(block, scope_id, span, |
| 312 | + TerminatorKind::If { |
| 313 | + cond: Operand::Consume(is_zero), |
| 314 | + targets: (zero_block, ok_block) |
| 315 | + }); |
| 316 | + |
| 317 | + self.panic(zero_block, zero_msg, span); |
| 318 | + block = ok_block; |
| 319 | + |
| 320 | + // We only need to check for the overflow in one case: |
| 321 | + // MIN / -1, and only for signed values. |
| 322 | + if ty.is_signed() { |
| 323 | + let neg_1 = self.neg_1_literal(span, ty); |
| 324 | + let min = self.minval_literal(span, ty); |
| 325 | + |
| 326 | + let is_neg_1 = self.temp(bool_ty); |
| 327 | + let is_min = self.temp(bool_ty); |
| 328 | + let of = self.temp(bool_ty); |
| 329 | + |
| 330 | + // this does (rhs == -1) & (lhs == MIN). It could short-circuit instead |
| 331 | + |
| 332 | + self.cfg.push_assign(block, scope_id, span, &is_neg_1, |
| 333 | + Rvalue::BinaryOp(BinOp::Eq, rhs.clone(), neg_1)); |
| 334 | + self.cfg.push_assign(block, scope_id, span, &is_min, |
| 335 | + Rvalue::BinaryOp(BinOp::Eq, lhs.clone(), min)); |
| 336 | + |
| 337 | + let is_neg_1 = Operand::Consume(is_neg_1); |
| 338 | + let is_min = Operand::Consume(is_min); |
| 339 | + self.cfg.push_assign(block, scope_id, span, &of, |
| 340 | + Rvalue::BinaryOp(BinOp::BitAnd, is_neg_1, is_min)); |
| 341 | + |
| 342 | + let of_block = self.cfg.start_new_block(); |
| 343 | + let ok_block = self.cfg.start_new_block(); |
| 344 | + |
| 345 | + self.cfg.terminate(block, scope_id, span, |
| 346 | + TerminatorKind::If { |
| 347 | + cond: Operand::Consume(of), |
| 348 | + targets: (of_block, ok_block) |
| 349 | + }); |
| 350 | + |
| 351 | + self.panic(of_block, overflow_msg, span); |
| 352 | + |
| 353 | + block = ok_block; |
| 354 | + } |
| 355 | + } |
| 356 | + |
| 357 | + block.and(Rvalue::BinaryOp(op, lhs, rhs)) |
| 358 | + } |
| 359 | + } |
| 360 | + |
| 361 | + // Helper to get a `-1` value of the appropriate type |
| 362 | + fn neg_1_literal(&mut self, span: Span, ty: ty::Ty<'tcx>) -> Operand<'tcx> { |
| 363 | + let literal = match ty.sty { |
| 364 | + ty::TyInt(ity) => { |
| 365 | + let val = match ity { |
| 366 | + ast::IntTy::I8 => ConstInt::I8(-1), |
| 367 | + ast::IntTy::I16 => ConstInt::I16(-1), |
| 368 | + ast::IntTy::I32 => ConstInt::I32(-1), |
| 369 | + ast::IntTy::I64 => ConstInt::I64(-1), |
| 370 | + ast::IntTy::Is => { |
| 371 | + let int_ty = self.hir.tcx().sess.target.int_type; |
| 372 | + let val = ConstIsize::new(-1, int_ty).unwrap(); |
| 373 | + ConstInt::Isize(val) |
| 374 | + } |
| 375 | + }; |
| 376 | + |
| 377 | + Literal::Value { value: ConstVal::Integral(val) } |
| 378 | + } |
| 379 | + _ => { |
| 380 | + span_bug!(span, "Invalid type for neg_1_literal: `{:?}`", ty) |
| 381 | + } |
| 382 | + }; |
| 383 | + |
| 384 | + self.literal_operand(span, ty, literal) |
| 385 | + } |
| 386 | + |
| 387 | + // Helper to get the minimum value of the appropriate type |
| 388 | + fn minval_literal(&mut self, span: Span, ty: ty::Ty<'tcx>) -> Operand<'tcx> { |
| 389 | + let literal = match ty.sty { |
| 390 | + ty::TyInt(ity) => { |
| 391 | + let val = match ity { |
| 392 | + ast::IntTy::I8 => ConstInt::I8(std::i8::MIN), |
| 393 | + ast::IntTy::I16 => ConstInt::I16(std::i16::MIN), |
| 394 | + ast::IntTy::I32 => ConstInt::I32(std::i32::MIN), |
| 395 | + ast::IntTy::I64 => ConstInt::I64(std::i64::MIN), |
| 396 | + ast::IntTy::Is => { |
| 397 | + let int_ty = self.hir.tcx().sess.target.int_type; |
| 398 | + let min = match int_ty { |
| 399 | + ast::IntTy::I32 => std::i32::MIN as i64, |
| 400 | + ast::IntTy::I64 => std::i64::MIN, |
| 401 | + _ => unreachable!() |
| 402 | + }; |
| 403 | + let val = ConstIsize::new(min, int_ty).unwrap(); |
| 404 | + ConstInt::Isize(val) |
| 405 | + } |
| 406 | + }; |
| 407 | + |
| 408 | + Literal::Value { value: ConstVal::Integral(val) } |
| 409 | + } |
| 410 | + _ => { |
| 411 | + span_bug!(span, "Invalid type for minval_literal: `{:?}`", ty) |
| 412 | + } |
| 413 | + }; |
| 414 | + |
| 415 | + self.literal_operand(span, ty, literal) |
| 416 | + } |
224 | 417 | }
|
0 commit comments