@graphty/webgpu-graph-algorithms 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +62 -32
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{context-BXqgCifx.js → context-Dvq-Cc6v.js} +71 -25
  4. package/dist/chunks/context-Dvq-Cc6v.js.map +1 -0
  5. package/dist/node.js +1 -1
  6. package/dist/src/accelerator.d.ts +8 -6
  7. package/dist/src/accelerator.d.ts.map +1 -1
  8. package/dist/src/accelerator.js +57 -6
  9. package/dist/src/accelerator.js.map +1 -1
  10. package/dist/src/algorithms/bellman-ford.d.ts +60 -0
  11. package/dist/src/algorithms/bellman-ford.d.ts.map +1 -0
  12. package/dist/src/algorithms/bellman-ford.js +301 -0
  13. package/dist/src/algorithms/bellman-ford.js.map +1 -0
  14. package/dist/src/algorithms/bfs.d.ts +67 -0
  15. package/dist/src/algorithms/bfs.d.ts.map +1 -0
  16. package/dist/src/algorithms/bfs.js +534 -0
  17. package/dist/src/algorithms/bfs.js.map +1 -0
  18. package/dist/src/algorithms/closeness.d.ts +53 -0
  19. package/dist/src/algorithms/closeness.d.ts.map +1 -0
  20. package/dist/src/algorithms/closeness.js +323 -0
  21. package/dist/src/algorithms/closeness.js.map +1 -0
  22. package/dist/src/algorithms/scope.d.ts +5 -3
  23. package/dist/src/algorithms/scope.d.ts.map +1 -1
  24. package/dist/src/algorithms/scope.js +3 -0
  25. package/dist/src/algorithms/scope.js.map +1 -1
  26. package/dist/src/algorithms/sssp.d.ts +71 -0
  27. package/dist/src/algorithms/sssp.d.ts.map +1 -0
  28. package/dist/src/algorithms/sssp.js +585 -0
  29. package/dist/src/algorithms/sssp.js.map +1 -0
  30. package/dist/src/constants.d.ts +12 -0
  31. package/dist/src/constants.d.ts.map +1 -1
  32. package/dist/src/constants.js +12 -0
  33. package/dist/src/constants.js.map +1 -1
  34. package/dist/src/index.d.ts +8 -2
  35. package/dist/src/index.d.ts.map +1 -1
  36. package/dist/src/index.js +7 -1
  37. package/dist/src/index.js.map +1 -1
  38. package/dist/src/kernel/prelude.d.ts +4 -4
  39. package/dist/src/kernel/prelude.d.ts.map +1 -1
  40. package/dist/src/kernel/prelude.js +39 -5
  41. package/dist/src/kernel/prelude.js.map +1 -1
  42. package/dist/src/kernel/uniform-ring.d.ts +8 -0
  43. package/dist/src/kernel/uniform-ring.d.ts.map +1 -1
  44. package/dist/src/kernel/uniform-ring.js +13 -0
  45. package/dist/src/kernel/uniform-ring.js.map +1 -1
  46. package/dist/src/kernels.d.ts +44 -4
  47. package/dist/src/kernels.d.ts.map +1 -1
  48. package/dist/src/kernels.js +371 -3
  49. package/dist/src/kernels.js.map +1 -1
  50. package/dist/src/primitives/advance.d.ts +62 -0
  51. package/dist/src/primitives/advance.d.ts.map +1 -0
  52. package/dist/src/primitives/advance.js +95 -0
  53. package/dist/src/primitives/advance.js.map +1 -0
  54. package/dist/src/primitives/compact.d.ts +89 -0
  55. package/dist/src/primitives/compact.d.ts.map +1 -0
  56. package/dist/src/primitives/compact.js +233 -0
  57. package/dist/src/primitives/compact.js.map +1 -0
  58. package/dist/src/primitives/core-shape.d.ts +22 -1
  59. package/dist/src/primitives/core-shape.d.ts.map +1 -1
  60. package/dist/src/primitives/core-shape.js +33 -3
  61. package/dist/src/primitives/core-shape.js.map +1 -1
  62. package/dist/src/primitives/frontier.d.ts +156 -0
  63. package/dist/src/primitives/frontier.d.ts.map +1 -0
  64. package/dist/src/primitives/frontier.js +259 -0
  65. package/dist/src/primitives/frontier.js.map +1 -0
  66. package/dist/src/types/accelerator.d.ts +16 -7
  67. package/dist/src/types/accelerator.d.ts.map +1 -1
  68. package/dist/src/types/traversal.d.ts +53 -0
  69. package/dist/src/types/traversal.d.ts.map +1 -0
  70. package/dist/src/types/traversal.js +10 -0
  71. package/dist/src/types/traversal.js.map +1 -0
  72. package/dist/src/wgsl/advance-expand.wgsl.d.ts +19 -0
  73. package/dist/src/wgsl/advance-expand.wgsl.d.ts.map +1 -0
  74. package/dist/src/wgsl/advance-expand.wgsl.js +69 -0
  75. package/dist/src/wgsl/advance-expand.wgsl.js.map +1 -0
  76. package/dist/src/wgsl/bf-relax.wgsl.d.ts +22 -0
  77. package/dist/src/wgsl/bf-relax.wgsl.d.ts.map +1 -0
  78. package/dist/src/wgsl/bf-relax.wgsl.js +58 -0
  79. package/dist/src/wgsl/bf-relax.wgsl.js.map +1 -0
  80. package/dist/src/wgsl/bfs-bitset-build.wgsl.d.ts +15 -0
  81. package/dist/src/wgsl/bfs-bitset-build.wgsl.d.ts.map +1 -0
  82. package/dist/src/wgsl/bfs-bitset-build.wgsl.js +24 -0
  83. package/dist/src/wgsl/bfs-bitset-build.wgsl.js.map +1 -0
  84. package/dist/src/wgsl/bfs-bottom-up.wgsl.d.ts +20 -0
  85. package/dist/src/wgsl/bfs-bottom-up.wgsl.d.ts.map +1 -0
  86. package/dist/src/wgsl/bfs-bottom-up.wgsl.js +67 -0
  87. package/dist/src/wgsl/bfs-bottom-up.wgsl.js.map +1 -0
  88. package/dist/src/wgsl/bfs-contract.wgsl.d.ts +20 -0
  89. package/dist/src/wgsl/bfs-contract.wgsl.d.ts.map +1 -0
  90. package/dist/src/wgsl/bfs-contract.wgsl.js +55 -0
  91. package/dist/src/wgsl/bfs-contract.wgsl.js.map +1 -0
  92. package/dist/src/wgsl/bfs-fused.wgsl.d.ts +25 -0
  93. package/dist/src/wgsl/bfs-fused.wgsl.d.ts.map +1 -0
  94. package/dist/src/wgsl/bfs-fused.wgsl.js +78 -0
  95. package/dist/src/wgsl/bfs-fused.wgsl.js.map +1 -0
  96. package/dist/src/wgsl/bfs-unvisited-flags.wgsl.d.ts +18 -0
  97. package/dist/src/wgsl/bfs-unvisited-flags.wgsl.d.ts.map +1 -0
  98. package/dist/src/wgsl/bfs-unvisited-flags.wgsl.js +42 -0
  99. package/dist/src/wgsl/bfs-unvisited-flags.wgsl.js.map +1 -0
  100. package/dist/src/wgsl/closeness-reduce.wgsl.d.ts +17 -0
  101. package/dist/src/wgsl/closeness-reduce.wgsl.d.ts.map +1 -0
  102. package/dist/src/wgsl/closeness-reduce.wgsl.js +65 -0
  103. package/dist/src/wgsl/closeness-reduce.wgsl.js.map +1 -0
  104. package/dist/src/wgsl/closeness-sweep.wgsl.d.ts +20 -0
  105. package/dist/src/wgsl/closeness-sweep.wgsl.d.ts.map +1 -0
  106. package/dist/src/wgsl/closeness-sweep.wgsl.js +96 -0
  107. package/dist/src/wgsl/closeness-sweep.wgsl.js.map +1 -0
  108. package/dist/src/wgsl/compact-scatter.wgsl.d.ts +9 -0
  109. package/dist/src/wgsl/compact-scatter.wgsl.d.ts.map +1 -0
  110. package/dist/src/wgsl/compact-scatter.wgsl.js +17 -0
  111. package/dist/src/wgsl/compact-scatter.wgsl.js.map +1 -0
  112. package/dist/src/wgsl/dedupe-claim.wgsl.d.ts +10 -0
  113. package/dist/src/wgsl/dedupe-claim.wgsl.d.ts.map +1 -0
  114. package/dist/src/wgsl/dedupe-claim.wgsl.js +19 -0
  115. package/dist/src/wgsl/dedupe-claim.wgsl.js.map +1 -0
  116. package/dist/src/wgsl/dedupe-filter.wgsl.d.ts +12 -0
  117. package/dist/src/wgsl/dedupe-filter.wgsl.d.ts.map +1 -0
  118. package/dist/src/wgsl/dedupe-filter.wgsl.js +46 -0
  119. package/dist/src/wgsl/dedupe-filter.wgsl.js.map +1 -0
  120. package/dist/src/wgsl/frontier-finalize.wgsl.d.ts +59 -0
  121. package/dist/src/wgsl/frontier-finalize.wgsl.d.ts.map +1 -0
  122. package/dist/src/wgsl/frontier-finalize.wgsl.js +210 -0
  123. package/dist/src/wgsl/frontier-finalize.wgsl.js.map +1 -0
  124. package/dist/src/wgsl/sssp-pred.wgsl.d.ts +28 -0
  125. package/dist/src/wgsl/sssp-pred.wgsl.d.ts.map +1 -0
  126. package/dist/src/wgsl/sssp-pred.wgsl.js +80 -0
  127. package/dist/src/wgsl/sssp-pred.wgsl.js.map +1 -0
  128. package/dist/src/wgsl/sssp-relax.wgsl.d.ts +30 -0
  129. package/dist/src/wgsl/sssp-relax.wgsl.d.ts.map +1 -0
  130. package/dist/src/wgsl/sssp-relax.wgsl.js +72 -0
  131. package/dist/src/wgsl/sssp-relax.wgsl.js.map +1 -0
  132. package/dist/webgpu-graph-algorithms.js +3207 -377
  133. package/dist/webgpu-graph-algorithms.js.map +1 -1
  134. package/package.json +5 -4
  135. package/src/accelerator.ts +65 -7
  136. package/src/algorithms/bellman-ford.ts +387 -0
  137. package/src/algorithms/bfs.ts +626 -0
  138. package/src/algorithms/closeness.ts +395 -0
  139. package/src/algorithms/scope.ts +13 -3
  140. package/src/algorithms/sssp.ts +767 -0
  141. package/src/constants.ts +12 -0
  142. package/src/index.ts +14 -1
  143. package/src/kernel/prelude.ts +39 -4
  144. package/src/kernel/uniform-ring.ts +14 -0
  145. package/src/kernels.ts +450 -6
  146. package/src/primitives/advance.ts +130 -0
  147. package/src/primitives/compact.ts +323 -0
  148. package/src/primitives/core-shape.ts +41 -3
  149. package/src/primitives/frontier.ts +388 -0
  150. package/src/types/accelerator.ts +18 -5
  151. package/src/types/traversal.ts +56 -0
  152. package/src/wgsl/advance-expand.wgsl.ts +68 -0
  153. package/src/wgsl/bf-relax.wgsl.ts +57 -0
  154. package/src/wgsl/bfs-bitset-build.wgsl.ts +23 -0
  155. package/src/wgsl/bfs-bottom-up.wgsl.ts +66 -0
  156. package/src/wgsl/bfs-contract.wgsl.ts +54 -0
  157. package/src/wgsl/bfs-fused.wgsl.ts +77 -0
  158. package/src/wgsl/bfs-unvisited-flags.wgsl.ts +41 -0
  159. package/src/wgsl/closeness-reduce.wgsl.ts +64 -0
  160. package/src/wgsl/closeness-sweep.wgsl.ts +95 -0
  161. package/src/wgsl/compact-scatter.wgsl.ts +16 -0
  162. package/src/wgsl/dedupe-claim.wgsl.ts +18 -0
  163. package/src/wgsl/dedupe-filter.wgsl.ts +45 -0
  164. package/src/wgsl/frontier-finalize.wgsl.ts +209 -0
  165. package/src/wgsl/sssp-pred.wgsl.ts +79 -0
  166. package/src/wgsl/sssp-relax.wgsl.ts +71 -0
  167. package/dist/chunks/context-BXqgCifx.js.map +0 -1
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The `advance` primitive of design 6 row 8 (P8-T5 / P8-T12; the P8 plan's PD-1, PD-23, DEP-P8-A, DEP-P8-E): the
3
+ * block-mapped expansion of a `Frontier`'s input queue into its edge queue by the `advance-expand` kernel, a DIRECT
4
+ * grid-stride dispatch over a grid planned for n entries that loops to `frontierCount` and runs only when the `path`
5
+ * word `frontier-finalize` role 0 wrote earlier in the same pass says the level is two-phase; the host never knows
6
+ * the frontier's size. The kernel leaves `edgeCount` (clamped later by role 1),
7
+ * `edgeCountUnclamped` (the overflow detector) and `frontierDegreeSum` in the counters block.
8
+ *
9
+ * Design 6 row 8 names three structures -- block-mapped, a WORKGROUP_PER_ROW tier for rows above 1,024 arcs, and a
10
+ * subgroup tier. Here they are: the block-mapped body is the large-frontier kernel and already balances a hub row
11
+ * over all `WG` lanes of its block (a 10,000-degree entry costs its block 40 strips, never one lane 10,000 reads);
12
+ * the workgroup-per-row structure IS `bfs-fused` (P8-T7), one workgroup per frontier entry, which the selector
13
+ * chooses for a small frontier such as a hub-only level, so `advance-expand` declares no `TIER` override and the tier
14
+ * choice is the fused / two-phase choice; and the subgroup tier is not a second body but the same body compiled
15
+ * against the prelude's subgroup helper block (`wg_scan_u32` becomes `subgroupExclusiveAdd` / `subgroupAdd` plus the
16
+ * elected-lane slot carry), which `needs: ["subgroups"]` in the registry entry switches on when the device has the
17
+ * feature. Both forms return each lane's prefix in LANE order, so the arcs land at the same queue positions; only
18
+ * the order ACROSS workgroups (the `atomicAdd` reservation order) is schedule-dependent, which is why a test compares
19
+ * sorted queues.
20
+ *
21
+ * A windowed core (spec 4.2: `colIdx` above `maxStorageBufferBindingSize`, bound as arc windows) is executed, not
22
+ * refused (P8-T12 lifts DEP-P8-E for the frontier family): one dispatch per window, the frontier and its count the
23
+ * same in every window, each dispatch binding that window's `colIdx` and
24
+ * carrying its owned arc range in `arcBase` / `arcEnd` (`coreWindows`: the ranges partition the arcs, which P4's
25
+ * overlapping window bounds do not). The body clips every row to the range, so a row straddling two windows emits
26
+ * each arc exactly once, and the per-window dispatches accumulate into the same edge queue through the same
27
+ * `edgeCount` reservation while `edgeCountUnclamped` and `frontierDegreeSum` add up across windows to the level's
28
+ * totals. `src/primitives/**` never imports `src/context.ts`.
29
+ */
30
+ import { type Kernel } from "../kernel/kernel.js";
31
+ import { type CoreBinding } from "../memory/residency.js";
32
+ import { type CoreWindow } from "./core-shape.js";
33
+ import { type Frontier, type FrontierScope } from "./frontier.js";
34
+ /** A prepared advance (design 6 row 8): records one expansion of a frontier per level. */
35
+ export interface AdvancePlanner {
36
+ /** The compiled `advance-expand` kernel (so `bfs-fused` can share its bind groups' shape). */
37
+ readonly kernel: Kernel;
38
+ /** The arc windows every level dispatches over: one over the whole core, or the windowed core's (P8-T12). */
39
+ readonly windows: readonly CoreWindow[];
40
+ /**
41
+ * Records the expansion of `frontier.input` into `frontier.edgeQueue` as a DIRECT grid-stride dispatch per window
42
+ * (`planGridStride(n)`: the frontier holds at most n entries; the kernel loops to the count word and runs only
43
+ * when the selector's path word says the level is two-phase): one `FrontierParams` record per window
44
+ * (`edgeCapacity`, `n`, `wg`, `stride` and the window's arc range) and the kernel bound to the window's graph
45
+ * arrays, the input queue, the counters block and the edge queue (`Kernel.bind` reuses the bind groups of the
46
+ * same buffers and ranges, so the two sides of a frontier cost two bind-group sets per window). A frontier
47
+ * whose `n` is not the core's row count is E_INVALID_ARGUMENT before anything is recorded.
48
+ * @param pass - the compute pass
49
+ * @param frontier - the queue to expand (its `input` side)
50
+ */
51
+ record(pass: GPUComputePassEncoder, frontier: Frontier): void;
52
+ }
53
+ /**
54
+ * Compiles `advance-expand` for the core's weights pattern (no permutation: the frontier names rows directly) so
55
+ * `record` is synchronous, and resolves the core's windows once. The planner lives exactly as long as the scope:
56
+ * never use it after the scope's dispose().
57
+ * @param scope - the caller's scope (pipelines, params)
58
+ * @param core - the resident core arrays of the snapshot the frontier walks (windowed or not)
59
+ * @returns the planner
60
+ */
61
+ export declare function prepareAdvance(scope: FrontierScope, core: CoreBinding): Promise<AdvancePlanner>;
62
+ //# sourceMappingURL=advance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advance.d.ts","sourceRoot":"","sources":["../../../src/primitives/advance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,KAAK,UAAU,EAA2B,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAElE,0FAA0F;AAC1F,MAAM,WAAW,cAAc;IAC3B,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6GAA6G;IAC7G,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,EAAE,qBAAqB,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;CACjE;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAGrG"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The `advance` primitive of design 6 row 8 (P8-T5 / P8-T12; the P8 plan's PD-1, PD-23, DEP-P8-A, DEP-P8-E): the
3
+ * block-mapped expansion of a `Frontier`'s input queue into its edge queue by the `advance-expand` kernel, a DIRECT
4
+ * grid-stride dispatch over a grid planned for n entries that loops to `frontierCount` and runs only when the `path`
5
+ * word `frontier-finalize` role 0 wrote earlier in the same pass says the level is two-phase; the host never knows
6
+ * the frontier's size. The kernel leaves `edgeCount` (clamped later by role 1),
7
+ * `edgeCountUnclamped` (the overflow detector) and `frontierDegreeSum` in the counters block.
8
+ *
9
+ * Design 6 row 8 names three structures -- block-mapped, a WORKGROUP_PER_ROW tier for rows above 1,024 arcs, and a
10
+ * subgroup tier. Here they are: the block-mapped body is the large-frontier kernel and already balances a hub row
11
+ * over all `WG` lanes of its block (a 10,000-degree entry costs its block 40 strips, never one lane 10,000 reads);
12
+ * the workgroup-per-row structure IS `bfs-fused` (P8-T7), one workgroup per frontier entry, which the selector
13
+ * chooses for a small frontier such as a hub-only level, so `advance-expand` declares no `TIER` override and the tier
14
+ * choice is the fused / two-phase choice; and the subgroup tier is not a second body but the same body compiled
15
+ * against the prelude's subgroup helper block (`wg_scan_u32` becomes `subgroupExclusiveAdd` / `subgroupAdd` plus the
16
+ * elected-lane slot carry), which `needs: ["subgroups"]` in the registry entry switches on when the device has the
17
+ * feature. Both forms return each lane's prefix in LANE order, so the arcs land at the same queue positions; only
18
+ * the order ACROSS workgroups (the `atomicAdd` reservation order) is schedule-dependent, which is why a test compares
19
+ * sorted queues.
20
+ *
21
+ * A windowed core (spec 4.2: `colIdx` above `maxStorageBufferBindingSize`, bound as arc windows) is executed, not
22
+ * refused (P8-T12 lifts DEP-P8-E for the frontier family): one dispatch per window, the frontier and its count the
23
+ * same in every window, each dispatch binding that window's `colIdx` and
24
+ * carrying its owned arc range in `arcBase` / `arcEnd` (`coreWindows`: the ranges partition the arcs, which P4's
25
+ * overlapping window bounds do not). The body clips every row to the range, so a row straddling two windows emits
26
+ * each arc exactly once, and the per-window dispatches accumulate into the same edge queue through the same
27
+ * `edgeCount` reservation while `edgeCountUnclamped` and `frontierDegreeSum` add up across windows to the level's
28
+ * totals. `src/primitives/**` never imports `src/context.ts`.
29
+ */
30
+ import { WebGpuGraphError } from "../errors.js";
31
+ import { planGridStride } from "../kernel/dispatch.js";
32
+ import { FRONTIER_PARAMS, graphBindings, graphOverrides, kernelSpec } from "../kernels.js";
33
+ import { coreWindows, rowCountOf } from "./core-shape.js";
34
+ /**
35
+ * Compiles `advance-expand` for the core's weights pattern (no permutation: the frontier names rows directly) so
36
+ * `record` is synchronous, and resolves the core's windows once. The planner lives exactly as long as the scope:
37
+ * never use it after the scope's dispose().
38
+ * @param scope - the caller's scope (pipelines, params)
39
+ * @param core - the resident core arrays of the snapshot the frontier walks (windowed or not)
40
+ * @returns the planner
41
+ */
42
+ export async function prepareAdvance(scope, core) {
43
+ const kernel = await scope.pipelines.kernel(kernelSpec("advance-expand", graphOverrides(core, null)));
44
+ return new AdvancePlannerImpl(scope, core, kernel);
45
+ }
46
+ /** The planner: the compiled kernel over one core's windows. */
47
+ class AdvancePlannerImpl {
48
+ /**
49
+ * Wraps the resolved kernel; use prepareAdvance().
50
+ * @param scope - the caller's scope
51
+ * @param core - the core the kernel was compiled for
52
+ * @param kernel - the `advance-expand` kernel
53
+ */
54
+ constructor(scope, core, kernel) {
55
+ this.scope = scope;
56
+ this.kernel = kernel;
57
+ this.windows = coreWindows(core);
58
+ this.n = rowCountOf(core, "advance");
59
+ }
60
+ /**
61
+ * Records one direct expansion per window (see the interface).
62
+ * @param pass - the compute pass
63
+ * @param frontier - the queue to expand
64
+ */
65
+ record(pass, frontier) {
66
+ if (frontier.n !== this.n) {
67
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `advance: the frontier holds ${frontier.n} vertices, the core ${this.n} rows`, {
68
+ argument: "frontier",
69
+ value: frontier.n,
70
+ expected: this.n,
71
+ });
72
+ }
73
+ const { scope } = this;
74
+ const plan = planGridStride(frontier.n, scope.workgroupSize, scope.caps); // the frontier holds at most n entries
75
+ for (const w of this.windows) {
76
+ const params = scope.params(FRONTIER_PARAMS, {
77
+ wg: scope.workgroupSize,
78
+ edgeCapacity: frontier.edgeCapacity,
79
+ n: frontier.n,
80
+ arcBase: w.arcBase,
81
+ arcEnd: w.arcEnd,
82
+ stride: plan.stride ?? scope.workgroupSize,
83
+ });
84
+ const bound = this.kernel.bind({
85
+ ...graphBindings(w.core, null),
86
+ frontierIn: frontier.input,
87
+ counters: frontier.counters,
88
+ edgeQueue: frontier.edgeQueue,
89
+ P: params.binding,
90
+ });
91
+ this.kernel.dispatch(pass, bound, plan, [params.offset]);
92
+ }
93
+ }
94
+ }
95
+ //# sourceMappingURL=advance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advance.js","sourceRoot":"","sources":["../../../src/primitives/advance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3F,OAAO,EAAmB,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAuB3E;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAoB,EAAE,IAAiB;IACxE,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,gBAAgB,EAAE,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACtG,OAAO,IAAI,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACvD,CAAC;AAED,gEAAgE;AAChE,MAAM,kBAAkB;IAMpB;;;;;OAKG;IACH,YAAY,KAAoB,EAAE,IAAiB,EAAE,MAAc;QAC/D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,IAA2B,EAAE,QAAkB;QAClD,IAAI,QAAQ,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,gBAAgB,CACtB,oBAAoB,EACpB,+BAA+B,QAAQ,CAAC,CAAC,uBAAuB,IAAI,CAAC,CAAC,OAAO,EAC7E;gBACI,QAAQ,EAAE,UAAU;gBACpB,KAAK,EAAE,QAAQ,CAAC,CAAC;gBACjB,QAAQ,EAAE,IAAI,CAAC,CAAC;aACnB,CACJ,CAAC;QACN,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;QACvB,MAAM,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,uCAAuC;QACjH,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,eAAe,EAAE;gBACzC,EAAE,EAAE,KAAK,CAAC,aAAa;gBACvB,YAAY,EAAE,QAAQ,CAAC,YAAY;gBACnC,CAAC,EAAE,QAAQ,CAAC,CAAC;gBACb,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,aAAa;aAC7C,CAAC,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;gBAC3B,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC;gBAC9B,UAAU,EAAE,QAAQ,CAAC,KAAK;gBAC1B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,SAAS,EAAE,QAAQ,CAAC,SAAS;gBAC7B,CAAC,EAAE,MAAM,CAAC,OAAO;aACpB,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7D,CAAC;IACL,CAAC;CACJ"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The `compact` and `dedupe` primitive driver (spec 6 row 4; P8-T3, the P8 plan's PD-1 / DEP-P8-A). `compact` is
3
+ * flag, scan, scatter: the flags are the caller's (one u32 per entry, written by the kernel that produced the queue),
4
+ * `exclusiveScan` turns them into offsets, and `compact-scatter` lands every flagged entry at its offset in queue
5
+ * order -- order-preserving, so bitwise reproducible -- and writes the total into one word of the caller's block.
6
+ * `dedupe` is Davidson's ownership trick in two dispatches: `dedupe-claim` stores every entry's index into
7
+ * `owner[vertex]`, `dedupe-filter` keeps the entries that still own their vertex. Two dispatches because a plain
8
+ * store read back in the same dispatch is a data race (WGSL 6.5.7); between dispatches the relaxed atomics make
9
+ * last-writer-wins well defined, so exactly one index per distinct vertex survives (set-deterministic: the SET is
10
+ * fixed, the order inside `out` follows the schedule).
11
+ *
12
+ * `owner` needs NO reset between calls, whatever it holds: a word is read only by an entry `i` whose vertex `v` a
13
+ * claim of the SAME call has just written, so a stale index or garbage at `owner[v]` is overwritten before any lane
14
+ * compares against it, and a word no current entry names is never read. The design's row implies a reset; the `fill`
15
+ * over `n` per round is the dispatch this saves.
16
+ *
17
+ * The count of `compact` is host-known. `dedupe`'s callers (the SSSP piles of P8-T9) only know their entry count on
18
+ * the device, so `countIndex` names a word of the counters block and the kernels read `min(counters[countIndex],
19
+ * count)` -- the device word clamped to the capacity `count` -- or `count` alone when `countIndex` is `U32_MAX`. The
20
+ * counters block is bound WHOLE and indexed, for the count word and for the output word alike, because
21
+ * `Kernel.bind` rejects a binding whose offset is not a multiple of 256 and a four-byte word never is.
22
+ *
23
+ * The driver owns no device objects: the caller supplies a ReduceScope (the same record `reduce` takes) and the
24
+ * compute pass to record into. `src/primitives/**` never imports `src/context.ts`.
25
+ */
26
+ import { type Binding } from "../types/memory.js";
27
+ import { type ReduceScope } from "./reduce.js";
28
+ /** One compaction: `count` entries of `queue` whose `flags` word is non-zero go to `out` in order; the total goes to `outCount[outIndex]`. */
29
+ export interface CompactRecord {
30
+ /** The entries (at least 4 x count bytes). */
31
+ readonly queue: Binding;
32
+ /** One u32 per entry, 0 or 1 (at least 4 x count bytes). */
33
+ readonly flags: Binding;
34
+ /** The entry count (a non-negative integer below 2^32; host-known). */
35
+ readonly count: number;
36
+ /** The compacted entries (at least 4 x count bytes; the words past the total are not written). */
37
+ readonly out: Binding;
38
+ /** The block whose word `outIndex` receives the total (bound whole). */
39
+ readonly outCount: Binding;
40
+ /** The word of `outCount` to write. */
41
+ readonly outIndex: number;
42
+ }
43
+ /** One dedupe: the entries of `queue` are filtered to one per distinct vertex into `out`; the surviving count is ADDED to `outCount[outIndex]`, which the caller zeroes before the pass. */
44
+ export interface DedupeRecord {
45
+ /** The entries, vertex indices below the owner's length (at least 4 x count bytes). */
46
+ readonly queue: Binding;
47
+ /** The entry count when `countIndex` is `U32_MAX`, else the capacity the device count is clamped to (a non-negative integer below 2^32). */
48
+ readonly count: number;
49
+ /** `U32_MAX` for a host-known count; else the word of `counters` holding the entry count on the device. */
50
+ readonly countIndex: number;
51
+ /** The block the count word is read from; with a device count it must be the SAME range as `outCount` (both kernels read the word through their own binding), and it is bound but never read when `countIndex` is `U32_MAX`. */
52
+ readonly counters: Binding;
53
+ /** The ownership words, one per vertex (the caller's promise: at least 4 x n bytes; never reset). */
54
+ readonly owner: Binding;
55
+ /** The surviving entries (at least 4 x count bytes; the words past the count are not written). */
56
+ readonly out: Binding;
57
+ /** The block whose word `outIndex` accumulates the surviving count (bound whole; zeroed by the caller before the pass). */
58
+ readonly outCount: Binding;
59
+ /** The word of `outCount` to add to; never equal to `countIndex`. */
60
+ readonly outIndex: number;
61
+ }
62
+ /** A prepared compact / dedupe (spec 6 row 4): records the dispatches of one call into a pass. */
63
+ export interface CompactPlanner {
64
+ /**
65
+ * Records the compaction (the flags' exclusive scan, then the scatter). For count 0 nothing is recorded and
66
+ * `outCount[outIndex]` keeps its prior value: a four-byte write at an unaligned offset is impossible, so every
67
+ * caller zeroes its block before the pass.
68
+ * @param pass - the compute pass
69
+ * @param record - the buffers and the count
70
+ */
71
+ record(pass: GPUComputePassEncoder, record: CompactRecord): void;
72
+ /**
73
+ * Records the claim and the filter over `plan1d(count)`; nothing for count 0 (`outCount[outIndex]` then keeps
74
+ * its prior value).
75
+ * @param pass - the compute pass
76
+ * @param record - the buffers, the count and the count word
77
+ */
78
+ recordDedupe(pass: GPUComputePassEncoder, record: DedupeRecord): void;
79
+ /** Dispatches the last record*() issued: compact 0 for count 0, else the scan's `2 x levels - 1` plus one (`2 x levels`); dedupe 0 for a direct count 0, else 2. */
80
+ readonly lastDispatches: number;
81
+ }
82
+ /**
83
+ * Prepares the three pipelines and the scan of a scope (compiles once) so record() is synchronous. The planner
84
+ * lives exactly as long as the scope (the scan keeps a scratch word of it): never use it after the scope's dispose().
85
+ * @param scope - the caller's scope (device, caps, cache, scratch, params)
86
+ * @returns the planner
87
+ */
88
+ export declare function prepareCompact(scope: ReduceScope): Promise<CompactPlanner>;
89
+ //# sourceMappingURL=compact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact.d.ts","sourceRoot":"","sources":["../../../src/primitives/compact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAOH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,8IAA8I;AAC9I,MAAM,WAAW,aAAa;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kGAAkG;IAClG,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;AAED,4LAA4L;AAC5L,MAAM,WAAW,YAAY;IACzB,uFAAuF;IACvF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,4IAA4I;IAC5I,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2GAA2G;IAC3G,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,gOAAgO;IAChO,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,qGAAqG;IACrG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,kGAAkG;IAClG,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,2HAA2H;IAC3H,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;AAED,kGAAkG;AAClG,MAAM,WAAW,cAAc;IAC3B;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACjE;;;;;OAKG;IACH,YAAY,CAAC,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACtE,oKAAoK;IACpK,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAMhF"}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * The `compact` and `dedupe` primitive driver (spec 6 row 4; P8-T3, the P8 plan's PD-1 / DEP-P8-A). `compact` is
3
+ * flag, scan, scatter: the flags are the caller's (one u32 per entry, written by the kernel that produced the queue),
4
+ * `exclusiveScan` turns them into offsets, and `compact-scatter` lands every flagged entry at its offset in queue
5
+ * order -- order-preserving, so bitwise reproducible -- and writes the total into one word of the caller's block.
6
+ * `dedupe` is Davidson's ownership trick in two dispatches: `dedupe-claim` stores every entry's index into
7
+ * `owner[vertex]`, `dedupe-filter` keeps the entries that still own their vertex. Two dispatches because a plain
8
+ * store read back in the same dispatch is a data race (WGSL 6.5.7); between dispatches the relaxed atomics make
9
+ * last-writer-wins well defined, so exactly one index per distinct vertex survives (set-deterministic: the SET is
10
+ * fixed, the order inside `out` follows the schedule).
11
+ *
12
+ * `owner` needs NO reset between calls, whatever it holds: a word is read only by an entry `i` whose vertex `v` a
13
+ * claim of the SAME call has just written, so a stale index or garbage at `owner[v]` is overwritten before any lane
14
+ * compares against it, and a word no current entry names is never read. The design's row implies a reset; the `fill`
15
+ * over `n` per round is the dispatch this saves.
16
+ *
17
+ * The count of `compact` is host-known. `dedupe`'s callers (the SSSP piles of P8-T9) only know their entry count on
18
+ * the device, so `countIndex` names a word of the counters block and the kernels read `min(counters[countIndex],
19
+ * count)` -- the device word clamped to the capacity `count` -- or `count` alone when `countIndex` is `U32_MAX`. The
20
+ * counters block is bound WHOLE and indexed, for the count word and for the output word alike, because
21
+ * `Kernel.bind` rejects a binding whose offset is not a multiple of 256 and a four-byte word never is.
22
+ *
23
+ * The driver owns no device objects: the caller supplies a ReduceScope (the same record `reduce` takes) and the
24
+ * compute pass to record into. `src/primitives/**` never imports `src/context.ts`.
25
+ */
26
+ import { U32_MAX } from "../constants.js";
27
+ import { WebGpuGraphError } from "../errors.js";
28
+ import { plan1d, planGridStride } from "../kernel/dispatch.js";
29
+ import { COMPACT_PARAMS, kernelSpec } from "../kernels.js";
30
+ import { prepareScan } from "./scan.js";
31
+ /**
32
+ * Prepares the three pipelines and the scan of a scope (compiles once) so record() is synchronous. The planner
33
+ * lives exactly as long as the scope (the scan keeps a scratch word of it): never use it after the scope's dispose().
34
+ * @param scope - the caller's scope (device, caps, cache, scratch, params)
35
+ * @returns the planner
36
+ */
37
+ export async function prepareCompact(scope) {
38
+ const scatter = await scope.pipelines.kernel(kernelSpec("compact-scatter"));
39
+ const claim = await scope.pipelines.kernel(kernelSpec("dedupe-claim"));
40
+ const filter = await scope.pipelines.kernel(kernelSpec("dedupe-filter"));
41
+ const scan = await prepareScan(scope);
42
+ return new CompactPlannerImpl(scope, scatter, claim, filter, scan);
43
+ }
44
+ /**
45
+ * The E_INVALID_ARGUMENT of a bad count.
46
+ * @param what - the primitive's name for the message
47
+ * @param count - the count
48
+ */
49
+ function checkCount(what, count) {
50
+ if (!Number.isSafeInteger(count) || count < 0 || count > U32_MAX) {
51
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: count must be a non-negative integer below 2^32`, {
52
+ argument: "count",
53
+ value: count,
54
+ });
55
+ }
56
+ }
57
+ /**
58
+ * The E_INVALID_ARGUMENT of a binding shorter than `words` u32.
59
+ * @param what - the primitive's name for the message
60
+ * @param name - the argument name
61
+ * @param binding - the binding
62
+ * @param words - the words it must hold
63
+ */
64
+ function checkWords(what, name, binding, words) {
65
+ if (binding.size < 4 * words) {
66
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: ${name} is smaller than 4 x ${words} bytes`, {
67
+ argument: name,
68
+ value: binding.size,
69
+ expected: 4 * words,
70
+ });
71
+ }
72
+ }
73
+ /**
74
+ * The E_INVALID_ARGUMENT of a word index outside a block.
75
+ * @param what - the primitive's name for the message
76
+ * @param name - the argument name
77
+ * @param index - the word index
78
+ * @param block - the block it indexes
79
+ */
80
+ function checkWordIndex(what, name, index, block) {
81
+ if (!Number.isSafeInteger(index) || index < 0 || 4 * (index + 1) > block.size) {
82
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: ${name} ${index} is outside the block`, {
83
+ argument: name,
84
+ value: index,
85
+ expected: `0 <= ${name} < ${Math.floor(block.size / 4)}`,
86
+ });
87
+ }
88
+ }
89
+ /**
90
+ * The argument checks of record() (E_INVALID_ARGUMENT before anything is recorded).
91
+ * @param r - the record
92
+ */
93
+ function checkCompactArguments(r) {
94
+ checkCount("compact", r.count);
95
+ checkWords("compact", "queue", r.queue, r.count);
96
+ checkWords("compact", "flags", r.flags, r.count);
97
+ checkWords("compact", "out", r.out, r.count);
98
+ checkWordIndex("compact", "outIndex", r.outIndex, r.outCount);
99
+ }
100
+ /**
101
+ * The argument checks of recordDedupe(): the count, the two entry buffers (`out` holds at
102
+ * most one entry per owner word, so it is checked against `min(count, owner words)`: the SSSP piles of P8-T9 dedupe
103
+ * a raw half of `arcCount` capacity into an `n`-word pile), the output word, and -- with a device count -- a count
104
+ * word that is inside `counters`, is not the output word (the filter's atomicAdd would corrupt the count other
105
+ * workgroups of the same dispatch still read) and lives in the block `outCount` names (the two kernels read it
106
+ * through their own binding).
107
+ * @param r - the record
108
+ */
109
+ function checkDedupeArguments(r) {
110
+ checkCount("dedupe", r.count);
111
+ checkWords("dedupe", "queue", r.queue, r.count);
112
+ checkWords("dedupe", "out", r.out, Math.min(r.count, Math.floor(r.owner.size / 4)));
113
+ checkWordIndex("dedupe", "outIndex", r.outIndex, r.outCount);
114
+ if (r.countIndex === U32_MAX) {
115
+ return;
116
+ }
117
+ checkWordIndex("dedupe", "countIndex", r.countIndex, r.counters);
118
+ if (r.countIndex === r.outIndex) {
119
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", "dedupe: countIndex must not be the output word outIndex", {
120
+ argument: "countIndex",
121
+ value: r.countIndex,
122
+ });
123
+ }
124
+ if (r.counters.buffer !== r.outCount.buffer ||
125
+ r.counters.offset !== r.outCount.offset ||
126
+ r.counters.size !== r.outCount.size) {
127
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", "dedupe: counters must be the same range as outCount when countIndex names a device word", {
128
+ argument: "counters",
129
+ value: r.counters.size,
130
+ expected: r.outCount.size,
131
+ });
132
+ }
133
+ }
134
+ /** The planner: the three resolved kernels and the scan over one scope. */
135
+ class CompactPlannerImpl {
136
+ /**
137
+ * Wraps the resolved kernels; use prepareCompact().
138
+ * @param scope - the caller's scope
139
+ * @param scatter - the `compact-scatter` kernel
140
+ * @param claim - the `dedupe-claim` kernel
141
+ * @param filter - the `dedupe-filter` kernel
142
+ * @param scan - the scan planner of the same scope
143
+ */
144
+ constructor(scope, scatter, claim, filter, scan) {
145
+ this.dispatches = 0;
146
+ this.scope = scope;
147
+ this.scatter = scatter;
148
+ this.claim = claim;
149
+ this.filter = filter;
150
+ this.scan = scan;
151
+ }
152
+ /**
153
+ * Dispatches the last record*() issued.
154
+ * @returns the count
155
+ */
156
+ get lastDispatches() {
157
+ return this.dispatches;
158
+ }
159
+ /**
160
+ * Records the scan and the scatter (see the interface).
161
+ * @param pass - the compute pass
162
+ * @param r - the record
163
+ */
164
+ record(pass, r) {
165
+ checkCompactArguments(r);
166
+ if (r.count === 0) {
167
+ this.dispatches = 0;
168
+ return;
169
+ }
170
+ const size = 4 * r.count;
171
+ const offsets = { buffer: this.scope.scratch(size, "compact/offsets"), offset: 0, size, window: null };
172
+ this.scan.record(pass, r.flags, r.count, offsets);
173
+ const params = this.scope.params(COMPACT_PARAMS, {
174
+ count: r.count,
175
+ outIndex: r.outIndex,
176
+ countIndex: U32_MAX,
177
+ stride: 0,
178
+ });
179
+ const bound = this.scatter.bind({
180
+ queue: r.queue,
181
+ flags: r.flags,
182
+ offsets,
183
+ out: r.out,
184
+ outCount: r.outCount,
185
+ P: params.binding,
186
+ });
187
+ this.scatter.dispatch(pass, bound, plan1d(r.count, this.scope.workgroupSize, this.scope.caps), [params.offset]);
188
+ this.dispatches = this.scan.lastDispatches + 1;
189
+ }
190
+ /**
191
+ * Records the claim and the filter over plan1d(count) (see the interface).
192
+ * @param pass - the compute pass
193
+ * @param r - the record
194
+ */
195
+ recordDedupe(pass, r) {
196
+ checkDedupeArguments(r);
197
+ if (r.count === 0) {
198
+ this.dispatches = 0;
199
+ return;
200
+ }
201
+ const plan = planGridStride(r.count, this.scope.workgroupSize, this.scope.caps);
202
+ const bound = this.bindDedupe(r, plan.stride ?? this.scope.workgroupSize);
203
+ this.claim.dispatch(pass, bound.claim, plan, [bound.offset]);
204
+ this.filter.dispatch(pass, bound.filter, plan, [bound.offset]);
205
+ this.dispatches = 2;
206
+ }
207
+ /**
208
+ * One params record (both kernels read the same values) and the two bind groups.
209
+ * @param r - the record
210
+ * @param stride - the grid-stride plan's stride (entries per pass over the grid)
211
+ * @returns the bound kernels and the record's dynamic offset
212
+ */
213
+ bindDedupe(r, stride) {
214
+ const params = this.scope.params(COMPACT_PARAMS, {
215
+ count: r.count,
216
+ outIndex: r.outIndex,
217
+ countIndex: r.countIndex,
218
+ stride,
219
+ });
220
+ return {
221
+ claim: this.claim.bind({ queue: r.queue, owner: r.owner, counters: r.counters, P: params.binding }),
222
+ filter: this.filter.bind({
223
+ queue: r.queue,
224
+ owner: r.owner,
225
+ out: r.out,
226
+ outCount: r.outCount,
227
+ P: params.binding,
228
+ }),
229
+ offset: params.offset,
230
+ };
231
+ }
232
+ }
233
+ //# sourceMappingURL=compact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact.js","sourceRoot":"","sources":["../../../src/primitives/compact.ts"],"names":[],"mappings":"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"}
@@ -44,6 +44,25 @@ export declare function arcCountOf(core: CoreBinding): number;
44
44
  * @returns the binding; E_INVALID_ARGUMENT when the core is not windowed or the array is absent
45
45
  */
46
46
  export declare function windowBinding(core: CoreBinding, name: "colIdx" | "weights" | "arcToEdge", w: ArcWindow): Binding;
47
+ /** One dispatch of a frontier-walking kernel over a core (P8-T12): the arc range it owns and the core with its arc-indexed arrays bound to that window (the core itself, over every arc, when it is not windowed). */
48
+ export interface CoreWindow {
49
+ readonly arcBase: number;
50
+ readonly arcEnd: number;
51
+ readonly core: CoreBinding;
52
+ }
53
+ /**
54
+ * The per-window dispatches of a kernel that walks rows named by a FRONTIER rather than by a row range (P8-T12:
55
+ * `advance-expand`, `bfs-fused`, `bfs-bottom-up`, `sssp-pred`): every window sees every frontier entry and clips
56
+ * each row to `[arcBase, arcEnd)`, so the ranges must PARTITION `[0, arcCount)` or a row is expanded twice. P4's
57
+ * windows do not: `planArcWindows` opens a window at the previous window's end aligned DOWN to 64 arcs, so two
58
+ * consecutive windows overlap by up to 63 arcs at an unaligned row boundary (the overlap belongs to the earlier
59
+ * window's rows, which a row-range dispatch such as `degree` never revisits). Here a window owns `[start, next
60
+ * window's start)` -- the last one `[start, end)` -- which is inside its binding (`next.start <= end`) and
61
+ * partitions the arcs exactly; a window whose owned range is empty dispatches, harmlessly, over nothing.
62
+ * @param core - the core (windowed or not)
63
+ * @returns one entry per window, in arc order; exactly one entry over `[0, arcCount)` when the core is not windowed
64
+ */
65
+ export declare function coreWindows(core: CoreBinding): readonly CoreWindow[];
47
66
  /**
48
67
  * Rejects a windowed core: the pull cannot accumulate its affine epilogue across windows (DEP-P4-B).
49
68
  * @param core - the core
@@ -67,7 +86,9 @@ export declare function assertWholeCore(core: CoreBinding, arcCount: number, lim
67
86
  * package. `plan` is "perArray" because views always upload per array (spec 4.3 lines 1182-1186) and `windows` is
68
87
  * null because a view is never windowed, which is what makes assertNotWindowed pass for a view. `serial` is -1: a
69
88
  * view is not a core and no caller of this function reads serial (nothing in src/primitives or src/algorithms
70
- * reads CoreBinding.serial).
89
+ * reads CoreBinding.serial). A zero-size `colIdx` or `weights` binding (the reverse view of an arc-less directed
90
+ * snapshot uploads zero-byte arc arrays) becomes null, the core's own spelling of "no arcs", so `graphBindings` binds
91
+ * the dummy instead of a zero-length range (P8-T8: the one-node directed fixture of the BFS suite).
71
92
  * @param v - the view binding, from residency.view(s, "reverse") or view(s, "edgeList")
72
93
  * @param arcCount - the arc count of the view, from v.scalars.arcCount[0]; it must agree with the colIdx binding,
73
94
  * which is what record() derives the arc window from (E_INVALID_ARGUMENT { argument: "arcCount" } otherwise)
@@ -1 +1 @@
1
- {"version":3,"file":"core-shape.d.ts","sourceRoot":"","sources":["../../../src/primitives/core-shape.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElE,iJAAiJ;AACjJ,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,gIAAgI;AAChI,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CA4B5D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAcvE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAUhH;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAM5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAQ3G;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAmCxE"}
1
+ {"version":3,"file":"core-shape.d.ts","sourceRoot":"","sources":["../../../src/primitives/core-shape.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElE,iJAAiJ;AACjJ,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,gIAAgI;AAChI,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CA4B5D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAcvE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAUhH;AAED,sNAAsN;AACtN,MAAM,WAAW,UAAU;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,UAAU,EAAE,CAapE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAM5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAQ3G;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAqCxE"}
@@ -83,6 +83,32 @@ export function windowBinding(core, name, w) {
83
83
  }
84
84
  return { buffer, offset: w.offset, size: 4 * (w.end - w.start), window: w };
85
85
  }
86
+ /**
87
+ * The per-window dispatches of a kernel that walks rows named by a FRONTIER rather than by a row range (P8-T12:
88
+ * `advance-expand`, `bfs-fused`, `bfs-bottom-up`, `sssp-pred`): every window sees every frontier entry and clips
89
+ * each row to `[arcBase, arcEnd)`, so the ranges must PARTITION `[0, arcCount)` or a row is expanded twice. P4's
90
+ * windows do not: `planArcWindows` opens a window at the previous window's end aligned DOWN to 64 arcs, so two
91
+ * consecutive windows overlap by up to 63 arcs at an unaligned row boundary (the overlap belongs to the earlier
92
+ * window's rows, which a row-range dispatch such as `degree` never revisits). Here a window owns `[start, next
93
+ * window's start)` -- the last one `[start, end)` -- which is inside its binding (`next.start <= end`) and
94
+ * partitions the arcs exactly; a window whose owned range is empty dispatches, harmlessly, over nothing.
95
+ * @param core - the core (windowed or not)
96
+ * @returns one entry per window, in arc order; exactly one entry over `[0, arcCount)` when the core is not windowed
97
+ */
98
+ export function coreWindows(core) {
99
+ if (core.windows === null) {
100
+ return [{ arcBase: 0, arcEnd: arcCountOf(core), core }];
101
+ }
102
+ return core.windows.map((w, k, all) => ({
103
+ arcBase: w.start,
104
+ arcEnd: k + 1 < all.length ? all[k + 1].start : w.end,
105
+ core: {
106
+ ...core,
107
+ colIdx: windowBinding(core, "colIdx", w),
108
+ weights: core.weights === null ? null : windowBinding(core, "weights", w),
109
+ },
110
+ }));
111
+ }
86
112
  /**
87
113
  * Rejects a windowed core: the pull cannot accumulate its affine epilogue across windows (DEP-P4-B).
88
114
  * @param core - the core
@@ -116,7 +142,9 @@ export function assertWholeCore(core, arcCount, limit, algorithm) {
116
142
  * package. `plan` is "perArray" because views always upload per array (spec 4.3 lines 1182-1186) and `windows` is
117
143
  * null because a view is never windowed, which is what makes assertNotWindowed pass for a view. `serial` is -1: a
118
144
  * view is not a core and no caller of this function reads serial (nothing in src/primitives or src/algorithms
119
- * reads CoreBinding.serial).
145
+ * reads CoreBinding.serial). A zero-size `colIdx` or `weights` binding (the reverse view of an arc-less directed
146
+ * snapshot uploads zero-byte arc arrays) becomes null, the core's own spelling of "no arcs", so `graphBindings` binds
147
+ * the dummy instead of a zero-length range (P8-T8: the one-node directed fixture of the BFS suite).
120
148
  * @param v - the view binding, from residency.view(s, "reverse") or view(s, "edgeList")
121
149
  * @param arcCount - the arc count of the view, from v.scalars.arcCount[0]; it must agree with the colIdx binding,
122
150
  * which is what record() derives the arc window from (E_INVALID_ARGUMENT { argument: "arcCount" } otherwise)
@@ -131,8 +159,10 @@ export function coreOfView(v, arcCount) {
131
159
  expected: "a view with a rowPtr binding (reverse)",
132
160
  });
133
161
  }
134
- const colIdx = v.bindings.colIdx ?? null;
135
- const weights = v.bindings.weights ?? null;
162
+ // an arc-less DIRECTED snapshot's reverse view uploads zero-byte arc arrays; the core spells "no arcs" as null
163
+ // (graphBindings then binds the rowPtr dummy), and a zero-size binding is never bound (spec 3.6)
164
+ const colIdx = v.bindings.colIdx === undefined || v.bindings.colIdx.size === 0 ? null : v.bindings.colIdx;
165
+ const weights = v.bindings.weights === undefined || v.bindings.weights.size === 0 ? null : v.bindings.weights;
136
166
  const bound = colIdx === null ? 0 : colIdx.size / 4;
137
167
  if (bound !== arcCount) {
138
168
  throw new WebGpuGraphError("E_INVALID_ARGUMENT", `the ${v.view} view binds ${bound} arcs but its arcCount scalar says ${arcCount}`, {