about summary refs log tree commit diff
path: root/tvix/eval/src/vm.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/eval/src/vm.rs')
-rw-r--r--tvix/eval/src/vm.rs131
1 files changed, 83 insertions, 48 deletions
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs
index a2b370ab9288..5fabdb491e1f 100644
--- a/tvix/eval/src/vm.rs
+++ b/tvix/eval/src/vm.rs
@@ -6,7 +6,7 @@ use std::{cell::RefMut, rc::Rc};
 use crate::{
     chunk::Chunk,
     errors::{Error, ErrorKind, EvalResult},
-    opcode::{ConstantIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx},
+    opcode::{CodeIdx, ConstantIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx},
     upvalues::UpvalueCarrier,
     value::{Closure, Lambda, NixAttrs, NixList, Thunk, Value},
 };
@@ -37,30 +37,50 @@ pub struct VM {
     with_stack: Vec<usize>,
 }
 
+/// This macro wraps a computation that returns an ErrorKind or a
+/// result, and wraps the ErrorKind in an Error struct if present.
+///
+/// The reason for this macro's existence is that calculating spans is
+/// potentially expensive, so it should be avoided to the last moment
+/// (i.e. definite instantiation of a runtime error) if possible.
+macro_rules! fallible {
+    ( $self:ident, $body:expr) => {
+        match $body {
+            Ok(result) => result,
+            Err(kind) => {
+                return Err(Error {
+                    kind,
+                    span: $self.current_span(),
+                })
+            }
+        }
+    };
+}
+
 #[macro_export]
 macro_rules! arithmetic_op {
     ( $self:ident, $op:tt ) => {{
         let b = $self.pop();
         let a = $self.pop();
-        let result = arithmetic_op!(a, b, $op);
+        let result = fallible!($self, arithmetic_op!(a, b, $op));
         $self.push(result);
     }};
 
     ( $a:ident, $b:ident, $op:tt ) => {{
         match ($a, $b) {
-            (Value::Integer(i1), Value::Integer(i2)) => Value::Integer(i1 $op i2),
-            (Value::Float(f1), Value::Float(f2)) => Value::Float(f1 $op f2),
-            (Value::Integer(i1), Value::Float(f2)) => Value::Float(i1 as f64 $op f2),
-            (Value::Float(f1), Value::Integer(i2)) => Value::Float(f1 $op i2 as f64),
+            (Value::Integer(i1), Value::Integer(i2)) => Ok(Value::Integer(i1 $op i2)),
+            (Value::Float(f1), Value::Float(f2)) => Ok(Value::Float(f1 $op f2)),
+            (Value::Integer(i1), Value::Float(f2)) => Ok(Value::Float(i1 as f64 $op f2)),
+            (Value::Float(f1), Value::Integer(i2)) => Ok(Value::Float(f1 $op i2 as f64)),
 
-            (v1, v2) => return Err(ErrorKind::TypeError {
+            (v1, v2) => Err(ErrorKind::TypeError {
                 expected: "number (either int or float)",
                 actual: if v1.is_number() {
                     v2.type_of()
                 } else {
                     v1.type_of()
                 },
-            }.into()),
+            }),
         }
     }};
 }
@@ -80,10 +100,10 @@ macro_rules! cmp_op {
             (Value::Float(f1), Value::Integer(i2)) => f1 $op (i2 as f64),
             (Value::String(s1), Value::String(s2)) => s1 $op s2,
 
-            (lhs, rhs) => return Err(ErrorKind::Incomparable {
+            (lhs, rhs) => return Err($self.error(ErrorKind::Incomparable {
                 lhs: lhs.type_of(),
                 rhs: rhs.type_of(),
-            }.into()),
+            })),
         };
 
         $self.push(Value::Bool(result));
@@ -126,6 +146,21 @@ impl VM {
         &self.stack[self.stack.len() - 1 - offset]
     }
 
+    /// Returns the source span of the instruction currently being
+    /// executed.
+    fn current_span(&self) -> codemap::Span {
+        self.chunk().get_span(CodeIdx(self.frame().ip - 1))
+    }
+
+    /// Construct an error from the given ErrorKind and the source
+    /// span of the current instruction.
+    fn error(&self, kind: ErrorKind) -> Error {
+        Error {
+            kind,
+            span: self.current_span(),
+        }
+    }
+
     pub fn call(&mut self, lambda: Rc<Lambda>, upvalues: Vec<Value>, arg_count: usize) {
         let frame = CallFrame {
             lambda,
@@ -171,7 +206,7 @@ impl VM {
                     let result = if let (Value::String(s1), Value::String(s2)) = (&a, &b) {
                         Value::String(s1.concat(s2))
                     } else {
-                        arithmetic_op!(a, b, +)
+                        fallible!(self, arithmetic_op!(a, b, +))
                     };
 
                     self.push(result)
@@ -182,7 +217,7 @@ impl VM {
                 OpCode::OpDiv => arithmetic_op!(self, /),
 
                 OpCode::OpInvert => {
-                    let v = self.pop().as_bool()?;
+                    let v = fallible!(self, self.pop().as_bool());
                     self.push(Value::Bool(!v));
                 }
 
@@ -190,11 +225,10 @@ impl VM {
                     Value::Integer(i) => self.push(Value::Integer(-i)),
                     Value::Float(f) => self.push(Value::Float(-f)),
                     v => {
-                        return Err(ErrorKind::TypeError {
+                        return Err(self.error(ErrorKind::TypeError {
                             expected: "number (either int or float)",
                             actual: v.type_of(),
-                        }
-                        .into())
+                        }));
                     }
                 },
 
@@ -218,30 +252,29 @@ impl VM {
                 OpCode::OpAttrPath(Count(count)) => self.run_attr_path(count)?,
 
                 OpCode::OpAttrsUpdate => {
-                    let rhs = unwrap_or_clone_rc(self.pop().to_attrs()?);
-                    let lhs = unwrap_or_clone_rc(self.pop().to_attrs()?);
+                    let rhs = unwrap_or_clone_rc(fallible!(self, self.pop().to_attrs()));
+                    let lhs = unwrap_or_clone_rc(fallible!(self, self.pop().to_attrs()));
 
                     self.push(Value::Attrs(Rc::new(lhs.update(rhs))))
                 }
 
                 OpCode::OpAttrsSelect => {
-                    let key = self.pop().to_string()?;
-                    let attrs = self.pop().to_attrs()?;
+                    let key = fallible!(self, self.pop().to_string());
+                    let attrs = fallible!(self, self.pop().to_attrs());
 
                     match attrs.select(key.as_str()) {
                         Some(value) => self.push(value.clone()),
 
                         None => {
-                            return Err(ErrorKind::AttributeNotFound {
+                            return Err(self.error(ErrorKind::AttributeNotFound {
                                 name: key.as_str().to_string(),
-                            }
-                            .into())
+                            }))
                         }
                     }
                 }
 
                 OpCode::OpAttrsTrySelect => {
-                    let key = self.pop().to_string()?;
+                    let key = fallible!(self, self.pop().to_string());
                     let value = match self.pop() {
                         Value::Attrs(attrs) => match attrs.select(key.as_str()) {
                             Some(value) => value.clone(),
@@ -255,7 +288,7 @@ impl VM {
                 }
 
                 OpCode::OpAttrsIsSet => {
-                    let key = self.pop().to_string()?;
+                    let key = fallible!(self, self.pop().to_string());
                     let result = match self.pop() {
                         Value::Attrs(attrs) => attrs.contains(key.as_str()),
 
@@ -274,8 +307,8 @@ impl VM {
                 }
 
                 OpCode::OpConcat => {
-                    let rhs = self.pop().to_list()?;
-                    let lhs = self.pop().to_list()?;
+                    let rhs = fallible!(self, self.pop().to_list());
+                    let lhs = fallible!(self, self.pop().to_list());
                     self.push(Value::List(lhs.concat(&rhs)))
                 }
 
@@ -286,13 +319,13 @@ impl VM {
                 }
 
                 OpCode::OpJumpIfTrue(JumpOffset(offset)) => {
-                    if self.peek(0).as_bool()? {
+                    if fallible!(self, self.peek(0).as_bool()) {
                         self.frame_mut().ip += offset;
                     }
                 }
 
                 OpCode::OpJumpIfFalse(JumpOffset(offset)) => {
-                    if !self.peek(0).as_bool()? {
+                    if !fallible!(self, self.peek(0).as_bool()) {
                         self.frame_mut().ip += offset;
                     }
                 }
@@ -311,11 +344,10 @@ impl VM {
                 OpCode::OpAssertBool => {
                     let val = self.peek(0);
                     if !val.is_bool() {
-                        return Err(ErrorKind::TypeError {
+                        return Err(self.error(ErrorKind::TypeError {
                             expected: "bool",
                             actual: val.type_of(),
-                        }
-                        .into());
+                        }));
                     }
                 }
 
@@ -347,13 +379,13 @@ impl VM {
                 }
 
                 OpCode::OpResolveWith => {
-                    let ident = self.pop().to_string()?;
+                    let ident = fallible!(self, self.pop().to_string());
                     let value = self.resolve_with(ident.as_str())?;
                     self.push(value)
                 }
 
                 OpCode::OpResolveWithOrUpvalue(idx) => {
-                    let ident = self.pop().to_string()?;
+                    let ident = fallible!(self, self.pop().to_string());
                     match self.resolve_with(ident.as_str()) {
                         // Variable found in local `with`-stack.
                         Ok(value) => self.push(value),
@@ -372,8 +404,8 @@ impl VM {
                 }
 
                 OpCode::OpAssert => {
-                    if !self.pop().as_bool()? {
-                        return Err(ErrorKind::AssertionFailed.into());
+                    if !fallible!(self, self.pop().as_bool()) {
+                        return Err(self.error(ErrorKind::AssertionFailed));
                     }
                 }
 
@@ -386,19 +418,18 @@ impl VM {
 
                         Value::Builtin(builtin) => {
                             let arg = self.pop();
-                            let result = builtin.apply(arg)?;
+                            let result = fallible!(self, builtin.apply(arg));
                             self.push(result);
                         }
-                        _ => return Err(ErrorKind::NotCallable.into()),
+                        _ => return Err(self.error(ErrorKind::NotCallable)),
                     };
                 }
 
                 OpCode::OpGetUpvalue(upv_idx) => {
                     let value = self.frame().upvalue(upv_idx).clone();
                     if let Value::DynamicUpvalueMissing(name) = value {
-                        return Err(
-                            ErrorKind::UnknownDynamicVariable(name.as_str().to_string()).into()
-                        );
+                        return Err(self
+                            .error(ErrorKind::UnknownDynamicVariable(name.as_str().to_string())));
                     }
 
                     self.push(value);
@@ -446,7 +477,7 @@ impl VM {
                     let mut value = self.pop();
 
                     if let Value::Thunk(thunk) = value {
-                        thunk.force(self)?;
+                        fallible!(self, thunk.force(self));
                         value = thunk.value().clone();
                     }
 
@@ -498,7 +529,7 @@ impl VM {
         let mut path = Vec::with_capacity(count);
 
         for _ in 0..count {
-            path.push(self.pop().to_string()?);
+            path.push(fallible!(self, self.pop().to_string()));
         }
 
         self.push(Value::AttrPath(path));
@@ -506,7 +537,11 @@ impl VM {
     }
 
     fn run_attrset(&mut self, count: usize) -> EvalResult<()> {
-        let attrs = NixAttrs::construct(count, self.stack.split_off(self.stack.len() - count * 2))?;
+        let attrs = fallible!(
+            self,
+            NixAttrs::construct(count, self.stack.split_off(self.stack.len() - count * 2))
+        );
+
         self.push(Value::Attrs(Rc::new(attrs)));
         Ok(())
     }
@@ -518,7 +553,7 @@ impl VM {
         let mut out = String::new();
 
         for _ in 0..count {
-            out.push_str(self.pop().to_string()?.as_str());
+            out.push_str(fallible!(self, self.pop().to_string()).as_str());
         }
 
         self.push(Value::String(out.into()));
@@ -527,7 +562,7 @@ impl VM {
 
     fn resolve_dynamic_upvalue(&mut self, ident_idx: ConstantIdx) -> EvalResult<Value> {
         let chunk = self.chunk();
-        let ident = chunk.constant(ident_idx).as_str()?.to_string();
+        let ident = fallible!(self, chunk.constant(ident_idx).as_str()).to_string();
 
         // Peek at the current instruction (note: IP has already
         // advanced!) to see if it is actually data indicating a
@@ -560,14 +595,14 @@ impl VM {
     /// Resolve a dynamic identifier through the with-stack at runtime.
     fn resolve_with(&self, ident: &str) -> EvalResult<Value> {
         for idx in self.with_stack.iter().rev() {
-            let with = self.stack[*idx].as_attrs()?;
+            let with = fallible!(self, self.stack[*idx].as_attrs());
             match with.select(ident) {
                 None => continue,
                 Some(val) => return Ok(val.clone()),
             }
         }
 
-        Err(ErrorKind::UnknownDynamicVariable(ident.to_string()).into())
+        Err(self.error(ErrorKind::UnknownDynamicVariable(ident.to_string())))
     }
 
     /// Populate the upvalue fields of a thunk or closure under construction.
@@ -619,7 +654,7 @@ impl VM {
             Value::List(list) => list.iter().try_for_each(|elem| self.force_for_output(elem)),
 
             Value::Thunk(thunk) => {
-                thunk.force(self)?;
+                fallible!(self, thunk.force(self));
                 self.force_for_output(&thunk.value())
             }