knitting 0.1.56 → 0.1.61

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 (101) hide show
  1. package/BROWSER.md +134 -0
  2. package/README.md +347 -46
  3. package/knitting.browser.d.ts +6 -0
  4. package/knitting.browser.js +1 -0
  5. package/knitting.d.ts +3 -2
  6. package/knitting.js +3 -3
  7. package/map.md +21 -9
  8. package/package.json +17 -2
  9. package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
  10. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  11. package/prebuilds/darwin-arm64-node-127/knitting_shm.node +0 -0
  12. package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
  13. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  14. package/prebuilds/darwin-arm64-node-137/knitting_shm.node +0 -0
  15. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  16. package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
  17. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  18. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  19. package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
  20. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  21. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  22. package/scripts/build-compiled-worker.ts +276 -0
  23. package/scripts/compiled-worker/porffor.d.ts +5 -0
  24. package/scripts/compiled-worker/runtime.ts +529 -0
  25. package/scripts/compiled-worker/task-shim.ts +32 -0
  26. package/src/api.d.ts +24 -1
  27. package/src/api.js +311 -27
  28. package/src/common/module-url.d.ts +1 -0
  29. package/src/common/module-url.js +30 -2
  30. package/src/common/node-compat.d.ts +2 -0
  31. package/src/common/path-canonical.js +20 -6
  32. package/src/common/runtime.d.ts +3 -1
  33. package/src/common/runtime.js +25 -4
  34. package/src/common/task-source.d.ts +2 -0
  35. package/src/common/task-source.js +17 -1
  36. package/src/common/worker-runtime.js +16 -7
  37. package/src/connections/buffer-reference-native.js +25 -1
  38. package/src/connections/buffer-reference.d.ts +23 -2
  39. package/src/connections/buffer-reference.js +143 -29
  40. package/src/connections/bun.d.ts +1 -1
  41. package/src/connections/bun.js +25 -14
  42. package/src/connections/deno.d.ts +3 -1
  43. package/src/connections/deno.js +35 -14
  44. package/src/connections/external-array-buffer.d.ts +2 -0
  45. package/src/connections/external-array-buffer.js +33 -0
  46. package/src/connections/node-addons.d.ts +11 -0
  47. package/src/connections/node-addons.js +49 -1
  48. package/src/connections/node-ffi-api.d.ts +19 -0
  49. package/src/connections/node-ffi-api.js +28 -0
  50. package/src/connections/node-ffi.d.ts +27 -0
  51. package/src/connections/node-ffi.js +262 -0
  52. package/src/connections/node.d.ts +1 -1
  53. package/src/connections/node.js +20 -20
  54. package/src/connections/package-assets.js +25 -15
  55. package/src/connections/posix.d.ts +42 -0
  56. package/src/connections/posix.js +101 -0
  57. package/src/connections/process-shared-buffer.d.ts +1 -0
  58. package/src/connections/process-shared-buffer.js +11 -45
  59. package/src/connections/types.d.ts +4 -0
  60. package/src/connections/windows.d.ts +19 -10
  61. package/src/connections/windows.js +87 -66
  62. package/src/error.d.ts +7 -6
  63. package/src/error.js +8 -7
  64. package/src/memory/lock.d.ts +112 -77
  65. package/src/memory/lock.js +314 -87
  66. package/src/memory/payloadCodec.d.ts +11 -2
  67. package/src/memory/payloadCodec.js +129 -37
  68. package/src/memory/regionRegistry.d.ts +1 -3
  69. package/src/memory/regionRegistry.js +16 -14
  70. package/src/permission/compatibility.d.ts +28 -0
  71. package/src/permission/compatibility.js +239 -0
  72. package/src/permission/index.d.ts +2 -0
  73. package/src/permission/index.js +1 -0
  74. package/src/permission/protocol.d.ts +1 -0
  75. package/src/permission/protocol.js +59 -15
  76. package/src/runtime/compiled-artifact.d.ts +21 -0
  77. package/src/runtime/compiled-artifact.js +260 -0
  78. package/src/runtime/compiled-builder.d.ts +8 -0
  79. package/src/runtime/compiled-builder.js +42 -0
  80. package/src/runtime/compiled-worker.d.ts +27 -0
  81. package/src/runtime/compiled-worker.js +562 -0
  82. package/src/runtime/dispatcher.js +9 -0
  83. package/src/runtime/inline-executor.js +27 -15
  84. package/src/runtime/pool.d.ts +121 -5
  85. package/src/runtime/pool.js +263 -49
  86. package/src/runtime/process-worker.d.ts +3 -13
  87. package/src/runtime/process-worker.js +97 -101
  88. package/src/runtime/tx-queue.d.ts +18 -2
  89. package/src/runtime/tx-queue.js +56 -16
  90. package/src/runtime/worker-common.d.ts +13 -0
  91. package/src/runtime/worker-common.js +102 -0
  92. package/src/types.d.ts +89 -9
  93. package/src/worker/composable-runners.js +3 -2
  94. package/src/worker/loop.d.ts +1 -0
  95. package/src/worker/loop.js +52 -17
  96. package/src/worker/rx-queue.d.ts +11 -1
  97. package/src/worker/rx-queue.js +4 -1
  98. package/src/worker/task-loader.d.ts +5 -4
  99. package/src/worker/task-loader.js +13 -7
  100. package/src/worker/timers.d.ts +8 -1
  101. package/src/worker/timers.js +28 -23
@@ -1,5 +1,10 @@
1
1
  import RingQueue from "../ipc/tools/ring-queue.js";
2
- import { decodePayload, encodePayload } from "./payloadCodec.js";
2
+ let registeredEncodePayload;
3
+ let registeredDecodePayload;
4
+ export const registerLockPayloadCodec = (encode, decode) => {
5
+ registeredEncodePayload = encode;
6
+ registeredDecodePayload = decode;
7
+ };
3
8
  import { createSharedArrayBuffer, createWasmSharedArrayBuffer, } from "../common/runtime.js";
4
9
  import { toSharedBufferRegion, } from "../common/shared-buffer-region.js";
5
10
  import { probeLockBufferTextCompat, } from "../common/shared-buffer-text.js";
@@ -7,69 +12,76 @@ import { resolvePayloadBufferOptions, } from "./payload-config.js";
7
12
  /**
8
13
  * TODO: Compose all the instance where the array is passed as argument
9
14
  */
10
- export var PayloadSignal;
11
- (function (PayloadSignal) {
12
- PayloadSignal[PayloadSignal["UNREACHABLE"] = 0] = "UNREACHABLE";
13
- PayloadSignal[PayloadSignal["BigInt"] = 2] = "BigInt";
14
- PayloadSignal[PayloadSignal["True"] = 3] = "True";
15
- PayloadSignal[PayloadSignal["False"] = 4] = "False";
16
- PayloadSignal[PayloadSignal["Undefined"] = 5] = "Undefined";
17
- PayloadSignal[PayloadSignal["NaN"] = 6] = "NaN";
18
- PayloadSignal[PayloadSignal["Float64"] = 9] = "Float64";
19
- PayloadSignal[PayloadSignal["Null"] = 10] = "Null";
20
- })(PayloadSignal || (PayloadSignal = {}));
21
- export var PayloadBuffer;
22
- (function (PayloadBuffer) {
23
- PayloadBuffer[PayloadBuffer["BORDER_SIGNAL_BUFFER"] = 11] = "BORDER_SIGNAL_BUFFER";
24
- PayloadBuffer[PayloadBuffer["String"] = 11] = "String";
25
- PayloadBuffer[PayloadBuffer["Json"] = 12] = "Json";
26
- PayloadBuffer[PayloadBuffer["StaticString"] = 15] = "StaticString";
27
- PayloadBuffer[PayloadBuffer["StaticJson"] = 16] = "StaticJson";
28
- PayloadBuffer[PayloadBuffer["Binary"] = 17] = "Binary";
29
- PayloadBuffer[PayloadBuffer["StaticBinary"] = 18] = "StaticBinary";
30
- PayloadBuffer[PayloadBuffer["Int32Array"] = 19] = "Int32Array";
31
- PayloadBuffer[PayloadBuffer["Float64Array"] = 20] = "Float64Array";
32
- PayloadBuffer[PayloadBuffer["BigInt64Array"] = 21] = "BigInt64Array";
33
- PayloadBuffer[PayloadBuffer["BigUint64Array"] = 22] = "BigUint64Array";
34
- PayloadBuffer[PayloadBuffer["DataView"] = 23] = "DataView";
35
- PayloadBuffer[PayloadBuffer["Error"] = 24] = "Error";
36
- PayloadBuffer[PayloadBuffer["Date"] = 25] = "Date";
37
- PayloadBuffer[PayloadBuffer["Symbol"] = 26] = "Symbol";
38
- PayloadBuffer[PayloadBuffer["StaticSymbol"] = 27] = "StaticSymbol";
39
- PayloadBuffer[PayloadBuffer["BigInt"] = 28] = "BigInt";
40
- PayloadBuffer[PayloadBuffer["StaticBigInt"] = 29] = "StaticBigInt";
41
- PayloadBuffer[PayloadBuffer["StaticInt32Array"] = 31] = "StaticInt32Array";
42
- PayloadBuffer[PayloadBuffer["StaticFloat64Array"] = 32] = "StaticFloat64Array";
43
- PayloadBuffer[PayloadBuffer["StaticBigInt64Array"] = 33] = "StaticBigInt64Array";
44
- PayloadBuffer[PayloadBuffer["StaticBigUint64Array"] = 34] = "StaticBigUint64Array";
45
- PayloadBuffer[PayloadBuffer["StaticDataView"] = 35] = "StaticDataView";
46
- PayloadBuffer[PayloadBuffer["ArrayBuffer"] = 36] = "ArrayBuffer";
47
- PayloadBuffer[PayloadBuffer["StaticArrayBuffer"] = 37] = "StaticArrayBuffer";
48
- PayloadBuffer[PayloadBuffer["Buffer"] = 38] = "Buffer";
49
- PayloadBuffer[PayloadBuffer["StaticBuffer"] = 39] = "StaticBuffer";
50
- PayloadBuffer[PayloadBuffer["EnvelopeStaticHeader"] = 40] = "EnvelopeStaticHeader";
51
- PayloadBuffer[PayloadBuffer["EnvelopeDynamicHeader"] = 41] = "EnvelopeDynamicHeader";
52
- PayloadBuffer[PayloadBuffer["EnvelopeStaticHeaderString"] = 42] = "EnvelopeStaticHeaderString";
53
- PayloadBuffer[PayloadBuffer["EnvelopeDynamicHeaderString"] = 43] = "EnvelopeDynamicHeaderString";
54
- PayloadBuffer[PayloadBuffer["ExternalPayload"] = 44] = "ExternalPayload";
55
- PayloadBuffer[PayloadBuffer["StaticExternalPayload"] = 45] = "StaticExternalPayload";
56
- PayloadBuffer[PayloadBuffer["ProcessSharedBuffer"] = 46] = "ProcessSharedBuffer";
57
- PayloadBuffer[PayloadBuffer["BufferReference"] = 47] = "BufferReference";
58
- PayloadBuffer[PayloadBuffer["SharedArrayBuffer"] = 48] = "SharedArrayBuffer";
59
- PayloadBuffer[PayloadBuffer["EnvelopeStaticHeaderExternal"] = 49] = "EnvelopeStaticHeaderExternal";
60
- PayloadBuffer[PayloadBuffer["EnvelopeDynamicHeaderExternal"] = 50] = "EnvelopeDynamicHeaderExternal";
61
- PayloadBuffer[PayloadBuffer["EnvelopeStaticHeaderStringExternal"] = 51] = "EnvelopeStaticHeaderStringExternal";
62
- PayloadBuffer[PayloadBuffer["EnvelopeDynamicHeaderStringExternal"] = 52] = "EnvelopeDynamicHeaderStringExternal";
63
- PayloadBuffer[PayloadBuffer["NumericArray"] = 53] = "NumericArray";
64
- PayloadBuffer[PayloadBuffer["StaticNumericArray"] = 54] = "StaticNumericArray";
65
- })(PayloadBuffer || (PayloadBuffer = {}));
66
- export var LockBound;
67
- (function (LockBound) {
68
- LockBound[LockBound["paddingLock"] = 0] = "paddingLock";
69
- LockBound[LockBound["padding"] = 0] = "padding";
70
- LockBound[LockBound["slots"] = 32] = "slots";
71
- LockBound[LockBound["header"] = 0] = "header";
72
- })(LockBound || (LockBound = {}));
15
+ // const objects replace `enum`s throughout this module: Andromeda's Nova engine
16
+ // panics on `enum`. Value access, duplicate-value members, and type preserved;
17
+ // identical emit on Node/Deno/Bun.
18
+ export const PayloadSignal = {
19
+ UNREACHABLE: 0,
20
+ BigInt: 2,
21
+ True: 3,
22
+ False: 4,
23
+ Undefined: 5,
24
+ NaN: 6,
25
+ Float64: 9,
26
+ Null: 10,
27
+ };
28
+ export const PayloadBuffer = {
29
+ BORDER_SIGNAL_BUFFER: 11,
30
+ String: 11,
31
+ Json: 12,
32
+ StaticString: 15,
33
+ StaticJson: 16,
34
+ Binary: 17,
35
+ StaticBinary: 18,
36
+ Int32Array: 19,
37
+ Float64Array: 20,
38
+ BigInt64Array: 21,
39
+ BigUint64Array: 22,
40
+ DataView: 23,
41
+ Error: 24,
42
+ Date: 25,
43
+ Symbol: 26,
44
+ StaticSymbol: 27,
45
+ BigInt: 28,
46
+ StaticBigInt: 29,
47
+ StaticInt32Array: 31,
48
+ StaticFloat64Array: 32,
49
+ StaticBigInt64Array: 33,
50
+ StaticBigUint64Array: 34,
51
+ StaticDataView: 35,
52
+ ArrayBuffer: 36,
53
+ StaticArrayBuffer: 37,
54
+ Buffer: 38,
55
+ StaticBuffer: 39,
56
+ EnvelopeStaticHeader: 40,
57
+ EnvelopeDynamicHeader: 41,
58
+ EnvelopeStaticHeaderString: 42,
59
+ EnvelopeDynamicHeaderString: 43,
60
+ ExternalPayload: 44,
61
+ StaticExternalPayload: 45,
62
+ ProcessSharedBuffer: 46,
63
+ BufferReference: 47,
64
+ SharedArrayBuffer: 48,
65
+ EnvelopeStaticHeaderExternal: 49,
66
+ EnvelopeDynamicHeaderExternal: 50,
67
+ EnvelopeStaticHeaderStringExternal: 51,
68
+ EnvelopeDynamicHeaderStringExternal: 52,
69
+ NumericArray: 53,
70
+ StaticNumericArray: 54,
71
+ };
72
+ // Value -> name lookup (enum reverse-map), for payload-limit error labels.
73
+ // Later keys win on duplicate values (11 -> "String"), as TS enums do.
74
+ const PayloadBufferName = {};
75
+ for (const key of Object.keys(PayloadBuffer)) {
76
+ PayloadBufferName[PayloadBuffer[key]] = key;
77
+ }
78
+ export const payloadBufferName = (value) => PayloadBufferName[value] ?? String(value);
79
+ export const LockBound = {
80
+ paddingLock: 0,
81
+ padding: 0,
82
+ slots: 32,
83
+ header: 0,
84
+ };
73
85
  export const LOCK_CACHE_LINE_BYTES = 64;
74
86
  export const LOCK_SECTOR_BYTES = 256;
75
87
  export const PayloadTransportFinalizer = Symbol.for("knitting.payloadCodec.transportFinalizer");
@@ -123,34 +135,33 @@ export const runTaskFinalizers = (task) => {
123
135
  }
124
136
  }
125
137
  };
126
- export var TaskIndex;
127
- (function (TaskIndex) {
138
+ export const TaskIndex = {
128
139
  /**
129
140
  * Worker -> host response flags word.
130
141
  */
131
- TaskIndex[TaskIndex["FlagsToHost"] = 0] = "FlagsToHost";
142
+ FlagsToHost: 0,
132
143
  /**
133
144
  * Host -> worker request function id (low 16 bits).
134
145
  * High 16 bits are reserved for caller metadata on request path.
135
146
  * NOTE: shares the same storage word as `FlagsToHost`.
136
147
  */
137
- TaskIndex[TaskIndex["FunctionID"] = 0] = "FunctionID";
138
- TaskIndex[TaskIndex["ID"] = 1] = "ID";
139
- TaskIndex[TaskIndex["Type"] = 2] = "Type";
140
- TaskIndex[TaskIndex["Start"] = 3] = "Start";
141
- TaskIndex[TaskIndex["End"] = 4] = "End";
142
- TaskIndex[TaskIndex["PayloadLen"] = 5] = "PayloadLen";
148
+ FunctionID: 0,
149
+ ID: 1,
150
+ Type: 2,
151
+ Start: 3,
152
+ End: 4,
153
+ PayloadLen: 5,
143
154
  /**
144
155
  * Low 5 bits: region slot index (0..31).
145
156
  * High 27 bits: reserved for caller metadata (e.g. enqueue timing).
146
157
  */
147
- TaskIndex[TaskIndex["slotBuffer"] = 6] = "slotBuffer";
148
- TaskIndex[TaskIndex["Size"] = 8] = "Size";
158
+ slotBuffer: 6,
159
+ Size: 8,
149
160
  /**
150
161
  * Total slot length in Uint32 words, including the task header.
151
162
  */
152
- TaskIndex[TaskIndex["TotalBuff"] = 144] = "TotalBuff";
153
- })(TaskIndex || (TaskIndex = {}));
163
+ TotalBuff: 144,
164
+ };
154
165
  export const TASK_SLOT_INDEX_BITS = 5;
155
166
  export const TASK_SLOT_INDEX_MASK = (1 << TASK_SLOT_INDEX_BITS) - 1;
156
167
  export const TASK_SLOT_META_BITS = 32 - TASK_SLOT_INDEX_BITS;
@@ -186,10 +197,9 @@ export const setTaskSlotMeta = (task, value) => {
186
197
  task[TaskIndex.slotBuffer] =
187
198
  ((task[TaskIndex.slotBuffer] & TASK_SLOT_INDEX_MASK) | encodedMeta) >>> 0;
188
199
  };
189
- export var TaskFlag;
190
- (function (TaskFlag) {
191
- TaskFlag[TaskFlag["Reject"] = 1] = "Reject";
192
- })(TaskFlag || (TaskFlag = {}));
200
+ export const TaskFlag = {
201
+ Reject: 1,
202
+ };
193
203
  // Main queue lock layout in bytes.
194
204
  // The queue protocol uses two Int32 signal words, each on its own cache line:
195
205
  // - hostBits at byte 0
@@ -216,6 +226,27 @@ export const HEADER_TASK_LINE_U32 = LOCK_CACHE_LINE_BYTES /
216
226
  export const HEADER_STATIC_PAYLOAD_U32 = TaskIndex.TotalBuff -
217
227
  HEADER_TASK_LINE_U32;
218
228
  export const HEADER_TASK_OFFSET_IN_SLOT_U32 = HEADER_STATIC_PAYLOAD_U32;
229
+ // Work-stealing control words live in the unused tail of each slot's header
230
+ // cache line. Task words occupy 0..7 (`TASK_LOCAL_FLAGS_INDEX = 7`) of the
231
+ // 16-word line, leaving words 8..15 free. Consumer `c` owns slot `c`'s pair, and
232
+ // because the slot stride is exactly 9 cache lines those pairs land on distinct
233
+ // lines. `ACK[c]` and `WANT[c]` share one line on purpose: same single writer,
234
+ // and a peer reading one pulls the other for free.
235
+ export const STEAL_ACK_SLOT_OFFSET_U32 = HEADER_TASK_OFFSET_IN_SLOT_U32 +
236
+ TaskIndex.Size;
237
+ export const STEAL_WANT_SLOT_OFFSET_U32 = STEAL_ACK_SLOT_OFFSET_U32 + 1;
238
+ /**
239
+ * The payload allocator needs its own participant-owned ACK per consumer, since
240
+ * several workers can free payload slots from one shared allocator. No intent
241
+ * word: freeing needs no arbitration, a consumer only frees what it drained.
242
+ */
243
+ export const STEAL_PAYLOAD_ACK_SLOT_OFFSET_U32 = STEAL_ACK_SLOT_OFFSET_U32 + 2;
244
+ /**
245
+ * Host-owned liveness mask for stealing consumers. A dead claimant may leave
246
+ * its WANT word set forever; survivors ignore WANT from consumers whose bit is
247
+ * clear here. This preserves single-writer ownership of every WANT word.
248
+ */
249
+ export const STEAL_LIVE_SLOT_OFFSET_U32 = STEAL_PAYLOAD_ACK_SLOT_OFFSET_U32 + 1;
219
250
  export const HEADER_U32_LENGTH = LockBound.header +
220
251
  (HEADER_SLOT_STRIDE_U32 * LockBound.slots);
221
252
  export const HEADER_BYTE_LENGTH = HEADER_U32_LENGTH *
@@ -240,6 +271,8 @@ export const makeTask = () => {
240
271
  const fillTaskFrom = (task, array, at) => {
241
272
  task[0] = array[at];
242
273
  task[1] = array[at + 1];
274
+ // Raw numeric tag word; const-object payload "enums" are strict literal unions
275
+ // (numeric `enum` accepted any number), so cast back on restore.
243
276
  task[2] = array[at + 2];
244
277
  task[3] = array[at + 3];
245
278
  task[4] = array[at + 4];
@@ -264,7 +297,7 @@ const settleTask = (task) => {
264
297
  task[TaskIndex["FlagsToHost"]] = 0;
265
298
  }
266
299
  };
267
- export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload, payloadConfig, payloadSector, textCompat, resultList, toSentList, recycleList, }) => {
300
+ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload, payloadConfig, payloadSector, textCompat, resultList, toSentList, recycleList, processBoundary, consumers, consumerId, regionLanes, }) => {
268
301
  // Layout within `lockSectorRegion`:
269
302
  // - hostBits starts at byte 0
270
303
  // - workerBits starts at byte 64
@@ -296,8 +329,62 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
296
329
  headers: headersRegion,
297
330
  payload: payloadSAB,
298
331
  });
332
+ // ---- work stealing (multi-consumer) state ----
333
+ // With N consumers the pending set is A ^ ACK[0] ^ ... ^ ACK[N-1]; every word
334
+ // still has exactly one writer, so no shared writable word is introduced.
335
+ const stealConsumers = Math.max(1, (consumers ?? 1) | 0);
336
+ const stealEnabled = stealConsumers > 1;
337
+ // A lock2 built without `consumerId` is the producer endpoint: it encodes but
338
+ // never claims. It still frees payload slots, so it needs an acknowledgement
339
+ // word of its own rather than sharing consumer 0's.
340
+ const stealIsProducer = consumerId === undefined;
341
+ const stealId = (consumerId ?? 0) | 0;
342
+ const stealRegionLanes = (regionLanes ?? 8) | 0;
343
+ const stealRegions = (LockBound.slots / stealRegionLanes) | 0;
344
+ if (stealEnabled) {
345
+ if (stealRegionLanes < 1 || (stealRegionLanes & (stealRegionLanes - 1)) !== 0) {
346
+ throw new RangeError("regionLanes must be a power of two");
347
+ }
348
+ if (stealRegions < stealConsumers) {
349
+ throw new RangeError(`regionLanes=${stealRegionLanes} yields ${stealRegions} regions, ` +
350
+ `too few for ${stealConsumers} consumers`);
351
+ }
352
+ if (stealId < 0 || stealId >= stealConsumers) {
353
+ throw new RangeError(`consumerId ${stealId} out of range`);
354
+ }
355
+ }
356
+ // Int32 alias over the headers SAB so the control words get signed bit math
357
+ // consistent with the rest of the protocol.
358
+ const stealView = new Int32Array(headersRegion.sab, headersRegion.byteOffset, headersRegion.byteLength >>> 2);
359
+ const stealAckIndex = new Int32Array(stealConsumers);
360
+ const stealWantIndex = new Int32Array(stealConsumers);
361
+ for (let c = 0; c < stealConsumers; c++) {
362
+ const slotBase = (c * headersSlotStride) + LockBound.header;
363
+ stealAckIndex[c] = slotBase + STEAL_ACK_SLOT_OFFSET_U32;
364
+ stealWantIndex[c] = slotBase + STEAL_WANT_SLOT_OFFSET_U32;
365
+ }
366
+ const stealLiveIndex = LockBound.header + STEAL_LIVE_SLOT_OFFSET_U32;
367
+ const stealAllLiveMask = stealConsumers === 32
368
+ ? -1
369
+ : ((1 << stealConsumers) - 1) | 0;
370
+ if (stealEnabled && stealIsProducer) {
371
+ Atomics.store(stealView, stealLiveIndex, stealAllLiveMask);
372
+ }
373
+ const stealIsLive = (mask, consumer) => (mask & (1 << consumer)) !== 0;
374
+ const stealAckXorAll = () => {
375
+ let x = 0 | 0;
376
+ for (let c = 0; c < stealConsumers; c++) {
377
+ x = (x ^ a_load(stealView, stealAckIndex[c])) | 0;
378
+ }
379
+ return x;
380
+ };
299
381
  let promiseHandler;
300
- const encodeTask = encodePayload({
382
+ if (registeredEncodePayload === undefined ||
383
+ registeredDecodePayload === undefined) {
384
+ throw new Error("Payload codec not registered before lock2(). Ensure the module that " +
385
+ 'builds locks imports "./payloadCodec.ts" (it self-registers on load).');
386
+ }
387
+ const encodeTask = registeredEncodePayload({
301
388
  payload: {
302
389
  sab: payloadSAB,
303
390
  config: resolvedPayloadConfig,
@@ -306,6 +393,7 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
306
393
  headerSlotStrideU32: headersSlotStride,
307
394
  lockSector: payloadLockRegion,
308
395
  textCompat: resolvedTextCompat,
396
+ processBoundary,
309
397
  onPromise: (task, isRejected, value) => {
310
398
  if ((task[TASK_LOCAL_FLAGS_INDEX] & TASK_LOCAL_PROMISE_TRACKED_FLAG) !==
311
399
  0 &&
@@ -318,7 +406,7 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
318
406
  promiseHandler(task, isRejected, value);
319
407
  },
320
408
  });
321
- const decodeTask = decodePayload({
409
+ const decodeTask = registeredDecodePayload({
322
410
  payload: {
323
411
  sab: payloadSAB,
324
412
  config: resolvedPayloadConfig,
@@ -327,6 +415,7 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
327
415
  headerSlotStrideU32: headersSlotStride,
328
416
  lockSector: payloadLockRegion,
329
417
  textCompat: resolvedTextCompat,
418
+ processBoundary,
330
419
  });
331
420
  let LastLocal = 0 | 0;
332
421
  let LastWorker = 0 | 0;
@@ -343,8 +432,11 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
343
432
  // false-busy-only sender-side staleness property, this may hide newly freed
344
433
  // lanes but cannot make a genuinely pending lane appear free. Refresh only
345
434
  // when the cached free set is exhausted.
346
- let workerShadow = a_load(workerBits, 0) | 0;
347
- const refreshWorkerShadow = () => workerShadow = a_load(workerBits, 0) | 0;
435
+ let workerShadow = 0 | 0;
436
+ const refreshWorkerShadow = stealEnabled
437
+ ? () => workerShadow = stealAckXorAll()
438
+ : () => workerShadow = a_load(workerBits, 0) | 0;
439
+ refreshWorkerShadow();
348
440
  const ensureSenderStateHasFree = (state) => (~state) !== 0 ? state : (LastLocal ^ refreshWorkerShadow()) | 0;
349
441
  // RingQueue method aliases (hot path)
350
442
  const toBeSentPush = (task) => toBeSent.push(task);
@@ -542,6 +634,122 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
542
634
  lastTake = last;
543
635
  return true;
544
636
  };
637
+ /**
638
+ * WORKER SIDE: decode, region-Dekker stealing variant.
639
+ *
640
+ * Claims one whole region of `stealRegionLanes` lanes per handshake, so the
641
+ * StoreLoad barrier is amortised over the region rather than paid per task.
642
+ * Priority is fixed by endpoint id: a junior withdraws for a senior, so at
643
+ * most one consumer ever acknowledges a published generation.
644
+ *
645
+ * The C reference needs an explicit mfence between the intent store and the
646
+ * peer loads. `Atomics.store` is sequentially consistent in JS, so that
647
+ * barrier is implicit here and holds on x86 and ARM alike.
648
+ */
649
+ const stealLaneMask = (region) => stealRegionLanes === 32
650
+ ? -1
651
+ : ((((1 << stealRegionLanes) - 1) << (region * stealRegionLanes)) | 0);
652
+ const stealPeerAcks = () => {
653
+ let x = 0 | 0;
654
+ for (let c = 0; c < stealConsumers; c++) {
655
+ if (c !== stealId)
656
+ x = (x ^ a_load(stealView, stealAckIndex[c])) | 0;
657
+ }
658
+ return x;
659
+ };
660
+ const stealJuniorWants = (intent) => {
661
+ const live = a_load(stealView, stealLiveIndex) | 0;
662
+ for (let c = stealId + 1; c < stealConsumers; c++) {
663
+ if (!stealIsLive(live, c))
664
+ continue;
665
+ if ((a_load(stealView, stealWantIndex[c]) & intent) !== 0)
666
+ return true;
667
+ }
668
+ return false;
669
+ };
670
+ let stealCursor = ((stealRegions * stealId) / stealConsumers) | 0;
671
+ const decodeSteal = () => {
672
+ const pending = (a_load(hostBits, 0) ^ LastWorker ^ stealPeerAcks()) | 0;
673
+ if (pending === 0)
674
+ return false;
675
+ let pendingRegions = 0 | 0;
676
+ if (stealRegions === 1)
677
+ pendingRegions = 1;
678
+ else {
679
+ for (let r = 0; r < stealRegions; r++) {
680
+ if ((pending & stealLaneMask(r)) !== 0)
681
+ pendingRegions |= 1 << r;
682
+ }
683
+ }
684
+ const liveBeforeClaim = a_load(stealView, stealLiveIndex) | 0;
685
+ let peerIntent = 0 | 0;
686
+ let seniorIntent = 0 | 0;
687
+ for (let c = 0; c < stealConsumers; c++) {
688
+ if (c === stealId || !stealIsLive(liveBeforeClaim, c))
689
+ continue;
690
+ const value = a_load(stealView, stealWantIndex[c]) | 0;
691
+ peerIntent |= value;
692
+ if (c < stealId)
693
+ seniorIntent |= value;
694
+ }
695
+ const notSenior = pendingRegions & ~seniorIntent;
696
+ if (notSenior === 0)
697
+ return false;
698
+ const clean = pendingRegions & ~peerIntent;
699
+ const candidates = clean !== 0 ? clean : notSenior;
700
+ let region = -1;
701
+ for (let step = 0; step < stealRegions; step++) {
702
+ const candidate = (stealCursor + step) % stealRegions;
703
+ if ((candidates & (1 << candidate)) !== 0) {
704
+ region = candidate;
705
+ break;
706
+ }
707
+ }
708
+ if (region < 0)
709
+ return false;
710
+ const intent = (1 << region) | 0;
711
+ a_store(stealView, stealWantIndex[stealId], intent);
712
+ // Dekker StoreLoad: implicit, `Atomics.store` above is seq-cst.
713
+ let seniorConflict = false;
714
+ let juniorConflict = false;
715
+ for (let c = 0; c < stealConsumers; c++) {
716
+ if (c === stealId || !stealIsLive(liveBeforeClaim, c))
717
+ continue;
718
+ if ((a_load(stealView, stealWantIndex[c]) & intent) === 0)
719
+ continue;
720
+ if (c < stealId)
721
+ seniorConflict = true;
722
+ else
723
+ juniorConflict = true;
724
+ }
725
+ if (seniorConflict) {
726
+ a_store(stealView, stealWantIndex[stealId], 0);
727
+ return false;
728
+ }
729
+ if (juniorConflict) {
730
+ while (stealJuniorWants(intent)) { /* junior withdraws */ }
731
+ }
732
+ // Last control loads before ownership. After this no control word is read
733
+ // until every slot in `take` has been decoded.
734
+ const take = ((a_load(hostBits, 0) ^ LastWorker ^ stealPeerAcks()) &
735
+ stealLaneMask(region)) | 0;
736
+ if (take === 0) {
737
+ a_store(stealView, stealWantIndex[stealId], 0);
738
+ return false;
739
+ }
740
+ let lanes = take;
741
+ while (lanes !== 0) {
742
+ decodeAt(31 - clz32((lanes & -lanes) >>> 0));
743
+ lanes = (lanes & (lanes - 1)) | 0;
744
+ }
745
+ // Retire the whole claimed mask with one ACK store, then release the region.
746
+ // ACK before intent-clear (paper Assumption 2(ii)).
747
+ LastWorker = (LastWorker ^ take) | 0;
748
+ a_store(stealView, stealAckIndex[stealId], LastWorker);
749
+ a_store(stealView, stealWantIndex[stealId], 0);
750
+ stealCursor = (region + 1) % stealRegions;
751
+ return true;
752
+ };
545
753
  /**
546
754
  * HOST SIDE: decode version
547
755
  */
@@ -711,6 +919,24 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
711
919
  deferredCount = 0 | 0;
712
920
  pendingPromiseCount = 0 | 0;
713
921
  };
922
+ /**
923
+ * HOST SIDE: remove a terminated consumer from stealing arbitration.
924
+ *
925
+ * Its ACK remains part of the generation XOR forever, because it records
926
+ * lanes that consumer retired while alive. Only its stale WANT becomes
927
+ * ineligible. The caller must invoke this only after the endpoint can no
928
+ * longer execute; there is deliberately no matching reactivation operation.
929
+ */
930
+ const deactivateStealConsumer = (id) => {
931
+ if (!stealEnabled || !stealIsProducer)
932
+ return false;
933
+ if (!Number.isInteger(id) || id < 0 || id >= stealConsumers) {
934
+ throw new RangeError(`consumerId ${id} out of range`);
935
+ }
936
+ const bit = 1 << id;
937
+ const previous = Atomics.and(stealView, stealLiveIndex, ~bit);
938
+ return (previous & bit) !== 0;
939
+ };
714
940
  return {
715
941
  enlist,
716
942
  encode,
@@ -718,7 +944,7 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
718
944
  encodeAll,
719
945
  publish,
720
946
  flushPending,
721
- decode,
947
+ decode: stealEnabled ? decodeSteal : decode,
722
948
  hasSpace,
723
949
  resolved,
724
950
  hostBits,
@@ -729,6 +955,7 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
729
955
  getPendingFrameCount: () => toBeSent.size | 0,
730
956
  getPendingPromiseCount: () => pendingPromiseCount | 0,
731
957
  resetPendingState,
958
+ deactivateStealConsumer,
732
959
  takeDeferredCount: () => {
733
960
  const count = deferredCount | 0;
734
961
  deferredCount = 0 | 0;
@@ -6,7 +6,7 @@ import type { SharedBufferSource } from "../common/shared-buffer-region.js";
6
6
  * Returns `true` when the payload is encoded successfully.
7
7
  * Returns `false` when dynamic payload space could not be reserved.
8
8
  */
9
- export declare const encodePayload: ({ lockSector, payload, sab, payloadConfig, headersBuffer, headerSlotStrideU32, textCompat, onPromise, }: {
9
+ export declare const encodePayload: ({ lockSector, payload, sab, payloadConfig, headersBuffer, headerSlotStrideU32, textCompat, onPromise, processBoundary, }: {
10
10
  lockSector?: SharedBufferSource;
11
11
  payload?: {
12
12
  sab?: SharedBufferSource;
@@ -24,8 +24,13 @@ export declare const encodePayload: ({ lockSector, payload, sab, payloadConfig,
24
24
  headerSlotStrideU32?: number;
25
25
  textCompat?: LockBufferTextCompat;
26
26
  onPromise?: PromisePayloadHandler;
27
+ /**
28
+ * Reject process-local pointer payloads while preserving ProcessSharedBuffer.
29
+ * Process transports must set this on both encoders and decoders.
30
+ */
31
+ processBoundary?: boolean;
27
32
  }) => (task: Task, slotIndex: number) => boolean;
28
- export declare const decodePayload: ({ lockSector, payload, sab, payloadConfig, headersBuffer, headerSlotStrideU32, textCompat, host, }: {
33
+ export declare const decodePayload: ({ lockSector, payload, sab, payloadConfig, headersBuffer, headerSlotStrideU32, textCompat, host, processBoundary, }: {
29
34
  lockSector?: SharedBufferSource;
30
35
  payload?: {
31
36
  sab?: SharedBufferSource;
@@ -43,4 +48,8 @@ export declare const decodePayload: ({ lockSector, payload, sab, payloadConfig,
43
48
  headerSlotStrideU32?: number;
44
49
  textCompat?: LockBufferTextCompat;
45
50
  host?: true;
51
+ /**
52
+ * Reject process-local pointer payloads before reading pointer/token metadata.
53
+ */
54
+ processBoundary?: boolean;
46
55
  }) => (task: Task, slotIndex: number, specialFlags?: number) => void;