use relon_codegen_cranelift::AotEvaluator;
use relon_codegen_llvm::LlvmAotEvaluator;
use relon_ir::ir::{Op, TaggedOp};
const INT_LIST_SRC: &str = "#schema R { List<Int> xs: * }\n\
#main(Int n) -> R\n\
{ xs: [10, 20, 30] }";
fn flatten_ops(src: &str) -> Vec<Op> {
let options = relon_analyzer::AnalyzeOptions {
strict_mode: false,
..Default::default()
};
let lowered = relon_ir::compile(src, &options).expect("frontend compile");
let mut out = Vec::new();
for func in &lowered.module.funcs {
collect(&func.body, &mut out);
}
out
}
fn collect(body: &[TaggedOp], out: &mut Vec<Op>) {
for t in body {
out.push(t.op.clone());
match &t.op {
Op::Block { body, .. } | Op::Loop { body, .. } => collect(body, out),
Op::If {
then_body,
else_body,
..
} => {
collect(then_body, out);
collect(else_body, out);
}
_ => {}
}
}
}
fn assert_codegen_parity(src: &str) -> String {
AotEvaluator::from_source(src)
.unwrap_or_else(|e| panic!("cranelift golden must compile src:\n{src}\nerr: {e:?}"));
let llvm = LlvmAotEvaluator::from_source(src)
.unwrap_or_else(|e| panic!("llvm backend must compile src:\n{src}\nerr: {e:?}"));
llvm.emit_ir_dump().to_string()
}
#[test]
fn int_list_lowers_to_const_list_int() {
let ops = flatten_ops(INT_LIST_SRC);
assert!(
ops.iter().any(|o| matches!(o, Op::ConstListInt { .. })),
"INT_LIST_SRC must lower to Op::ConstListInt; ops:\n{ops:#?}"
);
}
#[test]
fn int_list_codegen_parity_and_shape() {
let dump = assert_codegen_parity(INT_LIST_SRC);
assert!(
dump.contains("tail_rec_size8"),
"int-list IR must compute the ListInt tail-record size (shl 3 + 8). Dump:\n{dump}"
);
assert!(
dump.contains("llvm.memcpy"),
"int-list IR must memcpy the list record into the tail. Dump:\n{dump}"
);
}
#[test]
fn int_list_run_main_three_way_after_return_decode() {
use std::collections::HashMap;
use std::sync::Arc;
use relon_eval_api::{Evaluator, Value};
use relon_evaluator::{Context, Scope, TreeWalkEvaluator};
use relon_parser::parse_document;
fn xs_of(v: &Value) -> Vec<i64> {
let dict = match v {
Value::Dict(d) => d,
other => panic!("expected schema Dict return, got {other:?}"),
};
let list = dict.map.get("xs").expect("return dict has `xs` field");
match list {
Value::List(items) => items
.iter()
.map(|e| match e {
Value::Int(n) => *n,
other => panic!("expected Int element, got {other:?}"),
})
.collect(),
other => panic!("expected List for `xs`, got {other:?}"),
}
}
let node = parse_document(INT_LIST_SRC).expect("parse INT_LIST_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 tw_args = HashMap::new();
tw_args.insert("n".to_string(), Value::Int(0));
let want = xs_of(
&walker
.run_main(&scope, tw_args)
.expect("tree-walk run_main"),
);
assert_eq!(want, vec![10, 20, 30], "oracle sanity");
let llvm = LlvmAotEvaluator::from_source(INT_LIST_SRC).expect("llvm compiles the lowering");
let cl = AotEvaluator::from_source(INT_LIST_SRC).expect("cranelift golden compiles");
let mut a = HashMap::new();
a.insert("n".to_string(), Value::Int(0));
let got_llvm = xs_of(
&llvm
.run_main(a.clone())
.expect("llvm run_main decodes List<Int>"),
);
let got_cl = xs_of(&cl.run_main(a).expect("cranelift run_main"));
assert_eq!(
got_llvm, want,
"LLVM List<Int> return decode diverged from oracle"
);
assert_eq!(
got_cl, want,
"cranelift List<Int> return decode diverged from oracle"
);
}