diff options
author | Vincent Ambo <mail@tazj.in> | 2022-09-01T20·50+0300 |
---|---|---|
committer | tazjin <tazjin@tvl.su> | 2022-09-08T07·59+0000 |
commit | 377ba19d75a0354c51d73dd38c4a29feefcc68e4 (patch) | |
tree | 75cc9b21752a29c5b6b35db7530540b84bb78642 /tvix/eval/src/vm.rs | |
parent | 197fe37daef242596900bcab948d6fc14348f910 (diff) |
feat(tvix/eval): ensure all errors always carry a span r/4741
Previously error spans were optional because the information about code spans was not available at runtime. Now that this information has been added, the error type will always carry a span. This change is very invasive all throughout the codebase. This is due to the fact that many functions that are called *by* the VM expected to return `EvalResult`, but this no longer works as the span information is not available to those functions - only to the VM itself. To work around this the majority of these functions have been changed to return `Result<T, ErrorKind>` instead and an accompanying macro in the VM constructs the "real" error. Note that this implementatino currently has a bug where errors occuring within thunks will yield the location at which the thunk was forced, not the location at which the error occured within the code. This will be fixed soon, but the commit is large enough as is. Change-Id: Ib1ecb81a4d09d464a95ea7ea9e589f3bd08d5202 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6408 Reviewed-by: sterni <sternenseemann@systemli.org> Tested-by: BuildkiteCI
Diffstat (limited to 'tvix/eval/src/vm.rs')
-rw-r--r-- | tvix/eval/src/vm.rs | 131 |
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()) } |