Skip to content

fix(agg): reduction of a scalar sub-expression matches the scalar builtin#238

Merged
singaraiona merged 2 commits into
masterfrom
fix/dag-reduce-scalar-atom
Jun 10, 2026
Merged

fix(agg): reduction of a scalar sub-expression matches the scalar builtin#238
singaraiona merged 2 commits into
masterfrom
fix/dag-reduce-scalar-atom

Conversation

@ser-vasilich

Copy link
Copy Markdown
Collaborator

Problem

A reduction applied to a scalar sub-expression raised a spurious type error, while the same reduction on a literal scalar worked:

(sum (max [1 2 3]))            -> error: type     (expected 3)
(sum (count (distinct v)))     -> error: type     (expected the distinct count)
(sum (sum [1 2 3]))            -> error: type     (expected 6)
(avg (max [1 2 3]))            -> error: type     (expected 3.0)
(sum 6)                        -> 6               (literal works)

These compile to a DAG whose OP_SUM/OP_MIN/… input materialises to an atom (the inner reduction's scalar result). exec_reduction's atom branch handled only COUNT and returned a type error for everything else — a scalar↔DAG divergence in the same family as the M1–M5 aggregation reconciliation. Materialising the inner value out of the DAG ((set x (count (distinct v))) then (sum x), or (sum (+ 0 (max v)))) already worked, confirming it is purely the DAG atom-input path that diverged.

Fix

exec_reduction's atom branch now delegates to the scalar aggregate builtins (ray_sum_fn / ray_avg_fn / ray_min_fn / ray_max_fn / ray_first_fn / ray_last_fn / ray_med_fn / ray_var*_fn / ray_stddev*_fn), so the DAG agrees with the direct scalar form by construction — including type/admission: sum of a SYM atom reduces to itself exactly as the scalar (sum 'c) does, and sum of a TIME atom stays TIME. OP_PROD (no scalar builtin) returns the lone element. The builtins handle the atom in place (no recursion back into exec_reduction) and return an owned value without consuming input.

Verification

  • agg/reduce_scalar_subexpr.rfl pins the reduction-of-scalar forms, the scalar↔DAG parity ((sum (max v)) == (max v)), and type preservation (i64 / f64 / time).
  • Full suite green under gcc debug+ASan/UBSan, 0 failed — no leak/UAF from the delegation.

Not included

A separate, distinct issue remains open and is not addressed here: (sum (map (fn […] (count (distinct v))) range)) still errors, but that is sum over a map-produced LIST (a LIST→vector coercion path), not the atom-input path fixed here — (sum (at … 0)) and (fold + … ) over the same map result both work. Tracked separately.

🤖 Generated with Claude Code

ser-vasilich and others added 2 commits June 10, 2026 15:18
A reduction applied to a scalar sub-expression — (sum (max v)),
(sum (count (distinct v))), (sum (sum v)), (avg (max v)) — compiles to a DAG
whose OP_SUM/OP_MIN/... input materialises to an ATOM (the inner reduction's
scalar result).  exec_reduction's atom branch only handled COUNT and returned
a `type` error for everything else, while the direct scalar form (sum 6)
accepted it — a scalar<->DAG divergence (same family as M1-M5).

Materialising the inner value out of the DAG ((set x (count (distinct v)))
then (sum x), or (sum (+ 0 (max v)))) already worked, confirming it is purely
the DAG atom-input path that diverged.

Fix: exec_reduction's atom branch now delegates to the scalar aggregate
builtins (ray_sum_fn / ray_avg_fn / ray_min_fn / ray_max_fn / ray_first_fn /
ray_last_fn / ray_med_fn / ray_var*_fn / ray_stddev*_fn), so the DAG agrees
with the direct scalar form by construction — including type/admission
(sum of a SYM atom reduces to itself exactly as the scalar (sum 'c) does, and
sum of a TIME atom stays TIME).  OP_PROD (no scalar builtin) returns the lone
element.  The builtins handle the atom in place without recursing back here
and return an owned value without consuming `input`.

Tests: agg/reduce_scalar_subexpr.rfl pins the reduction-of-scalar forms,
scalar<->DAG parity, and type preservation.  Full suite green under gcc+ASan
(no leak/UAF from the delegation), 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@singaraiona singaraiona merged commit 0f92ebe into master Jun 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants