Skip to content

SQL specializer groundwork: engine-per-module refactor, design docs, pre-loop prep - #23

Merged
ahrzb merged 12 commits into
masterfrom
claude/duckdb-native-interpreter-36d016
Jul 25, 2026
Merged

SQL specializer groundwork: engine-per-module refactor, design docs, pre-loop prep#23
ahrzb merged 12 commits into
masterfrom
claude/duckdb-native-interpreter-36d016

Conversation

@claude-agent-ahrzb

Copy link
Copy Markdown

What

Three commits of groundwork for the SQL specializer (partial-evaluation engine, see the two new spec docs):

  1. refactor(interpreter) — existing engine moves under src/datafusion/; engine-agnostic pieces hoisted to the crate root (value.rs, types.rs, error.rs). DuckDBInferFn stub added (parses DuckDB dialect, infer() raises) — the future pyclass shell for the specializer.
  2. docs(specs)2026-07-25-sql-specializer-design.md (architecture, resolved decisions, row-major AoS ABI with prepare-time-generated marshaller) and 2026-07-25-sql-specializer-loop-execution-design.md (prep checklist + loop/workflow execution design).
  3. chore(specializer) — pre-loop prep, all checklist items done except the final design-review go:
    • duckdb 1.5.5 dev dep (oracle); substrait unavailable for this platform → frontend = sqlparser fallback (spiked, recorded)
    • cranelift-jit 0.126 spike green on windows-msvc
    • cargo test now runnable (extension-module → maturin-only feature); mise gate-specializer = cargo test + pytest, currently green (2 + 574 passed, 12 xfailed)
    • 678-case corpus mined from duckdb's sqllogictests → tests/corpus/duckdb_mined.jsonl (three-outcome replay contract)
    • backlog milestone m-7 seeded: TASK-41 (M-ir) → TASK-45 (M-boundary), dependency-chained

Verification

  • mise gate-specializer green end-to-end (cargo test 2 passed; pytest 574 passed, 12 xfailed)
  • Substrait + cranelift spike outputs recorded in the loop-execution doc

🤖 Generated with Claude Code

ahrzb and others added 6 commits July 25, 2026 04:23
Move the existing engine under src/datafusion/ and hoist what is
engine-agnostic to the crate root: Value marshalling (value.rs), the
Base/FieldType/Schema vocabulary (types.rs), InterpError (error.rs).
schema.rs/lookup.rs were already engine-free. datafusion::{expr,plan,
types} re-export the moved names so engine-internal paths still resolve.

src/duckdb/ adds DuckDBInferFn: same Python API as InferFn minus the
transformer callout. Stub only — the constructor parses in the DuckDB
dialect, infer() raises NotImplementedError.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Design doc: partial-evaluation architecture (BTA collapses static
subtrees at prepare; row-major AoS ABI with a prepare-time-generated
marshaller; DuckDB as frontend/static-evaluator/oracle; interpreter
oracle then Cranelift). Loop doc: prep checklist and the /loop-spine +
workflow-fan-out execution design. duckdb/ source clone gitignored as a
reference corpus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- duckdb 1.5.5 as dev dep (differential oracle). Substrait spike: extension
  unavailable for 1.5.5/windows_amd64 (404 all repos) -> frontend flag flipped
  to the sqlparser fallback; json_serialize_sql noted as parser cross-check.
- cranelift-jit 0.126 spike: works on x86_64-pc-windows-msvc.
- cargo test made runnable: extension-module moved out of the crate's default
  pyo3 features (maturin enables it via pyproject); scripts/gate.py puts the
  uv CPython's python3.dll on PATH. mise task: gate-specializer.
- pytest testpaths pinned so the duckdb/ clone's own scripts aren't collected.
- scripts/mine_duckdb_corpus.py: 678 replayable cases mined from duckdb's
  sqllogictests into tests/corpus/duckdb_mined.jsonl (three-outcome contract).
- backlog: milestone m-7 seeded, TASK-41..45 (M-ir..M-boundary) chained.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e rule hid it

The repo-root DataFusion clone is ignored via .git/info/exclude with the bare
pattern `datafusion/`, which matches at any depth and silently swallowed the
refactor's src/datafusion/ move (local builds passed off the working tree; CI
got a commit without the module). Exclude rule anchored locally to /datafusion/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
src/datafusion/ (Rust module) sits under a ruff src root, so isort's scan
started classifying the datafusion pip package as first-party on CI's
--all-files run (local pre-commit only checks changed files, so it never
fired). duckdb pinned too — the source clone collides the same way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… (M-ir)

src/specializer/ir/: SSA over bare typed scalars with the null lane split
into explicit i1 flags (.opt instruction forms) so a nullable value cannot
reach arithmetic by construction; strict block-param SSA (no dominance
analysis needed); acyclic CFG; emit/skip/trap terminators carrying the
store-completeness contract (|out|==|in| iff skip unreachable).

verify.rs enforces six rule groups, each with a rejecting test; print/parse
round-trip exactly (parse(print(p)) == p, bitwise literal equality so
nan/-0.0 survive); gen.rs is a seeded xorshift program generator — 300-seed
round-trip fuzz in tests, reused later for interpreter-vs-codegen fuzzing.
Design doc §6 updated to the implemented form. TASK-41.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ahrzb and others added 3 commits July 25, 2026 15:46
… — TASK-41

Six agents (4 attack lenses + 2 reviews) probed the initial IR; every
confirmed finding is fixed and pinned by a regression test:

- reachability DFS made iterative: a ~8k-block legal CFG stack-overflowed
  the process; 50k blocks now verify (deep_cfg_verifies_without_crashing)
- verified programs must print to parseable text: reject empty map static
  signatures (grammar can't express `map() -> ()`) and non-identifier
  function names
- Lit::F64 equality treats all NaNs as one class, matching the text form's
  canonical `nan` token — non-canonical payloads round-trip again
- store dataflow runs over the reachable subgraph: an unreachable cyclic
  island no longer starves a reachable join and masks its store errors
- docs aligned: double stores rejected on ALL paths incl. trap (deliberate),
  grammar EBNF covers store/comments/lenient %names, canonical-form note
- dead Inst::uses/Term::uses deleted; 7 missing rule-coverage tests added
  (entry params, duplicate cols, branch-to-entry, sload-on-map, scalar-static
  .opt pairing both ways, store.opt on non-nullable)

cargo 45 passed; gate green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…notes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Specializer M-ir: imperative IR — grammar, types, verifier, text format
ahrzb and others added 3 commits July 25, 2026 22:50
…terp)

src/specializer/exec/: runtime substrate (Batch, byte-backed bump Arena with
StrRef spans, sorted-vec map statics probed by allocation-free binary
search) + interp.rs (compile verifies first — nothing executes unverified
IR — then one pre-traversal builds per-block closures; terminators are a
small enum in the row loop; branch-arg copies are safe by SSA disjointness).

Steady state allocates nothing: registers/arena/out builders live in a
reusable RunState; a thread-local counting global allocator asserts zero
allocs on a warmed run. Semantics pins documented in interp.rs (checked
integer arithmetic, IEEE floats, half-away-from-zero ftoi.round, exact-parse
stoi/stof) — provisional until the DuckDB differential at M-lower.

All five M-ir fixtures execute against hand-computed expectations incl. the
trap path; 150-seed generated programs execute deterministically. TASK-42.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… — TASK-42

Six agents (4 attack lenses + 2 reviews) probed the oracle; confirmed
findings fixed and pinned:

- StrRef offsets widened u32 -> usize: a single call legitimately exceeding
  4 GiB of varlen data silently aliased old spans (Ok with corrupt output —
  the one thing an oracle must never do)
- store.opt with a false flag now stores the type-default payload the spec
  pins, not the live register
- check_input validates the validity lane of nullable columns (a short
  valid vec silently meant "all valid")
- register slots assigned densely at compile: sparse-but-legal value ids
  can no longer inflate the frame (Value(u32::MAX) demanded a 64 GiB vec)
- zero-allocation claim restated honestly: warmth is per content profile
  (branch paths, probe hits, non-NULL varlen), growth one-time + monotone —
  refuted-as-written by branch/probe/validity flips at identical shape
- RunState.emitted reports the row count (observable with 0 out columns)
- Ctx made private; out_decl drops its unread nullable flag; probe hit arm
  zips entry values directly
- semantics pins now all tested: overflow/div traps incl. irem MIN%-1, fcmp
  NaN table, ftoi ties + range traps, IEEE inf/nan flow, scmp order, exact
  stoi parse, load.opt garbage normalization

cargo 65 passed; gate green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…notes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ahrzb
ahrzb merged commit e9b3ff3 into master Jul 25, 2026
1 check 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.

1 participant