use std::collections::HashSet;
use cuda_async::predicate::{LaunchCheck, Predicate, Stage};
use crate::passes::proof_analysis::ProofResults;
#[derive(Debug, Clone)]
pub(crate) struct Obligation {
pub(crate) predicate: Predicate,
pub(crate) cause: String,
}
impl Obligation {
pub(crate) fn new(predicate: Predicate, cause: impl Into<String>) -> Self {
Self {
predicate,
cause: cause.into(),
}
}
}
#[derive(Debug, Clone)]
pub(crate) enum Resolution {
Jit,
#[allow(dead_code)] Launch(LaunchCheck),
Device,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct Assumptions {
believed: HashSet<Predicate>,
}
impl Assumptions {
pub(crate) fn from_preconditions(
proof: &ProofResults,
param_index: &std::collections::HashMap<String, usize>,
) -> Self {
use crate::passes::proof_analysis::MetadataExpr;
use crate::passes::proof_analysis::MetadataFact;
use cuda_async::predicate::{Atom, Term};
let mut believed = HashSet::new();
for fact in &proof.metadata_facts {
match fact {
MetadataFact::DimEq {
lhs:
MetadataExpr::Dim {
tensor: lt,
axis: la,
},
rhs:
MetadataExpr::Dim {
tensor: rt,
axis: ra,
},
} => {
let (Some(&lp), Some(&rp)) = (param_index.get(lt), param_index.get(rt)) else {
continue;
};
let lhs = Term::atom(Atom::Dim {
param: lp,
axis: *la,
});
let rhs = Term::atom(Atom::Dim {
param: rp,
axis: *ra,
});
if let Some(pred) = Predicate::eq(&lhs, &rhs) {
believed.insert(pred);
}
}
MetadataFact::DimDivisible {
tensor,
axis,
divisor,
} => {
let Some(¶m) = param_index.get(tensor) else {
continue;
};
let term = Term::atom(Atom::Dim { param, axis: *axis });
if let Some(pred) = Predicate::divisible_by(term, *divisor) {
believed.insert(pred);
}
}
}
}
Self { believed }
}
fn entails(&self, predicate: &Predicate) -> bool {
self.believed.contains(predicate)
}
}
pub(crate) fn resolve(obligation: &Obligation, assumptions: &Assumptions) -> Resolution {
if obligation.predicate.eval(&|_| None) == Some(true) {
return Resolution::Jit;
}
if assumptions.entails(&obligation.predicate) {
Resolution::Jit
} else if obligation.predicate.stage() <= Stage::Launch {
Resolution::Launch(LaunchCheck {
predicate: obligation.predicate.clone(),
cause: obligation.cause.clone(),
})
} else {
Resolution::Device
}
}
#[cfg(test)]
mod tests {
use super::*;
use cuda_async::predicate::{Atom, Term};
fn dim_term(param: usize, axis: usize) -> Term {
Term::atom(Atom::Dim { param, axis })
}
fn assumptions(preds: Vec<Predicate>) -> Assumptions {
Assumptions {
believed: preds.into_iter().collect(),
}
}
#[test]
fn dim_eq_entailed_resolves_at_jit() {
let env = assumptions(vec![
Predicate::eq(&dim_term(0, 0), &dim_term(1, 0)).unwrap()
]);
let obl = Obligation::new(
Predicate::eq(&dim_term(1, 0), &dim_term(0, 0)).unwrap(),
"test",
);
assert!(matches!(resolve(&obl, &env), Resolution::Jit));
}
#[test]
fn dim_eq_unentailed_but_launch_known_resolves_at_launch() {
let env = assumptions(vec![]);
let obl = Obligation::new(
Predicate::eq(&dim_term(0, 0), &dim_term(1, 1)).unwrap(),
"test",
);
assert!(matches!(resolve(&obl, &env), Resolution::Launch(_)));
}
#[test]
fn device_stage_predicate_falls_to_device() {
let env = assumptions(vec![]);
let obl = Obligation::new(Predicate::nonzero(Term::atom(Atom::Iv(3))), "iv");
assert!(matches!(resolve(&obl, &env), Resolution::Device));
}
#[test]
fn dim_nonzero_resolves_at_launch() {
let env = assumptions(vec![]);
let obl = Obligation::new(Predicate::nonzero(dim_term(0, 0)), "extent > 0");
assert!(matches!(resolve(&obl, &env), Resolution::Launch(_)));
}
#[test]
fn no_axiom_is_believed_without_a_declared_precondition() {
let env = Assumptions::from_preconditions(
&ProofResults::default(),
&std::collections::HashMap::new(),
);
let goal = Predicate::lt(
&Term::atom(Atom::TileBlockId(0)),
&Term::atom(Atom::NumTileBlocks(0)),
)
.unwrap();
let obl = Obligation::new(goal, "block id in grid");
assert!(matches!(resolve(&obl, &env), Resolution::Device));
}
}