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

Hold the state #18

Merged
merged 33 commits into from
Jun 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e73fa77
can't evaluate failed assertions yet
oli-obk May 31, 2016
8398781
remove one layer of indirection when interpreting const/static/main f…
oli-obk Jun 1, 2016
5a8b0ab
don't clone the MIR Rc in every iteration
oli-obk Jun 1, 2016
2405c81
stepwise interpretation
oli-obk Jun 1, 2016
77e1ec2
style: spaces between functions
oli-obk Jun 1, 2016
0c269a5
rename `iterator` module to `stepper`
oli-obk Jun 1, 2016
5211178
note that not all literal items are function pointers
oli-obk Jun 1, 2016
6ac64f1
also step through promoteds, constants and statics
oli-obk Jun 2, 2016
97bc1d4
add a const fn test
oli-obk Jun 2, 2016
38ae352
remove a debug message that snuck into the commit
oli-obk Jun 2, 2016
05f829c
merge the three stacks in the interpreter
oli-obk Jun 2, 2016
cc1ca73
jit interpretation of constants
oli-obk Jun 3, 2016
f995db9
store the current block in the frame
oli-obk Jun 3, 2016
346560b
factor out the statement index into the stackframe
oli-obk Jun 3, 2016
02eed64
update documentation
oli-obk Jun 3, 2016
4743842
move constants stack to stackframe
oli-obk Jun 3, 2016
dc85b11
stop producing executable files in the root folder during benchmarks...
oli-obk Jun 3, 2016
4c833a5
globally cache statics and promoteds
oli-obk Jun 3, 2016
4d44a97
move some methods from FnEvalContext to GlobalEvalContext
oli-obk Jun 8, 2016
f42be6d
move load_mir to the global eval context
oli-obk Jun 6, 2016
c881cf1
clippy nits
oli-obk Jun 6, 2016
1f27d3f
don't cache the MIR in the Stepper
oli-obk Jun 8, 2016
6b939bb
rebase leftovers
oli-obk Jun 8, 2016
8b25bc8
directly push stackframes for constants when they are encountered
oli-obk Jun 8, 2016
3de30e3
no more function pointers
oli-obk Jun 8, 2016
3868a62
put `ConstantId`'s common fields into a struct
oli-obk Jun 8, 2016
240f0c0
improve fn argument naming
oli-obk Jun 8, 2016
2178961
improve the docs of ConstantId
oli-obk Jun 8, 2016
cbbf58b
the statement/terminator has already been computed, don't do it again
oli-obk Jun 8, 2016
59d858a
refactor away the closures and `Event` enum
oli-obk Jun 9, 2016
225a6a2
we already have the constant's type, no need to recompute from the de…
oli-obk Jun 9, 2016
040a501
make sure globals that yield function pointers aren't treated like fu…
oli-obk Jun 9, 2016
05eaa52
rename `static_item` to `global_item`
oli-obk Jun 9, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion benches/miri_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extern crate test;

use self::miri::interpreter;
use self::rustc::session::Session;
use self::rustc_driver::{driver, CompilerCalls};
use self::rustc_driver::{driver, CompilerCalls, Compilation};
use std::cell::RefCell;
use std::rc::Rc;
use std::env::var;
Expand All @@ -35,6 +35,7 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls<'a> {

let bencher = self.0.clone();

control.after_analysis.stop = Compilation::Stop;
control.after_analysis.callback = Box::new(move |state| {
state.session.abort_if_errors();
bencher.borrow_mut().iter(|| {
Expand Down
589 changes: 325 additions & 264 deletions src/interpreter.rs → src/interpreter/mod.rs

Large diffs are not rendered by default.

189 changes: 189 additions & 0 deletions src/interpreter/stepper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use super::{
FnEvalContext,
CachedMir,
TerminatorTarget,
ConstantId,
GlobalEvalContext,
ConstantKind,
};
use error::EvalResult;
use rustc::mir::repr as mir;
use rustc::ty::{subst, self};
use rustc::hir::def_id::DefId;
use rustc::mir::visit::{Visitor, LvalueContext};
use syntax::codemap::Span;
use std::rc::Rc;
use memory::Pointer;

pub struct Stepper<'fncx, 'a: 'fncx, 'b: 'a + 'mir, 'mir: 'fncx, 'tcx: 'b>{
fncx: &'fncx mut FnEvalContext<'a, 'b, 'mir, 'tcx>,

// a cache of the constants to be computed before the next statement/terminator
// this is an optimization, so we don't have to allocate a new vector for every statement
constants: Vec<(ConstantId<'tcx>, Span, Pointer, CachedMir<'mir, 'tcx>)>,
}

impl<'fncx, 'a, 'b: 'a + 'mir, 'mir, 'tcx: 'b> Stepper<'fncx, 'a, 'b, 'mir, 'tcx> {
pub(super) fn new(fncx: &'fncx mut FnEvalContext<'a, 'b, 'mir, 'tcx>) -> Self {
Stepper {
fncx: fncx,
constants: Vec::new(),
}
}

fn statement(&mut self, stmt: &mir::Statement<'tcx>) -> EvalResult<()> {
trace!("{:?}", stmt);
let mir::StatementKind::Assign(ref lvalue, ref rvalue) = stmt.kind;
let result = self.fncx.eval_assignment(lvalue, rvalue);
self.fncx.maybe_report(result)?;
self.fncx.frame_mut().stmt += 1;
Ok(())
}

fn terminator(&mut self, terminator: &mir::Terminator<'tcx>) -> EvalResult<()> {
// after a terminator we go to a new block
self.fncx.frame_mut().stmt = 0;
let term = {
trace!("{:?}", terminator.kind);
let result = self.fncx.eval_terminator(terminator);
self.fncx.maybe_report(result)?
};
match term {
TerminatorTarget::Return => {
self.fncx.pop_stack_frame();
},
TerminatorTarget::Block |
TerminatorTarget::Call => trace!("// {:?}", self.fncx.frame().next_block),
}
Ok(())
}

// returns true as long as there are more things to do
pub fn step(&mut self) -> EvalResult<bool> {
if self.fncx.stack.is_empty() {
return Ok(false);
}

let block = self.fncx.frame().next_block;
let stmt = self.fncx.frame().stmt;
let mir = self.fncx.mir();
let basic_block = mir.basic_block_data(block);

if let Some(ref stmt) = basic_block.statements.get(stmt) {
assert!(self.constants.is_empty());
ConstantExtractor {
span: stmt.span,
substs: self.fncx.substs(),
def_id: self.fncx.frame().def_id,
gecx: self.fncx.gecx,
constants: &mut self.constants,
mir: &mir,
}.visit_statement(block, stmt);
if self.constants.is_empty() {
self.statement(stmt)?;
} else {
self.extract_constants()?;
}
return Ok(true);
}

let terminator = basic_block.terminator();
assert!(self.constants.is_empty());
ConstantExtractor {
span: terminator.span,
substs: self.fncx.substs(),
def_id: self.fncx.frame().def_id,
gecx: self.fncx.gecx,
constants: &mut self.constants,
mir: &mir,
}.visit_terminator(block, terminator);
if self.constants.is_empty() {
self.terminator(terminator)?;
} else {
self.extract_constants()?;
}
Ok(true)
}

fn extract_constants(&mut self) -> EvalResult<()> {
assert!(!self.constants.is_empty());
for (cid, span, return_ptr, mir) in self.constants.drain(..) {
trace!("queuing a constant");
self.fncx.push_stack_frame(cid.def_id, span, mir, cid.substs, Some(return_ptr));
}
// self.step() can't be "done", so it can't return false
assert!(self.step()?);
Ok(())
}
}

struct ConstantExtractor<'a, 'b: 'mir, 'mir: 'a, 'tcx: 'b> {
span: Span,
// FIXME: directly push the new stackframes instead of doing this intermediate caching
constants: &'a mut Vec<(ConstantId<'tcx>, Span, Pointer, CachedMir<'mir, 'tcx>)>,
gecx: &'a mut GlobalEvalContext<'b, 'tcx>,
mir: &'a mir::Mir<'tcx>,
def_id: DefId,
substs: &'tcx subst::Substs<'tcx>,
}

impl<'a, 'b, 'mir, 'tcx> ConstantExtractor<'a, 'b, 'mir, 'tcx> {
fn global_item(&mut self, def_id: DefId, substs: &'tcx subst::Substs<'tcx>, span: Span) {
let cid = ConstantId {
def_id: def_id,
substs: substs,
kind: ConstantKind::Global,
};
if self.gecx.statics.contains_key(&cid) {
return;
}
let mir = self.gecx.load_mir(def_id);
let ptr = self.gecx.alloc_ret_ptr(mir.return_ty, substs).expect("there's no such thing as an unreachable static");
self.gecx.statics.insert(cid.clone(), ptr);
self.constants.push((cid, span, ptr, mir));
}
}

impl<'a, 'b, 'mir, 'tcx> Visitor<'tcx> for ConstantExtractor<'a, 'b, 'mir, 'tcx> {
fn visit_constant(&mut self, constant: &mir::Constant<'tcx>) {
self.super_constant(constant);
match constant.literal {
// already computed by rustc
mir::Literal::Value { .. } => {}
mir::Literal::Item { def_id, substs } => {
if let ty::TyFnDef(..) = constant.ty.sty {
// No need to do anything here, even if function pointers are implemented,
// because the type is the actual function, not the signature of the function.
// Thus we can simply create a zero sized allocation in `evaluate_operand`
} else {
self.global_item(def_id, substs, constant.span);
}
},
mir::Literal::Promoted { index } => {
let cid = ConstantId {
def_id: self.def_id,
substs: self.substs,
kind: ConstantKind::Promoted(index),
};
if self.gecx.statics.contains_key(&cid) {
return;
}
let mir = self.mir.promoted[index].clone();
Copy link
Member

Choose a reason for hiding this comment

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

This is cloning an entire Mir structure... we should try to find a way around that.

let return_ty = mir.return_ty;
let return_ptr = self.gecx.alloc_ret_ptr(return_ty, cid.substs).expect("there's no such thing as an unreachable static");
let mir = CachedMir::Owned(Rc::new(mir));
self.gecx.statics.insert(cid.clone(), return_ptr);
self.constants.push((cid, constant.span, return_ptr, mir));
}
}
}

fn visit_lvalue(&mut self, lvalue: &mir::Lvalue<'tcx>, context: LvalueContext) {
self.super_lvalue(lvalue, context);
if let mir::Lvalue::Static(def_id) = *lvalue {
let substs = self.gecx.tcx.mk_substs(subst::Substs::empty());
let span = self.span;
self.global_item(def_id, substs, span);
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
filling_drop,
question_mark,
rustc_private,
pub_restricted,
)]

// From rustc.
Expand Down
2 changes: 1 addition & 1 deletion src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl Memory {
alloc.bytes.extend(iter::repeat(0).take(amount));
alloc.undef_mask.grow(amount, false);
} else if size > new_size {
return Err(EvalError::Unimplemented(format!("unimplemented allocation relocation")));
return Err(EvalError::Unimplemented(format!("unimplemented allocation relocation (from {} to {})", size, new_size)));
// alloc.bytes.truncate(new_size);
// alloc.undef_mask.len = new_size;
// TODO: potentially remove relocations
Expand Down
12 changes: 12 additions & 0 deletions tests/compile-fail/unimplemented.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]

//error-pattern:unimplemented: mentions of function items


#[miri_run]
fn failed_assertions() {
assert_eq!(5, 6);
}

fn main() {}
14 changes: 14 additions & 0 deletions tests/run-pass/bug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]

static mut X: usize = 5;

#[miri_run]
fn static_mut() {
unsafe {
X = 6;
assert_eq!(X, 6);
}
}

fn main() {}
11 changes: 10 additions & 1 deletion tests/run-pass/calls.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![feature(custom_attribute)]
#![feature(custom_attribute, const_fn)]
#![allow(dead_code, unused_attributes)]

#[miri_run]
Expand Down Expand Up @@ -33,6 +33,15 @@ fn cross_crate_fn_call() -> i64 {
if 1i32.is_positive() { 1 } else { 0 }
}

const fn foo(i: i64) -> i64 { *&i + 1 }

#[miri_run]
fn const_fn_call() -> i64 {
let x = 5 + foo(5);
assert_eq!(x, 11);
x
}

#[miri_run]
fn main() {
assert_eq!(call(), 2);
Expand Down
11 changes: 11 additions & 0 deletions tests/run-pass/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![feature(custom_attribute)]
#![allow(dead_code, unused_attributes)]

const A: usize = *&5;

#[miri_run]
fn foo() -> usize {
A
}

fn main() {}