use super::CodeGen;
use super::term_emit::{IMM_INT_MAX, IMM_INT_MIN, atom_word, int_word};
use plg_frontend::CgClause;
use plg_shared::cell::{
TAG_ATOM, TAG_BIG, TAG_FLT, TAG_INT, TAG_LST, TAG_STR, make, pack_functor, tag_of,
};
use plg_shared::{AtomId, Term};
use std::fmt::Write;
pub fn is_fact_predicate(clauses: &[CgClause]) -> bool {
!clauses.is_empty()
&& clauses
.iter()
.all(|c| c.body.is_empty() && head_args_ground(&c.head))
}
fn head_args_ground(head: &Term) -> bool {
match head {
Term::Atom(_) => true,
Term::Compound { args, .. } => args.iter().all(is_ground),
_ => false,
}
}
fn is_ground(t: &Term) -> bool {
match t {
Term::Atom(_) | Term::Integer(_) | Term::Float(_) => true,
Term::Var(_) => false,
Term::Compound { args, .. } => args.iter().all(is_ground),
Term::List { head, tail } => is_ground(head) && is_ground(tail),
}
}
fn serialize_arg(root: &Term, blob: &mut Vec<u64>) -> u64 {
let mut work: Vec<(&Term, usize)> = Vec::new();
let r = xlate(root, blob, &mut work);
while let Some((t, dst)) = work.pop() {
let w = xlate(t, blob, &mut work);
blob[dst] = w;
}
r
}
fn xlate<'a>(t: &'a Term, blob: &mut Vec<u64>, work: &mut Vec<(&'a Term, usize)>) -> u64 {
match t {
Term::Atom(id) => atom_word(*id),
Term::Integer(n) if (IMM_INT_MIN..=IMM_INT_MAX).contains(n) => {
int_word(*n).expect("immediate range checked")
}
Term::Integer(n) => {
let b = blob.len();
blob.push(*n as u64); make(TAG_BIG, b as u64)
}
Term::Float(f) => {
let b = blob.len();
blob.push(f.to_bits()); make(TAG_FLT, b as u64)
}
Term::Compound { functor, args } => {
let b = blob.len();
blob.push(pack_functor(*functor, args.len() as u32)); blob.resize(b + 1 + args.len(), 0); for (k, a) in args.iter().enumerate() {
work.push((a, b + 1 + k));
}
make(TAG_STR, b as u64)
}
Term::List { head, tail } => {
let b = blob.len();
blob.push(0); blob.push(0); work.push((head, b));
work.push((tail, b + 1));
make(TAG_LST, b as u64)
}
Term::Var(_) => unreachable!("is_fact_predicate guarantees ground args"),
}
}
fn emit_words_global(out: &mut String, name: &str, words: &[u64]) {
if words.is_empty() {
writeln!(
out,
"@{name} = private unnamed_addr constant [0 x i64] zeroinitializer"
)
.unwrap();
} else {
let body = words
.iter()
.map(|w| format!("i64 {w}"))
.collect::<Vec<_>>()
.join(", ");
writeln!(
out,
"@{name} = private unnamed_addr constant [{} x i64] [{body}]",
words.len()
)
.unwrap();
}
}
impl CodeGen<'_> {
pub fn emit_fact_predicate(
&mut self,
functor: AtomId,
arity: u32,
clauses: &[CgClause],
) -> Result<(), String> {
let sym = self.pred_symbol(functor, arity);
let tbl = format!("plg_facts_{functor}_{arity}");
let nrows = clauses.len();
let acount = arity as usize;
let mut table: Vec<u64> = Vec::with_capacity(nrows * acount);
let mut blob: Vec<u64> = Vec::new();
for c in clauses {
let args: &[Term] = match &c.head {
Term::Compound { args, .. } => args,
_ => &[], };
for a in args {
let cell = serialize_arg(a, &mut blob);
table.push(cell);
}
}
writeln!(
self.out,
"; {}/{arity} ({nrows} facts \u{2192} table)",
self.interner.resolve(functor)
)
.unwrap();
emit_words_global(&mut self.out, &tbl, &table);
let has_blob = !blob.is_empty();
if has_blob {
emit_words_global(&mut self.out, &format!("{tbl}_blob"), &blob);
}
let blob_len = blob.len();
let has_index = acount >= 1
&& (0..nrows).all(|r| matches!(tag_of(table[r * acount]), TAG_ATOM | TAG_INT));
if has_index {
let mut order: Vec<usize> = (0..nrows).collect();
order.sort_by_key(|&r| (table[r * acount], r));
let idxwords: Vec<u64> = order.iter().map(|&r| r as u64).collect();
emit_words_global(&mut self.out, &format!("{tbl}_idx"), &idxwords);
}
self.reset_temps();
writeln!(self.out, "define i32 @{sym}(ptr %m, i64 %env) {{").unwrap();
writeln!(self.out, "entry:").unwrap();
let s = self.fresh();
writeln!(self.out, " {s} = call i32 @plg_rt_step(ptr %m)").unwrap();
let c = self.fresh();
writeln!(self.out, " {c} = icmp ne i32 {s}, 0").unwrap();
writeln!(self.out, " br i1 {c}, label %go, label %fail").unwrap();
writeln!(self.out, "go:").unwrap();
let tp = self.fresh();
writeln!(self.out, " {tp} = ptrtoint ptr @{tbl} to i64").unwrap();
let ip = if has_index {
let ip = self.fresh();
writeln!(self.out, " {ip} = ptrtoint ptr @{tbl}_idx to i64").unwrap();
ip
} else {
"0".to_string()
};
let bp = if has_blob {
let bp = self.fresh();
writeln!(self.out, " {bp} = ptrtoint ptr @{tbl}_blob to i64").unwrap();
bp
} else {
"0".to_string()
};
let rp = self.fresh();
writeln!(self.out, " {rp} = ptrtoint ptr @{sym}_ftr to i64").unwrap();
let ok = self.fresh();
writeln!(
self.out,
" {ok} = call i32 @plg_rt_fact_first(ptr %m, i64 {tp}, i64 {ip}, i64 {bp}, i64 {blob_len}, i64 {nrows}, i64 {arity}, i64 {rp})"
)
.unwrap();
let d = self.fresh();
writeln!(self.out, " {d} = icmp ne i32 {ok}, 0").unwrap();
writeln!(self.out, " br i1 {d}, label %deliver, label %fail").unwrap();
writeln!(self.out, "deliver:").unwrap();
self.emit_fact_deliver();
writeln!(self.out, "fail:").unwrap();
writeln!(self.out, " ret i32 0").unwrap();
writeln!(self.out, "}}").unwrap();
self.reset_temps();
writeln!(
self.out,
"define internal i32 @{sym}_ftr(ptr %m, i64 %f) {{"
)
.unwrap();
writeln!(self.out, "entry:").unwrap();
let ok = self.fresh();
writeln!(
self.out,
" {ok} = call i32 @plg_rt_fact_next(ptr %m, i64 %f)"
)
.unwrap();
let d = self.fresh();
writeln!(self.out, " {d} = icmp ne i32 {ok}, 0").unwrap();
writeln!(self.out, " br i1 {d}, label %deliver, label %fail").unwrap();
writeln!(self.out, "deliver:").unwrap();
self.emit_fact_deliver();
writeln!(self.out, "fail:").unwrap();
writeln!(self.out, " ret i32 0").unwrap();
writeln!(self.out, "}}").unwrap();
Ok(())
}
fn emit_fact_deliver(&mut self) {
let kf = self.fresh();
writeln!(self.out, " {kf} = call i64 @plg_rt_k_fn(ptr %m)").unwrap();
let ke = self.fresh();
writeln!(self.out, " {ke} = call i64 @plg_rt_k_env(ptr %m)").unwrap();
let kp = self.fresh();
writeln!(self.out, " {kp} = inttoptr i64 {kf} to ptr").unwrap();
let r = self.fresh();
writeln!(self.out, " {r} = musttail call i32 {kp}(ptr %m, i64 {ke})").unwrap();
writeln!(self.out, " ret i32 {r}").unwrap();
}
}