rust/src/librustc_mir/interpret/const_eval.rs

563 lines
19 KiB
Rust
Raw Normal View History

2018-01-02 23:22:09 +00:00
use rustc::hir;
use rustc::middle::const_val::{ConstEvalErr, ConstVal, ErrKind};
2018-01-16 09:31:48 +01:00
use rustc::middle::const_val::ErrKind::{TypeckError, CheckMatchError};
2018-01-02 23:22:09 +00:00
use rustc::mir;
use rustc::ty::{self, TyCtxt, Ty, Instance};
use rustc::ty::layout::{self, LayoutOf};
use rustc::ty::subst::Subst;
use rustc::util::nodemap::FxHashSet;
use syntax::ast::Mutability;
use syntax::codemap::Span;
2018-01-16 09:31:48 +01:00
use rustc::mir::interpret::{EvalResult, EvalError, EvalErrorKind, GlobalId, Value, MemoryPointer, Pointer, PrimVal, AllocId};
use super::{Place, EvalContext, StackPopCleanup, ValTy, PlaceExtra, Memory};
use std::fmt;
use std::error::Error;
2018-03-14 20:11:23 +01:00
use rustc_data_structures::sync::Lrc;
2018-01-16 09:31:48 +01:00
pub fn mk_borrowck_eval_cx<'a, 'mir, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
instance: Instance<'tcx>,
mir: &'mir mir::Mir<'tcx>,
span: Span,
2018-01-16 09:31:48 +01:00
) -> EvalResult<'tcx, EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>> {
debug!("mk_borrowck_eval_cx: {:?}", instance);
let param_env = tcx.param_env(instance.def_id());
let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeEvaluator, ());
2018-01-16 09:31:48 +01:00
// insert a stack frame so any queries have the correct substs
ecx.push_stack_frame(
instance,
span,
2018-01-16 09:31:48 +01:00
mir,
Place::undef(),
StackPopCleanup::None,
)?;
Ok(ecx)
}
pub fn mk_eval_cx<'a, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
instance: Instance<'tcx>,
param_env: ty::ParamEnv<'tcx>,
2018-01-16 09:31:48 +01:00
) -> EvalResult<'tcx, EvalContext<'a, 'tcx, 'tcx, CompileTimeEvaluator>> {
debug!("mk_eval_cx: {:?}, {:?}", instance, param_env);
let span = tcx.def_span(instance.def_id());
let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeEvaluator, ());
let mir = ecx.load_mir(instance.def)?;
// insert a stack frame so any queries have the correct substs
ecx.push_stack_frame(
instance,
mir.span,
mir,
Place::undef(),
StackPopCleanup::None,
)?;
Ok(ecx)
}
2018-01-16 09:31:48 +01:00
pub fn eval_body_with_mir<'a, 'mir, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
2018-01-02 23:22:09 +00:00
cid: GlobalId<'tcx>,
2018-01-16 09:31:48 +01:00
mir: &'mir mir::Mir<'tcx>,
param_env: ty::ParamEnv<'tcx>,
2018-01-16 09:31:48 +01:00
) -> Option<(Value, Pointer, Ty<'tcx>)> {
let (res, ecx) = eval_body_and_ecx(tcx, cid, Some(mir), param_env);
2018-01-16 09:31:48 +01:00
match res {
Ok(val) => Some(val),
Err(mut err) => {
ecx.report(&mut err, true, None);
2018-01-16 09:31:48 +01:00
None
}
}
}
2018-01-16 09:31:48 +01:00
pub fn eval_body<'a, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
2018-01-02 23:22:09 +00:00
cid: GlobalId<'tcx>,
param_env: ty::ParamEnv<'tcx>,
2018-01-16 09:31:48 +01:00
) -> Option<(Value, Pointer, Ty<'tcx>)> {
let (res, ecx) = eval_body_and_ecx(tcx, cid, None, param_env);
2018-01-16 09:31:48 +01:00
match res {
Ok(val) => Some(val),
Err(mut err) => {
ecx.report(&mut err, true, None);
2018-01-16 09:31:48 +01:00
None
}
}
}
2018-01-16 09:31:48 +01:00
fn eval_body_and_ecx<'a, 'mir, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
2018-01-02 23:22:09 +00:00
cid: GlobalId<'tcx>,
2018-01-16 09:31:48 +01:00
mir: Option<&'mir mir::Mir<'tcx>>,
param_env: ty::ParamEnv<'tcx>,
) -> (EvalResult<'tcx, (Value, Pointer, Ty<'tcx>)>, EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>) {
2018-01-02 23:22:09 +00:00
debug!("eval_body: {:?}, {:?}", cid, param_env);
// we start out with the best span we have
// and try improving it down the road when more information is available
let span = tcx.def_span(cid.instance.def_id());
let mut span = mir.map(|mir| mir.span).unwrap_or(span);
let mut ecx = EvalContext::new(tcx.at(span), param_env, CompileTimeEvaluator, ());
let res = (|| {
2018-01-16 09:31:48 +01:00
let mut mir = match mir {
Some(mir) => mir,
None => ecx.load_mir(cid.instance.def)?,
};
2018-01-02 23:22:09 +00:00
if let Some(index) = cid.promoted {
mir = &mir.promoted[index];
}
span = mir.span;
2018-01-02 23:22:09 +00:00
let layout = ecx.layout_of(mir.return_ty().subst(tcx, cid.instance.substs))?;
let alloc = tcx.interpret_interner.get_cached(cid.instance.def_id());
let is_static = tcx.is_static(cid.instance.def_id()).is_some();
2018-01-16 09:31:48 +01:00
let alloc = match alloc {
Some(alloc) => {
assert!(cid.promoted.is_none());
assert!(param_env.caller_bounds.is_empty());
alloc
},
None => {
assert!(!layout.is_unsized());
let ptr = ecx.memory.allocate(
layout.size.bytes(),
layout.align,
None,
)?;
if is_static {
tcx.interpret_interner.cache(cid.instance.def_id(), ptr.alloc_id);
2018-01-16 09:31:48 +01:00
}
let internally_mutable = !layout.ty.is_freeze(tcx, param_env, mir.span);
2018-01-16 09:31:48 +01:00
let mutability = tcx.is_static(cid.instance.def_id());
let mutability = if mutability == Some(hir::Mutability::MutMutable) || internally_mutable {
Mutability::Mutable
} else {
Mutability::Immutable
};
let cleanup = StackPopCleanup::MarkStatic(mutability);
let name = ty::tls::with(|tcx| tcx.item_path_str(cid.instance.def_id()));
let prom = cid.promoted.map_or(String::new(), |p| format!("::promoted[{:?}]", p));
trace!("const_eval: pushing stack frame for global: {}{}", name, prom);
assert!(mir.arg_count == 0);
2018-01-16 09:31:48 +01:00
ecx.push_stack_frame(
cid.instance,
mir.span,
mir,
Place::from_ptr(ptr, layout.align),
cleanup,
)?;
while ecx.step()? {}
ptr.alloc_id
}
};
let ptr = MemoryPointer::new(alloc, 0).into();
// always try to read the value and report errors
2018-01-02 23:22:09 +00:00
let value = match ecx.try_read_value(ptr, layout.align, layout.ty)? {
// if it's a constant (so it needs no address, directly compute its value)
Some(val) if !is_static => val,
// point at the allocation
2018-01-02 23:22:09 +00:00
_ => Value::ByRef(ptr, layout.align),
};
2018-01-02 23:22:09 +00:00
Ok((value, ptr, layout.ty))
})();
(res, ecx)
}
pub struct CompileTimeEvaluator;
impl<'tcx> Into<EvalError<'tcx>> for ConstEvalError {
fn into(self) -> EvalError<'tcx> {
2018-01-16 09:24:38 +01:00
EvalErrorKind::MachineError(self.to_string()).into()
}
}
#[derive(Clone, Debug)]
enum ConstEvalError {
NeedsRfc(String),
NotConst(String),
}
impl fmt::Display for ConstEvalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::ConstEvalError::*;
match *self {
NeedsRfc(ref msg) => {
write!(
f,
"\"{}\" needs an rfc before being allowed inside constants",
msg
)
}
2018-01-26 15:19:01 +01:00
NotConst(ref msg) => write!(f, "{}", msg),
}
}
}
impl Error for ConstEvalError {
fn description(&self) -> &str {
use self::ConstEvalError::*;
match *self {
NeedsRfc(_) => "this feature needs an rfc before being allowed inside constants",
NotConst(_) => "this feature is not compatible with constant evaluation",
}
}
2018-02-23 10:15:26 -08:00
fn cause(&self) -> Option<&dyn Error> {
None
}
}
2018-01-16 09:31:48 +01:00
impl<'mir, 'tcx> super::Machine<'mir, 'tcx> for CompileTimeEvaluator {
type MemoryData = ();
type MemoryKinds = !;
fn eval_fn_call<'a>(
2018-01-16 09:31:48 +01:00
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
instance: ty::Instance<'tcx>,
destination: Option<(Place, mir::BasicBlock)>,
args: &[ValTy<'tcx>],
span: Span,
sig: ty::FnSig<'tcx>,
) -> EvalResult<'tcx, bool> {
debug!("eval_fn_call: {:?}", instance);
if !ecx.tcx.is_const_fn(instance.def_id()) {
let def_id = instance.def_id();
let (op, oflo) = if let Some(op) = ecx.tcx.is_binop_lang_item(def_id) {
op
} else {
return Err(
ConstEvalError::NotConst(format!("calling non-const fn `{}`", instance)).into(),
);
};
let (dest, bb) = destination.expect("128 lowerings can't diverge");
let dest_ty = sig.output();
if oflo {
ecx.intrinsic_with_overflow(op, args[0], args[1], dest, dest_ty)?;
} else {
ecx.intrinsic_overflowing(op, args[0], args[1], dest, dest_ty)?;
}
ecx.goto_block(bb);
return Ok(true);
}
let mir = match ecx.load_mir(instance.def) {
Ok(mir) => mir,
Err(err) => {
2018-01-31 15:06:45 +01:00
if let EvalErrorKind::NoMirFor(ref path) = err.kind {
return Err(
ConstEvalError::NeedsRfc(format!("calling extern function `{}`", path))
.into(),
);
}
return Err(err);
}
};
let (return_place, return_to_block) = match destination {
Some((place, block)) => (place, StackPopCleanup::Goto(block)),
None => (Place::undef(), StackPopCleanup::None),
};
ecx.push_stack_frame(
instance,
span,
mir,
return_place,
return_to_block,
)?;
Ok(false)
}
fn call_intrinsic<'a>(
2018-01-16 09:31:48 +01:00
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
instance: ty::Instance<'tcx>,
_args: &[ValTy<'tcx>],
dest: Place,
dest_layout: layout::TyLayout<'tcx>,
target: mir::BasicBlock,
) -> EvalResult<'tcx> {
let substs = instance.substs;
let intrinsic_name = &ecx.tcx.item_name(instance.def_id())[..];
match intrinsic_name {
"min_align_of" => {
let elem_ty = substs.type_at(0);
let elem_align = ecx.layout_of(elem_ty)?.align.abi();
let align_val = PrimVal::from_u128(elem_align as u128);
ecx.write_primval(dest, align_val, dest_layout.ty)?;
}
"size_of" => {
let ty = substs.type_at(0);
let size = ecx.layout_of(ty)?.size.bytes() as u128;
ecx.write_primval(dest, PrimVal::from_u128(size), dest_layout.ty)?;
}
"type_id" => {
let ty = substs.type_at(0);
let type_id = ecx.tcx.type_id_hash(ty) as u128;
ecx.write_primval(dest, PrimVal::from_u128(type_id), dest_layout.ty)?;
}
name => return Err(ConstEvalError::NeedsRfc(format!("calling intrinsic `{}`", name)).into()),
}
ecx.goto_block(target);
// Since we pushed no stack frame, the main loop will act
// as if the call just completed and it's returning to the
// current frame.
Ok(())
}
fn try_ptr_op<'a>(
2018-01-16 09:31:48 +01:00
_ecx: &EvalContext<'a, 'mir, 'tcx, Self>,
_bin_op: mir::BinOp,
left: PrimVal,
_left_ty: Ty<'tcx>,
right: PrimVal,
_right_ty: Ty<'tcx>,
) -> EvalResult<'tcx, Option<(PrimVal, bool)>> {
if left.is_bytes() && right.is_bytes() {
Ok(None)
} else {
Err(
ConstEvalError::NeedsRfc("Pointer arithmetic or comparison".to_string()).into(),
)
}
}
2018-01-16 09:31:48 +01:00
fn mark_static_initialized<'a>(
_mem: &mut Memory<'a, 'mir, 'tcx, Self>,
_id: AllocId,
_mutability: Mutability,
) -> EvalResult<'tcx, bool> {
Ok(false)
}
fn init_static<'a>(
ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
cid: GlobalId<'tcx>,
) -> EvalResult<'tcx, AllocId> {
2018-03-21 14:34:46 +01:00
let alloc = ecx
.tcx
.interpret_interner
.get_cached(cid.instance.def_id());
// Don't evaluate when already cached to prevent cycles
if let Some(alloc) = alloc {
return Ok(alloc)
}
// ensure the static is computed
2018-01-31 15:06:45 +01:00
ecx.const_eval(cid)?;
Ok(ecx
.tcx
.interpret_interner
.get_cached(cid.instance.def_id())
.expect("uncached static"))
}
fn box_alloc<'a>(
2018-01-16 09:31:48 +01:00
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
_ty: Ty<'tcx>,
_dest: Place,
) -> EvalResult<'tcx> {
Err(
ConstEvalError::NeedsRfc("Heap allocations via `box` keyword".to_string()).into(),
)
}
fn global_item_with_linkage<'a>(
2018-01-16 09:31:48 +01:00
_ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>,
_instance: ty::Instance<'tcx>,
_mutability: Mutability,
) -> EvalResult<'tcx> {
Err(
ConstEvalError::NotConst("statics with `linkage` attribute".to_string()).into(),
)
}
}
2018-01-16 09:24:38 +01:00
pub fn const_val_field<'a, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
instance: ty::Instance<'tcx>,
2018-01-16 09:26:37 +01:00
variant: Option<usize>,
field: mir::Field,
2018-01-31 15:06:45 +01:00
value: Value,
ty: Ty<'tcx>,
2018-01-16 09:24:38 +01:00
) -> ::rustc::middle::const_val::EvalResult<'tcx> {
2018-01-31 15:06:45 +01:00
trace!("const_val_field: {:?}, {:?}, {:?}, {:?}", instance, field, value, ty);
let mut ecx = mk_eval_cx(tcx, instance, param_env).unwrap();
let result = (|| {
let (mut field, ty) = match value {
Value::ByValPair(..) | Value::ByVal(_) => ecx.read_field(value, variant, field, ty)?.expect("const_val_field on non-field"),
Value::ByRef(ptr, align) => {
let place = Place::Ptr {
ptr,
align,
extra: variant.map_or(PlaceExtra::None, PlaceExtra::DowncastVariant),
};
let layout = ecx.layout_of(ty)?;
let (place, layout) = ecx.place_field(place, field, layout)?;
let (ptr, align) = place.to_ptr_align();
(Value::ByRef(ptr, align), layout.ty)
}
};
if let Value::ByRef(ptr, align) = field {
if let Some(val) = ecx.try_read_value(ptr, align, ty)? {
field = val;
}
}
Ok((field, ty))
})();
match result {
2018-01-16 09:24:38 +01:00
Ok((field, ty)) => Ok(tcx.mk_const(ty::Const {
val: ConstVal::Value(field),
ty,
})),
2018-01-31 15:06:45 +01:00
Err(err) => {
let (trace, span) = ecx.generate_stacktrace(None);
2018-01-31 15:06:45 +01:00
let err = ErrKind::Miri(err, trace);
Err(ConstEvalErr {
kind: err.into(),
span,
})
},
2018-01-16 09:24:38 +01:00
}
}
pub fn const_discr<'a, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
instance: ty::Instance<'tcx>,
value: Value,
ty: Ty<'tcx>,
2018-01-16 09:24:38 +01:00
) -> EvalResult<'tcx, u128> {
trace!("const_discr: {:?}, {:?}, {:?}", instance, value, ty);
let mut ecx = mk_eval_cx(tcx, instance, param_env).unwrap();
2018-01-16 09:24:38 +01:00
let (ptr, align) = match value {
Value::ByValPair(..) | Value::ByVal(_) => {
let layout = ecx.layout_of(ty)?;
use super::MemoryKind;
let ptr = ecx.memory.allocate(layout.size.bytes(), layout.align, Some(MemoryKind::Stack))?;
let ptr: Pointer = ptr.into();
ecx.write_value_to_ptr(value, ptr, layout.align, ty)?;
(ptr, layout.align)
},
Value::ByRef(ptr, align) => (ptr, align),
};
let place = Place::from_primval_ptr(ptr, align);
ecx.read_discriminant_value(place, ty)
}
pub fn const_eval_provider<'a, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
2018-01-02 23:22:09 +00:00
key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>,
) -> ::rustc::middle::const_val::EvalResult<'tcx> {
trace!("const eval: {:?}", key);
2018-01-02 23:22:09 +00:00
let cid = key.value;
let def_id = cid.instance.def.def_id();
2018-01-16 09:31:48 +01:00
if tcx.is_foreign_item(def_id) {
let id = tcx.interpret_interner.get_cached(def_id);
2018-01-16 09:31:48 +01:00
let id = match id {
// FIXME: due to caches this shouldn't happen, add some assertions
Some(id) => id,
None => {
let id = tcx.interpret_interner.reserve();
tcx.interpret_interner.cache(def_id, id);
2018-01-16 09:31:48 +01:00
id
},
};
let ty = tcx.type_of(def_id);
2018-01-31 15:45:59 +01:00
let layout = tcx.layout_of(key.param_env.and(ty)).unwrap();
2018-01-16 09:31:48 +01:00
let ptr = MemoryPointer::new(id, 0);
return Ok(tcx.mk_const(ty::Const {
val: ConstVal::Value(Value::ByRef(ptr.into(), layout.align)),
ty,
}))
}
2018-01-02 23:22:09 +00:00
if let Some(id) = tcx.hir.as_local_node_id(def_id) {
let tables = tcx.typeck_tables_of(def_id);
let span = tcx.def_span(def_id);
// Do match-check before building MIR
if tcx.check_match(def_id).is_err() {
return Err(ConstEvalErr {
2018-03-14 20:11:23 +01:00
kind: Lrc::new(CheckMatchError),
2018-01-16 09:31:48 +01:00
span,
});
}
2018-01-02 23:22:09 +00:00
if let hir::BodyOwnerKind::Const = tcx.hir.body_owner_kind(id) {
tcx.mir_const_qualif(def_id);
}
2018-01-02 23:22:09 +00:00
// Do not continue into miri if typeck errors occurred; it will fail horribly
if tables.tainted_by_errors {
return Err(ConstEvalErr {
2018-03-14 20:11:23 +01:00
kind: Lrc::new(TypeckError),
2018-01-02 23:22:09 +00:00
span,
});
}
};
2018-01-16 09:24:38 +01:00
let (res, ecx) = eval_body_and_ecx(tcx, cid, None, key.param_env);
res.map(|(miri_value, ptr, miri_ty)| {
if tcx.is_static(def_id).is_some() {
if let Ok(ptr) = ptr.primval.to_ptr() {
let mut seen = FxHashSet::default();
create_depgraph_edges(tcx, ptr.alloc_id, &mut seen);
}
}
2018-01-16 09:31:48 +01:00
tcx.mk_const(ty::Const {
val: ConstVal::Value(miri_value),
ty: miri_ty,
2018-01-16 09:31:48 +01:00
})
}).map_err(|mut err| {
if tcx.is_static(def_id).is_some() {
ecx.report(&mut err, true, None);
}
let (trace, span) = ecx.generate_stacktrace(None);
2018-01-31 15:06:45 +01:00
let err = ErrKind::Miri(err, trace);
2018-01-16 09:31:48 +01:00
ConstEvalErr {
kind: err.into(),
span,
2018-01-16 09:31:48 +01:00
}
})
}
// This function creates dep graph edges from statics to all referred to statics.
// This is necessary, because the `const_eval` query cannot directly call itself
// for other statics, because we cannot prevent recursion in queries.
//
// see test/incremental/static_refering_to_other_static2/issue.rs for an example
// where not creating those edges would cause static A, which refers to static B
// to point to the old allocation of static B, even though B has changed.
//
// In the future we will want to remove this funcion in favour of a system that
// makes sure that statics don't need to have edges to other statics as long as
// they are only referring by reference and not inspecting the other static's body.
fn create_depgraph_edges<'a, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
alloc_id: AllocId,
seen: &mut FxHashSet<AllocId>,
) {
trace!("create_depgraph_edges: {:?}, {:?}", alloc_id, seen);
if seen.insert(alloc_id) {
trace!("seen: {:?}, {:?}", alloc_id, seen);
if let Some(alloc) = tcx.interpret_interner.get_alloc(alloc_id) {
trace!("get_alloc: {:?}, {:?}, {:?}", alloc_id, seen, alloc);
for (_, &reloc) in &alloc.relocations {
if let Some(did) = tcx.interpret_interner.get_corresponding_static_def_id(reloc) {
trace!("get_corresponding: {:?}, {:?}, {:?}, {:?}, {:?}", alloc_id, seen, alloc, did, reloc);
let _ = tcx.maybe_optimized_mir(did);
}
create_depgraph_edges(tcx, reloc, seen);
}
}
}
}