knitting 0.1.63 → 0.1.73

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 (99) hide show
  1. package/README.md +623 -342
  2. package/knitting.browser.d.ts +3 -1
  3. package/knitting.browser.js +1 -1
  4. package/knitting.d.ts +3 -1
  5. package/knitting.js +2 -1
  6. package/map.md +0 -6
  7. package/package.json +10 -5
  8. package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
  9. package/prebuilds/darwin-arm64-node-127/knitting_doorbell.node +0 -0
  10. package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
  11. package/prebuilds/darwin-arm64-node-137/knitting_doorbell.node +0 -0
  12. package/prebuilds/darwin-x64-node-127/knitting_buffer_pointer.node +0 -0
  13. package/prebuilds/darwin-x64-node-127/knitting_doorbell.node +0 -0
  14. package/prebuilds/darwin-x64-node-137/knitting_buffer_pointer.node +0 -0
  15. package/prebuilds/darwin-x64-node-137/knitting_doorbell.node +0 -0
  16. package/prebuilds/linux-x64-node-127/knitting_buffer_pointer.node +0 -0
  17. package/prebuilds/linux-x64-node-127/knitting_doorbell.node +0 -0
  18. package/prebuilds/linux-x64-node-137/knitting_buffer_pointer.node +0 -0
  19. package/prebuilds/linux-x64-node-137/knitting_doorbell.node +0 -0
  20. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  21. package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
  22. package/prebuilds/win32-x64-node-127/knitting_doorbell.node +0 -0
  23. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  24. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  25. package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
  26. package/prebuilds/win32-x64-node-137/knitting_doorbell.node +0 -0
  27. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  28. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  29. package/scripts/build-native-addons.ts +5 -0
  30. package/shared-memory.d.ts +3 -0
  31. package/shared-memory.js +3 -0
  32. package/src/api.js +158 -71
  33. package/src/common/with-resolvers.js +2 -5
  34. package/src/common/worker-runtime.d.ts +7 -0
  35. package/src/common/worker-runtime.js +7 -0
  36. package/src/connections/buffer-reference.d.ts +10 -36
  37. package/src/connections/buffer-reference.js +15 -170
  38. package/src/connections/node-addons.d.ts +1 -1
  39. package/src/connections/node-addons.js +11 -1
  40. package/src/connections/shared-array-buffer-payload.d.ts +7 -0
  41. package/src/connections/shared-array-buffer-payload.js +27 -11
  42. package/src/debug/gate.js +1 -1
  43. package/src/debug/handle.d.ts +6 -1
  44. package/src/debug/handle.js +14 -6
  45. package/src/error.d.ts +9 -0
  46. package/src/error.js +16 -2
  47. package/src/knitting_buffer_pointer.cc +57 -2
  48. package/src/knitting_doorbell.cc +220 -0
  49. package/src/memory/knitting-body.d.ts +44 -0
  50. package/src/memory/knitting-body.js +51 -0
  51. package/src/memory/knitting-buffer-http.d.ts +116 -0
  52. package/src/memory/knitting-buffer-http.js +255 -0
  53. package/src/memory/knitting-buffer.d.ts +250 -0
  54. package/src/memory/knitting-buffer.js +695 -0
  55. package/src/memory/lazy-region-registry.d.ts +83 -0
  56. package/src/memory/lazy-region-registry.js +355 -0
  57. package/src/memory/lock.d.ts +80 -15
  58. package/src/memory/lock.js +473 -139
  59. package/src/memory/payloadCodec.d.ts +18 -2
  60. package/src/memory/payloadCodec.js +340 -76
  61. package/src/memory/regionRegistry.d.ts +6 -0
  62. package/src/memory/regionRegistry.js +125 -240
  63. package/src/memory/shared-buffer-io.d.ts +7 -0
  64. package/src/memory/shared-buffer-io.js +34 -8
  65. package/src/permission/protocol.d.ts +1 -0
  66. package/src/permission/protocol.js +8 -3
  67. package/src/runtime/deno-doorbell.d.ts +26 -0
  68. package/src/runtime/deno-doorbell.js +117 -0
  69. package/src/runtime/dispatcher.d.ts +13 -6
  70. package/src/runtime/dispatcher.js +101 -63
  71. package/src/runtime/host-arg-arena.d.ts +3 -0
  72. package/src/runtime/host-arg-arena.js +16 -0
  73. package/src/runtime/inline-executor.js +2 -1
  74. package/src/runtime/node-doorbell.d.ts +14 -0
  75. package/src/runtime/node-doorbell.js +84 -0
  76. package/src/runtime/pool.d.ts +30 -15
  77. package/src/runtime/pool.js +199 -151
  78. package/src/runtime/process-worker.d.ts +9 -0
  79. package/src/runtime/process-worker.js +32 -3
  80. package/src/runtime/tx-queue.d.ts +4 -6
  81. package/src/runtime/tx-queue.js +63 -48
  82. package/src/runtime/worker-common.d.ts +7 -0
  83. package/src/runtime/worker-common.js +28 -2
  84. package/src/types.d.ts +66 -78
  85. package/src/worker/loop.js +95 -60
  86. package/src/worker/rx-queue.d.ts +2 -3
  87. package/src/worker/rx-queue.js +34 -40
  88. package/src/worker/safety/index.d.ts +1 -1
  89. package/src/worker/safety/index.js +1 -1
  90. package/src/worker/safety/process.d.ts +2 -0
  91. package/src/worker/safety/process.js +8 -1
  92. package/src/worker/safety/startup.js +11 -6
  93. package/src/worker/shared-return.d.ts +9 -0
  94. package/src/worker/shared-return.js +22 -0
  95. package/src/worker/task-loader.js +1 -2
  96. package/src/worker/timers.d.ts +2 -6
  97. package/src/worker/timers.js +39 -22
  98. package/unsafe.d.ts +2 -1
  99. package/unsafe.js +2 -1
@@ -1,4 +1,4 @@
1
- import { attachPayloadTransportFinalizer, beginPromisePayload, finishPromisePayload, getTaskSlotIndex, HEADER_BYTE_LENGTH, HEADER_SLOT_STRIDE_U32, HEADER_STATIC_PAYLOAD_U32, LockBound, PayloadBuffer, payloadBufferName, PayloadSignal, registerLockPayloadCodec, TaskIndex, } from "./lock.js";
1
+ import { attachPayloadTransportFinalizer, beginPromisePayload, finishPromisePayload, HEADER_BYTE_LENGTH, HEADER_SLOT_STRIDE_U32, HEADER_STATIC_PAYLOAD_U32, LockBound, PayloadBuffer, payloadBufferName, PayloadSignal, registerLockPayloadCodec, TASK_SLOT_INDEX_MASK, TaskIndex, } from "./lock.js";
2
2
  import { register } from "./regionRegistry.js";
3
3
  import { createSharedDynamicBufferIO, createSharedStaticBufferIO, } from "./shared-buffer-io.js";
4
4
  import { getStridedRegionSpanBytes } from "./byte-carpet.js";
@@ -6,7 +6,8 @@ import { encoderError, ErrorKnitting } from "../error.js";
6
6
  import { Envelope } from "../common/envelope.js";
7
7
  import { resolvePayloadBufferOptions, } from "./payload-config.js";
8
8
  import { getSharedArrayBufferPayload, SHARED_ARRAY_BUFFER_CODEC_ID, SHARED_ARRAY_BUFFER_NUMERIC_TRANSFER, SHARED_ARRAY_BUFFER_NUMERIC_WORDS, } from "../connections/shared-array-buffer-payload.js";
9
- import { BUFFER_REFERENCE_NUMERIC_TRANSFER, BufferReference, isBufferReferenceValue, } from "../connections/buffer-reference.js";
9
+ import { BUFFER_REFERENCE_NUMERIC_TRANSFER, BufferReference, isArrayBufferDetached, isBufferReferenceValue, } from "../connections/buffer-reference.js";
10
+ import { getBufferReferenceCapabilities, } from "../connections/buffer-reference-native.js";
10
11
  import { isProcessSharedBufferValue, ProcessSharedBuffer, } from "../connections/process-shared-buffer.js";
11
12
  import { isNumericArray, numericArrayFromFloat64, } from "../connections/numeric-array.js";
12
13
  const memory = new ArrayBuffer(8);
@@ -39,7 +40,7 @@ const objectGetPrototypeOf = Object.getPrototypeOf;
39
40
  const objectHasOwn = Object.prototype.hasOwnProperty;
40
41
  const arrayIsArray = Array.isArray;
41
42
  const objectPrototype = Object.prototype;
42
- const UNSUPPORTED_OBJECT_DETAIL = "Unsupported object type. Allowed: plain object, array, Error, Date, Envelope, Buffer, ArrayBuffer, DataView, typed arrays, and registered external payloads. Serialize it yourself.";
43
+ const UNSUPPORTED_OBJECT_DETAIL = "Unsupported object type. Allowed: plain object, array, Error, Date, Envelope, Buffer, ArrayBuffer, DataView, Uint8Array, Int32Array, Float64Array, BigInt64Array, BigUint64Array, and registered external payloads. Serialize it yourself.";
43
44
  const ENVELOPE_PAYLOAD_DETAIL = "Envelope payload must be an ArrayBuffer, SharedArrayBuffer, " +
44
45
  "ProcessSharedBuffer, or BufferReference.";
45
46
  const ENVELOPE_HEADER_DETAIL = "Envelope header must be a JSON-like value or string.";
@@ -99,7 +100,7 @@ const readTrustedExternalPayloadMetadata = (value) => {
99
100
  }
100
101
  return value.toMetadata();
101
102
  };
102
- const decodeExternalPayload = (raw, processBoundary) => {
103
+ const decodeExternalPayload = (raw, processBoundary, transportKey) => {
103
104
  const payload = parseJSON(raw);
104
105
  if (!arrayIsArray(payload) || payload.length !== 2)
105
106
  return payload;
@@ -113,22 +114,33 @@ const decodeExternalPayload = (raw, processBoundary) => {
113
114
  }
114
115
  const codec = externalPayloadGlobal.__KNITTING_PAYLOAD_CODECS__?.[codecId];
115
116
  return typeof codec?.decode === "function"
116
- ? codec.decode(metadata)
117
+ ? codec.decode(metadata, transportKey)
117
118
  : { codec: codecId, metadata };
118
119
  };
119
120
  const PROCESS_SHARED_BUFFER_NUMERIC_WORDS = 8;
120
121
  const BUFFER_REFERENCE_NUMERIC_WORDS = 8;
121
122
  const NUMERIC_SENTINEL = 0xffffffff;
122
- const decodeNumericExternalPayload = (codecId, words) => {
123
+ /** Minimum size for automatically borrowed byte returns. */
124
+ export const SHARED_RETURN_MIN_BYTES = 256 * 1024;
125
+ /** Number of later borrowed returns kept before a region is recycled. */
126
+ export const SHARED_RETURN_BORROW_WINDOW = 32;
127
+ // Each payload buffer has one allocator for the worker return lane.
128
+ const sharedReturnAllocators = new WeakMap();
129
+ /** The borrowed-region allocator for `payload`, if an encoder built one. */
130
+ export const getSharedReturnAllocator = (payload) => sharedReturnAllocators.get(payload);
131
+ const decodeNumericExternalPayload = (codecId, words, transportKey) => {
123
132
  const codec = externalPayloadGlobal.__KNITTING_PAYLOAD_CODECS__?.[codecId];
124
133
  if (typeof codec?.decodeNumeric === "function") {
125
- return codec.decodeNumeric(words);
134
+ return codec.decodeNumeric(words, transportKey);
126
135
  }
127
136
  return { codec: codecId, metadata: Array.from(words) };
128
137
  };
129
138
  const decodeProcessSharedBufferNumericWords = (words) => decodeNumericExternalPayload(PROCESS_SHARED_BUFFER_CODEC_ID, words);
130
139
  const decodeBufferReferenceNumericWords = (words) => decodeNumericExternalPayload(BUFFER_REFERENCE_CODEC_ID, words);
131
- const decodeSharedArrayBufferNumericWords = (words) => decodeNumericExternalPayload(SHARED_ARRAY_BUFFER_CODEC_ID, words);
140
+ // The transport key scopes the host's adopted-alias cache to one return lane:
141
+ // producer tokens restart at 1 in every worker isolate, so a shared cache would
142
+ // serve one worker's buffer for another worker's identical token.
143
+ const decodeSharedArrayBufferNumericWords = (words, transportKey) => decodeNumericExternalPayload(SHARED_ARRAY_BUFFER_CODEC_ID, words, transportKey);
132
144
  const tryEncodePrimitiveTask = (task) => {
133
145
  const value = task.value;
134
146
  switch (typeof value) {
@@ -240,18 +252,23 @@ const toErrorPayload = (error) => {
240
252
  }
241
253
  return payload;
242
254
  };
243
- const parseErrorPayload = (raw) => {
244
- let parsed;
245
- try {
246
- parsed = parseJSON(raw);
255
+ // The shape `toErrorCause` writes for an Error cause. A plain-object cause
256
+ // with any other key is left as the object it was.
257
+ const isErrorPayload = (value) => {
258
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
259
+ return false;
247
260
  }
248
- catch {
249
- return new Error(raw);
261
+ const payload = value;
262
+ if (typeof payload.name !== "string" || typeof payload.message !== "string") {
263
+ return false;
250
264
  }
251
- if (parsed == null || typeof parsed !== "object") {
252
- return new Error(String(parsed));
265
+ for (const key in payload) {
266
+ if (key !== "name" && key !== "message" && key !== "stack" && key !== "cause")
267
+ return false;
253
268
  }
254
- const payload = parsed;
269
+ return true;
270
+ };
271
+ const reviveErrorPayload = (payload) => {
255
272
  const err = new Error(typeof payload.message === "string" ? payload.message : "");
256
273
  if (typeof payload.name === "string" && payload.name.length > 0) {
257
274
  err.name = payload.name;
@@ -264,10 +281,25 @@ const parseErrorPayload = (raw) => {
264
281
  }
265
282
  }
266
283
  if (objectHasOwn.call(payload, "cause")) {
267
- err.cause = payload.cause;
284
+ err.cause = isErrorPayload(payload.cause)
285
+ ? reviveErrorPayload(payload.cause)
286
+ : payload.cause;
268
287
  }
269
288
  return err;
270
289
  };
290
+ const parseErrorPayload = (raw) => {
291
+ let parsed;
292
+ try {
293
+ parsed = parseJSON(raw);
294
+ }
295
+ catch {
296
+ return new Error(raw);
297
+ }
298
+ if (parsed == null || typeof parsed !== "object") {
299
+ return new Error(String(parsed));
300
+ }
301
+ return reviveErrorPayload(parsed);
302
+ };
271
303
  const decodeBigIntBinary = (bytes) => {
272
304
  const sign = bytes[0];
273
305
  let value = 0n;
@@ -304,15 +336,28 @@ const requireStaticIO = (headersBuffer, headerSlotStrideU32, textCompat) => {
304
336
  * Returns `true` when the payload is encoded successfully.
305
337
  * Returns `false` when dynamic payload space could not be reserved.
306
338
  */
307
- export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headersBuffer, headerSlotStrideU32, textCompat, onPromise, processBoundary = false, }) => {
339
+ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headersBuffer, headerSlotStrideU32, textCompat, onPromise, processBoundary = false, sharedReturn = false, moveReturn = false, }) => {
308
340
  const payloadSab = payload?.sab ?? sab;
309
341
  const resolvedPayloadConfig = resolvePayloadBufferOptions({
310
342
  sab: payloadSab,
311
343
  options: payload?.config ?? payloadConfig,
312
344
  });
313
345
  const maxPayloadBytes = resolvedPayloadConfig.maxPayloadBytes;
314
- const { allocTask, setSlotLength, free } = register({ lockSector });
315
- const { writeBinary: writeDynamicBinary, writeBuffer: writeDynamicBuffer, writeArrayBuffer: writeDynamicArrayBuffer, write8Binary: writeDynamic8Binary, writeUtf8: writeDynamicUtf8, } = createSharedDynamicBufferIO({
346
+ // Automatic moves are an optimization. Keep ordinary byte returns working
347
+ // when this runtime has no usable ownership/FFI backend.
348
+ const canMoveReturn = moveReturn && (() => {
349
+ try {
350
+ getBufferReferenceCapabilities();
351
+ return true;
352
+ }
353
+ catch {
354
+ return false;
355
+ }
356
+ })();
357
+ const registryHandle = register({ lockSector });
358
+ const { allocTask, setSlotLength, tagTaskSlot, free } = registryHandle;
359
+ const { allocRegion, regionStart } = registryHandle;
360
+ const { writeBinary: writeDynamicBinary, writeBuffer: writeDynamicBuffer, writeArrayBuffer: writeDynamicArrayBuffer, write8Binary: writeDynamic8Binary, writeUtf8: writeDynamicUtf8, readBytesView: dynamicRegionView, ensureCapacity: ensureDynamicCapacity, currentBuffer: currentDynamicBuffer, regionBase: dynamicRegionBase, } = createSharedDynamicBufferIO({
316
361
  sab: payloadSab,
317
362
  payloadConfig: resolvedPayloadConfig,
318
363
  textCompat: textCompat?.payload,
@@ -350,9 +395,72 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
350
395
  return exactBytes;
351
396
  };
352
397
  const dynamicUtf8ReserveBytes = (task, text, label) => dynamicUtf8ReserveBytesWithExtra(task, text, 0, label);
398
+ // Borrowed regions are tracked as [slot, start, end] triples, oldest first.
399
+ const borrowed = [];
400
+ const BORROW_STRIDE = 3;
401
+ /** Limit borrowed regions to half the payload arena. */
402
+ const maxBorrowBytes = ((resolvedPayloadConfig.payloadMaxByteLength >> 1) /
403
+ SHARED_RETURN_BORROW_WINDOW) | 0;
404
+ /** Reserve a region to hand over by reference, or -1 when none is free. */
405
+ const reserveBorrowedRegion = (bytes) => {
406
+ if (bytes > maxBorrowBytes)
407
+ return -1;
408
+ const slot = allocRegion(bytes);
409
+ if (slot === -1)
410
+ return -1;
411
+ const start = regionStart(slot);
412
+ if (!ensureDynamicCapacity(start + bytes)) {
413
+ free(slot);
414
+ return -1;
415
+ }
416
+ borrowed.push(slot, start, start + bytes);
417
+ if (borrowed.length > SHARED_RETURN_BORROW_WINDOW * BORROW_STRIDE) {
418
+ free(borrowed[0]);
419
+ borrowed.splice(0, BORROW_STRIDE);
420
+ }
421
+ return slot;
422
+ };
423
+ const dropBorrowedRegion = (slot) => {
424
+ for (let at = 0; at < borrowed.length; at += BORROW_STRIDE) {
425
+ if (borrowed[at] === slot) {
426
+ borrowed.splice(at, BORROW_STRIDE);
427
+ break;
428
+ }
429
+ }
430
+ free(slot);
431
+ };
432
+ /** Return the arena offset when `view` is inside a live borrowed region. */
433
+ const borrowedStartOf = (view) => {
434
+ if (borrowed.length === 0)
435
+ return -1;
436
+ if (view.buffer !== currentDynamicBuffer())
437
+ return -1;
438
+ const start = view.byteOffset - dynamicRegionBase();
439
+ const end = start + view.byteLength;
440
+ for (let at = borrowed.length - BORROW_STRIDE; at >= 0; at -= BORROW_STRIDE) {
441
+ if (start >= borrowed[at + 1] && end <= borrowed[at + 2])
442
+ return start;
443
+ }
444
+ return -1;
445
+ };
446
+ /** Allocate a borrowed return region, or fall back when none is available. */
447
+ const allocateSharedReturn = (byteLength, zeroFill = false) => {
448
+ const slot = reserveBorrowedRegion(byteLength);
449
+ if (slot === -1)
450
+ return undefined;
451
+ const start = regionStart(slot);
452
+ const view = dynamicRegionView(start, start + byteLength);
453
+ if (zeroFill)
454
+ view.fill(0);
455
+ return view;
456
+ };
457
+ if (sharedReturn && payloadSab !== undefined) {
458
+ sharedReturnAllocators.set(payloadSab, allocateSharedReturn);
459
+ }
353
460
  const reserveDynamic = (task, bytes) => {
354
461
  task[TaskIndex.PayloadLen] = bytes;
355
- // PayloadCodec only reserves after the lock has guaranteed capacity.
462
+ // -1 is dynamic-region backpressure. Returning false leaves this frame on
463
+ // the lock's existing pending list until a receiver releases a region.
356
464
  return allocTask(task);
357
465
  };
358
466
  let objectDynamicSlot = -1;
@@ -419,6 +527,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
419
527
  return false;
420
528
  task[TaskIndex.Type] = PayloadBuffer.Error;
421
529
  const reservedSlot = reserveDynamicObject(task, reserveBytes);
530
+ if (reservedSlot === -1)
531
+ return false;
422
532
  const written = writeDynamicUtf8(text, task[TaskIndex.Start], reserveBytes);
423
533
  if (written < 0)
424
534
  return failDynamicWriteAfterReserve(task, reservedSlot);
@@ -443,6 +553,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
443
553
  return false;
444
554
  }
445
555
  const reservedSlot = reserveDynamicObject(task, bytes);
556
+ if (reservedSlot === -1)
557
+ return false;
446
558
  const written = writeDynamicBinary(bytesView, task[TaskIndex.Start]);
447
559
  if (written < 0)
448
560
  return failDynamicWriteAfterReserve(task, reservedSlot);
@@ -453,6 +565,15 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
453
565
  };
454
566
  const encodeObjectUint8Array = (task, slotIndex, bytesView) => {
455
567
  const bytes = bytesView.byteLength;
568
+ // Preserve an arena-backed subarray as an offset/length frame.
569
+ const borrowedStart = borrowedStartOf(bytesView);
570
+ if (borrowedStart !== -1) {
571
+ task[TaskIndex.Type] = PayloadBuffer.ArenaBinary;
572
+ task[TaskIndex.Start] = borrowedStart;
573
+ task[TaskIndex.PayloadLen] = bytes;
574
+ task.value = null;
575
+ return true;
576
+ }
456
577
  if (bytes <= staticMaxBytes) {
457
578
  writeStaticExactUint8Array(bytesView, slotIndex);
458
579
  task[TaskIndex.Type] = PayloadBuffer.StaticBinary;
@@ -460,10 +581,16 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
460
581
  task.value = null;
461
582
  return true;
462
583
  }
584
+ if (canMoveReturn && bytes >= SHARED_RETURN_MIN_BYTES &&
585
+ tryEncodeMovedReturn(task, slotIndex, bytesView, PayloadBuffer.MovedBinary)) {
586
+ return true;
587
+ }
463
588
  task[TaskIndex.Type] = PayloadBuffer.Binary;
464
589
  if (!ensureWithinDynamicLimit(task, bytes, "Binary"))
465
590
  return false;
466
591
  const reservedSlot = reserveDynamicObject(task, bytes);
592
+ if (reservedSlot === -1)
593
+ return false;
467
594
  const written = writeDynamicBinary(bytesView, task[TaskIndex.Start]);
468
595
  if (written < 0)
469
596
  return failDynamicWriteAfterReserve(task, reservedSlot);
@@ -487,6 +614,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
487
614
  if (!ensureWithinDynamicLimit(task, bytes, "Buffer"))
488
615
  return false;
489
616
  const reservedSlot = reserveDynamicObject(task, bytes);
617
+ if (reservedSlot === -1)
618
+ return false;
490
619
  const written = writeDynamicBuffer(buffer, task[TaskIndex.Start]);
491
620
  if (written < 0)
492
621
  return failDynamicWriteAfterReserve(task, reservedSlot);
@@ -510,6 +639,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
510
639
  if (!ensureWithinDynamicLimit(task, bytes, "Float64Array"))
511
640
  return false;
512
641
  const reservedSlot = reserveDynamicObject(task, bytes);
642
+ if (reservedSlot === -1)
643
+ return false;
513
644
  const written = writeDynamic8Binary(float64, task[TaskIndex.Start]);
514
645
  if (written < 0)
515
646
  return failDynamicWriteAfterReserve(task, reservedSlot);
@@ -541,6 +672,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
541
672
  if (!ensureWithinDynamicLimit(task, bytes, "NumericArray"))
542
673
  return false;
543
674
  const reservedSlot = reserveDynamicObject(task, bytes);
675
+ if (reservedSlot === -1)
676
+ return false;
544
677
  const written = writeDynamic8Binary(float64, task[TaskIndex.Start]);
545
678
  if (written < 0)
546
679
  return failDynamicWriteAfterReserve(task, reservedSlot);
@@ -560,10 +693,16 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
560
693
  return true;
561
694
  }
562
695
  }
696
+ if (canMoveReturn && bytes >= SHARED_RETURN_MIN_BYTES &&
697
+ tryEncodeMovedReturn(task, slotIndex, arrayBuffer, PayloadBuffer.MovedArrayBuffer)) {
698
+ return true;
699
+ }
563
700
  task[TaskIndex.Type] = PayloadBuffer.ArrayBuffer;
564
701
  if (!ensureWithinDynamicLimit(task, bytes, "ArrayBuffer"))
565
702
  return false;
566
703
  const reservedSlot = reserveDynamicObject(task, bytes);
704
+ if (reservedSlot === -1)
705
+ return false;
567
706
  const written = writeDynamicArrayBuffer(arrayBuffer, task[TaskIndex.Start]);
568
707
  if (written < 0)
569
708
  return failDynamicWriteAfterReserve(task, reservedSlot);
@@ -619,6 +758,68 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
619
758
  task.value = null;
620
759
  return true;
621
760
  };
761
+ /**
762
+ * The safe counterpart to an arena borrow. The producer's source is detached
763
+ * only after its task has settled; the host adopts the backing store before
764
+ * its acknowledgement lets the producer release the registry pin.
765
+ *
766
+ * This needs its own frame type because public task results remain ordinary
767
+ * Uint8Array / ArrayBuffer values rather than exposing BufferReference.
768
+ */
769
+ const tryEncodeMovedReturn = (task, slotIndex, source, type) => {
770
+ if (source.byteLength > 0xffffffff)
771
+ return false;
772
+ const backing = ArrayBuffer.isView(source) ? source.buffer : source;
773
+ if (typeof SharedArrayBuffer === "function" &&
774
+ backing instanceof SharedArrayBuffer) {
775
+ return false;
776
+ }
777
+ // Narrowed by the check above: what is left is a detachable ArrayBuffer.
778
+ const store = backing;
779
+ // A move takes the whole backing store, not the slice that was returned.
780
+ // `return scratch.subarray(0, n)` would detach the producer's entire
781
+ // scratch buffer and break its next call, so a partial view is copied
782
+ // like any other return. Only a view that *is* its buffer can move.
783
+ if (ArrayBuffer.isView(source) &&
784
+ (source.byteOffset !== 0 || source.byteLength !== store.byteLength)) {
785
+ return false;
786
+ }
787
+ // Moving is an optimization, so a source this runtime cannot detach --
788
+ // WASM memory, a buffer an external API pinned -- copies rather than
789
+ // failing the task. Construction is what detaches: a throw before it
790
+ // leaves the bytes intact and the copy path still works, while a throw
791
+ // after it has already consumed them has to be reported.
792
+ let reference;
793
+ try {
794
+ reference = new BufferReference(source);
795
+ }
796
+ catch (error) {
797
+ if (isArrayBufferDetached(store))
798
+ throw error;
799
+ return false;
800
+ }
801
+ // Belt and braces for a runtime that reports success without detaching.
802
+ // The host would be handed a store still aliasing memory the producer can
803
+ // write; the source is intact in that case, so dropping the reference and
804
+ // copying is both safe and the honest answer.
805
+ if (!isArrayBufferDetached(store)) {
806
+ reference.release();
807
+ return false;
808
+ }
809
+ const words = BufferReference.prototype[BUFFER_REFERENCE_NUMERIC_TRANSFER]
810
+ .call(reference);
811
+ // The size check above guarantees the numeric form for a local reference.
812
+ // Do not fall back after construction: the source has intentionally moved.
813
+ if (words === undefined) {
814
+ reference.release();
815
+ throw new Error("Moved BufferReference has no numeric transport form");
816
+ }
817
+ task[TaskIndex.Type] = type;
818
+ task[TaskIndex.PayloadLen] = writeStaticU32Words(words, BUFFER_REFERENCE_NUMERIC_WORDS, slotIndex);
819
+ attachPayloadTransportFinalizer(task, reference);
820
+ task.value = null;
821
+ return true;
822
+ };
622
823
  const tryEncodeSharedArrayBufferNumeric = (task, slotIndex, value) => {
623
824
  const words = value[SHARED_ARRAY_BUFFER_NUMERIC_TRANSFER]?.(lockSector);
624
825
  if (words === undefined)
@@ -719,6 +920,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
719
920
  if (reserveBytes < 0)
720
921
  return false;
721
922
  const reservedSlot = reserveDynamicObject(task, reserveBytes);
923
+ if (reservedSlot === -1)
924
+ return false;
722
925
  const written = writeDynamicUtf8(text, task[TaskIndex.Start], reserveBytes);
723
926
  if (written < 0)
724
927
  return failDynamicWriteAfterReserve(task, reservedSlot);
@@ -728,6 +931,48 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
728
931
  task.value = null;
729
932
  return true;
730
933
  };
934
+ /** Encode plain objects, arrays, and null-prototype objects as JSON. */
935
+ const encodeObjectJson = (task, slotIndex, objectValue) => {
936
+ let text;
937
+ try {
938
+ text = stringifyJSON(objectValue);
939
+ }
940
+ catch (error) {
941
+ const detail = error instanceof Error ? error.message : String(error);
942
+ return encoderError({
943
+ task,
944
+ type: ErrorKnitting.Json,
945
+ onPromise,
946
+ detail,
947
+ });
948
+ }
949
+ if (text.length <= staticMaxBytes) {
950
+ const written = writeStaticUtf8(text, slotIndex);
951
+ if (written !== -1) {
952
+ task[TaskIndex.Type] = PayloadBuffer.StaticJson;
953
+ task[TaskIndex.PayloadLen] = written;
954
+ // Retain any transport-owned memory until the call settles.
955
+ attachPayloadTransportFinalizer(task, objectValue);
956
+ task.value = null;
957
+ return true;
958
+ }
959
+ }
960
+ task[TaskIndex.Type] = PayloadBuffer.Json;
961
+ const reserveBytes = dynamicUtf8ReserveBytes(task, text, "Json");
962
+ if (reserveBytes < 0)
963
+ return false;
964
+ const reservedSlot = reserveDynamicObject(task, reserveBytes);
965
+ if (reservedSlot === -1)
966
+ return false;
967
+ const written = writeDynamicUtf8(text, task[TaskIndex.Start], reserveBytes);
968
+ if (written < 0)
969
+ return failDynamicWriteAfterReserve(task, reservedSlot);
970
+ task[TaskIndex.PayloadLen] = written;
971
+ setSlotLength(reservedSlot, written);
972
+ attachPayloadTransportFinalizer(task, objectValue);
973
+ task.value = null;
974
+ return true;
975
+ };
731
976
  const encodeObjectDate = (task, date) => {
732
977
  Float64View[0] = date.getTime();
733
978
  task[TaskIndex.Type] = PayloadBuffer.Date;
@@ -799,6 +1044,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
799
1044
  if (!ensureWithinDynamicLimit(task, payloadReserveBytes, "EnvelopeStaticHeaderPayload"))
800
1045
  return false;
801
1046
  const reservedSlot = reserveDynamicObject(task, payloadReserveBytes);
1047
+ if (reservedSlot === -1)
1048
+ return false;
802
1049
  task[TaskIndex.Type] = headerIsString
803
1050
  ? PayloadBuffer.EnvelopeStaticHeaderString
804
1051
  : PayloadBuffer.EnvelopeStaticHeader;
@@ -811,7 +1058,11 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
811
1058
  }
812
1059
  setSlotLength(reservedSlot, payloadWritten);
813
1060
  }
1061
+ else {
1062
+ setSlotLength(reservedSlot, 0);
1063
+ }
814
1064
  task.value = null;
1065
+ tagTaskSlot(task, reservedSlot);
815
1066
  return true;
816
1067
  }
817
1068
  const headerReserveBytes = dynamicUtf8ReserveBytesWithExtra(task, headerText, payloadLength, headerIsString ? "EnvelopeDynamicHeaderString" : "EnvelopeDynamicHeader");
@@ -821,6 +1072,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
821
1072
  ? PayloadBuffer.EnvelopeDynamicHeaderString
822
1073
  : PayloadBuffer.EnvelopeDynamicHeader;
823
1074
  const reservedSlot = reserveDynamicObject(task, headerReserveBytes + payloadLength);
1075
+ if (reservedSlot === -1)
1076
+ return false;
824
1077
  const baseStart = task[TaskIndex.Start];
825
1078
  const writtenHeaderBytes = writeDynamicUtf8(headerText, baseStart, headerReserveBytes);
826
1079
  if (writtenHeaderBytes < 0) {
@@ -836,6 +1089,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
836
1089
  task[TaskIndex.End] = payloadLength;
837
1090
  setSlotLength(reservedSlot, writtenHeaderBytes + payloadLength);
838
1091
  task.value = null;
1092
+ tagTaskSlot(task, reservedSlot);
839
1093
  return true;
840
1094
  };
841
1095
  const encodeEnvelopeExternalBody = (task, slotIndex, headerText, headerIsString, externalBody, trustedReservedCodec) => {
@@ -901,6 +1155,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
901
1155
  if (!ensureWithinDynamicLimit(task, bodyLength, "EnvelopeStaticHeaderExternal"))
902
1156
  return false;
903
1157
  const reservedSlot = reserveDynamicObject(task, bodyLength);
1158
+ if (reservedSlot === -1)
1159
+ return false;
904
1160
  task[TaskIndex.Type] = headerIsString
905
1161
  ? PayloadBuffer.EnvelopeStaticHeaderStringExternal
906
1162
  : PayloadBuffer.EnvelopeStaticHeaderExternal;
@@ -913,6 +1169,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
913
1169
  setSlotLength(reservedSlot, bodyWritten);
914
1170
  attachPayloadTransportFinalizer(task, externalBody);
915
1171
  task.value = null;
1172
+ tagTaskSlot(task, reservedSlot);
916
1173
  return true;
917
1174
  }
918
1175
  const headerReserveBytes = dynamicUtf8ReserveBytesWithExtra(task, headerText, bodyLength, headerIsString
@@ -924,6 +1181,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
924
1181
  ? PayloadBuffer.EnvelopeDynamicHeaderStringExternal
925
1182
  : PayloadBuffer.EnvelopeDynamicHeaderExternal;
926
1183
  const reservedSlot = reserveDynamicObject(task, headerReserveBytes + bodyLength);
1184
+ if (reservedSlot === -1)
1185
+ return false;
927
1186
  const baseStart = task[TaskIndex.Start];
928
1187
  const writtenHeaderBytes = writeDynamicUtf8(headerText, baseStart, headerReserveBytes);
929
1188
  if (writtenHeaderBytes < 0) {
@@ -938,6 +1197,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
938
1197
  setSlotLength(reservedSlot, writtenHeaderBytes + bodyLength);
939
1198
  attachPayloadTransportFinalizer(task, externalBody);
940
1199
  task.value = null;
1200
+ tagTaskSlot(task, reservedSlot);
941
1201
  return true;
942
1202
  };
943
1203
  const encodeObjectEnvelope = (task, slotIndex, envelope) => {
@@ -1005,6 +1265,10 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1005
1265
  return false;
1006
1266
  }
1007
1267
  const reservedSlot = reserveDynamic(task, binaryBytes);
1268
+ if (reservedSlot === -1) {
1269
+ clearBigIntScratch(binaryBytes);
1270
+ return false;
1271
+ }
1008
1272
  const written = writeDynamicBinary(binary, task[TaskIndex.Start]);
1009
1273
  if (written < 0) {
1010
1274
  clearBigIntScratch(binaryBytes);
@@ -1026,57 +1290,23 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1026
1290
  objectDynamicSlot = -1;
1027
1291
  try {
1028
1292
  const objectValue = args;
1293
+ const objectProto = objectGetPrototypeOf(objectValue);
1294
+ // Plain objects can skip the typed-array and external-payload checks.
1295
+ if (objectProto === objectPrototype) {
1296
+ return encodeObjectJson(task, slotIndex, objectValue);
1297
+ }
1029
1298
  const sharedArrayBufferPayload = getSharedArrayBufferPayload(objectValue);
1030
1299
  if (sharedArrayBufferPayload !== undefined) {
1031
1300
  return encodeObjectExternalPayload(task, slotIndex, sharedArrayBufferPayload, true);
1032
1301
  }
1033
- const objectProto = objectGetPrototypeOf(objectValue);
1034
1302
  if (isRuntimeUint8Array(objectValue)) {
1035
1303
  return encodeObjectUint8Array(task, slotIndex, objectValue);
1036
1304
  }
1037
1305
  if (arrayIsArray(objectValue) && isNumericArray(objectValue)) {
1038
1306
  return encodeObjectNumericArray(task, slotIndex, objectValue);
1039
1307
  }
1040
- if (arrayIsArray(objectValue) ||
1041
- objectProto === objectPrototype ||
1042
- objectProto === null) {
1043
- let text;
1044
- try {
1045
- text = stringifyJSON(objectValue);
1046
- }
1047
- catch (error) {
1048
- const detail = error instanceof Error
1049
- ? error.message
1050
- : String(error);
1051
- return encoderError({
1052
- task,
1053
- type: ErrorKnitting.Json,
1054
- onPromise,
1055
- detail,
1056
- });
1057
- }
1058
- if (text.length <= staticMaxBytes) {
1059
- const written = writeStaticUtf8(text, slotIndex);
1060
- if (written !== -1) {
1061
- task[TaskIndex.Type] = PayloadBuffer.StaticJson;
1062
- task[TaskIndex.PayloadLen] = written;
1063
- task.value = null;
1064
- return true;
1065
- }
1066
- }
1067
- task[TaskIndex.Type] = PayloadBuffer.Json;
1068
- const reserveBytes = dynamicUtf8ReserveBytes(task, text, "Json");
1069
- if (reserveBytes < 0)
1070
- return false;
1071
- const reservedSlot = reserveDynamicObject(task, reserveBytes);
1072
- const written = writeDynamicUtf8(text, task[TaskIndex.Start], reserveBytes);
1073
- if (written < 0) {
1074
- return failDynamicWriteAfterReserve(task, reservedSlot);
1075
- }
1076
- task[TaskIndex.PayloadLen] = written;
1077
- setSlotLength(reservedSlot, written);
1078
- task.value = null;
1079
- return true;
1308
+ if (arrayIsArray(objectValue) || objectProto === null) {
1309
+ return encodeObjectJson(task, slotIndex, objectValue);
1080
1310
  }
1081
1311
  if (isBufferReferenceValue(objectValue) ||
1082
1312
  isProcessSharedBufferValue(objectValue)) {
@@ -1165,6 +1395,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1165
1395
  if (reserveBytes < 0)
1166
1396
  return false;
1167
1397
  const reservedSlot = reserveDynamic(task, reserveBytes);
1398
+ if (reservedSlot === -1)
1399
+ return false;
1168
1400
  const written = writeDynamicUtf8(text, task[TaskIndex.Start], reserveBytes);
1169
1401
  if (written < 0) {
1170
1402
  return failDynamicWriteAfterReserve(task, reservedSlot);
@@ -1197,6 +1429,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1197
1429
  if (reserveBytes < 0)
1198
1430
  return false;
1199
1431
  const reservedSlot = reserveDynamic(task, reserveBytes);
1432
+ if (reservedSlot === -1)
1433
+ return false;
1200
1434
  const written = writeDynamicUtf8(key, task[TaskIndex.Start], reserveBytes);
1201
1435
  if (written < 0) {
1202
1436
  return failDynamicWriteAfterReserve(task, reservedSlot);
@@ -1218,8 +1452,11 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1218
1452
  options: payload?.config ?? payloadConfig,
1219
1453
  });
1220
1454
  const { free } = register({ lockSector });
1221
- const freeTaskSlot = (task) => free(getTaskSlotIndex(task));
1222
- const { readUtf8: readDynamicUtf8, readBytesCopy: readDynamicBytesCopy, readBytesBufferCopy: readDynamicBufferCopy, readBufferCopy: readDynamicBuffer, readBytesArrayBufferCopy: readDynamicArrayBufferCopy, readArrayBufferCopy: readDynamicArrayBuffer, read8BytesFloatCopy: readDynamic8BytesFloatCopy, read8BytesFloatView: readDynamic8BytesFloatView, } = createSharedDynamicBufferIO({
1455
+ // Region identity is 6 bits only for dynamic payloads. Its high bit rides in
1456
+ // End's otherwise-unused top bit, preserving the 5-bit queue-slot field.
1457
+ const freeTaskSlot = (task) => free((task[TaskIndex.slotBuffer] & TASK_SLOT_INDEX_MASK) |
1458
+ ((task[TaskIndex.End] >>> 31) << 5));
1459
+ const { readUtf8: readDynamicUtf8, readBytesCopy: readDynamicBytesCopy, readBytesBufferCopy: readDynamicBufferCopy, readBufferCopy: readDynamicBuffer, readBytesArrayBufferCopy: readDynamicArrayBufferCopy, readArrayBufferCopy: readDynamicArrayBuffer, read8BytesFloatCopy: readDynamic8BytesFloatCopy, read8BytesFloatView: readDynamic8BytesFloatView, readBytesView: readDynamicBytesView, syncGrowth: syncDynamicGrowth, } = createSharedDynamicBufferIO({
1223
1460
  sab: payloadSab,
1224
1461
  payloadConfig: resolvedPayloadConfig,
1225
1462
  textCompat: textCompat?.payload,
@@ -1230,10 +1467,13 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1230
1467
  // synchronously when building the ProcessSharedBuffer.
1231
1468
  const processSharedBufferWords = new Uint32Array(PROCESS_SHARED_BUFFER_NUMERIC_WORDS);
1232
1469
  const sharedArrayBufferWords = new Uint32Array(SHARED_ARRAY_BUFFER_NUMERIC_WORDS);
1470
+ // `readStaticU32Words` returns the whole scratch, and the SAB codec dispatches
1471
+ // on word count, so hand it a view cut to the frame's own length.
1472
+ const sharedArrayBufferWordViews = [];
1473
+ for (let count = 0; count <= SHARED_ARRAY_BUFFER_NUMERIC_WORDS; count++) {
1474
+ sharedArrayBufferWordViews.push(sharedArrayBufferWords.subarray(0, count));
1475
+ }
1233
1476
  const bufferReferenceWords = new Uint32Array(BUFFER_REFERENCE_NUMERIC_WORDS);
1234
- // TODO: remove slotIndex and make that all their callers
1235
- // store the slot in their Task, to just get it when it comes
1236
- // to the static versions of decoding
1237
1477
  return (task, slotIndex, specialFlags) => {
1238
1478
  const payloadType = task[TaskIndex.Type];
1239
1479
  if (processBoundary &&
@@ -1287,7 +1527,7 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1287
1527
  const header = task[TaskIndex.Type] === PayloadBuffer.EnvelopeStaticHeaderString
1288
1528
  ? rawHeader
1289
1529
  : parseJSON(rawHeader);
1290
- const payloadLength = task[TaskIndex.End];
1530
+ const payloadLength = task[TaskIndex.End] & 0x7FFFFFFF;
1291
1531
  const payload = payloadLength > 0
1292
1532
  ? readDynamicArrayBufferCopy(task[TaskIndex.Start], task[TaskIndex.Start] + payloadLength)
1293
1533
  : new ArrayBuffer(0);
@@ -1299,7 +1539,7 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1299
1539
  case PayloadBuffer.EnvelopeDynamicHeaderString: {
1300
1540
  const headerStart = task[TaskIndex.Start];
1301
1541
  const payloadStart = headerStart + task[TaskIndex.PayloadLen];
1302
- const payloadLength = task[TaskIndex.End];
1542
+ const payloadLength = task[TaskIndex.End] & 0x7FFFFFFF;
1303
1543
  const rawHeader = readDynamicUtf8(headerStart, payloadStart);
1304
1544
  const header = task[TaskIndex.Type] === PayloadBuffer.EnvelopeDynamicHeaderString
1305
1545
  ? rawHeader
@@ -1319,7 +1559,7 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1319
1559
  ? rawHeader
1320
1560
  : parseJSON(rawHeader);
1321
1561
  const bodyStart = task[TaskIndex.Start];
1322
- const body = decodeExternalPayload(readDynamicUtf8(bodyStart, bodyStart + task[TaskIndex.End]), processBoundary);
1562
+ const body = decodeExternalPayload(readDynamicUtf8(bodyStart, bodyStart + (task[TaskIndex.End] & 0x7FFFFFFF)), processBoundary);
1323
1563
  task.value = new Envelope(header, body);
1324
1564
  freeTaskSlot(task);
1325
1565
  return;
@@ -1333,7 +1573,7 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1333
1573
  PayloadBuffer.EnvelopeDynamicHeaderStringExternal
1334
1574
  ? rawHeader
1335
1575
  : parseJSON(rawHeader);
1336
- const body = decodeExternalPayload(readDynamicUtf8(bodyStart, bodyStart + task[TaskIndex.End]), processBoundary);
1576
+ const body = decodeExternalPayload(readDynamicUtf8(bodyStart, bodyStart + (task[TaskIndex.End] & 0x7FFFFFFF)), processBoundary);
1337
1577
  task.value = new Envelope(header, body);
1338
1578
  freeTaskSlot(task);
1339
1579
  return;
@@ -1413,21 +1653,39 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1413
1653
  return;
1414
1654
  }
1415
1655
  case PayloadBuffer.ExternalPayload:
1416
- task.value = decodeExternalPayload(readDynamicUtf8(task[TaskIndex.Start], task[TaskIndex.Start] + task[TaskIndex.PayloadLen]), processBoundary);
1656
+ task.value = decodeExternalPayload(readDynamicUtf8(task[TaskIndex.Start], task[TaskIndex.Start] + task[TaskIndex.PayloadLen]), processBoundary, lockSector);
1417
1657
  freeTaskSlot(task);
1418
1658
  return;
1419
1659
  case PayloadBuffer.StaticExternalPayload:
1420
- task.value = decodeExternalPayload(readStaticUtf8(0, task[TaskIndex.PayloadLen], slotIndex), processBoundary);
1660
+ task.value = decodeExternalPayload(readStaticUtf8(0, task[TaskIndex.PayloadLen], slotIndex), processBoundary, lockSector);
1421
1661
  return;
1422
1662
  case PayloadBuffer.ProcessSharedBuffer:
1423
1663
  task.value = decodeProcessSharedBufferNumericWords(readStaticU32Words(processSharedBufferWords, PROCESS_SHARED_BUFFER_NUMERIC_WORDS, slotIndex));
1424
1664
  return;
1425
- case PayloadBuffer.SharedArrayBuffer:
1426
- task.value = decodeSharedArrayBufferNumericWords(readStaticU32Words(sharedArrayBufferWords, task[TaskIndex.PayloadLen] >>> 2, slotIndex));
1665
+ case PayloadBuffer.SharedArrayBuffer: {
1666
+ const wordCount = task[TaskIndex.PayloadLen] >>> 2;
1667
+ task.value = decodeSharedArrayBufferNumericWords(readStaticU32Words(sharedArrayBufferWordViews[wordCount] ?? sharedArrayBufferWords, wordCount, slotIndex), lockSector);
1427
1668
  return;
1669
+ }
1428
1670
  case PayloadBuffer.BufferReference:
1429
1671
  task.value = decodeBufferReferenceNumericWords(readStaticU32Words(bufferReferenceWords, BUFFER_REFERENCE_NUMERIC_WORDS, slotIndex));
1430
1672
  return;
1673
+ case PayloadBuffer.MovedBinary: {
1674
+ const reference = decodeBufferReferenceNumericWords(readStaticU32Words(bufferReferenceWords, BUFFER_REFERENCE_NUMERIC_WORDS, slotIndex));
1675
+ if (!isBufferReferenceValue(reference)) {
1676
+ throw new TypeError("Invalid moved Uint8Array payload");
1677
+ }
1678
+ task.value = reference.toUint8Array();
1679
+ return;
1680
+ }
1681
+ case PayloadBuffer.MovedArrayBuffer: {
1682
+ const reference = decodeBufferReferenceNumericWords(readStaticU32Words(bufferReferenceWords, BUFFER_REFERENCE_NUMERIC_WORDS, slotIndex));
1683
+ if (!isBufferReferenceValue(reference)) {
1684
+ throw new TypeError("Invalid moved ArrayBuffer payload");
1685
+ }
1686
+ task.value = reference.toArrayBuffer();
1687
+ return;
1688
+ }
1431
1689
  case PayloadBuffer.Date:
1432
1690
  Uint32View[0] = task[TaskIndex.Start];
1433
1691
  Uint32View[1] = task[TaskIndex.End];
@@ -1437,6 +1695,12 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1437
1695
  task.value = parseErrorPayload(readDynamicUtf8(task[TaskIndex.Start], task[TaskIndex.Start] + task[TaskIndex.PayloadLen]));
1438
1696
  freeTaskSlot(task);
1439
1697
  return;
1698
+ // Borrowed region: decode a view into the shared arena.
1699
+ case PayloadBuffer.ArenaBinary:
1700
+ // A borrowed region can sit past the length this endpoint last saw.
1701
+ syncDynamicGrowth();
1702
+ task.value = readDynamicBytesView(task[TaskIndex.Start], task[TaskIndex.Start] + task[TaskIndex.PayloadLen]);
1703
+ return;
1440
1704
  case PayloadBuffer.Binary:
1441
1705
  {
1442
1706
  const buffer = readDynamicBufferCopy(task[TaskIndex.Start], task[TaskIndex.Start] + task[TaskIndex.PayloadLen]);