Skip to content

LINT030 on for the tree: the rule reads the callee's body, and the real order dependences are sequenced - #3994

Merged
borisbat merged 10 commits into
masterfrom
bbatkin/lint030-on
Sep 10, 2026
Merged

LINT030 on for the tree: the rule reads the callee's body, and the real order dependences are sequenced#3994
borisbat merged 10 commits into
masterfrom
bbatkin/lint030-on

Conversation

@borisbat

@borisbat borisbat commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

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 var parameter was its only write contract, and the tree carries var for 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. Lint030WriteScan walks the callee once and keeps the compiler's write marks on the parameter except three kinds: 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. 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_config turns 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.das pins that order against scalar draws from a twin Faker (red with two lanes swapped).
  • dasSpirv: the folded vector constants emit type and lanes in the interpreter's order, so the goldens hold.
  • utils/internal/flatten-fuzz: the three draws of a fuzz input.
  • Two dasLLAMA tests and one dasLLVM test bind a child run's result before the assertion whose message reads the output it filled.
  • tests/module_cache: the seven per-file options _lint = "LINT030" lines go with the tree policy in place; its REVIEW.md rule, now what the lint enforces, becomes the ban on silencing the rule there.
  • Docs: CLAUDE.md, daslib/ARCHITECTURE_LINT.md and lint.rst describe 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.md names its fourth architecture document and carries its routing line in the opening.

Observable behavior.

  • The whole-tree lint with LINT030 on: 118 findings under the var contract; 0 under the callee scan, with the pointer-handout, kv-accessor, builder-emitter and generator sites left as they were on master.
  • A new order-dependent site fails the per-PR changed-files lint and the nightly.

Where to look. daslib/lint.das (Lint030WriteScan, lint030_callee_writes, lint030_field_root) and utils/lint/tests/lint030_argument_order.das (twelve positives, ten negatives).

Validation, claims, ledger

Validation

  • The fixture pins every discount with a control: the pointer handout, the by-reference field, the call delegation and the nolint marker each red the fixture when their clause is removed from the rule (scratch copies of daslib); the kept-address, nested-callee, pointer-field-assignment and block-in-sibling positives fire.
  • Every changed .das lints clean under the full rule set; the tree-wide whitelist run reads 0 findings in tracked files (the untracked local checkouts under examples/daStrudel/sfx_lab, examples/games/sequence/modules/das-cards, utils/find-dupe/modules/das-claude and examples/wip.das are the only WARN lines).
  • Tests: tests/daslib/test_faker.das interp and jit; tests/spirv on this Mac, which resolves /opt/homebrew/bin/spirv-val (402/402, no skipped cell, census green, test_golden.das byte-identical); tests/module_cache; tests/lint; utils/lint/tests as a dastest suite; the flatten fuzzer's smoke run.
  • Gates: modules/dasLLAMA and modules/dasVulkan REVIEW.das OK; lint and review-md preflight gates green on the tip.
  • Preflight --full ran on the earlier, wider shape of this branch (all lanes green after the emitter pin moved and tests/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.
  • dasLLAMA --suite model-free: 72 of 74 files green on the wider shape; the two reds, test_vulkan_tier.das and test_vulkan_dec_tail.das, are the box's (MoltenVK declines the cls_ar class rail at a 32768 B cap; identical with every file at origin/master). --suite stocked, no --exclude: 47 files, 0 failed.
  • Two codex rounds on the wider shape: no correctness findings beyond the fixed-port test in nightly reds: daslang-live loads live_host, pinvoke panic leak, Windows 64 KB pipe, LINT030 argument order, CRLF/tsan/fast-math lanes #3993.

Claims - stated, not tested

  • No test pins the .lint_config line itself; the per-PR lane lints changed files and the nightly whole-tree pass is what holds the tree at zero.
  • lint030_callee_writes memoizes per mangled name for the process, so a generic's instances key apart (a static_if can make one write and another not - pinned in the fixture).

Not done

  • LINT030 frames calls only: an array literal or 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.
  • A write through a pointer the parameter holds is not counted even when the sibling reads through the same pointer; the rule's mention model is the variable, not what it reaches.

Copilot AI lite review requested due to automatic review settings September 10, 2026 19:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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_config and 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.das composite 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.

Comment thread tests/daslib/test_faker.das
borisbat and others added 9 commits September 10, 2026 13:15
…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>
@borisbat borisbat changed the title LINT030 on for the tree: the argument-order sweep, the Vulkan generator's counts before the call, faker's draws in order LINT030 on for the tree: the rule reads the callee's body, and the real order dependences are sequenced Sep 10, 2026
Copilot AI review requested due to automatic review settings September 10, 2026 20:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment thread daslib/lint.das Outdated
…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>
Copilot AI review requested due to automatic review settings September 10, 2026 20:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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

@borisbat
borisbat merged commit 0fd38c0 into master Sep 10, 2026
33 checks passed
@borisbat
borisbat deleted the bbatkin/lint030-on branch September 10, 2026 21:05
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.

2 participants