fix(tvix/eval): lift VM ops over Catchable

We want to handle bottoms in a consistent fashion. Previously this was
handled by repetitive is_catchable checks, which were not consistently
present.

Change-Id: I9614c479cc6297d1f64efba22b620a26e2a96802
Reviewed-on: https://cl.tvl.fyi/c/depot/+/10485
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
This commit is contained in:
edef 2023-12-30 00:52:27 +00:00
parent 5ac9450395
commit b624edb2ae
2 changed files with 102 additions and 115 deletions

View file

@ -36,9 +36,8 @@ macro_rules! arithmetic_op {
#[macro_export] #[macro_export]
macro_rules! cmp_op { macro_rules! cmp_op {
( $vm:ident, $frame:ident, $span:ident, $op:tt ) => {{ ( $vm:ident, $frame:ident, $span:ident, $op:tt ) => {{
let b = $vm.stack_pop(); lifted_pop! {
let a = $vm.stack_pop(); $vm(b, a) => {
async fn compare(a: Value, b: Value, co: GenCo) -> Result<Value, ErrorKind> { async fn compare(a: Value, b: Value, co: GenCo) -> Result<Value, ErrorKind> {
let a = generators::request_force(&co, a).await; let a = generators::request_force(&co, a).await;
let b = generators::request_force(&co, b).await; let b = generators::request_force(&co, b).await;
@ -54,6 +53,8 @@ macro_rules! cmp_op {
$vm.push_call_frame($span, $frame); $vm.push_call_frame($span, $frame);
$vm.enqueue_generator("compare", gen_span, |co| compare(a, b, co)); $vm.enqueue_generator("compare", gen_span, |co| compare(a, b, co));
return Ok(false); return Ok(false);
}
}
}}; }};
(@order < $ordering:expr) => { (@order < $ordering:expr) => {
@ -72,3 +73,21 @@ macro_rules! cmp_op {
matches!($ordering, Ordering::Equal | Ordering::Greater) matches!($ordering, Ordering::Equal | Ordering::Greater)
}; };
} }
#[macro_export]
macro_rules! lifted_pop {
($vm:ident ($($bind:ident),+) => $body:expr) => {
{
$(
let $bind = $vm.stack_pop();
)+
$(
if $bind.is_catchable() {
$vm.stack.push($bind);
continue;
}
)+
$body
}
}
}

View file

@ -23,6 +23,7 @@ use crate::{
compiler::GlobalsMap, compiler::GlobalsMap,
errors::{CatchableErrorKind, Error, ErrorKind, EvalResult}, errors::{CatchableErrorKind, Error, ErrorKind, EvalResult},
io::EvalIO, io::EvalIO,
lifted_pop,
nix_search_path::NixSearchPath, nix_search_path::NixSearchPath,
observer::RuntimeObserver, observer::RuntimeObserver,
opcode::{CodeIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx}, opcode::{CodeIdx, Count, JumpOffset, OpCode, StackIdx, UpvalueIdx},
@ -541,14 +542,8 @@ impl<'o> VM<'o> {
)))); ))));
} }
OpCode::OpAttrsSelect => { OpCode::OpAttrsSelect => lifted_pop! {
let key = self.stack_pop(); self(key, attrs) => {
let attrs = self.stack_pop();
if key.is_catchable() {
self.stack.push(key);
} else if attrs.is_catchable() {
self.stack.push(attrs);
} else {
let key = key.to_str().with_span(&frame, self)?; let key = key.to_str().with_span(&frame, self)?;
let attrs = attrs.to_attrs().with_span(&frame, self)?; let attrs = attrs.to_attrs().with_span(&frame, self)?;
@ -565,7 +560,7 @@ impl<'o> VM<'o> {
} }
} }
} }
} },
OpCode::OpJumpIfFalse(JumpOffset(offset)) => { OpCode::OpJumpIfFalse(JumpOffset(offset)) => {
debug_assert!(offset != 0); debug_assert!(offset != 0);
@ -629,9 +624,8 @@ impl<'o> VM<'o> {
frame.ip += offset; frame.ip += offset;
} }
OpCode::OpEqual => { OpCode::OpEqual => lifted_pop! {
let b = self.stack_pop(); self(b, a) => {
let a = self.stack_pop();
let gen_span = frame.current_light_span(); let gen_span = frame.current_light_span();
self.push_call_frame(span, frame); self.push_call_frame(span, frame);
self.enqueue_generator("nix_eq", gen_span.clone(), |co| { self.enqueue_generator("nix_eq", gen_span.clone(), |co| {
@ -639,6 +633,7 @@ impl<'o> VM<'o> {
}); });
return Ok(false); return Ok(false);
} }
},
// These assertion operations error out if the stack // These assertion operations error out if the stack
// top is not of the expected type. This is necessary // top is not of the expected type. This is necessary
@ -646,6 +641,7 @@ impl<'o> VM<'o> {
// exactly. // exactly.
OpCode::OpAssertBool => { OpCode::OpAssertBool => {
let val = self.stack_peek(0); let val = self.stack_peek(0);
// TODO(edef): propagate this into is_bool, since bottom values *are* values of any type
if !val.is_catchable() && !val.is_bool() { if !val.is_catchable() && !val.is_bool() {
return frame.error( return frame.error(
self, self,
@ -659,7 +655,8 @@ impl<'o> VM<'o> {
OpCode::OpAssertAttrs => { OpCode::OpAssertAttrs => {
let val = self.stack_peek(0); let val = self.stack_peek(0);
if !val.is_attrs() { // TODO(edef): propagate this into is_attrs, since bottom values *are* values of any type
if !val.is_catchable() && !val.is_attrs() {
return frame.error( return frame.error(
self, self,
ErrorKind::TypeError { ErrorKind::TypeError {
@ -672,29 +669,20 @@ impl<'o> VM<'o> {
OpCode::OpAttrs(Count(count)) => self.run_attrset(&frame, count)?, OpCode::OpAttrs(Count(count)) => self.run_attrset(&frame, count)?,
OpCode::OpAttrsUpdate => { OpCode::OpAttrsUpdate => lifted_pop! {
let rhs = self.stack_pop(); self(rhs, lhs) => {
let lhs = self.stack_pop();
if lhs.is_catchable() {
self.stack.push(lhs);
} else if rhs.is_catchable() {
self.stack.push(rhs);
} else {
let rhs = rhs.to_attrs().with_span(&frame, self)?; let rhs = rhs.to_attrs().with_span(&frame, self)?;
let lhs = lhs.to_attrs().with_span(&frame, self)?; let lhs = lhs.to_attrs().with_span(&frame, self)?;
self.stack.push(Value::attrs(lhs.update(*rhs))) self.stack.push(Value::attrs(lhs.update(*rhs)))
} }
} },
OpCode::OpInvert => { OpCode::OpInvert => lifted_pop! {
let v = self.stack_pop(); self(v) => {
if v.is_catchable() {
self.stack.push(v);
} else {
let v = v.as_bool().with_span(&frame, self)?; let v = v.as_bool().with_span(&frame, self)?;
self.stack.push(Value::Bool(!v)); self.stack.push(Value::Bool(!v));
} }
} },
OpCode::OpList(Count(count)) => { OpCode::OpList(Count(count)) => {
let list = let list =
@ -710,14 +698,8 @@ impl<'o> VM<'o> {
} }
} }
OpCode::OpHasAttr => { OpCode::OpHasAttr => lifted_pop! {
let key = self.stack_pop(); self(key, attrs) => {
let attrs = self.stack_pop();
if key.is_catchable() {
self.stack.push(key);
} else if attrs.is_catchable() {
self.stack.push(attrs);
} else {
let key = key.to_str().with_span(&frame, self)?; let key = key.to_str().with_span(&frame, self)?;
let result = match attrs { let result = match attrs {
Value::Attrs(attrs) => attrs.contains(key.as_str()), Value::Attrs(attrs) => attrs.contains(key.as_str()),
@ -729,21 +711,15 @@ impl<'o> VM<'o> {
self.stack.push(Value::Bool(result)); self.stack.push(Value::Bool(result));
} }
} },
OpCode::OpConcat => { OpCode::OpConcat => lifted_pop! {
// right hand side, left hand side self(rhs, lhs) => {
match (self.stack_pop(), self.stack_pop()) {
(Value::Catchable(cek), _) | (_, Value::Catchable(cek)) => {
self.stack.push(Value::Catchable(cek));
}
(rhs, lhs) => {
let rhs = rhs.to_list().with_span(&frame, self)?.into_inner(); let rhs = rhs.to_list().with_span(&frame, self)?.into_inner();
let lhs = lhs.to_list().with_span(&frame, self)?.into_inner(); let lhs = lhs.to_list().with_span(&frame, self)?.into_inner();
self.stack.push(Value::List(NixList::from(lhs + rhs))) self.stack.push(Value::List(NixList::from(lhs + rhs)))
} }
} },
}
OpCode::OpResolveWith => { OpCode::OpResolveWith => {
let ident = self.stack_pop().to_str().with_span(&frame, self)?; let ident = self.stack_pop().to_str().with_span(&frame, self)?;
@ -816,44 +792,35 @@ impl<'o> VM<'o> {
} }
} }
OpCode::OpAdd => { OpCode::OpAdd => lifted_pop! {
match (self.stack_pop(), self.stack_pop()) { self(b, a) => {
(Value::Catchable(cek), _) | (_, Value::Catchable(cek)) => {
self.stack.push(Value::Catchable(cek));
}
(b, a) => {
let gen_span = frame.current_light_span(); let gen_span = frame.current_light_span();
self.push_call_frame(span, frame); self.push_call_frame(span, frame);
// OpAdd can add not just numbers, but also string-like // OpAdd can add not just numbers, but also string-like
// things, which requires more VM logic. This operation is // things, which requires more VM logic. This operation is
// evaluated in a generator frame. // evaluated in a generator frame.
self.enqueue_generator("add_values", gen_span, |co| { self.enqueue_generator("add_values", gen_span, |co| add_values(co, a, b));
add_values(co, a, b)
});
return Ok(false); return Ok(false);
} }
} },
}
OpCode::OpSub => { OpCode::OpSub => lifted_pop! {
let b = self.stack_pop(); self(b, a) => {
let a = self.stack_pop();
let result = arithmetic_op!(&a, &b, -).with_span(&frame, self)?; let result = arithmetic_op!(&a, &b, -).with_span(&frame, self)?;
self.stack.push(result); self.stack.push(result);
} }
},
OpCode::OpMul => { OpCode::OpMul => lifted_pop! {
let b = self.stack_pop(); self(b, a) => {
let a = self.stack_pop();
let result = arithmetic_op!(&a, &b, *).with_span(&frame, self)?; let result = arithmetic_op!(&a, &b, *).with_span(&frame, self)?;
self.stack.push(result); self.stack.push(result);
} }
},
OpCode::OpDiv => { OpCode::OpDiv => lifted_pop! {
let b = self.stack_pop(); self(b, a) => {
match b { match b {
Value::Integer(0) => return frame.error(self, ErrorKind::DivisionByZero), Value::Integer(0) => return frame.error(self, ErrorKind::DivisionByZero),
Value::Float(b) if b == 0.0_f64 => { Value::Float(b) if b == 0.0_f64 => {
@ -862,14 +829,15 @@ impl<'o> VM<'o> {
_ => {} _ => {}
}; };
let a = self.stack_pop();
let result = arithmetic_op!(&a, &b, /).with_span(&frame, self)?; let result = arithmetic_op!(&a, &b, /).with_span(&frame, self)?;
self.stack.push(result); self.stack.push(result);
} }
},
OpCode::OpNegate => match self.stack_pop() { OpCode::OpNegate => match self.stack_pop() {
Value::Integer(i) => self.stack.push(Value::Integer(-i)), Value::Integer(i) => self.stack.push(Value::Integer(-i)),
Value::Float(f) => self.stack.push(Value::Float(-f)), Value::Float(f) => self.stack.push(Value::Float(-f)),
Value::Catchable(cex) => self.stack.push(Value::Catchable(cex)),
v => { v => {
return frame.error( return frame.error(
self, self,