use std::collections::HashMap;
use std::sync::Arc;
use relon_codegen_llvm::LlvmAotEvaluator;
use relon_eval_api::{Evaluator, Value};
use relon_evaluator::{Context, Scope, TreeWalkEvaluator};
use relon_parser::parse_document;
const MIXED_MOD_SRC: &str = "#unstrict\n\
#main(Int n) -> Float\n\
range(n).reduce(0.0, (acc, i) => acc + i % 2.5)";
const FLOAT_MOD_SRC: &str = "#unstrict\n\
#main(Int n) -> Float\n\
range(n).reduce(0.0, (acc, i) => acc + (i / 2.0) % 3.0)";
const NEG_MOD_SRC: &str = "#unstrict\n\
#main(Int n) -> Float\n\
range(n).reduce(0.0, (acc, i) => acc + (0.0 - i) % 2.5)";
const RUNTIME_DIVISOR_SRC: &str = "#unstrict\n\
#main(Int n) -> Float\n\
range(n).reduce(0.0, (acc, i) => acc + 10.0 % (i + 1.0))";
fn as_f64(v: &Value) -> f64 {
match v {
Value::Float(f) => f.into_inner(),
Value::Int(n) => *n as f64,
other => panic!("expected Float result, got {other:?}"),
}
}
fn oracle(src: &str, n: i64) -> f64 {
let node = parse_document(src).expect("parse oracle src");
let analyzed = Arc::new(relon_analyzer::analyze(&node));
let mut ctx = Context::new()
.with_root(node)
.with_analyzed(Arc::clone(&analyzed));
TreeWalkEvaluator::prepare_in_place(&mut ctx);
let walker = TreeWalkEvaluator::new(Arc::new(ctx));
let scope = Arc::new(Scope::default());
let mut args = HashMap::new();
args.insert("n".to_string(), Value::Int(n));
as_f64(&walker.run_main(&scope, args).expect("tree-walker run_main"))
}
fn aot(src: &str, n: i64) -> f64 {
let ev = LlvmAotEvaluator::from_source(src)
.unwrap_or_else(|e| panic!("LLVM AOT from_source failed: {e:?}"));
let mut args = HashMap::new();
args.insert("n".to_string(), Value::Int(n));
as_f64(&ev.run_main(args).expect("LLVM run_main"))
}
fn assert_bit_identical(label: &str, src: &str) {
for &n in &[0i64, 1, 2, 3, 5, 7, 10, 100, 1000] {
let got = aot(src, n);
let want = oracle(src, n);
assert_eq!(
got.to_bits(),
want.to_bits(),
"{label} AOT diverged from tree-walker at n={n}: \
aot_bits={:#018x} ({got}) oracle_bits={:#018x} ({want})",
got.to_bits(),
want.to_bits(),
);
}
}
#[test]
fn mixed_int_float_mod_aot_bit_identical_to_oracle() {
assert_bit_identical("Int%Float", MIXED_MOD_SRC);
}
#[test]
fn float_float_mod_aot_bit_identical_to_oracle() {
assert_bit_identical("Float%Float", FLOAT_MOD_SRC);
}
#[test]
fn negative_dividend_mod_aot_bit_identical_to_oracle() {
assert_bit_identical("(-)%Float", NEG_MOD_SRC);
}
#[test]
fn runtime_divisor_mod_aot_bit_identical_to_oracle() {
assert_bit_identical("F64%runtime", RUNTIME_DIVISOR_SRC);
}
#[test]
fn f64_mod_emits_div_by_zero_trap_guard() {
let ev = LlvmAotEvaluator::from_source(RUNTIME_DIVISOR_SRC)
.unwrap_or_else(|e| panic!("LLVM AOT from_source failed: {e:?}"));
let dump = ev.emit_ir_dump();
assert!(
dump.contains("llvm.trap"),
"IR dump missing llvm.trap guard for F64 Mod:\n{dump}"
);
assert!(
dump.contains("frem"),
"IR dump missing frem for F64 Mod:\n{dump}"
);
}