Skip to content

Commit ed38bf1

Browse files
committed
feat(hoist): pre-compute loop hoisting metadata at compilation time
Introduce a compiler pass that analyzes and pre-computes loop hoisting information during policy compilation. This hoisted metadata is stored in lookup tables and made available to downstream consumers: - interpreter: use HoistedLoop entries during evaluation (replaces runtime scanning) - type inference: can leverage pre-computed loop structure for type propagation - RVM compiler: will consume hoisting metadata for optimized bytecode generation Changes: - populate loop hoisting tables during engine preparation and query snippet execution - refactor eval_stmts_in_loop and eval_output_expr_in_loop to consume HoistedLoop directly - add helper methods for accessing loop expressions, collections, and indices from HoistedLoop - extend Lookup with get_checked and into_slots for safe query context access and merging Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
1 parent 57f2e77 commit ed38bf1

10 files changed

Lines changed: 1378 additions & 252 deletions

File tree

src/compiled_policy.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
use crate::ast::*;
5+
use crate::compiler::hoist::HoistedLoopsLookup;
56
use crate::engine::Engine;
67
use crate::scheduler::*;
78
use crate::utils::*;
@@ -212,4 +213,7 @@ pub(crate) struct CompiledPolicyData {
212213

213214
// The semantics of extensions ought to be changes to be more Clone friendly.
214215
pub(crate) extensions: Map<String, (u8, Rc<Box<dyn Extension>>)>,
216+
217+
// Pre-computed loop hoisting information
218+
pub(crate) loop_hoisting_table: HoistedLoopsLookup,
215219
}

src/compiler.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
//! Compiler-related functionality for Regorus.
5+
//!
6+
//! This module contains utilities and data structures used during
7+
//! the compilation phase to prepare policies for efficient execution.
8+
9+
pub mod context;
10+
pub mod hoist;

src/compiler/context.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
//! Compilation context types shared across compiler components.
5+
//!
6+
//! This module defines context structures used for tracking scope-level information
7+
//! during compilation and analysis phases. These types are designed to be compatible
8+
//! with both the interpreter's loop hoisting and the RVM compiler.
9+
10+
use crate::ast::ExprRef;
11+
use alloc::collections::BTreeSet;
12+
use alloc::string::{String, ToString};
13+
14+
/// Type of compilation context for tracking different scenarios
15+
#[derive(Debug, Clone, PartialEq, Eq)]
16+
pub enum ContextType {
17+
/// Rule context (Complete, PartialSet, PartialObject, or Function)
18+
Rule,
19+
/// Comprehension context (Array, Set, or Object)
20+
Comprehension,
21+
/// Every quantifier context
22+
Every,
23+
/// Query/statement context (no output expressions)
24+
Query,
25+
}
26+
27+
/// Context for tracking variable bindings and output expressions within a scope.
28+
/// Used during loop hoisting and compilation to determine what needs to be hoisted
29+
/// and what's already bound.
30+
///
31+
/// This design is compatible with RVM's CompilationContext for potential future unification.
32+
#[derive(Debug, Clone)]
33+
pub struct ScopeContext {
34+
/// Type of context (Rule, Comprehension, Every, Query)
35+
pub context_type: ContextType,
36+
37+
/// Variables that are bound in the current scope
38+
pub bound_vars: BTreeSet<String>,
39+
40+
/// Variables that are explicitly marked as unbound (from `some` declarations)
41+
pub unbound_vars: BTreeSet<String>,
42+
43+
/// Key expression from rule head or object comprehension (for output expression hoisting)
44+
pub key_expr: Option<ExprRef>,
45+
46+
/// Value expression from rule assignment or comprehension term (for output expression hoisting)
47+
pub value_expr: Option<ExprRef>,
48+
}
49+
50+
impl ScopeContext {
51+
/// Create a new context with Query type (default, no output expressions)
52+
pub fn new() -> Self {
53+
Self {
54+
context_type: ContextType::Query,
55+
bound_vars: BTreeSet::new(),
56+
unbound_vars: BTreeSet::new(),
57+
key_expr: None,
58+
value_expr: None,
59+
}
60+
}
61+
62+
/// Create a new context with a specific context type
63+
pub fn with_context_type(context_type: ContextType) -> Self {
64+
Self {
65+
context_type,
66+
bound_vars: BTreeSet::new(),
67+
unbound_vars: BTreeSet::new(),
68+
key_expr: None,
69+
value_expr: None,
70+
}
71+
}
72+
73+
/// Create a new context with output expressions (for rules and comprehensions)
74+
pub fn with_output_exprs(
75+
context_type: ContextType,
76+
key_expr: Option<ExprRef>,
77+
value_expr: Option<ExprRef>,
78+
) -> Self {
79+
Self {
80+
context_type,
81+
bound_vars: BTreeSet::new(),
82+
unbound_vars: BTreeSet::new(),
83+
key_expr,
84+
value_expr,
85+
}
86+
}
87+
88+
/// Create a child context that inherits bindings but overrides context type and output expressions
89+
pub fn child_with_output_exprs(
90+
&self,
91+
context_type: ContextType,
92+
key_expr: Option<ExprRef>,
93+
value_expr: Option<ExprRef>,
94+
) -> Self {
95+
Self {
96+
context_type,
97+
bound_vars: self.bound_vars.clone(),
98+
unbound_vars: self.unbound_vars.clone(),
99+
key_expr,
100+
value_expr,
101+
}
102+
}
103+
104+
/// Add a variable to the bound set
105+
pub fn bind_variable(&mut self, var_name: &str) {
106+
if var_name != "_" {
107+
self.bound_vars.insert(var_name.to_string());
108+
self.unbound_vars.remove(var_name);
109+
}
110+
}
111+
112+
/// Mark a variable as unbound
113+
pub fn add_unbound_variable(&mut self, var_name: &str) {
114+
if var_name != "_" && !self.bound_vars.contains(var_name) {
115+
self.unbound_vars.insert(var_name.to_string());
116+
}
117+
}
118+
119+
/// Check if a variable is known to be unbound
120+
pub fn is_unbound(&self, var_name: &str) -> bool {
121+
self.unbound_vars.contains(var_name)
122+
}
123+
124+
/// Check if we can determine that a variable should be treated as a loop iterator
125+
/// (either it's unbound or explicitly marked as such)
126+
pub fn should_hoist_as_loop(&self, var_name: &str) -> bool {
127+
if var_name == "_" {
128+
true
129+
} else if self.is_unbound(var_name) {
130+
true
131+
} else {
132+
// Treat variables that haven't been bound in this scope as potential loop iterators
133+
!self.bound_vars.contains(var_name)
134+
}
135+
}
136+
137+
/// Create a child context inheriting parent bindings, output expressions, and context type
138+
pub fn child(&self) -> Self {
139+
Self {
140+
context_type: self.context_type.clone(),
141+
bound_vars: self.bound_vars.clone(),
142+
unbound_vars: self.unbound_vars.clone(),
143+
key_expr: self.key_expr.clone(),
144+
value_expr: self.value_expr.clone(),
145+
}
146+
}
147+
}
148+
149+
impl Default for ScopeContext {
150+
fn default() -> Self {
151+
Self::new()
152+
}
153+
}

0 commit comments

Comments
 (0)