@graphty/webgpu-graph-algorithms 0.6.4 → 0.6.5
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.
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-Dvq-Cc6v.js → context-hzGggHeM.js} +28 -30
- package/dist/chunks/context-hzGggHeM.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/src/algorithms/bfs.d.ts +1 -1
- package/dist/src/algorithms/bfs.js +1 -1
- package/dist/src/algorithms/scope.d.ts +3 -3
- package/dist/src/algorithms/scope.d.ts.map +1 -1
- package/dist/src/algorithms/scope.js +0 -2
- package/dist/src/algorithms/scope.js.map +1 -1
- package/dist/src/algorithms/sssp.d.ts +4 -3
- package/dist/src/algorithms/sssp.d.ts.map +1 -1
- package/dist/src/algorithms/sssp.js +4 -3
- package/dist/src/algorithms/sssp.js.map +1 -1
- package/dist/src/constants.d.ts +0 -2
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +0 -2
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernels.d.ts +8 -7
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +12 -15
- package/dist/src/kernels.js.map +1 -1
- package/dist/src/primitives/advance.d.ts +3 -2
- package/dist/src/primitives/advance.d.ts.map +1 -1
- package/dist/src/primitives/advance.js.map +1 -1
- package/dist/src/primitives/frontier.d.ts +33 -38
- package/dist/src/primitives/frontier.d.ts.map +1 -1
- package/dist/src/primitives/frontier.js +23 -32
- package/dist/src/primitives/frontier.js.map +1 -1
- package/dist/src/wgsl/frontier-finalize.wgsl.d.ts +24 -30
- package/dist/src/wgsl/frontier-finalize.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/frontier-finalize.wgsl.js +36 -82
- package/dist/src/wgsl/frontier-finalize.wgsl.js.map +1 -1
- package/dist/webgpu-graph-algorithms.js +27 -86
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +1 -1
- package/src/algorithms/bfs.ts +1 -1
- package/src/algorithms/scope.ts +3 -10
- package/src/algorithms/sssp.ts +4 -3
- package/src/constants.ts +0 -2
- package/src/kernels.ts +12 -15
- package/src/primitives/advance.ts +5 -4
- package/src/primitives/frontier.ts +40 -56
- package/src/wgsl/frontier-finalize.wgsl.ts +36 -82
- package/dist/chunks/context-Dvq-Cc6v.js.map +0 -1
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
import { type Kernel } from "../kernel/kernel.js";
|
|
31
31
|
import { type CoreBinding } from "../memory/residency.js";
|
|
32
32
|
import { type CoreWindow } from "./core-shape.js";
|
|
33
|
-
import { type Frontier
|
|
33
|
+
import { type Frontier } from "./frontier.js";
|
|
34
|
+
import { type ReduceScope } from "./reduce.js";
|
|
34
35
|
/** A prepared advance (design 6 row 8): records one expansion of a frontier per level. */
|
|
35
36
|
export interface AdvancePlanner {
|
|
36
37
|
/** The compiled `advance-expand` kernel (so `bfs-fused` can share its bind groups' shape). */
|
|
@@ -58,5 +59,5 @@ export interface AdvancePlanner {
|
|
|
58
59
|
* @param core - the resident core arrays of the snapshot the frontier walks (windowed or not)
|
|
59
60
|
* @returns the planner
|
|
60
61
|
*/
|
|
61
|
-
export declare function prepareAdvance(scope:
|
|
62
|
+
export declare function prepareAdvance(scope: ReduceScope, core: CoreBinding): Promise<AdvancePlanner>;
|
|
62
63
|
//# sourceMappingURL=advance.d.ts.map
|
|
@@ -1 +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,
|
|
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,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,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,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAGnG"}
|
|
@@ -1 +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;
|
|
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;AAwB3E;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAkB,EAAE,IAAiB;IACtE,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,KAAkB,EAAE,IAAiB,EAAE,MAAc;QAC7D,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"}
|
|
@@ -1,38 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `Frontier` of design 6 row 7 and the device-side dispatch selector of design 5.4 (P8-T4; the P8 plan's PD-1,
|
|
3
|
-
* PD-3, PD-8, PD-23, DEP-P8-A, DEP-P8-C). A traversal's per-level state is two n-slot vertex queues, ONE
|
|
3
|
+
* PD-3, PD-8, PD-23, DEP-P8-A, DEP-P8-C). A traversal's per-level state is two n-slot vertex queues, ONE 25-word
|
|
4
4
|
* counters block (every counter of the phase is a word of it: a four-byte word is never a legal storage-binding
|
|
5
|
-
* offset, and the selector must reach every count it acts on through one binding)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
5
|
+
* offset, and the selector must reach every count it acts on through one binding) and the edge queue. The host
|
|
6
|
+
* never reads a counter inside a submit: `frontier-finalize`, one lane, runs at the START of every level (role 0:
|
|
7
|
+
* rotates `nextFrontierCount` into `frontierCount`, advances `level`, decides `done`, chooses the level's path) and
|
|
8
|
+
* again once the edge queue is filled (role 1: clamps `edgeCount`, or on an overflow -- `edgeCountUnclamped >
|
|
9
|
+
* edgeCapacity` -- switches the path to the fused retry, PD-23). The choice is the block's `path` word (`W.path`,
|
|
10
|
+
* word 24); every level kernel is a direct grid-stride dispatch that reads it first and does nothing unless the
|
|
11
|
+
* word names it (design/decisions/2026-09-25-frontier-kernels-dispatch-directly.md: the seven indirect slots the
|
|
12
|
+
* selector once wrote per level cost about 0.4 ms of Dawn validation each and were 97 % of a traversal's wall
|
|
13
|
+
* time; they and their args buffer are gone). The host records up to `MAX_LEVELS_PER_SUBMIT` levels per submit and
|
|
14
|
+
* reads `done` (four bytes) once per submit; a boundary that finds `done` set writes path 0 and moves no counter,
|
|
15
|
+
* so the recorded levels past the end are no-ops and the counters freeze at the finishing boundary's values.
|
|
14
16
|
*
|
|
15
17
|
* The seed: `frontierCount` is NEVER seeded, because the first boundary rotates it out unread. A BFS driver calls
|
|
16
18
|
* `reset(queue, source, { nextFrontierCount: 1, level: U32_MAX })`: the first boundary rotates the 1 in, adds it into
|
|
17
19
|
* `visitedCount`, and wraps `level` to 0, so the level-0 expansion claims the source's neighbours at `level + 1 == 1`.
|
|
18
20
|
* `reset` is a queue write, ordered before the submit that follows, and it puts the source on side 0.
|
|
19
21
|
*
|
|
20
|
-
* Every buffer comes from the caller's ONE lease (`scope.scratch
|
|
22
|
+
* Every buffer comes from the caller's ONE lease (`scope.scratch`) so the algorithm's dispose()
|
|
21
23
|
* releases them together (design 4.4). The two vertex queues are two BUFFERS, never two ranges of one: `Kernel.bind`
|
|
22
24
|
* rejects one buffer bound read-only and read-write in one dispatch even for disjoint ranges. `src/primitives/**`
|
|
23
25
|
* never imports `src/context.ts`.
|
|
24
26
|
*/
|
|
25
27
|
import { type Binding } from "../types/memory.js";
|
|
26
28
|
import { type ReduceScope } from "./reduce.js";
|
|
27
|
-
/** The
|
|
28
|
-
export declare const
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
/** The values of the `path` word (`W.path`): what a level's kernels run; every level kernel reads it first. */
|
|
30
|
+
export declare const PATH: Readonly<{
|
|
31
|
+
none: 0;
|
|
32
|
+
twoPhase: 1;
|
|
31
33
|
fused: 2;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
bottomUp: 3;
|
|
35
|
+
fusedRetry: 4;
|
|
36
|
+
near: 5;
|
|
37
|
+
far: 6;
|
|
36
38
|
}>;
|
|
37
39
|
/** The words of the counters block (`FRONTIER_COUNTERS`), by index: byte offset 4 x word; no driver types a number. */
|
|
38
40
|
export declare const W: Readonly<{
|
|
@@ -64,21 +66,14 @@ export declare const W: Readonly<{
|
|
|
64
66
|
}>;
|
|
65
67
|
/** The words a `reset` seeds (every other word is zeroed). */
|
|
66
68
|
export type FrontierSeed = Readonly<Partial<Record<keyof typeof W, number>>>;
|
|
67
|
-
/**
|
|
68
|
-
export interface FrontierScope extends ReduceScope {
|
|
69
|
-
/** A STORAGE | INDIRECT | COPY_DST | COPY_SRC buffer of the scope's lease. */
|
|
70
|
-
indirect(byteLength: number, label: string): GPUBuffer;
|
|
71
|
-
}
|
|
72
|
-
/** The `FrontierParams` fields a caller passes to `recordFinalize`; the planner fills `role`, `slotBase`, `wg`, `edgeCapacity` and `n` itself. A missing field is written as 0. */
|
|
69
|
+
/** The `FrontierParams` fields a caller passes to `recordFinalize`; the planner fills `role`, `wg`, `edgeCapacity` and `n` itself. A missing field is written as 0. */
|
|
73
70
|
export type FrontierFinalizeFields = Readonly<Partial<Record<"alpha" | "beta" | "fusedMax" | "maxDepth" | "mode" | "cutoffBits" | "arcBase" | "arcEnd" | "predKind" | "bitsBase" | "source" | "stride" | "firstOfSubmit" | "iteration", number>>>;
|
|
74
|
-
/** The frontier queue of design 6 row 7: two vertex queues, the counters block
|
|
71
|
+
/** The frontier queue of design 6 row 7: two vertex queues, the counters block and the edge queue, all leased by the caller's scope. */
|
|
75
72
|
export declare class Frontier {
|
|
76
73
|
/** The two n-slot u32 vertex queues (`vertices[side]` is the input of the current level). */
|
|
77
74
|
readonly vertices: readonly [Binding, Binding];
|
|
78
|
-
/** The `FrontierCounters` block,
|
|
75
|
+
/** The `FrontierCounters` block, 112 B, bound by every kernel as `array<atomic<u32>>`. */
|
|
79
76
|
readonly counters: Binding;
|
|
80
|
-
/** The indirect args, `MAX_LEVELS_PER_SUBMIT x FRONTIER_CANDIDATES` 16-byte slots (usage INDIRECT | STORAGE | COPY_DST | COPY_SRC). */
|
|
81
|
-
readonly args: Binding;
|
|
82
77
|
/** The edge queue: `edgeCapacity` entries of u32 (the target vertex of an arc). */
|
|
83
78
|
readonly edgeQueue: Binding;
|
|
84
79
|
/** How many entries the edge queue holds; role 1 clamps `edgeCount` to it and detects an overflow above it. */
|
|
@@ -90,12 +85,11 @@ export declare class Frontier {
|
|
|
90
85
|
* Wraps the leased buffers; use prepareFrontier().
|
|
91
86
|
* @param vertices - the two vertex queues
|
|
92
87
|
* @param counters - the counters block
|
|
93
|
-
* @param args - the args buffer
|
|
94
88
|
* @param edgeQueue - the edge queue
|
|
95
89
|
* @param edgeCapacity - the edge queue's entry count
|
|
96
90
|
* @param n - the vertex count
|
|
97
91
|
*/
|
|
98
|
-
constructor(vertices: readonly [Binding, Binding], counters: Binding,
|
|
92
|
+
constructor(vertices: readonly [Binding, Binding], counters: Binding, edgeQueue: Binding, edgeCapacity: number, n: number);
|
|
99
93
|
/**
|
|
100
94
|
* Which vertex queue is the input of the current level (a host-side index between two cached bind-group sets).
|
|
101
95
|
* @returns 0 or 1
|
|
@@ -114,7 +108,7 @@ export declare class Frontier {
|
|
|
114
108
|
/** Flips the two vertex queues; the counts rotate inside the block, so nothing else moves. */
|
|
115
109
|
swap(): void;
|
|
116
110
|
/**
|
|
117
|
-
* Seeds a traversal: one `queue.writeBuffer` of the whole
|
|
111
|
+
* Seeds a traversal: one `queue.writeBuffer` of the whole 112-byte block (zero except the caller's words) and one of
|
|
118
112
|
* `vertices[0][0] = source`, both ordered before the submit that follows; the source is on side 0 afterwards.
|
|
119
113
|
* `frontierCount` is not a word to seed: the first boundary rotates word 1 into it (the BFS seed is
|
|
120
114
|
* `{ nextFrontierCount: 1, level: U32_MAX }`). A source outside `[0, n)`, an unknown word or a value that is not a
|
|
@@ -127,13 +121,14 @@ export declare class Frontier {
|
|
|
127
121
|
}
|
|
128
122
|
/** A prepared frontier (design 6 row 7): the leased queue and the recorded selector dispatches. */
|
|
129
123
|
export interface FrontierPlanner {
|
|
130
|
-
/** The queue the planner
|
|
124
|
+
/** The queue the planner's selector rotates and chooses the path of. */
|
|
131
125
|
readonly frontier: Frontier;
|
|
132
126
|
/**
|
|
133
127
|
* Records one `frontier-finalize` dispatch (one workgroup) in `role` for `level` of the current submit: one
|
|
134
|
-
* `FrontierParams` record with `
|
|
135
|
-
*
|
|
136
|
-
* `[0, 3]` is E_INVALID_ARGUMENT before
|
|
128
|
+
* `FrontierParams` record with `wg`, `edgeCapacity` and `n` filled by the planner and every other field from
|
|
129
|
+
* `fields`. A level outside `[0, MAX_LEVELS_PER_SUBMIT)` (the selector addresses nothing by level any more, but
|
|
130
|
+
* the host's submit cadence still is the bound) or a role outside `[0, 3]` is E_INVALID_ARGUMENT before
|
|
131
|
+
* anything is recorded.
|
|
137
132
|
* @param pass - the compute pass
|
|
138
133
|
* @param role - 0 the level boundary, 1 the edge-queue role (2 and 3 are P8-T9's)
|
|
139
134
|
* @param level - the level inside the submit
|
|
@@ -146,11 +141,11 @@ export interface FrontierPlanner {
|
|
|
146
141
|
* edge capacity defaults to `max(1, min(arcCount, floor(maxStorageBufferBindingSize / 4)))` (never a zero-length
|
|
147
142
|
* buffer: the one-node graph has no arcs); a test passes a small one to force the overflow path. The planner lives
|
|
148
143
|
* exactly as long as the scope: never use it after the scope's dispose().
|
|
149
|
-
* @param scope - the caller's scope (device, caps, cache, scratch,
|
|
144
|
+
* @param scope - the caller's scope (device, caps, cache, scratch, params)
|
|
150
145
|
* @param n - the vertex count
|
|
151
146
|
* @param arcCount - the arc count (the natural edge-queue size)
|
|
152
147
|
* @param edgeCapacity - the edge queue's entry count, when the caller chooses it (an integer >= 1)
|
|
153
148
|
* @returns the planner
|
|
154
149
|
*/
|
|
155
|
-
export declare function prepareFrontier(scope:
|
|
150
|
+
export declare function prepareFrontier(scope: ReduceScope, n: number, arcCount: number, edgeCapacity?: number): Promise<FrontierPlanner>;
|
|
156
151
|
//# sourceMappingURL=frontier.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontier.d.ts","sourceRoot":"","sources":["../../../src/primitives/frontier.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"frontier.d.ts","sourceRoot":"","sources":["../../../src/primitives/frontier.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `Frontier` of design 6 row 7 and the device-side dispatch selector of design 5.4 (P8-T4; the P8 plan's PD-1,
|
|
3
|
-
* PD-3, PD-8, PD-23, DEP-P8-A, DEP-P8-C). A traversal's per-level state is two n-slot vertex queues, ONE
|
|
3
|
+
* PD-3, PD-8, PD-23, DEP-P8-A, DEP-P8-C). A traversal's per-level state is two n-slot vertex queues, ONE 25-word
|
|
4
4
|
* counters block (every counter of the phase is a word of it: a four-byte word is never a legal storage-binding
|
|
5
|
-
* offset, and the selector must reach every count it acts on through one binding)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
5
|
+
* offset, and the selector must reach every count it acts on through one binding) and the edge queue. The host
|
|
6
|
+
* never reads a counter inside a submit: `frontier-finalize`, one lane, runs at the START of every level (role 0:
|
|
7
|
+
* rotates `nextFrontierCount` into `frontierCount`, advances `level`, decides `done`, chooses the level's path) and
|
|
8
|
+
* again once the edge queue is filled (role 1: clamps `edgeCount`, or on an overflow -- `edgeCountUnclamped >
|
|
9
|
+
* edgeCapacity` -- switches the path to the fused retry, PD-23). The choice is the block's `path` word (`W.path`,
|
|
10
|
+
* word 24); every level kernel is a direct grid-stride dispatch that reads it first and does nothing unless the
|
|
11
|
+
* word names it (design/decisions/2026-09-25-frontier-kernels-dispatch-directly.md: the seven indirect slots the
|
|
12
|
+
* selector once wrote per level cost about 0.4 ms of Dawn validation each and were 97 % of a traversal's wall
|
|
13
|
+
* time; they and their args buffer are gone). The host records up to `MAX_LEVELS_PER_SUBMIT` levels per submit and
|
|
14
|
+
* reads `done` (four bytes) once per submit; a boundary that finds `done` set writes path 0 and moves no counter,
|
|
15
|
+
* so the recorded levels past the end are no-ops and the counters freeze at the finishing boundary's values.
|
|
14
16
|
*
|
|
15
17
|
* The seed: `frontierCount` is NEVER seeded, because the first boundary rotates it out unread. A BFS driver calls
|
|
16
18
|
* `reset(queue, source, { nextFrontierCount: 1, level: U32_MAX })`: the first boundary rotates the 1 in, adds it into
|
|
17
19
|
* `visitedCount`, and wraps `level` to 0, so the level-0 expansion claims the source's neighbours at `level + 1 == 1`.
|
|
18
20
|
* `reset` is a queue write, ordered before the submit that follows, and it puts the source on side 0.
|
|
19
21
|
*
|
|
20
|
-
* Every buffer comes from the caller's ONE lease (`scope.scratch
|
|
22
|
+
* Every buffer comes from the caller's ONE lease (`scope.scratch`) so the algorithm's dispose()
|
|
21
23
|
* releases them together (design 4.4). The two vertex queues are two BUFFERS, never two ranges of one: `Kernel.bind`
|
|
22
24
|
* rejects one buffer bound read-only and read-write in one dispatch even for disjoint ranges. `src/primitives/**`
|
|
23
25
|
* never imports `src/context.ts`.
|
|
24
26
|
*/
|
|
25
|
-
import {
|
|
27
|
+
import { MAX_LEVELS_PER_SUBMIT, U32_MAX } from "../constants.js";
|
|
26
28
|
import { WebGpuGraphError } from "../errors.js";
|
|
27
29
|
import { plan1d } from "../kernel/dispatch.js";
|
|
28
|
-
import { INDIRECT_ARGS_STRIDE } from "../kernel/kernel.js";
|
|
29
30
|
import { FRONTIER_COUNTERS, FRONTIER_PARAMS, kernelSpec } from "../kernels.js";
|
|
30
|
-
/** The
|
|
31
|
-
export const
|
|
31
|
+
/** The values of the `path` word (`W.path`): what a level's kernels run; every level kernel reads it first. */
|
|
32
|
+
export const PATH = Object.freeze({ none: 0, twoPhase: 1, fused: 2, bottomUp: 3, fusedRetry: 4, near: 5, far: 6 });
|
|
32
33
|
/** The words of the counters block (`FRONTIER_COUNTERS`), by index: byte offset 4 x word; no driver types a number. */
|
|
33
34
|
export const W = Object.freeze({
|
|
34
35
|
frontierCount: 0,
|
|
@@ -88,22 +89,20 @@ function assertCount(argument, value) {
|
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
|
-
/** The frontier queue of design 6 row 7: two vertex queues, the counters block
|
|
92
|
+
/** The frontier queue of design 6 row 7: two vertex queues, the counters block and the edge queue, all leased by the caller's scope. */
|
|
92
93
|
export class Frontier {
|
|
93
94
|
/**
|
|
94
95
|
* Wraps the leased buffers; use prepareFrontier().
|
|
95
96
|
* @param vertices - the two vertex queues
|
|
96
97
|
* @param counters - the counters block
|
|
97
|
-
* @param args - the args buffer
|
|
98
98
|
* @param edgeQueue - the edge queue
|
|
99
99
|
* @param edgeCapacity - the edge queue's entry count
|
|
100
100
|
* @param n - the vertex count
|
|
101
101
|
*/
|
|
102
|
-
constructor(vertices, counters,
|
|
102
|
+
constructor(vertices, counters, edgeQueue, edgeCapacity, n) {
|
|
103
103
|
this.sideIndex = 0;
|
|
104
104
|
this.vertices = vertices;
|
|
105
105
|
this.counters = counters;
|
|
106
|
-
this.args = args;
|
|
107
106
|
this.edgeQueue = edgeQueue;
|
|
108
107
|
this.edgeCapacity = edgeCapacity;
|
|
109
108
|
this.n = n;
|
|
@@ -134,7 +133,7 @@ export class Frontier {
|
|
|
134
133
|
this.sideIndex = this.sideIndex === 0 ? 1 : 0;
|
|
135
134
|
}
|
|
136
135
|
/**
|
|
137
|
-
* Seeds a traversal: one `queue.writeBuffer` of the whole
|
|
136
|
+
* Seeds a traversal: one `queue.writeBuffer` of the whole 112-byte block (zero except the caller's words) and one of
|
|
138
137
|
* `vertices[0][0] = source`, both ordered before the submit that follows; the source is on side 0 afterwards.
|
|
139
138
|
* `frontierCount` is not a word to seed: the first boundary rotates word 1 into it (the BFS seed is
|
|
140
139
|
* `{ nextFrontierCount: 1, level: U32_MAX }`). A source outside `[0, n)`, an unknown word or a value that is not a
|
|
@@ -163,7 +162,7 @@ export class Frontier {
|
|
|
163
162
|
* edge capacity defaults to `max(1, min(arcCount, floor(maxStorageBufferBindingSize / 4)))` (never a zero-length
|
|
164
163
|
* buffer: the one-node graph has no arcs); a test passes a small one to force the overflow path. The planner lives
|
|
165
164
|
* exactly as long as the scope: never use it after the scope's dispose().
|
|
166
|
-
* @param scope - the caller's scope (device, caps, cache, scratch,
|
|
165
|
+
* @param scope - the caller's scope (device, caps, cache, scratch, params)
|
|
167
166
|
* @param n - the vertex count
|
|
168
167
|
* @param arcCount - the arc count (the natural edge-queue size)
|
|
169
168
|
* @param edgeCapacity - the edge queue's entry count, when the caller chooses it (an integer >= 1)
|
|
@@ -182,7 +181,6 @@ export async function prepareFrontier(scope, n, arcCount, edgeCapacity) {
|
|
|
182
181
|
}
|
|
183
182
|
const kernel = await scope.pipelines.kernel(kernelSpec("frontier-finalize"));
|
|
184
183
|
const queueBytes = 4 * Math.max(1, n);
|
|
185
|
-
const argsBytes = MAX_LEVELS_PER_SUBMIT * FRONTIER_CANDIDATES * INDIRECT_ARGS_STRIDE;
|
|
186
184
|
const vertices = [
|
|
187
185
|
{ buffer: scope.scratch(queueBytes, "frontier/vertices-0"), offset: 0, size: queueBytes, window: null },
|
|
188
186
|
{ buffer: scope.scratch(queueBytes, "frontier/vertices-1"), offset: 0, size: queueBytes, window: null },
|
|
@@ -193,22 +191,16 @@ export async function prepareFrontier(scope, n, arcCount, edgeCapacity) {
|
|
|
193
191
|
size: FRONTIER_COUNTERS.byteLength,
|
|
194
192
|
window: null,
|
|
195
193
|
};
|
|
196
|
-
const args = {
|
|
197
|
-
buffer: scope.indirect(argsBytes, "frontier/args"),
|
|
198
|
-
offset: 0,
|
|
199
|
-
size: argsBytes,
|
|
200
|
-
window: null,
|
|
201
|
-
};
|
|
202
194
|
const edgeQueue = {
|
|
203
195
|
buffer: scope.scratch(4 * capacity, "frontier/edge-queue"),
|
|
204
196
|
offset: 0,
|
|
205
197
|
size: 4 * capacity,
|
|
206
198
|
window: null,
|
|
207
199
|
};
|
|
208
|
-
const frontier = new Frontier(vertices, counters,
|
|
200
|
+
const frontier = new Frontier(vertices, counters, edgeQueue, capacity, n);
|
|
209
201
|
return new FrontierPlannerImpl(scope, kernel, frontier);
|
|
210
202
|
}
|
|
211
|
-
/** The planner: the selector kernel bound once to the frontier's block
|
|
203
|
+
/** The planner: the selector kernel bound once to the frontier's block. */
|
|
212
204
|
class FrontierPlannerImpl {
|
|
213
205
|
/**
|
|
214
206
|
* Wraps the resolved kernel; use prepareFrontier().
|
|
@@ -247,12 +239,11 @@ class FrontierPlannerImpl {
|
|
|
247
239
|
const params = scope.params(FRONTIER_PARAMS, {
|
|
248
240
|
...definedWords(fields),
|
|
249
241
|
role,
|
|
250
|
-
slotBase: level * FRONTIER_CANDIDATES,
|
|
251
242
|
wg: scope.workgroupSize,
|
|
252
243
|
edgeCapacity: frontier.edgeCapacity,
|
|
253
244
|
n: frontier.n,
|
|
254
245
|
});
|
|
255
|
-
const bound = this.kernel.bind({ counters: frontier.counters,
|
|
246
|
+
const bound = this.kernel.bind({ counters: frontier.counters, P: params.binding });
|
|
256
247
|
this.kernel.dispatch(pass, bound, plan1d(1, scope.workgroupSize, scope.caps), [params.offset]);
|
|
257
248
|
}
|
|
258
249
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontier.js","sourceRoot":"","sources":["../../../src/primitives/frontier.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"frontier.js","sourceRoot":"","sources":["../../../src/primitives/frontier.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `frontier-finalize` kernel body (design 5.4, 6 row 7; P8-T4, the P8 plan's PD-3 / PD-23 / DEP-P8-C): the
|
|
3
3
|
* device-side selector of the frontier family. One workgroup, one lane, no barrier after the early return (spec 3.5
|
|
4
|
-
* rule 1). It is recorded TWICE per level, in two roles chosen by `P.role`, because a level's
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
4
|
+
* rule 1). It is recorded TWICE per level, in two roles chosen by `P.role`, because a level's counts become known at
|
|
5
|
+
* two moments: role 0 runs at the START of a level -- rotates `nextFrontierCount` into `frontierCount`, advances
|
|
6
|
+
* `level`, decides `done` (an empty frontier, or `level >= P.maxDepth`) and chooses the path; role 1 runs once the
|
|
7
|
+
* edge queue is filled -- clamps `edgeCount` to `P.edgeCapacity`, or, when `edgeCountUnclamped` exceeds the
|
|
8
|
+
* capacity, switches the path to the fused retry over `frontierCount` (PD-23). The decision is ONE word of the
|
|
9
|
+
* counters block, `path` (word 24): 0 nothing (done, or a level past the end), 1 two-phase, 2 fused, 3 bottom-up,
|
|
10
|
+
* 4 the fused retry (role 1), 5 a near SSSP round, 6 a far one (role 2). Every level kernel is a direct grid-stride
|
|
11
|
+
* dispatch that reads that word first and runs only when it names it (decision record
|
|
12
|
+
* design/decisions/2026-09-25-frontier-kernels-dispatch-directly.md: Dawn's validation of an indirect dispatch cost
|
|
13
|
+
* about 0.4 ms of device time each, and the seven indirect slots this kernel once wrote per level were 97 % of a
|
|
14
|
+
* traversal's wall time; the slots and their args buffer are gone). Two rules: a boundary that finds `done` set
|
|
15
|
+
* moves no counter word (the host records levels past the end); role 1 counts a two-phase level only when role 0
|
|
16
|
+
* chose one, which it reads from the path word (a storage write of one dispatch is visible to the next of the same
|
|
17
|
+
* pass).
|
|
16
18
|
*
|
|
17
19
|
* Roles 2 and 3 are the SSSP round boundary of the near-far loop (P8-T9, PD-20), over the same block read in its
|
|
18
20
|
* SSSP sense (word 1 the raw near half's appends, 21 the raw far half's, 0 and 20 the deduped pile counts, 22 the
|
|
19
|
-
* threshold, 23 the delta, 4 the previous threshold, 14 the round's mode)
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* threshold, 23 the delta, 4 the previous threshold, 14 the round's mode): role 2 finds a non-empty raw near half
|
|
22
|
+
* and sizes the near dedupe (count word 1, output word 0; the dedupe's count in word 8) in mode 0; finds it empty
|
|
23
|
+
* and the far half not, raises the threshold by the delta (one f32 add; an add that returns the threshold
|
|
22
24
|
* unchanged sets `done 3`, the host's E_UNSUPPORTED), remembers the previous threshold in word 4 and sizes the far
|
|
23
|
-
* dedupe (
|
|
24
|
-
* raw half above the capacity and sets `done 2` (the host's E_TOO_LARGE). It counts a round in
|
|
25
|
-
* picks a mode and NOT at the done boundary, so `level` at the end is the number of relax rounds
|
|
26
|
-
* boundary that finds `done` set obeys rule 1. Role 3 runs once the dedupe has landed
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* dedupe (count word 21, output word 20; the dedupe's count in word 9) in mode 1; finds both empty and sets
|
|
26
|
+
* `done 1`; and finds a raw half above the capacity and sets `done 2` (the host's E_TOO_LARGE). It counts a round in
|
|
27
|
+
* `level` when it picks a mode and NOT at the done boundary, so `level` at the end is the number of relax rounds
|
|
28
|
+
* dispatched; a boundary that finds `done` set obeys rule 1. Role 3 runs once the dedupe has landed and restarts the
|
|
29
|
+
* raw half the round consumed: mode 0 restarts the raw near half (word 1 to 0), mode 1 the raw far half (word 21
|
|
30
|
+
* to 0); the relax kernels size themselves from words 0 and 20.
|
|
29
31
|
*
|
|
30
32
|
* Beamer's test (P8-T8, PD-21), evaluated at every boundary BEFORE the `done` branch (so a switch can be counted at
|
|
31
33
|
* the done boundary too, which the host model of the tests mirrors): top-down switches to bottom-up when
|
|
@@ -46,14 +48,6 @@
|
|
|
46
48
|
* overstated m_u makes the switch INTO bottom-up harder, never easier -- and the next submit's rebuild makes it
|
|
47
49
|
* exact again. Body only (spec 3.5, D9); the text is normative: the sabotage rows of test/helpers/sabotage.ts are
|
|
48
50
|
* textual edits of it.
|
|
49
|
-
*
|
|
50
|
-
* Since 2026-09-25 nothing dispatches FROM the slots (G8-F5: Dawn's validation of an indirect dispatch cost about
|
|
51
|
-
* 0.4 ms of device time each, whether or not it dispatched anything, and the seven slots of thirty-two recorded
|
|
52
|
-
* levels were 97 % of a traversal's wall time). Every level kernel is a direct grid-stride dispatch that reads the
|
|
53
|
-
* `path` word (24) this kernel writes -- 0 nothing (done, or a level past the end), 1 two-phase, 2 fused, 3
|
|
54
|
-
* bottom-up, 4 the fused retry (role 1), 5 a near SSSP round, 6 a far one (role 2) -- and the SSSP dedupes read
|
|
55
|
-
* their counts from words 8 and 9, which role 2 writes. The slots stay as the selector's recorded decision, read
|
|
56
|
-
* back by the frontier tests; deleting them with those tests is the follow-up.
|
|
57
51
|
*/
|
|
58
|
-
export declare const frontierFinalizeWgsl = "\nfn write_slot_groups(slot: u32, groups: u32, count: u32) { // groups workgroups, split in 2D above the per-dim limit\n var x = groups;\n var y = 1u;\n if (groups > MAX_WORKGROUPS_PER_DIM) {\n x = MAX_WORKGROUPS_PER_DIM;\n y = (groups + MAX_WORKGROUPS_PER_DIM - 1u) / MAX_WORKGROUPS_PER_DIM;\n }\n let base = 4u * (P.slotBase + slot); // 16-byte slots: (x, y, 1, count)\n args[base] = x; args[base + 1u] = y; args[base + 2u] = 1u; args[base + 3u] = count;\n}\nfn write_slot(slot: u32, count: u32) { // one INVOCATION per entry: ceil(count / wg) workgroups\n let groups = count / P.wg + select(0u, 1u, count % P.wg != 0u); // ceil(count / wg) without the u32 wrap (indirect-finalize's rule)\n write_slot_groups(slot, groups, count);\n}\nfn zero_slot(slot: u32) {\n let base = 4u * (P.slotBase + slot);\n args[base] = 0u; args[base + 1u] = 0u; args[base + 2u] = 1u; args[base + 3u] = 0u;\n}\n\n@compute @workgroup_size(WG)\nfn frontier_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {\n if (lid.x != 0u) { return; } // one lane; no barrier follows (3.5 rule 1)\n if (P.role == 0u) { // the level boundary\n if (atomicLoad(&counters[15]) != 0u) { // done already: a no-op level the host recorded past the end\n for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); } // this slotBase holds the previous submit's args\n return; // no counter word moves (P8-T6's levels formula reads them)\n }\n let finished = atomicLoad(&counters[0]);\n let next = atomicLoad(&counters[1]);\n let degSum = atomicLoad(&counters[2]);\n atomicStore(&counters[3], finished); // prevFrontierCount\n atomicStore(&counters[4], degSum); // prevDegreeSum\n atomicStore(&counters[0], next); // the rotation\n atomicStore(&counters[1], 0u);\n atomicStore(&counters[2], 0u);\n atomicStore(&counters[8], 0u); // edgeCount\n atomicStore(&counters[9], 0u); // edgeCountUnclamped\n atomicStore(&counters[12], atomicLoad(&counters[12]) + next); // visitedCount\n if (P.firstOfSubmit >= 1u) { // b = the boundary's index inside its submit, clamped to 2 (P8-T8, PD-18)\n atomicStore(&counters[5], atomicLoad(&counters[5]) - next); // unvisitedCount (exact): F_b was inside the submit's rebuilt sum iff b >= 1\n }\n if (P.firstOfSubmit >= 2u) {\n atomicStore(&counters[6], atomicLoad(&counters[6]) - degSum); // unvisitedDegreeSum (one level stale): F_{b-1} was inside it iff b >= 2\n }\n let level = atomicLoad(&counters[11]) + 1u; // the seed is U32_MAX, so the first boundary lands on 0\n atomicStore(&counters[11], level);\n let done = (next == 0u) || (level >= P.maxDepth);\n atomicStore(&counters[15], select(0u, 1u, done));\n var direction = atomicLoad(&counters[14]);\n if (P.mode == 1u) {\n direction = 0u; // top-down only (the test seam)\n } else if (direction == 0u) {\n if (degSum > atomicLoad(&counters[6]) / P.alpha && next > finished) { direction = 1u; } // m_f > m_u / alpha and growing\n } else {\n if (next * P.beta < atomicLoad(&counters[5]) && next < finished) { direction = 0u; } // next * beta < unvisited and shrinking\n }\n if (direction != atomicLoad(&counters[14])) { atomicStore(&counters[13], atomicLoad(&counters[13]) + 1u); } // switches\n var path = 0u; // word 24: what the level's kernels run (0 nothing, 1 two-phase, 2 fused, 3 bottom-up; role 1 writes 4 for the retry)\n if (done) {\n for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); }\n } else if (direction == 1u) { // the bottom-up level (P8-T8): the bits fill, the bitset build, the sweep\n zero_slot(0u); zero_slot(1u); zero_slot(2u); zero_slot(6u);\n write_slot(3u, (P.n + 31u) / 32u); write_slot(4u, next); write_slot(5u, atomicLoad(&counters[7]));\n path = 3u;\n atomicStore(&counters[19], atomicLoad(&counters[19]) + 1u);\n } else if (next < P.fusedMax) { // P8-T7 makes this branch reachable (fusedMax is 0 until then)\n zero_slot(0u); zero_slot(1u); zero_slot(3u); zero_slot(4u); zero_slot(5u); zero_slot(6u);\n write_slot_groups(2u, next, next); // bfs-fused is one WORKGROUP per frontier entry\n path = 2u;\n atomicStore(&counters[17], atomicLoad(&counters[17]) + 1u);\n } else {\n zero_slot(2u); zero_slot(3u); zero_slot(4u); zero_slot(5u);\n write_slot(0u, next); // slots 1 and 6 are role 1's\n path = 1u;\n }\n atomicStore(&counters[14], direction);\n atomicStore(&counters[24], path);\n } else if (P.role == 1u) { // the edge queue is filled\n if (args[4u * P.slotBase] == 0u) { // role 0 did not choose the two-phase path (done, fused or bottom-up): nothing to size, nothing to count\n zero_slot(1u); zero_slot(6u);\n return;\n }\n let clamped = min(atomicLoad(&counters[8]), P.edgeCapacity);\n atomicStore(&counters[8], clamped);\n if (atomicLoad(&counters[9]) > P.edgeCapacity) { // PD-23: the fused retry\n let entries = atomicLoad(&counters[0]);\n zero_slot(1u); write_slot_groups(6u, entries, entries); // one workgroup per frontier entry, as slot 2\n atomicStore(&counters[24], 4u); // the path word: bfs-fused runs the retry, bfs-contract nothing\n atomicStore(&counters[10], atomicLoad(&counters[10]) + 1u);\n atomicStore(&counters[17], atomicLoad(&counters[17]) + 1u);\n } else {\n write_slot(1u, clamped); zero_slot(6u);\n atomicStore(&counters[18], atomicLoad(&counters[18]) + 1u); // twoPhaseLevels counts the CHOICE role 0 made, even for zero edges (P8-T7 Step 4's invariant)\n }\n } else if (P.role == 2u) { // the SSSP round boundary (P8-T9, PD-20): which pile this round relaxes\n atomicStore(&counters[8], 0u); // the dedupe counts (words 8 and 9, the SSSP sense) and the path word: nothing unless a pile is chosen below\n atomicStore(&counters[9], 0u);\n atomicStore(&counters[24], 0u);\n if (atomicLoad(&counters[15]) != 0u) { // done already: a no-op round the host recorded past the end (rule 1)\n for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); }\n return;\n }\n let nearRaw = atomicLoad(&counters[1]); // the raw near half's appends, unclamped\n let farRaw = atomicLoad(&counters[21]); // the raw far half's appends, unclamped\n if (nearRaw > P.edgeCapacity || farRaw > P.edgeCapacity) { // a pile overflowed its half: the host raises E_TOO_LARGE\n atomicStore(&counters[15], 2u);\n for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); }\n return;\n }\n zero_slot(2u); zero_slot(5u); zero_slot(6u); // role 3 sizes the relax slots once the piles are deduped\n if (nearRaw != 0u) { // a near round: dedupe the near half into nearIn\n atomicStore(&counters[0], 0u); // the deduped near count, accumulated by dedupe-filter\n atomicStore(&counters[14], 0u); // mode 0\n write_slot(0u, nearRaw); write_slot(1u, nearRaw); // dedupe-claim, dedupe-filter over the near half\n atomicStore(&counters[8], nearRaw); // the near dedupe's count word\n atomicStore(&counters[24], 5u); // the path word: sssp-relax role 0 runs, role 1 nothing\n zero_slot(3u); zero_slot(4u);\n atomicStore(&counters[11], atomicLoad(&counters[11]) + 1u); // rounds dispatched (the done boundary is not counted)\n } else if (farRaw != 0u) { // the near pile is empty: raise the threshold and re-bucket the far pile\n let threshold = bitcast<f32>(atomicLoad(&counters[22]));\n let raised = threshold + bitcast<f32>(atomicLoad(&counters[23])); // ONE f32 add on the bit patterns (PD-9)\n if (raised == threshold) { // the delta is below the threshold's ulp: the host raises E_UNSUPPORTED\n atomicStore(&counters[15], 3u);\n for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); }\n return;\n }\n atomicStore(&counters[4], atomicLoad(&counters[22])); // prevThresholdBits: what the pass-through drops below\n atomicStore(&counters[22], bitcast<u32>(raised));\n atomicStore(&counters[20], 0u); // the deduped far count, accumulated by dedupe-filter\n atomicStore(&counters[14], 1u); // mode 1\n zero_slot(0u); zero_slot(1u);\n write_slot(3u, farRaw); write_slot(4u, farRaw); // dedupe-claim, dedupe-filter over the far half\n atomicStore(&counters[9], farRaw); // the far dedupe's count word\n atomicStore(&counters[24], 6u); // the path word: sssp-relax role 1 runs, role 0 nothing\n atomicStore(&counters[11], atomicLoad(&counters[11]) + 1u);\n } else { // both piles empty: finished\n atomicStore(&counters[15], 1u);\n for (var s = 0u; s < 7u; s = s + 1u) { zero_slot(s); }\n }\n } else if (P.role == 3u) { // the piles are deduped: size the relax\n if (atomicLoad(&counters[15]) != 0u) { return; } // role 2 zeroed every slot of the round\n if (atomicLoad(&counters[14]) == 0u) {\n write_slot(2u, atomicLoad(&counters[0])); // the near round over nearIn\n atomicStore(&counters[1], 0u); // the raw near half restarts\n } else {\n write_slot(5u, atomicLoad(&counters[20])); // the pass-through over farIn\n atomicStore(&counters[21], 0u); // the raw far half restarts (the pass-through re-appends what stays far)\n }\n }\n}\n";
|
|
52
|
+
export declare const frontierFinalizeWgsl = "\n@compute @workgroup_size(WG)\nfn frontier_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {\n if (lid.x != 0u) { return; } // one lane; no barrier follows (3.5 rule 1)\n if (P.role == 0u) { // the level boundary\n if (atomicLoad(&counters[15]) != 0u) { // done already: a no-op level the host recorded past the end\n atomicStore(&counters[24], 0u); // the path word is the only word that moves (P8-T6's levels formula reads the rest)\n return;\n }\n let finished = atomicLoad(&counters[0]);\n let next = atomicLoad(&counters[1]);\n let degSum = atomicLoad(&counters[2]);\n atomicStore(&counters[3], finished); // prevFrontierCount\n atomicStore(&counters[4], degSum); // prevDegreeSum\n atomicStore(&counters[0], next); // the rotation\n atomicStore(&counters[1], 0u);\n atomicStore(&counters[2], 0u);\n atomicStore(&counters[8], 0u); // edgeCount\n atomicStore(&counters[9], 0u); // edgeCountUnclamped\n atomicStore(&counters[12], atomicLoad(&counters[12]) + next); // visitedCount\n if (P.firstOfSubmit >= 1u) { // b = the boundary's index inside its submit, clamped to 2 (P8-T8, PD-18)\n atomicStore(&counters[5], atomicLoad(&counters[5]) - next); // unvisitedCount (exact): F_b was inside the submit's rebuilt sum iff b >= 1\n }\n if (P.firstOfSubmit >= 2u) {\n atomicStore(&counters[6], atomicLoad(&counters[6]) - degSum); // unvisitedDegreeSum (one level stale): F_{b-1} was inside it iff b >= 2\n }\n let level = atomicLoad(&counters[11]) + 1u; // the seed is U32_MAX, so the first boundary lands on 0\n atomicStore(&counters[11], level);\n let done = (next == 0u) || (level >= P.maxDepth);\n atomicStore(&counters[15], select(0u, 1u, done));\n var direction = atomicLoad(&counters[14]);\n if (P.mode == 1u) {\n direction = 0u; // top-down only (the test seam)\n } else if (direction == 0u) {\n if (degSum > atomicLoad(&counters[6]) / P.alpha && next > finished) { direction = 1u; } // m_f > m_u / alpha and growing\n } else {\n if (next * P.beta < atomicLoad(&counters[5]) && next < finished) { direction = 0u; } // next * beta < unvisited and shrinking\n }\n if (direction != atomicLoad(&counters[14])) { atomicStore(&counters[13], atomicLoad(&counters[13]) + 1u); } // switches\n var path = 0u; // word 24: what the level's kernels run (0 nothing, 1 two-phase, 2 fused, 3 bottom-up; role 1 writes 4 for the retry)\n if (done) {\n path = 0u;\n } else if (direction == 1u) { // the bottom-up level (P8-T8): the bits fill, the bitset build, the sweep\n path = 3u;\n atomicStore(&counters[19], atomicLoad(&counters[19]) + 1u);\n } else if (next < P.fusedMax) { // P8-T7 makes this branch reachable (fusedMax is 0 until then)\n path = 2u; // bfs-fused: one WORKGROUP per frontier entry\n atomicStore(&counters[17], atomicLoad(&counters[17]) + 1u);\n } else {\n path = 1u; // advance-expand, then role 1 and bfs-contract\n }\n atomicStore(&counters[14], direction);\n atomicStore(&counters[24], path);\n } else if (P.role == 1u) { // the edge queue is filled\n if (atomicLoad(&counters[24]) != 1u) { // role 0 did not choose the two-phase path (done, fused or bottom-up): nothing to clamp, nothing to count\n return;\n }\n let clamped = min(atomicLoad(&counters[8]), P.edgeCapacity);\n atomicStore(&counters[8], clamped);\n if (atomicLoad(&counters[9]) > P.edgeCapacity) { // PD-23: the fused retry\n atomicStore(&counters[24], 4u); // the path word: bfs-fused runs the retry over frontierCount, bfs-contract nothing\n atomicStore(&counters[10], atomicLoad(&counters[10]) + 1u);\n atomicStore(&counters[17], atomicLoad(&counters[17]) + 1u);\n } else {\n atomicStore(&counters[18], atomicLoad(&counters[18]) + 1u); // twoPhaseLevels counts the CHOICE role 0 made, even for zero edges (P8-T7 Step 4's invariant)\n }\n } else if (P.role == 2u) { // the SSSP round boundary (P8-T9, PD-20): which pile this round relaxes\n atomicStore(&counters[8], 0u); // the dedupe counts (words 8 and 9, the SSSP sense) and the path word: nothing unless a pile is chosen below\n atomicStore(&counters[9], 0u);\n atomicStore(&counters[24], 0u);\n if (atomicLoad(&counters[15]) != 0u) { // done already: a no-op round the host recorded past the end (rule 1)\n return;\n }\n let nearRaw = atomicLoad(&counters[1]); // the raw near half's appends, unclamped\n let farRaw = atomicLoad(&counters[21]); // the raw far half's appends, unclamped\n if (nearRaw > P.edgeCapacity || farRaw > P.edgeCapacity) { // a pile overflowed its half: the host raises E_TOO_LARGE\n atomicStore(&counters[15], 2u);\n return;\n }\n if (nearRaw != 0u) { // a near round: dedupe the near half into nearIn\n atomicStore(&counters[0], 0u); // the deduped near count, accumulated by dedupe-filter\n atomicStore(&counters[14], 0u); // mode 0\n atomicStore(&counters[8], nearRaw); // the near dedupe's count word (dedupe-claim, dedupe-filter over the near half)\n atomicStore(&counters[24], 5u); // the path word: sssp-relax role 0 runs, role 1 nothing\n atomicStore(&counters[11], atomicLoad(&counters[11]) + 1u); // rounds dispatched (the done boundary is not counted)\n } else if (farRaw != 0u) { // the near pile is empty: raise the threshold and re-bucket the far pile\n let threshold = bitcast<f32>(atomicLoad(&counters[22]));\n let raised = threshold + bitcast<f32>(atomicLoad(&counters[23])); // ONE f32 add on the bit patterns (PD-9)\n if (raised == threshold) { // the delta is below the threshold's ulp: the host raises E_UNSUPPORTED\n atomicStore(&counters[15], 3u);\n return;\n }\n atomicStore(&counters[4], atomicLoad(&counters[22])); // prevThresholdBits: what the pass-through drops below\n atomicStore(&counters[22], bitcast<u32>(raised));\n atomicStore(&counters[20], 0u); // the deduped far count, accumulated by dedupe-filter\n atomicStore(&counters[14], 1u); // mode 1\n atomicStore(&counters[9], farRaw); // the far dedupe's count word (dedupe-claim, dedupe-filter over the far half)\n atomicStore(&counters[24], 6u); // the path word: sssp-relax role 1 runs, role 0 nothing\n atomicStore(&counters[11], atomicLoad(&counters[11]) + 1u);\n } else { // both piles empty: finished\n atomicStore(&counters[15], 1u);\n }\n } else if (P.role == 3u) { // the piles are deduped: restart the raw half the round consumed\n if (atomicLoad(&counters[15]) != 0u) { return; } // role 2 chose no pile this round\n if (atomicLoad(&counters[14]) == 0u) {\n atomicStore(&counters[1], 0u); // the raw near half restarts (sssp-relax role 0 sizes itself from word 0)\n } else {\n atomicStore(&counters[21], 0u); // the raw far half restarts (the pass-through re-appends what stays far; role 1 sizes itself from word 20)\n }\n }\n}\n";
|
|
59
53
|
//# sourceMappingURL=frontier-finalize.wgsl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontier-finalize.wgsl.d.ts","sourceRoot":"","sources":["../../../src/wgsl/frontier-finalize.wgsl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"frontier-finalize.wgsl.d.ts","sourceRoot":"","sources":["../../../src/wgsl/frontier-finalize.wgsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,eAAO,MAAM,oBAAoB,spRA+GhC,CAAC"}
|