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

Add and use helper function for calling a machine function and passing it some arguments #1083

Merged
merged 2 commits into from
Nov 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
183 changes: 82 additions & 101 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,8 @@ use rand::SeedableRng;
use rustc::hir::def_id::DefId;
use rustc::ty::layout::{LayoutOf, Size};
use rustc::ty::{self, TyCtxt};
use syntax::source_map::DUMMY_SP;

use crate::{
EnvVars, Evaluator, FnVal, HelpersEvalContextExt, InterpCx, InterpError,
InterpResult, MemoryExtra, MiriMemoryKind, Pointer, Scalar, StackPopCleanup, Tag,
TlsEvalContextExt, MPlaceTy
};
use crate::*;

/// Configuration needed to spawn a Miri instance.
#[derive(Clone)]
Expand Down Expand Up @@ -65,122 +60,108 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(main_ret_ty))),
)
.unwrap();
let start_mir = ecx.load_mir(start_instance.def, None)?;

if start_mir.arg_count != 3 {
bug!(
"'start' lang item should have three arguments, but has {}",
start_mir.arg_count
);
}

// Return value (in static memory so that it does not count as leak).
let ret = ecx.layout_of(start_mir.return_ty())?;
let ret_ptr = ecx.allocate(ret, MiriMemoryKind::Static.into());

// Push our stack frame.
ecx.push_stack_frame(
start_instance,
// There is no call site.
DUMMY_SP,
start_mir,
Some(ret_ptr.into()),
StackPopCleanup::None { cleanup: true },
)?;

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

// First argument: pointer to `main()`.
let main_ptr = ecx
.memory
.create_fn_alloc(FnVal::Instance(main_instance));
let dest = ecx.local_place(args.next().unwrap())?;
ecx.write_scalar(Scalar::Ptr(main_ptr), dest)?;

// Second argument (argc): `1`.
let dest = ecx.local_place(args.next().unwrap())?;
let argc = Scalar::from_uint(config.args.len() as u128, dest.layout.size);
ecx.write_scalar(argc, dest)?;
// Store argc for macOS's `_NSGetArgc`.
{
let argc_place = ecx.allocate(dest.layout, MiriMemoryKind::Env.into());
ecx.write_scalar(argc, argc_place.into())?;
ecx.machine.argc = Some(argc_place.ptr);
}

// Second argument (argc): length of `config.args`.
let argc = Scalar::from_uint(config.args.len() as u128, ecx.pointer_size());
// Third argument (`argv`): created from `config.args`.
let dest = ecx.local_place(args.next().unwrap())?;
// For Windows, construct a command string with all the aguments.
let mut cmd = String::new();
for arg in config.args.iter() {
if !cmd.is_empty() {
cmd.push(' ');
let argv = {
// For Windows, construct a command string with all the aguments (before we take apart `config.args`).
let mut cmd = String::new();
for arg in config.args.iter() {
if !cmd.is_empty() {
cmd.push(' ');
}
cmd.push_str(&*shell_escape::windows::escape(arg.as_str().into()));
}
cmd.push_str(&*shell_escape::windows::escape(arg.as_str().into()));
}
// Don't forget `0` terminator.
cmd.push(std::char::from_u32(0).unwrap());
// Collect the pointers to the individual strings.
let mut argvs = Vec::<Pointer<Tag>>::new();
for arg in config.args {
// Add `0` terminator.
let mut arg = arg.into_bytes();
arg.push(0);
argvs.push(
ecx.memory
.allocate_static_bytes(arg.as_slice(), MiriMemoryKind::Static.into()),
);
}
// Make an array with all these pointers, in the Miri memory.
let argvs_layout = ecx.layout_of(
tcx.mk_array(tcx.mk_imm_ptr(tcx.types.u8), argvs.len() as u64),
)?;
let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Env.into());
for (idx, arg) in argvs.into_iter().enumerate() {
let place = ecx.mplace_field(argvs_place, idx as u64)?;
ecx.write_scalar(Scalar::Ptr(arg), place.into())?;
}
ecx.memory
.mark_immutable(argvs_place.ptr.assert_ptr().alloc_id)?;
// Write a pointer to that place as the argument.
let argv = argvs_place.ptr;
ecx.write_scalar(argv, dest)?;
// Store `argv` for macOS `_NSGetArgv`.
{
let argv_place = ecx.allocate(dest.layout, MiriMemoryKind::Env.into());
ecx.write_scalar(argv, argv_place.into())?;
ecx.machine.argv = Some(argv_place.ptr);
}
// Store command line as UTF-16 for Windows `GetCommandLineW`.
{
let cmd_utf16: Vec<u16> = cmd.encode_utf16().collect();
let cmd_type = tcx.mk_array(tcx.types.u16, cmd_utf16.len() as u64);
let cmd_place = ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Env.into());
ecx.machine.cmd_line = Some(cmd_place.ptr);
// Store the UTF-16 string. We just allocated so we know the bounds are fine.
let char_size = Size::from_bytes(2);
for (idx, &c) in cmd_utf16.iter().enumerate() {
let place = ecx.mplace_field(cmd_place, idx as u64)?;
ecx.write_scalar(Scalar::from_uint(c, char_size), place.into())?;
// Don't forget `0` terminator.
cmd.push(std::char::from_u32(0).unwrap());
// Collect the pointers to the individual strings.
let mut argvs = Vec::<Pointer<Tag>>::new();
for arg in config.args {
// Add `0` terminator.
let mut arg = arg.into_bytes();
arg.push(0);
argvs.push(
ecx.memory
.allocate_static_bytes(arg.as_slice(), MiriMemoryKind::Static.into()),
);
}
}
// Make an array with all these pointers, in the Miri memory.
let argvs_layout = ecx.layout_of(
tcx.mk_array(tcx.mk_imm_ptr(tcx.types.u8), argvs.len() as u64),
)?;
let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Env.into());
for (idx, arg) in argvs.into_iter().enumerate() {
let place = ecx.mplace_field(argvs_place, idx as u64)?;
ecx.write_scalar(Scalar::Ptr(arg), place.into())?;
}
ecx.memory
.mark_immutable(argvs_place.ptr.assert_ptr().alloc_id)?;
// A pointer to that place is the argument.
let argv = argvs_place.ptr;
// Store `argc` and `argv` for macOS `_NSGetArg{c,v}`.
{
let argc_place = ecx.allocate(
ecx.layout_of(tcx.types.isize)?,
MiriMemoryKind::Env.into(),
);
ecx.write_scalar(argc, argc_place.into())?;
ecx.machine.argc = Some(argc_place.ptr);

let argv_place = ecx.allocate(
ecx.layout_of(tcx.mk_imm_ptr(tcx.types.unit))?,
MiriMemoryKind::Env.into(),
);
ecx.write_scalar(argv, argv_place.into())?;
ecx.machine.argv = Some(argv_place.ptr);
}
// Store command line as UTF-16 for Windows `GetCommandLineW`.
{
let cmd_utf16: Vec<u16> = cmd.encode_utf16().collect();
let cmd_type = tcx.mk_array(tcx.types.u16, cmd_utf16.len() as u64);
let cmd_place = ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Env.into());
ecx.machine.cmd_line = Some(cmd_place.ptr);
// Store the UTF-16 string. We just allocated so we know the bounds are fine.
let char_size = Size::from_bytes(2);
for (idx, &c) in cmd_utf16.iter().enumerate() {
let place = ecx.mplace_field(cmd_place, idx as u64)?;
ecx.write_scalar(Scalar::from_uint(c, char_size), place.into())?;
}
}
argv
};

args.next().expect_none("start lang item has more arguments than expected");
// Return place (in static memory so that it does not count as leak).
let ret_place = ecx.allocate(
ecx.layout_of(tcx.types.isize)?,
MiriMemoryKind::Static.into(),
);
// Call start function.
ecx.call_function(
start_instance,
&[main_ptr.into(), argc, argv],
Some(ret_place.into()),
StackPopCleanup::None { cleanup: true },
)?;

// Set the last_error to 0
let errno_layout = ecx.layout_of(tcx.types.u32)?;
let errno_place = ecx.allocate(errno_layout, MiriMemoryKind::Static.into());
ecx.write_scalar(Scalar::from_u32(0), errno_place.into())?;
ecx.machine.last_error = Some(errno_place);

Ok((ecx, ret_ptr))
Ok((ecx, ret_place))
}

/// Evaluates the main function specified by `main_id`.
/// Returns `Some(return_code)` if program executed completed.
/// Returns `None` if an evaluation error occured.
pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) -> Option<i64> {
let (mut ecx, ret_ptr) = match create_ecx(tcx, main_id, config) {
let (mut ecx, ret_place) = match create_ecx(tcx, main_id, config) {
Ok(v) => v,
Err(mut err) => {
err.print_backtrace();
Expand All @@ -193,7 +174,7 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
ecx.run()?;
// Read the return code pointer *before* we run TLS destructors, to assert
// that it was written to by the time that `start` lang item returned.
let return_code = ecx.read_scalar(ret_ptr.into())?.not_undef()?.to_machine_isize(&ecx)?;
let return_code = ecx.read_scalar(ret_place.into())?.not_undef()?.to_machine_isize(&ecx)?;
ecx.run_tls_dtors()?;
Ok(return_code)
})();
Expand Down
35 changes: 35 additions & 0 deletions src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{mem, iter};
use std::ffi::{OsStr, OsString};

use syntax::source_map::DUMMY_SP;
use rustc::hir::def_id::{DefId, CRATE_DEF_INDEX};
use rustc::mir;
use rustc::ty::{
Expand Down Expand Up @@ -118,6 +119,40 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.memory.write_bytes(ptr, data.iter().copied())
}

/// Call a function: Push the stack frame and pass the arguments.
/// For now, arguments must be scalars (so that the caller does not have to know the layout).
fn call_function(
&mut self,
f: ty::Instance<'tcx>,
args: &[Scalar<Tag>],
dest: Option<PlaceTy<'tcx, Tag>>,
stack_pop: StackPopCleanup,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();

// Push frame.
let mir = this.load_mir(f.def, None)?;
this.push_stack_frame(
f,
DUMMY_SP, // There is no call site.
Copy link
Contributor

Choose a reason for hiding this comment

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

That's not true in general. You can take the current frame's current statement/terminator and get a span from that. If there are no frames, only then we definitely can't have a span because nothing calls the start function

Copy link
Member Author

Choose a reason for hiding this comment

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

As far as I can see, none of the code I am replacing did that correctly so far. They used DUMMY_SP or the span of the callee MIR, the latter being just blatantly wrong.

To avoid code duplication I'd prefer to add a Frame::current_source_info method on the rustc side and use that here. I'll add that to the rustc patch that I am working on anyway for #1070.

Would you prefer to block this PR on that? Either way is fine for me.

Copy link
Contributor

Choose a reason for hiding this comment

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

nah, this can totally happen later, you're right this is preexisting.

mir,
dest,
stack_pop,
)?;

// Initialize arguments.
let mut callee_args = this.frame().body.args_iter();
for arg in args {
let callee_arg = this.local_place(
callee_args.next().expect("callee has fewer arguments than expected")
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
)?;
this.write_scalar(*arg, callee_arg)?;
}
callee_args.next().expect_none("callee has more arguments than expected");

Ok(())
}

/// Visits the memory covered by `place`, sensitive to freezing: the 3rd parameter
/// will be true if this is frozen, false if this is in an `UnsafeCell`.
fn visit_freeze_sensitive(
Expand Down
31 changes: 10 additions & 21 deletions src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,37 +230,26 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
dest: PlaceTy<'tcx, Tag>,
) -> InterpResult<'tcx> {
trace!("box_alloc for {:?}", dest.layout.ty);
let layout = ecx.layout_of(dest.layout.ty.builtin_deref(false).unwrap().ty)?;
// First argument: `size`.
// (`0` is allowed here -- this is expected to be handled by the lang item).
let size = Scalar::from_uint(layout.size.bytes(), ecx.pointer_size());

// Second argument: `align`.
let align = Scalar::from_uint(layout.align.abi.bytes(), ecx.pointer_size());

// Call the `exchange_malloc` lang item.
let malloc = ecx.tcx.lang_items().exchange_malloc_fn().unwrap();
let malloc = ty::Instance::mono(ecx.tcx.tcx, malloc);
let malloc_mir = ecx.load_mir(malloc.def, None)?;
ecx.push_stack_frame(
ecx.call_function(
malloc,
malloc_mir.span,
malloc_mir,
&[size, align],
Some(dest),
// Don't do anything when we are done. The `statement()` function will increment
// the old stack frame's stmt counter to the next statement, which means that when
// `exchange_malloc` returns, we go on evaluating exactly where we want to be.
StackPopCleanup::None { cleanup: true },
)?;

let mut args = ecx.frame().body.args_iter();
let layout = ecx.layout_of(dest.layout.ty.builtin_deref(false).unwrap().ty)?;

// First argument: `size`.
// (`0` is allowed here -- this is expected to be handled by the lang item).
let arg = ecx.local_place(args.next().unwrap())?;
let size = layout.size.bytes();
ecx.write_scalar(Scalar::from_uint(size, arg.layout.size), arg)?;

// Second argument: `align`.
let arg = ecx.local_place(args.next().unwrap())?;
let align = layout.align.abi.bytes();
ecx.write_scalar(Scalar::from_uint(align, arg.layout.size), arg)?;

// No more arguments.
args.next().expect_none("`exchange_malloc` lang item has more arguments than expected");
Ok(())
}

Expand Down
21 changes: 2 additions & 19 deletions src/shims/panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx

/// Handles the special "miri_start_panic" intrinsic, which is called
/// by libpanic_unwind to delegate the actual unwinding process to Miri.
#[inline(always)]
fn handle_miri_start_panic(
&mut self,
args: &[OpTy<'tcx, Tag>],
Expand All @@ -57,7 +56,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
return Ok(())
}

#[inline(always)]
fn handle_catch_panic(
&mut self,
args: &[OpTy<'tcx, Tag>],
Expand All @@ -83,30 +81,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
// Now we make a function call, and pass `f_arg` as first and only argument.
let f_instance = this.memory.get_fn(f)?.as_instance()?;
trace!("__rust_maybe_catch_panic: {:?}", f_instance);
// TODO: consider making this reusable? `InterpCx::step` does something similar
// for the TLS destructors, and of course `eval_main`.
let mir = this.load_mir(f_instance.def, None)?;
let ret_place =
MPlaceTy::dangling(this.layout_of(tcx.mk_unit())?, this).into();
this.push_stack_frame(
this.call_function(
f_instance,
mir.span,
mir,
&[f_arg],
Some(ret_place),
// Directly return to caller.
StackPopCleanup::Goto { ret: Some(ret), unwind: None },
)?;

let mut args = this.frame().body.args_iter();
// First argument.
let arg_local = args
.next()
.expect("Argument to __rust_maybe_catch_panic does not take enough arguments.");
let arg_dest = this.local_place(arg_local)?;
this.write_scalar(f_arg, arg_dest)?;
// No more arguments.
args.next().expect_none("__rust_maybe_catch_panic argument has more arguments than expected");

// We ourselves will return `0`, eventually (will be overwritten if we catch a panic).
this.write_null(dest)?;

Expand All @@ -124,7 +108,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
return Ok(());
}

#[inline(always)]
fn handle_stack_pop(
&mut self,
mut extra: FrameData<'tcx>,
Expand Down
Loading