Skip to content

Commit 5294cdc

Browse files
Merge pull request #881 from saulshanabrook/codex/split-primitive-body-runtime-apis
Add primitive body typecheck helpers
2 parents a2da717 + a80b235 commit 5294cdc

5 files changed

Lines changed: 515 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Add a BigRat to-i64 primitive for integral rationals.
1010
- Add f64 exp, log, and sqrt primitives.
1111
- Add `RunReport::can_stop` so scheduler progress can be reported separately from database updates.
12+
- Add `EGraph::typecheck_expr_with_bindings_and_output`, `Core::eval_resolved_expr`, and `Core::apply_primitive` for body-defined primitive support, including normal command-path global rewrites for expressions typechecked through the helper.
1213
- Allow `unstable-fn` function containers to target primitive overloads.
1314
- Desugar `relation`s to `constructor`s to simplify the language and implementation. Relations no longer return unit `()` values.
1415
- Refactored API to use [`TermId`] more consistently instead of `Term` where possible, simplifying egglog code.

src/ast/remove_globals.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ fn replace_global_vars(expr: ResolvedExpr) -> ResolvedExpr {
7878
}
7979
}
8080

81-
fn remove_globals_expr(expr: ResolvedExpr) -> ResolvedExpr {
81+
pub(crate) fn remove_globals_expr(expr: ResolvedExpr) -> ResolvedExpr {
8282
expr.visit_exprs(&mut replace_global_vars)
8383
}
8484

src/exec_state.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ use crate::core_relations::{
3636
BaseValue, BaseValues, ContainerValue, ContainerValues, ExecutionState, ExternalFunctionId,
3737
TableId, Value,
3838
};
39+
use crate::{
40+
ast::{FunctionSubtype, Literal, ResolvedExpr},
41+
core::ResolvedCall,
42+
sort::{F, S},
43+
typechecking::FuncType,
44+
};
3945
use egglog_bridge::{ActionRegistry, TableAction};
4046

4147
/// The four contexts a primitive may run in, named after the
@@ -101,6 +107,26 @@ pub(crate) trait Internal<'a, 'db: 'a>: 'a {
101107
fn raw_exec_state(&mut self) -> &mut ExecutionState<'db> {
102108
self.es_mut()
103109
}
110+
fn apply_resolved_function(&mut self, _func: &FuncType, _args: &[Value]) -> Option<Value> {
111+
None
112+
}
113+
114+
fn apply_table_function(
115+
&mut self,
116+
subtype: FunctionSubtype,
117+
action: &TableAction,
118+
args: &[Value],
119+
) -> Option<Value> {
120+
match (subtype, self.ctx()) {
121+
(FunctionSubtype::Constructor, Context::Write | Context::Full) => {
122+
action.lookup_or_insert(self.es_mut(), args)
123+
}
124+
(FunctionSubtype::Custom, Context::Read | Context::Full) => {
125+
action.lookup(self.es(), args)
126+
}
127+
_ => None,
128+
}
129+
}
104130
}
105131

106132
/// Sealed accessor for the [`ActionRegistry`]. Implemented by every
@@ -197,6 +223,74 @@ pub trait Core<'a, 'db: 'a>: Internal<'a, 'db> {
197223
let mut pure = PureState::wrap(self.raw_exec_state(), ctx);
198224
fc.apply(&mut pure, args)
199225
}
226+
227+
/// Dispatch an already type-specialized primitive in the current
228+
/// call-site context.
229+
///
230+
/// This is a trusted evaluator hook, not an authorization boundary:
231+
/// callers must only pass primitives that were resolved for this same
232+
/// call-site context and whose surrounding expression has already been
233+
/// checked to require no more capability than this state wrapper provides.
234+
/// For example, a primitive body evaluator should typecheck the body under
235+
/// the runtime context it will register for, infer the body's required
236+
/// context, and register the primitive using the matching state wrapper.
237+
fn apply_primitive(
238+
&mut self,
239+
primitive: &crate::core::SpecializedPrimitive,
240+
args: &[Value],
241+
) -> Option<Value> {
242+
let id = primitive.external_id(self.ctx());
243+
self.es_mut().call_external_func(id, args)
244+
}
245+
246+
/// Evaluate an already typechecked expression in this primitive call
247+
/// context, using `bindings` for local variables.
248+
///
249+
/// Primitive calls dispatch through the current call-site context.
250+
/// Table-backed function calls follow the same capability split as
251+
/// `unstable-app`: custom functions require a read-capable state,
252+
/// and constructors require a write-capable state that can mint on miss.
253+
///
254+
/// This method assumes `expr` came from a trusted preparation pipeline that
255+
/// typechecked it for this exact runtime context and rejected expressions
256+
/// whose required capabilities exceed the receiver's state wrapper. It
257+
/// should not be used to evaluate arbitrary resolved expressions from a
258+
/// wider context inside a less-capable wrapper.
259+
fn eval_resolved_expr(
260+
&mut self,
261+
expr: &ResolvedExpr,
262+
bindings: &[(&str, Value)],
263+
) -> Option<Value> {
264+
match expr {
265+
ResolvedExpr::Lit(_, literal) => Some(match literal {
266+
Literal::Int(x) => self.base_to_value(*x),
267+
Literal::Float(x) => self.base_to_value(F::from(*x)),
268+
Literal::String(x) => self.base_to_value(S::new(x.clone())),
269+
Literal::Bool(x) => self.base_to_value(*x),
270+
Literal::Unit => self.base_to_value(()),
271+
}),
272+
ResolvedExpr::Var(_, resolved_var) => {
273+
assert!(
274+
!resolved_var.is_global_ref,
275+
"global variable {:?} reached direct expression evaluation before remove_globals",
276+
resolved_var.name
277+
);
278+
bindings
279+
.iter()
280+
.find_map(|(name, value)| (*name == resolved_var.name).then_some(*value))
281+
}
282+
ResolvedExpr::Call(_, resolved_call, children) => {
283+
let mut values = Vec::with_capacity(children.len());
284+
for child in children {
285+
values.push(self.eval_resolved_expr(child, bindings)?);
286+
}
287+
match resolved_call {
288+
ResolvedCall::Primitive(primitive) => self.apply_primitive(primitive, &values),
289+
ResolvedCall::Func(func) => self.apply_resolved_function(func, &values),
290+
}
291+
}
292+
}
293+
}
200294
}
201295

202296
/// Read-side methods — name-indexed table lookup. Implemented for
@@ -270,6 +364,15 @@ fn lookup_action<'r>(registry: &'r ActionRegistry, name: &str) -> &'r TableActio
270364
.unwrap_or_else(|| panic!("missing table action for table: {name}"))
271365
}
272366

367+
fn apply_registered_function<'a, 'db: 'a>(
368+
state: &mut impl RegistrySealed<'a, 'db>,
369+
func: &FuncType,
370+
args: &[Value],
371+
) -> Option<Value> {
372+
let action = lookup_action(state.registry(), &func.name).clone();
373+
state.apply_table_function(func.subtype, &action, args)
374+
}
375+
273376
// =====================================================================
274377
// The four wrapper types — plain structs, methods come from traits.
275378
// =====================================================================
@@ -422,6 +525,9 @@ impl<'a, 'db: 'a> Internal<'a, 'db> for ReadState<'a, 'db> {
422525
fn ctx(&self) -> Context {
423526
self.ctx
424527
}
528+
fn apply_resolved_function(&mut self, func: &FuncType, args: &[Value]) -> Option<Value> {
529+
apply_registered_function(self, func, args)
530+
}
425531
}
426532
impl<'a, 'db: 'a> RegistrySealed<'a, 'db> for ReadState<'a, 'db> {
427533
fn registry(&self) -> &ActionRegistry {
@@ -441,6 +547,9 @@ impl<'a, 'db: 'a> Internal<'a, 'db> for WriteState<'a, 'db> {
441547
fn ctx(&self) -> Context {
442548
self.ctx
443549
}
550+
fn apply_resolved_function(&mut self, func: &FuncType, args: &[Value]) -> Option<Value> {
551+
apply_registered_function(self, func, args)
552+
}
444553
}
445554
impl<'a, 'db: 'a> RegistrySealed<'a, 'db> for WriteState<'a, 'db> {
446555
fn registry(&self) -> &ActionRegistry {
@@ -460,6 +569,9 @@ impl<'a, 'db: 'a> Internal<'a, 'db> for FullState<'a, 'db> {
460569
fn ctx(&self) -> Context {
461570
self.ctx
462571
}
572+
fn apply_resolved_function(&mut self, func: &FuncType, args: &[Value]) -> Option<Value> {
573+
apply_registered_function(self, func, args)
574+
}
463575
}
464576
impl<'a, 'db: 'a> RegistrySealed<'a, 'db> for FullState<'a, 'db> {
465577
fn registry(&self) -> &ActionRegistry {

0 commit comments

Comments
 (0)