quote lowering phase 5: JIT lane — jit_enabled triggers QuotePass, lift -jit folder skips#3114
Conversation
…ft -jit skips
QuotePass now lowers quotes when policies.jit_enabled is set (daslang -jit,
dastest -jit, jit.exe), so quote/qmacro code JITs natively instead of
panicking the whole file. Three bugs surfaced on first contact:
* Wrapper return type: the generated `quote`lowered`N function used
autoinfer, leaking the concrete node type (quote(0) -> ExprConstInt?
instead of Expression?), which broke
`cond ? clone_expression(x) : quote(0)` (30411 — cond arms must match
exactly). Now fn.result = clone_type(expr._type) — identity with the raw
quote's static type by construction.
* Macro-module programs are excluded from the jit trigger: macro contexts
are never JITted (llvm_macro's is_compiling_macros skip) and the lowered
construction frames overflowed the macro-context stack at apply time.
Infer-time predicate is prog.flags.needMacroModule —
is_compiling_macros() is only true during makeMacroModule's simulate,
not when QuotePass runs. ast_infer_type.cpp's noAot-skip mirrors the
same predicate. Raw quotes that still reach JIT (runtime-used functions
from macro modules) now fall back per-function via DisableJitVisitor's
new preVisitExprQuote (warn + disable) instead of failing the file.
* The jit lane gets the same 1MB stack the -aot-macros flow always
shipped with (daslang -jit in main.cpp, dastest -jit in suite.das and
the ser path, jit.exe). A module that is macro-CALLED but not itself a
macro module (daslib/linq_fold_decs — plain functions invoked from
linq_boost's fold macros) is not excluded by needMacroModule, so its
lowered quotes evaluate at macro-apply time on the calling macro
module's context stack; without the bump that overflowed ("stack
overflow while calling @linq_fold_decs::`quote`lowered`17" -> 31206 ->
consumer fails to compile), order-dependently — the margin varies with
module-cache state, so single-folder runs passed while full-tree
root-form runs failed deterministically. Macro-module programs inherit
consumer policies, so macro contexts get the headroom too.
Coverage lifted: the -jit folder skips in tests/.das_test (quote, ast,
ast_match, no_aot, gc) and the matching jit_cache_all_tests excludes are
gone; tests/template/test_push_block_list [no_jit] markers removed
(qmacro_block JITs now). Folder runs: quote 20/20, ast 1/1, ast_match
380/380, flatten/no_aot 14/14, gc 48/48, template 10/10.
Not quote-related, found while lifting the gc skip: LLVM JIT does not
implement force_escape_free / force_allocate_on_stack (heap grows where
interp stays flat; scope-exit free aborts on JIT-allocated owning nodes
under persistent_heap; nested new-in-make-struct field arrives null).
Scoped [no_jit] markers on the worker functions in the three tests/gc
files keep the rest of the folder JIT-covered; the JIT gap is a separate
work item.
No LLVM_JIT_CODEGEN_VERSION bump: emitters are unchanged (gate +
collection only) and the dll cache hash folds per-function AOT hashes, so
lowered ASTs self-invalidate. CLAUDE.md's pointer to the constant fixed
(it lives in llvm_jit_run.das, not llvm_macro.das).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Enables quote lowering in the LLVM JIT lane by treating policies.jit_enabled as a trigger for daslib/quote’s QuotePass, so runtime quote/qmacro code can compile under JIT without failing whole-file codegen. This also removes broad -jit test-folder skips and replaces them with narrower per-function fallbacks/markers where JIT still lacks features.
Changes:
- Expand
QuotePassgating to includejit_enabled(with a macro-module exclusion) and fix lowered-wrapper return typing. - Increase policy stack size to 1MB in JIT-enabled entry points to prevent stack overflows from large lowered-quote construction frames.
- Lift
-jitfolder skips across tests and add targeted[no_jit]on specific GC escape-analysis workers where LLVM JIT behavior diverges.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/jit/main.das | Ensure standalone jit.exe registers LLVM JIT support and applies a 1MB stack when jit_enabled is used. |
| utils/daScript/main.cpp | Apply the same 1MB stack bump when running with JIT enabled (not just -aot-macros). |
| utils/CMakeLists.txt | Lift JIT prewarm excludes that were only needed before JIT quote lowering; keep module-based excludes. |
| modules/dasLLVM/daslib/llvm_jit.das | Add per-function JIT disable on raw ExprQuote (warning + interpreter fallback). |
| daslib/quote.das | Trigger quote lowering under jit_enabled (excluding macro-module programs) and preserve wrapper return type. |
| src/ast/ast_infer_type.cpp | Align noAot marking for quote() with the new jit_enabled lowering gate (incl. macro-module exclusion). |
| dastest/suite.das | Apply 1MB stack bump for JIT-enabled test compilation to support lowered quote frames. |
| dastest/dastest.das | Apply 1MB stack bump during JIT-enabled serialization compilation. |
| tests/.das_test | Remove -jit directory skips (quote/ast/ast_match/no_aot/gc) while keeping --use-aot gating. |
| tests/template/test_push_block_list.das | Remove [no_jit] markers now that block-form qmacro lowering works under JIT. |
| tests/gc/test_gc_escape_free.das | Add [no_jit] to the specific worker affected by missing JIT force_escape_free behavior. |
| tests/gc/test_gc_escape_free_frees.das | Add [no_jit] to escape-free workers (and control) due to JIT divergence/crash cases. |
| tests/gc/test_gc_allocate_on_stack.das | Add [no_jit] to ascend/owning workers due to missing JIT force_allocate_on_stack support. |
| skills/writing_tests.md | Update testing guidance to match lifted -jit folder gating and recommend per-function [no_jit]. |
| QUOTE_LOWERING.md | Document Phase 5 completion and key findings for the JIT lane. |
| CLAUDE.md | Correct the documented location/meaning of LLVM_JIT_CODEGEN_VERSION. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The first cut bumped policies.stack to 1MB in the three jit drivers (daslang -jit, dastest -jit, jit.exe), mirroring the -aot-macros flow. But policies.stack also sizes the PRODUCED program's runtime context: cross-compiled wasm executables tried to carve 1MB out of wasm linear memory and trapped at runtime (CI wasm_cross, f2s.wasm "thrown Wasm exception"). Reverted the driver bumps. Program::makeMacroModule now sizes the macro context to max(getContextStackSize(), 1MB) when aot_macros or jit_enabled is set — the headroom lands exactly where lowered quotes evaluate at macro-apply time, for any embedder, without touching the produced program's stack. The -aot-macros global bump keeps its historical behavior. Full-tree dastest -jit re-verified green with the macro-context-only fix: 10071 tests, 10065 passed, 0 failed, 0 errors, 6 skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…de-jits The DisableJitVisitor warning and the codegen backstop said "without aot_macros lowering" — misleading in the jit lane, where jit_enabled is the usual trigger. Both now name the lowering generically with its gates (jit_enabled / aot_macros) and state that lowering didn't run for the defining module. skills/writing_tests.md no longer leads with "-jit / --use-aot" argv scanning for the .das_test filter — only --use-aot checks remain after the skip lift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t_macros makeMacroModule's 1MB headroom triggered on policies only; a macro module opting into lowering via `options aot_macros` would still lower its quotes and could overflow its macro context at apply time. The condition now mirrors the full QuotePass gate (same getBoolOption clause the ast_infer_type.cpp noAot-skip uses). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… locals Debug lanes caught what Release hid: heap_collect frees a heap object whose only reference is a local in a jitted frame (native-stack locals are invisible to the collector), so test_gc_escape_free's later `delete n` double-frees and trips the memory_model.h assert. Probe-proven independent of force_escape_free (interp keeps n.x=7 across collect+churn, jit reads the reused slot). That makes GC-semantics tests systemically unsound under JIT — restore the folder-level skip in tests/.das_test (root cause in the comment), mirror --exclude gc on jit_cache_all_tests, and revert the per-function [no_jit] markers the skip supersedes (tests/gc back to master state). The 3 linux-Debug typer_errors "failures" were collateral: the gc crash sent CI into the --isolated-mode fallback, whose 32 workers all cache-miss the same harness dll (`-jit -jit` hashes differently than the prewarmed outer run) and race writing one .o/.dll path — half-written-object, truncated-file, and post-write-verify assert. Pre-existing infra bug, noted in QUOTE_LOWERING.md; it heals here because the sweep no longer crashes into the fallback. skills/writing_tests.md updated: gc keeps its -jit skip; a green local Release sweep does not prove a lifted skip sound — Debug CI is the oracle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Debug CI round (3 red lanes: linux/darwin15/darwin26 Debug) — root-caused, fixed in 93cfca0. All three lanes trace to one root cause that only Debug builds can see, plus collateral: 1. The real bug — var n = make_node(7) // returned pointer, held in a local
unsafe { heap_collect(true, true) }
// ... churn 1000 allocations ...
print("n.x={n.x}") // interp: 7 -jit: -1 (slot reused)That makes GC-semantics tests systemically unsound under JIT — almost certainly why the 2. The 3 linux-Debug Lesson recorded in Local gates after the fix: tests/gc interp 48/48; full |
Phase 5 of the quote-lowering plan (
QUOTE_LOWERING.md; AOT lane = #3109):policies.jit_enablednow triggers thedaslib/quoteQuotePass, so quote/qmacro code compiles natively under the LLVM JIT instead of panicking the whole file, and the-jitfolder skips are lifted.What changed
daslib/quote.das— QuotePass gate widens toaot_macros || (jit_enabled && !needMacroModule) || options aot_macros. Three traps found on first contact, all fixed here:`quote`lowered`Nfunction usedautoinfer, leaking the concrete node type (quote(0)→ExprConstInt?instead ofExpression?) and breakingcond ? clone_expression(x) : quote(0)(30411 — cond arms must match exactly; daslib/typemacro_boost has exactly this construct). Nowfn.result = clone_type(expr._type)— identical to the raw quote's static type by construction.llvm_macro'sis_compiling_macros()skip), so lowering there is pure cost — and the lowered construction frames overflowed the macro-context stack at apply time. Infer-time predicate isprog.flags.needMacroModule(is_compiling_macros()is only true duringmakeMacroModule's simulate, not when QuotePass runs). Theast_infer_type.cppnoAot-skip mirrors the same predicate.daslib/linq_fold_decs— plain functions invoked from linq_boost's fold macros) DO lower, and their lowered quotes evaluate at macro-apply time on the calling macro module's context stack. Without headroom:stack overflow while calling @linq_fold_decs::`quote`lowered`17→ 31206 → consumer file fails to compile, order-dependently (the margin varies with module-cache state — single-folder runs passed, full-tree runs failed deterministically; took a long bisect to pin). Fix:Program::makeMacroModulesizes the macro context tomax(getContextStackSize(), 1MB)whenaot_macros || jit_enabled— the headroom lands exactly where lowered quotes evaluate, for any embedder. (First cut bumpedpolicies.stackin the three jit drivers, mirroring-aot-macros— that regressedwasm_cross:policies.stackalso sizes the PRODUCED program's runtime context, and cross-compiled wasm trapped trying to carve 1MB out of linear memory. Reverted in the follow-up commit; the-aot-macrosglobal bump keeps its historical behavior.)modules/dasLLVM/daslib/llvm_jit.das—DisableJitVisitorgetspreVisitExprQuote(warn + per-function interpreter fallback). Raw quotes can still reach JIT from macro modules' runtime-used functions; previously that failed the whole file's codegen, now just that function stays interpreted (e.g.ast_match'speel_lambda_*helpers).tests/.das_test-jitskips (quote, ast, ast_match, no_aot, gc) and the matchingjit_cache_all_tests--excludes are gone;tests/template/test_push_block_list.das[no_jit]markers removed (its stated reason — JIT can't lower ExprQuote — no longer holds; qmacro_block now JITs).LLVM_JIT_CODEGEN_VERSIONbump: emitters unchanged (gate + collection only), and the dll cache hash folds per-function AOT hashes, so lowered ASTs self-invalidate. (CLAUDE.md's stale pointer to the constant fixed — it lives inllvm_jit_run.das.)Found along the way (pre-existing, not fixed here)
force_escape_free/force_allocate_on_stack— three symptoms in tests/gc: heap grows where interp stays flat; the scope-exit free aborts (deleting ... which is not a chunk pointer) on JIT-allocated owning nodes underpersistent_heap; nestednew Inner(...)make-struct field arrives null. Scoped per-function[no_jit]markers on the workers in the three escape-analysis test files keep the rest of the gc folder JIT-covered. The JIT gap itself is a separate work item — flagging for a fix-vs-document decision.recover(exit 127, no diagnostic; LLVM 22.1.5 Windows, opt-level 3). Reproduces with master daslib state and an identical content-addressed dll hash, so unrelated to this PR; CI's LLVM doesn't show it (and the CI step has the--isolated-modefallback). Affectstests/language/table_operations.dasandtests/linq/test_linq_table_source.daslocally.Verification
dastest -jit(CI form, two pre-existing-crash files excluded locally): 10071 tests, 10065 passed, 0 failed, 0 errors, 6 skipped — ~550 more tests under JIT than the skip-era baseline. Re-verified identical after the makeMacroModule fixup. Folder detail: quote 20/20, ast 1/1, ast_match 380/380, flatten/no_aot 14/14, gc 48/48, template 10/10.test_aot -use-aot, fresh stubs): 9464 / 9458 / 0 / 0 / 6..dasfiles (two pre-existing lint findings inutils/jit/main.dascleaned while touching it).Phase 6 (follow-up, per QUOTE_LOWERING.md): lift
options no_aoton the ~41 quote-using test files case-by-case, JIT-of-macro-contexts behind a policy, chunked lowered-construction frames (removes the macro-context special-casing), and the imgui_demo compile-time measurement.🤖 Generated with Claude Code