use std::collections::BTreeSet;
use proptest::prelude::*;
use super::harness::{
Program, Transaction, ZSet, any_config, apply_proposals, check, configs, fixpoint, map_steps,
proposals, read_zset, set_after, set_zset, workloads,
};
use crate::{
OutputHandle, RootCircuit, Stream, ZSetHandle, ZWeight,
typed_batch::{OrdZSet, SpineSnapshot},
utils::{Tup2, Tup3, test::CIRCUIT_CASES},
};
type Alloc = Tup2<u64, u64>;
type Assign = Tup2<u64, u64>;
type Store = Tup3<u64, u64, u64>;
type Load = Tup3<u64, u64, u64>;
type PointsTo = Tup2<u64, u64>;
type FieldPointsTo = Tup3<u64, u64, u64>;
#[derive(Clone, Debug, Default)]
struct ProgramFacts {
alloc: Vec<(Alloc, ZWeight)>,
assign: Vec<(Assign, ZWeight)>,
store: Vec<(Store, ZWeight)>,
load: Vec<(Load, ZWeight)>,
}
#[derive(Clone)]
struct PointsToAnalysis;
impl Program for PointsToAnalysis {
type Input = ProgramFacts;
type Handles = (
ZSetHandle<Alloc>,
ZSetHandle<Assign>,
ZSetHandle<Store>,
ZSetHandle<Load>,
OutputHandle<SpineSnapshot<OrdZSet<PointsTo>>>,
OutputHandle<SpineSnapshot<OrdZSet<FieldPointsTo>>>,
);
type Output = (ZSet<PointsTo>, ZSet<FieldPointsTo>);
fn build(&self, circuit: &mut RootCircuit) -> Self::Handles {
let (alloc, alloc_handle) = circuit.add_input_zset::<Alloc>();
let (assign, assign_handle) = circuit.add_input_zset::<Assign>();
let (store, store_handle) = circuit.add_input_zset::<Store>();
let (load, load_handle) = circuit.add_input_zset::<Load>();
let (points_to, field_points_to) = circuit
.recursive(
|child,
(points_to, field_points_to): (
Stream<_, OrdZSet<PointsTo>>,
Stream<_, OrdZSet<FieldPointsTo>>,
)| {
let by_var = points_to.map_index(|Tup2(var, object)| (*var, *object));
let assigned = assign
.delta0(child)
.map_index(|Tup2(to, from)| (*from, *to))
.join(&by_var, |_from, to, object| Tup2(*to, *object));
let stored = store
.delta0(child)
.map_index(|Tup3(base, field, from)| (*base, Tup2(*field, *from)))
.join_index(&by_var, |_base, Tup2(field, from), base_object| {
Some((*from, Tup2(*base_object, *field)))
})
.join(&by_var, |_from, Tup2(base_object, field), object| {
Tup3(*base_object, *field, *object)
});
let loaded = load
.delta0(child)
.map_index(|Tup3(to, base, field)| (*base, Tup2(*to, *field)))
.join_index(&by_var, |_base, Tup2(to, field), base_object| {
Some((Tup2(*base_object, *field), *to))
})
.join(
&field_points_to.map_index(|Tup3(base_object, field, object)| {
(Tup2(*base_object, *field), *object)
}),
|_base_field, to, object| Tup2(*to, *object),
);
Ok((alloc.delta0(child).plus(&assigned).plus(&loaded), stored))
},
)
.unwrap();
(
alloc_handle,
assign_handle,
store_handle,
load_handle,
points_to.accumulate_integrate().accumulate_output(),
field_points_to.accumulate_integrate().accumulate_output(),
)
}
fn push(&self, (alloc, assign, store, load, _, _): &Self::Handles, input: &ProgramFacts) {
for (row, weight) in &input.alloc {
alloc.push(*row, *weight);
}
for (row, weight) in &input.assign {
assign.push(*row, *weight);
}
for (row, weight) in &input.store {
store.push(*row, *weight);
}
for (row, weight) in &input.load {
load.push(*row, *weight);
}
}
fn read(&self, (_, _, _, _, points_to, field_points_to): &Self::Handles) -> Self::Output {
(read_zset(points_to), read_zset(field_points_to))
}
fn model(&self, inputs: &[ProgramFacts]) -> Self::Output {
let alloc = set_after(inputs.iter().map(|input| input.alloc.as_slice()));
let assign = set_after(inputs.iter().map(|input| input.assign.as_slice()));
let store = set_after(inputs.iter().map(|input| input.store.as_slice()));
let load = set_after(inputs.iter().map(|input| input.load.as_slice()));
let objects_of = |points_to: &BTreeSet<PointsTo>, var: u64| {
points_to
.range(Tup2(var, 0)..=Tup2(var, u64::MAX))
.map(|Tup2(_, object)| *object)
.collect::<Vec<_>>()
};
let (points_to, field_points_to) = fixpoint(
(BTreeSet::new(), BTreeSet::new()),
|(points_to, field_points_to): &(BTreeSet<PointsTo>, BTreeSet<FieldPointsTo>)| {
let mut points_to_next = alloc.clone();
for Tup2(to, from) in &assign {
for object in objects_of(points_to, *from) {
points_to_next.insert(Tup2(*to, object));
}
}
for Tup3(to, base, field) in &load {
for base_object in objects_of(points_to, *base) {
for Tup3(_, _, object) in field_points_to.range(
Tup3(base_object, *field, 0)..=Tup3(base_object, *field, u64::MAX),
) {
points_to_next.insert(Tup2(*to, *object));
}
}
}
let mut field_points_to_next = BTreeSet::new();
for Tup3(base, field, from) in &store {
for base_object in objects_of(points_to, *base) {
for object in objects_of(points_to, *from) {
field_points_to_next.insert(Tup3(base_object, *field, object));
}
}
}
(points_to_next, field_points_to_next)
},
);
(set_zset(&points_to), set_zset(&field_points_to))
}
}
fn points_to_triggers() -> Vec<Vec<Transaction<ProgramFacts>>> {
let setup = ProgramFacts {
alloc: vec![(Tup2(0, 100), 1)],
assign: (1..=8).map(|var| (Tup2(var, var - 1), 1)).collect(),
store: vec![(Tup3(8, 0, 20), 1), (Tup3(8, 1, 22), 1)],
load: vec![(Tup3(30, 8, 0), 1), (Tup3(31, 8, 1), 1)],
};
let two_objects = |var: u64, object: u64, weight: ZWeight| ProgramFacts {
alloc: vec![
(Tup2(var, object), weight),
(Tup2(var - 1, object + 1), weight),
],
assign: vec![(Tup2(var, var - 1), weight)],
..ProgramFacts::default()
};
let cut_chain = |weight| ProgramFacts {
assign: vec![(Tup2(4, 3), weight)],
..ProgramFacts::default()
};
vec![
vec![vec![setup.clone()], vec![two_objects(20, 200, 1)]],
vec![
vec![setup.clone()],
vec![two_objects(20, 200, 1), two_objects(22, 202, 1)],
],
vec![
vec![setup],
vec![two_objects(20, 200, 1)],
vec![two_objects(20, 200, -1)],
vec![cut_chain(-1)],
vec![two_objects(20, 200, 1)],
vec![cut_chain(1)],
],
]
}
fn points_to_workloads() -> impl Strategy<Value = Vec<Transaction<ProgramFacts>>> {
let alloc = (0..5u64, 0..3u64).prop_map(|(var, object)| Tup2(var, 100 + object));
let assign = (0..5u64, 0..5u64).prop_map(|(to, from)| Tup2(to, from));
let store = (0..5u64, 0..2u64, 0..5u64).prop_map(|(base, field, from)| Tup3(base, field, from));
let load = (0..5u64, 0..5u64, 0..2u64).prop_map(|(to, base, field)| Tup3(to, base, field));
let step = (
proposals(alloc, 2),
proposals(assign, 3),
proposals(store, 2),
proposals(load, 2),
);
workloads(step, 5, 3).prop_map(|raw| {
let mut live = (
BTreeSet::new(),
BTreeSet::new(),
BTreeSet::new(),
BTreeSet::new(),
);
map_steps(raw, |(alloc, assign, store, load)| ProgramFacts {
alloc: apply_proposals(&mut live.0, &alloc),
assign: apply_proposals(&mut live.1, &assign),
store: apply_proposals(&mut live.2, &store),
load: apply_proposals(&mut live.3, &load),
})
})
}
#[test]
fn points_to_triggers_hold() {
for workload in points_to_triggers() {
for config in configs() {
check(&PointsToAnalysis, &workload, config);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(CIRCUIT_CASES))]
#[test]
fn points_to_random(workload in points_to_workloads(), config in any_config()) {
check(&PointsToAnalysis, &workload, config);
}
}