Skip to content

Commit 6a20f04

Browse files
Add Souffle variants of some Algorithms
1 parent 6911864 commit 6a20f04

File tree

5 files changed

+82
-40
lines changed

5 files changed

+82
-40
lines changed

polonius-engine/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ pub use facts::AllFacts;
1313
pub use facts::Atom;
1414
pub use facts::FactTypes;
1515
pub use output::Algorithm;
16+
pub use output::Engine;
1617
pub use output::Output;

polonius-engine/src/output/mod.rs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,19 @@ mod liveness;
2121
mod location_insensitive;
2222
mod naive;
2323

24+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25+
pub enum Engine {
26+
Souffle,
27+
Datafrog,
28+
}
29+
2430
#[derive(Debug, Clone, Copy)]
2531
pub enum Algorithm {
2632
/// Simple rules, but slower to execute
27-
Naive,
33+
Naive(Engine),
2834

2935
/// Optimized variant of the rules
30-
DatafrogOpt,
36+
DatafrogOpt(Engine),
3137

3238
/// Fast to compute, but imprecise: there can be false-positives
3339
/// but no false-negatives. Tailored for quick "early return" situations.
@@ -42,14 +48,20 @@ pub enum Algorithm {
4248
Hybrid,
4349
}
4450

51+
impl Default for Algorithm {
52+
fn default() -> Self {
53+
Self::Naive(Engine::Datafrog)
54+
}
55+
}
56+
4557
impl Algorithm {
4658
/// Optimized variants that ought to be equivalent to "naive"
47-
pub const OPTIMIZED: &'static [Algorithm] = &[Algorithm::DatafrogOpt];
59+
pub const OPTIMIZED: &'static [Algorithm] = &[Algorithm::DatafrogOpt(Engine::Datafrog)];
4860

4961
pub fn variants() -> [&'static str; 5] {
5062
[
51-
"Naive",
52-
"DatafrogOpt",
63+
"Naive(-Souffle)",
64+
"DatafrogOpt(-Souffle)",
5365
"LocationInsensitive",
5466
"Compare",
5567
"Hybrid",
@@ -79,14 +91,14 @@ impl ::std::str::FromStr for Algorithm {
7991
type Err = String;
8092
fn from_str(s: &str) -> Result<Self, Self::Err> {
8193
match s.to_lowercase().as_ref() {
82-
"naive" => Ok(Algorithm::Naive),
83-
"datafrogopt" => Ok(Algorithm::DatafrogOpt),
94+
"naive" => Ok(Algorithm::Naive(Engine::Datafrog)),
95+
"naive-souffle" => Ok(Algorithm::Naive(Engine::Souffle)),
96+
"datafrogopt" => Ok(Algorithm::DatafrogOpt(Engine::Datafrog)),
97+
"datafrogopt-souffle" => Ok(Algorithm::DatafrogOpt(Engine::Souffle)),
8498
"locationinsensitive" => Ok(Algorithm::LocationInsensitive),
8599
"compare" => Ok(Algorithm::Compare),
86100
"hybrid" => Ok(Algorithm::Hybrid),
87-
_ => Err(String::from(
88-
"valid values: Naive, DatafrogOpt, LocationInsensitive, Compare, Hybrid",
89-
)),
101+
_ => Err(format!("valid values: {}", Self::variants().join(", "))),
90102
}
91103
}
92104
}
@@ -175,6 +187,33 @@ impl<T: FactTypes> Output<T> {
175187
/// partial results can also be stored in the context, so that the following
176188
/// variant can use it to prune its own input data
177189
pub fn compute(all_facts: &AllFacts<T>, algorithm: Algorithm, dump_enabled: bool) -> Self {
190+
if algorithm.engine() == Engine::Souffle {
191+
let name = algorithm
192+
.souffle_name()
193+
.expect("Algorithm does not have Soufflé version");
194+
let facts = polonius_souffle::run_from_facts(name, &all_facts);
195+
196+
let mut out = Output::new(dump_enabled);
197+
198+
for tuple in facts.get("errors").unwrap().iter() {
199+
let [loan, location]: [u32; 2] = tuple.try_into().unwrap();
200+
out.errors
201+
.entry(T::Point::from(location))
202+
.or_default()
203+
.push(T::Loan::from(loan));
204+
}
205+
206+
for tuple in facts.get("subset_errors").unwrap().iter() {
207+
let [origin1, origin2, location]: [u32; 3] = tuple.try_into().unwrap();
208+
out.subset_errors
209+
.entry(T::Point::from(location))
210+
.or_default()
211+
.insert((T::Origin::from(origin1), T::Origin::from(origin2)));
212+
}
213+
214+
return out;
215+
}
216+
178217
let mut result = Output::new(dump_enabled);
179218

180219
// TODO: remove all the cloning thereafter, but that needs to be done in concert with rustc
@@ -317,8 +356,8 @@ impl<T: FactTypes> Output<T> {
317356

318357
(potential_errors, potential_subset_errors)
319358
}
320-
Algorithm::Naive => naive::compute(&ctx, &mut result),
321-
Algorithm::DatafrogOpt => datafrog_opt::compute(&ctx, &mut result),
359+
Algorithm::Naive(_) => naive::compute(&ctx, &mut result),
360+
Algorithm::DatafrogOpt(_) => datafrog_opt::compute(&ctx, &mut result),
322361
Algorithm::Hybrid => {
323362
// Execute the fast `LocationInsensitive` computation as a pre-pass:
324363
// if it finds no possible errors, we don't need to do the more complex

src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use log::{error, Level, LevelFilter, Metadata, Record, SetLoggerError};
22
use pico_args as pico;
3-
use polonius_engine::Algorithm;
3+
use polonius_engine::{Algorithm, Engine};
44
use std::env;
55
use std::error;
66
use std::fmt;

src/test.rs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ use crate::test_util::{
99
assert_checkers_match, assert_equal, assert_outputs_match, location_insensitive_checker_for,
1010
naive_checker_for, opt_checker_for,
1111
};
12-
use polonius_engine::Algorithm;
12+
use polonius_engine::{Algorithm, Engine};
1313
use rustc_hash::FxHashMap;
1414
use std::error::Error;
1515
use std::path::Path;
1616

1717
fn test_facts(all_facts: &AllFacts, algorithms: &[Algorithm]) {
18-
let naive = Output::compute(all_facts, Algorithm::Naive, true);
18+
let naive = Output::compute(all_facts, Algorithm::Naive(Engine::Datafrog), true);
1919

2020
// Check that the "naive errors" are a subset of the "insensitive
2121
// ones".
@@ -111,7 +111,7 @@ macro_rules! tests {
111111

112112
#[test]
113113
fn datafrog_opt() -> Result<(), Box<dyn Error>> {
114-
test_fn($dir, $fn, Algorithm::DatafrogOpt)
114+
test_fn($dir, $fn, Algorithm::DatafrogOpt(Engine::Datafrog))
115115
}
116116
}
117117
)*
@@ -168,7 +168,7 @@ fn test_sensitive_passes_issue_47680() -> Result<(), Box<dyn Error>> {
168168
.join("main");
169169
let tables = &mut intern::InternerTables::new();
170170
let all_facts = tab_delim::load_tab_delimited_facts(tables, &facts_dir)?;
171-
let sensitive = Output::compute(&all_facts, Algorithm::DatafrogOpt, false);
171+
let sensitive = Output::compute(&all_facts, Algorithm::DatafrogOpt(Engine::Datafrog), false);
172172

173173
assert!(sensitive.errors.is_empty());
174174

@@ -201,15 +201,15 @@ fn no_subset_symmetries_exist() -> Result<(), Box<dyn Error>> {
201201
false
202202
};
203203

204-
let naive = Output::compute(&all_facts, Algorithm::Naive, true);
204+
let naive = Output::compute(&all_facts, Algorithm::Naive(Engine::Datafrog), true);
205205
assert!(!subset_symmetries_exist(&naive));
206206

207207
// FIXME: the issue-47680 dataset is suboptimal here as DatafrogOpt does not
208208
// produce subset symmetries for it. It does for clap, and it was used to manually verify
209209
// that the assert in verbose mode didn't trigger. Therefore, switch to this dataset
210210
// whenever it's fast enough to be enabled in tests, or somehow create a test facts program
211211
// or reduce it from clap.
212-
let opt = Output::compute(&all_facts, Algorithm::DatafrogOpt, true);
212+
let opt = Output::compute(&all_facts, Algorithm::DatafrogOpt(Engine::Datafrog), true);
213213
assert!(!subset_symmetries_exist(&opt));
214214
Ok(())
215215
}
@@ -323,8 +323,8 @@ fn smoke_test_errors() {
323323
let facts = tab_delim::load_tab_delimited_facts(tables, &facts_dir).expect("facts");
324324

325325
let location_insensitive = Output::compute(&facts, Algorithm::LocationInsensitive, true);
326-
let naive = Output::compute(&facts, Algorithm::Naive, true);
327-
let opt = Output::compute(&facts, Algorithm::DatafrogOpt, true);
326+
let naive = Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true);
327+
let opt = Output::compute(&facts, Algorithm::DatafrogOpt(Engine::Datafrog), true);
328328

329329
// We have to find errors with every analysis
330330
assert!(
@@ -399,7 +399,8 @@ fn var_live_in_single_block() {
399399
let mut tables = intern::InternerTables::new();
400400
let facts = parse_from_program(program, &mut tables).expect("Parsing failure");
401401

402-
let liveness = Output::compute(&facts, Algorithm::Naive, true).var_live_on_entry;
402+
let liveness =
403+
Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true).var_live_on_entry;
403404
println!("Registered liveness data: {:?}", liveness);
404405
for (point, variables) in liveness.iter() {
405406
println!("{:?} has live variables: {:?}", point, variables);
@@ -433,7 +434,8 @@ fn var_live_in_successor_propagates_to_predecessor() {
433434
let mut tables = intern::InternerTables::new();
434435
let facts = parse_from_program(program, &mut tables).expect("Parsing failure");
435436

436-
let liveness = Output::compute(&facts, Algorithm::Naive, true).var_live_on_entry;
437+
let liveness =
438+
Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true).var_live_on_entry;
437439
println!("Registered liveness data: {:?}", liveness);
438440
println!("CFG: {:?}", facts.cfg_edge);
439441
for (point, variables) in liveness.iter() {
@@ -470,7 +472,7 @@ fn var_live_in_successor_killed_by_reassignment() {
470472
let mut tables = intern::InternerTables::new();
471473
let facts = parse_from_program(program, &mut tables).expect("Parsing failure");
472474

473-
let result = Output::compute(&facts, Algorithm::Naive, true);
475+
let result = Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true);
474476
println!("result: {:#?}", result);
475477
let liveness = result.var_live_on_entry;
476478
println!("CFG: {:#?}", facts.cfg_edge);
@@ -531,7 +533,7 @@ fn var_drop_used_simple() {
531533
let mut tables = intern::InternerTables::new();
532534
let facts = parse_from_program(program, &mut tables).expect("Parsing failure");
533535

534-
let result = Output::compute(&facts, Algorithm::Naive, true);
536+
let result = Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true);
535537
println!("result: {:#?}", result);
536538
let liveness = result.var_drop_live_on_entry;
537539
println!("CFG: {:#?}", facts.cfg_edge);
@@ -573,7 +575,7 @@ fn var_drop_used_simple() {
573575
fn illegal_subset_error() {
574576
let program = r"
575577
placeholders { 'a, 'b }
576-
578+
577579
block B0 {
578580
// creates a transitive `'b: 'a` subset
579581
loan_issued_at('x, L0),
@@ -638,7 +640,7 @@ fn transitive_known_subset() {
638640
let program = r"
639641
placeholders { 'a, 'b, 'c }
640642
known_subsets { 'a: 'b, 'b: 'c }
641-
643+
642644
block B0 {
643645
loan_issued_at('x, L0),
644646
outlives('a: 'x),
@@ -670,7 +672,7 @@ fn transitive_illegal_subset_error() {
670672
let program = r"
671673
placeholders { 'a, 'b, 'c }
672674
known_subsets { 'a: 'b }
673-
675+
674676
block B0 {
675677
// this transitive `'a: 'b` subset is already known
676678
loan_issued_at('x, L0),
@@ -679,7 +681,7 @@ fn transitive_illegal_subset_error() {
679681
680682
// creates unknown transitive subsets:
681683
// - `'b: 'c`
682-
// - and therefore `'a: 'c`
684+
// - and therefore `'a: 'c`
683685
loan_issued_at('y, L1),
684686
outlives('b: 'y),
685687
outlives('y: 'c);
@@ -721,15 +723,15 @@ fn successes_in_subset_relations_dataset() {
721723
let tables = &mut intern::InternerTables::new();
722724
let facts = tab_delim::load_tab_delimited_facts(tables, &facts_dir).expect("facts");
723725

724-
let naive = Output::compute(&facts, Algorithm::Naive, true);
726+
let naive = Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true);
725727
assert!(naive.errors.is_empty());
726728
assert!(naive.subset_errors.is_empty());
727729

728730
let insensitive = Output::compute(&facts, Algorithm::LocationInsensitive, true);
729731
assert!(insensitive.errors.is_empty());
730732
assert!(insensitive.subset_errors.is_empty());
731733

732-
let opt = Output::compute(&facts, Algorithm::DatafrogOpt, true);
734+
let opt = Output::compute(&facts, Algorithm::DatafrogOpt(Engine::Datafrog), true);
733735
assert!(opt.errors.is_empty());
734736
assert!(opt.subset_errors.is_empty());
735737
}
@@ -746,7 +748,7 @@ fn errors_in_subset_relations_dataset() {
746748
let facts = tab_delim::load_tab_delimited_facts(tables, &facts_dir).expect("facts");
747749

748750
// this function has no illegal access errors, but one subset error, over 3 points
749-
let naive = Output::compute(&facts, Algorithm::Naive, true);
751+
let naive = Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true);
750752
assert!(naive.errors.is_empty());
751753
assert_eq!(naive.subset_errors.len(), 3);
752754

@@ -782,7 +784,7 @@ fn errors_in_subset_relations_dataset() {
782784
assert!(insensitive_subset_errors.contains(&expected_subset_error));
783785

784786
// And the optimized analysis results should be the same as the naive one's.
785-
let opt = Output::compute(&facts, Algorithm::Naive, true);
787+
let opt = Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true);
786788
assert_outputs_match(&naive, &opt);
787789
}
788790

@@ -802,7 +804,7 @@ fn successes_in_move_errors_dataset() {
802804
let tables = &mut intern::InternerTables::new();
803805
let facts = tab_delim::load_tab_delimited_facts(tables, &facts_dir).expect("facts");
804806

805-
let naive = Output::compute(&facts, Algorithm::Naive, true);
807+
let naive = Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true);
806808
assert!(naive.errors.is_empty());
807809
assert!(naive.subset_errors.is_empty());
808810
assert!(naive.move_errors.is_empty());
@@ -812,7 +814,7 @@ fn successes_in_move_errors_dataset() {
812814
assert!(insensitive.subset_errors.is_empty());
813815
assert!(insensitive.move_errors.is_empty());
814816

815-
let opt = Output::compute(&facts, Algorithm::DatafrogOpt, true);
817+
let opt = Output::compute(&facts, Algorithm::DatafrogOpt(Engine::Datafrog), true);
816818
assert!(opt.errors.is_empty());
817819
assert!(opt.subset_errors.is_empty());
818820
assert!(opt.move_errors.is_empty());
@@ -829,7 +831,7 @@ fn basic_move_error() {
829831
let tables = &mut intern::InternerTables::new();
830832
let facts = tab_delim::load_tab_delimited_facts(tables, &facts_dir).expect("facts");
831833

832-
let result = Output::compute(&facts, Algorithm::Naive, true);
834+
let result = Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true);
833835
assert!(result.errors.is_empty());
834836
assert!(result.subset_errors.is_empty());
835837

@@ -853,7 +855,7 @@ fn conditional_init() {
853855
let tables = &mut intern::InternerTables::new();
854856
let facts = tab_delim::load_tab_delimited_facts(tables, &facts_dir).expect("facts");
855857

856-
let result = Output::compute(&facts, Algorithm::Naive, true);
858+
let result = Output::compute(&facts, Algorithm::Naive(Engine::Datafrog), true);
857859
assert!(result.errors.is_empty());
858860
assert!(result.subset_errors.is_empty());
859861

src/test_util.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![cfg(test)]
22

3-
use polonius_engine::{Algorithm, AllFacts, Output};
3+
use polonius_engine::{Algorithm, AllFacts, Engine, Output};
44
use std::fmt::Debug;
55

66
use crate::facts::LocalFacts;
@@ -76,15 +76,15 @@ pub(crate) fn check_program(
7676
}
7777

7878
pub(crate) fn naive_checker_for(program: &str) -> FactChecker {
79-
check_program(program, Algorithm::Naive, true)
79+
check_program(program, Algorithm::Naive(Engine::Datafrog), true)
8080
}
8181

8282
pub(crate) fn location_insensitive_checker_for(program: &str) -> FactChecker {
8383
check_program(program, Algorithm::LocationInsensitive, true)
8484
}
8585

8686
pub(crate) fn opt_checker_for(program: &str) -> FactChecker {
87-
check_program(program, Algorithm::DatafrogOpt, true)
87+
check_program(program, Algorithm::DatafrogOpt(Engine::Datafrog), true)
8888
}
8989

9090
pub(crate) fn assert_checkers_match(checker_a: &FactChecker, checker_b: &FactChecker) {

0 commit comments

Comments
 (0)