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),
         }
     }