feat(tvix/eval): nonrecursive nix_cmp_ordering(), fixes b/339
This commit rewrites Value::nix_cmp_ordering() into an equivalent nonrecursive form. Except for calls to Thunk::force(), the new form no longer uses generators, and is async only because of the fact that it calls Thunk::force(). I originally believed that this commit would make evaluation faster. In fact it is slightly slower. I believe this is due to the added vec![] allocation. I am investigating. Prev-Nixpkgs-Benchmark: {"attrpath":"pkgsCross.aarch64-multiplatform.hello.outPath","peak-kbytes":"460048","system-seconds":"0.68","user-seconds":"5.73"} This-Nixpkgs-Benchmark: {"attrpath":"pkgsCross.aarch64-multiplatform.hello.outPath","peak-kbytes":"460224","system-seconds":"0.67","user-seconds":"5.84"} Change-Id: Ic627bc220d9c5aa3c5e68b9b8bf199837cd55af5 Reviewed-on: https://cl.tvl.fyi/c/depot/+/10212 Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI Autosubmit: Adam Joseph <adam@westernsemico.com>
This commit is contained in:
parent
8a40f75c2d
commit
edbd5055a1
7 changed files with 75 additions and 44 deletions
|
@ -610,8 +610,9 @@ mod pure_builtins {
|
||||||
|
|
||||||
#[builtin("lessThan")]
|
#[builtin("lessThan")]
|
||||||
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> {
|
||||||
|
let span = generators::request_span(&co).await;
|
||||||
Ok(Value::Bool(matches!(
|
Ok(Value::Bool(matches!(
|
||||||
x.nix_cmp_ordering(y, co).await?,
|
x.nix_cmp_ordering(y, co, span).await?,
|
||||||
Ordering::Less
|
Ordering::Less
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -375,6 +375,16 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn nix_eq_owned_genco(
|
||||||
|
self,
|
||||||
|
other: Value,
|
||||||
|
co: GenCo,
|
||||||
|
ptr_eq: PointerEquality,
|
||||||
|
span: LightSpan,
|
||||||
|
) -> Result<Value, ErrorKind> {
|
||||||
|
self.nix_eq(other, &co, ptr_eq, span).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Compare two Nix values for equality, forcing nested parts of the structure
|
/// Compare two Nix values for equality, forcing nested parts of the structure
|
||||||
/// as needed.
|
/// as needed.
|
||||||
///
|
///
|
||||||
|
@ -388,7 +398,7 @@ impl Value {
|
||||||
pub(crate) async fn nix_eq(
|
pub(crate) async fn nix_eq(
|
||||||
self,
|
self,
|
||||||
other: Value,
|
other: Value,
|
||||||
co: GenCo,
|
co: &GenCo,
|
||||||
ptr_eq: PointerEquality,
|
ptr_eq: PointerEquality,
|
||||||
span: LightSpan,
|
span: LightSpan,
|
||||||
) -> Result<Value, ErrorKind> {
|
) -> Result<Value, ErrorKind> {
|
||||||
|
@ -416,13 +426,13 @@ impl Value {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Thunk::force_(thunk, &co, span.clone()).await?
|
Thunk::force_(thunk, co, span.clone()).await?
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => a,
|
_ => a,
|
||||||
};
|
};
|
||||||
|
|
||||||
let b = b.force(&co, span.clone()).await?;
|
let b = b.force(co, span.clone()).await?;
|
||||||
|
|
||||||
debug_assert!(!matches!(a, Value::Thunk(_)));
|
debug_assert!(!matches!(a, Value::Thunk(_)));
|
||||||
debug_assert!(!matches!(b, Value::Thunk(_)));
|
debug_assert!(!matches!(b, Value::Thunk(_)));
|
||||||
|
@ -471,8 +481,8 @@ impl Value {
|
||||||
#[allow(clippy::single_match)] // might need more match arms later
|
#[allow(clippy::single_match)] // might need more match arms later
|
||||||
match (a1.select("type"), a2.select("type")) {
|
match (a1.select("type"), a2.select("type")) {
|
||||||
(Some(v1), Some(v2)) => {
|
(Some(v1), Some(v2)) => {
|
||||||
let s1 = v1.clone().force(&co, span.clone()).await?.to_str();
|
let s1 = v1.clone().force(co, span.clone()).await?.to_str();
|
||||||
let s2 = v2.clone().force(&co, span.clone()).await?.to_str();
|
let s2 = v2.clone().force(co, span.clone()).await?.to_str();
|
||||||
|
|
||||||
if let (Ok(s1), Ok(s2)) = (s1, s2) {
|
if let (Ok(s1), Ok(s2)) = (s1, s2) {
|
||||||
if s1.as_str() == "derivation" && s2.as_str() == "derivation" {
|
if s1.as_str() == "derivation" && s2.as_str() == "derivation" {
|
||||||
|
@ -490,10 +500,10 @@ impl Value {
|
||||||
|
|
||||||
let result = out1
|
let result = out1
|
||||||
.clone()
|
.clone()
|
||||||
.force(&co, span.clone())
|
.force(co, span.clone())
|
||||||
.await?
|
.await?
|
||||||
.to_str()?
|
.to_str()?
|
||||||
== out2.clone().force(&co, span.clone()).await?.to_str()?;
|
== out2.clone().force(co, span.clone()).await?.to_str()?;
|
||||||
if !result {
|
if !result {
|
||||||
return Ok(Value::Bool(false));
|
return Ok(Value::Bool(false));
|
||||||
} else {
|
} else {
|
||||||
|
@ -597,55 +607,71 @@ impl Value {
|
||||||
gen_is!(is_bool, Value::Bool(_));
|
gen_is!(is_bool, Value::Bool(_));
|
||||||
gen_is!(is_attrs, Value::Attrs(_));
|
gen_is!(is_attrs, Value::Attrs(_));
|
||||||
|
|
||||||
// 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.
|
||||||
///
|
///
|
||||||
/// 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(self, other: Self, co: GenCo) -> Result<Ordering, ErrorKind> {
|
pub async fn nix_cmp_ordering(
|
||||||
Self::nix_cmp_ordering_(self, other, co).await
|
self,
|
||||||
|
other: Self,
|
||||||
|
co: GenCo,
|
||||||
|
span: LightSpan,
|
||||||
|
) -> Result<Ordering, ErrorKind> {
|
||||||
|
Self::nix_cmp_ordering_(self, other, co, span).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn nix_cmp_ordering_(
|
async fn nix_cmp_ordering_(
|
||||||
mut myself: Self,
|
myself: Self,
|
||||||
mut other: Self,
|
other: Self,
|
||||||
co: GenCo,
|
co: GenCo,
|
||||||
|
span: LightSpan,
|
||||||
) -> Result<Ordering, ErrorKind> {
|
) -> Result<Ordering, ErrorKind> {
|
||||||
'outer: loop {
|
// this is a stack of ((v1,v2),peq) triples to be compared;
|
||||||
match (myself, other) {
|
// after each triple is popped off of the stack, v1 is
|
||||||
// same types
|
// compared to v2 using peq-mode PointerEquality
|
||||||
// Nix does not support NaN or "negative infinity",
|
let mut vals = vec![((myself, other), PointerEquality::ForbidAll)];
|
||||||
// so its floats are in fact totally ordered.
|
|
||||||
(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)) => {
|
|
||||||
for i in 0.. {
|
|
||||||
if i == l2.len() {
|
|
||||||
return Ok(Ordering::Greater);
|
|
||||||
} else if i == l1.len() {
|
|
||||||
return Ok(Ordering::Less);
|
|
||||||
} else if !generators::check_equality(
|
|
||||||
&co,
|
|
||||||
l1[i].clone(),
|
|
||||||
l2[i].clone(),
|
|
||||||
PointerEquality::AllowAll,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
// TODO: do we need to control `top_level` here?
|
|
||||||
myself = generators::request_force(&co, l1[i].clone()).await;
|
|
||||||
other = generators::request_force(&co, l2[i].clone()).await;
|
|
||||||
continue 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!()
|
loop {
|
||||||
|
let ((mut a, mut b), ptr_eq) = if let Some(abp) = vals.pop() {
|
||||||
|
abp
|
||||||
|
} else {
|
||||||
|
// stack is empty, so they are equal
|
||||||
|
return Ok(Ordering::Equal);
|
||||||
|
};
|
||||||
|
if ptr_eq == PointerEquality::AllowAll {
|
||||||
|
if a.clone()
|
||||||
|
.nix_eq(b.clone(), &co, PointerEquality::AllowAll, span.clone())
|
||||||
|
.await?
|
||||||
|
.as_bool()?
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
a = a.force(&co, span.clone()).await?;
|
||||||
|
b = b.force(&co, span.clone()).await?;
|
||||||
|
}
|
||||||
|
let result = match (a, b) {
|
||||||
|
// same types
|
||||||
|
(Value::Integer(i1), Value::Integer(i2)) => i1.cmp(&i2),
|
||||||
|
(Value::Float(f1), Value::Float(f2)) => f1.total_cmp(&f2),
|
||||||
|
(Value::String(s1), Value::String(s2)) => s1.cmp(&s2),
|
||||||
|
(Value::List(l1), Value::List(l2)) => {
|
||||||
|
let max = l1.len().max(l2.len());
|
||||||
|
for j in 0..max {
|
||||||
|
let i = max - 1 - j;
|
||||||
|
if i >= l2.len() {
|
||||||
|
vals.push(((1.into(), 0.into()), PointerEquality::ForbidAll));
|
||||||
|
} else if i >= l1.len() {
|
||||||
|
vals.push(((0.into(), 1.into()), PointerEquality::ForbidAll));
|
||||||
|
} else {
|
||||||
|
vals.push(((l1[i].clone(), l2[i].clone()), PointerEquality::AllowAll));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// different types
|
// different types
|
||||||
(Value::Integer(i1), Value::Float(f2)) => return Ok((i1 as f64).total_cmp(&f2)),
|
(Value::Integer(i1), Value::Float(f2)) => (i1 as f64).total_cmp(&f2),
|
||||||
(Value::Float(f1), Value::Integer(i2)) => return Ok(f1.total_cmp(&(i2 as f64))),
|
(Value::Float(f1), Value::Integer(i2)) => f1.total_cmp(&(i2 as f64)),
|
||||||
|
|
||||||
// unsupported types
|
// unsupported types
|
||||||
(lhs, rhs) => {
|
(lhs, rhs) => {
|
||||||
|
@ -654,6 +680,9 @@ impl Value {
|
||||||
rhs: rhs.type_of(),
|
rhs: rhs.type_of(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
if result != Ordering::Equal {
|
||||||
|
return Ok(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -337,7 +337,7 @@ impl<'o> VM<'o> {
|
||||||
let values = *values;
|
let values = *values;
|
||||||
self.reenqueue_generator(name, span.clone(), generator);
|
self.reenqueue_generator(name, span.clone(), generator);
|
||||||
self.enqueue_generator("nix_eq", span.clone(), |co| {
|
self.enqueue_generator("nix_eq", span.clone(), |co| {
|
||||||
values.0.nix_eq(values.1, co, ptr_eq, span)
|
values.0.nix_eq_owned_genco(values.1, co, ptr_eq, span)
|
||||||
});
|
});
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,8 @@ macro_rules! cmp_op {
|
||||||
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;
|
||||||
let ordering = a.nix_cmp_ordering(b, co).await?;
|
let span = generators::request_span(&co).await;
|
||||||
|
let ordering = a.nix_cmp_ordering(b, co, span).await?;
|
||||||
Ok(Value::Bool(cmp_op!(@order $op ordering)))
|
Ok(Value::Bool(cmp_op!(@order $op ordering)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -616,7 +616,7 @@ impl<'o> VM<'o> {
|
||||||
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(b, co, PointerEquality::ForbidAll, gen_span)
|
a.nix_eq_owned_genco(b, co, PointerEquality::ForbidAll, gen_span)
|
||||||
});
|
});
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue