From 65d3be0084bed17730ddabee06e1e72b88a7e87a Mon Sep 17 00:00:00 2001 From: Scott Olson <scott@solson.me> Date: Fri, 14 Oct 2016 22:10:06 -0600 Subject: [PATCH] Add support for local `Value`s in `Lvalue`. The new `Lvalue` has an additional form, `Lvalue::Local`, with the old form being `Lvalue::Ptr`. In an upcoming commit, we will start producing the new form for locals, and enable reading and writing of primitive locals without ever touching `Memory`. Statics should be able to get a similar treatment, where primitive statics can be stored and accessed without touching `Memory`. --- src/interpreter/mod.rs | 284 ++++++++++++++++++++++++----------------- 1 file changed, 167 insertions(+), 117 deletions(-) diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index dc787a7608c..77d4c3d560c 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -42,6 +42,7 @@ pub struct EvalContext<'a, 'tcx: 'a> { memory: Memory<'a, 'tcx>, /// Precomputed statics, constants and promoteds. + // FIXME(solson): Change from Pointer to Value. statics: HashMap<ConstantId<'tcx>, Pointer>, /// The virtual call stack. @@ -93,13 +94,24 @@ pub struct Frame<'a, 'tcx: 'a> { } #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Lvalue { - ptr: Pointer, - extra: LvalueExtra, +pub enum Lvalue { + /// An lvalue referring to a value allocated in the `Memory` system. + Ptr { + ptr: Pointer, + extra: LvalueExtra, + }, + + /// An lvalue referring to a value on the stack. + Local { + frame: usize, + local: usize, + } + + // TODO(solson): Static/Const? None/Never? } #[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum LvalueExtra { +pub enum LvalueExtra { None, Length(u64), Vtable(Pointer), @@ -603,9 +615,13 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { let dest = dest.to_ptr(); let lvalue = self.eval_lvalue(lvalue)?; - self.memory.write_ptr(dest, lvalue.ptr)?; + + // FIXME(solson) + let (ptr, extra) = lvalue.to_ptr_and_extra(); + + self.memory.write_ptr(dest, ptr)?; let extra_ptr = dest.offset(self.memory.pointer_size() as isize); - match lvalue.extra { + match extra { LvalueExtra::None => {}, LvalueExtra::Length(len) => self.memory.write_usize(extra_ptr, len)?, LvalueExtra::Vtable(ptr) => self.memory.write_ptr(extra_ptr, ptr)?, @@ -818,12 +834,19 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { fn eval_lvalue(&mut self, lvalue: &mir::Lvalue<'tcx>) -> EvalResult<'tcx, Lvalue> { use rustc::mir::repr::Lvalue::*; - let ptr = match *lvalue { + match *lvalue { Local(i) => { - match self.frame().locals[i.index()] { + // FIXME(solson): Switch to the following code to start enabling lvalues referring + // to `Value`s placed on the locals stack instead of in `Memory`: + // + // let frame_index = self.stack.len(); + // Ok(Lvalue::Local { frame: frame_index, local: i.index() }) + // + let ptr = match self.frame().locals[i.index()] { Value::ByRef(p) => p, _ => bug!(), - } + }; + Ok(Lvalue::Ptr { ptr: ptr, extra: LvalueExtra::None }) } Static(def_id) => { @@ -833,119 +856,132 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { substs: substs, kind: ConstantKind::Global, }; - *self.statics.get(&cid).expect("static should have been cached (lvalue)") - }, + let ptr = *self.statics.get(&cid) + .expect("static should have been cached (lvalue)"); + Ok(Lvalue::Ptr { ptr: ptr, extra: LvalueExtra::None }) + } - Projection(ref proj) => { - let base = self.eval_lvalue(&proj.base)?; - let base_ty = self.lvalue_ty(&proj.base); - let base_layout = self.type_layout(base_ty); + Projection(ref proj) => self.eval_lvalue_projection(proj), + } + } - use rustc::mir::repr::ProjectionElem::*; - match proj.elem { - Field(field, field_ty) => { - let field_ty = self.monomorphize(field_ty, self.substs()); - use rustc::ty::layout::Layout::*; - let field = field.index(); - let offset = match *base_layout { - Univariant { ref variant, .. } => variant.offsets[field], - General { ref variants, .. } => { - if let LvalueExtra::DowncastVariant(variant_idx) = base.extra { - // +1 for the discriminant, which is field 0 - variants[variant_idx].offsets[field + 1] - } else { - bug!("field access on enum had no variant index"); - } - } - RawNullablePointer { .. } => { - assert_eq!(field.index(), 0); - return Ok(base); - } - StructWrappedNullablePointer { ref nonnull, .. } => { - nonnull.offsets[field] - } - _ => bug!("field access on non-product type: {:?}", base_layout), - }; + fn eval_lvalue_projection( + &mut self, + proj: &mir::LvalueProjection<'tcx>, + ) -> EvalResult<'tcx, Lvalue> { + let base = self.eval_lvalue(&proj.base)?; + let (base_ptr, base_extra) = base.to_ptr_and_extra(); + let base_ty = self.lvalue_ty(&proj.base); + let base_layout = self.type_layout(base_ty); - let ptr = base.ptr.offset(offset.bytes() as isize); - if self.type_is_sized(field_ty) { - ptr + use rustc::mir::repr::ProjectionElem::*; + let (ptr, extra) = match proj.elem { + Field(field, field_ty) => { + let field_ty = self.monomorphize(field_ty, self.substs()); + let field = field.index(); + + use rustc::ty::layout::Layout::*; + let offset = match *base_layout { + Univariant { ref variant, .. } => variant.offsets[field], + + General { ref variants, .. } => { + if let LvalueExtra::DowncastVariant(variant_idx) = base_extra { + // +1 for the discriminant, which is field 0 + variants[variant_idx].offsets[field + 1] } else { - match base.extra { - LvalueExtra::None => bug!("expected fat pointer"), - LvalueExtra::DowncastVariant(..) => bug!("Rust doesn't support unsized fields in enum variants"), - LvalueExtra::Vtable(_) | - LvalueExtra::Length(_) => {}, - } - return Ok(Lvalue { - ptr: ptr, - extra: base.extra, - }); + bug!("field access on enum had no variant index"); } - }, - - Downcast(_, variant) => { - use rustc::ty::layout::Layout::*; - match *base_layout { - General { .. } => { - return Ok(Lvalue { - ptr: base.ptr, - extra: LvalueExtra::DowncastVariant(variant), - }); - } - RawNullablePointer { .. } | StructWrappedNullablePointer { .. } => { - return Ok(base); - } - _ => bug!("variant downcast on non-aggregate: {:?}", base_layout), - } - }, - - Deref => { - use primval::PrimVal::*; - use interpreter::value::Value::*; - let (ptr, extra) = match self.read_value(base.ptr, base_ty)? { - ByValPair(Ptr(ptr), Ptr(vptr)) => (ptr, LvalueExtra::Vtable(vptr)), - ByValPair(Ptr(ptr), n) => (ptr, LvalueExtra::Length(n.expect_uint("slice length"))), - ByVal(Ptr(ptr)) => (ptr, LvalueExtra::None), - _ => bug!("can't deref non pointer types"), - }; - return Ok(Lvalue { ptr: ptr, extra: extra }); } - Index(ref operand) => { - let (elem_ty, len) = base.elem_ty_and_len(base_ty); - let elem_size = self.type_size(elem_ty); - let n_ptr = self.eval_operand(operand)?; - let usize = self.tcx.types.usize; - let n = self.value_to_primval(n_ptr, usize)?.expect_uint("Projection::Index expected usize"); - assert!(n < len); - base.ptr.offset(n as isize * elem_size as isize) + RawNullablePointer { .. } => { + assert_eq!(field.index(), 0); + return Ok(base); } - ConstantIndex { offset, min_length, from_end } => { - let (elem_ty, n) = base.elem_ty_and_len(base_ty); - let elem_size = self.type_size(elem_ty); - assert!(n >= min_length as u64); - if from_end { - base.ptr.offset((n as isize - offset as isize) * elem_size as isize) - } else { - base.ptr.offset(offset as isize * elem_size as isize) - } - }, - Subslice { from, to } => { - let (elem_ty, n) = base.elem_ty_and_len(base_ty); - let elem_size = self.type_size(elem_ty); - assert!((from as u64) <= n - (to as u64)); - return Ok(Lvalue { - ptr: base.ptr.offset(from as isize * elem_size as isize), - extra: LvalueExtra::Length(n - to as u64 - from as u64), - }) - }, + StructWrappedNullablePointer { ref nonnull, .. } => { + nonnull.offsets[field] + } + + _ => bug!("field access on non-product type: {:?}", base_layout), + }; + + let ptr = base_ptr.offset(offset.bytes() as isize); + let extra = if self.type_is_sized(field_ty) { + LvalueExtra::None + } else { + match base_extra { + LvalueExtra::None => bug!("expected fat pointer"), + LvalueExtra::DowncastVariant(..) => + bug!("Rust doesn't support unsized fields in enum variants"), + LvalueExtra::Vtable(_) | + LvalueExtra::Length(_) => {}, + } + base_extra + }; + + (ptr, extra) + } + + Downcast(_, variant) => { + use rustc::ty::layout::Layout::*; + let extra = match *base_layout { + General { .. } => LvalueExtra::DowncastVariant(variant), + RawNullablePointer { .. } | StructWrappedNullablePointer { .. } => base_extra, + _ => bug!("variant downcast on non-aggregate: {:?}", base_layout), + }; + (base_ptr, extra) + } + + Deref => { + use primval::PrimVal::*; + use interpreter::value::Value::*; + match self.read_value(base_ptr, base_ty)? { + ByValPair(Ptr(ptr), Ptr(vptr)) => (ptr, LvalueExtra::Vtable(vptr)), + ByValPair(Ptr(ptr), n) => + (ptr, LvalueExtra::Length(n.expect_uint("slice length"))), + ByVal(Ptr(ptr)) => (ptr, LvalueExtra::None), + _ => bug!("can't deref non pointer types"), } } + + Index(ref operand) => { + let (elem_ty, len) = base.elem_ty_and_len(base_ty); + let elem_size = self.type_size(elem_ty); + let n_ptr = self.eval_operand(operand)?; + let usize = self.tcx.types.usize; + let n = self.value_to_primval(n_ptr, usize)? + .expect_uint("Projection::Index expected usize"); + assert!(n < len); + let ptr = base_ptr.offset(n as isize * elem_size as isize); + (ptr, LvalueExtra::None) + } + + ConstantIndex { offset, min_length, from_end } => { + let (elem_ty, n) = base.elem_ty_and_len(base_ty); + let elem_size = self.type_size(elem_ty); + assert!(n >= min_length as u64); + + let index = if from_end { + n as isize - offset as isize + } else { + offset as isize + }; + + let ptr = base_ptr.offset(index * elem_size as isize); + (ptr, LvalueExtra::None) + } + + Subslice { from, to } => { + let (elem_ty, n) = base.elem_ty_and_len(base_ty); + let elem_size = self.type_size(elem_ty); + assert!((from as u64) <= n - (to as u64)); + let ptr = base_ptr.offset(from as isize * elem_size as isize); + let extra = LvalueExtra::Length(n - to as u64 - from as u64); + (ptr, extra) + } }; - Ok(Lvalue { ptr: ptr, extra: LvalueExtra::None }) + Ok(Lvalue::Ptr { ptr: ptr, extra: extra }) } fn lvalue_ty(&self, lvalue: &mir::Lvalue<'tcx>) -> Ty<'tcx> { @@ -1242,22 +1278,36 @@ impl<'a, 'tcx> EvalContext<'a, 'tcx> { impl Lvalue { fn from_ptr(ptr: Pointer) -> Self { - Lvalue { ptr: ptr, extra: LvalueExtra::None } + Lvalue::Ptr { ptr: ptr, extra: LvalueExtra::None } + } + + fn to_ptr_and_extra(self) -> (Pointer, LvalueExtra) { + if let Lvalue::Ptr { ptr, extra } = self { + (ptr, extra) + } else { + // FIXME(solson): This isn't really a bug, but it's unhandled until I finish + // refactoring. + bug!("from_ptr: Not an Lvalue::Ptr"); + } } fn to_ptr(self) -> Pointer { - assert_eq!(self.extra, LvalueExtra::None); - self.ptr + let (ptr, extra) = self.to_ptr_and_extra(); + assert_eq!(extra, LvalueExtra::None); + ptr } fn elem_ty_and_len<'tcx>(self, ty: Ty<'tcx>) -> (Ty<'tcx>, u64) { match ty.sty { ty::TyArray(elem, n) => (elem, n as u64), - ty::TySlice(elem) => if let LvalueExtra::Length(len) = self.extra { - (elem, len) - } else { - bug!("elem_ty_and_len called on a slice given non-slice lvalue: {:?}", self); - }, + + ty::TySlice(elem) => { + match self { + Lvalue::Ptr { extra: LvalueExtra::Length(len), .. } => (elem, len), + _ => bug!("elem_ty_and_len of a TySlice given non-slice lvalue: {:?}", self), + } + } + _ => bug!("elem_ty_and_len expected array or slice, got {:?}", ty), } }