use datafrog::Relation;
use rustc_hash::{FxHashMap, FxHashSet};
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use crate::facts::{AllFacts, Atom, FactTypes};
mod datafrog_opt;
mod initialization;
mod liveness;
mod location_insensitive;
mod naive;
#[derive(Debug, Clone, Copy)]
pub enum Algorithm {
Naive,
DatafrogOpt,
LocationInsensitive,
Compare,
Hybrid,
}
impl Algorithm {
pub const OPTIMIZED: &'static [Algorithm] = &[Algorithm::DatafrogOpt];
pub fn variants() -> [&'static str; 5] {
[
"Naive",
"DatafrogOpt",
"LocationInsensitive",
"Compare",
"Hybrid",
]
}
}
impl ::std::str::FromStr for Algorithm {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_ref() {
"naive" => Ok(Algorithm::Naive),
"datafrogopt" => Ok(Algorithm::DatafrogOpt),
"locationinsensitive" => Ok(Algorithm::LocationInsensitive),
"compare" => Ok(Algorithm::Compare),
"hybrid" => Ok(Algorithm::Hybrid),
_ => Err(String::from(
"valid values: Naive, DatafrogOpt, LocationInsensitive, Compare, Hybrid",
)),
}
}
}
#[derive(Clone, Debug)]
pub struct Output<T: FactTypes> {
pub errors: FxHashMap<T::Point, Vec<T::Loan>>,
pub subset_errors: FxHashMap<T::Point, BTreeSet<(T::Origin, T::Origin)>>,
pub move_errors: FxHashMap<T::Point, Vec<T::Path>>,
pub dump_enabled: bool,
pub loan_live_at: FxHashMap<T::Point, Vec<T::Loan>>,
pub origin_contains_loan_at: FxHashMap<T::Point, BTreeMap<T::Origin, BTreeSet<T::Loan>>>,
pub origin_contains_loan_anywhere: FxHashMap<T::Origin, BTreeSet<T::Loan>>,
pub origin_live_on_entry: FxHashMap<T::Point, Vec<T::Origin>>,
pub loan_invalidated_at: FxHashMap<T::Point, Vec<T::Loan>>,
pub subset: FxHashMap<T::Point, BTreeMap<T::Origin, BTreeSet<T::Origin>>>,
pub subset_anywhere: FxHashMap<T::Origin, BTreeSet<T::Origin>>,
pub var_live_on_entry: FxHashMap<T::Point, Vec<T::Variable>>,
pub var_drop_live_on_entry: FxHashMap<T::Point, Vec<T::Variable>>,
pub path_maybe_initialized_on_exit: FxHashMap<T::Point, Vec<T::Path>>,
pub path_maybe_uninitialized_on_exit: FxHashMap<T::Point, Vec<T::Path>>,
pub known_contains: FxHashMap<T::Origin, BTreeSet<T::Loan>>,
pub var_maybe_partly_initialized_on_exit: FxHashMap<T::Point, Vec<T::Variable>>,
}
struct InitializationContext<T: FactTypes> {
child_path: Vec<(T::Path, T::Path)>,
path_is_var: Vec<(T::Path, T::Variable)>,
path_assigned_at_base: Vec<(T::Path, T::Point)>,
path_moved_at_base: Vec<(T::Path, T::Point)>,
path_accessed_at_base: Vec<(T::Path, T::Point)>,
}
struct LivenessContext<T: FactTypes> {
var_used_at: Vec<(T::Variable, T::Point)>,
var_defined_at: Vec<(T::Variable, T::Point)>,
var_dropped_at: Vec<(T::Variable, T::Point)>,
use_of_var_derefs_origin: Vec<(T::Variable, T::Origin)>,
drop_of_var_derefs_origin: Vec<(T::Variable, T::Origin)>,
}
struct Context<'ctx, T: FactTypes> {
origin_live_on_entry: Relation<(T::Origin, T::Point)>,
loan_invalidated_at: Relation<(T::Loan, T::Point)>,
subset_base: &'ctx Vec<(T::Origin, T::Origin, T::Point)>,
loan_issued_at: &'ctx Vec<(T::Origin, T::Loan, T::Point)>,
loan_killed_at: Relation<(T::Loan, T::Point)>,
known_contains: Relation<(T::Origin, T::Loan)>,
placeholder_origin: Relation<(T::Origin, ())>,
placeholder_loan: Relation<(T::Loan, T::Origin)>,
known_placeholder_subset: Relation<(T::Origin, T::Origin)>,
cfg_edge: Relation<(T::Point, T::Point)>,
#[allow(dead_code)]
potential_errors: Option<FxHashSet<T::Loan>>,
#[allow(dead_code)]
potential_subset_errors: Option<Relation<(T::Origin, T::Origin)>>,
}
impl<T: FactTypes> Output<T> {
pub fn compute(all_facts: &AllFacts<T>, algorithm: Algorithm, dump_enabled: bool) -> Self {
let mut result = Output::new(dump_enabled);
let cfg_edge = all_facts.cfg_edge.clone().into();
let initialization_ctx = InitializationContext {
child_path: all_facts.child_path.clone(),
path_is_var: all_facts.path_is_var.clone(),
path_assigned_at_base: all_facts.path_assigned_at_base.clone(),
path_moved_at_base: all_facts.path_moved_at_base.clone(),
path_accessed_at_base: all_facts.path_accessed_at_base.clone(),
};
let initialization::InitializationResult::<T>(
var_maybe_partly_initialized_on_exit,
move_errors,
) = initialization::compute(initialization_ctx, &cfg_edge, &mut result);
for &(path, location) in move_errors.iter() {
result.move_errors.entry(location).or_default().push(path);
}
let liveness_ctx = LivenessContext {
var_used_at: all_facts.var_used_at.clone(),
var_defined_at: all_facts.var_defined_at.clone(),
var_dropped_at: all_facts.var_dropped_at.clone(),
use_of_var_derefs_origin: all_facts.use_of_var_derefs_origin.clone(),
drop_of_var_derefs_origin: all_facts.drop_of_var_derefs_origin.clone(),
};
let mut origin_live_on_entry = liveness::compute_live_origins(
liveness_ctx,
&cfg_edge,
var_maybe_partly_initialized_on_exit,
&mut result,
);
let cfg_node = cfg_edge
.iter()
.map(|&(point1, _)| point1)
.chain(cfg_edge.iter().map(|&(_, point2)| point2))
.collect();
liveness::make_universal_regions_live::<T>(
&mut origin_live_on_entry,
&cfg_node,
&all_facts.universal_region,
);
let origin_live_on_entry = origin_live_on_entry.into();
let loan_invalidated_at = Relation::from_iter(
all_facts
.loan_invalidated_at
.iter()
.map(|&(point, loan)| (loan, point)),
);
let loan_killed_at = all_facts.loan_killed_at.clone().into();
let known_placeholder_subset = all_facts.known_placeholder_subset.clone().into();
let known_contains =
Output::<T>::compute_known_contains(&known_placeholder_subset, &all_facts.placeholder);
let known_placeholder_subset =
Output::<T>::compute_known_placeholder_subset(&known_placeholder_subset);
let placeholder_origin: Relation<_> = Relation::from_iter(
all_facts
.universal_region
.iter()
.map(|&origin| (origin, ())),
);
let placeholder_loan = Relation::from_iter(
all_facts
.placeholder
.iter()
.map(|&(origin, loan)| (loan, origin)),
);
let mut ctx = Context {
origin_live_on_entry,
loan_invalidated_at,
cfg_edge,
subset_base: &all_facts.subset_base,
loan_issued_at: &all_facts.loan_issued_at,
loan_killed_at,
known_contains,
known_placeholder_subset,
placeholder_origin,
placeholder_loan,
potential_errors: None,
potential_subset_errors: None,
};
let (errors, subset_errors) = match algorithm {
Algorithm::LocationInsensitive => {
let (potential_errors, potential_subset_errors) =
location_insensitive::compute(&ctx, &mut result);
let potential_subset_errors: Relation<(T::Origin, T::Origin, T::Point)> =
Relation::from_iter(
potential_subset_errors
.into_iter()
.map(|&(origin1, origin2)| (origin1, origin2, 0.into())),
);
(potential_errors, potential_subset_errors)
}
Algorithm::Naive => naive::compute(&ctx, &mut result),
Algorithm::DatafrogOpt => datafrog_opt::compute(&ctx, &mut result),
Algorithm::Hybrid => {
let (potential_errors, potential_subset_errors) =
location_insensitive::compute(&ctx, &mut result);
if potential_errors.is_empty() && potential_subset_errors.is_empty() {
(potential_errors, Vec::new().into())
} else {
ctx.potential_errors =
Some(potential_errors.iter().map(|&(loan, _)| loan).collect());
ctx.potential_subset_errors = Some(potential_subset_errors);
datafrog_opt::compute(&ctx, &mut result)
}
}
Algorithm::Compare => {
let (naive_errors, naive_subset_errors) = naive::compute(&ctx, &mut result);
let (opt_errors, _) = datafrog_opt::compute(&ctx, &mut result);
let mut naive_errors_by_point = FxHashMap::default();
for &(loan, point) in naive_errors.iter() {
naive_errors_by_point
.entry(point)
.or_insert_with(Vec::new)
.push(loan);
}
let mut opt_errors_by_point = FxHashMap::default();
for &(loan, point) in opt_errors.iter() {
opt_errors_by_point
.entry(point)
.or_insert_with(Vec::new)
.push(loan);
}
if compare_errors(&naive_errors_by_point, &opt_errors_by_point) {
panic!(concat!(
"The errors reported by the naive algorithm differ from ",
"the errors reported by the optimized algorithm. ",
"See the error log for details."
));
} else {
debug!("Naive and optimized algorithms reported the same errors.");
}
(naive_errors, naive_subset_errors)
}
};
for &(loan, location) in errors.iter() {
result.errors.entry(location).or_default().push(loan);
}
for &(origin1, origin2, location) in subset_errors.iter() {
result
.subset_errors
.entry(location)
.or_default()
.insert((origin1, origin2));
}
if dump_enabled {
for &(origin, location) in ctx.origin_live_on_entry.iter() {
result
.origin_live_on_entry
.entry(location)
.or_default()
.push(origin);
}
for &(origin, loan) in ctx.known_contains.iter() {
result
.known_contains
.entry(origin)
.or_default()
.insert(loan);
}
}
result
}
fn compute_known_contains(
known_placeholder_subset: &Relation<(T::Origin, T::Origin)>,
placeholder: &[(T::Origin, T::Loan)],
) -> Relation<(T::Origin, T::Loan)> {
let mut iteration = datafrog::Iteration::new();
let known_contains = iteration.variable("known_contains");
known_contains.extend(placeholder.iter());
while iteration.changed() {
known_contains.from_join(
&known_contains,
known_placeholder_subset,
|&_origin1, &loan1, &origin2| (origin2, loan1),
);
}
known_contains.complete()
}
fn compute_known_placeholder_subset(
known_placeholder_subset_base: &Relation<(T::Origin, T::Origin)>,
) -> Relation<(T::Origin, T::Origin)> {
use datafrog::{Iteration, RelationLeaper};
let mut iteration = Iteration::new();
let known_placeholder_subset = iteration.variable("known_placeholder_subset");
known_placeholder_subset.extend(known_placeholder_subset_base.iter());
while iteration.changed() {
known_placeholder_subset.from_leapjoin(
&known_placeholder_subset,
known_placeholder_subset_base.extend_with(|&(_origin1, origin2)| origin2),
|&(origin1, _origin2), &origin3| (origin1, origin3),
);
}
known_placeholder_subset.complete()
}
fn new(dump_enabled: bool) -> Self {
Output {
errors: FxHashMap::default(),
subset_errors: FxHashMap::default(),
dump_enabled,
loan_live_at: FxHashMap::default(),
origin_contains_loan_at: FxHashMap::default(),
origin_contains_loan_anywhere: FxHashMap::default(),
origin_live_on_entry: FxHashMap::default(),
loan_invalidated_at: FxHashMap::default(),
move_errors: FxHashMap::default(),
subset: FxHashMap::default(),
subset_anywhere: FxHashMap::default(),
var_live_on_entry: FxHashMap::default(),
var_drop_live_on_entry: FxHashMap::default(),
path_maybe_initialized_on_exit: FxHashMap::default(),
path_maybe_uninitialized_on_exit: FxHashMap::default(),
var_maybe_partly_initialized_on_exit: FxHashMap::default(),
known_contains: FxHashMap::default(),
}
}
pub fn errors_at(&self, location: T::Point) -> &[T::Loan] {
match self.errors.get(&location) {
Some(v) => v,
None => &[],
}
}
pub fn loans_in_scope_at(&self, location: T::Point) -> &[T::Loan] {
match self.loan_live_at.get(&location) {
Some(p) => p,
None => &[],
}
}
pub fn origin_contains_loan_at(
&self,
location: T::Point,
) -> Cow<'_, BTreeMap<T::Origin, BTreeSet<T::Loan>>> {
assert!(self.dump_enabled);
match self.origin_contains_loan_at.get(&location) {
Some(map) => Cow::Borrowed(map),
None => Cow::Owned(BTreeMap::default()),
}
}
pub fn origins_live_at(&self, location: T::Point) -> &[T::Origin] {
assert!(self.dump_enabled);
match self.origin_live_on_entry.get(&location) {
Some(v) => v,
None => &[],
}
}
pub fn subsets_at(
&self,
location: T::Point,
) -> Cow<'_, BTreeMap<T::Origin, BTreeSet<T::Origin>>> {
assert!(self.dump_enabled);
match self.subset.get(&location) {
Some(v) => Cow::Borrowed(v),
None => Cow::Owned(BTreeMap::default()),
}
}
}
fn compare_errors<Loan: Atom, Point: Atom>(
all_naive_errors: &FxHashMap<Point, Vec<Loan>>,
all_opt_errors: &FxHashMap<Point, Vec<Loan>>,
) -> bool {
let points = all_naive_errors.keys().chain(all_opt_errors.keys());
let mut differ = false;
for point in points {
let mut naive_errors = all_naive_errors.get(&point).cloned().unwrap_or_default();
naive_errors.sort();
let mut opt_errors = all_opt_errors.get(&point).cloned().unwrap_or_default();
opt_errors.sort();
for err in naive_errors.iter() {
if !opt_errors.contains(err) {
error!(
"Error {0:?} at {1:?} reported by naive, but not opt.",
err, point
);
differ = true;
}
}
for err in opt_errors.iter() {
if !naive_errors.contains(err) {
error!(
"Error {0:?} at {1:?} reported by opt, but not naive.",
err, point
);
differ = true;
}
}
}
differ
}
#[cfg(test)]
mod tests {
use super::*;
impl Atom for usize {
fn index(self) -> usize {
self
}
}
fn compare(
errors1: &FxHashMap<usize, Vec<usize>>,
errors2: &FxHashMap<usize, Vec<usize>>,
) -> bool {
let diff1 = compare_errors(errors1, errors2);
let diff2 = compare_errors(errors2, errors1);
assert_eq!(diff1, diff2);
diff1
}
#[test]
fn test_compare_errors() {
let empty = FxHashMap::default();
assert_eq!(false, compare(&empty, &empty));
let mut empty_vec = FxHashMap::default();
empty_vec.insert(1, vec![]);
empty_vec.insert(2, vec![]);
assert_eq!(false, compare(&empty, &empty_vec));
let mut singleton1 = FxHashMap::default();
singleton1.insert(1, vec![10]);
assert_eq!(false, compare(&singleton1, &singleton1));
let mut singleton2 = FxHashMap::default();
singleton2.insert(1, vec![11]);
assert_eq!(false, compare(&singleton2, &singleton2));
let mut singleton3 = FxHashMap::default();
singleton3.insert(2, vec![10]);
assert_eq!(false, compare(&singleton3, &singleton3));
assert_eq!(true, compare(&singleton1, &singleton2));
assert_eq!(true, compare(&singleton2, &singleton3));
assert_eq!(true, compare(&singleton1, &singleton3));
assert_eq!(true, compare(&empty, &singleton1));
assert_eq!(true, compare(&empty, &singleton2));
assert_eq!(true, compare(&empty, &singleton3));
let mut errors1 = FxHashMap::default();
errors1.insert(1, vec![11]);
errors1.insert(2, vec![10]);
assert_eq!(false, compare(&errors1, &errors1));
assert_eq!(true, compare(&errors1, &singleton1));
assert_eq!(true, compare(&errors1, &singleton2));
assert_eq!(true, compare(&errors1, &singleton3));
}
}