Skip to content

Tracking Issue: Execute RowFn over Vortex arrays #9130

Description

@connortsui20

This is a tracking issue for the private machinery that executes a RowFn over Vortex arrays.

Parent Epic: #9128

Related API tracking issue: #9129

This issue is mostly a WIP since I still need to figure out if the mechanics can be optimize further.

Design

A RowFn supplies typed row kernels. A blanket ScalarFnVTable implementation and private lifting layer turn those kernels into columnar execution. There is no separate public strict-function vtable.

The lifting owns the parts that should not be reimplemented by every function:

  1. Read arity, dense safety, decode behavior, and fallibility from the RowFn witnesses. Run dispatch to validate the arguments and derive the result dtype from the visited OutputElement or OutputSink, then widen it when any input is nullable.
  2. Short-circuit a null constant to an all-null result, or evaluate an entirely constant call once and broadcast it.
  3. Decode each input once. A partially constant argument is decoded as one row and read with stride 0.
  4. Derive the strict input validity and choose how the kernel sees rows behind nulls.
  5. Execute the row kernel or an encoding-aware reduction, then reconcile the output dtype and apply validity.

Null handling has two contracts. Dense may evaluate payloads behind null rows and mask the result, so it is only available when every input element is DENSE_SAFE and execution is infallible. Filter guarantees that the row computation only sees valid rows.

Adaptive execution

For a mixed validity mask under Filter, the lifting chooses between two mechanisms per batch:

  • Branch-and-skip decodes the original columns, computes only set rows, fills skipped output slots with placeholders, and masks the result. This avoids filtering and scattering and preserves the input encodings.
  • Filter-and-scatter filters every input to the valid rows, executes densely, and scatters the result back. This is the fallback when null-tolerant decoding is unavailable, and can win when filtering avoids substantial per-row decode work.

The choice is invisible to the RowFn. InputElement::decode_null_tolerant first determines whether branch-and-skip is sound for the concrete arrays. DECODE_SHRINKS_WHEN_FILTERED then tells the selector whether filtering can avoid substantial decode work. If branch execution is unavailable for any reason, the batch falls back to filter-and-scatter.

The current rule always prefers branch-and-skip for bulk decodes. For a per-row decode, it filters when fewer than 75% of rows survive. This matches the faster forced strategy across the current byte_length and geo contains measurements, but it is still an experimental global threshold rather than part of the RowFn contract.

Even a Dense kernel may eventually benefit from skipping all-null mask words while keeping contiguous all-valid runs dense. That needs a skipped-row output contract and benchmarks for mask shape as well as surviving fraction.

Sink-backed execution initially remains on the dense and filter paths because a sink has no skipped-row representation.

Constant decoding and constant computation are separate. The machinery detects batch constants, decodes them once, and exposes their values to visit_prepared. The function still decides which work can be hoisted and computes that state itself.

Steps

  • Derive the ScalarFnVTable arity, strictness, fallibility, validity, and options serialization from RowFn, and derive its result dtype through dispatch.
  • Implement null-constant and all-constant lifting, dense execution, filtering, scattering, and output dtype reconciliation.
  • Implement branch-and-skip with null-tolerant decoding and output placeholders.
  • Add adaptive per-batch selection, forced-strategy test controls, and bytes/geometry crossover benchmarks.
  • Preserve encoding-aware reductions across dense, filtered, and branch-and-skip execution.
  • Validate the machinery with primitive, bytes, tensor, sink, fallible, and geometry functions.
  • Add before-and-after benchmarks for representative constant, nullable, and ordinary row workloads.

Unresolved questions

  • Does sink-backed execution need branch-and-skip before this work is complete, or are dense and filter sufficient for the initial API?
  • What benchmark set and regression threshold are required before replacing an existing hand-written implementation?

Non-blocking follow-ups:

  • Avoid probing reduce_encoded twice when branch execution is unsupported.
  • Revisit the global 75% threshold when another element with substantial per-row decode work exists.
  • Allow the fallible branch loop to stop iterating immediately after the first error.

Implementation history

None yet.

Metadata

Metadata

Assignees

Labels

tracking-issueShared implementation context for work likely to span multiple PRs.

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions