Skip to content

Work with MIR-libstd #171

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

Merged
merged 20 commits into from
May 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ab90500
Make println!("String") work
RalfJung May 24, 2017
1ae01b4
add instructions for how to compile libstd with xargo
RalfJung May 24, 2017
33d42f4
also support writing to stderr
RalfJung May 25, 2017
452cc9b
handle statics with linkage: initialize them with NULL
RalfJung May 25, 2017
238211e
implement TLS
RalfJung May 25, 2017
a66f359
support TLS destructors
RalfJung May 26, 2017
14b16dc
use proper span for TLS dtors; fix some nits
RalfJung May 26, 2017
55438fe
unify the way we intercept missing MIR and C ABI calls; only intercep…
RalfJung May 26, 2017
720c5f8
implement __rust_maybe_catch_panic
RalfJung May 27, 2017
cd6e3e6
If a "start" lang item incl. MIR is present, run that instead of runn…
RalfJung May 27, 2017
99433a1
improve fn pointer signature check to allow some casts that should be…
RalfJung May 27, 2017
1241938
test suite now also passes on MIR-libstd
RalfJung May 27, 2017
633a34d
re-disable aux_test -- it passes here, but not on Travis
RalfJung May 27, 2017
24a9a14
fix various small nits
RalfJung May 30, 2017
dad9547
test thread-local key with no dtor
RalfJung May 30, 2017
cdf7a05
latest rust nightly contains all the bits needed to re-compile libstd
RalfJung May 30, 2017
b8e0b79
add a test for output string formatting
RalfJung May 30, 2017
b44babf
allocate return pointer only when we start the program via the start …
RalfJung May 30, 2017
2b37d50
simplify determining size and alignment of a pointer
RalfJung May 31, 2017
d06c165
simplify xargo instructions
RalfJung May 31, 2017
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
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,29 @@ Then, inside your own project, use `cargo +nightly miri` to run your project, if
a bin project, or run `cargo +nightly miri test` to run all tests in your project
through miri.

## Running miri with full libstd

Per default libstd does not contain the MIR of non-polymorphic functions. When
miri hits a call to such a function, execution terminates. To fix this, it is
possible to compile libstd with full MIR:

```sh
rustup component add rust-src
chmod +x -R ~/.rustup/toolchains/*/lib/rustlib/src/rust/src/jemalloc/include/jemalloc/
cargo install xargo
cd xargo/
RUSTFLAGS='-Zalways-encode-mir' xargo build
```

Now you can run miri against the libstd compiled by xargo:

```sh
cargo run --bin miri -- --sysroot ~/.xargo/HOST tests/run-pass/vecs.rs
```

Notice that you will have to re-run the last step of the preparations above when
your toolchain changes (e.g., when you update the nightly).

## Contributing and getting help

Check out the issues on this GitHub repository for some ideas. There's lots that
Expand Down
6 changes: 4 additions & 2 deletions src/bin/miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) {
if i.attrs.iter().any(|attr| attr.name().map_or(false, |n| n == "test")) {
let did = self.1.hir.body_owner_def_id(body_id);
println!("running test: {}", self.1.hir.def_path(did).to_string(self.1));
miri::eval_main(self.1, did, self.0);
miri::eval_main(self.1, did, None, self.0);
self.2.session.abort_if_errors();
}
}
Expand All @@ -95,7 +95,9 @@ fn after_analysis<'a, 'tcx>(state: &mut CompileState<'a, 'tcx>) {
state.hir_crate.unwrap().visit_all_item_likes(&mut Visitor(limits, tcx, state));
} else if let Some((entry_node_id, _)) = *state.session.entry_fn.borrow() {
let entry_def_id = tcx.hir.local_def_id(entry_node_id);
miri::eval_main(tcx, entry_def_id, limits);
let start_wrapper = tcx.lang_items.start_fn().and_then(|start_fn|
if tcx.is_mir_available(start_fn) { Some(start_fn) } else { None });
miri::eval_main(tcx, entry_def_id, start_wrapper, limits);

state.session.abort_if_errors();
} else {
Expand Down
8 changes: 8 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ pub enum EvalError<'tcx> {
},
ExecutionTimeLimitReached,
StackFrameLimitReached,
OutOfTls,
TlsOutOfBounds,
AbiViolation(String),
AlignmentCheckFailed {
required: u64,
has: u64,
Expand Down Expand Up @@ -101,6 +104,11 @@ impl<'tcx> Error for EvalError<'tcx> {
"reached the configured maximum execution time",
EvalError::StackFrameLimitReached =>
"reached the configured maximum number of stack frames",
EvalError::OutOfTls =>
"reached the maximum number of representable TLS keys",
EvalError::TlsOutOfBounds =>
"accessed an invalid (unallocated) TLS key",
EvalError::AbiViolation(ref msg) => msg,
EvalError::AlignmentCheckFailed{..} =>
"tried to execute a misaligned read or write",
EvalError::CalledClosureAsFunction =>
Expand Down
145 changes: 99 additions & 46 deletions src/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ impl Default for ResourceLimits {

impl<'a, 'tcx> EvalContext<'a, 'tcx> {
pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>, limits: ResourceLimits) -> Self {
// Register array drop glue code
let source_info = mir::SourceInfo {
span: DUMMY_SP,
scope: mir::ARGUMENT_VISIBILITY_SCOPE
Expand Down Expand Up @@ -852,7 +853,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
let fn_ptr = self.memory.create_fn_alloc(instance);
self.write_value(Value::ByVal(PrimVal::Ptr(fn_ptr)), dest, dest_ty)?;
},
ref other => bug!("reify fn pointer on {:?}", other),
ref other => bug!("closure fn pointer on {:?}", other),
},
}
}
Expand Down Expand Up @@ -1557,6 +1558,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}

pub(super) fn dump_local(&self, lvalue: Lvalue<'tcx>) {
// Debug output
if let Lvalue::Local { frame, local, field } = lvalue {
let mut allocs = Vec::new();
let mut msg = format!("{:?}", local);
Expand Down Expand Up @@ -1676,62 +1678,113 @@ impl<'tcx> Frame<'tcx> {

pub fn eval_main<'a, 'tcx: 'a>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
def_id: DefId,
main_id: DefId,
start_wrapper: Option<DefId>,
limits: ResourceLimits,
) {
let mut ecx = EvalContext::new(tcx, limits);
let instance = ty::Instance::mono(tcx, def_id);
let mir = ecx.load_mir(instance.def).expect("main function's MIR not found");

if !mir.return_ty.is_nil() || mir.arg_count != 0 {
let msg = "miri does not support main functions without `fn()` type signatures";
tcx.sess.err(&EvalError::Unimplemented(String::from(msg)).to_string());
return;
}

ecx.push_stack_frame(
instance,
DUMMY_SP,
mir,
Lvalue::from_ptr(Pointer::zst_ptr()),
StackPopCleanup::None,
).expect("could not allocate first stack frame");

loop {
match ecx.step() {
Ok(true) => {}
Ok(false) => {
let leaks = ecx.memory.leak_report();
if leaks != 0 {
tcx.sess.err("the evaluated program leaked memory");
}
return;
fn run_main<'a, 'tcx: 'a>(
ecx: &mut EvalContext<'a, 'tcx>,
main_id: DefId,
start_wrapper: Option<DefId>,
) -> EvalResult<'tcx> {
let main_instance = ty::Instance::mono(ecx.tcx, main_id);
let main_mir = ecx.load_mir(main_instance.def)?;
let mut cleanup_ptr = None; // Pointer to be deallocated when we are done

if !main_mir.return_ty.is_nil() || main_mir.arg_count != 0 {
return Err(EvalError::Unimplemented("miri does not support main functions without `fn()` type signatures".to_owned()));
}

if let Some(start_id) = start_wrapper {
let start_instance = ty::Instance::mono(ecx.tcx, start_id);
let start_mir = ecx.load_mir(start_instance.def)?;

if start_mir.arg_count != 3 {
return Err(EvalError::AbiViolation(format!("'start' lang item should have three arguments, but has {}", start_mir.arg_count)));
}
Err(e) => {
report(tcx, &ecx, e);
return;

// Return value
let ret_ptr = ecx.memory.allocate(ecx.tcx.data_layout.pointer_size.bytes(), ecx.tcx.data_layout.pointer_align.abi())?;
cleanup_ptr = Some(ret_ptr);

// Push our stack frame
ecx.push_stack_frame(
start_instance,
start_mir.span,
start_mir,
Lvalue::from_ptr(ret_ptr),
StackPopCleanup::None,
)?;

let mut args = ecx.frame().mir.args_iter();

// First argument: pointer to main()
let main_ptr = ecx.memory.create_fn_alloc(main_instance);
let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?;
let main_ty = main_instance.def.def_ty(ecx.tcx);
let main_ptr_ty = ecx.tcx.mk_fn_ptr(main_ty.fn_sig());
ecx.write_value(Value::ByVal(PrimVal::Ptr(main_ptr)), dest, main_ptr_ty)?;

// Second argument (argc): 0
let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?;
let ty = ecx.tcx.types.isize;
ecx.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, ty)?;

// Third argument (argv): 0
let dest = ecx.eval_lvalue(&mir::Lvalue::Local(args.next().unwrap()))?;
let ty = ecx.tcx.mk_imm_ptr(ecx.tcx.mk_imm_ptr(ecx.tcx.types.u8));
ecx.write_value(Value::ByVal(PrimVal::Bytes(0)), dest, ty)?;
} else {
ecx.push_stack_frame(
main_instance,
main_mir.span,
main_mir,
Lvalue::from_ptr(Pointer::zst_ptr()),
StackPopCleanup::None,
)?;
}

while ecx.step()? {}
if let Some(cleanup_ptr) = cleanup_ptr {
ecx.memory.deallocate(cleanup_ptr)?;
}
return Ok(());
}

let mut ecx = EvalContext::new(tcx, limits);
match run_main(&mut ecx, main_id, start_wrapper) {
Ok(()) => {
let leaks = ecx.memory.leak_report();
if leaks != 0 {
tcx.sess.err("the evaluated program leaked memory");
}
}
Err(e) => {
report(tcx, &ecx, e);
}
}
}

fn report(tcx: TyCtxt, ecx: &EvalContext, e: EvalError) {
let frame = ecx.stack().last().expect("stackframe was empty");
let block = &frame.mir.basic_blocks()[frame.block];
let span = if frame.stmt < block.statements.len() {
block.statements[frame.stmt].source_info.span
} else {
block.terminator().source_info.span
};
let mut err = tcx.sess.struct_span_err(span, &e.to_string());
for &Frame { instance, span, .. } in ecx.stack().iter().rev() {
if tcx.def_key(instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr {
err.span_note(span, "inside call to closure");
continue;
if let Some(frame) = ecx.stack().last() {
let block = &frame.mir.basic_blocks()[frame.block];
let span = if frame.stmt < block.statements.len() {
block.statements[frame.stmt].source_info.span
} else {
block.terminator().source_info.span
};
let mut err = tcx.sess.struct_span_err(span, &e.to_string());
for &Frame { instance, span, .. } in ecx.stack().iter().rev() {
if tcx.def_key(instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr {
err.span_note(span, "inside call to closure");
continue;
}
err.span_note(span, &format!("inside call to {}", instance));
}
err.span_note(span, &format!("inside call to {}", instance));
err.emit();
} else {
tcx.sess.err(&e.to_string());
}
err.emit();
}

// TODO(solson): Upstream these methods into rustc::ty::layout.
Expand Down
9 changes: 9 additions & 0 deletions src/lvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ impl<'tcx> Global<'tcx> {
initialized: false,
}
}

pub(super) fn initialized(ty: Ty<'tcx>, value: Value, mutable: bool) -> Self {
Global {
value,
mutable,
ty,
initialized: true,
}
}
}

impl<'a, 'tcx> EvalContext<'a, 'tcx> {
Expand Down
73 changes: 73 additions & 0 deletions src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ impl Pointer {
pub fn never_ptr() -> Self {
Pointer::new(NEVER_ALLOC_ID, 0)
}

pub fn is_null_ptr(&self) -> bool {
return *self == Pointer::from_int(0)
}
}

pub type TlsKey = usize;

#[derive(Copy, Clone, Debug)]
pub struct TlsEntry<'tcx> {
data: Pointer, // will eventually become a map from thread IDs to pointers
dtor: Option<ty::Instance<'tcx>>,
}

////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -149,6 +161,12 @@ pub struct Memory<'a, 'tcx> {
/// A cache for basic byte allocations keyed by their contents. This is used to deduplicate
/// allocations for string and bytestring literals.
literal_alloc_cache: HashMap<Vec<u8>, AllocId>,

/// pthreads-style Thread-local storage. We only have one thread, so this is just a map from TLS keys (indices into the vector) to the pointer stored there.
thread_local: HashMap<TlsKey, TlsEntry<'tcx>>,

/// The Key to use for the next thread-local allocation.
next_thread_local: TlsKey,
}

const ZST_ALLOC_ID: AllocId = AllocId(0);
Expand All @@ -167,6 +185,8 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
packed: BTreeSet::new(),
static_alloc: HashSet::new(),
literal_alloc_cache: HashMap::new(),
thread_local: HashMap::new(),
next_thread_local: 0,
}
}

Expand Down Expand Up @@ -345,6 +365,59 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
pub(crate) fn clear_packed(&mut self) {
self.packed.clear();
}

pub(crate) fn create_tls_key(&mut self, dtor: Option<ty::Instance<'tcx>>) -> TlsKey {
let new_key = self.next_thread_local;
self.next_thread_local += 1;
self.thread_local.insert(new_key, TlsEntry { data: Pointer::from_int(0), dtor });
trace!("New TLS key allocated: {} with dtor {:?}", new_key, dtor);
return new_key;
}

pub(crate) fn delete_tls_key(&mut self, key: TlsKey) -> EvalResult<'tcx> {
return match self.thread_local.remove(&key) {
Some(_) => {
trace!("TLS key {} removed", key);
Ok(())
},
None => Err(EvalError::TlsOutOfBounds)
}
}

pub(crate) fn load_tls(&mut self, key: TlsKey) -> EvalResult<'tcx, Pointer> {
return match self.thread_local.get(&key) {
Some(&TlsEntry { data, .. }) => {
trace!("TLS key {} loaded: {:?}", key, data);
Ok(data)
},
None => Err(EvalError::TlsOutOfBounds)
}
}

pub(crate) fn store_tls(&mut self, key: TlsKey, new_data: Pointer) -> EvalResult<'tcx> {
return match self.thread_local.get_mut(&key) {
Some(&mut TlsEntry { ref mut data, .. }) => {
trace!("TLS key {} stored: {:?}", key, new_data);
*data = new_data;
Ok(())
},
None => Err(EvalError::TlsOutOfBounds)
}
}

// Returns a dtor and its argument, if one is supposed to run
pub(crate) fn fetch_tls_dtor(&mut self) -> Option<(ty::Instance<'tcx>, Pointer)> {
for (_, &mut TlsEntry { ref mut data, dtor }) in self.thread_local.iter_mut() {
if !data.is_null_ptr() {
if let Some(dtor) = dtor {
let old_data = *data;
*data = Pointer::from_int(0);
return Some((dtor, old_data));
}
}
}
return None;
}
}

// The derived `Ord` impl sorts first by the first field, then, if the fields are the same
Expand Down
Loading