@@ -36,6 +36,13 @@ enum FunctionModifier {
3636 Value ( Value ) ,
3737}
3838
39+ #[ derive( Debug , Clone ) ]
40+ struct Optimized {
41+ data : Value ,
42+ processed : BTreeSet < Ref < Rule > > ,
43+ processed_paths : Value ,
44+ }
45+
3946#[ derive( Debug , Clone ) ]
4047pub struct Interpreter {
4148 modules : Vec < Ref < Module > > ,
@@ -73,6 +80,9 @@ pub struct Interpreter {
7380 gather_prints : bool ,
7481 prints : Vec < String > ,
7582 rule_paths : Set < String > ,
83+ is_constant_folding : bool ,
84+ optimized : Option < Optimized > ,
85+ has_side_effects : bool ,
7686}
7787
7888impl Default for Interpreter {
@@ -197,6 +207,9 @@ impl Interpreter {
197207 gather_prints : false ,
198208 prints : Vec :: default ( ) ,
199209 rule_paths : Set :: new ( ) ,
210+ is_constant_folding : false ,
211+ has_side_effects : false ,
212+ optimized : None ,
200213 }
201214 }
202215
@@ -255,9 +268,16 @@ impl Interpreter {
255268 }
256269
257270 pub fn clean_internal_evaluation_state ( & mut self ) {
258- self . data = self . init_data . clone ( ) ;
259- self . processed . clear ( ) ;
260- self . processed_paths = Value :: new_object ( ) ;
271+ if let Some ( optimized) = & self . optimized {
272+ self . data = optimized. data . clone ( ) ;
273+ // TODO: Check use of processed and processed_paths
274+ self . processed = optimized. processed . clone ( ) ;
275+ self . processed_paths = optimized. processed_paths . clone ( ) ;
276+ } else {
277+ self . data = self . init_data . clone ( ) ;
278+ self . processed . clear ( ) ;
279+ self . processed_paths = Value :: new_object ( ) ;
280+ }
261281 self . loop_var_values . clear ( ) ;
262282 self . scopes = vec ! [ Scope :: new( ) ] ;
263283 self . contexts = vec ! [ ] ;
@@ -1281,6 +1301,7 @@ impl Interpreter {
12811301 // Mark modified rules as processed.
12821302 if let Some ( rules) = self . rules . get ( & target) {
12831303 for r in rules {
1304+ // TODO: check if this is correct for constant folding
12841305 self . processed . insert ( r. clone ( ) ) ;
12851306 }
12861307 }
@@ -1507,6 +1528,15 @@ impl Interpreter {
15071528
15081529 self . scopes . pop ( ) ;
15091530
1531+ // When constant folding, evaluate one iteration of the loop so as to
1532+ // fold constants within the loop body. But return false to prevent
1533+ // the loop itself from ptentially being treated as a constant.
1534+ //if self.is_constant_folding {
1535+ // return Ok(false);
1536+ //}
1537+ // Ignore previous comment.
1538+ // TODO: Implement expression value caching for constant values.
1539+
15101540 // Return true if at least on iteration returned true
15111541 Ok ( result)
15121542 }
@@ -2055,6 +2085,8 @@ impl Interpreter {
20552085 }
20562086
20572087 fn eval_array_compr ( & mut self , term : & ExprRef , query : & Ref < Query > ) -> Result < Value > {
2088+ let hse = self . has_side_effects ;
2089+ self . has_side_effects = false ;
20582090 // Push new context
20592091 self . contexts . push ( Context {
20602092 output_expr : Some ( term. clone ( ) ) ,
@@ -2066,13 +2098,21 @@ impl Interpreter {
20662098 // Evaluate body first.
20672099 self . eval_query ( query) ?;
20682100
2101+ self . has_side_effects = hse || self . has_side_effects ;
2102+ if self . is_constant_folding && self . has_side_effects {
2103+ return Ok ( Value :: Undefined ) ;
2104+ }
2105+
20692106 match self . contexts . pop ( ) {
20702107 Some ( ctx) => Ok ( ctx. value ) ,
20712108 None => bail ! ( "internal error: context already popped" ) ,
20722109 }
20732110 }
20742111
20752112 fn eval_set_compr ( & mut self , term : & ExprRef , query : & Ref < Query > ) -> Result < Value > {
2113+ let hse = self . has_side_effects ;
2114+ self . has_side_effects = false ;
2115+
20762116 // Push new context
20772117 self . contexts . push ( Context {
20782118 output_expr : Some ( term. clone ( ) ) ,
@@ -2083,6 +2123,11 @@ impl Interpreter {
20832123
20842124 self . eval_query ( query) ?;
20852125
2126+ self . has_side_effects = hse || self . has_side_effects ;
2127+ if self . is_constant_folding && self . has_side_effects {
2128+ return Ok ( Value :: Undefined ) ;
2129+ }
2130+
20862131 match self . contexts . pop ( ) {
20872132 Some ( ctx) => Ok ( ctx. value ) ,
20882133 None => bail ! ( "internal error: context already popped" ) ,
@@ -2095,6 +2140,9 @@ impl Interpreter {
20952140 value : & ExprRef ,
20962141 query : & Ref < Query > ,
20972142 ) -> Result < Value > {
2143+ let hse = self . has_side_effects ;
2144+ self . has_side_effects = false ;
2145+
20982146 // Push new context
20992147 self . contexts . push ( Context {
21002148 key_expr : Some ( key. clone ( ) ) ,
@@ -2106,6 +2154,11 @@ impl Interpreter {
21062154
21072155 self . eval_query ( query) ?;
21082156
2157+ self . has_side_effects = hse || self . has_side_effects ;
2158+ if self . is_constant_folding && self . has_side_effects {
2159+ return Ok ( Value :: Undefined ) ;
2160+ }
2161+
21092162 match self . contexts . pop ( ) {
21102163 Some ( ctx) => Ok ( ctx. value ) ,
21112164 None => bail ! ( "internal error: context already popped" ) ,
@@ -2137,6 +2190,12 @@ impl Interpreter {
21372190 return Ok ( Value :: Undefined ) ;
21382191 }
21392192
2193+ // TODO: Allow builtins that can be constant folded
2194+ if self . is_constant_folding {
2195+ self . has_side_effects = true ;
2196+ return Ok ( Value :: Undefined ) ;
2197+ }
2198+
21402199 let cache = builtins:: must_cache ( name) ;
21412200 if let Some ( name) = & cache {
21422201 if let Some ( v) = self . builtins_cache . get ( & ( name, args. clone ( ) ) ) {
@@ -2316,6 +2375,10 @@ impl Interpreter {
23162375 extension = Some ( ext) ;
23172376 ( & empty, None )
23182377 } else if fcn_path == "print" {
2378+ if self . is_constant_folding {
2379+ // Ignore side-effects in constant folding.
2380+ return Ok ( Value :: Undefined ) ;
2381+ }
23192382 return self . eval_print ( span, params, param_values) ;
23202383 }
23212384 // Look up builtin function.
@@ -2344,6 +2407,11 @@ impl Interpreter {
23442407 }
23452408
23462409 if let Some ( ( nargs, ext) ) = extension {
2410+ if self . is_constant_folding {
2411+ // Extensions are not supported in constant folding.
2412+ return Ok ( Value :: Undefined ) ;
2413+ }
2414+
23472415 if param_values. len ( ) != * nargs as usize {
23482416 bail ! ( span. error( "incorrect number of parameters supplied to extension" ) ) ;
23492417 }
@@ -2621,14 +2689,20 @@ impl Interpreter {
26212689 }
26222690
26232691 // Evaluate the associated default rules after non-default rules
2624- if let Some ( rules) = self . default_rules . get ( & path) {
2625- matched = true ;
2626- for ( r, _) in rules. clone ( ) {
2627- if !self . processed . contains ( & r) {
2628- let module = self . get_rule_module ( & r) ?;
2629- let prev_module = self . set_current_module ( Some ( module) ) ?;
2630- self . eval_default_rule ( & r) ?;
2631- self . set_current_module ( prev_module) ?;
2692+ if self . is_constant_folding {
2693+ // We don't want to evaluate default rules at this point.
2694+ // A non default rule with the same path could have failed due to it needing input.
2695+ // TODO: Cleanup rule evaluation bookmarking.
2696+ } else {
2697+ if let Some ( rules) = self . default_rules . get ( & path) {
2698+ matched = true ;
2699+ for ( r, _) in rules. clone ( ) {
2700+ if !self . processed . contains ( & r) {
2701+ let module = self . get_rule_module ( & r) ?;
2702+ let prev_module = self . set_current_module ( Some ( module) ) ?;
2703+ self . eval_default_rule ( & r) ?;
2704+ self . set_current_module ( prev_module) ?;
2705+ }
26322706 }
26332707 }
26342708 }
@@ -2660,6 +2734,12 @@ impl Interpreter {
26602734
26612735 fn mark_processed ( & mut self , path : & [ & str ] ) -> Result < ( ) > {
26622736 let obj = self . processed_paths . make_or_get_value_mut ( path) ?;
2737+
2738+ if self . is_constant_folding && obj == & Value :: Undefined {
2739+ // If constant folding, then do not register undefined values.
2740+ return Ok ( ( ) ) ;
2741+ }
2742+
26632743 if obj == & Value :: Undefined {
26642744 * obj = Value :: new_object ( ) ;
26652745 }
@@ -2677,6 +2757,11 @@ impl Interpreter {
26772757
26782758 // Handle input.
26792759 if name. text ( ) == "input" {
2760+ if self . is_constant_folding {
2761+ // When constant folding, expressions cannot depend on input.
2762+ self . has_side_effects = true ;
2763+ return Ok ( Value :: Undefined ) ;
2764+ }
26802765 return Ok ( Self :: get_value_chained ( self . input . clone ( ) , fields) ) ;
26812766 }
26822767
@@ -3297,7 +3382,16 @@ impl Interpreter {
32973382 if let Ok ( mut comps) = self . eval_rule_ref ( refr) {
32983383 let mut full_path = package_components;
32993384 full_path. append ( & mut comps) ;
3300- self . update_rule_value ( span, full_path, Value :: new_set ( ) , true ) ?;
3385+ if value == Value :: Undefined && self . is_constant_folding {
3386+ // Do not create empty set if constant folding and rule failed to evaluate.
3387+ } else {
3388+ self . update_rule_value (
3389+ span,
3390+ full_path,
3391+ Value :: new_set ( ) ,
3392+ true ,
3393+ ) ?;
3394+ }
33013395 }
33023396 } else if is_object {
33033397 // Fetch the rule, ignoring the key.
@@ -3314,7 +3408,15 @@ impl Interpreter {
33143408 }
33153409 }
33163410 }
3317- self . processed . insert ( rule. clone ( ) ) ;
3411+
3412+ if self . is_constant_folding {
3413+ if value != Value :: Undefined {
3414+ // When constant folding, record only those rules that have successfully evaluated.
3415+ self . processed . insert ( rule. clone ( ) ) ;
3416+ }
3417+ } else {
3418+ self . processed . insert ( rule. clone ( ) ) ;
3419+ }
33183420 }
33193421 RuleHead :: Func {
33203422 refr, args, assign, ..
@@ -3392,7 +3494,10 @@ impl Interpreter {
33923494 let scopes = core:: mem:: take ( & mut self . scopes ) ;
33933495 let prev_module = self . set_current_module ( Some ( module. clone ( ) ) ) ?;
33943496
3497+ let hse = self . has_side_effects ;
3498+ self . has_side_effects = false ;
33953499 let res = self . eval_rule_impl ( module, rule) ;
3500+ self . has_side_effects = hse;
33963501
33973502 self . set_current_module ( prev_module) ?;
33983503 self . scopes = scopes;
@@ -3485,6 +3590,30 @@ impl Interpreter {
34853590 }
34863591 }
34873592
3593+ pub fn constant_fold ( & mut self ) -> Result < ( ) > {
3594+ self . is_constant_folding = true ;
3595+ self . optimized = None ;
3596+ self . clean_internal_evaluation_state ( ) ;
3597+ // TODO: Maybe use scheduler information to determine which rules to evaluate and in which order.
3598+ for module in & self . modules . clone ( ) {
3599+ for rule in & module. policy {
3600+ if let Rule :: Spec { head, .. } = rule. as_ref ( ) {
3601+ if matches ! ( & head, RuleHead :: Compr { .. } | RuleHead :: Set { .. } ) {
3602+ // Evaluate rule and ignore any errors.
3603+ let _ = self . eval_rule ( module, rule) ;
3604+ }
3605+ }
3606+ }
3607+ }
3608+ self . is_constant_folding = false ;
3609+ self . optimized = Some ( Optimized {
3610+ data : self . data . clone ( ) ,
3611+ processed : self . processed . clone ( ) ,
3612+ processed_paths : self . processed_paths . clone ( ) ,
3613+ } ) ;
3614+ Ok ( ( ) )
3615+ }
3616+
34883617 fn get_rule_path_components ( mut refr : & Ref < Expr > ) -> Result < Vec < Rc < str > > > {
34893618 let mut components: Vec < Rc < str > > = vec ! [ ] ;
34903619 loop {
0 commit comments