Skip to content

rustup to rustc 1.15.0-dev (ace092f56 2016-12-13) #93

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 6 commits into from
Dec 15, 2016
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
22 changes: 12 additions & 10 deletions src/bin/miri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extern crate syntax;
#[macro_use] extern crate log;

use rustc::session::Session;
use rustc_driver::{CompilerCalls, Compilation};
use rustc_driver::CompilerCalls;
use rustc_driver::driver::{CompileState, CompileController};
use syntax::ast::{MetaItemKind, NestedMetaItemKind};

Expand All @@ -21,7 +21,6 @@ impl<'a> CompilerCalls<'a> for MiriCompilerCalls {
let mut control = CompileController::basic();
control.after_hir_lowering.callback = Box::new(after_hir_lowering);
control.after_analysis.callback = Box::new(after_analysis);
control.after_analysis.stop = Compilation::Stop;
control
}
}
Expand All @@ -35,14 +34,16 @@ fn after_analysis(state: &mut CompileState) {
state.session.abort_if_errors();

let tcx = state.tcx.unwrap();
let (entry_node_id, _) = state.session.entry_fn.borrow()
.expect("no main or start function found");
let entry_def_id = tcx.map.local_def_id(entry_node_id);
let limits = resource_limits_from_attributes(state);
miri::run_mir_passes(tcx);
miri::eval_main(tcx, entry_def_id, limits);

state.session.abort_if_errors();
if let Some((entry_node_id, _)) = *state.session.entry_fn.borrow() {
let entry_def_id = tcx.map.local_def_id(entry_node_id);
let limits = resource_limits_from_attributes(state);
miri::run_mir_passes(tcx);
miri::eval_main(tcx, entry_def_id, limits);

state.session.abort_if_errors();
} else {
println!("no main function found, assuming auxiliary build");
}
}

fn resource_limits_from_attributes(state: &CompileState) -> miri::ResourceLimits {
Expand Down Expand Up @@ -134,6 +135,7 @@ fn main() {
args.push(sysroot_flag);
args.push(find_sysroot());
}
args.push("-Zalways-encode-mir".to_owned());

rustc_driver::run_compiler(&args, &mut MiriCompilerCalls, None, None);
}
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use syntax::codemap::Span;
pub enum EvalError<'tcx> {
FunctionPointerTyMismatch(Abi, &'tcx FnSig<'tcx>, &'tcx BareFnTy<'tcx>),
NoMirFor(String),
UnterminatedCString(Pointer),
DanglingPointerDeref,
InvalidMemoryAccess,
InvalidFunctionPointer,
Expand Down Expand Up @@ -119,6 +120,8 @@ impl<'tcx> Error for EvalError<'tcx> {
"tried to deallocate frozen memory",
EvalError::Layout(_) =>
"rustc layout computation failed",
EvalError::UnterminatedCString(_) =>
"attempted to get length of a null terminated string, but no null found before end of allocation",
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,22 @@ impl<'a, 'tcx> Memory<'a, 'tcx> {
Ok(())
}

pub fn read_c_str(&self, ptr: Pointer) -> EvalResult<'tcx, &[u8]> {
let alloc = self.get(ptr.alloc_id)?;
assert_eq!(ptr.offset as usize as u64, ptr.offset);
let offset = ptr.offset as usize;
match alloc.bytes[offset..].iter().position(|&c| c == 0) {
Some(size) => {
if self.relocations(ptr, (size + 1) as u64)?.count() != 0 {
return Err(EvalError::ReadPointerAsBytes);
}
self.check_defined(ptr, (size + 1) as u64)?;
Ok(&alloc.bytes[offset..offset + size])
},
None => Err(EvalError::UnterminatedCString(ptr)),
}
}

pub fn read_bytes(&self, ptr: Pointer, size: u64) -> EvalResult<'tcx, &[u8]> {
self.get_bytes(ptr, size, 1)
}
Expand Down
1 change: 1 addition & 0 deletions src/terminator/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
}

"atomic_load" |
"atomic_load_relaxed" |
"atomic_load_acq" |
"volatile_load" => {
let ty = substs.type_at(0);
Expand Down
55 changes: 45 additions & 10 deletions src/terminator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,16 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
ty::TyFnPtr(bare_fn_ty) => {
let fn_ptr = self.eval_operand_to_primval(func)?.to_ptr();
let (def_id, substs, abi, sig) = self.memory.get_fn(fn_ptr.alloc_id)?;
if abi != bare_fn_ty.abi || sig != bare_fn_ty.sig.skip_binder() {
let bare_sig = self.tcx.erase_late_bound_regions_and_normalize(&bare_fn_ty.sig);
let bare_sig = self.tcx.erase_regions(&bare_sig);
// transmuting function pointers in miri is fine as long as the number of
// arguments and the abi don't change.
// FIXME: also check the size of the arguments' type and the return type
// Didn't get it to work, since that triggers an assertion in rustc which
// checks whether the type has escaping regions
if abi != bare_fn_ty.abi ||
sig.variadic != bare_sig.variadic ||
sig.inputs().len() != bare_sig.inputs().len() {
return Err(EvalError::FunctionPointerTyMismatch(abi, sig, bare_fn_ty));
}
self.eval_fn_call(def_id, substs, bare_fn_ty, destination, args,
Expand Down Expand Up @@ -189,15 +198,15 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
use syntax::abi::Abi;
match fn_ty.abi {
Abi::RustIntrinsic => {
let ty = fn_ty.sig.0.output;
let ty = fn_ty.sig.0.output();
let layout = self.type_layout(ty)?;
let (ret, target) = destination.unwrap();
self.call_intrinsic(def_id, substs, arg_operands, ret, ty, layout, target)?;
Ok(())
}

Abi::C => {
let ty = fn_ty.sig.0.output;
let ty = fn_ty.sig.0.output();
let (ret, target) = destination.unwrap();
self.call_c_abi(def_id, arg_operands, ret, ty)?;
self.goto_block(target);
Expand Down Expand Up @@ -320,11 +329,6 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
.collect();
let args = args_res?;

if link_name.starts_with("pthread_") {
warn!("ignoring C ABI call: {}", link_name);
return Ok(());
}

let usize = self.tcx.types.usize;

match &link_name[..] {
Expand Down Expand Up @@ -371,6 +375,37 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
self.write_primval(dest, PrimVal::new(result as u64), dest_ty)?;
}

"memchr" => {
let ptr = args[0].read_ptr(&self.memory)?;
let val = self.value_to_primval(args[1], usize)?.to_u64() as u8;
let num = self.value_to_primval(args[2], usize)?.to_u64();
if let Some(idx) = self.memory.read_bytes(ptr, num)?.iter().position(|&c| c == val) {
let new_ptr = ptr.offset(idx as u64);
self.write_value(Value::ByVal(PrimVal::from_ptr(new_ptr)), dest, dest_ty)?;
} else {
self.write_value(Value::ByVal(PrimVal::new(0)), dest, dest_ty)?;
}
}

"getenv" => {
{
let name_ptr = args[0].read_ptr(&self.memory)?;
let name = self.memory.read_c_str(name_ptr)?;
info!("ignored env var request for `{:?}`", ::std::str::from_utf8(name));
}
self.write_value(Value::ByVal(PrimVal::new(0)), dest, dest_ty)?;
}

// unix panic code inside libstd will read the return value of this function
"pthread_rwlock_rdlock" => {
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 fine for our current experimentation, but I guess we'll need a plan for stuff like this eventually. For the real CTFE that gets put into rustc, it would probably make more sense to have cfg(const) sections that avoid needing the libc calls.

@eddyb Have you thought about this? Panicking, printing when panicking, and even Vec allocation all currently require us to hook libc calls, which might be considered too dirty...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

real ctfe can only evaluate const fn.

Also we can't do any cfg(const) if we don't want to end up compiling libstd twice (once for the target and once for const eval).

Copy link
Member

Choose a reason for hiding this comment

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

We want to make panic allowed in real CTFE in some form, though, don't we? Otherwise we can't call anything that has any code path leading to panic.

It doesn't need to be literally cfg(), but something to tell CTFE what to do instead of going into low-level code.

Copy link
Member

Choose a reason for hiding this comment

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

Allowing panicking in const fn implies that whatever functions panic!() calls (transitively) all need to be const fn unless we cut it off somewhere with a const fn-specific feature to tell CTFE to do something differently from what the native code does.

Copy link
Member

Choose a reason for hiding this comment

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

In fact, hooking libc calls is effectively just one version of "tell CTFE to do something differently", but we could perhaps do it in a more first-class, maintainable way if it was explicit in the code implementing panic.

But I'm still pretty unsure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The easiest thing would be to create an unstable attribute that libstd can use to annotate functions that CTFE should not go into this function, but instead do some magic.

self.write_primval(dest, PrimVal::new(0), dest_ty)?;
}

link_name if link_name.starts_with("pthread_") => {
warn!("ignoring C ABI call: {}", link_name);
return Ok(());
},

_ => {
return Err(EvalError::Unimplemented(format!("can't call C ABI function: {}", link_name)));
}
Expand Down Expand Up @@ -520,7 +555,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
let offset = idx * self.memory.pointer_size();
let fn_ptr = self.memory.read_ptr(vtable.offset(offset))?;
let (def_id, substs, _abi, sig) = self.memory.get_fn(fn_ptr.alloc_id)?;
*first_ty = sig.inputs[0];
*first_ty = sig.inputs()[0];
Ok((def_id, substs, Vec::new()))
} else {
Err(EvalError::VtableForArgumentlessMethod)
Expand Down Expand Up @@ -664,7 +699,7 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> {
// some values don't need to call a drop impl, so the value is null
if drop_fn != Pointer::from_int(0) {
let (def_id, substs, _abi, sig) = self.memory.get_fn(drop_fn.alloc_id)?;
let real_ty = sig.inputs[0];
let real_ty = sig.inputs()[0];
self.drop(Lvalue::from_ptr(ptr), real_ty, drop)?;
drop.push((def_id, Value::ByVal(PrimVal::from_ptr(ptr)), substs));
} else {
Expand Down
70 changes: 12 additions & 58 deletions tests/compiletest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ fn run_pass() {
compiletest::run_tests(&config);
}

fn miri_pass(path: &str, target: &str) {
let mut config = compiletest::default_config();
config.mode = "mir-opt".parse().expect("Invalid mode");
Copy link
Contributor Author

@oli-obk oli-obk Dec 14, 2016

Choose a reason for hiding this comment

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

mir-opt is not ideal

  1. it runs the optimization passes (but I think we run miri before those are run)
  2. it generates a final binary that is then executed on the host.

The clean solution would be to add a "compile-pass" test to compiletest

But it saves us this giant mess below, which would just get bigger if we started to play with auxiliary builds.

config.src_base = PathBuf::from(path);
config.target = target.to_owned();
config.rustc_path = PathBuf::from("target/debug/miri");
compiletest::run_tests(&config);
}

fn for_all_targets<F: FnMut(String)>(sysroot: &str, mut f: F) {
for target in std::fs::read_dir(format!("{}/lib/rustlib/", sysroot)).unwrap() {
let target = target.unwrap();
Expand Down Expand Up @@ -57,65 +66,10 @@ fn compile_test() {
};
run_pass();
for_all_targets(&sysroot, |target| {
let files = std::fs::read_dir("tests/run-pass").unwrap();
let files: Box<Iterator<Item=_>> = if let Ok(path) = std::env::var("MIRI_RUSTC_TEST") {
Box::new(files.chain(std::fs::read_dir(path).unwrap()))
} else {
Box::new(files)
};
let mut mir_not_found = 0;
let mut crate_not_found = 0;
let mut success = 0;
let mut failed = 0;
for file in files {
let file = file.unwrap();
let path = file.path();

if !file.metadata().unwrap().is_file() || !path.to_str().unwrap().ends_with(".rs") {
continue;
}

let stderr = std::io::stderr();
write!(stderr.lock(), "test [miri-pass] {} ... ", path.display()).unwrap();
let mut cmd = std::process::Command::new("target/debug/miri");
cmd.arg(path);
cmd.arg(format!("--target={}", target));
let libs = Path::new(&sysroot).join("lib");
let sysroot = libs.join("rustlib").join(&target).join("lib");
let paths = std::env::join_paths(&[libs, sysroot]).unwrap();
cmd.env(compiletest::procsrv::dylib_env_var(), paths);

match cmd.output() {
Ok(ref output) if output.status.success() => {
success += 1;
writeln!(stderr.lock(), "ok").unwrap()
},
Ok(output) => {
let output_err = std::str::from_utf8(&output.stderr).unwrap();
if let Some(text) = output_err.splitn(2, "no mir for `").nth(1) {
mir_not_found += 1;
let end = text.find('`').unwrap();
writeln!(stderr.lock(), "NO MIR FOR `{}`", &text[..end]).unwrap();
} else if let Some(text) = output_err.splitn(2, "can't find crate for `").nth(1) {
crate_not_found += 1;
let end = text.find('`').unwrap();
writeln!(stderr.lock(), "CAN'T FIND CRATE FOR `{}`", &text[..end]).unwrap();
} else {
failed += 1;
writeln!(stderr.lock(), "FAILED with exit code {:?}", output.status.code()).unwrap();
writeln!(stderr.lock(), "stdout: \n {}", std::str::from_utf8(&output.stdout).unwrap()).unwrap();
writeln!(stderr.lock(), "stderr: \n {}", output_err).unwrap();
}
}
Err(e) => {
writeln!(stderr.lock(), "FAILED: {}", e).unwrap();
panic!("failed to execute miri");
},
}
miri_pass("tests/run-pass", &target);
if let Ok(path) = std::env::var("MIRI_RUSTC_TEST") {
miri_pass(&path, &target);
}
let stderr = std::io::stderr();
writeln!(stderr.lock(), "{} success, {} mir not found, {} crate not found, {} failed", success, mir_not_found, crate_not_found, failed).unwrap();
assert_eq!(failed, 0, "some tests failed");
});
compile_fail(&sysroot);
}
7 changes: 7 additions & 0 deletions tests/run-pass/aux_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// aux-build:dep.rs

extern crate dep;

fn main() {
dep::foo();
}
1 change: 1 addition & 0 deletions tests/run-pass/auxiliary/dep.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub fn foo() {}