If a "start" lang item incl. MIR is present, run that instead of running main directly

This fixes the memory leaks when running a simple "Hello World" with MIR-libstd
This commit is contained in:
Ralf Jung 2017-05-26 17:36:16 -07:00
parent 720c5f874e
commit cd6e3e6431
5 changed files with 134 additions and 65 deletions

View File

@ -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();
}
}
@ -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 {

View File

@ -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
@ -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),
},
}
}
@ -1676,62 +1677,120 @@ 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");
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)?;
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;
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)));
}
// Push our stack frame
ecx.push_stack_frame(
start_instance,
start_mir.span,
start_mir,
Lvalue::from_ptr(Pointer::zst_ptr()), // we'll fix the return lvalue later
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,
)?;
}
// Allocate memory for the return value. We have to do this when a stack frame was already pushed as the type code below
// calls EvalContext::substs, which needs a frame to be allocated (?!?)
let ret_ptr = {
let ty = ecx.tcx.types.isize;
let layout = ecx.type_layout(ty)?;
let size = layout.size(&ecx.tcx.data_layout).bytes();
let align = layout.align(&ecx.tcx.data_layout).pref(); // FIXME is this right?
ecx.memory.allocate(size, align)?
};
ecx.frame_mut().return_lvalue = Lvalue::from_ptr(ret_ptr);
loop {
if !ecx.step()? {
ecx.memory.deallocate(ret_ptr)?;
return Ok(());
}
}
}
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;
}
Err(e) => {
report(tcx, &ecx, e);
return;
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.

View File

@ -43,14 +43,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
Lvalue::from_ptr(Pointer::zst_ptr()),
StackPopCleanup::None,
)?;
if let Some(arg_local) = self.frame().mir.args_iter().next() {
let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?;
let ty = self.tcx.mk_mut_ptr(self.tcx.types.u8);
self.write_value(Value::ByVal(PrimVal::Ptr(ptr)), dest, ty)?;
} else {
return Err(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned()));
}
let arg_local = self.frame().mir.args_iter().next().ok_or(EvalError::AbiViolation("TLS dtor does not take enough arguments.".to_owned()))?;
let dest = self.eval_lvalue(&mir::Lvalue::Local(arg_local))?;
let ty = self.tcx.mk_mut_ptr(self.tcx.types.u8);
self.write_value(Value::ByVal(PrimVal::Ptr(ptr)), dest, ty)?;
return Ok(true);
}
return Ok(false);

View File

@ -637,23 +637,33 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.write_primval(dest, PrimVal::Bytes(result as u128), dest_ty)?;
}
// unix panic code inside libstd will read the return value of this function
"pthread_rwlock_rdlock" => {
// Some things needed for sys::thread initialization to go through
"signal" | "sigaction" | "sigaltstack" => {
self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?;
}
"sysconf" => {
let name = self.value_to_primval(args[0], usize)?.to_u64()?;
trace!("sysconf() called with name {}", name);
let result = match name {
30 => 4096, // _SC_PAGESIZE
_ => return Err(EvalError::Unimplemented(format!("Unimplemented sysconf name: {}", name)))
};
self.write_primval(dest, PrimVal::Bytes(result), dest_ty)?;
}
"mmap" => {
// This is a horrible hack, but well... the guard page mechanism calls mmap and expects a particular return value, so we give it that value
let addr = args[0].read_ptr(&self.memory)?;
self.write_primval(dest, PrimVal::Ptr(addr), dest_ty)?;
}
// Hook pthread calls that go to the thread-local storage memory subsystem
"pthread_key_create" => {
let key_ptr = args[0].read_ptr(&self.memory)?;
// Extract the function type out of the signature (that seems easier than constructing it ourselves...)
let dtor_fn_ty = match self.operand_ty(&arg_operands[1]).sty {
TypeVariants::TyAdt(_, ref substs) => {
substs.type_at(0)
}
_ => return Err(EvalError::AbiViolation("Wrong signature used for pthread_key_create: Second argument must be option of a function pointer.".to_owned()))
};
let dtor_ptr = self.value_to_primval(args[1], dtor_fn_ty)?.to_ptr()?;
let dtor_ptr = args[1].read_ptr(&self.memory)?;
// TODO: The null-pointer case here is entirely untested
let dtor = if dtor_ptr.is_null_ptr() { None } else { Some(self.memory.get_fn(dtor_ptr.alloc_id)?) };
@ -699,8 +709,10 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?;
}
// Stub out all the other pthread calls to just return 0
link_name if link_name.starts_with("pthread_") => {
warn!("ignoring C ABI call: {}", link_name);
self.write_primval(dest, PrimVal::Bytes(0), dest_ty)?;
},
_ => {

View File

@ -1,7 +1,7 @@
#![feature(custom_attribute, attr_literals)]
#![miri(memory_size=0)]
#![miri(memory_size=20)]
fn main() {
let _x = [42; 10];
//~^ERROR tried to allocate 40 more bytes, but only 0 bytes are free of the 0 byte memory
//~^ERROR tried to allocate 40 more bytes, but only
}