From 1195b83aa98982f7a6f54901dcdabd76076ed98a Mon Sep 17 00:00:00 2001 From: Joe Isaacs Date: Tue, 16 Jun 2026 13:55:18 +0100 Subject: [PATCH 1/5] u Signed-off-by: Joe Isaacs --- vortex-array/src/executor.rs | 41 ++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/vortex-array/src/executor.rs b/vortex-array/src/executor.rs index d6070ac1a4d..b788d0db0bd 100644 --- a/vortex-array/src/executor.rs +++ b/vortex-array/src/executor.rs @@ -26,7 +26,6 @@ use vortex_error::VortexResult; use vortex_error::vortex_bail; use vortex_error::vortex_ensure; use vortex_error::vortex_panic; -use vortex_session::Ref; use vortex_session::SessionExt; use vortex_session::VortexSession; @@ -164,6 +163,13 @@ impl ArrayRef { let mut stack: Vec = Vec::new(); let max_iterations = max_iterations(); + // Steps 2a/2b both route through execute_parent_for_child so a registered + // ArrayKernels parent kernel fires whether the parent is suspended on the stack + // (2a) or is the current array (2b). The kernels registry is stable for the whole + // execution, so fetch it once here and share across every iteration. + let tmp_session = ctx.session().clone(); + let kernels = tmp_session.get::(); + for _ in 0..max_iterations { let is_done = stack .last() @@ -198,8 +204,13 @@ impl ArrayRef { // would be lost when we restore frame.parent_builder. if current_builder.is_none() && let Some(frame) = stack.last() - && let Some(result) = - current_array.execute_parent(&frame.parent_array, frame.slot_idx, ctx)? + && let Some(result) = execute_parent_for_child( + &frame.parent_array, + ¤t_array, + frame.slot_idx, + &kernels, + ctx, + )? { ctx.log(format_args!( "execute_parent (stack) rewrote {} -> {}", @@ -213,7 +224,7 @@ impl ArrayRef { // Step 2b: execute_parent against current_array's own children. if current_builder.is_none() - && let Some(rewritten) = try_execute_parent(¤t_array, ctx)? + && let Some(rewritten) = try_execute_parent(¤t_array, &kernels, ctx)? { ctx.log(format_args!( "execute_parent rewrote {} -> {}", @@ -425,12 +436,12 @@ impl Executable for ArrayRef { } let tmp_session = ctx.session().clone(); - let kernels = tmp_session.get_opt::(); + let kernels = tmp_session.get::(); for (slot_idx, slot) in array.slots().iter().enumerate() { let Some(child) = slot else { continue }; if let Some(executed_parent) = - execute_parent_for_child(&array, child, slot_idx, kernels.as_ref(), ctx)? + execute_parent_for_child(&array, child, slot_idx, &kernels, ctx)? { ctx.log(format_args!( "execute_parent: slot[{}]({}) rewrote {} -> {}", @@ -542,13 +553,10 @@ fn execute_parent_for_child( parent: &ArrayRef, child: &ArrayRef, slot_idx: usize, - kernels: Option<&Ref>, + kernels: &ArrayKernels, ctx: &mut ExecutionCtx, ) -> VortexResult> { - if let Some(kernels) = kernels - && let Some(plugins) = - kernels.find_execute_parent(parent.encoding_id(), child.encoding_id()) - { + if let Some(plugins) = kernels.find_execute_parent(parent.encoding_id(), child.encoding_id()) { for plugin in plugins.as_ref() { if let Some(result) = plugin(child, parent, slot_idx, ctx)? { return Ok(Some(result)); @@ -560,14 +568,15 @@ fn execute_parent_for_child( } /// Try execute_parent on each occupied slot of the array. -fn try_execute_parent(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult> { - let tmp_session = ctx.session().clone(); - let kernels = tmp_session.get_opt::(); - +fn try_execute_parent( + array: &ArrayRef, + kernels: &ArrayKernels, + ctx: &mut ExecutionCtx, +) -> VortexResult> { for (slot_idx, slot) in array.slots().iter().enumerate() { let Some(child) = slot else { continue }; if let Some(executed_parent) = - execute_parent_for_child(array, child, slot_idx, kernels.as_ref(), ctx)? + execute_parent_for_child(array, child, slot_idx, kernels, ctx)? { ctx.log(format_args!( "execute_parent: slot[{}]({}) rewrote {} -> {}", From bb4f00fff561234cd1b5a9922f1ddd0bea011fab Mon Sep 17 00:00:00 2001 From: Joe Isaacs Date: Tue, 16 Jun 2026 13:57:43 +0100 Subject: [PATCH 2/5] u Signed-off-by: Joe Isaacs --- vortex-array/src/executor.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/vortex-array/src/executor.rs b/vortex-array/src/executor.rs index b788d0db0bd..67aeda0bd46 100644 --- a/vortex-array/src/executor.rs +++ b/vortex-array/src/executor.rs @@ -163,12 +163,8 @@ impl ArrayRef { let mut stack: Vec = Vec::new(); let max_iterations = max_iterations(); - // Steps 2a/2b both route through execute_parent_for_child so a registered - // ArrayKernels parent kernel fires whether the parent is suspended on the stack - // (2a) or is the current array (2b). The kernels registry is stable for the whole - // execution, so fetch it once here and share across every iteration. - let tmp_session = ctx.session().clone(); - let kernels = tmp_session.get::(); + let session = ctx.session().clone(); + let kernels = session.get::(); for _ in 0..max_iterations { let is_done = stack From 09395c7308eb4b796baca8f3bb634d2e6214a32c Mon Sep 17 00:00:00 2001 From: Joe Isaacs Date: Wed, 17 Jun 2026 12:05:33 +0100 Subject: [PATCH 3/5] u Signed-off-by: Joe Isaacs --- vortex-array/src/executor.rs | 118 ++++++++++++++++------------------- 1 file changed, 53 insertions(+), 65 deletions(-) diff --git a/vortex-array/src/executor.rs b/vortex-array/src/executor.rs index 67aeda0bd46..298529abd2b 100644 --- a/vortex-array/src/executor.rs +++ b/vortex-array/src/executor.rs @@ -200,18 +200,13 @@ impl ArrayRef { // would be lost when we restore frame.parent_builder. if current_builder.is_none() && let Some(frame) = stack.last() - && let Some(result) = execute_parent_for_child( + && let Some(result) = try_execute_parent( &frame.parent_array, - ¤t_array, - frame.slot_idx, + std::iter::once((frame.slot_idx, ¤t_array)), &kernels, ctx, )? { - ctx.log(format_args!( - "execute_parent (stack) rewrote {} -> {}", - current_array, result - )); let frame = stack.pop().vortex_expect("just peeked"); current_array = result.optimize_ctx(ctx.session())?; current_builder = frame.parent_builder; @@ -220,12 +215,9 @@ impl ArrayRef { // Step 2b: execute_parent against current_array's own children. if current_builder.is_none() - && let Some(rewritten) = try_execute_parent(¤t_array, &kernels, ctx)? + && let Some(rewritten) = + try_execute_parent(¤t_array, occupied_slots(¤t_array), &kernels, ctx)? { - ctx.log(format_args!( - "execute_parent rewrote {} -> {}", - current_array, rewritten - )); current_array = rewritten.optimize_ctx(ctx.session())?; continue; } @@ -431,26 +423,13 @@ impl Executable for ArrayRef { } } - let tmp_session = ctx.session().clone(); - let kernels = tmp_session.get::(); + let session = ctx.session().clone(); + let kernels = session.get::(); - for (slot_idx, slot) in array.slots().iter().enumerate() { - let Some(child) = slot else { continue }; - if let Some(executed_parent) = - execute_parent_for_child(&array, child, slot_idx, &kernels, ctx)? - { - ctx.log(format_args!( - "execute_parent: slot[{}]({}) rewrote {} -> {}", - slot_idx, - child.encoding_id(), - array, - executed_parent - )); - executed_parent - .statistics() - .inherit_from(array.statistics()); - return Ok(executed_parent); - } + if let Some(executed_parent) = + try_execute_parent(&array, occupied_slots(&array), &kernels, ctx)? + { + return Ok(executed_parent); } ctx.log(format_args!("executing {}", array)); @@ -545,49 +524,58 @@ fn finalize_done( Ok((output, None)) } -fn execute_parent_for_child( +/// Offer `parent` to each `(slot_idx, child)` pair, consulting the [`ArrayKernels`] registry +/// first and then the child's `execute_parent` method, and return the first successful rewrite +/// with `parent`'s statistics inherited onto it. +/// +/// Step 2a of [`ArrayRef::execute_until`] passes a single pair (the suspended stack parent and +/// the focused child); Step 2b and the single-step [`Executable`] pass every occupied slot of an +/// array via [`occupied_slots`]. +fn try_execute_parent<'a>( parent: &ArrayRef, - child: &ArrayRef, - slot_idx: usize, + children: impl IntoIterator, kernels: &ArrayKernels, ctx: &mut ExecutionCtx, ) -> VortexResult> { - if let Some(plugins) = kernels.find_execute_parent(parent.encoding_id(), child.encoding_id()) { - for plugin in plugins.as_ref() { - if let Some(result) = plugin(child, parent, slot_idx, ctx)? { - return Ok(Some(result)); + for (slot_idx, child) in children { + let mut rewritten = None; + if let Some(plugins) = + kernels.find_execute_parent(parent.encoding_id(), child.encoding_id()) + { + for plugin in plugins.as_ref() { + if let Some(result) = plugin(child, parent, slot_idx, ctx)? { + rewritten = Some(result); + break; + } } } + let rewritten = match rewritten { + Some(result) => result, + None => match child.execute_parent(parent, slot_idx, ctx)? { + Some(result) => result, + None => continue, + }, + }; + ctx.log(format_args!( + "execute_parent: slot[{}]({}) rewrote {} -> {}", + slot_idx, + child.encoding_id(), + parent, + rewritten + )); + rewritten.statistics().inherit_from(parent.statistics()); + return Ok(Some(rewritten)); } - - child.execute_parent(parent, slot_idx, ctx) + Ok(None) } -/// Try execute_parent on each occupied slot of the array. -fn try_execute_parent( - array: &ArrayRef, - kernels: &ArrayKernels, - ctx: &mut ExecutionCtx, -) -> VortexResult> { - for (slot_idx, slot) in array.slots().iter().enumerate() { - let Some(child) = slot else { continue }; - if let Some(executed_parent) = - execute_parent_for_child(array, child, slot_idx, kernels, ctx)? - { - ctx.log(format_args!( - "execute_parent: slot[{}]({}) rewrote {} -> {}", - slot_idx, - child.encoding_id(), - array, - executed_parent - )); - executed_parent - .statistics() - .inherit_from(array.statistics()); - return Ok(Some(executed_parent)); - } - } - Ok(None) +/// Iterator over an array's occupied (`Some`) slots paired with their slot index. +fn occupied_slots(array: &ArrayRef) -> impl Iterator { + array + .slots() + .iter() + .enumerate() + .filter_map(|(idx, slot)| slot.as_ref().map(|child| (idx, child))) } /// A predicate that determines when an array has reached a desired form during execution. From 718ffeab838ba914628aa4fcd81bdecb8fc0e47f Mon Sep 17 00:00:00 2001 From: Robert Kruszewski Date: Thu, 18 Jun 2026 10:28:25 +0100 Subject: [PATCH 4/5] fixes Signed-off-by: Robert Kruszewski --- vortex-array/src/arrays/constant/vtable/mod.rs | 4 ++-- .../src/arrays/patched/compute/take.rs | 4 ++-- vortex-array/src/arrays/patched/vtable/mod.rs | 18 +++++++++--------- .../src/arrays/patched/vtable/operations.rs | 8 ++++---- vortex-array/src/executor.rs | 13 ++++++++----- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/vortex-array/src/arrays/constant/vtable/mod.rs b/vortex-array/src/arrays/constant/vtable/mod.rs index 89c571eb827..30477c44961 100644 --- a/vortex-array/src/arrays/constant/vtable/mod.rs +++ b/vortex-array/src/arrays/constant/vtable/mod.rs @@ -270,10 +270,10 @@ fn append_value_or_nulls( mod tests { use rstest::rstest; use vortex_error::VortexResult; - use vortex_session::VortexSession; use crate::IntoArray; use crate::VortexSessionExecute; + use crate::array_session; use crate::arrays::ConstantArray; use crate::arrays::constant::vtable::canonical::constant_canonicalize; use crate::assert_arrays_eq; @@ -286,7 +286,7 @@ mod tests { /// Appends `array` into a fresh builder and asserts the result matches `constant_canonicalize`. fn assert_append_matches_canonical(array: ConstantArray) -> VortexResult<()> { - let mut ctx = VortexSession::empty().create_execution_ctx(); + let mut ctx = array_session().create_execution_ctx(); let expected = constant_canonicalize(array.as_view(), &mut ctx)?.into_array(); let mut builder = builder_with_capacity(array.dtype(), array.len()); diff --git a/vortex-array/src/arrays/patched/compute/take.rs b/vortex-array/src/arrays/patched/compute/take.rs index 893d18db1ac..edb4137adb5 100644 --- a/vortex-array/src/arrays/patched/compute/take.rs +++ b/vortex-array/src/arrays/patched/compute/take.rs @@ -133,11 +133,11 @@ mod tests { use vortex_buffer::buffer; use vortex_error::VortexResult; - use vortex_session::VortexSession; use crate::ArrayRef; use crate::ExecutionCtx; use crate::IntoArray; + use crate::array_session; use crate::arrays::Patched; use crate::arrays::PrimitiveArray; use crate::assert_arrays_eq; @@ -158,7 +158,7 @@ mod tests { None, )?; - let session = VortexSession::empty(); + let session = array_session(); let mut ctx = ExecutionCtx::new(session); Patched::from_array_and_patches(values, &patches, &mut ctx)? diff --git a/vortex-array/src/arrays/patched/vtable/mod.rs b/vortex-array/src/arrays/patched/vtable/mod.rs index 4ba5bc542e1..88ba11128af 100644 --- a/vortex-array/src/arrays/patched/vtable/mod.rs +++ b/vortex-array/src/arrays/patched/vtable/mod.rs @@ -354,7 +354,6 @@ mod tests { use vortex_buffer::buffer; use vortex_buffer::buffer_mut; use vortex_error::VortexResult; - use vortex_session::VortexSession; use vortex_session::registry::ReadContext; use crate::ArrayContext; @@ -363,6 +362,7 @@ mod tests { use crate::ExecutionCtx; use crate::IntoArray; use crate::LEGACY_SESSION; + use crate::array_session; use crate::arrays::Patched; use crate::arrays::PatchedArray; use crate::arrays::PrimitiveArray; @@ -389,7 +389,7 @@ mod tests { ) .unwrap(); - let session = VortexSession::empty(); + let session = array_session(); let mut ctx = ExecutionCtx::new(session); let array = Patched::from_array_and_patches(values, &patches, &mut ctx) @@ -422,7 +422,7 @@ mod tests { ) .unwrap(); - let session = VortexSession::empty(); + let session = array_session(); let mut ctx = ExecutionCtx::new(session); let array = Patched::from_array_and_patches(values, &patches, &mut ctx) @@ -455,7 +455,7 @@ mod tests { ) .unwrap(); - let session = VortexSession::empty(); + let session = array_session(); let mut ctx = ExecutionCtx::new(session); let array = Patched::from_array_and_patches(values, &patches, &mut ctx) @@ -488,7 +488,7 @@ mod tests { ) .unwrap(); - let session = VortexSession::empty(); + let session = array_session(); let mut ctx = ExecutionCtx::new(session); let array = Patched::from_array_and_patches(values, &patches, &mut ctx) @@ -525,7 +525,7 @@ mod tests { ) .unwrap(); - let session = VortexSession::empty(); + let session = array_session(); let mut ctx = ExecutionCtx::new(session); let array = Patched::from_array_and_patches(values, &patches, &mut ctx) @@ -569,7 +569,7 @@ mod tests { let patches = Patches::new(len, 0, indices, patch_vals, None)?; - let session = VortexSession::empty(); + let session = array_session(); let mut ctx = ExecutionCtx::new(session); Patched::from_array_and_patches(array, &patches, &mut ctx) @@ -646,7 +646,7 @@ mod tests { assert_eq!(array_ref.dtype(), new_array.dtype()); // Execute both and compare results - let mut ctx = ExecutionCtx::new(VortexSession::empty()); + let mut ctx = ExecutionCtx::new(array_session()); let original_executed = array_ref.execute::(&mut ctx)?.into_primitive(); let new_executed = new_array.execute::(&mut ctx)?.into_primitive(); @@ -672,7 +672,7 @@ mod tests { let new_array = array_ref.with_slots(slots.into_slots())?; // Execute and verify the inner values changed (except at patch positions) - let mut ctx = ExecutionCtx::new(VortexSession::empty()); + let mut ctx = ExecutionCtx::new(array_session()); let executed = new_array.execute::(&mut ctx)?.into_primitive(); // Expected: all 5s except indices 1, 2, 3 which are patched to 10, 20, 30 diff --git a/vortex-array/src/arrays/patched/vtable/operations.rs b/vortex-array/src/arrays/patched/vtable/operations.rs index f0491666e56..ee6ca79155f 100644 --- a/vortex-array/src/arrays/patched/vtable/operations.rs +++ b/vortex-array/src/arrays/patched/vtable/operations.rs @@ -58,12 +58,12 @@ impl OperationsVTable for Patched { #[cfg(test)] mod tests { use vortex_buffer::buffer; - use vortex_session::VortexSession; use crate::ExecutionCtx; use crate::IntoArray; use crate::LEGACY_SESSION; use crate::VortexSessionExecute; + use crate::array_session; use crate::arrays::Patched; use crate::dtype::Nullability; use crate::optimizer::ArrayOptimizer; @@ -82,7 +82,7 @@ mod tests { ) .unwrap(); - let session = VortexSession::empty(); + let session = array_session(); let mut ctx = ExecutionCtx::new(session); let array = Patched::from_array_and_patches(values, &patches, &mut ctx) @@ -127,7 +127,7 @@ mod tests { ) .unwrap(); - let session = VortexSession::empty(); + let session = array_session(); let mut ctx = ExecutionCtx::new(session); let array = Patched::from_array_and_patches(values, &patches, &mut ctx) @@ -159,7 +159,7 @@ mod tests { ) .unwrap(); - let session = VortexSession::empty(); + let session = array_session(); let mut ctx = ExecutionCtx::new(session); let array = Patched::from_array_and_patches(values, &patches, &mut ctx) diff --git a/vortex-array/src/executor.rs b/vortex-array/src/executor.rs index 298529abd2b..144062fc54d 100644 --- a/vortex-array/src/executor.rs +++ b/vortex-array/src/executor.rs @@ -26,7 +26,6 @@ use vortex_error::VortexResult; use vortex_error::vortex_bail; use vortex_error::vortex_ensure; use vortex_error::vortex_panic; -use vortex_session::SessionExt; use vortex_session::VortexSession; use crate::AnyCanonical; @@ -203,7 +202,7 @@ impl ArrayRef { && let Some(result) = try_execute_parent( &frame.parent_array, std::iter::once((frame.slot_idx, ¤t_array)), - &kernels, + kernels, ctx, )? { @@ -215,8 +214,12 @@ impl ArrayRef { // Step 2b: execute_parent against current_array's own children. if current_builder.is_none() - && let Some(rewritten) = - try_execute_parent(¤t_array, occupied_slots(¤t_array), &kernels, ctx)? + && let Some(rewritten) = try_execute_parent( + ¤t_array, + occupied_slots(¤t_array), + kernels, + ctx, + )? { current_array = rewritten.optimize_ctx(ctx.session())?; continue; @@ -427,7 +430,7 @@ impl Executable for ArrayRef { let kernels = session.get::(); if let Some(executed_parent) = - try_execute_parent(&array, occupied_slots(&array), &kernels, ctx)? + try_execute_parent(&array, occupied_slots(&array), kernels, ctx)? { return Ok(executed_parent); } From 8914550e32b3171d86b31fce3bbbcbef347e8b19 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 10:28:55 +0000 Subject: [PATCH 5/5] fix: tolerate sessions without ArrayKernels in executor `execute_until` and the single-step `Executable::execute` looked up the `ArrayKernels` session variable with `session.get`, which panics when the variable is absent. Sessions built without registering `ArrayKernels` (such as the `run_end_take` benchmark's `VortexSession::empty()`) then panicked during execution. Fall back to an empty `ArrayKernels` registry when the variable is not present, matching the optimizer's `get_opt` contract. The registry is consulted first and then each child's static `execute_parent` method, so built-in kernels still fire under an empty registry. Signed-off-by: Joe Isaacs --- vortex-array/src/executor.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/vortex-array/src/executor.rs b/vortex-array/src/executor.rs index 144062fc54d..85bf5ee256d 100644 --- a/vortex-array/src/executor.rs +++ b/vortex-array/src/executor.rs @@ -163,7 +163,14 @@ impl ArrayRef { let max_iterations = max_iterations(); let session = ctx.session().clone(); - let kernels = session.get::(); + let fallback_kernels; + let kernels = match session.get_opt::() { + Some(kernels) => kernels, + None => { + fallback_kernels = ArrayKernels::empty(); + &fallback_kernels + } + }; for _ in 0..max_iterations { let is_done = stack @@ -427,7 +434,14 @@ impl Executable for ArrayRef { } let session = ctx.session().clone(); - let kernels = session.get::(); + let fallback_kernels; + let kernels = match session.get_opt::() { + Some(kernels) => kernels, + None => { + fallback_kernels = ArrayKernels::empty(); + &fallback_kernels + } + }; if let Some(executed_parent) = try_execute_parent(&array, occupied_slots(&array), kernels, ctx)?