use super::dist::{DefaultWeight, Dist, Item, SELECTOR_BASE, Span};
use super::func::{Func, Stmt};
use super::ir::{ArrayNode, Expr};
use super::priority::{self, SOFT_BASE, Soft, SoftKey};
use super::real_domain::{Real, RealCond, RealDist, RealDistItem, RealExpr, RealSpan, RealVarId};
use super::reduce::ItemOffsets;
use super::registry::{
Home, Outcome, ProgArray, ProgBlock, ProgHandle, ProgState, ProgVar, Program, Target, owner,
};
use crate::objects::{self, FieldSlot, Window};
use num_bigint::BigUint;
use std::collections::HashMap;
use std::convert::Infallible;
use std::sync::Arc;
pub(super) struct Member {
pub handle: u64,
pub program: Arc<Result<Program, String>>,
pub rand_mode: Vec<bool>,
}
impl Member {
fn prog(&self) -> Result<&Program, Outcome> {
self.program
.as_ref()
.as_ref()
.map_err(|_| Outcome::NoProgram)
}
}
pub(super) struct Closure {
pub members: Vec<Member>,
rank: Vec<u32>,
}
pub(super) fn read_handle(obj: u64, slot: FieldSlot, bit: u32) -> Result<Option<u64>, Outcome> {
let mut buf = vec![0u64; slot.words];
if !objects::field_get(obj, slot, &mut buf) {
return Err(Outcome::DeadHandle);
}
let w = (bit / 64) as usize;
let half = slot.words / 2;
let (val, unk) = if bit == 0 && slot.words == 2 {
(buf[0], buf[1])
} else {
(
*buf.get(w).ok_or(Outcome::Failed)?,
*buf.get(half + w).ok_or(Outcome::Failed)?,
)
};
if unk != 0 {
return Err(Outcome::StateUnknown);
}
Ok((val != 0).then_some(val))
}
pub(super) fn closure(
root: u64,
root_program: &Arc<Result<Program, String>>,
root_mode: &[bool],
statics: &[(u32, u64)],
) -> Result<Closure, Outcome> {
let mut members = vec![Member {
handle: root,
program: Arc::clone(root_program),
rand_mode: root_mode.to_vec(),
}];
let mut children: HashMap<u64, Vec<u64>> = HashMap::new();
let mut next = 0;
while next < members.len() {
let me = members[next].handle;
let handles: Vec<ProgHandle> = members[next].prog()?.rand_handles.clone();
let mut kids = Vec::new();
for ph in &handles {
let active = match ph.prop_index {
Some(i) => members[next]
.rand_mode
.get(i)
.copied()
.ok_or(Outcome::Failed)?,
None => true,
};
if !active {
continue;
}
let o = owner(ph.home, me, statics)?;
let Some(child) = read_handle(o, ph.slot, ph.bit)? else {
continue;
};
let template = objects::dynamic_template(child).ok_or(Outcome::DeadHandle)?;
kids.push(child);
if members.iter().any(|m| m.handle == child) {
continue;
}
let program = super::registry::program(template).ok_or(Outcome::NoProgram)?;
let meta = objects::rand_meta(child).ok_or(Outcome::DeadHandle)?;
members.push(Member {
handle: child,
program,
rand_mode: meta.rand_mode,
});
}
children.insert(me, kids);
next += 1;
}
let order = priority::object_order(&root, &|o: &u64| {
children.get(o).cloned().unwrap_or_default()
});
let rank = members
.iter()
.map(|m| order.iter().position(|o| *o == m.handle).unwrap_or(0) as u32)
.collect();
Ok(Closure { members, rank })
}
pub(super) struct Merged {
pub program: Program,
pub rand_mode: Vec<bool>,
}
struct Offsets {
var: u32,
state: u32,
array: u32,
func: u32,
dist: u32,
soft: u32,
conj: usize,
prop: usize,
disable: u32,
}
pub(super) enum StateTo {
State(u32),
Const(BigUint),
}
struct Remap<'a> {
off: &'a Offsets,
sub: Option<&'a [Option<u32>]>,
vars: bool,
var_map: Option<&'a [u32]>,
array_map: Option<&'a [u32]>,
state_to: Option<&'a [StateTo]>,
}
impl<'a> Remap<'a> {
fn offset(off: &'a Offsets, sub: Option<&'a [Option<u32>]>, vars: bool) -> Self {
Remap {
off,
sub,
vars,
var_map: None,
array_map: None,
state_to: None,
}
}
fn expr(&self, e: &Expr) -> Expr {
let mapped = e
.try_map_children::<Infallible>(&mut |c| Ok(self.expr(c)))
.unwrap_or_else(|never| match never {});
match mapped {
Expr::Var(v, t) if self.vars => Expr::Var(self.var(v), t),
Expr::State(s, t) => {
match self
.sub
.and_then(|sub| sub.get(s as usize).copied().flatten())
{
Some(v) => Expr::Var(v, t),
None => match self.state_to.and_then(|m| m.get(s as usize)) {
Some(StateTo::State(n)) => Expr::State(*n, t),
Some(StateTo::Const(c)) => Expr::Const(t, c.clone()),
None => Expr::State(s + self.off.state, t),
},
}
}
Expr::Array(n) => Expr::Array(self.node(n)),
other => other,
}
}
fn var(&self, v: u32) -> u32 {
if v >= SOFT_BASE {
v + self.off.soft
} else if v >= SELECTOR_BASE {
v + self.off.dist
} else {
self.prog_var(v)
}
}
fn prog_var(&self, v: u32) -> u32 {
match self.var_map {
Some(m) => m[v as usize],
None => v + self.off.var,
}
}
fn arr(&self, a: u32) -> u32 {
match self.array_map {
Some(m) => m[a as usize],
None => a + self.off.array,
}
}
fn node(&self, n: ArrayNode) -> ArrayNode {
match n {
ArrayNode::Size(x) => ArrayNode::Size(self.arr(x)),
ArrayNode::Loop(..) | ArrayNode::Pick { .. } => n,
ArrayNode::Elem { array, index, ty } => ArrayNode::Elem {
array: self.arr(array),
index,
ty,
},
ArrayNode::Foreach { array, var, body } => ArrayNode::Foreach {
array: self.arr(array),
var,
body,
},
ArrayNode::Reduce {
op,
array,
var,
index_var,
body,
} => ArrayNode::Reduce {
op,
array: self.arr(array),
var,
index_var,
body,
},
ArrayNode::Distinct { scalars, arrays } => ArrayNode::Distinct {
scalars,
arrays: arrays.into_iter().map(|a| self.arr(a)).collect(),
},
ArrayNode::RowScope {
template,
row,
body,
} => ArrayNode::RowScope {
template: self.arr(template),
row,
body,
},
ArrayNode::Call { func, args, ty } => ArrayNode::Call {
func: func + self.off.func,
args,
ty,
},
}
}
fn stmts(&self, body: &[Stmt]) -> Vec<Stmt> {
body.iter().map(|s| self.stmt(s)).collect()
}
fn stmt(&self, s: &Stmt) -> Stmt {
match s {
Stmt::Assign(slot, e) => Stmt::Assign(*slot, self.expr(e)),
Stmt::If(c, t, o) => Stmt::If(self.expr(c), self.stmts(t), self.stmts(o)),
Stmt::Loop {
cond,
body,
step,
test_first,
} => Stmt::Loop {
cond: cond.as_ref().map(|c| self.expr(c)),
body: self.stmts(body),
step: self.stmts(step),
test_first: *test_first,
},
Stmt::Break => Stmt::Break,
Stmt::Continue => Stmt::Continue,
Stmt::Return => Stmt::Return,
}
}
}
fn stmt_exprs<'a>(body: &'a [Stmt], f: &mut impl FnMut(&'a Expr)) {
for s in body {
match s {
Stmt::Assign(_, e) => f(e),
Stmt::If(c, t, o) => {
f(c);
stmt_exprs(t, f);
stmt_exprs(o, f);
}
Stmt::Loop {
cond, body, step, ..
} => {
if let Some(c) = cond {
f(c);
}
stmt_exprs(body, f);
stmt_exprs(step, f);
}
Stmt::Break | Stmt::Continue | Stmt::Return => {}
}
}
}
fn resolve_via(
c: &Closure,
j: usize,
s: &ProgState,
statics: &[(u32, u64)],
) -> Option<(usize, FieldSlot)> {
if s.class_of {
return None;
}
let (last, hops) = s.via.split_last()?;
let mut obj = owner(s.home, c.members[j].handle, statics).ok()?;
for (i, slot) in std::iter::once(&s.slot).chain(hops).enumerate() {
obj = read_handle(obj, *slot, if i == 0 { s.root_bit } else { 0 }).ok()??;
}
let k = c.members.iter().position(|m| m.handle == obj)?;
Some((k, *last))
}
fn substitution(
c: &Closure,
progs: &[&Program],
offs: &[Offsets],
j: usize,
s: &ProgState,
statics: &[(u32, u64)],
) -> Result<Option<u32>, Outcome> {
let Some((k, last)) = resolve_via(c, j, s, statics) else {
return Ok(None);
};
let found = progs[k].vars.iter().position(|v| {
v.home == Home::Instance
&& v.slot.offset == last.offset
&& v.slot.words == last.words
&& v.window == s.window
});
let Some(i) = found else { return Ok(None) };
if progs[k].vars[i].ty != s.ty {
return Err(Outcome::Failed);
}
Ok(Some(offs[k].var + i as u32))
}
#[derive(Debug, PartialEq, Eq)]
pub(super) enum SelfAlias {
None,
Integral,
}
pub(super) fn self_alias(
c: &Closure,
prog: &Program,
statics: &[(u32, u64)],
) -> Result<SelfAlias, Outcome> {
let mut out = SelfAlias::None;
for s in &prog.states {
let Some((_, last)) = resolve_via(c, 0, s, statics) else {
continue;
};
if prog.real_vars.iter().any(|v| {
v.home == Home::Instance
&& v.slot.offset == last.offset
&& v.slot.words == last.words
&& v.window == s.window
}) {
return Err(Outcome::GlobalUnsupported);
}
if prog.vars.iter().any(|v| {
v.home == Home::Instance
&& v.slot.offset == last.offset
&& v.slot.words == last.words
&& v.window == s.window
}) {
out = SelfAlias::Integral;
}
}
Ok(out)
}
pub(super) fn merge(c: &Closure, statics: &[(u32, u64)]) -> Result<Merged, Outcome> {
let progs: Vec<&Program> = c
.members
.iter()
.map(Member::prog)
.collect::<Result<_, _>>()?;
let mut seen_statics: Vec<u32> = Vec::new();
for p in &progs {
if !p.real_vars.is_empty() {
return Err(Outcome::GlobalUnsupported);
}
let mut mine: Vec<u32> = p
.vars
.iter()
.map(|v| v.home)
.chain(p.arrays.iter().map(|a| a.home))
.filter_map(|h| match h {
Home::Static(id) => Some(id),
_ => None,
})
.collect();
mine.sort_unstable();
mine.dedup();
if mine.iter().any(|id| seen_statics.contains(id)) {
return Err(Outcome::GlobalUnsupported);
}
seen_statics.extend(mine);
}
let mut offs: Vec<Offsets> = Vec::with_capacity(progs.len());
let mut at = Offsets {
var: 0,
state: 0,
array: 0,
func: 0,
dist: 0,
soft: 0,
conj: 0,
prop: 0,
disable: 0,
};
for (m, p) in c.members.iter().zip(&progs) {
offs.push(Offsets { ..at });
at.var += p.vars.len() as u32;
at.state += p.states.len() as u32;
at.array += p.arrays.len() as u32;
at.func += p.funcs.len() as u32;
at.dist += p.dists.len() as u32;
at.soft += p.softs.len() as u32;
at.conj += p.conjuncts.len();
at.prop += m.rand_mode.len();
at.disable += p.n_disables as u32;
}
let mut out = Program::default();
let mut rand_mode = Vec::with_capacity(at.prop);
for (j, m) in c.members.iter().enumerate() {
let p = progs[j];
let off = &offs[j];
let mut sub = Vec::with_capacity(p.states.len());
for s in &p.states {
sub.push(substitution(c, &progs, &offs, j, s, statics)?);
}
let with_sub = Remap::offset(off, Some(&sub), true);
let plain = Remap::offset(off, None, true);
let frame = Remap::offset(off, None, false);
let reads_random = |e: &Expr| {
let mut hit = false;
e.for_each_state(&mut |s| hit |= sub.get(s as usize).copied().flatten().is_some());
hit
};
let home = |h: Home| match h {
Home::Instance if j > 0 => Home::Object(m.handle),
other => other,
};
let prop = |i: Option<usize>| i.map(|i| i + off.prop);
let item_off = ItemOffsets {
dists: off.dist as usize,
softs: off.soft as usize,
before: out.before.len(),
disables: off.disable as usize,
};
out.vars.extend(p.vars.iter().map(|v| ProgVar {
home: home(v.home),
prop_index: prop(v.prop_index),
domain_conjunct: v.domain_conjunct.map(|d| d + off.conj),
..v.clone()
}));
out.states.extend(p.states.iter().map(|s| ProgState {
home: home(s.home),
..s.clone()
}));
out.arrays.extend(p.arrays.iter().map(|a| ProgArray {
home: home(a.home),
prop_index: prop(a.prop_index),
template_of: a.template_of.map(|t| t + off.array),
..a.clone()
}));
out.blocks.extend(p.blocks.iter().map(|b| ProgBlock {
home: home(b.home),
conjuncts: b.conjuncts.start + off.conj..b.conjuncts.end + off.conj,
items: b.items.shifted(&item_off),
..b.clone()
}));
out.conjuncts
.extend(p.conjuncts.iter().map(|e| with_sub.expr(e)));
out.before.extend(
p.before
.iter()
.map(|&(a, b)| (plain.prog_var(a), plain.prog_var(b))),
);
for d in &p.dists {
out.dists.push(remap_dist(d, &plain, &reads_random)?);
}
for f in &p.funcs {
let mut hit = false;
stmt_exprs(&f.body, &mut |e| hit |= reads_random(e));
if hit {
return Err(Outcome::GlobalUnsupported);
}
out.funcs.push(Func {
body: frame.stmts(&f.body),
..f.clone()
});
}
out.softs.extend(p.softs.iter().map(|s| Soft {
priority: priority::Priority {
object: c.rank[j],
..s.priority
},
dists: s.dists.iter().map(|d| d + off.dist).collect(),
keys: s.keys.iter().map(|k| remap_key(&plain, *k)).collect(),
disabled: s.disabled,
disabled_by: s.disabled_by.iter().map(|d| d + off.disable).collect(),
}));
out.n_disables += p.n_disables;
rand_mode.extend_from_slice(&m.rand_mode);
}
if let Some(p) = apply_static_modes(&out, &mut rand_mode, statics) {
out = p;
}
Ok(Merged {
program: out,
rand_mode,
})
}
pub(super) fn overlay(
base: &Program,
frag: &Program,
consts: &[Option<BigUint>],
) -> Result<Program, Outcome> {
let mut out = base.clone();
let mut var_map = Vec::with_capacity(frag.vars.len());
let mut known = Vec::with_capacity(frag.vars.len());
let mut promoted: Vec<u32> = Vec::new();
for v in &frag.vars {
let found = base
.vars
.iter()
.position(|b| b.home == v.home && b.slot == v.slot && b.window == v.window);
match found {
Some(j) => {
if base.vars[j].ty != v.ty {
return Err(Outcome::Failed);
}
var_map.push(j as u32);
known.push(true);
}
None => {
promoted.push(out.vars.len() as u32);
var_map.push(out.vars.len() as u32);
known.push(false);
out.vars.push(ProgVar {
domain_conjunct: None,
..v.clone()
});
}
}
}
if !promoted.is_empty() {
promote_states(&mut out, base, &promoted)?;
}
let mut array_map: Vec<u32> = vec![u32::MAX; frag.arrays.len()];
for (k, a) in frag.arrays.iter().enumerate() {
if a.template_of.is_some() {
continue;
}
let j = match out.arrays.iter().position(|b| {
b.template_of.is_none() && b.home == a.home && b.slot == a.slot && b.window == a.window
}) {
Some(j) => {
if a.rand && !out.arrays[j].rand {
out.arrays[j].rand = true;
out.arrays[j].prop_index = a.prop_index;
}
j
}
None => {
out.arrays.push(a.clone());
out.arrays.len() - 1
}
};
array_map[k] = j as u32;
}
for (k, a) in frag.arrays.iter().enumerate() {
let Some(outer) = a.template_of else { continue };
let outer = array_map[outer as usize];
let j = match out.arrays.iter().position(|b| b.template_of == Some(outer)) {
Some(j) => j,
None => {
let mut t = a.clone();
t.template_of = Some(outer);
out.arrays.push(t);
out.arrays.len() - 1
}
};
array_map[k] = j as u32;
}
let mut state_to = Vec::with_capacity(frag.states.len());
for (k, s) in frag.states.iter().enumerate() {
if let Some(Some(c)) = consts.get(k) {
state_to.push(StateTo::Const(c.clone()));
continue;
}
let j = match out.states.iter().position(|b| b == s) {
Some(j) => j,
None => {
out.states.push(s.clone());
out.states.len() - 1
}
};
state_to.push(StateTo::State(j as u32));
}
let off = Offsets {
var: 0,
state: 0,
array: 0,
func: base.funcs.len() as u32,
dist: base.dists.len() as u32,
soft: base.softs.len() as u32,
conj: base.conjuncts.len(),
prop: 0,
disable: base.n_disables as u32,
};
let mk = |vars: bool| Remap {
off: &off,
sub: None,
vars,
var_map: Some(&var_map),
array_map: Some(&array_map),
state_to: Some(&state_to),
};
let (plain, frame) = (mk(true), mk(false));
let dropped: Vec<usize> = frag
.vars
.iter()
.zip(&known)
.filter(|&(_, &k)| k)
.filter_map(|(v, _)| v.domain_conjunct)
.collect();
let mut conj_at: Vec<Option<usize>> = vec![None; frag.conjuncts.len()];
for (i, c) in frag.conjuncts.iter().enumerate() {
if !dropped.contains(&i) {
conj_at[i] = Some(out.conjuncts.len());
out.conjuncts.push(plain.expr(c));
}
}
for (v, new) in frag.vars.iter().zip(&var_map) {
if promoted.contains(new) {
out.vars[*new as usize].domain_conjunct = v
.domain_conjunct
.and_then(|d| conj_at.get(d).copied().flatten());
}
}
out.before.extend(
frag.before
.iter()
.map(|&(a, b)| (plain.prog_var(a), plain.prog_var(b))),
);
for d in &frag.dists {
out.dists.push(remap_dist(d, &plain, &|_: &Expr| false)?);
}
for f in &frag.funcs {
out.funcs.push(Func {
body: frame.stmts(&f.body),
..f.clone()
});
}
for s in &frag.softs {
out.softs.push(Soft {
priority: s.priority,
dists: s.dists.iter().map(|d| d + off.dist).collect(),
keys: s.keys.iter().map(|k| remap_key(&plain, *k)).collect(),
disabled: s.disabled,
disabled_by: s.disabled_by.iter().map(|d| d + off.disable).collect(),
});
}
let first = off.disable + (frag.n_disables - frag.disable_keys.len()) as u32;
for (k, key) in frag.disable_keys.iter().enumerate() {
let key = remap_key(&plain, *key);
for s in out.softs.iter_mut().take(base.softs.len()) {
if s.keys.iter().any(|&m| key.disables(m)) {
s.disabled_by.push(first + k as u32);
s.disabled = true;
}
}
}
out.n_disables += frag.n_disables;
merge_real(&mut out, frag, &plain, &state_to)?;
Ok(out)
}
fn merge_real(
out: &mut Program,
frag: &Program,
plain: &Remap<'_>,
state_to: &[StateTo],
) -> Result<(), Outcome> {
if frag.real_vars.len() != frag.real.kinds.len() {
return Err(Outcome::Failed);
}
let base_real_vars = out.real_vars.len();
let mut map: Vec<RealVarId> = Vec::with_capacity(frag.real_vars.len());
for v in &frag.real_vars {
let found = out
.real_vars
.iter()
.position(|b| b.home == v.home && b.slot == v.slot && b.window == v.window);
match found {
Some(j) => {
if out.real_vars[j].kind != v.kind {
return Err(Outcome::Failed);
}
map.push(j as RealVarId);
}
None => {
map.push(out.real_vars.len() as RealVarId);
out.real_vars.push(v.clone());
out.real.kinds.push(v.kind);
}
}
}
let rr = RealRemap {
plain,
map: &map,
state_to,
};
for c in &frag.real.conds {
out.real.conds.push(rr.cond(c));
}
for d in &frag.real.dists {
let span = |s: &RealSpan| match s {
RealSpan::Point(e) => RealSpan::Point(rr.expr(e)),
RealSpan::Range { lo, hi } => RealSpan::Range {
lo: lo.as_ref().map(|e| rr.expr(e)),
hi: hi.as_ref().map(|e| rr.expr(e)),
},
};
out.real.dists.push(RealDist {
var: map[d.var as usize],
items: d
.items
.iter()
.map(|i| RealDistItem {
span: span(&i.span),
weight: plain.expr(&i.weight),
})
.collect(),
default: d.default.as_ref().map(|w| plain.expr(w)),
});
}
out.real.before.extend(
frag.real
.before
.iter()
.map(|&(a, b)| (map[a as usize], map[b as usize])),
);
promote_real_states(out, base_real_vars)
}
fn promote_real_states(out: &mut Program, first: usize) -> Result<(), Outcome> {
let sub: Vec<Option<RealVarId>> = out
.states
.iter()
.map(|s| {
(first..out.real_vars.len())
.find(|&j| {
let v = &out.real_vars[j];
s.via.is_empty() && s.home == v.home && s.slot == v.slot && s.window == v.window
})
.map(|j| j as RealVarId)
})
.collect();
if sub.iter().all(Option::is_none) {
return Ok(());
}
let reads = |e: &Expr| {
let mut hit = false;
e.for_each_state(&mut |s| hit |= sub.get(s as usize).copied().flatten().is_some());
hit
};
if out.conjuncts.iter().any(&reads) || out.dists.iter().any(|d| dist_reads(d, &reads)) {
return Err(Outcome::GlobalUnsupported);
}
for f in &out.funcs {
let mut hit = false;
stmt_exprs(&f.body, &mut |e| hit |= reads(e));
if hit {
return Err(Outcome::GlobalUnsupported);
}
}
fn expr(e: &mut RealExpr, sub: &[Option<RealVarId>], reads: &dyn Fn(&Expr) -> bool) -> bool {
match e {
RealExpr::Const(..) => false,
RealExpr::Var(_) => false,
RealExpr::State(s, _) => {
if let Some(Some(v)) = sub.get(*s as usize) {
*e = RealExpr::Var(*v);
}
false
}
RealExpr::FromInt(x) => reads(x),
RealExpr::Neg(x) | RealExpr::ToShort(x) => expr(x, sub, reads),
RealExpr::Add(x, y)
| RealExpr::Sub(x, y)
| RealExpr::Mul(x, y)
| RealExpr::Div(x, y) => expr(x, sub, reads) | expr(y, sub, reads),
}
}
fn cond(c: &mut RealCond, sub: &[Option<RealVarId>], reads: &dyn Fn(&Expr) -> bool) -> bool {
match c {
RealCond::Const(_) => false,
RealCond::Int(e) => reads(e),
RealCond::Cmp(_, x, y) => expr(x, sub, reads) | expr(y, sub, reads),
RealCond::Not(x) => cond(x, sub, reads),
RealCond::And(v) | RealCond::Or(v) => {
v.iter_mut().fold(false, |h, x| h | cond(x, sub, reads))
}
RealCond::Guard(p, t, e) => {
cond(p, sub, reads) | cond(t, sub, reads) | cond(e, sub, reads)
}
}
}
let mut bad = false;
for c in &mut out.real.conds {
bad |= cond(c, &sub, &reads);
}
for d in &mut out.real.dists {
for i in &mut d.items {
bad |= reads(&i.weight);
match &mut i.span {
RealSpan::Point(e) => bad |= expr(e, &sub, &reads),
RealSpan::Range { lo, hi } => {
for e in lo.iter_mut().chain(hi.iter_mut()) {
bad |= expr(e, &sub, &reads);
}
}
}
}
bad |= d.default.as_ref().is_some_and(&reads);
}
if bad {
return Err(Outcome::GlobalUnsupported);
}
Ok(())
}
struct RealRemap<'a> {
plain: &'a Remap<'a>,
map: &'a [RealVarId],
state_to: &'a [StateTo],
}
impl RealRemap<'_> {
fn expr(&self, e: &RealExpr) -> RealExpr {
let b = |x: &RealExpr| Box::new(self.expr(x));
match e {
RealExpr::Const(..) => e.clone(),
RealExpr::Var(v) => RealExpr::Var(self.map[*v as usize]),
RealExpr::State(s, k) => match self.state_to.get(*s as usize) {
Some(StateTo::State(n)) => RealExpr::State(*n, *k),
Some(StateTo::Const(c)) => RealExpr::Const(Real::new(k.from_bits(c)), *k),
None => RealExpr::State(*s, *k),
},
RealExpr::FromInt(x) => RealExpr::FromInt(Box::new(self.plain.expr(x))),
RealExpr::Neg(x) => RealExpr::Neg(b(x)),
RealExpr::ToShort(x) => RealExpr::ToShort(b(x)),
RealExpr::Add(x, y) => RealExpr::Add(b(x), b(y)),
RealExpr::Sub(x, y) => RealExpr::Sub(b(x), b(y)),
RealExpr::Mul(x, y) => RealExpr::Mul(b(x), b(y)),
RealExpr::Div(x, y) => RealExpr::Div(b(x), b(y)),
}
}
fn cond(&self, c: &RealCond) -> RealCond {
let b = |x: &RealCond| Box::new(self.cond(x));
match c {
RealCond::Const(_) => c.clone(),
RealCond::Int(e) => RealCond::Int(self.plain.expr(e)),
RealCond::Cmp(op, x, y) => RealCond::Cmp(*op, self.expr(x), self.expr(y)),
RealCond::Not(x) => RealCond::Not(b(x)),
RealCond::And(v) => RealCond::And(v.iter().map(|x| self.cond(x)).collect()),
RealCond::Or(v) => RealCond::Or(v.iter().map(|x| self.cond(x)).collect()),
RealCond::Guard(p, t, e) => RealCond::Guard(b(p), b(t), b(e)),
}
}
}
fn promote_states(out: &mut Program, base: &Program, promoted: &[u32]) -> Result<(), Outcome> {
let mut sub: Vec<Option<u32>> = Vec::with_capacity(base.states.len());
for s in &base.states {
sub.push(promoted.iter().copied().find(|&p| {
let v = &out.vars[p as usize];
s.via.is_empty() && s.home == v.home && s.slot == v.slot && s.window == v.window
}));
}
if sub.iter().all(Option::is_none) {
return Ok(());
}
let zero = Offsets {
var: 0,
state: 0,
array: 0,
func: 0,
dist: 0,
soft: 0,
conj: 0,
prop: 0,
disable: 0,
};
let with_sub = Remap::offset(&zero, Some(&sub), true);
let plain = Remap::offset(&zero, None, true);
let reads_random = |e: &Expr| {
let mut hit = false;
e.for_each_state(&mut |s| hit |= sub.get(s as usize).copied().flatten().is_some());
hit
};
for c in &mut out.conjuncts {
*c = with_sub.expr(c);
}
let mut dists = Vec::with_capacity(base.dists.len());
for d in &base.dists {
dists.push(remap_dist(d, &plain, &reads_random)?);
}
out.dists = dists;
for f in &base.funcs {
let mut hit = false;
stmt_exprs(&f.body, &mut |e| hit |= reads_random(e));
if hit {
return Err(Outcome::GlobalUnsupported);
}
}
Ok(())
}
pub(super) fn restrict(
p: &mut Program,
rand_mode: &mut Vec<bool>,
only: &[Target],
) -> Result<(), Outcome> {
let names = |t: &Target, home: Home, slot: FieldSlot, window: Option<Window>| {
t.home == home && t.slot == slot && (t.window.is_none() || t.window == window)
};
let mut hit = vec![false; only.len()];
let mut bit = |on: bool| -> usize {
rand_mode.push(on);
rand_mode.len() - 1
};
for v in &mut p.vars {
let mut on = false;
for (k, t) in only.iter().enumerate() {
if names(t, v.home, v.slot, v.window) {
on = true;
hit[k] = true;
}
}
v.prop_index = Some(bit(on));
}
for a in &mut p.arrays {
let mut on = false;
for (k, t) in only.iter().enumerate() {
if names(t, a.home, a.slot, None) {
on = true;
hit[k] = true;
}
}
a.prop_index = Some(bit(on));
}
for r in &mut p.real_vars {
let mut on = false;
for (k, t) in only.iter().enumerate() {
if names(t, r.home, r.slot, r.window) {
on = true;
hit[k] = true;
}
}
r.prop_index = Some(bit(on));
}
for h in &mut p.rand_handles {
let mut on = false;
for (k, t) in only.iter().enumerate() {
if names(t, h.home, h.slot, None) {
on = true;
hit[k] = true;
}
}
h.prop_index = Some(bit(on));
}
if hit.iter().all(|&h| h) {
Ok(())
} else {
Err(Outcome::Failed)
}
}
pub(super) fn apply_static_modes(
p: &Program,
rand_mode: &mut Vec<bool>,
statics: &[(u32, u64)],
) -> Option<Program> {
let is_off = |home: Home, slot: FieldSlot, bit: Option<usize>| -> bool {
if bit.is_some() {
return false;
}
let Home::Static(id) = home else { return false };
statics
.iter()
.find(|&&(i, _)| i == id)
.and_then(|&(_, s)| objects::static_rand_mode(s, slot.offset))
== Some(false)
};
let any = p.vars.iter().any(|v| is_off(v.home, v.slot, v.prop_index))
|| p.arrays
.iter()
.any(|a| is_off(a.home, a.slot, a.prop_index))
|| p.real_vars
.iter()
.any(|r| is_off(r.home, r.slot, r.prop_index))
|| p.rand_handles
.iter()
.any(|h| is_off(h.home, h.slot, h.prop_index));
if !any {
return None;
}
let mut out = p.clone();
let mut bit = || -> Option<usize> {
rand_mode.push(false);
Some(rand_mode.len() - 1)
};
for v in &mut out.vars {
if is_off(v.home, v.slot, v.prop_index) {
v.prop_index = bit();
}
}
for a in &mut out.arrays {
if is_off(a.home, a.slot, a.prop_index) {
a.prop_index = bit();
}
}
for r in &mut out.real_vars {
if is_off(r.home, r.slot, r.prop_index) {
r.prop_index = bit();
}
}
for h in &mut out.rand_handles {
if is_off(h.home, h.slot, h.prop_index) {
h.prop_index = bit();
}
}
Some(out)
}
pub(super) fn apply_leaf_modes(
p: &Program,
rand_mode: &mut Vec<bool>,
leaf_off: &[(usize, u32)],
leaf_on: &[(usize, u32)],
) -> Option<Program> {
let forced = |home: Home, bit: Option<usize>, w: Option<objects::Window>| -> Option<bool> {
match (home, bit, w) {
(Home::Instance, Some(i), Some(w)) if leaf_off.contains(&(i, w.bit)) => Some(false),
(Home::Instance, Some(i), Some(w)) if leaf_on.contains(&(i, w.bit)) => Some(true),
_ => None,
}
};
let any = p
.vars
.iter()
.any(|v| forced(v.home, v.prop_index, v.window).is_some())
|| p.real_vars
.iter()
.any(|r| forced(r.home, r.prop_index, r.window).is_some());
if !any {
return None;
}
let mut out = p.clone();
let mut bit = |on: bool| -> Option<usize> {
rand_mode.push(on);
Some(rand_mode.len() - 1)
};
for v in &mut out.vars {
if let Some(on) = forced(v.home, v.prop_index, v.window) {
v.prop_index = bit(on);
}
}
for r in &mut out.real_vars {
if let Some(on) = forced(r.home, r.prop_index, r.window) {
r.prop_index = bit(on);
}
}
Some(out)
}
fn remap_key(r: &Remap<'_>, k: SoftKey) -> SoftKey {
match k {
SoftKey::Var(v) => SoftKey::Var(r.prog_var(v)),
SoftKey::Array(a) => SoftKey::Array(r.arr(a)),
SoftKey::ArraySize(a) => SoftKey::ArraySize(r.arr(a)),
}
}
fn dist_reads(d: &Dist, reads: &impl Fn(&Expr) -> bool) -> bool {
let mut hit = false;
let mut check = |e: &Expr| hit |= reads(e);
for item in &d.items {
check(&item.member);
check(&item.weight);
match &item.span {
Span::Point(e) => check(e),
Span::Range { lo, hi } => {
lo.iter().chain(hi).for_each(&mut check);
}
}
}
if let Some(def) = &d.default {
check(&def.weight);
}
hit
}
fn remap_dist(
d: &Dist,
plain: &Remap<'_>,
reads_random: &impl Fn(&Expr) -> bool,
) -> Result<Dist, Outcome> {
if d.subject.is_some() || dist_reads(d, reads_random) {
return Err(Outcome::GlobalUnsupported);
}
let opt = |e: &Option<Expr>| e.as_ref().map(|e| plain.expr(e));
Ok(Dist {
subject_ty: d.subject_ty,
plain_var: d.plain_var.map(|v| plain.prog_var(v)),
vars: d.vars.iter().map(|&v| plain.prog_var(v)).collect(),
items: d
.items
.iter()
.map(|i| Item {
member: plain.expr(&i.member),
ty: i.ty,
span: match &i.span {
Span::Point(e) => Span::Point(plain.expr(e)),
Span::Range { lo, hi } => Span::Range {
lo: opt(lo),
hi: opt(hi),
},
},
weight: plain.expr(&i.weight),
per_range: i.per_range,
})
.collect(),
default: d.default.as_ref().map(|w| DefaultWeight {
weight: plain.expr(&w.weight),
per_range: w.per_range,
}),
subject: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constraints::ir::{BinOp, Ty};
use crate::constraints::real_domain::{CmpOp, FloatKind, RealProblem};
use crate::constraints::registry::{self, ProgRealVar};
use crate::netlist::ClassTemplateId;
use crate::objects::{RandKind, RandProp, alloc, register_rand_props};
const HANDLE: FieldSlot = FieldSlot {
offset: 0,
words: 2,
};
const V: FieldSlot = FieldSlot {
offset: 2,
words: 1,
};
fn u8t() -> Ty {
Ty::unsigned(8)
}
fn bin(op: BinOp, a: Expr, b: Expr) -> Expr {
Expr::Binary(op, Box::new(a), Box::new(b), Ty::BIT)
}
fn k(v: u64) -> Expr {
Expr::constant(u8t(), v)
}
fn node_class(id: u32, conjuncts: Vec<Expr>) -> ClassTemplateId {
let t = ClassTemplateId(id);
register_rand_props(
t,
vec![
RandProp {
slot: HANDLE,
kind: RandKind::Rand,
},
RandProp {
slot: V,
kind: RandKind::Rand,
},
],
);
registry::register_program(
t,
Ok(Program {
vars: vec![ProgVar {
ty: u8t(),
slot: V,
home: Home::Instance,
prop_index: Some(1),
domain_conjunct: None,
randc: false,
window: None,
}],
states: vec![ProgState {
class_of: false,
root_bit: 0,
ty: u8t(),
slot: HANDLE,
window: None,
home: Home::Instance,
via: vec![V],
}],
conjuncts,
rand_handles: vec![ProgHandle {
slot: HANDLE,
home: Home::Instance,
prop_index: Some(0),
bit: 0,
}],
..Program::default()
}),
);
t
}
fn leaf_class(id: u32, conjuncts: Vec<Expr>) -> ClassTemplateId {
let t = ClassTemplateId(id);
register_rand_props(
t,
vec![RandProp {
slot: V,
kind: RandKind::Rand,
}],
);
registry::register_program(
t,
Ok(Program {
vars: vec![ProgVar {
ty: u8t(),
slot: V,
home: Home::Instance,
prop_index: Some(0),
domain_conjunct: None,
randc: false,
window: None,
}],
conjuncts,
..Program::default()
}),
);
t
}
fn set(h: u64, slot: FieldSlot, words: &[u64]) {
assert!(objects::field_set(h, slot, words));
}
fn v_of(h: u64) -> u64 {
let mut b = [0u64];
assert!(objects::field_get(h, V, &mut b));
b[0]
}
fn closure_of(h: u64) -> Result<Closure, Outcome> {
let t = objects::dynamic_template(h).expect("live");
let entry = registry::program(t).expect("program");
closure(
h,
&entry,
&objects::rand_meta(h).expect("meta").rand_mode,
&[],
)
}
fn handles(c: &Closure) -> Vec<u64> {
c.members.iter().map(|m| m.handle).collect()
}
fn differ() -> Vec<Expr> {
vec![bin(BinOp::Ne, Expr::State(0, u8t()), Expr::Var(0, u8t()))]
}
#[test]
fn a_cyclic_pair_of_objects_is_collected_once_each_and_merged_into_one_problem() {
let t = node_class(0x7b00_0001, differ());
let (p, q) = (alloc(t, 3), alloc(t, 3));
set(p, HANDLE, &[q, 0]);
set(q, HANDLE, &[p, 0]);
let c = closure_of(p).expect("closure");
assert_eq!(handles(&c), vec![p, q], "p first, q once, p not re-entered");
let m = merge(&c, &[]).expect("merge");
assert_eq!(m.program.vars.len(), 2);
assert_eq!(m.program.conjuncts.len(), 2, "one `differ` per object");
assert_eq!(m.rand_mode.len(), 4, "two rand props each");
assert_eq!(m.program.vars[0].home, Home::Instance);
assert_eq!(m.program.vars[1].home, Home::Object(q));
assert_eq!(m.program.vars[1].prop_index, Some(3));
assert_eq!(
m.program.conjuncts[0],
bin(BinOp::Ne, Expr::Var(1, u8t()), Expr::Var(0, u8t()))
);
assert_eq!(
m.program.conjuncts[1],
bin(BinOp::Ne, Expr::Var(0, u8t()), Expr::Var(1, u8t()))
);
let mut seen = std::collections::BTreeSet::new();
for _ in 0..200 {
assert_eq!(registry::randomize(p, &[]), Outcome::Solved);
assert_ne!(v_of(p), v_of(q));
seen.insert((v_of(p), v_of(q)));
}
assert!(seen.len() > 100, "{}", seen.len());
}
#[test]
fn a_chain_is_walked_to_its_end_and_a_null_handle_ends_it() {
let t = node_class(0x7b00_0002, Vec::new());
let (root, a, b, c) = (alloc(t, 3), alloc(t, 3), alloc(t, 3), alloc(t, 3));
set(root, HANDLE, &[a, 0]);
set(a, HANDLE, &[c, 0]);
set(b, HANDLE, &[c, 0]);
assert_eq!(handles(&closure_of(root).unwrap()), vec![root, a, c]);
assert_eq!(handles(&closure_of(b).unwrap()), vec![b, c]);
set(c, HANDLE, &[0, 0]);
assert_eq!(handles(&closure_of(c).unwrap()), vec![c]);
}
#[test]
fn an_inactive_rand_handle_leaves_its_object_out_and_its_members_are_state() {
let t = node_class(0x7b00_0003, differ());
let (p, q) = (alloc(t, 3), alloc(t, 3));
set(p, HANDLE, &[q, 0]);
set(q, V, &[5]);
let mut meta = objects::rand_meta(p).expect("meta");
meta.rand_mode[0] = false; objects::set_rand_meta(p, meta);
assert_eq!(handles(&closure_of(p).unwrap()), vec![p]);
for _ in 0..50 {
assert_eq!(registry::randomize(p, &[]), Outcome::Solved);
assert_ne!(v_of(p), 5);
assert_eq!(v_of(q), 5);
}
}
#[test]
fn a_global_constraint_with_no_solution_is_unsatisfiable_and_writes_nothing() {
let leaf = leaf_class(
0x7b00_0004,
vec![bin(BinOp::Eq, Expr::Var(0, u8t()), k(255))],
);
let parent = node_class(
0x7b00_0005,
vec![bin(BinOp::Gt, Expr::State(0, u8t()), Expr::Var(0, u8t()))],
);
let (p, q) = (alloc(parent, 3), alloc(leaf, 3));
set(p, HANDLE, &[q, 0]);
set(p, V, &[9]);
set(q, V, &[7]);
assert_eq!(registry::randomize(p, &[]), Outcome::Solved);
assert_eq!(v_of(q), 255);
assert!(v_of(p) < 255);
let parent2 = node_class(
0x7b00_0006,
vec![
bin(BinOp::Gt, Expr::State(0, u8t()), Expr::Var(0, u8t())),
bin(BinOp::Eq, Expr::Var(0, u8t()), k(255)),
],
);
let p2 = alloc(parent2, 3);
set(p2, HANDLE, &[q, 0]);
set(p2, V, &[9]);
set(q, V, &[7]);
assert_eq!(registry::randomize(p2, &[]), Outcome::Unsatisfiable);
assert_eq!((v_of(p2), v_of(q)), (9, 7));
}
#[test]
fn the_joint_solve_is_uniform_over_the_combinations_of_all_objects() {
let two = |e: Expr| bin(BinOp::Lt, e, k(4));
let leaf = leaf_class(0x7b00_0007, vec![two(Expr::Var(0, u8t()))]);
let parent = node_class(
0x7b00_0008,
vec![
two(Expr::Var(0, u8t())),
bin(BinOp::Le, Expr::Var(0, u8t()), Expr::State(0, u8t())),
],
);
let (p, q) = (alloc(parent, 3), alloc(leaf, 3));
set(p, HANDLE, &[q, 0]);
let mut counts = std::collections::BTreeMap::new();
for _ in 0..5000 {
assert_eq!(registry::randomize(p, &[]), Outcome::Solved);
*counts.entry((v_of(p), v_of(q))).or_insert(0u32) += 1;
}
assert_eq!(counts.len(), 10, "{counts:?}");
for (pair, n) in &counts {
assert!(pair.0 <= pair.1);
assert!((380..=620).contains(n), "{pair:?}: {n}");
}
}
#[test]
fn a_real_random_variable_in_a_contained_object_is_a_loud_limit() {
let leaf = leaf_class(0x7b00_0009, Vec::new());
registry::register_program(
leaf,
Ok(Program {
real_vars: vec![ProgRealVar {
kind: FloatKind::F64,
slot: FieldSlot {
offset: 2,
words: 2,
},
home: Home::Instance,
prop_index: Some(0),
window: None,
}],
..Program::default()
}),
);
let parent = node_class(0x7b00_000a, Vec::new());
let (p, q) = (alloc(parent, 4), alloc(leaf, 4));
set(p, HANDLE, &[q, 0]);
assert_eq!(registry::randomize(p, &[]), Outcome::GlobalUnsupported);
}
const A: FieldSlot = FieldSlot {
offset: 2,
words: 1,
};
const B: FieldSlot = FieldSlot {
offset: 3,
words: 1,
};
const C: FieldSlot = FieldSlot {
offset: 4,
words: 1,
};
fn pvar(slot: FieldSlot, prop: Option<usize>) -> ProgVar {
ProgVar {
ty: u8t(),
slot,
home: Home::Instance,
prop_index: prop,
domain_conjunct: None,
randc: false,
window: None,
}
}
fn pstate(slot: FieldSlot) -> ProgState {
ProgState {
class_of: false,
root_bit: 0,
ty: u8t(),
slot,
window: None,
home: Home::Instance,
via: Vec::new(),
}
}
fn lt(a: Expr, b: Expr) -> Expr {
bin(BinOp::Lt, a, b)
}
fn base_ab() -> Program {
Program {
vars: vec![pvar(A, Some(0)), pvar(B, Some(1))],
conjuncts: vec![bin(BinOp::Ne, Expr::Var(0, u8t()), Expr::Var(1, u8t()))],
..Program::default()
}
}
#[test]
fn overlay_identifies_a_fragment_variable_by_where_it_lives_not_by_its_number() {
let frag = Program {
vars: vec![pvar(B, None)],
conjuncts: vec![lt(Expr::Var(0, u8t()), k(5))],
..Program::default()
};
let out = overlay(&base_ab(), &frag, &[]).expect("overlay");
assert_eq!(out.vars.len(), 2, "no variable was added");
assert_eq!(out.conjuncts.len(), 2);
assert_eq!(out.conjuncts[0], base_ab().conjuncts[0], "the base is kept");
assert_eq!(out.conjuncts[1], lt(Expr::Var(1, u8t()), k(5)));
}
#[test]
fn overlay_makes_a_captured_local_a_constant_and_never_a_state() {
let frag = Program {
vars: vec![pvar(A, None)],
states: vec![pstate(C)],
conjuncts: vec![lt(Expr::Var(0, u8t()), Expr::State(0, u8t()))],
..Program::default()
};
let out = overlay(&base_ab(), &frag, &[Some(BigUint::from(7u32))]).expect("overlay");
assert!(out.states.is_empty(), "the capture is not a state");
assert_eq!(out.conjuncts[1], lt(Expr::Var(0, u8t()), k(7)));
}
#[test]
fn overlay_shares_a_state_the_base_already_reads_and_appends_a_new_one() {
let mut base = base_ab();
base.states = vec![pstate(C)];
let frag = Program {
states: vec![pstate(A), pstate(C)],
conjuncts: vec![lt(Expr::State(0, u8t()), Expr::State(1, u8t()))],
..Program::default()
};
let out = overlay(&base, &frag, &[]).expect("overlay");
assert_eq!(out.states, vec![pstate(C), pstate(A)]);
assert_eq!(
out.conjuncts[1],
lt(Expr::State(1, u8t()), Expr::State(0, u8t()))
);
}
#[test]
fn overlay_promotes_a_property_the_base_reads_as_state_to_a_variable() {
let mut base = base_ab();
base.states = vec![pstate(C)];
base.conjuncts.push(lt(Expr::State(0, u8t()), k(3)));
let frag = Program {
vars: vec![pvar(C, None)],
..Program::default()
};
let out = overlay(&base, &frag, &[]).expect("overlay");
assert_eq!(out.vars.len(), 3);
assert_eq!(out.vars[2].slot, C);
assert_eq!(out.conjuncts[1], lt(Expr::Var(2, u8t()), k(3)));
}
#[test]
fn overlay_refuses_what_it_cannot_lay_over_loudly() {
let wide = ProgVar {
ty: Ty::unsigned(16),
..pvar(A, None)
};
let frag = Program {
vars: vec![wide],
..Program::default()
};
assert_eq!(overlay(&base_ab(), &frag, &[]), Err(Outcome::Failed));
let real_var = |kind| registry::ProgRealVar {
kind,
slot: A,
home: Home::Instance,
prop_index: None,
window: None,
};
let mut base = base_ab();
base.real_vars = vec![real_var(FloatKind::F64)];
base.real.kinds = vec![FloatKind::F64];
let frag = Program {
real_vars: vec![real_var(FloatKind::F32)],
real: RealProblem {
kinds: vec![FloatKind::F32],
..Default::default()
},
..Program::default()
};
assert_eq!(overlay(&base, &frag, &[]), Err(Outcome::Failed));
}
#[test]
fn overlay_merges_real_constraints_sharing_a_variable_the_base_has() {
let rv = registry::ProgRealVar {
kind: FloatKind::F64,
slot: A,
home: Home::Instance,
prop_index: None,
window: None,
};
let c = |x: f64| RealExpr::Const(Real::new(x), FloatKind::F64);
let mut base = base_ab();
base.real_vars = vec![rv.clone()];
base.real = RealProblem {
kinds: vec![FloatKind::F64],
conds: vec![RealCond::Cmp(CmpOp::Lt, RealExpr::Var(0), c(10.0))],
..Default::default()
};
let frag = Program {
real_vars: vec![rv],
states: vec![pstate(C)],
real: RealProblem {
kinds: vec![FloatKind::F64],
conds: vec![
RealCond::Cmp(CmpOp::Gt, RealExpr::Var(0), c(2.0)),
RealCond::Cmp(
CmpOp::Lt,
RealExpr::Var(0),
RealExpr::State(0, FloatKind::F64),
),
],
..Default::default()
},
..Program::default()
};
let bits = BigUint::from(3.0f64.to_bits());
let out = overlay(&base, &frag, &[Some(bits)]).expect("overlay");
assert_eq!(out.real_vars.len(), 1, "the variable is shared");
assert_eq!(out.real.kinds.len(), 1);
assert_eq!(out.real.conds.len(), 3);
assert_eq!(
out.real.conds[2],
RealCond::Cmp(CmpOp::Lt, RealExpr::Var(0), c(3.0)),
"the captured local is its value"
);
}
#[test]
fn overlay_promotes_a_real_property_the_base_reads_as_state_to_a_variable() {
let rv = registry::ProgRealVar {
kind: FloatKind::F64,
slot: C,
home: Home::Instance,
prop_index: None,
window: None,
};
let c = |x: f64| RealExpr::Const(Real::new(x), FloatKind::F64);
let mut base = base_ab();
let r_state = base.states.len() as u32;
base.states.push(pstate(C));
base.real = RealProblem {
conds: vec![RealCond::Cmp(
CmpOp::Lt,
RealExpr::State(r_state, FloatKind::F64),
c(10.0),
)],
..Default::default()
};
let frag = Program {
real_vars: vec![rv],
real: RealProblem {
kinds: vec![FloatKind::F64],
..Default::default()
},
..Program::default()
};
let out = overlay(&base, &frag, &[]).expect("overlay");
assert_eq!(out.real_vars.len(), 1);
assert_eq!(
out.real.conds[0],
RealCond::Cmp(CmpOp::Lt, RealExpr::Var(0), c(10.0)),
"the base condition reads the promoted variable"
);
let mut base2 = base.clone();
base2.conjuncts.push(Expr::State(r_state, u8t()));
assert_eq!(overlay(&base2, &frag, &[]), Err(Outcome::GlobalUnsupported));
}
#[test]
fn restrict_keeps_only_the_named_variables_active_and_leaves_the_objects_bits_alone() {
let mut p = base_ab();
let mut mode = vec![true, true];
let only = [Target {
home: Home::Instance,
slot: B,
window: None,
}];
restrict(&mut p, &mut mode, &only).expect("restrict");
assert_eq!(
mode[..2],
[true, true],
"the object's own bits are untouched"
);
let on = |v: &ProgVar| mode[v.prop_index.expect("bit")];
assert!(!on(&p.vars[0]), "a is state");
assert!(on(&p.vars[1]), "b is the random set");
}
#[test]
fn restrict_activates_a_variable_the_object_has_switched_off() {
let mut p = base_ab();
let mut mode = vec![false, false];
let only = [Target {
home: Home::Instance,
slot: A,
window: None,
}];
restrict(&mut p, &mut mode, &only).expect("restrict");
assert!(mode[p.vars[0].prop_index.unwrap()]);
assert!(!mode[p.vars[1].prop_index.unwrap()]);
}
#[test]
fn restrict_with_no_targets_is_a_checker_and_an_unknown_target_is_loud() {
let mut p = base_ab();
let mut mode = vec![true, true];
restrict(&mut p, &mut mode, &[]).expect("randomize(null)");
assert!(p.vars.iter().all(|v| !mode[v.prop_index.unwrap()]));
let nowhere = [Target {
home: Home::Instance,
slot: C,
window: None,
}];
assert_eq!(
restrict(&mut base_ab(), &mut vec![true, true], &nowhere),
Err(Outcome::Failed)
);
}
fn spec_frag_lt_capture(id: u32) -> (ClassTemplateId, u32) {
let t = leaf_class(id, Vec::new());
let frag = Program {
vars: vec![pvar(V, None)],
states: vec![pstate(C)],
conjuncts: vec![lt(Expr::Var(0, u8t()), Expr::State(0, u8t()))],
..Program::default()
};
let spec = registry::register_spec(registry::Spec {
frag,
captures: vec![registry::Capture { state: 0, width: 8 }],
only: None,
});
(t, spec)
}
#[test]
fn a_spec_call_constrains_the_draw_by_the_value_captured_at_that_call() {
let (t, spec) = spec_frag_lt_capture(0x7b00_0011);
let o = alloc(t, 3);
for limit in [1u64, 5, 200] {
for _ in 0..50 {
assert_eq!(
registry::randomize_spec(o, &[], spec, &[limit]),
Outcome::Solved
);
assert!(v_of(o) < limit, "{} < {limit}", v_of(o));
}
}
let mut seen_high = false;
for _ in 0..100 {
assert_eq!(registry::randomize(o, &[]), Outcome::Solved);
seen_high |= v_of(o) >= 200;
}
assert!(seen_high);
}
#[test]
fn a_spec_that_cannot_hold_fails_and_leaves_the_object_untouched() {
let (t, spec) = spec_frag_lt_capture(0x7b00_0012);
let o = alloc(t, 3);
set(o, V, &[42]);
assert_eq!(
registry::randomize_spec(o, &[], spec, &[0]),
Outcome::Unsatisfiable
);
assert_eq!(v_of(o), 42);
assert_eq!(
registry::randomize_spec(o, &[], spec, &[9 | (1 << 8)]),
Outcome::StateUnknown
);
assert_eq!(v_of(o), 42);
assert_eq!(registry::randomize_spec(o, &[], spec, &[]), Outcome::Failed);
assert_eq!(
registry::randomize_spec(o, &[], u32::MAX, &[]),
Outcome::Failed
);
}
#[test]
fn randomize_null_is_a_checker_that_changes_nothing() {
let t = leaf_class(0x7b00_0013, Vec::new());
let make = |bound: u64| {
registry::register_spec(registry::Spec {
frag: Program {
vars: vec![pvar(V, None)],
conjuncts: vec![bin(BinOp::Eq, Expr::Var(0, u8t()), k(bound))],
..Program::default()
},
captures: Vec::new(),
only: Some(Vec::new()),
})
};
let o = alloc(t, 3);
set(o, V, &[3]);
assert_eq!(
registry::randomize_spec(o, &[], make(3), &[]),
Outcome::Solved
);
assert_eq!(v_of(o), 3);
assert_eq!(
registry::randomize_spec(o, &[], make(4), &[]),
Outcome::Unsatisfiable
);
assert_eq!(v_of(o), 3);
}
fn block(home: Home, conjuncts: std::ops::Range<usize>) -> ProgBlock {
ProgBlock {
home,
ordinal: 0,
conjuncts,
items: Default::default(),
}
}
#[test]
fn merge_keeps_every_objects_blocks_with_shifted_ranges_and_homes() {
let t = node_class(0x7b00_0031, differ());
let mut prog = registry::program(t)
.expect("program")
.as_ref()
.clone()
.expect("built");
prog.blocks = vec![block(Home::Instance, 0..1), block(Home::Static(4), 1..1)];
registry::register_program(t, Ok(prog));
let (p, q) = (alloc(t, 3), alloc(t, 3));
set(p, HANDLE, &[q, 0]);
set(q, HANDLE, &[p, 0]);
let m = merge(&closure_of(p).expect("closure"), &[]).expect("merge");
let homes: Vec<Home> = m.program.blocks.iter().map(|b| b.home).collect();
assert_eq!(
homes,
vec![
Home::Instance,
Home::Static(4),
Home::Object(q),
Home::Static(4)
]
);
let ranges: Vec<_> = m
.program
.blocks
.iter()
.map(|b| b.conjuncts.clone())
.collect();
assert_eq!(
ranges,
vec![0..1, 1..1, 1..2, 2..2],
"q's conjunct follows p's"
);
}
#[test]
fn apply_static_modes_turns_a_switched_off_static_into_state() {
let st = alloc(ClassTemplateId(0x7b00_0032), 1);
let var = |home: Home, prop: Option<usize>| ProgVar {
home,
..pvar(
FieldSlot {
offset: 0,
words: 1,
},
prop,
)
};
let p = Program {
vars: vec![var(Home::Static(7), None), var(Home::Static(8), None)],
..Program::default()
};
let mut mode = vec![true];
let statics = [(7u32, st)];
assert!(apply_static_modes(&p, &mut mode, &statics).is_none());
assert!(objects::set_static_rand_mode(st, 0, false));
let out = apply_static_modes(&p, &mut mode, &statics).expect("switched off");
assert_eq!(mode, vec![true, false], "one fresh inactive bit");
assert_eq!(out.vars[0].prop_index, Some(1));
assert_eq!(out.vars[1].prop_index, None, "static 8 was not passed");
let mut owned = p.clone();
owned.vars[0].prop_index = Some(0);
let mut mode = vec![true];
assert!(apply_static_modes(&owned, &mut mode, &statics).is_none());
assert_eq!(mode, vec![true]);
}
}