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:
parent
5ac9450395
commit
b624edb2ae
2 changed files with 102 additions and 115 deletions
|
@ -36,24 +36,25 @@ 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> {
|
||||||
|
let a = generators::request_force(&co, a).await;
|
||||||
|
let b = generators::request_force(&co, b).await;
|
||||||
|
let span = generators::request_span(&co).await;
|
||||||
|
let ordering = a.nix_cmp_ordering(b, co, span).await?;
|
||||||
|
match ordering {
|
||||||
|
Err(cek) => Ok(Value::Catchable(cek)),
|
||||||
|
Ok(ordering) => Ok(Value::Bool(cmp_op!(@order $op ordering))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn compare(a: Value, b: Value, co: GenCo) -> Result<Value, ErrorKind> {
|
let gen_span = $frame.current_light_span();
|
||||||
let a = generators::request_force(&co, a).await;
|
$vm.push_call_frame($span, $frame);
|
||||||
let b = generators::request_force(&co, b).await;
|
$vm.enqueue_generator("compare", gen_span, |co| compare(a, b, co));
|
||||||
let span = generators::request_span(&co).await;
|
return Ok(false);
|
||||||
let ordering = a.nix_cmp_ordering(b, co, span).await?;
|
|
||||||
match ordering {
|
|
||||||
Err(cek) => Ok(Value::Catchable(cek)),
|
|
||||||
Ok(ordering) => Ok(Value::Bool(cmp_op!(@order $op ordering))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let gen_span = $frame.current_light_span();
|
|
||||||
$vm.push_call_frame($span, $frame);
|
|
||||||
$vm.enqueue_generator("compare", gen_span, |co| compare(a, b, co));
|
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,16 +624,16 @@ 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| {
|
a.nix_eq_owned_genco(b, co, PointerEquality::ForbidAll, gen_span)
|
||||||
a.nix_eq_owned_genco(b, co, PointerEquality::ForbidAll, gen_span)
|
});
|
||||||
});
|
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()) {
|
let rhs = rhs.to_list().with_span(&frame, self)?.into_inner();
|
||||||
(Value::Catchable(cek), _) | (_, Value::Catchable(cek)) => {
|
let lhs = lhs.to_list().with_span(&frame, self)?.into_inner();
|
||||||
self.stack.push(Value::Catchable(cek));
|
self.stack.push(Value::List(NixList::from(lhs + rhs)))
|
||||||
}
|
|
||||||
(rhs, lhs) => {
|
|
||||||
let rhs = rhs.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)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
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,60 +792,52 @@ 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)) => {
|
let gen_span = frame.current_light_span();
|
||||||
self.stack.push(Value::Catchable(cek));
|
self.push_call_frame(span, frame);
|
||||||
}
|
|
||||||
|
|
||||||
(b, a) => {
|
// OpAdd can add not just numbers, but also string-like
|
||||||
let gen_span = frame.current_light_span();
|
// things, which requires more VM logic. This operation is
|
||||||
self.push_call_frame(span, frame);
|
// evaluated in a generator frame.
|
||||||
|
self.enqueue_generator("add_values", gen_span, |co| add_values(co, a, b));
|
||||||
// OpAdd can add not just numbers, but also string-like
|
return Ok(false);
|
||||||
// things, which requires more VM logic. This operation is
|
|
||||||
// evaluated in a generator frame.
|
|
||||||
self.enqueue_generator("add_values", gen_span, |co| {
|
|
||||||
add_values(co, a, b)
|
|
||||||
});
|
|
||||||
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 {
|
||||||
|
Value::Integer(0) => return frame.error(self, ErrorKind::DivisionByZero),
|
||||||
|
Value::Float(b) if b == 0.0_f64 => {
|
||||||
|
return frame.error(self, ErrorKind::DivisionByZero)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
match b {
|
let result = arithmetic_op!(&a, &b, /).with_span(&frame, self)?;
|
||||||
Value::Integer(0) => return frame.error(self, ErrorKind::DivisionByZero),
|
self.stack.push(result);
|
||||||
Value::Float(b) if b == 0.0_f64 => {
|
}
|
||||||
return frame.error(self, ErrorKind::DivisionByZero)
|
},
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
let a = self.stack_pop();
|
|
||||||
let result = arithmetic_op!(&a, &b, /).with_span(&frame, self)?;
|
|
||||||
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,
|
||||||
|
|
Loading…
Reference in a new issue