Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/mir/eval.rs')
-rw-r--r--crates/hir-ty/src/mir/eval.rs751
1 files changed, 541 insertions, 210 deletions
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index c5d843d9eb..7b83645fae 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -1,6 +1,6 @@
//! This module provides a MIR interpreter, which is used in const eval.
-use std::{borrow::Cow, collections::HashMap, iter};
+use std::{borrow::Cow, collections::HashMap, iter, ops::Range, sync::Arc};
use base_db::CrateId;
use chalk_ir::{
@@ -11,7 +11,7 @@ use hir_def::{
builtin_type::BuiltinType,
lang_item::{lang_attr, LangItem},
layout::{Layout, LayoutError, RustcEnumVariantIdx, TagEncoding, Variants},
- AdtId, DefWithBodyId, EnumVariantId, FunctionId, HasModule, Lookup, VariantId,
+ AdtId, DefWithBodyId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, VariantId,
};
use intern::Interned;
use la_arena::ArenaMap;
@@ -23,8 +23,10 @@ use crate::{
infer::{normalize, PointerCast},
layout::layout_of_ty,
mapping::from_chalk,
- method_resolution::lookup_impl_method,
- CallableDefId, Const, ConstScalar, Interner, MemoryMap, Substitution, Ty, TyBuilder, TyExt,
+ method_resolution::{is_dyn_method, lookup_impl_method},
+ traits::FnTrait,
+ CallableDefId, Const, ConstScalar, FnDefId, GenericArgData, Interner, MemoryMap, Substitution,
+ TraitEnvironment, Ty, TyBuilder, TyExt,
};
use super::{
@@ -32,10 +34,51 @@ use super::{
Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, UnOp,
};
+macro_rules! from_bytes {
+ ($ty:tt, $value:expr) => {
+ ($ty::from_le_bytes(match ($value).try_into() {
+ Ok(x) => x,
+ Err(_) => return Err(MirEvalError::TypeError("mismatched size")),
+ }))
+ };
+}
+
+#[derive(Debug, Default)]
+struct VTableMap {
+ ty_to_id: HashMap<Ty, usize>,
+ id_to_ty: Vec<Ty>,
+}
+
+impl VTableMap {
+ fn id(&mut self, ty: Ty) -> usize {
+ if let Some(x) = self.ty_to_id.get(&ty) {
+ return *x;
+ }
+ let id = self.id_to_ty.len();
+ self.id_to_ty.push(ty.clone());
+ self.ty_to_id.insert(ty, id);
+ id
+ }
+
+ fn ty(&self, id: usize) -> Result<&Ty> {
+ self.id_to_ty.get(id).ok_or(MirEvalError::InvalidVTableId(id))
+ }
+
+ fn ty_of_bytes(&self, bytes: &[u8]) -> Result<&Ty> {
+ let id = from_bytes!(usize, bytes);
+ self.ty(id)
+ }
+}
+
pub struct Evaluator<'a> {
db: &'a dyn HirDatabase,
+ trait_env: Arc<TraitEnvironment>,
stack: Vec<u8>,
heap: Vec<u8>,
+ /// We don't really have function pointers, i.e. pointers to some assembly instructions that we can run. Instead, we
+ /// store the type as an interned id in place of function and vtable pointers, and we recover back the type at the
+ /// time of use.
+ vtable_map: VTableMap,
crate_id: CrateId,
// FIXME: This is a workaround, see the comment on `interpret_mir`
assert_placeholder_ty_is_unused: bool,
@@ -53,11 +96,18 @@ enum Address {
use Address::*;
+#[derive(Debug, Clone, Copy)]
struct Interval {
addr: Address,
size: usize,
}
+#[derive(Debug, Clone)]
+struct IntervalAndTy {
+ interval: Interval,
+ ty: Ty,
+}
+
impl Interval {
fn new(addr: Address, size: usize) -> Self {
Self { addr, size }
@@ -66,6 +116,36 @@ impl Interval {
fn get<'a>(&self, memory: &'a Evaluator<'a>) -> Result<&'a [u8]> {
memory.read_memory(self.addr, self.size)
}
+
+ fn write_from_bytes(&self, memory: &mut Evaluator<'_>, bytes: &[u8]) -> Result<()> {
+ memory.write_memory(self.addr, bytes)
+ }
+
+ fn write_from_interval(&self, memory: &mut Evaluator<'_>, interval: Interval) -> Result<()> {
+ // FIXME: this could be more efficent
+ let bytes = &interval.get(memory)?.to_vec();
+ memory.write_memory(self.addr, bytes)
+ }
+
+ fn slice(self, range: Range<usize>) -> Interval {
+ Interval { addr: self.addr.offset(range.start), size: range.len() }
+ }
+}
+
+impl IntervalAndTy {
+ fn get<'a>(&self, memory: &'a Evaluator<'a>) -> Result<&'a [u8]> {
+ memory.read_memory(self.interval.addr, self.interval.size)
+ }
+
+ fn new(
+ addr: Address,
+ ty: Ty,
+ evaluator: &Evaluator<'_>,
+ locals: &Locals<'_>,
+ ) -> Result<IntervalAndTy> {
+ let size = evaluator.size_of_sized(&ty, locals, "type of interval")?;
+ Ok(IntervalAndTy { interval: Interval { addr, size }, ty })
+ }
}
enum IntervalOrOwned {
@@ -81,15 +161,6 @@ impl IntervalOrOwned {
}
}
-macro_rules! from_bytes {
- ($ty:tt, $value:expr) => {
- ($ty::from_le_bytes(match ($value).try_into() {
- Ok(x) => x,
- Err(_) => return Err(MirEvalError::TypeError("mismatched size")),
- }))
- };
-}
-
impl Address {
fn from_bytes(x: &[u8]) -> Result<Self> {
Ok(Address::from_usize(from_bytes!(usize, x)))
@@ -97,7 +168,7 @@ impl Address {
fn from_usize(x: usize) -> Self {
if x > usize::MAX / 2 {
- Stack(usize::MAX - x)
+ Stack(x - usize::MAX / 2)
} else {
Heap(x)
}
@@ -109,7 +180,7 @@ impl Address {
fn to_usize(&self) -> usize {
let as_num = match self {
- Stack(x) => usize::MAX - *x,
+ Stack(x) => *x + usize::MAX / 2,
Heap(x) => *x,
};
as_num
@@ -136,7 +207,7 @@ pub enum MirEvalError {
/// Means that code had undefined behavior. We don't try to actively detect UB, but if it was detected
/// then use this type of error.
UndefinedBehavior(&'static str),
- Panic,
+ Panic(String),
MirLowerError(FunctionId, MirLowerError),
TypeIsUnsized(Ty, &'static str),
NotSupported(String),
@@ -145,6 +216,7 @@ pub enum MirEvalError {
ExecutionLimitExceeded,
StackOverflow,
TargetDataLayoutNotAvailable,
+ InvalidVTableId(usize),
}
impl std::fmt::Debug for MirEvalError {
@@ -158,7 +230,7 @@ impl std::fmt::Debug for MirEvalError {
Self::UndefinedBehavior(arg0) => {
f.debug_tuple("UndefinedBehavior").field(arg0).finish()
}
- Self::Panic => write!(f, "Panic"),
+ Self::Panic(msg) => write!(f, "Panic with message:\n{msg:?}"),
Self::TargetDataLayoutNotAvailable => write!(f, "TargetDataLayoutNotAvailable"),
Self::TypeIsUnsized(ty, it) => write!(f, "{ty:?} is unsized. {it} should be sized."),
Self::ExecutionLimitExceeded => write!(f, "execution limit exceeded"),
@@ -166,6 +238,7 @@ impl std::fmt::Debug for MirEvalError {
Self::MirLowerError(arg0, arg1) => {
f.debug_tuple("MirLowerError").field(arg0).field(arg1).finish()
}
+ Self::InvalidVTableId(arg0) => f.debug_tuple("InvalidVTableId").field(arg0).finish(),
Self::NotSupported(arg0) => f.debug_tuple("NotSupported").field(arg0).finish(),
Self::InvalidConst(arg0) => {
let data = &arg0.data(Interner);
@@ -209,6 +282,7 @@ struct Locals<'a> {
pub fn interpret_mir(
db: &dyn HirDatabase,
body: &MirBody,
+ subst: Substitution,
// FIXME: This is workaround. Ideally, const generics should have a separate body (issue #7434), but now
// they share their body with their parent, so in MIR lowering we have locals of the parent body, which
// might have placeholders. With this argument, we (wrongly) assume that every placeholder type has
@@ -217,13 +291,12 @@ pub fn interpret_mir(
assert_placeholder_ty_is_unused: bool,
) -> Result<Const> {
let ty = body.locals[return_slot()].ty.clone();
- let mut evaluator =
- Evaluator::new(db, body.owner.module(db.upcast()).krate(), assert_placeholder_ty_is_unused);
- let bytes = evaluator.interpret_mir_with_no_arg(&body)?;
+ let mut evaluator = Evaluator::new(db, body, assert_placeholder_ty_is_unused);
+ let bytes = evaluator.interpret_mir(&body, None.into_iter(), subst.clone())?;
let memory_map = evaluator.create_memory_map(
&bytes,
&ty,
- &Locals { ptr: &ArenaMap::new(), body: &body, subst: &Substitution::empty(Interner) },
+ &Locals { ptr: &ArenaMap::new(), body: &body, subst: &subst },
)?;
return Ok(intern_const_scalar(ConstScalar::Bytes(bytes, memory_map), ty));
}
@@ -231,13 +304,17 @@ pub fn interpret_mir(
impl Evaluator<'_> {
pub fn new<'a>(
db: &'a dyn HirDatabase,
- crate_id: CrateId,
+ body: &MirBody,
assert_placeholder_ty_is_unused: bool,
) -> Evaluator<'a> {
+ let crate_id = body.owner.module(db.upcast()).krate();
+ let trait_env = db.trait_environment_for_body(body.owner);
Evaluator {
stack: vec![0],
heap: vec![0],
+ vtable_map: VTableMap::default(),
db,
+ trait_env,
crate_id,
assert_placeholder_ty_is_unused,
stack_depth_limit: 100,
@@ -246,7 +323,19 @@ impl Evaluator<'_> {
}
fn place_addr(&self, p: &Place, locals: &Locals<'_>) -> Result<Address> {
- Ok(self.place_addr_and_ty(p, locals)?.0)
+ Ok(self.place_addr_and_ty_and_metadata(p, locals)?.0)
+ }
+
+ fn place_interval(&self, p: &Place, locals: &Locals<'_>) -> Result<Interval> {
+ let place_addr_and_ty = self.place_addr_and_ty_and_metadata(p, locals)?;
+ Ok(Interval {
+ addr: place_addr_and_ty.0,
+ size: self.size_of_sized(
+ &place_addr_and_ty.1,
+ locals,
+ "Type of place that we need its interval",
+ )?,
+ })
}
fn ptr_size(&self) -> usize {
@@ -256,10 +345,15 @@ impl Evaluator<'_> {
}
}
- fn place_addr_and_ty<'a>(&'a self, p: &Place, locals: &'a Locals<'a>) -> Result<(Address, Ty)> {
+ fn place_addr_and_ty_and_metadata<'a>(
+ &'a self,
+ p: &Place,
+ locals: &'a Locals<'a>,
+ ) -> Result<(Address, Ty, Option<Interval>)> {
let mut addr = locals.ptr[p.local];
let mut ty: Ty =
self.ty_filler(&locals.body.locals[p.local].ty, locals.subst, locals.body.owner)?;
+ let mut metadata = None; // locals are always sized
for proj in &p.projection {
match proj {
ProjectionElem::Deref => {
@@ -271,12 +365,18 @@ impl Evaluator<'_> {
))
}
};
+ metadata = if self.size_of(&ty, locals)?.is_none() {
+ Some(Interval { addr: addr.offset(self.ptr_size()), size: self.ptr_size() })
+ } else {
+ None
+ };
let x = from_bytes!(usize, self.read_memory(addr, self.ptr_size())?);
addr = Address::from_usize(x);
}
ProjectionElem::Index(op) => {
let offset =
from_bytes!(usize, self.read_memory(locals.ptr[*op], self.ptr_size())?);
+ metadata = None; // Result of index is always sized
match &ty.data(Interner).kind {
TyKind::Ref(_, _, inner) => match &inner.data(Interner).kind {
TyKind::Slice(inner) => {
@@ -314,6 +414,7 @@ impl Evaluator<'_> {
.clone();
let offset = layout.fields.offset(f).bytes_usize();
addr = addr.offset(offset);
+ metadata = None; // tuple field is always sized
}
_ => return Err(MirEvalError::TypeError("Only tuple has tuple fields")),
},
@@ -343,6 +444,8 @@ impl Evaluator<'_> {
.offset(u32::from(f.local_id.into_raw()) as usize)
.bytes_usize();
addr = addr.offset(offset);
+ // FIXME: support structs with unsized fields
+ metadata = None;
}
_ => return Err(MirEvalError::TypeError("Only adt has fields")),
},
@@ -353,7 +456,7 @@ impl Evaluator<'_> {
ProjectionElem::OpaqueCast(_) => not_supported!("opaque cast"),
}
}
- Ok((addr, ty))
+ Ok((addr, ty, metadata))
}
fn layout(&self, ty: &Ty) -> Result<Layout> {
@@ -368,16 +471,23 @@ impl Evaluator<'_> {
}
fn place_ty<'a>(&'a self, p: &Place, locals: &'a Locals<'a>) -> Result<Ty> {
- Ok(self.place_addr_and_ty(p, locals)?.1)
+ Ok(self.place_addr_and_ty_and_metadata(p, locals)?.1)
}
- fn operand_ty<'a>(&'a self, o: &'a Operand, locals: &'a Locals<'a>) -> Result<Ty> {
+ fn operand_ty(&self, o: &Operand, locals: &Locals<'_>) -> Result<Ty> {
Ok(match o {
Operand::Copy(p) | Operand::Move(p) => self.place_ty(p, locals)?,
Operand::Constant(c) => c.data(Interner).ty.clone(),
})
}
+ fn operand_ty_and_eval(&mut self, o: &Operand, locals: &Locals<'_>) -> Result<IntervalAndTy> {
+ Ok(IntervalAndTy {
+ interval: self.eval_operand(o, locals)?,
+ ty: self.operand_ty(o, locals)?,
+ })
+ }
+
fn interpret_mir(
&mut self,
body: &MirBody,
@@ -455,116 +565,23 @@ impl Evaluator<'_> {
cleanup: _,
from_hir_call: _,
} => {
+ let destination = self.place_interval(destination, &locals)?;
let fn_ty = self.operand_ty(func, &locals)?;
+ let args = args
+ .iter()
+ .map(|x| self.operand_ty_and_eval(x, &locals))
+ .collect::<Result<Vec<_>>>()?;
match &fn_ty.data(Interner).kind {
+ TyKind::Function(_) => {
+ let bytes = self.eval_operand(func, &locals)?;
+ self.exec_fn_pointer(bytes, destination, &args, &locals)?;
+ }
TyKind::FnDef(def, generic_args) => {
- let def: CallableDefId = from_chalk(self.db, *def);
- let generic_args = self.subst_filler(generic_args, &locals);
- match def {
- CallableDefId::FunctionId(def) => {
- let arg_bytes = args
- .iter()
- .map(|x| {
- Ok(self
- .eval_operand(x, &locals)?
- .get(&self)?
- .to_owned())
- })
- .collect::<Result<Vec<_>>>()?
- .into_iter();
- let function_data = self.db.function_data(def);
- let is_intrinsic = match &function_data.abi {
- Some(abi) => *abi == Interned::new_str("rust-intrinsic"),
- None => match def.lookup(self.db.upcast()).container {
- hir_def::ItemContainerId::ExternBlockId(block) => {
- let id = block.lookup(self.db.upcast()).id;
- id.item_tree(self.db.upcast())[id.value]
- .abi
- .as_deref()
- == Some("rust-intrinsic")
- }
- _ => false,
- },
- };
- let result = if is_intrinsic {
- self.exec_intrinsic(
- function_data
- .name
- .as_text()
- .unwrap_or_default()
- .as_str(),
- arg_bytes,
- generic_args,
- &locals,
- )?
- } else if let Some(x) = self.detect_lang_function(def) {
- self.exec_lang_item(x, arg_bytes)?
- } else {
- let trait_env = {
- let Some(d) = body.owner.as_generic_def_id() else {
- not_supported!("trait resolving in non generic def id");
- };
- self.db.trait_environment(d)
- };
- let (imp, generic_args) = lookup_impl_method(
- self.db,
- trait_env,
- def,
- generic_args.clone(),
- );
- let generic_args =
- self.subst_filler(&generic_args, &locals);
- let def = imp.into();
- let mir_body = self
- .db
- .mir_body(def)
- .map_err(|e| MirEvalError::MirLowerError(imp, e))?;
- self.interpret_mir(&mir_body, arg_bytes, generic_args)
- .map_err(|e| {
- MirEvalError::InFunction(imp, Box::new(e))
- })?
- };
- let dest_addr = self.place_addr(destination, &locals)?;
- self.write_memory(dest_addr, &result)?;
- }
- CallableDefId::StructId(id) => {
- let (size, variant_layout, tag) = self.layout_of_variant(
- id.into(),
- generic_args.clone(),
- &locals,
- )?;
- let result = self.make_by_layout(
- size,
- &variant_layout,
- tag,
- args,
- &locals,
- )?;
- let dest_addr = self.place_addr(destination, &locals)?;
- self.write_memory(dest_addr, &result)?;
- }
- CallableDefId::EnumVariantId(id) => {
- let (size, variant_layout, tag) = self.layout_of_variant(
- id.into(),
- generic_args.clone(),
- &locals,
- )?;
- let result = self.make_by_layout(
- size,
- &variant_layout,
- tag,
- args,
- &locals,
- )?;
- let dest_addr = self.place_addr(destination, &locals)?;
- self.write_memory(dest_addr, &result)?;
- }
- }
- current_block_idx =
- target.expect("broken mir, function without target");
+ self.exec_fn_def(*def, generic_args, destination, &args, &locals)?;
}
- _ => not_supported!("unknown function type"),
+ x => not_supported!("unknown function type {x:?}"),
}
+ current_block_idx = target.expect("broken mir, function without target");
}
Terminator::SwitchInt { discr, targets } => {
let val = u128::from_le_bytes(pad16(
@@ -584,7 +601,7 @@ impl Evaluator<'_> {
.to_owned());
}
Terminator::Unreachable => {
- return Err(MirEvalError::UndefinedBehavior("unreachable executed"))
+ return Err(MirEvalError::UndefinedBehavior("unreachable executed"));
}
_ => not_supported!("unknown terminator"),
}
@@ -600,8 +617,12 @@ impl Evaluator<'_> {
Ok(match r {
Rvalue::Use(x) => Borrowed(self.eval_operand(x, locals)?),
Rvalue::Ref(_, p) => {
- let addr = self.place_addr(p, locals)?;
- Owned(addr.to_bytes())
+ let (addr, _, metadata) = self.place_addr_and_ty_and_metadata(p, locals)?;
+ let mut r = addr.to_bytes();
+ if let Some(metadata) = metadata {
+ r.extend(metadata.get(self)?);
+ }
+ Owned(r)
}
Rvalue::Len(_) => not_supported!("rvalue len"),
Rvalue::UnaryOp(op, val) => {
@@ -640,7 +661,14 @@ impl Evaluator<'_> {
let mut ty = self.operand_ty(lhs, locals)?;
while let TyKind::Ref(_, _, z) = ty.kind(Interner) {
ty = z.clone();
- let size = self.size_of_sized(&ty, locals, "operand of binary op")?;
+ let size = if ty.kind(Interner) == &TyKind::Str {
+ let ns = from_bytes!(usize, &lc[self.ptr_size()..self.ptr_size() * 2]);
+ lc = &lc[..self.ptr_size()];
+ rc = &rc[..self.ptr_size()];
+ ns
+ } else {
+ self.size_of_sized(&ty, locals, "operand of binary op")?
+ };
lc = self.read_memory(Address::from_bytes(lc)?, size)?;
rc = self.read_memory(Address::from_bytes(rc)?, size)?;
}
@@ -672,8 +700,12 @@ impl Evaluator<'_> {
let r = match op {
BinOp::Add => l128.overflowing_add(r128).0,
BinOp::Mul => l128.overflowing_mul(r128).0,
- BinOp::Div => l128.checked_div(r128).ok_or(MirEvalError::Panic)?,
- BinOp::Rem => l128.checked_rem(r128).ok_or(MirEvalError::Panic)?,
+ BinOp::Div => l128.checked_div(r128).ok_or_else(|| {
+ MirEvalError::Panic(format!("Overflow in {op:?}"))
+ })?,
+ BinOp::Rem => l128.checked_rem(r128).ok_or_else(|| {
+ MirEvalError::Panic(format!("Overflow in {op:?}"))
+ })?,
BinOp::Sub => l128.overflowing_sub(r128).0,
BinOp::BitAnd => l128 & r128,
BinOp::BitOr => l128 | r128,
@@ -683,16 +715,16 @@ impl Evaluator<'_> {
let r = r.to_le_bytes();
for &k in &r[lc.len()..] {
if k != 0 && (k != 255 || !is_signed) {
- return Err(MirEvalError::Panic);
+ return Err(MirEvalError::Panic(format!("Overflow in {op:?}")));
}
}
Owned(r[0..lc.len()].into())
}
BinOp::Shl | BinOp::Shr => {
let shift_amout = if r128 < 0 {
- return Err(MirEvalError::Panic);
+ return Err(MirEvalError::Panic(format!("Overflow in {op:?}")));
} else if r128 > 128 {
- return Err(MirEvalError::Panic);
+ return Err(MirEvalError::Panic(format!("Overflow in {op:?}")));
} else {
r128 as u8
};
@@ -710,8 +742,24 @@ impl Evaluator<'_> {
let ty = self.place_ty(p, locals)?;
let bytes = self.eval_place(p, locals)?.get(&self)?;
let layout = self.layout(&ty)?;
+ let enum_id = 'b: {
+ match ty.kind(Interner) {
+ TyKind::Adt(e, _) => match e.0 {
+ AdtId::EnumId(e) => break 'b e,
+ _ => (),
+ },
+ _ => (),
+ }
+ return Ok(Owned(0u128.to_le_bytes().to_vec()));
+ };
match layout.variants {
- Variants::Single { .. } => Owned(0u128.to_le_bytes().to_vec()),
+ Variants::Single { index } => {
+ let r = self.db.const_eval_discriminant(EnumVariantId {
+ parent: enum_id,
+ local_id: index.0,
+ })?;
+ Owned(r.to_le_bytes().to_vec())
+ }
Variants::Multiple { tag, tag_encoding, .. } => {
let Some(target_data_layout) = self.db.target_data_layout(self.crate_id) else {
not_supported!("missing target data layout");
@@ -727,13 +775,6 @@ impl Evaluator<'_> {
let tag = &bytes[offset..offset + size];
let candidate_discriminant = i128::from_le_bytes(pad16(tag, false))
.wrapping_sub(niche_start as i128);
- let enum_id = match ty.kind(Interner) {
- TyKind::Adt(e, _) => match e.0 {
- AdtId::EnumId(e) => e,
- _ => not_supported!("Non enum with multi variant layout"),
- },
- _ => not_supported!("Non adt with multi variant layout"),
- };
let enum_data = self.db.enum_data(enum_id);
let result = 'b: {
for (local_id, _) in enum_data.variants.iter() {
@@ -759,48 +800,65 @@ impl Evaluator<'_> {
}
Rvalue::ShallowInitBox(_, _) => not_supported!("shallow init box"),
Rvalue::CopyForDeref(_) => not_supported!("copy for deref"),
- Rvalue::Aggregate(kind, values) => match kind {
- AggregateKind::Array(_) => {
- let mut r = vec![];
- for x in values {
- let value = self.eval_operand(x, locals)?.get(&self)?;
- r.extend(value);
+ Rvalue::Aggregate(kind, values) => {
+ let values = values
+ .iter()
+ .map(|x| self.eval_operand(x, locals))
+ .collect::<Result<Vec<_>>>()?;
+ match kind {
+ AggregateKind::Array(_) => {
+ let mut r = vec![];
+ for x in values {
+ let value = x.get(&self)?;
+ r.extend(value);
+ }
+ Owned(r)
+ }
+ AggregateKind::Tuple(ty) => {
+ let layout = self.layout(&ty)?;
+ Owned(self.make_by_layout(
+ layout.size.bytes_usize(),
+ &layout,
+ None,
+ values.iter().copied(),
+ )?)
+ }
+ AggregateKind::Union(x, f) => {
+ let layout = self.layout_adt((*x).into(), Substitution::empty(Interner))?;
+ let offset = layout
+ .fields
+ .offset(u32::from(f.local_id.into_raw()) as usize)
+ .bytes_usize();
+ let op = values[0].get(&self)?;
+ let mut result = vec![0; layout.size.bytes_usize()];
+ result[offset..offset + op.len()].copy_from_slice(op);
+ Owned(result)
+ }
+ AggregateKind::Adt(x, subst) => {
+ let subst = self.subst_filler(subst, locals);
+ let (size, variant_layout, tag) =
+ self.layout_of_variant(*x, subst, locals)?;
+ Owned(self.make_by_layout(
+ size,
+ &variant_layout,
+ tag,
+ values.iter().copied(),
+ )?)
}
- Owned(r)
- }
- AggregateKind::Tuple(ty) => {
- let layout = self.layout(&ty)?;
- Owned(self.make_by_layout(
- layout.size.bytes_usize(),
- &layout,
- None,
- values,
- locals,
- )?)
- }
- AggregateKind::Union(x, f) => {
- let layout = self.layout_adt((*x).into(), Substitution::empty(Interner))?;
- let offset = layout
- .fields
- .offset(u32::from(f.local_id.into_raw()) as usize)
- .bytes_usize();
- let op = self.eval_operand(&values[0], locals)?.get(&self)?;
- let mut result = vec![0; layout.size.bytes_usize()];
- result[offset..offset + op.len()].copy_from_slice(op);
- Owned(result)
- }
- AggregateKind::Adt(x, subst) => {
- let (size, variant_layout, tag) =
- self.layout_of_variant(*x, subst.clone(), locals)?;
- Owned(self.make_by_layout(size, &variant_layout, tag, values, locals)?)
}
- },
+ }
Rvalue::Cast(kind, operand, target_ty) => match kind {
- CastKind::PointerExposeAddress => not_supported!("exposing pointer address"),
- CastKind::PointerFromExposedAddress => {
- not_supported!("creating pointer from exposed address")
- }
CastKind::Pointer(cast) => match cast {
+ PointerCast::ReifyFnPointer => {
+ let current_ty = self.operand_ty(operand, locals)?;
+ if let TyKind::FnDef(_, _) = &current_ty.data(Interner).kind {
+ let id = self.vtable_map.id(current_ty);
+ let ptr_size = self.ptr_size();
+ Owned(id.to_le_bytes()[0..ptr_size].to_vec())
+ } else {
+ not_supported!("ReifyFnPointer cast of a non FnDef type");
+ }
+ }
PointerCast::Unsize => {
let current_ty = self.operand_ty(operand, locals)?;
match &target_ty.data(Interner).kind {
@@ -826,7 +884,18 @@ impl Evaluator<'_> {
}
_ => not_supported!("slice unsizing from non pointers"),
},
- TyKind::Dyn(_) => not_supported!("dyn pointer unsize cast"),
+ TyKind::Dyn(_) => match &current_ty.data(Interner).kind {
+ TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => {
+ let vtable = self.vtable_map.id(ty.clone());
+ let addr =
+ self.eval_operand(operand, locals)?.get(&self)?;
+ let mut r = Vec::with_capacity(16);
+ r.extend(addr.iter().copied());
+ r.extend(vtable.to_le_bytes().into_iter());
+ Owned(r)
+ }
+ _ => not_supported!("dyn unsizing from non pointers"),
+ },
_ => not_supported!("unknown unsized cast"),
}
}
@@ -836,7 +905,9 @@ impl Evaluator<'_> {
x => not_supported!("pointer cast {x:?}"),
},
CastKind::DynStar => not_supported!("dyn star cast"),
- CastKind::IntToInt => {
+ CastKind::IntToInt
+ | CastKind::PointerExposeAddress
+ | CastKind::PointerFromExposedAddress => {
// FIXME: handle signed cast
let current = pad16(self.eval_operand(operand, locals)?.get(&self)?, false);
let dest_size =
@@ -846,7 +917,12 @@ impl Evaluator<'_> {
CastKind::FloatToInt => not_supported!("float to int cast"),
CastKind::FloatToFloat => not_supported!("float to float cast"),
CastKind::IntToFloat => not_supported!("float to int cast"),
- CastKind::PtrToPtr => not_supported!("ptr to ptr cast"),
+ CastKind::PtrToPtr => {
+ let current = pad16(self.eval_operand(operand, locals)?.get(&self)?, false);
+ let dest_size =
+ self.size_of_sized(target_ty, locals, "destination of ptr to ptr cast")?;
+ Owned(current[0..dest_size].to_vec())
+ }
CastKind::FnPtrToPtr => not_supported!("fn ptr to ptr cast"),
},
})
@@ -913,16 +989,15 @@ impl Evaluator<'_> {
size: usize, // Not neccessarily equal to variant_layout.size
variant_layout: &Layout,
tag: Option<(usize, usize, i128)>,
- values: &Vec<Operand>,
- locals: &Locals<'_>,
+ values: impl Iterator<Item = Interval>,
) -> Result<Vec<u8>> {
let mut result = vec![0; size];
if let Some((offset, size, value)) = tag {
result[offset..offset + size].copy_from_slice(&value.to_le_bytes()[0..size]);
}
- for (i, op) in values.iter().enumerate() {
+ for (i, op) in values.enumerate() {
let offset = variant_layout.fields.offset(i).bytes_usize();
- let op = self.eval_operand(op, locals)?.get(&self)?;
+ let op = op.get(&self)?;
result[offset..offset + op.len()].copy_from_slice(op);
}
Ok(result)
@@ -1124,12 +1199,27 @@ impl Evaluator<'_> {
}
fn detect_lang_function(&self, def: FunctionId) -> Option<LangItem> {
+ use LangItem::*;
let candidate = lang_attr(self.db.upcast(), def)?;
- // filter normal lang functions out
- if [LangItem::IntoIterIntoIter, LangItem::IteratorNext].contains(&candidate) {
+ // We want to execute these functions with special logic
+ if [PanicFmt, BeginPanic, SliceLen].contains(&candidate) {
+ return Some(candidate);
+ }
+ None
+ }
+
+ fn detect_fn_trait(&self, def: FunctionId) -> Option<FnTrait> {
+ use LangItem::*;
+ let ItemContainerId::TraitId(parent) = self.db.lookup_intern_function(def).container else {
return None;
+ };
+ let l = lang_attr(self.db.upcast(), parent)?;
+ match l {
+ FnOnce => Some(FnTrait::FnOnce),
+ FnMut => Some(FnTrait::FnMut),
+ Fn => Some(FnTrait::Fn),
+ _ => None,
}
- Some(candidate)
}
fn create_memory_map(&self, bytes: &[u8], ty: &Ty, locals: &Locals<'_>) -> Result<MemoryMap> {
@@ -1199,35 +1289,276 @@ impl Evaluator<'_> {
}
fn exec_intrinsic(
- &self,
+ &mut self,
as_str: &str,
- _arg_bytes: impl Iterator<Item = Vec<u8>>,
+ args: &[IntervalAndTy],
generic_args: Substitution,
+ destination: Interval,
locals: &Locals<'_>,
- ) -> Result<Vec<u8>> {
+ ) -> Result<()> {
match as_str {
"size_of" => {
let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
return Err(MirEvalError::TypeError("size_of generic arg is not provided"));
};
- let size = self.size_of(ty, locals)?;
- match size {
- Some(x) => Ok(x.to_le_bytes().to_vec()),
- None => return Err(MirEvalError::TypeError("size_of arg is unsized")),
+ let size = self.size_of_sized(ty, locals, "size_of arg")?;
+ destination.write_from_bytes(self, &size.to_le_bytes()[0..destination.size])
+ }
+ "wrapping_add" => {
+ let [lhs, rhs] = args else {
+ return Err(MirEvalError::TypeError("const_eval_select args are not provided"));
+ };
+ let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false));
+ let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false));
+ let ans = lhs.wrapping_add(rhs);
+ destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
+ }
+ "copy" | "copy_nonoverlapping" => {
+ let [src, dst, offset] = args else {
+ return Err(MirEvalError::TypeError("copy_nonoverlapping args are not provided"));
+ };
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
+ return Err(MirEvalError::TypeError("copy_nonoverlapping generic arg is not provided"));
+ };
+ let src = Address::from_bytes(src.get(self)?)?;
+ let dst = Address::from_bytes(dst.get(self)?)?;
+ let offset = from_bytes!(usize, offset.get(self)?);
+ let size = self.size_of_sized(ty, locals, "copy_nonoverlapping ptr type")?;
+ let size = offset * size;
+ let src = Interval { addr: src, size };
+ let dst = Interval { addr: dst, size };
+ dst.write_from_interval(self, src)
+ }
+ "offset" | "arith_offset" => {
+ let [ptr, offset] = args else {
+ return Err(MirEvalError::TypeError("offset args are not provided"));
+ };
+ let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
+ return Err(MirEvalError::TypeError("offset generic arg is not provided"));
+ };
+ let ptr = u128::from_le_bytes(pad16(ptr.get(self)?, false));
+ let offset = u128::from_le_bytes(pad16(offset.get(self)?, false));
+ let size = self.size_of_sized(ty, locals, "offset ptr type")? as u128;
+ let ans = ptr + offset * size;
+ destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
+ }
+ "assert_inhabited" | "assert_zero_valid" | "assert_uninit_valid" => {
+ // FIXME: We should actually implement these checks
+ Ok(())
+ }
+ "forget" => {
+ // We don't call any drop glue yet, so there is nothing here
+ Ok(())
+ }
+ "transmute" => {
+ let [arg] = args else {
+ return Err(MirEvalError::TypeError("trasmute arg is not provided"));
+ };
+ destination.write_from_interval(self, arg.interval)
+ }
+ "const_eval_select" => {
+ let [tuple, const_fn, _] = args else {
+ return Err(MirEvalError::TypeError("const_eval_select args are not provided"));
+ };
+ let mut args = vec![const_fn.clone()];
+ let TyKind::Tuple(_, fields) = tuple.ty.kind(Interner) else {
+ return Err(MirEvalError::TypeError("const_eval_select arg[0] is not a tuple"));
+ };
+ let layout = self.layout(&tuple.ty)?;
+ for (i, field) in fields.iter(Interner).enumerate() {
+ let field = field.assert_ty_ref(Interner).clone();
+ let offset = layout.fields.offset(i).bytes_usize();
+ let addr = tuple.interval.addr.offset(offset);
+ args.push(IntervalAndTy::new(addr, field, self, locals)?);
}
+ self.exec_fn_trait(&args, destination, locals)
}
_ => not_supported!("unknown intrinsic {as_str}"),
}
}
- pub(crate) fn exec_lang_item(
- &self,
- x: LangItem,
- mut args: std::vec::IntoIter<Vec<u8>>,
- ) -> Result<Vec<u8>> {
+ fn exec_fn_pointer(
+ &mut self,
+ bytes: Interval,
+ destination: Interval,
+ args: &[IntervalAndTy],
+ locals: &Locals<'_>,
+ ) -> Result<()> {
+ let id = from_bytes!(usize, bytes.get(self)?);
+ let next_ty = self.vtable_map.ty(id)?.clone();
+ if let TyKind::FnDef(def, generic_args) = &next_ty.data(Interner).kind {
+ self.exec_fn_def(*def, generic_args, destination, args, &locals)?;
+ } else {
+ return Err(MirEvalError::TypeError("function pointer to non function"));
+ }
+ Ok(())
+ }
+
+ fn exec_fn_def(
+ &mut self,
+ def: FnDefId,
+ generic_args: &Substitution,
+ destination: Interval,
+ args: &[IntervalAndTy],
+ locals: &Locals<'_>,
+ ) -> Result<()> {
+ let def: CallableDefId = from_chalk(self.db, def);
+ let generic_args = self.subst_filler(generic_args, &locals);
+ match def {
+ CallableDefId::FunctionId(def) => {
+ if let Some(_) = self.detect_fn_trait(def) {
+ self.exec_fn_trait(&args, destination, locals)?;
+ return Ok(());
+ }
+ self.exec_fn_with_args(def, args, generic_args, locals, destination)?;
+ }
+ CallableDefId::StructId(id) => {
+ let (size, variant_layout, tag) =
+ self.layout_of_variant(id.into(), generic_args.clone(), &locals)?;
+ let result = self.make_by_layout(
+ size,
+ &variant_layout,
+ tag,
+ args.iter().map(|x| x.interval),
+ )?;
+ destination.write_from_bytes(self, &result)?;
+ }
+ CallableDefId::EnumVariantId(id) => {
+ let (size, variant_layout, tag) =
+ self.layout_of_variant(id.into(), generic_args.clone(), &locals)?;
+ let result = self.make_by_layout(
+ size,
+ &variant_layout,
+ tag,
+ args.iter().map(|x| x.interval),
+ )?;
+ destination.write_from_bytes(self, &result)?;
+ }
+ }
+ Ok(())
+ }
+
+ fn exec_fn_with_args(
+ &mut self,
+ def: FunctionId,
+ args: &[IntervalAndTy],
+ generic_args: Substitution,
+ locals: &Locals<'_>,
+ destination: Interval,
+ ) -> Result<()> {
+ let function_data = self.db.function_data(def);
+ let is_intrinsic = match &function_data.abi {
+ Some(abi) => *abi == Interned::new_str("rust-intrinsic"),
+ None => match def.lookup(self.db.upcast()).container {
+ hir_def::ItemContainerId::ExternBlockId(block) => {
+ let id = block.lookup(self.db.upcast()).id;
+ id.item_tree(self.db.upcast())[id.value].abi.as_deref()
+ == Some("rust-intrinsic")
+ }
+ _ => false,
+ },
+ };
+ if is_intrinsic {
+ return self.exec_intrinsic(
+ function_data.name.as_text().unwrap_or_default().as_str(),
+ args,
+ generic_args,
+ destination,
+ &locals,
+ );
+ }
+ let arg_bytes =
+ args.iter().map(|x| Ok(x.get(&self)?.to_owned())).collect::<Result<Vec<_>>>()?;
+ let result = if let Some(x) = self.detect_lang_function(def) {
+ self.exec_lang_item(x, &arg_bytes)?
+ } else {
+ if let Some(self_ty_idx) =
+ is_dyn_method(self.db, self.trait_env.clone(), def, generic_args.clone())
+ {
+ // In the layout of current possible receiver, which at the moment of writing this code is one of
+ // `&T`, `&mut T`, `Box<T>`, `Rc<T>`, `Arc<T>`, and `Pin<P>` where `P` is one of possible recievers,
+ // the vtable is exactly in the `[ptr_size..2*ptr_size]` bytes. So we can use it without branching on
+ // the type.
+ let ty = self
+ .vtable_map
+ .ty_of_bytes(&arg_bytes[0][self.ptr_size()..self.ptr_size() * 2])?;
+ let mut args_for_target = args.to_vec();
+ args_for_target[0] = IntervalAndTy {
+ interval: args_for_target[0].interval.slice(0..self.ptr_size()),
+ ty: ty.clone(),
+ };
+ let ty = GenericArgData::Ty(ty.clone()).intern(Interner);
+ let generics_for_target = Substitution::from_iter(
+ Interner,
+ generic_args.iter(Interner).enumerate().map(|(i, x)| {
+ if i == self_ty_idx {
+ &ty
+ } else {
+ x
+ }
+ }),
+ );
+ return self.exec_fn_with_args(
+ def,
+ &args_for_target,
+ generics_for_target,
+ locals,
+ destination,
+ );
+ }
+ let (imp, generic_args) =
+ lookup_impl_method(self.db, self.trait_env.clone(), def, generic_args.clone());
+ let generic_args = self.subst_filler(&generic_args, &locals);
+ let def = imp.into();
+ let mir_body =
+ self.db.mir_body(def).map_err(|e| MirEvalError::MirLowerError(imp, e))?;
+ self.interpret_mir(&mir_body, arg_bytes.iter().cloned(), generic_args)
+ .map_err(|e| MirEvalError::InFunction(imp, Box::new(e)))?
+ };
+ destination.write_from_bytes(self, &result)?;
+ Ok(())
+ }
+
+ fn exec_fn_trait(
+ &mut self,
+ args: &[IntervalAndTy],
+ destination: Interval,
+ locals: &Locals<'_>,
+ ) -> Result<()> {
+ let func = args.get(0).ok_or(MirEvalError::TypeError("fn trait with no arg"))?;
+ let mut func_ty = func.ty.clone();
+ let mut func_data = func.interval;
+ while let TyKind::Ref(_, _, z) = func_ty.kind(Interner) {
+ func_ty = z.clone();
+ if matches!(func_ty.kind(Interner), TyKind::Dyn(_)) {
+ let id =
+ from_bytes!(usize, &func_data.get(self)?[self.ptr_size()..self.ptr_size() * 2]);
+ func_data = func_data.slice(0..self.ptr_size());
+ func_ty = self.vtable_map.ty(id)?.clone();
+ }
+ let size = self.size_of_sized(&func_ty, locals, "self type of fn trait")?;
+ func_data = Interval { addr: Address::from_bytes(func_data.get(self)?)?, size };
+ }
+ match &func_ty.data(Interner).kind {
+ TyKind::FnDef(def, subst) => {
+ self.exec_fn_def(*def, subst, destination, &args[1..], locals)?;
+ }
+ TyKind::Function(_) => {
+ self.exec_fn_pointer(func_data, destination, &args[1..], locals)?;
+ }
+ x => not_supported!("Call FnTrait methods with type {x:?}"),
+ }
+ Ok(())
+ }
+
+ fn exec_lang_item(&self, x: LangItem, args: &[Vec<u8>]) -> Result<Vec<u8>> {
use LangItem::*;
+ let mut args = args.iter();
match x {
- PanicFmt | BeginPanic => Err(MirEvalError::Panic),
+ // FIXME: we want to find the panic message from arguments, but it wouldn't work
+ // currently even if we do that, since macro expansion of panic related macros
+ // is dummy.
+ PanicFmt | BeginPanic => Err(MirEvalError::Panic("<format-args>".to_string())),
SliceLen => {
let arg = args
.next()