66//!
77//! Optimization runs between execution steps, which is what enables cross-step optimizations:
88//! after a child is decoded, new `reduce_parent` rules may match that were previously blocked.
9+ //!
10+ //! There are two entry points:
11+ //!
12+ //! * [`ArrayOptimizer::optimize`] — runs the static rules only (the child encoding's
13+ //! `PARENT_RULES`). It does not require an execution context and is used by helpers like
14+ //! `ArrayBuiltins::cast` and `ArrayRef::slice` that build wrapped expressions and need them
15+ //! normalized inline.
16+ //! * [`ArrayOptimizer::optimize_ctx`] — runs the static rules and additionally consults the
17+ //! session's [`OptimizerSession`] registry keyed by `(parent_encoding_id, child_encoding_id)`
18+ //! before each `reduce_parent` step. The execute loop calls this entry point so plugin-
19+ //! registered parent-reduce rules fire during execution.
20+
21+ use std:: sync:: Arc ;
922
1023use vortex_error:: VortexResult ;
1124use vortex_error:: vortex_bail;
25+ use vortex_session:: SessionExt ;
1226
1327use crate :: ArrayRef ;
28+ use crate :: ExecutionCtx ;
29+ use crate :: optimizer:: session:: OptimizerSession ;
1430
1531pub mod rules;
32+ pub mod session;
33+
34+ /// Pluggable parent-reduce function signature used by [`OptimizerSession`].
35+ ///
36+ /// A function of this type rewrites the parent array that holds `child` at `child_idx`, given
37+ /// the child itself and its parent. Returns `Ok(None)` when the function doesn't apply.
38+ ///
39+ /// Registered under `(parent_encoding_id, child_encoding_id)`; callers downcast the erased
40+ /// `child`/`parent` to their expected types before applying logic.
41+ pub type ReduceParentFn =
42+ fn ( child : & ArrayRef , parent : & ArrayRef , child_idx : usize ) -> VortexResult < Option < ArrayRef > > ;
1643
1744/// Extension trait for optimizing array trees using reduce/reduce_parent rules.
1845pub trait ArrayOptimizer {
19- /// Optimize the root array node only by running reduce and reduce_parent rules to fixpoint.
46+ /// Optimize the root array node by running reduce and reduce_parent rules to fixpoint.
47+ ///
48+ /// Uses only the child encoding's static `PARENT_RULES`. Use [`Self::optimize_ctx`] from
49+ /// inside the execute loop to also consult the session-scoped [`OptimizerSession`] registry.
2050 fn optimize ( & self ) -> VortexResult < ArrayRef > ;
2151
22- /// Optimize the entire array tree recursively (root and all descendants).
52+ /// Like [`Self::optimize`], but additionally consults the session's [`OptimizerSession`]
53+ /// registry for each `(parent_encoding_id, child_encoding_id)` pair before the static
54+ /// vtable rules.
55+ fn optimize_ctx ( & self , ctx : & ExecutionCtx ) -> VortexResult < ArrayRef > ;
56+
57+ /// Optimize the entire array tree recursively (root and all descendants), static rules only.
2358 fn optimize_recursive ( & self ) -> VortexResult < ArrayRef > ;
2459}
2560
2661impl ArrayOptimizer for ArrayRef {
2762 fn optimize ( & self ) -> VortexResult < ArrayRef > {
28- Ok ( try_optimize ( self ) ?. unwrap_or_else ( || self . clone ( ) ) )
63+ Ok ( try_optimize ( self , None ) ?. unwrap_or_else ( || self . clone ( ) ) )
64+ }
65+
66+ fn optimize_ctx ( & self , ctx : & ExecutionCtx ) -> VortexResult < ArrayRef > {
67+ Ok ( try_optimize ( self , Some ( ctx) ) ?. unwrap_or_else ( || self . clone ( ) ) )
2968 }
3069
3170 fn optimize_recursive ( & self ) -> VortexResult < ArrayRef > {
3271 Ok ( try_optimize_recursive ( self ) ?. unwrap_or_else ( || self . clone ( ) ) )
3372 }
3473}
3574
36- fn try_optimize ( array : & ArrayRef ) -> VortexResult < Option < ArrayRef > > {
75+ /// Resolve a pluggable [`ReduceParentFn`] for `(parent, child)` from the session registry.
76+ ///
77+ /// Returns `None` when no [`OptimizerSession`] is registered, or no function is registered under
78+ /// `(parent.encoding_id(), child.encoding_id())`. The returned `Arc` is owned so the caller is
79+ /// free to drop the session borrow before invoking it.
80+ fn plugin_reduce_parent (
81+ ctx : & ExecutionCtx ,
82+ parent : & ArrayRef ,
83+ child : & ArrayRef ,
84+ ) -> Option < Arc < ReduceParentFn > > {
85+ ctx. session ( ) . get_opt :: < OptimizerSession > ( ) . and_then ( |s| {
86+ s. registry ( )
87+ . find :: < ReduceParentFn > ( parent. encoding_id ( ) , child. encoding_id ( ) )
88+ } )
89+ }
90+
91+ fn try_optimize ( array : & ArrayRef , ctx : Option < & ExecutionCtx > ) -> VortexResult < Option < ArrayRef > > {
3792 let mut current_array = array. clone ( ) ;
3893 let mut any_optimizations = false ;
3994
@@ -55,6 +110,17 @@ fn try_optimize(array: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
55110 // Its important to take all slots here, as `current_array` can change inside the loop.
56111 for ( slot_idx, slot) in current_array. slots ( ) . iter ( ) . enumerate ( ) {
57112 let Some ( child) = slot else { continue } ;
113+
114+ // Registry-based override: tried before the child encoding's static PARENT_RULES.
115+ if let Some ( ctx) = ctx
116+ && let Some ( plugin) = plugin_reduce_parent ( ctx, & current_array, child)
117+ && let Some ( new_array) = plugin ( child, & current_array, slot_idx) ?
118+ {
119+ current_array = new_array;
120+ any_optimizations = true ;
121+ continue ' outer;
122+ }
123+
58124 if let Some ( new_array) = child. reduce_parent ( & current_array, slot_idx) ? {
59125 // If the parent was replaced, then we attempt to reduce it again.
60126 current_array = new_array;
@@ -80,7 +146,7 @@ fn try_optimize_recursive(array: &ArrayRef) -> VortexResult<Option<ArrayRef>> {
80146 let mut current_array = array. clone ( ) ;
81147 let mut any_optimizations = false ;
82148
83- if let Some ( new_array) = try_optimize ( & current_array) ? {
149+ if let Some ( new_array) = try_optimize ( & current_array, None ) ? {
84150 current_array = new_array;
85151 any_optimizations = true ;
86152 }
0 commit comments