#![cfg(feature = "encoding")]
extern crate lambda_calculus as lambda;
use lambda::*;
use std::thread;
const DEPTH: usize = 1000;
const LIBTEST_STACK: usize = 2 * 1024 * 1024;
const MAX_BYTES_PER_LEVEL: f64 = 1600.0;
fn nest(n: usize) -> Term {
let mut t = Var(1);
for _ in 0..n {
t = abs(app(t, Var(1)));
}
t
}
fn per_level(name: &str, f: impl FnOnce()) -> f64 {
let (_, peak) = stackler::measure_peak(f);
let peak = peak.expect("the prober's stack could not be painted");
assert!(
!peak.is_saturated(),
"{name}: {} bytes is only a lower bound; raise PAINT_DEPTH",
peak.bytes()
);
let cost = peak.bytes() as f64 / DEPTH as f64;
println!(
"{name:18} {cost:>7.0} B/level -> at most {:>7.0} levels on libtest's {} MiB",
LIBTEST_STACK as f64 / cost,
LIBTEST_STACK / (1024 * 1024)
);
assert!(
cost <= MAX_BYTES_PER_LEVEL,
"{name} costs {cost:.0} B per nesting level, over the {MAX_BYTES_PER_LEVEL:.0} B \
ceiling: terms deeper than {:.0} levels now overflow libtest's {} MiB stack",
LIBTEST_STACK as f64 / cost,
LIBTEST_STACK / (1024 * 1024)
);
cost
}
#[test]
fn recursive_term_ops_stay_shallow() {
const MIB: usize = 1024 * 1024;
const STACK_SIZE: usize = 64 * MIB;
const PAINT_DEPTH: usize = 32 * MIB;
let prober = thread::Builder::new()
.name("prober".into())
.stack_size(STACK_SIZE)
.spawn(|| {
assert!(
stackler::Stackler::new().paint_depth(PAINT_DEPTH).install(),
"the prober's stack bounds could not be determined"
);
println!("over {DEPTH} nested levels:\n");
let subject = nest(DEPTH);
per_level("clone", || {
std::hint::black_box(subject.clone());
});
per_level("PartialEq", || {
std::hint::black_box(subject == subject.clone());
});
per_level("Display", || {
std::hint::black_box(subject.to_string().len());
});
let debug = per_level("Debug", || {
std::hint::black_box(format!("{subject:?}").len());
});
per_level("beta NOR", || {
std::hint::black_box(beta(subject.clone(), NOR, 1));
});
per_level("eta", || {
std::hint::black_box(eta(subject.clone(), 1));
});
let owned = nest(DEPTH);
per_level("drop", move || drop(owned));
println!(
"\nassert_eq! on terms deeper than ~{:.0} levels aborts instead of failing",
LIBTEST_STACK as f64 / debug
);
})
.unwrap();
prober.join().unwrap();
}
#[test]
fn representative_workloads_are_far_from_the_limit() {
use lambda::data::num::church;
const PAINT_DEPTH: usize = 8 * 1024 * 1024;
assert!(
stackler::Stackler::new().paint_depth(PAINT_DEPTH).install(),
"the test thread's stack bounds could not be determined"
);
let (_, peak) = stackler::measure_peak(|| {
std::hint::black_box(beta(app(church::fac(), 5.into_church()), NOR, 0));
});
let peak = peak.expect("the test thread's stack could not be painted");
println!(
"church::fac 5 NOR: {} B ({:.1}% of libtest's {} MiB)",
peak.bytes(),
100.0 * peak.bytes() as f64 / LIBTEST_STACK as f64,
LIBTEST_STACK / (1024 * 1024)
);
assert!(
peak.bytes() < LIBTEST_STACK / 10,
"fac 5 now uses {} bytes, over a tenth of libtest's {} MiB stack",
peak.bytes(),
LIBTEST_STACK / (1024 * 1024)
);
}