Skip to content

Core: include() emits a kernel's WGSL helpers and their dependencies once; workgroupBytes() - #84

Merged
jowens merged 2 commits into
mainfrom
wgsl-include
Oct 27, 2026
Merged

jowens merged 2 commits into
mainfrom
wgsl-include

Conversation

@jowens

@jowens jowens commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

This is the next step in moving per-primitive boilerplate into core: WGSL helpers that bring their own dependencies.

Problem

Each kernel listed its WGSL helpers, and everything they need, by hand, in an order that compiles:

  • enableSubgroupsIfAppropriate first, then commonDefinitions, subgroupEmulation, binop.wgslfn, subgroupInclusiveOpScan, wgScan, and so on;
  • HierarchicalScan and both MultisplitPerm modules repeated the same six lines, DLDFScan listed twelve, and OneSweep spread its list through its module;
  • leaving one out failed to compile, and so did listing one twice.

Workgroup memory was counted by hand too. MultisplitPerm's local finish subtracted a magic 3,328 bytes.

Change

  • fnDeclarations.include(...helpers):

    • emits each helper and its dependencies once, with enable first;
    • takes [name, args] for helpers with arguments;
    • for inline (statement-level) helpers, adds only what they need at module scope.

    A dependency is satisfied by any inclusion of that helper, whatever its arguments. A helper listed explicitly is emitted once per distinct set of arguments, so exclusive and inclusive wgScan can coexist.

    ${this.fnDeclarations.include(
      "wgScan",
      ["wgHistogram", { name: "cnt", numBins: RADIX, workgroupSize: W }],
      "computeLinearizedGridParametersSplit",
    )}
  • fnDeclarations.workgroupBytes(...helpers): the workgroup memory those helpers declare, for the subgroup or emulated variant.

  • All five primitives use include():

    • MultisplitPerm's local finish computes its reserve (localFinishReservedBytes: its own arrays plus workgroupBytes) instead of using 3,328 bytes;
    • Histogram's workgroupBins uses workgroupBytes.
  • The no-subgroup wgScan, a Hillis–Steele scan in workgroup memory, no longer pulls in subgroup emulation, which it never used.

Checked

  • Kernels compared with DLDFScan and OneSweep declare their buffers once; UniformStruct slots #83's, with and without subgroups (61 kernels each). They're unchanged except for two intended differences:

    • Histogram's module now starts with enable subgroups;;
    • emulated HierarchicalScan and MultisplitPerm modules no longer carry the unused emulation code.

    All 122 compile.

  • New regression test for include(). It covers:

    • no duplicate binop or builtins;
    • exclusive and inclusive wgScan together;
    • a helper listed after it already arrived as a dependency. This case caught a bug in my first version: that helper was emitted twice. It's fixed;
    • enable first;
    • the module compiling;
    • workgroupBytes.
  • Simulated RDNA4: 84 of 84 tests pass. Lavapipe: everything passes except the existing 8-lane sort failures. The package test passes.

Note on localFinishCap

The computed reserve is smaller than 3,328 (2,316 bytes with subgroups, 3,084 without). The cap can therefore only grow, and only when the device's workgroup memory is what limits it, which none of the defaults hit. localFinishCap keeps 3,328 as its default for callers that don't pass a reserve.


Generated by Claude Code

…ndencies once; workgroupBytes()

A kernel listed its WGSL helpers and everything they need by hand
(enableSubgroupsIfAppropriate first, commonDefinitions, subgroupEmulation,
binop.wgslfn, subgroupInclusiveOpScan, wgScan, ...): HierarchicalScan and both
MultisplitPerm modules repeated the same six lines, DLDFScan twelve, and
OneSweep spread its list through the module. Leaving one out failed to
compile; listing one twice failed too.

- wgslFunctions.include(...helpers) emits each helper and its dependencies
  (DEPENDENCIES) once, enable first; [name, args] for helpers with
  arguments. A dependency is satisfied by any inclusion of that helper; a
  helper listed explicitly is emitted once per distinct argument set.
  Inline (statement) helpers add only what they need at module scope.
- workgroupBytes(...helpers) is the workgroup memory those helpers declare
  (WORKGROUP_BYTES), per subgroup or emulated variant.
- The no-subgroup wgScan (a Hillis-Steele scan in workgroup memory) no longer
  pulls in subgroup emulation, which it never used.
- All primitives use include(). MultisplitPerm's local finish computes its
  reserved workgroup memory (localFinishReservedBytes: its own arrays plus
  workgroupBytes) instead of a hand-counted 3,328 bytes; Histogram's
  workgroupBins uses workgroupBytes.
- Kernels compared with the previous commit, with and without subgroups (61
  kernels each): unchanged except that Histogram's module now starts with
  `enable subgroups;` and emulated HierarchicalScan and MultisplitPerm
  modules no longer carry the unused emulation code. All 122 compile.
- Regression test for include() (dedup, exclusive and inclusive wgScan, a
  helper listed after arriving as a dependency, enable first, compiles,
  workgroupBytes). Types for fnDeclarations; docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ
…ulated CI step)

In Node, the emulated CI step (DISABLE_SUBGROUPS) hides subgroups from the
device; regression.mjs's useEmu is only the browser's URL switch and stays
false. The test expected the subgroup variant (enable first, 256-byte scans)
from a primitive that was emulated. It now asks the primitive
(!scan.useSubgroups). Checked on a device with and without subgroups: the
old test fails without them (as in CI run 40106488874), the new one passes
both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ
@jowens
jowens merged commit 24ef0af into main Oct 27, 2026
4 checks passed
@jowens
jowens deleted the wgsl-include branch October 27, 2026 19:31
jowens pushed a commit that referenced this pull request Oct 27, 2026
Conflict in docs/architecture.md: both sides added a section before
"Composing primitives". Kept both: "WGSL helpers" (#84), then "Actions, and
when they take effect" (this PR).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kfNC6id65zEHzuAAH4jtJ
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