fix(tvix/eval): never use partial_cmp() (partial fix b/338)

This is part of a fix for b/338.

We should never use PartialOrd::partial_cmp().

All Nix types except floats are obviously totally-ordered.  In
addition, it turns out that because Nix treats division by zero
rather than producing a NaN, and because it does not support
"negative zero", even floats are in fact totally ordered in Nix.

Therefore, every call to PartialOrd::partial_cmp() in tvix is an
error.  We have to *implement* this function, but we should never
call it on built-in types.

Moreover, nix_cmp_ordering() currently returns an Option<Ordering>.
I'm not sure what was going on there, since it's impossible for it
to return None.  This commit fixes it to return simply Ordering
rather than Option<Ordering>.

Change-Id: If5c084164cf19cfb38c5a15554c0422faa5f895d
Reviewed-on: https://cl.tvl.fyi/c/depot/+/10218
Autosubmit: Adam Joseph <adam@westernsemico.com>
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
This commit is contained in:
Adam Joseph 2023-12-08 23:25:21 -08:00 committed by clbot
parent 19d13eb070
commit 8a40f75c2d
3 changed files with 16 additions and 23 deletions

View file

@ -612,7 +612,7 @@ mod pure_builtins {
async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> { async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
Ok(Value::Bool(matches!( Ok(Value::Bool(matches!(
x.nix_cmp_ordering(y, co).await?, x.nix_cmp_ordering(y, co).await?,
Some(Ordering::Less) Ordering::Less
))) )))
} }

View file

@ -600,18 +600,9 @@ impl Value {
// TODO(amjoseph): de-asyncify this (when called directly by the VM) // TODO(amjoseph): de-asyncify this (when called directly by the VM)
/// Compare `self` against other using (fallible) Nix ordering semantics. /// Compare `self` against other using (fallible) Nix ordering semantics.
/// ///
/// Note that as this returns an `Option<Ordering>` it can not directly be
/// used as a generator function in the VM. The exact use depends on the
/// callsite, as the meaning is interpreted in different ways e.g. based on
/// the comparison operator used.
///
/// The function is intended to be used from within other generator /// The function is intended to be used from within other generator
/// functions or `gen!` blocks. /// functions or `gen!` blocks.
pub async fn nix_cmp_ordering( pub async fn nix_cmp_ordering(self, other: Self, co: GenCo) -> Result<Ordering, ErrorKind> {
self,
other: Self,
co: GenCo,
) -> Result<Option<Ordering>, ErrorKind> {
Self::nix_cmp_ordering_(self, other, co).await Self::nix_cmp_ordering_(self, other, co).await
} }
@ -619,19 +610,21 @@ impl Value {
mut myself: Self, mut myself: Self,
mut other: Self, mut other: Self,
co: GenCo, co: GenCo,
) -> Result<Option<Ordering>, ErrorKind> { ) -> Result<Ordering, ErrorKind> {
'outer: loop { 'outer: loop {
match (myself, other) { match (myself, other) {
// same types // same types
(Value::Integer(i1), Value::Integer(i2)) => return Ok(i1.partial_cmp(&i2)), // Nix does not support NaN or "negative infinity",
(Value::Float(f1), Value::Float(f2)) => return Ok(f1.partial_cmp(&f2)), // so its floats are in fact totally ordered.
(Value::String(s1), Value::String(s2)) => return Ok(s1.partial_cmp(&s2)), (Value::Integer(i1), Value::Integer(i2)) => return Ok(i1.cmp(&i2)),
(Value::Float(f1), Value::Float(f2)) => return Ok(f1.total_cmp(&f2)),
(Value::String(s1), Value::String(s2)) => return Ok(s1.cmp(&s2)),
(Value::List(l1), Value::List(l2)) => { (Value::List(l1), Value::List(l2)) => {
for i in 0.. { for i in 0.. {
if i == l2.len() { if i == l2.len() {
return Ok(Some(Ordering::Greater)); return Ok(Ordering::Greater);
} else if i == l1.len() { } else if i == l1.len() {
return Ok(Some(Ordering::Less)); return Ok(Ordering::Less);
} else if !generators::check_equality( } else if !generators::check_equality(
&co, &co,
l1[i].clone(), l1[i].clone(),
@ -651,8 +644,8 @@ impl Value {
} }
// different types // different types
(Value::Integer(i1), Value::Float(f2)) => return Ok((i1 as f64).partial_cmp(&f2)), (Value::Integer(i1), Value::Float(f2)) => return Ok((i1 as f64).total_cmp(&f2)),
(Value::Float(f1), Value::Integer(i2)) => return Ok(f1.partial_cmp(&(i2 as f64))), (Value::Float(f1), Value::Integer(i2)) => return Ok(f1.total_cmp(&(i2 as f64))),
// unsupported types // unsupported types
(lhs, rhs) => { (lhs, rhs) => {

View file

@ -53,18 +53,18 @@ macro_rules! cmp_op {
}}; }};
(@order < $ordering:expr) => { (@order < $ordering:expr) => {
$ordering == Some(Ordering::Less) $ordering == Ordering::Less
}; };
(@order > $ordering:expr) => { (@order > $ordering:expr) => {
$ordering == Some(Ordering::Greater) $ordering == Ordering::Greater
}; };
(@order <= $ordering:expr) => { (@order <= $ordering:expr) => {
!matches!($ordering, None | Some(Ordering::Greater)) matches!($ordering, Ordering::Equal | Ordering::Less)
}; };
(@order >= $ordering:expr) => { (@order >= $ordering:expr) => {
!matches!($ordering, None | Some(Ordering::Less)) matches!($ordering, Ordering::Equal | Ordering::Greater)
}; };
} }