1
Fork 0

Add a new Assert terminator to MIR for bounds & arithmetic checks.

This commit is contained in:
Eduard Burtescu 2016-05-25 08:39:32 +03:00
parent 7fbff36d01
commit 156b1fb9e1
22 changed files with 305 additions and 196 deletions

View file

@ -10,7 +10,7 @@
use graphviz::IntoCow;
use middle::const_val::ConstVal;
use rustc_const_math::{ConstUsize, ConstInt};
use rustc_const_math::{ConstUsize, ConstInt, ConstMathErr};
use hir::def_id::DefId;
use ty::subst::Substs;
use ty::{self, AdtDef, ClosureSubsts, FnOutput, Region, Ty};
@ -354,6 +354,16 @@ pub enum TerminatorKind<'tcx> {
/// Cleanups to be done if the call unwinds.
cleanup: Option<BasicBlock>
},
/// Jump to the target if the condition has the expected value,
/// otherwise panic with a message and a cleanup target.
Assert {
cond: Operand<'tcx>,
expected: bool,
msg: AssertMessage<'tcx>,
target: BasicBlock,
cleanup: Option<BasicBlock>
}
}
impl<'tcx> Terminator<'tcx> {
@ -389,6 +399,8 @@ impl<'tcx> TerminatorKind<'tcx> {
Drop { ref target, unwind: None, .. } => {
slice::ref_slice(target).into_cow()
}
Assert { target, cleanup: Some(unwind), .. } => vec![target, unwind].into_cow(),
Assert { ref target, .. } => slice::ref_slice(target).into_cow(),
}
}
@ -413,6 +425,8 @@ impl<'tcx> TerminatorKind<'tcx> {
Drop { ref mut target, unwind: None, .. } => {
vec![target]
}
Assert { ref mut target, cleanup: Some(ref mut unwind), .. } => vec![target, unwind],
Assert { ref mut target, .. } => vec![target]
}
}
}
@ -495,6 +509,26 @@ impl<'tcx> TerminatorKind<'tcx> {
}
write!(fmt, ")")
}
Assert { ref cond, expected, ref msg, .. } => {
write!(fmt, "assert(")?;
if !expected {
write!(fmt, "!")?;
}
write!(fmt, "{:?}, ", cond)?;
match *msg {
AssertMessage::BoundsCheck { ref len, ref index } => {
write!(fmt, "{:?}, {:?}, {:?}",
"index out of bounds: the len is {} but the index is {}",
len, index)?;
}
AssertMessage::Math(ref err) => {
write!(fmt, "{:?}", err.description())?;
}
}
write!(fmt, ")")
}
}
}
@ -532,10 +566,21 @@ impl<'tcx> TerminatorKind<'tcx> {
Drop { unwind: Some(_), .. } => {
vec!["return".into_cow(), "unwind".into_cow()]
}
Assert { cleanup: None, .. } => vec!["".into()],
Assert { .. } =>
vec!["success".into_cow(), "unwind".into_cow()]
}
}
}
#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
pub enum AssertMessage<'tcx> {
BoundsCheck {
len: Operand<'tcx>,
index: Operand<'tcx>
},
Math(ConstMathErr)
}
///////////////////////////////////////////////////////////////////////////
// Statements

View file

@ -127,6 +127,11 @@ macro_rules! make_mir_visitor {
self.super_terminator_kind(block, kind);
}
fn visit_assert_message(&mut self,
msg: & $($mutability)* AssertMessage<'tcx>) {
self.super_assert_message(msg);
}
fn visit_rvalue(&mut self,
rvalue: & $($mutability)* Rvalue<'tcx>) {
self.super_rvalue(rvalue);
@ -426,6 +431,31 @@ macro_rules! make_mir_visitor {
}
cleanup.map(|t| self.visit_branch(block, t));
}
TerminatorKind::Assert { ref $($mutability)* cond,
expected: _,
ref $($mutability)* msg,
target,
cleanup } => {
self.visit_operand(cond);
self.visit_assert_message(msg);
self.visit_branch(block, target);
cleanup.map(|t| self.visit_branch(block, t));
}
}
}
fn super_assert_message(&mut self,
msg: & $($mutability)* AssertMessage<'tcx>) {
match *msg {
AssertMessage::BoundsCheck {
ref $($mutability)* len,
ref $($mutability)* index
} => {
self.visit_operand(len);
self.visit_operand(index);
}
AssertMessage::Math(_) => {}
}
}

View file

@ -450,13 +450,14 @@ impl<'a, 'tcx: 'a, D> DataflowAnalysis<'a, 'tcx, D>
repr::TerminatorKind::Return |
repr::TerminatorKind::Resume => {}
repr::TerminatorKind::Goto { ref target } |
repr::TerminatorKind::Assert { ref target, cleanup: None, .. } |
repr::TerminatorKind::Drop { ref target, location: _, unwind: None } |
repr::TerminatorKind::DropAndReplace {
ref target, value: _, location: _, unwind: None
} => {
self.propagate_bits_into_entry_set_for(in_out, changed, target);
}
repr::TerminatorKind::Assert { ref target, cleanup: Some(ref unwind), .. } |
repr::TerminatorKind::Drop { ref target, location: _, unwind: Some(ref unwind) } |
repr::TerminatorKind::DropAndReplace {
ref target, value: _, location: _, unwind: Some(ref unwind)

View file

@ -663,6 +663,22 @@ fn gather_moves<'a, 'tcx>(mir: &Mir<'tcx>, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> MoveD
bb_ctxt.on_operand(SK::If, cond, source);
}
TerminatorKind::Assert {
ref cond, expected: _,
ref msg, target: _, cleanup: _
} => {
// The `cond` is always of (copyable) type `bool`,
// so there will never be anything to move.
let _ = cond;
match *msg {
AssertMessage:: BoundsCheck { ref len, ref index } => {
// Same for the usize length and index in bounds-checking.
let _ = (len, index);
}
AssertMessage::Math(_) => {}
}
}
TerminatorKind::SwitchInt { switch_ty: _, values: _, targets: _, ref discr } |
TerminatorKind::Switch { adt_def: _, targets: _, ref discr } => {
// The `discr` is not consumed; that is instead

View file

@ -10,7 +10,7 @@
use syntax::ast;
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, RustcEncodable, RustcDecodable)]
pub enum ConstMathErr {
NotInRange,
CmpBetweenUnequalTypes,
@ -25,7 +25,7 @@ pub enum ConstMathErr {
}
pub use self::ConstMathErr::*;
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, RustcEncodable, RustcDecodable)]
pub enum Op {
Add,
Sub,

View file

@ -12,7 +12,6 @@ use build::{BlockAnd, BlockAndExtension, Builder};
use hair::*;
use rustc::mir::repr::*;
use rustc::hir;
use syntax::codemap::Span;
impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
pub fn ast_block(&mut self,
@ -82,22 +81,4 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
block.unit()
})
}
// Helper method for generating a conditional branch
// Returns (TrueBlock, FalseBlock)
pub fn build_cond_br(&mut self, block: BasicBlock, span: Span,
cond: Operand<'tcx>) -> (BasicBlock, BasicBlock) {
let scope_id = self.innermost_scope_id();
let then_block = self.cfg.start_new_block();
let else_block = self.cfg.start_new_block();
self.cfg.terminate(block, scope_id, span,
TerminatorKind::If {
cond: cond,
targets: (then_block, else_block)
});
(then_block, else_block)
}
}

View file

@ -66,15 +66,12 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
idx.clone(),
Operand::Consume(len.clone())));
let (success, failure) = (this.cfg.start_new_block(), this.cfg.start_new_block());
this.cfg.terminate(block,
scope_id,
expr_span,
TerminatorKind::If {
cond: Operand::Consume(lt),
targets: (success, failure),
});
this.panic_bounds_check(failure, idx.clone(), Operand::Consume(len), expr_span);
let msg = AssertMessage::BoundsCheck {
len: Operand::Consume(len),
index: idx.clone()
};
let success = this.assert(block, Operand::Consume(lt), true,
msg, expr_span);
success.and(slice.index(idx))
}
ExprKind::SelfRef => {

View file

@ -12,6 +12,7 @@
use std;
use rustc_const_math::{ConstMathErr, Op};
use rustc_data_structures::fnv::FnvHashMap;
use build::{BlockAnd, BlockAndExtension, Builder};
@ -88,10 +89,9 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
this.cfg.push_assign(block, scope_id, expr_span, &is_min,
Rvalue::BinaryOp(BinOp::Eq, arg.clone(), minval));
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);
block = ok_block;
let err = ConstMathErr::Overflow(Op::Neg);
block = this.assert(block, Operand::Consume(is_min), false,
AssertMessage::Math(err), expr_span);
}
block.and(Rvalue::UnaryOp(op, arg))
}
@ -261,27 +261,32 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
let val = result_value.clone().field(val_fld, ty);
let of = result_value.field(of_fld, bool_ty);
let msg = if op == BinOp::Shl || op == BinOp::Shr {
"shift operation overflowed"
} else {
"arithmetic operation overflowed"
};
let err = ConstMathErr::Overflow(match op {
BinOp::Add => Op::Add,
BinOp::Sub => Op::Sub,
BinOp::Mul => Op::Mul,
BinOp::Shl => Op::Shl,
BinOp::Shr => Op::Shr,
_ => {
bug!("MIR build_binary_op: {:?} is not checkable", op)
}
});
let (of_block, ok_block) = self.build_cond_br(block, span, Operand::Consume(of));
self.panic(of_block, msg, span);
block = self.assert(block, Operand::Consume(of), false,
AssertMessage::Math(err), span);
ok_block.and(Rvalue::Use(Operand::Consume(val)))
block.and(Rvalue::Use(Operand::Consume(val)))
} else {
if ty.is_integral() && (op == BinOp::Div || op == BinOp::Rem) {
// Checking division and remainder is more complex, since we 1. always check
// and 2. there are two possible failure cases, divide-by-zero and overflow.
let (zero_msg, overflow_msg) = if op == BinOp::Div {
("attempted to divide by zero",
"attempted to divide with overflow")
let (zero_err, overflow_err) = if op == BinOp::Div {
(ConstMathErr::DivisionByZero,
ConstMathErr::Overflow(Op::Div))
} else {
("attempted remainder with a divisor of zero",
"attempted remainder with overflow")
(ConstMathErr::RemainderByZero,
ConstMathErr::Overflow(Op::Rem))
};
// Check for / 0
@ -290,11 +295,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
self.cfg.push_assign(block, scope_id, span, &is_zero,
Rvalue::BinaryOp(BinOp::Eq, rhs.clone(), zero));
let (zero_block, ok_block) = self.build_cond_br(block, span,
Operand::Consume(is_zero));
self.panic(zero_block, zero_msg, span);
block = ok_block;
block = self.assert(block, Operand::Consume(is_zero), false,
AssertMessage::Math(zero_err), span);
// We only need to check for the overflow in one case:
// MIN / -1, and only for signed values.
@ -318,11 +320,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
self.cfg.push_assign(block, scope_id, span, &of,
Rvalue::BinaryOp(BinOp::BitAnd, is_neg_1, is_min));
let (of_block, ok_block) = self.build_cond_br(block, span,
Operand::Consume(of));
self.panic(of_block, overflow_msg, span);
block = ok_block;
block = self.assert(block, Operand::Consume(of), false,
AssertMessage::Math(overflow_err), span);
}
}

View file

@ -517,7 +517,6 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
}
pub fn build_drop_and_replace(&mut self,
block: BasicBlock,
span: Span,
@ -538,48 +537,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
next_target.unit()
}
// Panicking
// =========
// FIXME: should be moved into their own module
pub fn panic_bounds_check(&mut self,
block: BasicBlock,
index: Operand<'tcx>,
len: Operand<'tcx>,
span: Span) {
// fn(&(filename: &'static str, line: u32), index: usize, length: usize) -> !
let region = ty::ReStatic; // FIXME(mir-borrowck): use a better region?
let func = self.lang_function(lang_items::PanicBoundsCheckFnLangItem);
let args = self.hir.tcx().replace_late_bound_regions(&func.ty.fn_args(), |_| region).0;
let ref_ty = args[0];
let tup_ty = if let ty::TyRef(_, tyandmut) = ref_ty.sty {
tyandmut.ty
} else {
span_bug!(span, "unexpected panic_bound_check type: {:?}", func.ty);
};
let (tuple, tuple_ref) = (self.temp(tup_ty), self.temp(ref_ty));
let (file, line) = self.span_to_fileline_args(span);
let elems = vec![Operand::Constant(file), Operand::Constant(line)];
let scope_id = self.innermost_scope_id();
// FIXME: We should have this as a constant, rather than a stack variable (to not pollute
// icache with cold branch code), however to achieve that we either have to rely on rvalue
// promotion or have some way, in MIR, to create constants.
self.cfg.push_assign(block, scope_id, span, &tuple, // tuple = (file_arg, line_arg);
Rvalue::Aggregate(AggregateKind::Tuple, elems));
// FIXME: is this region really correct here?
self.cfg.push_assign(block, scope_id, span, &tuple_ref, // tuple_ref = &tuple;
Rvalue::Ref(region, BorrowKind::Shared, tuple));
let cleanup = self.diverge_cleanup();
self.cfg.terminate(block, scope_id, span, TerminatorKind::Call {
func: Operand::Constant(func),
args: vec![Operand::Consume(tuple_ref), index, len],
destination: None,
cleanup: cleanup,
});
}
/// Create diverge cleanup and branch to it from `block`.
// FIXME: Remove this (used only for unreachable cases in match).
pub fn panic(&mut self, block: BasicBlock, msg: &'static str, span: Span) {
// fn(&(msg: &'static str filename: &'static str, line: u32)) -> !
let region = ty::ReStatic; // FIXME(mir-borrowck): use a better region?
@ -622,6 +581,32 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
});
}
/// Create an Assert terminator and return the success block.
/// If the boolean condition operand is not the expected value,
/// a runtime panic will be caused with the given message.
pub fn assert(&mut self, block: BasicBlock,
cond: Operand<'tcx>,
expected: bool,
msg: AssertMessage<'tcx>,
span: Span)
-> BasicBlock {
let scope_id = self.innermost_scope_id();
let success_block = self.cfg.start_new_block();
let cleanup = self.diverge_cleanup();
self.cfg.terminate(block, scope_id, span,
TerminatorKind::Assert {
cond: cond,
expected: expected,
msg: msg,
target: success_block,
cleanup: cleanup
});
success_block
}
fn lang_function(&mut self, lang_item: lang_items::LangItem) -> Constant<'tcx> {
let funcdid = match self.hir.tcx().lang_items.require(lang_item) {
Ok(d) => d,

View file

@ -30,6 +30,7 @@ impl<'tcx> MutVisitor<'tcx> for NoLandingPads {
/* nothing to do */
},
TerminatorKind::Call { cleanup: ref mut unwind, .. } |
TerminatorKind::Assert { cleanup: ref mut unwind, .. } |
TerminatorKind::DropAndReplace { ref mut unwind, .. } |
TerminatorKind::Drop { ref mut unwind, .. } => {
unwind.take();

View file

@ -332,61 +332,6 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
}
}
/// Returns true if the block ends in a bounds check branch, i.e.:
/// len = Len(array);
/// cond = Lt(idx, len);
/// if cond {
/// ...
/// } else {
/// loc = (...);
/// loc_ref = &loc;
/// panic_bounds_check(loc_ref, idx, len);
/// }
fn is_bounds_check(&self, bb: BasicBlock,
cond_op: &Operand<'tcx>,
if_else: BasicBlock) -> bool {
use rustc::mir::repr::Lvalue::*;
use rustc::mir::repr::Operand::Consume;
use rustc::mir::repr::Rvalue::*;
use rustc::mir::repr::StatementKind::*;
use rustc::mir::repr::TerminatorKind::*;
let stmts = &self.mir[bb].statements;
let stmts_panic = &self.mir[if_else].statements;
if stmts.len() < 2 || stmts_panic.len() != 2 {
return false;
}
let all = (&stmts[stmts.len() - 2].kind,
&stmts[stmts.len() - 1].kind,
cond_op,
&stmts_panic[0].kind,
&stmts_panic[1].kind,
&self.mir[if_else].terminator().kind);
match all {
(&Assign(Temp(len), Len(_)),
&Assign(Temp(cond), BinaryOp(BinOp::Lt, ref idx, Consume(Temp(len2)))),
/* if */ &Consume(Temp(cond2)), /* {...} else */
&Assign(Temp(loc), Aggregate(..)),
&Assign(Temp(loc_ref), Ref(_, _, Temp(loc2))),
&Call {
func: Operand::Constant(Constant {
literal: Literal::Item { def_id, .. }, ..
}),
ref args,
destination: None,
..
}) => {
len == len2 && cond == cond2 && loc == loc2 &&
args[0] == Consume(Temp(loc_ref)) &&
args[1] == *idx &&
args[2] == Consume(Temp(len)) &&
Some(def_id) == self.tcx.lang_items.panic_bounds_check_fn()
}
_ => false
}
}
/// Qualify a whole const, static initializer or const fn.
fn qualify_const(&mut self) -> Qualif {
let mir = self.mir;
@ -402,6 +347,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
TerminatorKind::Goto { target } |
// Drops are considered noops.
TerminatorKind::Drop { target, .. } |
TerminatorKind::Assert { target, .. } |
TerminatorKind::Call { destination: Some((_, target)), .. } => {
Some(target)
}
@ -411,15 +357,7 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
return Qualif::empty();
}
// Need to allow bounds checking branches.
TerminatorKind::If { ref cond, targets: (if_true, if_else) } => {
if self.is_bounds_check(bb, cond, if_else) {
Some(if_true)
} else {
None
}
}
TerminatorKind::If {..} |
TerminatorKind::Switch {..} |
TerminatorKind::SwitchInt {..} |
TerminatorKind::DropAndReplace { .. } |

View file

@ -181,7 +181,17 @@ fn simplify_branches(mir: &mut Mir) {
}
}
TerminatorKind::Assert { target, cond: Operand::Constant(Constant {
literal: Literal::Value {
value: ConstVal::Bool(cond)
}, ..
}), expected, .. } if cond == expected => {
changed = true;
TerminatorKind::Goto { target: target }
}
TerminatorKind::SwitchInt { ref targets, .. } if targets.len() == 1 => {
changed = true;
TerminatorKind::Goto { target: targets[0] }
}
_ => continue

View file

@ -431,6 +431,21 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
self.check_call_inputs(mir, term, &sig, args);
}
}
TerminatorKind::Assert { ref cond, ref msg, .. } => {
let cond_ty = mir.operand_ty(tcx, cond);
if cond_ty != tcx.types.bool {
span_mirbug!(self, term, "bad Assert ({:?}, not bool", cond_ty);
}
if let AssertMessage::BoundsCheck { ref len, ref index } = *msg {
if mir.operand_ty(tcx, len) != tcx.types.usize {
span_mirbug!(self, len, "bounds-check length non-usize {:?}", len)
}
if mir.operand_ty(tcx, index) != tcx.types.usize {
span_mirbug!(self, index, "bounds-check index non-usize {:?}", index)
}
}
}
}
}
@ -561,7 +576,8 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
}
}
TerminatorKind::Drop { target, unwind, .. } |
TerminatorKind::DropAndReplace { target, unwind, .. } => {
TerminatorKind::DropAndReplace { target, unwind, .. } |
TerminatorKind::Assert { target, cleanup: unwind, .. } => {
self.assert_iscleanup(mir, block, target, is_cleanup);
if let Some(unwind) = unwind {
if is_cleanup {

View file

@ -880,7 +880,7 @@ fn compare_values<'blk, 'tcx>(cx: Block<'blk, 'tcx>,
rhs_t: Ty<'tcx>,
debug_loc: DebugLoc)
-> Result<'blk, 'tcx> {
let did = langcall(bcx,
let did = langcall(bcx.tcx(),
None,
&format!("comparison of `{}`", rhs_t),
StrEqFnLangItem);

View file

@ -1165,18 +1165,18 @@ pub fn normalize_and_test_predicates<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
})
}
pub fn langcall(bcx: Block,
pub fn langcall(tcx: TyCtxt,
span: Option<Span>,
msg: &str,
li: LangItem)
-> DefId {
match bcx.tcx().lang_items.require(li) {
match tcx.lang_items.require(li) {
Ok(id) => id,
Err(s) => {
let msg = format!("{} {}", msg, s);
match span {
Some(span) => bcx.tcx().sess.span_fatal(span, &msg[..]),
None => bcx.tcx().sess.fatal(&msg[..]),
Some(span) => tcx.sess.span_fatal(span, &msg[..]),
None => tcx.sess.fatal(&msg[..]),
}
}
}

View file

@ -400,7 +400,7 @@ pub fn trans_fail<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
let align = machine::llalign_of_min(ccx, val_ty(expr_file_line_const));
let expr_file_line = consts::addr_of(ccx, expr_file_line_const, align, "panic_loc");
let args = vec!(expr_file_line);
let did = langcall(bcx, Some(call_info.span), "", PanicFnLangItem);
let did = langcall(bcx.tcx(), Some(call_info.span), "", PanicFnLangItem);
Callee::def(ccx, did, ccx.tcx().mk_substs(Substs::empty()))
.call(bcx, call_info.debug_loc(), ArgVals(&args), None).bcx
}
@ -428,7 +428,7 @@ pub fn trans_fail_bounds_check<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
let align = machine::llalign_of_min(ccx, val_ty(file_line_const));
let file_line = consts::addr_of(ccx, file_line_const, align, "panic_bounds_check_loc");
let args = vec!(file_line, index, len);
let did = langcall(bcx, Some(call_info.span), "", PanicBoundsCheckFnLangItem);
let did = langcall(bcx.tcx(), Some(call_info.span), "", PanicBoundsCheckFnLangItem);
Callee::def(ccx, did, ccx.tcx().mk_substs(Substs::empty()))
.call(bcx, call_info.debug_loc(), ArgVals(&args), None).bcx
}

View file

@ -2230,11 +2230,11 @@ impl OverflowOpViaIntrinsic {
binop_debug_loc);
let expect = bcx.ccx().get_intrinsic(&"llvm.expect.i1");
Call(bcx, expect, &[cond, C_integral(Type::i1(bcx.ccx()), 0, false)],
binop_debug_loc);
let expected = Call(bcx, expect, &[cond, C_bool(bcx.ccx(), false)],
binop_debug_loc);
let bcx =
base::with_cond(bcx, cond, |bcx|
base::with_cond(bcx, expected, |bcx|
controlflow::trans_fail(bcx, info,
InternedString::new("arithmetic operation overflowed")));

View file

@ -53,7 +53,7 @@ pub fn trans_exchange_free_dyn<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
-> Block<'blk, 'tcx> {
let _icx = push_ctxt("trans_exchange_free");
let def_id = langcall(bcx, None, "", ExchangeFreeFnLangItem);
let def_id = langcall(bcx.tcx(), None, "", ExchangeFreeFnLangItem);
let args = [PointerCast(bcx, v, Type::i8p(bcx.ccx())), size, align];
Callee::def(bcx.ccx(), def_id, bcx.tcx().mk_substs(Substs::empty()))
.call(bcx, debug_loc, ArgVals(&args), None).bcx

View file

@ -161,6 +161,7 @@ pub fn cleanup_kinds<'bcx,'tcx>(_bcx: Block<'bcx,'tcx>,
/* nothing to do */
}
TerminatorKind::Call { cleanup: unwind, .. } |
TerminatorKind::Assert { cleanup: unwind, .. } |
TerminatorKind::DropAndReplace { unwind, .. } |
TerminatorKind::Drop { unwind, .. } => {
if let Some(unwind) = unwind {

View file

@ -9,6 +9,7 @@
// except according to those terms.
use llvm::{self, ValueRef};
use rustc::middle::lang_items;
use rustc::ty;
use rustc::mir::repr as mir;
use abi::{Abi, FnType, ArgType};
@ -16,7 +17,9 @@ use adt;
use base;
use build;
use callee::{Callee, CalleeData, Fn, Intrinsic, NamedTupleConstructor, Virtual};
use common::{self, type_is_fat_ptr, Block, BlockAndBuilder, LandingPad, C_undef};
use common::{self, type_is_fat_ptr, Block, BlockAndBuilder, LandingPad};
use common::{C_bool, C_str_slice, C_struct, C_u32, C_undef};
use consts;
use debuginfo::DebugLoc;
use Disr;
use machine::{llalign_of_min, llbitsize_of_real};
@ -24,7 +27,9 @@ use meth;
use type_of;
use glue;
use type_::Type;
use rustc_data_structures::fnv::FnvHashMap;
use syntax::parse::token;
use super::{MirContext, TempRef};
use super::analyze::CleanupKind;
@ -212,6 +217,92 @@ impl<'bcx, 'tcx> MirContext<'bcx, 'tcx> {
}
}
mir::TerminatorKind::Assert { ref cond, expected, ref msg, target, cleanup } => {
let cond = self.trans_operand(&bcx, cond).immediate();
let const_cond = common::const_to_opt_uint(cond).map(|c| c == 1);
// Don't translate the panic block if success if known.
let lltarget = self.llblock(target);
if const_cond == Some(expected) {
funclet_br(bcx, lltarget);
return;
}
if const_cond == Some(!expected) {
// Do nothing to end up with an unconditional panic.
} else {
// Pass the condition through llvm.expect for branch hinting.
let expect = bcx.ccx().get_intrinsic(&"llvm.expect.i1");
let cond = bcx.call(expect, &[cond, C_bool(bcx.ccx(), expected)], None);
// Create the failure block and the conditional branch to it.
// After this point, bcx is the block for the call to panic.
let panic_block = self.fcx.new_block("panic", None);
if expected {
bcx.cond_br(cond, lltarget, panic_block.llbb);
} else {
bcx.cond_br(cond, panic_block.llbb, lltarget);
}
bcx = panic_block.build();
}
// Get the location information.
let loc = bcx.sess().codemap().lookup_char_pos(terminator.span.lo);
let filename = token::intern_and_get_ident(&loc.file.name);
let filename = C_str_slice(bcx.ccx(), filename);
let line = C_u32(bcx.ccx(), loc.line as u32);
// Put together the arguments to the panic entry point.
let (lang_item, args) = match *msg {
mir::AssertMessage::BoundsCheck { ref len, ref index } => {
let len = self.trans_operand(&mut bcx, len);
let index = self.trans_operand(&mut bcx, index);
let file_line = C_struct(bcx.ccx(), &[filename, line], false);
let align = llalign_of_min(bcx.ccx(), common::val_ty(file_line));
let file_line = consts::addr_of(bcx.ccx(),
file_line,
align,
"panic_bounds_check_loc");
(lang_items::PanicBoundsCheckFnLangItem,
vec![file_line, index.immediate(), len.immediate()])
}
mir::AssertMessage::Math(ref err) => {
let msg_str = token::intern_and_get_ident(err.description());
let msg_str = C_str_slice(bcx.ccx(), msg_str);
let msg_file_line = C_struct(bcx.ccx(),
&[msg_str, filename, line],
false);
let align = llalign_of_min(bcx.ccx(), common::val_ty(msg_file_line));
let msg_file_line = consts::addr_of(bcx.ccx(),
msg_file_line,
align,
"panic_loc");
(lang_items::PanicFnLangItem, vec![msg_file_line])
}
};
// Obtain the panic entry point.
let def_id = common::langcall(bcx.tcx(), Some(terminator.span), "", lang_item);
let callee = Callee::def(bcx.ccx(), def_id,
bcx.ccx().empty_substs_for_def_id(def_id));
let llfn = callee.reify(bcx.ccx()).val;
// Translate the actual panic invoke/call.
if let Some(unwind) = cleanup {
let uwbcx = self.bcx(unwind);
let unwind = self.make_landing_pad(uwbcx);
bcx.invoke(llfn,
&args,
self.unreachable_block().llbb,
unwind.llbb(),
cleanup_bundle.as_ref());
} else {
bcx.call(llfn, &args, cleanup_bundle.as_ref());
bcx.unreachable();
}
}
mir::TerminatorKind::DropAndReplace { .. } => {
bug!("undesugared DropAndReplace in trans: {:?}", data);
}

View file

@ -287,14 +287,21 @@ impl<'a, 'tcx> MirConstContext<'a, 'tcx> {
}))
}
// This is only supported to make bounds checking work.
mir::TerminatorKind::If { ref cond, targets: (true_bb, false_bb) } => {
mir::TerminatorKind::Assert { ref cond, expected, ref msg, target, .. } => {
let cond = self.const_operand(cond, span)?;
if common::const_to_uint(cond.llval) != 0 {
true_bb
} else {
false_bb
let cond_bool = common::const_to_uint(cond.llval) != 0;
if cond_bool != expected {
let err = match *msg {
mir::AssertMessage::BoundsCheck {..} => {
ErrKind::IndexOutOfBounds
}
mir::AssertMessage::Math(ref err) => {
ErrKind::Math(err.clone())
}
};
consts::const_err(self.ccx, span, Err(err), TrueConst::Yes)?;
}
target
}
mir::TerminatorKind::Call { ref func, ref args, ref destination, .. } => {
@ -308,13 +315,6 @@ impl<'a, 'tcx> MirConstContext<'a, 'tcx> {
func, fn_ty)
};
// Indexing OOB doesn't call a const fn, handle it.
if Some(instance.def) == tcx.lang_items.panic_bounds_check_fn() {
consts::const_err(self.ccx, span,
Err(ErrKind::IndexOutOfBounds),
TrueConst::Yes)?;
}
let args = args.iter().map(|arg| {
self.const_operand(arg, span)
}).collect::<Result<Vec<_>, _>>()?;

View file

@ -17,13 +17,13 @@ use asm;
use base;
use callee::Callee;
use common::{self, val_ty,
C_null,
C_uint, C_undef, C_u8, BlockAndBuilder, Result};
C_null, C_uint, C_undef, BlockAndBuilder, Result};
use datum::{Datum, Lvalue};
use debuginfo::DebugLoc;
use adt;
use machine;
use type_of;
use type_::Type;
use tvec;
use value::Value;
use Disr;
@ -611,9 +611,7 @@ impl<'bcx, 'tcx> MirContext<'bcx, 'tcx> {
(val, bcx.zext(of, Type::bool(bcx.ccx())))
}
_ => {
// Fall back to regular translation with a constant-false overflow flag
(self.trans_scalar_binop(bcx, op, lhs, rhs, input_ty),
C_u8(bcx.ccx(), 0))
bug!("Operator `{:?}` is not a checkable operator", op)
}
};