Skip to content

Make ScalarFn array validity lazy when the function defines a validity expression#8336

Draft
joseph-isaacs wants to merge 9 commits into
claude/cool-bardeen-l8jlsy-3-definitely-all-invalidfrom
claude/cool-bardeen-l8jlsy-4-lazy-scalarfn-validity
Draft

Make ScalarFn array validity lazy when the function defines a validity expression#8336
joseph-isaacs wants to merge 9 commits into
claude/cool-bardeen-l8jlsy-3-definitely-all-invalidfrom
claude/cool-bardeen-l8jlsy-4-lazy-scalarfn-validity

Conversation

@joseph-isaacs

@joseph-isaacs joseph-isaacs commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

PR 4 of a 4-PR stack (stacked on #8335) — the payoff: lazy validity for ScalarFn arrays.

Previously ValidityVTable<ScalarFn> always eagerly executed the validity expression via the legacy session. Now, when the scalar function provides a validity expression over its inputs, the expression is converted into a lazy ScalarFn array DAG instead:

  • Literal nodes → ConstantArray
  • ArrayExpr leaves → unwrap to the child array they hold
  • interior nodes → lazy ScalarFn arrays via Array::<ScalarFn>::try_new
  • constant results are folded back into AllValid/AllInvalid via child_to_validity

@joseph-isaacs joseph-isaacs added the changelog/feature A new feature label Jun 10, 2026 — with Claude
@codspeed-hq

codspeed-hq Bot commented Jun 10, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 17.42%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 5 regressed benchmarks
✅ 1527 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation baseline_eq[4, 65536] 186.4 µs 238.8 µs -21.93%
Simulation baseline_lt[16, 65536] 219 µs 276.4 µs -20.78%
Simulation baseline_lt[4, 65536] 202.2 µs 254 µs -20.39%
Simulation bitwise_not_vortex_buffer_mut[128] 215.3 ns 244.4 ns -11.93%
Simulation baseline_eq[16, 65536] 231.3 µs 261.2 µs -11.45%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/cool-bardeen-l8jlsy-4-lazy-scalarfn-validity (f9b0a16) with claude/cool-bardeen-l8jlsy-3-definitely-all-invalid (a13f0ab)

Open in CodSpeed

Copy link
Copy Markdown
Contributor Author

Investigated the CodSpeed varbinview_zip_* regressions — they are a codegen-layout artifact, not a real cost of this change:

  1. The changed code never runs in these benchmarks. Instrumenting ScalarFn::validity with a counter shows 0 calls during varbinview_zip_block_mask/varbinview_zip_fragmented_mask (vs ~47k calls in the bool-consistency tests, confirming the instrumentation works). The benches go straight through the dedicated ZipKernel for VarBinView and never query a ScalarFn array's validity.

  2. Bisecting the two files in this PR reproduces the full ~19% delta locally only when arrays/scalar_fn/vtable/validity.rs changes — the file whose code is never executed here. The scalar_fn/erased.rs change alone shows no delta.

  3. codegen-units=1 erases the regression entirely (branch 3: 505µs median vs branch 3 + this PR's files: 494µs). With default codegen units, adding code to that module shifts LLVM's CGU partitioning and changes inlining in the unrelated hot zip loop, which CodSpeed's instruction-counting simulation faithfully reports.

The same flavor of noise shows in the rest of the stack: the bitwise_not_vortex_buffer_mut and chunked_*_canonical_into benchmarks flip between ±20–47% on adjacent PRs that don't touch vortex-buffer or the chunked builders at all.

I can't acknowledge regressions on the CodSpeed dashboard from this session — that needs someone with dashboard access.

https://claude.ai/code/session_01VPQ7dfZtijfrsjAipwXvEj


Generated by Claude Code

/// Transforms the expression into one representing the validity of this expression.
pub fn validity(&self, expr: &Expression) -> VortexResult<Expression> {
Ok(self.0.validity(expr)?.unwrap_or_else(|| {
Ok(self.validity_opt(expr)?.unwrap_or_else(|| {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want to remove the TODO?

@joseph-isaacs joseph-isaacs force-pushed the claude/cool-bardeen-l8jlsy-4-lazy-scalarfn-validity branch from d72800b to f4e8d62 Compare June 11, 2026 10:17
@joseph-isaacs joseph-isaacs force-pushed the claude/cool-bardeen-l8jlsy-3-definitely-all-invalid branch 2 times, most recently from 5d2c247 to e515a28 Compare June 11, 2026 10:19
@joseph-isaacs joseph-isaacs force-pushed the claude/cool-bardeen-l8jlsy-4-lazy-scalarfn-validity branch 2 times, most recently from 9673272 to e9341da Compare June 11, 2026 10:41
@joseph-isaacs joseph-isaacs force-pushed the claude/cool-bardeen-l8jlsy-3-definitely-all-invalid branch from e515a28 to e507aa3 Compare June 11, 2026 10:41
If two Mask::AllTrue or AllFalse are passed we don't need a bit buffer
to check equality

Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
@joseph-isaacs joseph-isaacs force-pushed the claude/cool-bardeen-l8jlsy-3-definitely-all-invalid branch from e507aa3 to 2d2498f Compare June 11, 2026 10:58
@joseph-isaacs joseph-isaacs force-pushed the claude/cool-bardeen-l8jlsy-4-lazy-scalarfn-validity branch from e9341da to 587baf9 Compare June 11, 2026 10:58
@joseph-isaacs joseph-isaacs marked this pull request as draft June 11, 2026 11:01
@joseph-isaacs joseph-isaacs force-pushed the claude/cool-bardeen-l8jlsy-3-definitely-all-invalid branch from 2d2498f to a13f0ab Compare June 11, 2026 11:01
@joseph-isaacs joseph-isaacs force-pushed the claude/cool-bardeen-l8jlsy-4-lazy-scalarfn-validity branch from 587baf9 to 6fe8231 Compare June 11, 2026 11:01
AdamGS and others added 8 commits June 11, 2026 13:42
## Summary

Adds docs for `StatsRewriteRule` and its functions.  

Can be considered a follow-up for
#8345, but can be individually
merged.

Signed-off-by: Adam Gutglick <adam@spiraldb.com>
## Summary

**PR 2 of a 4-PR stack** (stacked on #8333) preparing `Validity` for
lazy validity arrays.

`mask_eq` previously returned `false` for any mixed-variant pairing
without executing — e.g. a `Validity::Array` that resolves to all-true
compared against `Validity::AllValid`. With lazy validity arrays,
unresolved `Array` variants frequently hold constant masks, making this
silently wrong rather than merely conservative.

---------

Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
## Summary

Adds import/export from `vortex-json` to Arrow's JSON canonical
extension type.

---------

Signed-off-by: Adam Gutglick <adam@spiraldb.com>
For Array-vs-constant pairings, the min/max statistics of the validity
array decide all-valid/all-invalid exactly, so consult them first and
only fall back to executing the validity array into a Mask when
statistics are unavailable. Constant variants with opposite masks now
short-circuit on length instead of building two masks.

https://claude.ai/code/session_01VPQ7dfZtijfrsjAipwXvEj
Signed-off-by: Claude <noreply@anthropic.com>
…aths

Replace scattered matches!(.., Validity::AllInvalid) checks with a
named helper, symmetric with definitely_no_nulls(). The name makes the
conservative semantics explicit: a Validity::Array may still resolve to
all-invalid once executed, so a false result means "unknown without
compute", not "definitely has valid values".

Call sites that assert an exact variant in tests keep the raw matches!.

https://claude.ai/code/session_01VPQ7dfZtijfrsjAipwXvEj
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
The non-test code now uses definitely_all_invalid() and no longer
references the Validity type directly; the test module has its own
import.

https://claude.ai/code/session_01VPQ7dfZtijfrsjAipwXvEj
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
…y expression

Previously ValidityVTable<ScalarFn> always eagerly executed the
validity expression via the legacy session. Now, when the scalar
function provides a validity expression over its inputs, the expression
is converted into a lazy ScalarFn array DAG instead: Literal nodes
become ConstantArrays, ArrayExpr leaves unwrap to the child arrays they
hold, and interior nodes become lazy ScalarFn arrays. Constant results
are folded back into AllValid/AllInvalid via child_to_validity.

Functions that do not define a validity expression (e.g. Kleene logic
and/or, where validity depends on the computed values) keep the eager
path. The erased fallback for these is is_not_null over the expression
itself, so a lazy representation would be self-referential: resolving
the validity of the inner node spawns another is_not_null DAG, which
recurses without ever shrinking (this manifested as a stack overflow in
element-wise execution paths). ScalarFnRef::validity_opt is added to
expose whether a function defines its own validity expression.

https://claude.ai/code/session_01VPQ7dfZtijfrsjAipwXvEj
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
@joseph-isaacs joseph-isaacs force-pushed the claude/cool-bardeen-l8jlsy-4-lazy-scalarfn-validity branch from 6fe8231 to f9b0a16 Compare June 11, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants