The module cache is on by default, and a covered box is tuned by its first compile - #3936
Conversation
A run that executes (interp or -jit; daslang-live too) now rides the module cache silently at .jitted_scripts/module_cache/<script>-<hash>.dascache in the cwd - the hash is the normalized script path, so same-named roots never share a file - and finish() creates the missing parent directories. -no-module-cache opts out. The default stays off under -exe (one compilation unit in front of the optimizer is the faster binary - the artifact keeps the slow path), -compile-only (preflight and lint spawn one per file), -documentation and the debugger. An explicit -module-cache <path> is unchanged: it rides every mode, -exe included, prints the deser:/ser: verdicts, and is what the -ser/-deser exclusion guard keys on now. The default path is quiet end to end - the reader's per-record ser: lines and the dasbind cumulative-hash drift warning are gated on the new AstSerializer::quietCache - so a plain run prints nothing it did not print before. Measured on M5 (lcpp_bench, JIT DLLs warm): 16 s -> 2 s per warm start, both pairs exact; zen2 is 160 s -> 18 s. The ledger's deser-vs-parse hash divergence (plans/jit_compile_time.md item 1) is closed: the round-trip test's -deser run links 3/3 cached partitions and a cache write followed by a DLL cache hit was observed at 9364 functions. tests/module_cache/test_default_cache_path.das covers the default: a plain run writes one file and says nothing, the warm run adds none, -no-module-cache and -compile-only write none, an explicit -module-cache keeps the loud verdict. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nd never relaunches The compile-time ladder is now sidecar entry -> this box's class entry in the scope's shipped defaults profile -> fallback= -> reference, on both rails ([tune] directly, [tuned] through tune_kernel_pick). The profile is read off the [tune_scope] annotations on the AST (a macro global is a per-requiring-context copy) by the same locate the runtime adopt walks, so the two halves cannot pick different files; every candidate on the class ladder is a module-cache dependency, absent ones included. Stamps record tune_source (manifest / profile / fallback / grid) and tune_status reports it. Runtime: a fully covered scope writes no sidecar, bumps no relaunch counter and does not re-exec - a sidecar now means something was MEASURED on this box; a residue still adopts, races through --tune-only and relaunches. A scope that ships defaults must compile before the modules it covers, or its kernels stamp fallbacks while the guard reads the box as covered: the scope apply refuses a covers= module that already compiled. dasLLAMA's scope moves to dasllama_tune_scope.das, required by every kernel module. --tune / --tune-only are read at RUNTIME by the injected guard (tune_cli_force), and the auto guard is emitted for a complete scope too: the module cache can serve a compile whose macros never saw this run's argv - the profiles test had --tune silently ignored under the cache. The same class covers the environment: the host's default module-cache key now folds every DAS* variable and the binary's mtime+size, so a different tune policy or a rebuild is a different cache, never a stale hit. Mint instrumentation: @tune end carries ms= since its begin, the Metal crown race reports as the metal_crowns family, and dasllama_tuner stamps mint_gen_ms / mint_kernels_ms / mint_total_ms into the sidecar's provenance before archiving. Measured on M5 (lcpp_bench, no sidecar): 144 s and two compiles before, 73 s and one compile after - "covered by the 'arm-i8mm' defaults profile (49 kernel entries) - stamped at compile time, no restart", no sidecar written. Tests: llvm_tune_profiles asserts the covered box (no tuner, no relaunch, no sidecar, source=profile), --tune and --tune-only under the warm cache, and the reference policy; cant_tune_scope_after_covered is the ordering refusal; llvm_tune_scope / llvm_tune_manifest spawn their children -no-module-cache because they assert what a fresh compile prints; test_default_cache_path covers the DAS*-keyed path. A committed leftover of the profiles test's scratch dir is removed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nlinked A box its shipped class profile covers stamps its kernels at compile time and never needed the exchange; the residue races locally. Both exchange policies now default to off (accept "off", submit "never"), the typo fallback narrows to the same, and only an explicit exchange_* key in the config or the environment opts back in - so no first-start consent question is asked about a service that is not there. The client code stays whole for a reopen. The site drops the sidecars tab, the footer link and the "sidecars in the exchange" counter on every page; sidecars.html stays in place, unlinked and noindex. The server README, the control card's no-sidecar line, the engine architecture charter and the exchange client test follow the new default. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the cutoff fell A mid-chain edit was reported as "the whole cache invalidates". Measured: it does not - the prefix before the edited module is served and everything after re-parses, exactly the positional rule; the run only reads as cold when the heavy modules sit late in the chain (dasLLAMA's GPU classes: 7 of 12 s). The verdict could not show that: "deser: FALLBACK - modules reparsed from source" was printed whether 0 or 66 modules had been served. The reader now counts served records and remembers the record that cut the stream with its reason (file changed / module order changed / macro dependency changed / stale stream); the hosts print "deser: FALLBACK - N module(s) served from cache, re-parsed from '<file>' (<reason>)" - the prefix the round-trip test matches on is unchanged. A dependency-aware cutoff is ruled out in the ledger: a macro can rewrite any later module, so no require graph bounds the blast radius; require order stays the mitigation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ts, the sweeps' slowest files The per-gate seconds were scattered across a twenty-minute log, the sweeps were opaque single numbers, and the tests-aot / sequence / imgui gates started their clock after their build step, so the test_aot build never counted. Now every gate that builds before it runs reports "build Ns + run Ns" under its line, the three dastest sweeps run --timing-outliers 10 and their "Top 10 slowest files" table prints under the gate on PASS too, and the summary ends with the gates sorted by time with their share of the wall - a slow CI lane's local mirror names itself. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Facts move where they are read: the default module-cache key (src/builtin/ ARCHITECTURE.md, now numbered), preflight's breakdown (skills/internal/ preflight.md), the [tuned] perm precedence and the mint wall in provenance (ARCHITECTURE_MEASUREMENT.md 2.40-2.41, cited by resolve_perm and the tuner's main), the get_user_args slice (llvm_tune_internals.md). Two rules land: a module-cache read prints only when the serializer is not quiet (src/builtin/ REVIEW.md), and a dasLLVM test asserting a compile-time print spawns its child -no-module-cache (modules/dasLLVM/REVIEW.md). Two renames carry what comments said: pickSource/pickPath -> tuneSource/tuneSourcePath, metal_plan -> metal_crown_kernels. Duplicates of those documents are gone from the sources; what stays is site-local contract. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…dule-cache wins over -module-cache The default cache file was keyed by script path, DAS* environment and the binary alone, so a run under -v1syntax (or -jit, or another -project) hit the AST a differently-flagged run had stored. The key now folds every host argument up to `--`; the script's own arguments after it stay out, so the dev loop's varying `-- --model ...` still hits. -no-module-cache beside an explicit -module-cache <path> installed the explicit cache anyway. It now disables the cache outright, so a spawner can append it as an override. Both hosts, both fixed; the default-cache test carries the red-first cases (a host flag keys a new file, script args do not, the explicit file stays unwritten under the override). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es up with the compile-time profile read build_and_debug.md: the cache paragraph leaves "Fast JIT iteration" for a section of its own - it is on by default and not JIT-specific - with the key components listed one per line, a WHY per exclusion, the verdict lines named where they are claimed, and the dated box numbers dropped. CLAUDE.md's trigger row now names the cache. tune.md: a covered box never tunes at startup, -exe bakes the build box's profile stamps, the --tune hint fires only below the profile tier, the CI kill switch kills the rail but not the stamps, and "the module cache" is defined where it is first used. src/builtin/REVIEW.md: the silence rule splits into what the test pins and the duty on a new module-cache diagnostic. modules/dasLLVM/REVIEW.md: the env-read rule reduces to the residue its scanner test leaves, the vector emitter rule splits in two, and a repo-root path says so. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cache is refused beside -ser/-deser argv[0] of a launch by bare name through PATH does not stat, so the binary stamp silently dropped out of the default cache key and a rebuilt binary kept hitting the old file. Both hosts now resolve their own executable through getExecutablePathName (declared in sysos.h for the first time) and fall back to argv[0] only when the platform cannot answer. -no-module-cache beside -ser or -deser installed the explicit halves anyway. Those flags exist for their verdict, so silently disabling them would make a round-trip run lie; the host rejects the command line, as it already does for -module-cache beside them. The default-cache test carries both cases: a PATH launch keys the same file as a path launch, and the rejection names the conflict. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…scope as untuned stops instead of stacking processes The auto policy's re-exec had no depth limit. Any defect that flips a scope's completeness between the guard and the compile - a macro-time argv read served from a warm module cache was one - made each relaunch tune and relaunch again, and every link of that chain is a live process holding a JIT context: 1797 of them reset a 128 GiB box through swap during this branch's own audit. The re-exec now arms DAS_TUNE_RELAUNCH for its child, and a child that still finds the scope untuned reports the non-convergence and runs the stamps it has. llvm_tune_profiles.das case 8 pins the fuse, case 3 pins the arming, and the client prints the depth and the tune-status count so the profile tier's "tuned" reading is asserted too. Also here: the covered-box announce honours DAS_TUNE_VERBOSITY=silent; the one module-cache read diagnostic that stayed ungated is gated; the default cache test covers a truncated cache file and the fallback verdict that names the served count and the cutoff; the arm-neon profiles fixture is back as master tracks it - deleting it had emptied a REVIEW.das guard. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ocs follow the compile-time profile read The default-cache silence rule moves to tests/module_cache/REVIEW.md and the reader-gating duty to src/ast/REVIEW.md - each fires where its trigger is. dasLLVM's -no-module-cache rule names the property (macro output, or its absence) and exempts the cache's own tests; its sidecar rule shrinks to the section clause its WHY supports; the vector-emitter rule splits in two; the env-read rule reduces to its scanner's residue. dasLLAMA's timing-rig, measurement and override rules lose their location enumerations; the harness rule scopes the Metal crown race out. utils drops a routing line its subfolder already carries and defines the CI row it keeps naming. tune_for_this_box.md, which the SDK installs, describes the scope's new home and the covered-box start. The TDD auditor's charter caps what a control may spawn. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
test_tune_scope_wire_key pins the version_of / version_key pair by reading the scope declaration's source; the scope moved to dasllama_tune_scope.das with the compile-time profile read, and the test still read the old file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… gate, and each new checklist gets its folder's own document site-dasllama: sidecars.html no longer promises the automatic pickup a closed exchange cannot do - its lede states the class-profile default and the manual browse that remains; the opt-in flow and the transcript no longer producible are gone; the home lede and the story template drop the exchange, and the home page stops fetching a count for a tile it no longer has. The consent notice and the recorded /exchange fixture carry the closed defaults (accept off, submit never) and no longer quote a tuning wall a covered box never pays. src/ast gets ARCHITECTURE.md sec.1 (the module-cache read) and a REVIEW.das that scans trySerializeProgramModule for a print outside a quietCache guard; the prose rule reduces to the weakening residue. tests/module_cache gets its own ARCHITECTURE.md. Four checklist wordings follow their auditors: the server's route rule reaches a value a required module supplies, the site's claim rule reads dated entries by their date and standing copy by now, the dasLLAMA tests checklist names the wire-key pin and splits its registration duty out of the weakening ban. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The writer rewrites the whole cache file - served records re-serialized, then the fresh ones - not from the cutoff on; a record reparses in place when its header matched and its payload failed, whatever the process's warmth; the diagnostics list names the changed-file and failed-payload cases too. Cross-folder architecture cites become prose. The test charters state properties, name the fixtures folder, and describe the explicit-cache cases as they run. The output-check rule names both arms it pins, and the shared WHY reads as the code has it: the default cache is on unasked for an ordinary run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The two spawn-environment helpers differed by one value; the dupes gate saw the pair. One helper takes the DAS_TUNE_RELAUNCH value it arms. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, and -use-aot never installs the default cache The full preflight found the two places the default-on cache was wrong. daslib/build_const folds command-line flags and environment variables into constants at macro time; the cache key sees neither, so a warm compile served the constant an earlier run had folded (build_const_test, eight cases red under the default cache). The dependency pin now takes virtual inputs beside files - `env:NAME` is the variable's value, `arg:--flag` the flag's occurrences on the command line, the value after a bare flag included - captured and re-validated through the same stat, so a changed carrier cuts the stream exactly like a changed sidecar. Every build_const accessor pins what it read. test_aot -use-aot served deserialized ASTs that do not link against the AOT stubs by hash - 1190 files of error[50101]. An AOT-consuming run joins -exe, -compile-only and -documentation as a mode that never installs the default cache; the default-cache test skips under an AOT host and asserts that -use-aot writes no cache file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…h claim Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… emitter can still emit it The full preflight's AOT suite was red on 1190 files with error[50101], and the stubs were not stale but empty. The AOT generator, utils/aot/main.das, is a daslang script; its own root compile ran under the default cache, and the shared modules the cache served - rtti, ast, strings_boost - were constructed as plain Module, whose aotRequire answers no_aot. Every stub whose module graph touched one of them came out as "AOT disabled due to module requirements", dastest/testing.das first, so nothing linked. The deserializer now constructs ModuleDas, the parser's class, at both script module sites; the promoted-builtin site stays. The default-cache test runs the generator twice on a fixture requiring a shared daslib module and asserts the warm output carries the same functions as the cold one and no module reads as AOT-disabled - red before the fix. The desync skill gains the check and its wipe recipe includes dastest/_aot_generated, which holds the testing module's stub. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
build_const now calls it, and build_const is AOT'd into test_aot; a bound function a stub calls needs its declaration in the module's AOT header. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ile time, the exchange closed Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The default-cache “quiet” mode is not consistently enforced (some serializer warnings bypass quietCache), and the new parent-directory creation logic needs safer handling for Windows/UNC paths.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR changes daslang’s default runtime behavior to (1) enable an AST module cache by default for executing runs (silent, stored under .jitted_scripts/module_cache/), and (2) ensure shipped CPU-class tune profiles are applied at compile time so covered boxes don’t spawn tuners/relaunch. It also updates related docs, tests, preflight reporting, and dasLLAMA exchange defaults to reflect the new tuning story.
Changes:
- Enable a default-on (quiet) AST module cache for
daslanganddaslang-live, add-no-module-cache, and improve explicit-cache verdict reporting. - Extend module-cache invalidation to cover macro-pinned virtual inputs (
env:NAME,arg:--flag) and make deserialized modules AOT-capable (ModuleDas). - Update tuning/profile flow (compile-time profile stamping + relaunch fuse), plus new/updated tests, docs, and dasllama.io/exchange defaults.
File summaries
| File | Description |
|---|---|
| utils/REVIEW.md | Clarifies CI-row terminology and tool-deletion record rules. |
| utils/internal/preflight/main.das | Adds per-gate breakdown output and end-of-run time-by-gate summary; tweaks dastest timing flags. |
| utils/dasllama-server/tests/fixtures/exchange.json | Updates fixture defaults for exchange policy (closed/off by default). |
| utils/dasllama-server/test_exchange_client.das | Adjusts tests for exchange defaults and consent behavior. |
| utils/dasllama-server/REVIEW.md | Tightens fixture recapture rule wording to include module-supplied values. |
| utils/dasllama-server/README.md | Documents exchange-closed defaults and new tuning/profile behavior. |
| utils/dasllama-server/main.das | Updates top-level comments to reflect profile-first tuning and exchange opt-in. |
| utils/dasllama-server/control.html | Updates UI text describing tuning + exchange closure/opt-in. |
| utils/daslang/main.cpp | Implements default-on quiet module cache, -no-module-cache, and enhanced fallback verdict output. |
| utils/daslang-live/main.cpp | Implements default-on quiet module cache and -no-module-cache behavior for live reloads. |
| tests/module_cache/test_default_cache_path.das | New process-level tests for default cache path/keying, quietness, fallback verdict, and opt-outs. |
| tests/module_cache/REVIEW.md | Adds checklist pinning default-cache quietness expectations. |
| tests/module_cache/ARCHITECTURE.md | Documents why cache tests spawn processes and what each test covers. |
| src/builtin/REVIEW.md | Adds checklist rule for gating cache-related prints on quiet mode. |
| src/builtin/module_builtin_runtime.cpp | Adds commandLineArgumentOccurrences to support arg: virtual deps hashing. |
| src/builtin/module_builtin_ast.cpp | Extends addModuleCacheDependency to support env:/arg: virtual inputs (skip absolutize). |
| src/builtin/module_builtin_ast_serialize.cpp | Adds default cache path derivation, quiet mode plumbing, directory creation, and deserializer uses ModuleDas. |
| src/builtin/ARCHITECTURE.md | Documents default module-cache path keying inputs and flag behavior. |
| src/ast/REVIEW.md | New checklist binding the src/ast cache-diagnostic gating gate. |
| src/ast/REVIEW.das | New gate enforcing quietCache guards around reader diagnostics in trySerializeProgramModule. |
| src/ast/ast_parse.cpp | Adds hashing for env:/arg: deps and quietCache gating + cutoff attribution for cache read failures. |
| src/ast/ARCHITECTURE.md | Documents module-cache reader mechanism, cutoff semantics, and quiet diagnostics contract. |
| skills/tune.md | Updates tuning design docs for shipped profiles, runtime --tune read, and relaunch fuse semantics. |
| skills/internal/preflight.md | Documents preflight gate breakdown output and time-by-gate table. |
| skills/internal/llvm_tune_internals.md | Updates internal tune notes for profile stamping and runtime --tune decision. |
| skills/internal/build_and_debug.md | Rewrites front-end cache docs to match default-on cache + new flags and exclusions. |
| skills/internal/aot_hash_desync_debugging.md | Adds guidance for cache-related AOT stub emptiness and includes dastest in _aot_generated wipe. |
| site-dasllama/stories/2026-09-03-text-to-speech.html | Removes sidecars tab/footer links (exchange closed narrative). |
| site-dasllama/stories/2026-09-03-mtp-on-mac.html | Removes sidecars tab/footer links. |
| site-dasllama/stories/2026-08-30-apple-m5.html | Removes sidecars tab/footer links. |
| site-dasllama/stories.html | Removes sidecars tab/footer links. |
| site-dasllama/sidecars.html | Reframes page as archive (exchange closed), adds noindex, removes “CLI does this for you” section. |
| site-dasllama/REVIEW.md | Strengthens “publish-time truth” rule wording for standing copy vs dated posts. |
| site-dasllama/README.md | Notes sidecars page is unlinked + noindex while exchange is closed. |
| site-dasllama/ladder.html | Removes sidecars tab/footer links. |
| site-dasllama/index.html | Removes sidecars references and updates masthead copy to class-profile tuning. |
| site-dasllama/files/dasllama-io.js | Removes sidecar-count fetch/render from home page. |
| site-dasllama/_stories/template.html | Removes sidecars tab/footer links in story template. |
| plans/jit_compile_time.md | Updates dev-loop doc to reflect default-on module cache (no explicit -module-cache needed). |
| modules/dasLLVM/tests/llvm_tune_scope.das | Forces spawned children to use -no-module-cache to keep compile-time output assertions valid. |
| modules/dasLLVM/tests/llvm_tune_scope_early.das | Adds a fixture module used to test scope ordering constraints. |
| modules/dasLLVM/tests/llvm_tune_profiles.das | Updates tests to match profile-first behavior and adds relaunch fuse case. |
| modules/dasLLVM/tests/llvm_tune_profiles_client.das | Prints relaunch depth and logs tune status for profile tests. |
| modules/dasLLVM/tests/llvm_tune_manifest.das | Forces spawned children to use -no-module-cache and updates rationale. |
| modules/dasLLVM/tests/cant_tune_scope_after_covered.das | Adds compile-time negative test for scope ordering when defaults profiles are involved. |
| modules/dasLLVM/REVIEW.md | Adds rule requiring -no-module-cache for compile-time macro-output tests; clarifies other checklist wording. |
| modules/dasLLVM/ENVIRONMENT.md | Documents DAS_TUNE_RELAUNCH. |
| modules/dasLLVM/daslib/llvm_env.das | Adds tune_relaunch env config field. |
| modules/dasLLVM/ARCHITECTURE.md | Notes shipped profile inputs as cache dependencies and rebuild re-keying via binary stamp. |
| modules/dasLLAMA/tune_for_this_box.md | Updates “zero-config” tuning story to class profiles + residue tuning. |
| modules/dasLLAMA/tests/test_exchange_schema.das | Updates scope wire-key pin to new dasllama_tune_scope.das location. |
| modules/dasLLAMA/tests/REVIEW.md | Updates pinned-set wording and clarifies test_exchange_schema’s scope pin provenance. |
| modules/dasLLAMA/REVIEW.md | Broadens measurement checklist scope and clarifies “override” definition. |
| modules/dasLLAMA/HOW_TO_GET_SIDECAR.md | Updates wording to reflect profile stamping (compile-time) instead of adoption-at-start framing. |
| modules/dasLLAMA/harness/tune_kernels.das | Adds progress plan accounting for Metal crowns and emits progress begin/end for crowns. |
| modules/dasLLAMA/harness/REVIEW.md | Updates “last bench” rule scope to include post-list timings and excludes Metal crowns. |
| modules/dasLLAMA/harness/dasllama_tuner.das | Records mint wall times into sidecar provenance and adds arch citation. |
| modules/dasLLAMA/dasllama/dasllama_tune.das | Switches tuned-perm source from sidecar-only to tune_kernel_pick (sidecar/profile) and adds arch citation. |
| modules/dasLLAMA/dasllama/dasllama_tune_scope.das | Introduces a dedicated module containing the single [tune_scope] definition. |
| modules/dasLLAMA/dasllama/dasllama_math.das | Requires the new tune-scope module to ensure scope is on AST before tuned kernels resolve. |
| modules/dasLLAMA/dasllama/dasllama_math_gen.das | Removes inline scope definition and requires the new tune-scope module. |
| modules/dasLLAMA/dasllama/dasllama_math_default.das | Requires the new tune-scope module. |
| modules/dasLLAMA/dasllama/dasllama_math_aarch64_neon.das | Requires the new tune-scope module. |
| modules/dasLLAMA/dasllama/dasllama_kv_codec.das | Requires the new tune-scope module. |
| modules/dasLLAMA/dasllama/dasllama_exchange.das | Changes exchange defaults to off/never and updates consent notice copy. |
| modules/dasLLAMA/dasllama/dasllama_convert.das | Requires the new tune-scope module. |
| modules/dasLLAMA/CMakeLists.txt | Updates install comment to reference dasllama_tune_scope.das as defaults-profile resolver base. |
| modules/dasLLAMA/ARCHITECTURE.md | Updates companion index references for new measurement sections. |
| modules/dasLLAMA/ARCHITECTURE_MEASUREMENT.md | Updates tuning gate narrative and adds sections documenting [tuned] precedence + mint-wall provenance. |
| modules/dasLLAMA/ARCHITECTURE_ENGINE.md | Documents new scope module and exchange-closed defaults. |
| modules/dasLLAMA/.das_module | Adds dasllama_tune_scope to module initialization list. |
| include/daScript/simulate/aot_builtin.h | Declares commandLineArgumentOccurrences for use in front-end hashing. |
| include/daScript/simulate/aot_builtin_ast.h | Declares addModuleCacheDependency for AOT header completeness. |
| include/daScript/misc/sysos.h | Exposes getExecutablePathName used for stable binary stamp. |
| include/daScript/ast/ast.h | Documents virtual module-cache dependencies semantics in Program state. |
| include/daScript/ast/ast_serializer.h | Adds quietCache + cutoff/served fields and extends ModuleFileCache API (quiet + defaultPath). |
| daslib/build_const.das | Pins macro-read argv/env inputs as module-cache dependencies via arg:/env: virtual inputs. |
| CLAUDE.md | Updates skill table entry to mention front-end AST module cache. |
| .claude/agents/tdd-auditor.md | Adds safety guidance to cap negative control process spawning. |
Review details
- Files reviewed: 81/81 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟢 Approval recommended
The changes are extensive but internally consistent and covered by new/updated tests, with only a minor documentation clarification suggested.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
include/daScript/simulate/aot_builtin.h:20
- The public header comment for commandLineArgumentOccurrences is ambiguous about the returned string format. The implementation concatenates occurrences using non-printable separators (\x01 between a bare flag and its value, \x02 between occurrences), so callers might incorrectly assume it's whitespace- or newline-separated. Please document the delimiter scheme (and that this is intended for hashing/cache keys, not display parsing).
- Files reviewed: 81/81 changed files
- Comments generated: 0 new
- Review effort level: Lite
…Cache, and the no_fileio build compiles Six warnings the default cache could reach stayed unconditional - a version mismatch, a program read failure in either catch arm, a module not found, a module read failure, an empty stream - and each would have been a line every user sees on a stale cache. They are gated on quietCache like the reader's other diagnostics; the write-path warnings about unregistered ids stay loud, they report serializer defects. The default-cache test adds a garbage-header case beside the truncated one. The virtualInput flag in addModuleCacheDependency was read only under file IO, so the no_fileio build failed on -Werror=unused-variable; it now lives inside that block. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
There are confirmed functional issues in the new preflight timing breakdown parsing and in module-cache dependency hashing/keying that should be corrected before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
utils/daslang-live/main.cpp:904
- hostOptions currently records every argv token up to "--", including the positional script filename. Since ModuleFileCache::defaultPath already normalizes and hashes scriptPath separately, including the raw script argument here makes the cache key depend on script spelling and can split the default cache across equivalent invocations.
utils/daslang/main.cpp:801 - hostOptions currently records every argv token up to "--", including positional script filenames (e.g. "foo.das", "./foo.das"). Since ModuleFileCache::defaultPath already normalizes and hashes scriptPath separately, including the raw script argument here makes the cache key depend on the script's spelling and can split the default cache across equivalent invocations.
utils/internal/preflight/main.das:1141 - In slowest_files_block, the "[I] " prefix is stripped after strip(raw), which reintroduces leading spaces for timing rows (they start with " "), so the subsequent
first_character(ln)check sees a space and breaks out early. This prevents the per-file timing rows from ever being captured.
- Files reviewed: 81/81 changed files
- Comments generated: 1
- Review effort level: Lite
…gv pin is NUL-separated --jit-split-modules defaults to -1: per-das-module partitions with auto threads, the obj cache on. What the module cache does for parsing, this does for codegen across programs - every program that shares a module reuses its partition, so a test suite of one process per file compiles the engine once, and a warm edit re-emits only the changed suffix. The monolith, one context with full cross-module inlining, is --jit-split-modules=0; -exe and AOT emission are always one unit, so the default moves -jit runs only. The DLL-path-only warning fires only when the flag was actually given. Under the default the JIT test gate runs in 330 s where it took 396 s. The arg:--flag dependency pin separates its fields with NUL, which no argv string can contain, so two argument lists never hash alike. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Several newly introduced “explicit cache fallback” reporting paths and the default-silent cache contract have correctness gaps that should be fixed before merging.
Review details
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
src/ast/ast_parse.cpp:704
- In
trySerializeProgramModule, the record-header failure path (failed header deserialize ordepCount > SER_MAX_MACRO_DEPS) doesn’t setserializer_read->cutoffFile/cutoffReason. That means an explicit-module-cacherun can reportdeser: FALLBACKwithout naming the cutoff file/reason, even though this branch is a definite cutoff point in the stream.
Set cutoff metadata here (similar to the other cutoff branches) so the host’s fallback verdict can reliably report the cutoff file and why it fell back.
utils/daslang-live/main.cpp:260
cres.saveFailedis printed even whencacheQuietis true (the default-on cache). If the default cache can’t write (e.g. read-only cwd), this introduces user-visible output even though the default path is intended to be silent.
Gate this diagnostic on !cacheQuiet so only explicit/loud cache usage reports it.
utils/daslang/main.cpp:541
cres.saveFailedis reported even whencacheQuietis true (the default-on cache). That breaks the “default cache is silent” contract and can add unexpected stdout in read-only or permission-restricted working directories.
Consider gating this line on !cacheQuiet, so only explicit/loud cache modes report write failures.
- Files reviewed: 83/83 changed files
- Comments generated: 0 new
- Review effort level: Lite
… record-grade mint A covered box has no sidecar by design, so "stale or absent sidecar" no longer names the cause; the profile covers the kernels but is not this box's mint, and a record needs the mint. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The default module-cache path key construction uses newline separators, which can collide when argv/env values contain newlines and could cause incorrect cache-file reuse.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/builtin/module_builtin_ast_serialize.cpp:3361
- ModuleFileCache::defaultPath builds the cache-key string using "\n" as a separator between (script path + binary stamp + hostOptions) and each DAS* environment entry. Because argv/env strings can legally contain newlines, two different option/env sets can collapse to the same concatenated key, producing the same and potentially reusing the wrong cache file.
To make the key unambiguous, use a separator that cannot appear in argv/env (NUL) or length-prefix each field, and hash with a length-aware routine (e.g. hash_block64 over (data,size)) instead of hash_blockz64.
- Files reviewed: 84/84 changed files
- Comments generated: 0 new
- Review effort level: Lite
…n first use The cache stores a file record's name and length, never its text, so a module served from it had no source lines: a lint reading a nolint marker off the line fired anyway (LINT003 on daslib/clargs.das:939, nine instantiations, every program requiring daslib/lint - the utils test lane red), and an error message on a served module had no line to print. A served TextFileInfo now reads the file on first use and keeps the bytes only when the size still matches the recorded length. The default-cache test pins it: a generic with a nolint-marked variable in a module that stays served while the root re-parses and its lint pass runs - red before, green after. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
TextFileInfo::getSourceAndLength can return a null source pointer with a non-zero length after the new lazy reload path, and several callers dereference the pointer without a null check, risking a crash on missing/unreadable source files.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 85/85 changed files
- Comments generated: 1
- Review effort level: Lite
A served TextFileInfo whose reload fails carried its recorded length with a null source, and callers dereference the source whenever the length is non-zero. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The default (quiet) module-cache mode can still emit write-failure lines on stdout in daslang/daslang-live, violating the “silent by default” contract and potentially breaking tooling that depends on clean output.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
utils/daslang-live/main.cpp:260
daslang-livedefaults the module cache to quiet mode, but still prints a write-failure line even whencacheQuietis true. This makes ordinary (flagless) runs emit output in read-only or otherwise unwritable working directories, violating the “default cache is silent” contract.
utils/daslang/main.cpp:541- In the default (quiet) cache mode, the host still prints
ser: cannot write ...oncres.saveFailed, which breaks the documented/tested contract that the default cache is silent unless the user explicitly asked for cache output. If the default cache cannot be written, it should fail silently (or at least not emit normal stdout lines) the same way other quiet-cache diagnostics are suppressed.
- Files reviewed: 85/85 changed files
- Comments generated: 0 new
- Review effort level: Lite
A command string that starts with a quoted exe path is mangled by cmd /c quote stripping, so every child in the file failed on the Windows lane with 'The filename, directory name, or volume label syntax is incorrect'. The paths carry no spaces on any lane; the sibling tests already spell them bare. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
It is a large cross-cutting behavior change touching core compiler cache semantics, tuning policy behavior, and multiple tools/tests/docs, warranting final human review.
Review details
- Files reviewed: 85/85 changed files
- Comments generated: 0 new
- Review effort level: Lite
Behavior change:
daslanganddaslang-livekeep an AST module cache by default, at.jitted_scripts/module_cache/under the current directory, and-jitcodegen is split per das-module by default.-no-module-cacheturns the cache off;--jit-split-modules=0selects the one-unit codegen.-exe,-compile-only,-documentation,-use-aotand the debugger never use the cache, and-exe/AOT emission are always one unit.A dasLLAMA start compiled the whole module graph on every launch, about three minutes on the zen2 box, twice per mint. A box the shipped tune profile already covered still spawned a tuner, wrote a sidecar and relaunched itself. The exchange server on dasllama.io existed to shave that time.
The module cache is on for any run that executes. Its file is keyed by the script path, the host command line before
--, everyDAS*variable and the running binary's mtime and size; arguments after--do not key it. A macro can pin what it read as a dependency: a file, or the new virtual inputsenv:NAMEandarg:--flag, whichdaslib/build_constuses for every carrier. A served module reloads its source text from the file on first use, so lint markers and error lines read as a parse would.-no-module-cachewins over-module-cacheand is rejected beside-ser/-deser. The fallback verdict names the served count and the cutoff. Split codegen is what the module cache is for parsing: the obj cache serves every program that shares a module, so a suite of one process per test file compiles the engine once.The shipped class profile is read at compile time: a covered box stamps its picks with source
profile, spawns no tuner, writes no sidecar and never relaunches; a partly covered box adopts and races only the families its ISA unlocks.--tuneis read at runtime by the guard, because a warm cache serves a compile whose macros never saw this run's argv. The relaunch carries a fuse (DAS_TUNE_RELAUNCH): a relaunched child that still finds a scope untuned reports it and runs, instead of stacking processes. dasLLAMA's[tune_scope]moves into its own module; the exchange client is off by default and its page says the exchange is closed. preflight prints a time-by-gate table.Where to look:
ModuleFileCache::defaultPathand the install site inutils/daslang/main.cpp;statAndHashFileDependencyinsrc/ast/ast_parse.cpp;TextFileInfo::getSourceAndLengthinsrc/simulate/debug_info.cpp; the split default inmodules/dasLLVM/daslib/llvm_jit_run.das;profile_kernels,tune_cli_forceandtune_auto_prepareinmodules/dasLLVM/daslib/llvm_tune.das.Validation, claims, ledger
Validation
D:\Work\daScript, Ninja Release,bin\daslang.exe -jit modules/dasLLAMA/benchmarks/lcpp_bench.das): fresh start 239 s with one JIT compile (before: two compiles of about 180 s each and a relaunch), warm start 6 s, warm with-no-module-cache57 s. M5 Max (this tree, same script): fresh start 144 s to 73 s; the covered box raced nothing, wrote no sidecar, did not relaunch.lcpp_bench --ngl 0 -p 512 -n 128 -r 5 --for-debug-purposes,DASLLAMA_ALLOW_UNTUNED=1on both arms, one process at a time, weights warm): Qwen3.8-27B Q4_K_M pp512 66.9 ± 0.8 split vs 66.6 ± 1.3 monolith, tg128 11.53 ± 0.05 vs 11.58 ± 0.06; Qwen3-30B-A3B Q8_0 pp512 375 ± 16 vs 359 ± 13, tg128 70.1 ± 4.3 vs 70.4 ± 3.1. No measurable cost. A first pass with cold weights read 4-5% lower on the split arm, which ran first; the order effect, not codegen. The JIT test gate runs in 330 s under split where it took 396 s.-jiton the M5 Max: dasLLVM 74/74 (again under the split default); dasLLAMA model-free 85 files, 1447 tests, 0 failed, 85 skipped for absent models; dasLLAMA Metal kernel units 26/26;test_kv_codec42/42;test_exchange_schema23/23;tests/module_cache6/6; the lint tool's own suite 118/118 under dastest.preflight --fullran once (1058 s; tests-jit 396 s, sphinx 199 s, tests-aot 123 s, tests-interp 99 s, imgui 84 s, lint 43 s, the rest under 25 s each): 20 gates green, 3 red. The reds were three real defects of the default-on cache, each fixed red-first and validated with its targeted gate: (1)build_constfolds argv and env at macro time, invisible to the key - the pin's virtual inputs; interp and JIT suites green after. (2) The AOT generatorutils/aot/main.dasis a daslang script, and the shared modules its root compile took from the cache were constructed as plainModule, whoseaotRequireanswers no, so every stub touchingrtti/ast/strings_boostcame out empty and all 1190 tests failed to link; the deserializer now buildsModuleDas, andtests/module_cacheruns the generator cold and warm asserting the same functions come out; the AOT gate is green after a full stub wipe (424 s, 314 s of it regeneration). (3)add_module_cache_dependencyneeded its declaration in theastAOT header oncebuild_constcalled it.daslib/lintfired LINT003 through anolint:marker ondaslib/clargs.das:939in every program requiring it. A servedTextFileInfonow reloads the file on first use, accepted only while the size matches;tests/module_cachepins it with a marked generic in a served module while the root re-parses (red before, green after), and the lint tool's suite is green under dastest again.tests/module_cache/test_default_cache_path.das: the key omitted the host flags;-no-module-cachelost to an explicit-module-cache; a PATH launch'sargv[0]does not stat, so the binary stamp dropped out of the key;-no-module-cachebeside-ser/-deserstill installed them. The review rounds gated the six remaining read-path serializer warnings on the quiet flag and made the argv pin NUL-separated.--tuneread turned the relaunch into an unbounded process chain (1797 live processes, 417 GiB) and reset the audit box through swap. That is the defect the fuse closes;llvm_tune_profiles.dascase 8 pins it. The auditor's charter now caps what a control may spawn.llvm_tune_profiles.dascase 2 serve the stale no-profile AST; thesrc/astgate reports an ungatedser: read failedline when its guard is removed.Claims - stated, not tested
-exe,-documentationand the debugger, and daslang-live's default cache path, share the tested daslang code shape; no test spawns those modes.ser: read failedline and the six serializer warnings gated in the review round have no red-first case: a truncated file or a garbage header trips the stream-level check first, whose silence the test pins.-exestamp path bakes the build box's class-profile winners into a standalone exe; stated inskills/tune.md./exchangefixture underutils/dasllama-server/tests/fixtures/was edited by hand to the closed defaults and the shortened consent notice, not re-captured from a running server.modules/dasLLAMA/REVIEW.mdwas applied by hand toutils/dasllama-server/main.dasand its README.Not done
test_ple_modesis a model suite in the model-free list (560 s of the 2825).REVIEW.dasgate undermodules/dasLLVMthat reds a test spawning a child daslang without-no-module-cache(allow-list: the AOT arm ofllvm_tune_scope.das); one undermodules/dasLLAMA/harnesspinningdot_q8q8_laneq4x4as the lastbenchesrow.[arch]citation, sosrc/builtin/ARCHITECTURE.mdsec.2 is cited by nothing and the contract's anchor-plus-citation clause is unsatisfiable there; the dasLLAMA companion index assigns sec.2.28 to three companions; a dragon proposes moving the two tests-only rules ofmodules/dasLLVM/REVIEW.mdinto amodules/dasLLVM/tests/REVIEW.md.lcpp_bench's tune gate still refuses aprofile-stamped box for a record, by design (a record needs this box's own mint, the profile carries kernels only); its message now says so.tests/module_cachetests each keep their own copy of the two spawn helpers, the folder's existing shape; a shared fixture module is a follow-up. The other exact hits are the one-expression-function and enum-to-string shape classes.profile_kernelswalks the AST once per[tune]apply with no memoization.🤖 Generated with Claude Code