LINT030 on for the tree: the rule reads the callee's body, and the real order dependences are sequenced - #3994
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The new Faker ordering test compares urange64 via string formatting instead of type-safe value equality, making it unnecessarily brittle.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Enables LINT030 across this repository (while keeping the SDK default off) and performs a tree-wide sweep to eliminate argument-evaluation-order dependent call sites by sequencing side-effecting/var-taking expressions before the outer call, including one generator tweak and regenerated Vulkan wrappers.
Changes:
- Turn on LINT030 in
.lint_configand update related documentation/checklists to reflect repo-vs-SDK policy. - Sequence order-sensitive call arguments across multiple modules (dasLLAMA/dasLLVM/dasMetal/dasSpirv/utils) to make behavior consistent across interpreter/JIT/AOT tiers.
- Update
daslib/faker.dascomposite draws to bind scalar draws in order, and add a regression test for that ordering.
File summaries
| File | Description |
|---|---|
| utils/internal/flatten-fuzz/main.das | Sequences RNG draws before constructing int3 inputs to avoid argument-order dependence. |
| tests/module_cache/test_require_module_now.das | Removes per-file LINT030 opt-in now that the rule is enabled repo-wide. |
| tests/module_cache/test_module_groups.das | Removes per-file LINT030 opt-in now that the rule is enabled repo-wide. |
| tests/module_cache/test_macro_dep_invalidate.das | Removes per-file LINT030 opt-in now that the rule is enabled repo-wide. |
| tests/module_cache/test_generic_instance_origin.das | Removes per-file LINT030 opt-in now that the rule is enabled repo-wide. |
| tests/module_cache/test_descriptor_manifest.das | Removes per-file LINT030 opt-in now that the rule is enabled repo-wide. |
| tests/module_cache/test_deferred_modules.das | Removes per-file LINT030 opt-in now that the rule is enabled repo-wide. |
| tests/module_cache/test_default_cache_path.das | Removes per-file LINT030 opt-in now that the rule is enabled repo-wide. |
| tests/module_cache/REVIEW.md | Updates folder checklist language to forbid silencing LINT030 rather than requiring per-file opt-in. |
| tests/module_cache/ARCHITECTURE.md | Clarifies cross-tier argument-order behavior motivating the rule in this folder’s architecture. |
| tests/daslib/test_faker.das | Adds test coverage pinning composite Faker draws to scalar draw order. |
| modules/dasVulkan/generator/vk_emit_boost.das | Emits pre-call vkcount_* locals (deduped) so counts are computed before array_addr/var arguments. |
| modules/dasVulkan/generator/REVIEW.md | Tightens checklist wording for report growth triggers (naming/commit-message expectations). |
| modules/dasVulkan/daslib/vulkan_cmds.das | Regenerated wrappers to use precomputed vkcount_* locals in calls. |
| modules/dasSpirv/spirv/spirv_emit.das | Sequences composite constant emission (type + lane constants) before const_composite. |
| modules/dasMetal/metal/msl_emit.das | Sequences MSL name computation before inserting UserFn entries. |
| modules/dasLLVM/tests/llvm_tune_fat.das | Sequences spawn_child return codes into locals before assertions. |
| modules/dasLLVM/REVIEW.md | Updates checklist routing/architecture docs and moves a rule to the top section. |
| modules/dasLLVM/daslib/llvm_jit.das | Hoists length(...) results into locals before passing arrays to LLVM C API calls. |
| modules/dasLLVM/daslib/llvm_jit_run.das | Updates LLVM_JIT_EMITTER_HASH to reflect emitter-source changes. |
| modules/dasLLVM/daslib/llvm_jit_intrin.das | Hoists vector element counts before LLVMConstVector calls. |
| modules/dasLLVM/daslib/llvm_jit_di.das | Hoists member/entry/subrange counts before DI builder calls. |
| modules/dasLLVM/daslib/llvm_boost.das | Hoists lengths before LLVM helper calls (LLVMConstVector, LLVMAddIncoming, intrinsic decl). |
| modules/dasLLAMA/tests/test_run_suites.das | Sequences run_runner return codes into locals before assertions. |
| modules/dasLLAMA/tests/test_box_profile.das | Sequences set_mtime result into local before assertion. |
| modules/dasLLAMA/REVIEW_GPU.md | Refines parity requirement wording to focus on changes that can alter selection/computation. |
| modules/dasLLAMA/dasllama/dasllama_mtp_gemma.das | Sequences plan_tensor results into locals before pushing into offset arrays. |
| modules/dasLLAMA/dasllama/dasllama_metal_prefill.das | Hoists KV base pointer calculations into locals before kv_store_batch call. |
| modules/dasLLAMA/dasllama/dasllama_gpu_resident.das | Hoists KV base pointer calculations into locals before KV sync/read calls. |
| modules/dasLLAMA/dasllama/dasllama_gemm_gen.das | Sequences intermediate LLVM values (e.g., iq2xs_word16, table parts, shifts) before dependent ops. |
| modules/dasLLAMA/dasllama/dasllama_blocks.das | Hoists KV base pointers into locals before hydration call. |
| doc/source/reference/language/lint.rst | Updates LINT030 rationale text while keeping “ships off” framing for the SDK docs. |
| daslib/faker.das | Sequences composite draws (vectors/ranges/64-bit) by binding scalar draws before constructors. |
| daslib/ARCHITECTURE_LINT.md | Clarifies LINT030 shipping posture (SDK default-off vs repo enabled via .lint_config). |
| CLAUDE.md | Updates repo guidance to state LINT030 is enabled in this tree. |
| .lint_config | Enables LINT030=true for this repository. |
Review details
- Files reviewed: 36/36 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…efault-off seed, and every site the sweep found runs its writing call as a statement of its own - faker's vector and range draws bind each draw before the constructor, so the draw order is the left-to-right order every tier read; the dasLLAMA kv accessors (kv_layer_kbase / kv_layer_vbase beside each other), the gemm emitter's iq2xs word and VPMULTISHIFTQB shifts, the VPERMI2B lookup's per-level pair (vbmi_lookup_level), the gemma drafter's plan_tensor offsets and two test message strings; the dasLLVM helpers' array_data_ptr beside its length (llvm_boost, llvm_jit, llvm_jit_di, llvm_jit_intrin) and the fat-tune test's spawn beside its log; the MSL emitter's user-function name beside the table it joins; the SPIR-V emitter's folded vector constants, type and lanes emitted in the order the interpreter took so the goldens hold; the flatten fuzzer's three draws; and the Vulkan generator emits `let vkcount_<arr> = uint(length(arr))` before a wrapper's call instead of the count inline beside array_addr, vulkan_cmds.das regenerated with --no-cpp; extended_checks gains a step running every rule fixture under utils/lint/tests through dastest, since the linter skips the folder and nothing else enforced their expect counts; lint.rst, CLAUDE.md, ARCHITECTURE_LINT.md and tests/module_cache/REVIEW.md say the rule is on here and off in the SDK Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…a site of the shape is a defect, CLAUDE.md's bullet says the rule is on in this tree and stops, ARCHITECTURE_LINT.md keeps the default-off reason with the seed and names .lint_config as what turns it on here, tests/module_cache/REVIEW.md replaces the child-as-statement duty LINT030 now enforces with the ban on silencing it and names no tier, tests/module_cache/ARCHITECTURE.md says the same about the nested run, and the seven per-file `options _lint` lines go with the tree policy in place; the dasVulkan generator checklist keys its two report rules on a name added rather than a file grown and points at the module's ledger, and the generator's link comment names the let Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…f before the fixed_array literal - the shape LINT030 does not reach, since a make-array is no call; modules/dasLLAMA/REVIEW_GPU.md keys the GPU-parity duty on a change that can alter what a served path computes or selects, so a local binding of the same call sits outside it by criterion; modules/dasLLVM/REVIEW.md names its fourth architecture document in the opening and carries its routing line there Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…alks utils/lint, fixtures included, so the step ran the same 119 tests a second time; and the JIT emitter-source pin moves with the four hoisted length reads in the emitter files - no emitted code changed, so LLVM_JIT_CODEGEN_VERSION stays Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…run of the tests/msl suite: whether an emitter edit moved the emitted text is the suite's decision, never the das source's Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… lanes in: an int2, a float4, a urange64 and a uint64 drawn from one Faker equal the scalar draws taken in sequence from a twin seeded the same way - the contract the sequenced draws state, red with two lanes swapped Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…k under the 300-line gate Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rve-out keeps a rename, a comment and a bake path and drops the local binding, since a binding that fixes an order is a change to a value the order decided; lint.rst's LINT030 section keeps the ships-off reason an SDK reader can act on and names no repository's config; the dasMetal which-suite-runs rule goes, CI's per-PR test lanes running tests/msl already Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…call writes a variable only when its body writes that parameter during the call - Lint030WriteScan walks the callee once and keeps the compiler's write marks on the parameter except an address taken inside a return (a pointer handout), a pointer the parameter holds read out or reached through (the pointee's, not the parameter's; assigning the pointer field itself still counts), and the mark every writable argument of a call gets, which that callee's own body decides for that slot, memoized per function site; a builtin or extern stays a writer, a by-value parameter never is; and a read that is the outer call's own by-reference argument - the variable or a chain of plain fields rooted at it - is not a dependency. With that reading the tree-wide arming reports nothing in tracked files, so the pointer-handout, kv-accessor, builder-emitter and Vulkan-generator sites the earlier sweep sequenced return to master's text, the emitter pin with them; the fixture pins every discount with a positive beside it - a kept address, a nested callee's write, an assigned pointer field, a block run by a sibling - and the negatives: the handout, the field of a by-reference target, a variable passed on beside another argument's write, a method through a pointer field, a pointer field read; ARCHITECTURE_LINT.md and lint.rst say what the scan discounts Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
55582d0 to
df05146
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The new lint030_callee_writes memo key can conflate generic instantiations (same source site, different mangled names), potentially producing incorrect LINT030 results in type-dependent generic bodies.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 25/25 changed files
- Comments generated: 1
- Review effort level: Lite
…tion site: a generic's instances share a site and a static_if can make one write its parameter and another not, so a site key handed the second instance the first's verdict; the fixture pins the pair - the float instance reads, the int instance writes and is reported Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
It changes core lint semantics and repo-wide lint policy (LINT030 on), with broad impact across the tree that merits final human verification despite strong fixtures/docs/tests.
Review details
- Files reviewed: 25/25 changed files
- Comments generated: 0 new
- Review effort level: Lite
Behavior change: LINT030 is on for this tree. A call whose argument reads a variable that a nested call in a sibling argument writes by reference is a lint red in the per-PR lane and the nightly whole-tree pass. The SDK's default stays off.
Why. The rule shipped off because a
varparameter was its only write contract, and the tree carriesvarfor other reasons:array_data_ptr(x)hands out a pointer,kv_layer_kbase(s)returns an address,dr.offs |> push(plan(dr))takes the array by reference. Under that contract a tree-wide arming found about ninety sites, most of them working code. The rule now reads the callee's body and counts only a write that lands during the call, so what it reports is the real shape:report(run(cmd, out), out), a random pair drawn as two calls to one generator, a builder's ids emitted in argument order.What changes.
daslib/lint.das: a nested call is a writer of a variable only if its body writes that parameter during the call.Lint030WriteScanwalks the callee once and keeps the compiler's write marks on the parameter except three kinds: an address taken inside areturn(a pointer handout), a pointer the parameter holds read out or reached through (the pointee's, not the parameter's - assigning the pointer field itself still counts), and the mark every writable argument of a call gets, which that callee's own body decides for that slot. A builtin or extern, with no body to read, stays a writer; a by-value parameter is never one. A read that is the outer call's own by-reference argument - the variable, or a chain of plain fields rooted at it - is not a dependency either..lint_configturns LINT030 on (LINT030 = true);daslib/lint_config.das's SDK seed keeps it off there.daslib/faker.das: the vector, range and 64-bit draws bind each draw before the constructor, in the left-to-right order every tier read until now;tests/daslib/test_faker.daspins that order against scalar draws from a twin Faker (red with two lanes swapped).utils/internal/flatten-fuzz: the three draws of a fuzz input.tests/module_cache: the seven per-fileoptions _lint = "LINT030"lines go with the tree policy in place; itsREVIEW.mdrule, now what the lint enforces, becomes the ban on silencing the rule there.CLAUDE.md,daslib/ARCHITECTURE_LINT.mdandlint.rstdescribe the callee scan and the policy. Checklist repairs from the review round: the dasVulkan generator's two report rules key on a name added, not a file grown, and point at the module's ledger;modules/dasLLAMA/REVIEW_GPU.md's parity rule keys on a change that can alter what a served path computes or selects;modules/dasLLVM/REVIEW.mdnames its fourth architecture document and carries its routing line in the opening.Observable behavior.
varcontract; 0 under the callee scan, with the pointer-handout, kv-accessor, builder-emitter and generator sites left as they were on master.Where to look.
daslib/lint.das(Lint030WriteScan,lint030_callee_writes,lint030_field_root) andutils/lint/tests/lint030_argument_order.das(twelve positives, ten negatives).Validation, claims, ledger
Validation
daslib); the kept-address, nested-callee, pointer-field-assignment and block-in-sibling positives fire..daslints clean under the full rule set; the tree-wide whitelist run reads 0 findings in tracked files (the untracked local checkouts underexamples/daStrudel/sfx_lab,examples/games/sequence/modules/das-cards,utils/find-dupe/modules/das-claudeandexamples/wip.dasare the only WARN lines).tests/daslib/test_faker.dasinterp and jit;tests/spirvon this Mac, which resolves/opt/homebrew/bin/spirv-val(402/402, no skipped cell, census green,test_golden.dasbyte-identical);tests/module_cache;tests/lint;utils/lint/testsas a dastest suite; the flatten fuzzer's smoke run.modules/dasLLAMAandmodules/dasVulkanREVIEW.das OK; lint and review-md preflight gates green on the tip.--fullran on the earlier, wider shape of this branch (all lanes green after the emitter pin moved andtests/module_cache's AOT sources were regenerated); the callee-scan tip re-ran the fast tier, the lint and review-md gates and the suites above. The engine, emitter and generator files that earlier shape touched are back at master's text.--suite model-free: 72 of 74 files green on the wider shape; the two reds,test_vulkan_tier.dasandtest_vulkan_dec_tail.das, are the box's (MoltenVK declines thecls_arclass rail at a 32768 B cap; identical with every file atorigin/master).--suite stocked, no--exclude: 47 files, 0 failed.Claims - stated, not tested
.lint_configline itself; the per-PR lane lints changed files and the nightly whole-tree pass is what holds the tree at zero.lint030_callee_writesmemoizes per mangled name for the process, so a generic's instances key apart (astatic_ifcan make one write and another not - pinned in the fixture).Not done
fixed_array<T>(a, b)whose elements write one variable is not seen; faker's 64-bit draws had that shape and are sequenced by hand. Extending the rule to make-array, make-struct and make-tuple element lists is a rule change of its own.