Skip to content

Specializer M-cranelift: codegen backend behind the same interface (TASK-44) - #27

Merged
claude-agent-ahrzb[bot] merged 3 commits into
masterfrom
claude/specializer-m-cranelift
Jul 26, 2026
Merged

Specializer M-cranelift: codegen backend behind the same interface (TASK-44)#27
claude-agent-ahrzb[bot] merged 3 commits into
masterfrom
claude/specializer-m-cranelift

Conversation

@claude-agent-ahrzb

@claude-agent-ahrzb claude-agent-ahrzb Bot commented Jul 26, 2026

Copy link
Copy Markdown

What (TASK-44, milestone m-7 — COMPLETE, all 4 acceptance criteria)

Cranelift-jit 0.126 backend for the imperative IR, behind the same interface as the interpreter, with full instruction coverage.

Shape (design doc §7): one JIT'd function per program, called per row — extern "C" fn(*mut Cx) -> i64 (0 emit / 1 skip / 2 helper trap / 3+k term trap). Cx is repr(C); generated code reads exactly one field directly (trap_flag at offset 0, checked after every fallible helper call). IR blocks map 1:1 to CLIF blocks + params (the IR was shaped for this); strings travel as (offset, length) i64 pairs into the shared arena.

Coverage-first strategy: consts, float arithmetic, integer compares, logic, select, itof, fabs are inline CLIF; everything nontrivial — checked int arithmetic, all string ops, loads/stores, statics, probes — calls extern "C" helpers that delegate to the interpreter's own semantic functions (casemap, substr_window, duck_fcmp, DuckF64, the arena). The backends physically cannot drift where they share code. Inlining hot helpers is a later, measured optimization.

Verification (AC #1, #4):

  • 500-seed random-IR differential demanding byte-identical outputs and traps — it caught two real bugs before landing (load.opt/sload.opt must normalize payloads to type defaults under a false flag)
  • The whole pytest suite now runs on the JIT: 32 duck_check differentials vs duckdb-python and the 678-case corpus replay at 0 FAILs
  • Gate green: cargo + 607 pytest, 12 xfail

Fallback (AC #2): DuckDBInferFn compiles cranelift-first, falls back to the interpreter on any compile error; SPECIALIZER_FORCE_INTERP env knob pins the control; .backend getter pins the choice in tests.

Numbers (AC #3), scripts/bench_specializer.py, p50/p99 ns/call at n ∈ {1, 8, 64, 1024} vs interpreter control + existing native + codegen engines, boundary no-op baselined first per §10:

  • The pydantic boundary dominates end-to-end: a no-op passthrough costs within ~20% of a full join at every n
  • Through Python, cranelift == interpreter (as predicted); both beat the existing native/codegen engines ~1.5–2× at larger n
  • Raw compute isolated (backend_compute_bench): cranelift 13.2 ns/row vs interpreter 37.6 ns/row2.8×. The JIT win is real and waiting behind the boundary; M-boundary (generated row marshaller) is where it becomes end-to-end visible.

🤖 Generated with Claude Code

ahrzb and others added 3 commits July 26, 2026 08:07
…wired, in-tree JIT smoke green (TASK-44)

TASK-43 marked Done (PR #26 merged); TASK-44 stretch plan recorded in
backlog. cranelift-{jit,module,codegen,frontend} 0.126 added and a smoke
test JITs and executes a function on x86_64-pc-windows-msvc — the spike
result is now validated in-tree, not just in a note. The real per-row
backend (extern "C" fn(ctx) -> i64 ABI, coverage-first with shared
helpers, interpreter fallback) lands next per the stretch plan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ferential green, wired behind DuckDBInferFn (TASK-44 stretches 1-3)

One JIT'd function per program, called per row: extern "C"
fn(*mut Cx) -> i64 (0 emit / 1 skip / 2 helper trap / 3+k term trap).
Coverage-first: float arith, int compares, logic, select, itof, fabs,
and consts map inline to CLIF; everything nontrivial — checked int
arith, all string ops, loads/stores, statics, probes — calls extern
helpers that delegate to the interpreter's own semantic functions
(casemap, substr_window, duck_fcmp, DuckF64, the arena), so the
backends cannot drift where they share code. Strings travel as
(offset, length) i64 pairs; trap_flag at Cx offset 0 is checked after
every fallible helper call. CraneliftFn owns a compiled InterpFn for
input/state checks, prepared statics, and the always-available
fallback.

The 500-seed random-IR differential (fuzz_cranelift_agrees_with_
interpreter) demands byte-identical outputs and traps — it caught two
real bugs before landing: load.opt and sload.opt must NORMALIZE
payloads to type defaults under a false flag, exactly as the
interpreter documents.

DuckDBInferFn now compiles cranelift-first with interpreter fallback
(AC #2) and exposes .backend; the whole pytest suite — 32 duck_check
differentials and the 678-case corpus replay at 0 FAILs — now
exercises the JIT.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gines, raw-compute isolation (TASK-44 stretch 4)

scripts/bench_specializer.py reports p50/p99 ns/call at n in
{1,8,64,1024} for the no-op boundary baseline, cranelift, the
interpreter control (SPECIALIZER_FORCE_INTERP env knob, also a
debugging escape hatch), and the existing native + codegen engines.
Measured on this machine: the pydantic boundary dominates end-to-end
(a no-op passthrough costs within ~20% of a full join), cranelift and
interp are at parity through Python, and both beat native/codegen by
~1.5-2x at larger n. The ignored backend_compute_bench isolates raw
compute without the boundary: cranelift 13.2 ns/row vs interpreter
37.6 ns/row (2.8x) — the JIT win is real and waiting behind the
boundary, which is M-boundary's job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude-agent-ahrzb
claude-agent-ahrzb Bot merged commit 25fc76d into master Jul 26, 2026
1 check passed
claude-agent-ahrzb Bot pushed a commit that referenced this pull request Jul 26, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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