about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.exp2
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.nix1
-rw-r--r--tvix/eval/src/vm.rs551
3 files changed, 285 insertions, 269 deletions
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.exp
index 770a5b6db405..8b6ed7dbac6b 100644
--- a/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.exp
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.exp
@@ -1 +1 @@
-{ w = { success = false; value = false; }; x = { success = true; value = "x"; }; y = { success = false; value = false; }; z = { success = false; value = false; }; }
+{ v = false; w = { success = false; value = false; }; x = { success = true; value = "x"; }; y = { success = false; value = false; }; z = { success = false; value = false; }; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.nix
index 8915032abd6f..e2357c798753 100644
--- a/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.nix
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-tryeval.nix
@@ -1,4 +1,5 @@
 {
+  v = (builtins.tryEval (toString <oink>)).value;
   w = builtins.tryEval <nope>;
   x = builtins.tryEval "x";
   y = builtins.tryEval (assert false; "y");
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs
index daee8c886c84..ebf3de297b5a 100644
--- a/tvix/eval/src/vm.rs
+++ b/tvix/eval/src/vm.rs
@@ -362,342 +362,357 @@ impl<'o> VM<'o> {
             self.observer
                 .observe_execute_op(self.frame().ip, &op, &self.stack);
 
-            match op {
-                OpCode::OpConstant(idx) => {
-                    let c = self.chunk()[idx].clone();
-                    self.push(c);
-                }
-
-                OpCode::OpPop => {
-                    self.pop();
-                }
+            let res = self.run_op(op);
 
-                OpCode::OpAdd => {
-                    let b = self.pop();
-                    let a = self.pop();
-
-                    let result = match (&a, &b) {
-                        (Value::String(s1), Value::String(s2)) => Value::String(s1.concat(s2)),
-                        (Value::Path(p), v) => {
-                            let mut path = p.to_string_lossy().into_owned();
-                            path.push_str(
-                                &v.coerce_to_string(CoercionKind::Weak, self)
-                                    .map_err(|ek| self.error(ek))?,
-                            );
-                            PathBuf::from(path).clean().into()
-                        }
-                        _ => fallible!(self, arithmetic_op!(&a, &b, +)),
-                    };
-
-                    self.push(result)
-                }
+            if self.frame().ip.0 == self.chunk().code.len() {
+                self.frames.pop();
+                return res;
+            } else {
+                res?;
+            }
+        }
+    }
 
-                OpCode::OpSub => arithmetic_op!(self, -),
-                OpCode::OpMul => arithmetic_op!(self, *),
-                OpCode::OpDiv => arithmetic_op!(self, /),
+    fn run_op(&mut self, op: OpCode) -> EvalResult<()> {
+        match op {
+            OpCode::OpConstant(idx) => {
+                let c = self.chunk()[idx].clone();
+                self.push(c);
+            }
 
-                OpCode::OpInvert => {
-                    let v = fallible!(self, self.pop().as_bool());
-                    self.push(Value::Bool(!v));
-                }
+            OpCode::OpPop => {
+                self.pop();
+            }
 
-                OpCode::OpNegate => match self.pop() {
-                    Value::Integer(i) => self.push(Value::Integer(-i)),
-                    Value::Float(f) => self.push(Value::Float(-f)),
-                    v => {
-                        return Err(self.error(ErrorKind::TypeError {
-                            expected: "number (either int or float)",
-                            actual: v.type_of(),
-                        }));
+            OpCode::OpAdd => {
+                let b = self.pop();
+                let a = self.pop();
+
+                let result = match (&a, &b) {
+                    (Value::String(s1), Value::String(s2)) => Value::String(s1.concat(s2)),
+                    (Value::Path(p), v) => {
+                        let mut path = p.to_string_lossy().into_owned();
+                        path.push_str(
+                            &v.coerce_to_string(CoercionKind::Weak, self)
+                                .map_err(|ek| self.error(ek))?,
+                        );
+                        PathBuf::from(path).clean().into()
                     }
-                },
+                    _ => fallible!(self, arithmetic_op!(&a, &b, +)),
+                };
+
+                self.push(result)
+            }
 
-                OpCode::OpEqual => {
-                    let v2 = self.pop();
-                    let v1 = self.pop();
-                    let res = fallible!(self, v1.nix_eq(&v2, self));
+            OpCode::OpSub => arithmetic_op!(self, -),
+            OpCode::OpMul => arithmetic_op!(self, *),
+            OpCode::OpDiv => arithmetic_op!(self, /),
 
-                    self.push(Value::Bool(res))
+            OpCode::OpInvert => {
+                let v = fallible!(self, self.pop().as_bool());
+                self.push(Value::Bool(!v));
+            }
+
+            OpCode::OpNegate => match self.pop() {
+                Value::Integer(i) => self.push(Value::Integer(-i)),
+                Value::Float(f) => self.push(Value::Float(-f)),
+                v => {
+                    return Err(self.error(ErrorKind::TypeError {
+                        expected: "number (either int or float)",
+                        actual: v.type_of(),
+                    }));
                 }
+            },
 
-                OpCode::OpLess => cmp_op!(self, <),
-                OpCode::OpLessOrEq => cmp_op!(self, <=),
-                OpCode::OpMore => cmp_op!(self, >),
-                OpCode::OpMoreOrEq => cmp_op!(self, >=),
+            OpCode::OpEqual => {
+                let v2 = self.pop();
+                let v1 = self.pop();
+                let res = fallible!(self, v1.nix_eq(&v2, self));
 
-                OpCode::OpNull => self.push(Value::Null),
-                OpCode::OpTrue => self.push(Value::Bool(true)),
-                OpCode::OpFalse => self.push(Value::Bool(false)),
+                self.push(Value::Bool(res))
+            }
 
-                OpCode::OpAttrs(Count(count)) => self.run_attrset(count)?,
+            OpCode::OpLess => cmp_op!(self, <),
+            OpCode::OpLessOrEq => cmp_op!(self, <=),
+            OpCode::OpMore => cmp_op!(self, >),
+            OpCode::OpMoreOrEq => cmp_op!(self, >=),
 
-                OpCode::OpAttrsUpdate => {
-                    let rhs = unwrap_or_clone_rc(fallible!(self, self.pop().to_attrs()));
-                    let lhs = unwrap_or_clone_rc(fallible!(self, self.pop().to_attrs()));
+            OpCode::OpNull => self.push(Value::Null),
+            OpCode::OpTrue => self.push(Value::Bool(true)),
+            OpCode::OpFalse => self.push(Value::Bool(false)),
 
-                    self.push(Value::attrs(lhs.update(rhs)))
-                }
+            OpCode::OpAttrs(Count(count)) => self.run_attrset(count)?,
 
-                OpCode::OpAttrsSelect => {
-                    let key = fallible!(self, self.pop().to_str());
-                    let attrs = fallible!(self, self.pop().to_attrs());
+            OpCode::OpAttrsUpdate => {
+                let rhs = unwrap_or_clone_rc(fallible!(self, self.pop().to_attrs()));
+                let lhs = unwrap_or_clone_rc(fallible!(self, self.pop().to_attrs()));
 
-                    match attrs.select(key.as_str()) {
-                        Some(value) => self.push(value.clone()),
+                self.push(Value::attrs(lhs.update(rhs)))
+            }
+
+            OpCode::OpAttrsSelect => {
+                let key = fallible!(self, self.pop().to_str());
+                let attrs = fallible!(self, self.pop().to_attrs());
+
+                match attrs.select(key.as_str()) {
+                    Some(value) => self.push(value.clone()),
 
-                        None => {
-                            return Err(self.error(ErrorKind::AttributeNotFound {
-                                name: key.as_str().to_string(),
-                            }))
-                        }
+                    None => {
+                        return Err(self.error(ErrorKind::AttributeNotFound {
+                            name: key.as_str().to_string(),
+                        }))
                     }
                 }
+            }
 
-                OpCode::OpAttrsTrySelect => {
-                    let key = fallible!(self, self.pop().to_str());
-                    let value = match self.pop() {
-                        Value::Attrs(attrs) => match attrs.select(key.as_str()) {
-                            Some(value) => value.clone(),
-                            None => Value::AttrNotFound,
-                        },
+            OpCode::OpAttrsTrySelect => {
+                let key = fallible!(self, self.pop().to_str());
+                let value = match self.pop() {
+                    Value::Attrs(attrs) => match attrs.select(key.as_str()) {
+                        Some(value) => value.clone(),
+                        None => Value::AttrNotFound,
+                    },
 
-                        _ => Value::AttrNotFound,
-                    };
+                    _ => Value::AttrNotFound,
+                };
 
-                    self.push(value);
-                }
+                self.push(value);
+            }
 
-                OpCode::OpHasAttr => {
-                    let key = fallible!(self, self.pop().to_str());
-                    let result = match self.pop() {
-                        Value::Attrs(attrs) => attrs.contains(key.as_str()),
+            OpCode::OpHasAttr => {
+                let key = fallible!(self, self.pop().to_str());
+                let result = match self.pop() {
+                    Value::Attrs(attrs) => attrs.contains(key.as_str()),
 
-                        // Nix allows use of `?` on non-set types, but
-                        // always returns false in those cases.
-                        _ => false,
-                    };
+                    // Nix allows use of `?` on non-set types, but
+                    // always returns false in those cases.
+                    _ => false,
+                };
 
-                    self.push(Value::Bool(result));
-                }
+                self.push(Value::Bool(result));
+            }
 
-                OpCode::OpList(Count(count)) => {
-                    let list =
-                        NixList::construct(count, self.stack.split_off(self.stack.len() - count));
-                    self.push(Value::List(list));
-                }
+            OpCode::OpList(Count(count)) => {
+                let list =
+                    NixList::construct(count, self.stack.split_off(self.stack.len() - count));
+                self.push(Value::List(list));
+            }
 
-                OpCode::OpConcat => {
-                    let rhs = fallible!(self, self.pop().to_list());
-                    let lhs = fallible!(self, self.pop().to_list());
-                    self.push(Value::List(lhs.concat(&rhs)))
-                }
+            OpCode::OpConcat => {
+                let rhs = fallible!(self, self.pop().to_list());
+                let lhs = fallible!(self, self.pop().to_list());
+                self.push(Value::List(lhs.concat(&rhs)))
+            }
 
-                OpCode::OpInterpolate(Count(count)) => self.run_interpolate(count)?,
+            OpCode::OpInterpolate(Count(count)) => self.run_interpolate(count)?,
 
-                OpCode::OpCoerceToString => {
-                    // TODO: handle string context, copying to store
-                    let string = fallible!(
-                        self,
-                        // note that coerce_to_string also forces
-                        self.pop().coerce_to_string(CoercionKind::Weak, self)
-                    );
-                    self.push(Value::String(string));
-                }
+            OpCode::OpCoerceToString => {
+                // TODO: handle string context, copying to store
+                let string = fallible!(
+                    self,
+                    // note that coerce_to_string also forces
+                    self.pop().coerce_to_string(CoercionKind::Weak, self)
+                );
+                self.push(Value::String(string));
+            }
 
-                OpCode::OpFindFile => {
-                    let path = self.pop().to_str().map_err(|e| self.error(e))?;
-                    let resolved = self.nix_path.resolve(path).map_err(|e| self.error(e))?;
-                    self.push(resolved.into());
-                }
+            OpCode::OpFindFile => {
+                let path = self.pop().to_str().map_err(|e| self.error(e))?;
+                let resolved = self.nix_path.resolve(path).map_err(|e| self.error(e))?;
+                self.push(resolved.into());
+            }
 
-                OpCode::OpJump(JumpOffset(offset)) => {
-                    debug_assert!(offset != 0);
+            OpCode::OpJump(JumpOffset(offset)) => {
+                debug_assert!(offset != 0);
+                self.frame_mut().ip += offset;
+            }
+
+            OpCode::OpJumpIfTrue(JumpOffset(offset)) => {
+                debug_assert!(offset != 0);
+                if fallible!(self, self.peek(0).as_bool()) {
                     self.frame_mut().ip += offset;
                 }
+            }
 
-                OpCode::OpJumpIfTrue(JumpOffset(offset)) => {
-                    debug_assert!(offset != 0);
-                    if fallible!(self, self.peek(0).as_bool()) {
-                        self.frame_mut().ip += offset;
-                    }
+            OpCode::OpJumpIfFalse(JumpOffset(offset)) => {
+                debug_assert!(offset != 0);
+                if !fallible!(self, self.peek(0).as_bool()) {
+                    self.frame_mut().ip += offset;
                 }
+            }
 
-                OpCode::OpJumpIfFalse(JumpOffset(offset)) => {
-                    debug_assert!(offset != 0);
-                    if !fallible!(self, self.peek(0).as_bool()) {
-                        self.frame_mut().ip += offset;
-                    }
+            OpCode::OpJumpIfNotFound(JumpOffset(offset)) => {
+                debug_assert!(offset != 0);
+                if matches!(self.peek(0), Value::AttrNotFound) {
+                    self.pop();
+                    self.frame_mut().ip += offset;
                 }
+            }
 
-                OpCode::OpJumpIfNotFound(JumpOffset(offset)) => {
-                    debug_assert!(offset != 0);
-                    if matches!(self.peek(0), Value::AttrNotFound) {
-                        self.pop();
-                        self.frame_mut().ip += offset;
-                    }
+            // These assertion operations error out if the stack
+            // top is not of the expected type. This is necessary
+            // to implement some specific behaviours of Nix
+            // exactly.
+            OpCode::OpAssertBool => {
+                let val = self.peek(0);
+                if !val.is_bool() {
+                    return Err(self.error(ErrorKind::TypeError {
+                        expected: "bool",
+                        actual: val.type_of(),
+                    }));
                 }
+            }
 
-                // These assertion operations error out if the stack
-                // top is not of the expected type. This is necessary
-                // to implement some specific behaviours of Nix
-                // exactly.
-                OpCode::OpAssertBool => {
-                    let val = self.peek(0);
-                    if !val.is_bool() {
-                        return Err(self.error(ErrorKind::TypeError {
-                            expected: "bool",
-                            actual: val.type_of(),
-                        }));
-                    }
-                }
+            // Remove the given number of elements from the stack,
+            // but retain the top value.
+            OpCode::OpCloseScope(Count(count)) => {
+                // Immediately move the top value into the right
+                // position.
+                let target_idx = self.stack.len() - 1 - count;
+                self.stack[target_idx] = self.pop();
 
-                // Remove the given number of elements from the stack,
-                // but retain the top value.
-                OpCode::OpCloseScope(Count(count)) => {
-                    // Immediately move the top value into the right
-                    // position.
-                    let target_idx = self.stack.len() - 1 - count;
-                    self.stack[target_idx] = self.pop();
-
-                    // Then drop the remaining values.
-                    for _ in 0..(count - 1) {
-                        self.pop();
-                    }
+                // Then drop the remaining values.
+                for _ in 0..(count - 1) {
+                    self.pop();
                 }
+            }
 
-                OpCode::OpGetLocal(StackIdx(local_idx)) => {
-                    let idx = self.frame().stack_offset + local_idx;
-                    self.push(self.stack[idx].clone());
-                }
+            OpCode::OpGetLocal(StackIdx(local_idx)) => {
+                let idx = self.frame().stack_offset + local_idx;
+                self.push(self.stack[idx].clone());
+            }
 
-                OpCode::OpPushWith(StackIdx(idx)) => {
-                    self.with_stack.push(self.frame().stack_offset + idx)
-                }
+            OpCode::OpPushWith(StackIdx(idx)) => {
+                self.with_stack.push(self.frame().stack_offset + idx)
+            }
 
-                OpCode::OpPopWith => {
-                    self.with_stack.pop();
-                }
+            OpCode::OpPopWith => {
+                self.with_stack.pop();
+            }
 
-                OpCode::OpResolveWith => {
-                    let ident = fallible!(self, self.pop().to_str());
-                    let value = self.resolve_with(ident.as_str())?;
-                    self.push(value)
-                }
+            OpCode::OpResolveWith => {
+                let ident = fallible!(self, self.pop().to_str());
+                let value = self.resolve_with(ident.as_str())?;
+                self.push(value)
+            }
 
-                OpCode::OpResolveWithOrUpvalue(idx) => {
-                    let ident = fallible!(self, self.pop().to_str());
-                    match self.resolve_with(ident.as_str()) {
-                        // Variable found in local `with`-stack.
-                        Ok(value) => self.push(value),
-
-                        // Variable not found => check upvalues.
-                        Err(Error {
-                            kind: ErrorKind::UnknownDynamicVariable(_),
-                            ..
-                        }) => {
-                            let value = self.frame().upvalue(idx).clone();
-                            self.push(value);
-                        }
-
-                        Err(err) => return Err(err),
+            OpCode::OpResolveWithOrUpvalue(idx) => {
+                let ident = fallible!(self, self.pop().to_str());
+                match self.resolve_with(ident.as_str()) {
+                    // Variable found in local `with`-stack.
+                    Ok(value) => self.push(value),
+
+                    // Variable not found => check upvalues.
+                    Err(Error {
+                        kind: ErrorKind::UnknownDynamicVariable(_),
+                        ..
+                    }) => {
+                        let value = self.frame().upvalue(idx).clone();
+                        self.push(value);
                     }
-                }
 
-                OpCode::OpAssertFail => {
-                    return Err(self.error(ErrorKind::AssertionFailed));
+                    Err(err) => return Err(err),
                 }
+            }
 
-                OpCode::OpCall => {
-                    let callable = self.pop();
-                    self.call_value(&callable)?;
-                }
+            OpCode::OpAssertFail => {
+                return Err(self.error(ErrorKind::AssertionFailed));
+            }
 
-                OpCode::OpTailCall => {
-                    let callable = self.pop();
-                    self.tail_call_value(callable)?;
-                }
+            OpCode::OpCall => {
+                let callable = self.pop();
+                self.call_value(&callable)?;
+            }
 
-                OpCode::OpGetUpvalue(upv_idx) => {
-                    let value = self.frame().upvalue(upv_idx).clone();
-                    self.push(value);
-                }
+            OpCode::OpTailCall => {
+                let callable = self.pop();
+                self.tail_call_value(callable)?;
+            }
 
-                OpCode::OpClosure(idx) => {
-                    let blueprint = match &self.chunk()[idx] {
-                        Value::Blueprint(lambda) => lambda.clone(),
-                        _ => panic!("compiler bug: non-blueprint in blueprint slot"),
-                    };
-
-                    let upvalue_count = blueprint.upvalue_count;
-                    debug_assert!(
-                        upvalue_count > 0,
-                        "OpClosure should not be called for plain lambdas"
-                    );
-
-                    let closure = Closure::new(blueprint);
-                    let upvalues = closure.upvalues_mut();
-                    self.push(Value::Closure(closure.clone()));
-
-                    // From this point on we internally mutate the
-                    // closure object's upvalues. The closure is
-                    // already in its stack slot, which means that it
-                    // can capture itself as an upvalue for
-                    // self-recursion.
-                    self.populate_upvalues(upvalue_count, upvalues)?;
-                }
+            OpCode::OpGetUpvalue(upv_idx) => {
+                let value = self.frame().upvalue(upv_idx).clone();
+                self.push(value);
+            }
 
-                OpCode::OpThunk(idx) => {
-                    let blueprint = match &self.chunk()[idx] {
-                        Value::Blueprint(lambda) => lambda.clone(),
-                        _ => panic!("compiler bug: non-blueprint in blueprint slot"),
-                    };
+            OpCode::OpClosure(idx) => {
+                let blueprint = match &self.chunk()[idx] {
+                    Value::Blueprint(lambda) => lambda.clone(),
+                    _ => panic!("compiler bug: non-blueprint in blueprint slot"),
+                };
+
+                let upvalue_count = blueprint.upvalue_count;
+                debug_assert!(
+                    upvalue_count > 0,
+                    "OpClosure should not be called for plain lambdas"
+                );
+
+                let closure = Closure::new(blueprint);
+                let upvalues = closure.upvalues_mut();
+                self.push(Value::Closure(closure.clone()));
+
+                // From this point on we internally mutate the
+                // closure object's upvalues. The closure is
+                // already in its stack slot, which means that it
+                // can capture itself as an upvalue for
+                // self-recursion.
+                self.populate_upvalues(upvalue_count, upvalues)?;
+            }
 
-                    let upvalue_count = blueprint.upvalue_count;
-                    let thunk = Thunk::new(blueprint, self.current_span());
-                    let upvalues = thunk.upvalues_mut();
+            OpCode::OpThunk(idx) => {
+                let blueprint = match &self.chunk()[idx] {
+                    Value::Blueprint(lambda) => lambda.clone(),
+                    _ => panic!("compiler bug: non-blueprint in blueprint slot"),
+                };
 
-                    self.push(Value::Thunk(thunk.clone()));
-                    self.populate_upvalues(upvalue_count, upvalues)?;
-                }
+                let upvalue_count = blueprint.upvalue_count;
+                let thunk = Thunk::new(blueprint, self.current_span());
+                let upvalues = thunk.upvalues_mut();
 
-                OpCode::OpForce => {
-                    let mut value = self.pop();
+                self.push(Value::Thunk(thunk.clone()));
+                self.populate_upvalues(upvalue_count, upvalues)?;
+            }
 
-                    if let Value::Thunk(thunk) = value {
-                        fallible!(self, thunk.force(self));
-                        value = thunk.value().clone();
-                    }
+            OpCode::OpForce => {
+                let mut value = self.pop();
 
-                    self.push(value);
+                if let Value::Thunk(thunk) = value {
+                    fallible!(self, thunk.force(self));
+                    value = thunk.value().clone();
                 }
 
-                OpCode::OpFinalise(StackIdx(idx)) => {
-                    match &self.stack[self.frame().stack_offset + idx] {
-                        Value::Closure(closure) => closure
-                            .resolve_deferred_upvalues(&self.stack[self.frame().stack_offset..]),
+                self.push(value);
+            }
 
-                        Value::Thunk(thunk) => thunk
-                            .resolve_deferred_upvalues(&self.stack[self.frame().stack_offset..]),
+            OpCode::OpFinalise(StackIdx(idx)) => {
+                match &self.stack[self.frame().stack_offset + idx] {
+                    Value::Closure(closure) => {
+                        closure.resolve_deferred_upvalues(&self.stack[self.frame().stack_offset..])
+                    }
 
-                        // In functions with "formals" attributes, it is
-                        // possible for `OpFinalise` to be called on a
-                        // non-capturing value, in which case it is a no-op.
-                        //
-                        // TODO: detect this in some phase and skip the finalise; fail here
-                        _ => { /* TODO: panic here again to catch bugs */ }
+                    Value::Thunk(thunk) => {
+                        thunk.resolve_deferred_upvalues(&self.stack[self.frame().stack_offset..])
                     }
-                }
 
-                // Data-carrying operands should never be executed,
-                // that is a critical error in the VM.
-                OpCode::DataLocalIdx(_)
-                | OpCode::DataDeferredLocal(_)
-                | OpCode::DataUpvalueIdx(_)
-                | OpCode::DataCaptureWith => {
-                    panic!("VM bug: attempted to execute data-carrying operand")
+                    // In functions with "formals" attributes, it is
+                    // possible for `OpFinalise` to be called on a
+                    // non-capturing value, in which case it is a no-op.
+                    //
+                    // TODO: detect this in some phase and skip the finalise; fail here
+                    _ => { /* TODO: panic here again to catch bugs */ }
                 }
             }
+
+            // Data-carrying operands should never be executed,
+            // that is a critical error in the VM.
+            OpCode::DataLocalIdx(_)
+            | OpCode::DataDeferredLocal(_)
+            | OpCode::DataUpvalueIdx(_)
+            | OpCode::DataCaptureWith => {
+                panic!("VM bug: attempted to execute data-carrying operand")
+            }
         }
+
+        Ok(())
     }
 
     fn run_attrset(&mut self, count: usize) -> EvalResult<()> {