knitting 0.1.63 → 0.1.70
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/README.md +525 -335
- package/knitting.browser.js +1 -1
- package/map.md +0 -6
- package/package.json +3 -3
- package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_doorbell.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_doorbell.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_doorbell.node +0 -0
- package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
- package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_doorbell.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
- package/scripts/build-native-addons.ts +5 -0
- package/shared-memory.d.ts +3 -0
- package/shared-memory.js +3 -0
- package/src/api.js +105 -42
- package/src/common/with-resolvers.js +2 -5
- package/src/common/worker-runtime.d.ts +7 -0
- package/src/common/worker-runtime.js +7 -0
- package/src/connections/buffer-reference.d.ts +10 -36
- package/src/connections/buffer-reference.js +15 -170
- package/src/connections/node-addons.d.ts +1 -1
- package/src/connections/shared-array-buffer-payload.d.ts +7 -0
- package/src/connections/shared-array-buffer-payload.js +27 -11
- package/src/knitting_buffer_pointer.cc +57 -2
- package/src/knitting_doorbell.cc +220 -0
- package/src/memory/knitting-body.d.ts +44 -0
- package/src/memory/knitting-body.js +51 -0
- package/src/memory/knitting-buffer-http.d.ts +116 -0
- package/src/memory/knitting-buffer-http.js +255 -0
- package/src/memory/knitting-buffer.d.ts +250 -0
- package/src/memory/knitting-buffer.js +695 -0
- package/src/memory/lazy-region-registry.d.ts +83 -0
- package/src/memory/lazy-region-registry.js +355 -0
- package/src/memory/lock.d.ts +38 -15
- package/src/memory/lock.js +205 -79
- package/src/memory/payloadCodec.d.ts +18 -2
- package/src/memory/payloadCodec.js +309 -65
- package/src/memory/regionRegistry.d.ts +6 -0
- package/src/memory/regionRegistry.js +125 -240
- package/src/memory/shared-buffer-io.d.ts +7 -0
- package/src/memory/shared-buffer-io.js +34 -8
- package/src/runtime/deno-doorbell.d.ts +26 -0
- package/src/runtime/deno-doorbell.js +117 -0
- package/src/runtime/dispatcher.d.ts +8 -6
- package/src/runtime/dispatcher.js +80 -58
- package/src/runtime/host-arg-arena.d.ts +3 -0
- package/src/runtime/host-arg-arena.js +16 -0
- package/src/runtime/node-doorbell.d.ts +14 -0
- package/src/runtime/node-doorbell.js +84 -0
- package/src/runtime/pool.d.ts +21 -15
- package/src/runtime/pool.js +104 -116
- package/src/runtime/process-worker.d.ts +9 -0
- package/src/runtime/process-worker.js +22 -2
- package/src/runtime/tx-queue.d.ts +2 -5
- package/src/runtime/tx-queue.js +52 -48
- package/src/types.d.ts +35 -71
- package/src/worker/loop.js +79 -57
- package/src/worker/rx-queue.d.ts +2 -3
- package/src/worker/rx-queue.js +34 -40
- package/src/worker/shared-return.d.ts +9 -0
- package/src/worker/shared-return.js +22 -0
- package/src/worker/task-loader.js +1 -2
- package/src/worker/timers.d.ts +2 -6
- package/src/worker/timers.js +14 -19
- package/unsafe.d.ts +2 -1
- package/unsafe.js +2 -1
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { Task } from "./lock.js";
|
|
2
2
|
import { type SharedBufferSource } from "../common/shared-buffer-region.js";
|
|
3
|
+
/** Dynamic payload-region identities; independent from 32 queue header slots. */
|
|
4
|
+
export declare const DYNAMIC_PAYLOAD_SLOTS = 64;
|
|
5
|
+
export declare const DYNAMIC_PAYLOAD_SLOT_MASK: number;
|
|
3
6
|
export type RegisterMalloc = ReturnType<typeof register>;
|
|
4
7
|
export declare const register: ({ lockSector, }: {
|
|
5
8
|
lockSector?: SharedBufferSource;
|
|
6
9
|
}) => {
|
|
7
10
|
allocTask: (task: Task) => number;
|
|
11
|
+
allocRegion: (payloadLen: number) => number;
|
|
12
|
+
regionStart: (slot: number) => number;
|
|
8
13
|
setSlotLength: (slotIndex: number, payloadLen: number) => boolean;
|
|
14
|
+
tagTaskSlot: (task: Task, slot: number) => void;
|
|
9
15
|
lockSAB: import("../common/shared-buffer-region.js").SharedBuffer;
|
|
10
16
|
free: (index: number) => void;
|
|
11
17
|
hostBits: Int32Array<import("../common/shared-buffer-region.js").SharedBuffer>;
|
|
@@ -1,282 +1,167 @@
|
|
|
1
|
-
import { LOCK_SECTOR_BYTE_LENGTH,
|
|
1
|
+
import { LOCK_SECTOR_BYTE_LENGTH, PAYLOAD_LOCK_HOST_BITS_OFFSET_BYTES, PAYLOAD_LOCK_WORKER_BITS_OFFSET_BYTES, TASK_SLOT_INDEX_MASK, TaskIndex, } from "./lock.js";
|
|
2
2
|
import { createWasmSharedArrayBuffer } from "../common/runtime.js";
|
|
3
3
|
import { toSharedBufferRegion, } from "../common/shared-buffer-region.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
4
|
+
/** Dynamic payload-region identities; independent from 32 queue header slots. */
|
|
5
|
+
export const DYNAMIC_PAYLOAD_SLOTS = 64;
|
|
6
|
+
export const DYNAMIC_PAYLOAD_SLOT_MASK = DYNAMIC_PAYLOAD_SLOTS - 1;
|
|
7
|
+
const PAYLOAD_LOCK_STATE_WORDS = DYNAMIC_PAYLOAD_SLOTS / 32;
|
|
8
|
+
// Low 6 bits store the region index; the remaining bits store its byte offset.
|
|
9
|
+
const EMPTY = 0xFFFFFFBF >>> 0; // 111...10111111
|
|
10
|
+
const SLOT_META_PACKED_MASK = (~TASK_SLOT_INDEX_MASK) >>> 0;
|
|
7
11
|
export const register = ({ lockSector, }) => {
|
|
8
12
|
const lockRegion = toSharedBufferRegion(lockSector ?? createWasmSharedArrayBuffer(LOCK_SECTOR_BYTE_LENGTH));
|
|
9
13
|
const lockSAB = lockRegion.sab;
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
14
|
+
// Dynamic-region state shares the queue lock's cache-line padding.
|
|
15
|
+
const hostBits = new Int32Array(lockSAB, lockRegion.byteOffset + PAYLOAD_LOCK_HOST_BITS_OFFSET_BYTES, PAYLOAD_LOCK_STATE_WORDS);
|
|
16
|
+
const workerBits = new Int32Array(lockSAB, lockRegion.byteOffset + PAYLOAD_LOCK_WORKER_BITS_OFFSET_BYTES, PAYLOAD_LOCK_STATE_WORDS);
|
|
17
|
+
const startAndIndex = new Uint32Array(DYNAMIC_PAYLOAD_SLOTS);
|
|
18
|
+
const size64bit = new Uint32Array(DYNAMIC_PAYLOAD_SLOTS);
|
|
19
|
+
// Keep region offsets indexed by slot for O(1) lookup.
|
|
20
|
+
const startBySlot = new Uint32Array(DYNAMIC_PAYLOAD_SLOTS);
|
|
15
21
|
const a_load = Atomics.load;
|
|
16
22
|
const a_store = Atomics.store;
|
|
17
23
|
const a_xor = Atomics.xor;
|
|
18
|
-
const
|
|
19
|
-
const SLOT_MASK = TASK_SLOT_INDEX_MASK;
|
|
24
|
+
const SLOT_MASK = DYNAMIC_PAYLOAD_SLOT_MASK;
|
|
20
25
|
const START_MASK = (~SLOT_MASK) >>> 0;
|
|
21
26
|
startAndIndex.fill(EMPTY);
|
|
22
27
|
let tableLength = 0;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
let
|
|
28
|
+
// Keep the common <=32-region path in scalar words.
|
|
29
|
+
let usedBits0 = 0 | 0;
|
|
30
|
+
let usedBits1 = 0 | 0;
|
|
31
|
+
let hostLast0 = 0 | 0;
|
|
32
|
+
let hostLast1 = 0 | 0;
|
|
26
33
|
const startAndIndexToArray = (length) => startAndIndex.slice(0, length);
|
|
27
|
-
const
|
|
34
|
+
const slotWord = (slot) => slot >>> 5;
|
|
35
|
+
const slotBit = (slot) => 1 << (slot & 31);
|
|
36
|
+
const compactFreed = (freeBits0, freeBits1) => {
|
|
28
37
|
const sai = startAndIndex;
|
|
29
|
-
let
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
38
|
+
let write = 0;
|
|
39
|
+
for (let read = 0; read < tableLength; read++) {
|
|
40
|
+
const value = sai[read];
|
|
41
|
+
const slot = value & SLOT_MASK;
|
|
42
|
+
const freeBits = slot < 32 ? freeBits0 : freeBits1;
|
|
43
|
+
if ((freeBits & slotBit(slot)) !== 0)
|
|
35
44
|
continue;
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
if (write !== read)
|
|
46
|
+
sai[write] = value;
|
|
47
|
+
write++;
|
|
48
|
+
}
|
|
49
|
+
for (let at = write; at < tableLength; at++)
|
|
50
|
+
sai[at] = EMPTY;
|
|
51
|
+
tableLength = write;
|
|
52
|
+
};
|
|
53
|
+
const findFreeSlot = () => {
|
|
54
|
+
let available = ~usedBits0;
|
|
55
|
+
if (available !== 0) {
|
|
56
|
+
return 31 - Math.clz32((available & -available) >>> 0);
|
|
41
57
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
available = ~usedBits1;
|
|
59
|
+
return available === 0
|
|
60
|
+
? -1
|
|
61
|
+
: 32 + 31 - Math.clz32((available & -available) >>> 0);
|
|
46
62
|
};
|
|
63
|
+
/** Store the sixth region-index bit in bit 31 of the task length word. */
|
|
64
|
+
const tagTaskSlot = (task, slot) => {
|
|
65
|
+
task[TaskIndex.End] = ((task[TaskIndex.End] & 0x7FFFFFFF) | ((slot & 32) << 26)) >>> 0;
|
|
66
|
+
};
|
|
67
|
+
const reserveSlot = (slot) => {
|
|
68
|
+
const bit = slotBit(slot);
|
|
69
|
+
if (slot < 32) {
|
|
70
|
+
usedBits0 = (usedBits0 | bit) | 0;
|
|
71
|
+
hostLast0 = (hostLast0 ^ bit) | 0;
|
|
72
|
+
a_store(hostBits, 0, hostLast0);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
usedBits1 = (usedBits1 | bit) | 0;
|
|
76
|
+
hostLast1 = (hostLast1 ^ bit) | 0;
|
|
77
|
+
a_store(hostBits, 1, hostLast1);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
// Reconcile completed reads and remove freed regions from the table.
|
|
47
81
|
const updateTable = () => {
|
|
48
|
-
|
|
49
|
-
const w = a_load(workerBits, 0) | 0;
|
|
50
|
-
const state = (hostLast ^ w) >>> 0;
|
|
51
|
-
// freeBits = bits where host/worker agree (toggle resolved)
|
|
52
|
-
let freeBits = (~state) >>> 0;
|
|
53
|
-
if (tableLength === 0 || freeBits === 0)
|
|
82
|
+
if (tableLength === 0)
|
|
54
83
|
return;
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
const freeBits0 = usedBits0 === 0
|
|
85
|
+
? 0
|
|
86
|
+
: (~(hostLast0 ^ a_load(workerBits, 0)) & usedBits0) | 0;
|
|
87
|
+
const freeBits1 = usedBits1 === 0
|
|
88
|
+
? 0
|
|
89
|
+
: (~(hostLast1 ^ a_load(workerBits, 1)) & usedBits1) | 0;
|
|
90
|
+
if ((freeBits0 | freeBits1) === 0)
|
|
91
|
+
return;
|
|
92
|
+
if (freeBits0 === usedBits0 && freeBits1 === usedBits1) {
|
|
57
93
|
tableLength = 0;
|
|
58
|
-
|
|
94
|
+
usedBits0 = 0;
|
|
95
|
+
usedBits1 = 0;
|
|
59
96
|
return;
|
|
60
97
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return;
|
|
65
|
-
usedBits &= ~freeBits;
|
|
66
|
-
tableLength = compactFreeBitsStable(tableLength, freeBits);
|
|
98
|
+
usedBits0 = (usedBits0 & ~freeBits0) | 0;
|
|
99
|
+
usedBits1 = (usedBits1 & ~freeBits1) | 0;
|
|
100
|
+
compactFreed(freeBits0, freeBits1);
|
|
67
101
|
};
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const sz = size64bit;
|
|
73
|
-
let tl = tableLength;
|
|
74
|
-
let insertAt = -1;
|
|
75
|
-
let insertStart = 0;
|
|
76
|
-
let prevEnd = 0;
|
|
77
|
-
let didCompactScan = false;
|
|
78
|
-
if (tl === 0 && usedBits === 0) {
|
|
79
|
-
sai[0] = 0;
|
|
80
|
-
sz[0] = size;
|
|
81
|
-
task[TaskIndex.Start] = 0;
|
|
82
|
-
task[TaskIndex.slotBuffer] =
|
|
83
|
-
((task[TaskIndex.slotBuffer] & SLOT_META_PACKED_MASK) | 0) >>> 0;
|
|
84
|
-
tableLength = 1;
|
|
85
|
-
usedBits = 1;
|
|
86
|
-
hostLast ^= 1;
|
|
87
|
-
return 0;
|
|
88
|
-
}
|
|
89
|
-
if (tl !== 0) {
|
|
90
|
-
const w = a_load(workerBits, 0) | 0;
|
|
91
|
-
let freeBits = (~(hostLast ^ w)) >>> 0;
|
|
92
|
-
if (freeBits !== 0)
|
|
93
|
-
freeBits &= usedBits;
|
|
94
|
-
// Every tracked slot is free, so restart the arena from offset 0. A free
|
|
95
|
-
// racing after this snapshot can only delay reclamation until the next
|
|
96
|
-
// allocation; it cannot make a live slot appear free.
|
|
97
|
-
if (freeBits === (usedBits >>> 0)) {
|
|
98
|
-
tableLength = 0;
|
|
99
|
-
usedBits = 0 | 0;
|
|
100
|
-
tl = 0;
|
|
101
|
-
freeBits = 0 >>> 0;
|
|
102
|
-
}
|
|
103
|
-
else if (freeBits !== 0) {
|
|
104
|
-
for (let i = 0; i < tl; i++) {
|
|
105
|
-
const v = sai[i];
|
|
106
|
-
const reclaimedSlot = v & SLOT_MASK;
|
|
107
|
-
const reclaimedBit = 1 << reclaimedSlot;
|
|
108
|
-
if ((freeBits & reclaimedBit) === 0)
|
|
109
|
-
continue;
|
|
110
|
-
if ((sz[reclaimedSlot] >>> 0) !== (size >>> 0))
|
|
111
|
-
continue;
|
|
112
|
-
const availableBits = (~usedBits) >>> 0;
|
|
113
|
-
const freeBit = (availableBits & -availableBits) >>> 0;
|
|
114
|
-
if (freeBit === 0)
|
|
115
|
-
return -1;
|
|
116
|
-
const slotIndex = 31 - clz32(freeBit);
|
|
117
|
-
const start = v & START_MASK;
|
|
118
|
-
sai[i] = (start | slotIndex) >>> 0;
|
|
119
|
-
sz[slotIndex] = size;
|
|
120
|
-
task[TaskIndex.Start] = start;
|
|
121
|
-
task[TaskIndex.slotBuffer] =
|
|
122
|
-
((task[TaskIndex.slotBuffer] & SLOT_META_PACKED_MASK) |
|
|
123
|
-
slotIndex) >>> 0;
|
|
124
|
-
usedBits = (usedBits & ~reclaimedBit) | freeBit;
|
|
125
|
-
hostLast ^= freeBit;
|
|
126
|
-
return slotIndex;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
if (tl !== 0 && freeBits !== 0 && freeBits !== EMPTY) {
|
|
130
|
-
didCompactScan = true;
|
|
131
|
-
let write = 0;
|
|
132
|
-
for (let read = 0; read < tl; read++) {
|
|
133
|
-
const v = sai[read];
|
|
134
|
-
const slot = v & SLOT_MASK;
|
|
135
|
-
if ((freeBits & (1 << slot)) !== 0)
|
|
136
|
-
continue;
|
|
137
|
-
const curStart = v & START_MASK;
|
|
138
|
-
if (insertAt === -1 && ((curStart - prevEnd) >>> 0) >= (size >>> 0)) {
|
|
139
|
-
insertAt = write;
|
|
140
|
-
insertStart = prevEnd;
|
|
141
|
-
}
|
|
142
|
-
if (write !== read)
|
|
143
|
-
sai[write] = v;
|
|
144
|
-
write++;
|
|
145
|
-
prevEnd = (curStart + (sz[slot] >>> 0)) >>> 0;
|
|
146
|
-
}
|
|
147
|
-
for (let i = write; i < tl; i++)
|
|
148
|
-
sai[i] = EMPTY;
|
|
149
|
-
if (freeBits !== 0)
|
|
150
|
-
usedBits &= ~freeBits;
|
|
151
|
-
tableLength = tl = write;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
if (tl >= LockBound.slots)
|
|
102
|
+
// The extent table stays sorted by offset and is limited to 64 regions.
|
|
103
|
+
const findAndInsert = (size) => {
|
|
104
|
+
updateTable();
|
|
105
|
+
if (tableLength >= DYNAMIC_PAYLOAD_SLOTS)
|
|
155
106
|
return -1;
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
if (freeBit === 0)
|
|
107
|
+
const slot = findFreeSlot();
|
|
108
|
+
if (slot === -1)
|
|
159
109
|
return -1;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
task[TaskIndex.Start] = 0;
|
|
169
|
-
task[TaskIndex.slotBuffer] =
|
|
170
|
-
((task[TaskIndex.slotBuffer] & SLOT_META_PACKED_MASK) | slotIndex) >>>
|
|
171
|
-
0;
|
|
172
|
-
tableLength = tl + 1;
|
|
173
|
-
usedBits |= freeBit;
|
|
174
|
-
hostLast ^= freeBit;
|
|
175
|
-
return slotIndex;
|
|
110
|
+
let previousEnd = 0;
|
|
111
|
+
let insertAt = tableLength;
|
|
112
|
+
for (let at = 0; at < tableLength; at++) {
|
|
113
|
+
const value = startAndIndex[at];
|
|
114
|
+
const start = value & START_MASK;
|
|
115
|
+
if (((start - previousEnd) >>> 0) >= (size >>> 0)) {
|
|
116
|
+
insertAt = at;
|
|
117
|
+
break;
|
|
176
118
|
}
|
|
177
|
-
|
|
178
|
-
const cur = sai[at];
|
|
179
|
-
const curStart = cur & START_MASK;
|
|
180
|
-
const curEnd = (curStart + (sz[cur & SLOT_MASK] >>> 0)) >>> 0;
|
|
181
|
-
const nextStart = sai[at + 1] & START_MASK;
|
|
182
|
-
if ((nextStart - curEnd) >>> 0 < (size >>> 0))
|
|
183
|
-
continue;
|
|
184
|
-
for (let i = tl; i > at + 1; i--)
|
|
185
|
-
sai[i] = sai[i - 1];
|
|
186
|
-
sai[at + 1] = (curEnd | slotIndex) >>> 0;
|
|
187
|
-
sz[slotIndex] = size;
|
|
188
|
-
task[TaskIndex.Start] = curEnd;
|
|
189
|
-
task[TaskIndex.slotBuffer] =
|
|
190
|
-
((task[TaskIndex.slotBuffer] & SLOT_META_PACKED_MASK) | slotIndex) >>>
|
|
191
|
-
0;
|
|
192
|
-
tableLength = tl + 1;
|
|
193
|
-
usedBits |= freeBit;
|
|
194
|
-
hostLast ^= freeBit;
|
|
195
|
-
return slotIndex;
|
|
196
|
-
}
|
|
197
|
-
const last = sai[tl - 1];
|
|
198
|
-
const lastStart = last & START_MASK;
|
|
199
|
-
const newStart = (lastStart + (sz[last & SLOT_MASK] >>> 0)) >>> 0;
|
|
200
|
-
sai[tl] = (newStart | slotIndex) >>> 0;
|
|
201
|
-
sz[slotIndex] = size;
|
|
202
|
-
task[TaskIndex.Start] = newStart;
|
|
203
|
-
task[TaskIndex.slotBuffer] =
|
|
204
|
-
((task[TaskIndex.slotBuffer] & SLOT_META_PACKED_MASK) | slotIndex) >>>
|
|
205
|
-
0;
|
|
206
|
-
tableLength = tl + 1;
|
|
207
|
-
usedBits |= freeBit;
|
|
208
|
-
hostLast ^= freeBit;
|
|
209
|
-
return slotIndex;
|
|
210
|
-
}
|
|
211
|
-
if (tl === 0) {
|
|
212
|
-
sai[0] = slotIndex;
|
|
213
|
-
sz[slotIndex] = size;
|
|
214
|
-
task[TaskIndex.Start] = 0;
|
|
215
|
-
task[TaskIndex.slotBuffer] =
|
|
216
|
-
((task[TaskIndex.slotBuffer] & SLOT_META_PACKED_MASK) | slotIndex) >>>
|
|
217
|
-
0;
|
|
218
|
-
tableLength = 1;
|
|
219
|
-
usedBits |= freeBit;
|
|
220
|
-
hostLast ^= freeBit;
|
|
221
|
-
return slotIndex;
|
|
119
|
+
previousEnd = (start + (size64bit[value & SLOT_MASK] >>> 0)) >>> 0;
|
|
222
120
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
sai[i] = sai[i - 1];
|
|
226
|
-
sai[insertAt] = (insertStart | slotIndex) >>> 0;
|
|
227
|
-
sz[slotIndex] = size;
|
|
228
|
-
task[TaskIndex.Start] = insertStart;
|
|
229
|
-
task[TaskIndex.slotBuffer] =
|
|
230
|
-
((task[TaskIndex.slotBuffer] & SLOT_META_PACKED_MASK) | slotIndex) >>>
|
|
231
|
-
0;
|
|
232
|
-
tableLength = tl + 1;
|
|
233
|
-
usedBits |= freeBit;
|
|
234
|
-
hostLast ^= freeBit;
|
|
235
|
-
return slotIndex;
|
|
121
|
+
for (let at = tableLength; at > insertAt; at--) {
|
|
122
|
+
startAndIndex[at] = startAndIndex[at - 1];
|
|
236
123
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
usedBits |= freeBit;
|
|
244
|
-
hostLast ^= freeBit;
|
|
245
|
-
return slotIndex;
|
|
124
|
+
startAndIndex[insertAt] = (previousEnd | slot) >>> 0;
|
|
125
|
+
size64bit[slot] = size >>> 0;
|
|
126
|
+
startBySlot[slot] = previousEnd >>> 0;
|
|
127
|
+
tableLength++;
|
|
128
|
+
reserveSlot(slot);
|
|
129
|
+
return slot;
|
|
246
130
|
};
|
|
131
|
+
/**
|
|
132
|
+
* Reserve a region for `payloadLen` bytes without a task to stamp it into.
|
|
133
|
+
* Returns the region index, or -1 when every identity is taken. Its offset is
|
|
134
|
+
* `regionStart(slot)`.
|
|
135
|
+
*/
|
|
136
|
+
const allocRegion = (payloadLen) => findAndInsert(((payloadLen | 0) + 63) & ~63);
|
|
137
|
+
/** Byte offset of a region reserved by `allocRegion` or `allocTask`. */
|
|
138
|
+
const regionStart = (slot) => startBySlot[slot & SLOT_MASK];
|
|
247
139
|
const allocTask = (task) => {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
const slotIndex = findAndInsert(task, size);
|
|
251
|
-
if (slotIndex === -1)
|
|
140
|
+
const slot = findAndInsert(((task[TaskIndex.PayloadLen] | 0) + 63) & ~63);
|
|
141
|
+
if (slot === -1)
|
|
252
142
|
return -1;
|
|
253
|
-
|
|
254
|
-
|
|
143
|
+
task[TaskIndex.Start] = startBySlot[slot];
|
|
144
|
+
task[TaskIndex.slotBuffer] = ((task[TaskIndex.slotBuffer] & SLOT_META_PACKED_MASK) |
|
|
145
|
+
(slot & TASK_SLOT_INDEX_MASK)) >>> 0;
|
|
146
|
+
tagTaskSlot(task, slot);
|
|
147
|
+
return slot;
|
|
255
148
|
};
|
|
256
149
|
const setSlotLength = (slotIndex, payloadLen) => {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
//if ((usedBits & bit) === 0) return false;
|
|
260
|
-
//const current = size64bit[slotIndex] >>> 0;
|
|
261
|
-
const aligned = ((payloadLen | 0) + 63) & ~63;
|
|
262
|
-
//if (aligned < 0) return false;
|
|
263
|
-
//if ((aligned >>> 0) > current) return false;
|
|
264
|
-
size64bit[slotIndex] = aligned >>> 0;
|
|
150
|
+
const slot = slotIndex & SLOT_MASK;
|
|
151
|
+
size64bit[slot] = (((payloadLen | 0) + 63) & ~63) >>> 0;
|
|
265
152
|
return true;
|
|
266
153
|
};
|
|
267
|
-
//
|
|
268
|
-
// work stealing: the encode-side rollback (`failDynamicWriteAfterReserve`)
|
|
269
|
-
// and the decode-side release (`freeTaskSlot`). Each held a private
|
|
270
|
-
// `workerLast` shadow and blind-stored the whole word, so one overwrote the
|
|
271
|
-
// other's toggles — the allocator then handed out a region that was still
|
|
272
|
-
// live, aliasing payloads. XOR of distinct bits commutes, so one atomic
|
|
273
|
-
// toggle per free is correct for any number of writers.
|
|
154
|
+
// XOR preserves independent release toggles from encode and decode paths.
|
|
274
155
|
const free = (index) => {
|
|
275
|
-
|
|
156
|
+
const slot = index & SLOT_MASK;
|
|
157
|
+
a_xor(workerBits, slotWord(slot), slotBit(slot));
|
|
276
158
|
};
|
|
277
159
|
return {
|
|
278
160
|
allocTask,
|
|
161
|
+
allocRegion,
|
|
162
|
+
regionStart,
|
|
279
163
|
setSlotLength,
|
|
164
|
+
tagTaskSlot,
|
|
280
165
|
lockSAB,
|
|
281
166
|
free,
|
|
282
167
|
hostBits,
|
|
@@ -10,6 +10,13 @@ export declare const createSharedDynamicBufferIO: ({ sab, payloadConfig, textCom
|
|
|
10
10
|
payloadConfig?: PayloadBufferOptions;
|
|
11
11
|
textCompat?: SharedBufferTextCompat;
|
|
12
12
|
}) => {
|
|
13
|
+
ensureCapacity: (neededBytes: number) => boolean;
|
|
14
|
+
syncGrowth: () => void;
|
|
15
|
+
capacityBytes: () => number;
|
|
16
|
+
/** The buffer the dynamic views currently sit on; growth can replace it. */
|
|
17
|
+
currentBuffer: () => SharedBuffer;
|
|
18
|
+
/** Byte offset of region 0 inside that buffer; growth can move it. */
|
|
19
|
+
regionBase: () => number;
|
|
13
20
|
readUtf8: (start: number, end: number) => string;
|
|
14
21
|
writeBinary: (src: Uint8Array, start?: number) => number;
|
|
15
22
|
writeBuffer: (src: Uint8Array, start?: number) => number;
|
|
@@ -127,11 +127,29 @@ export const createSharedDynamicBufferIO = ({ sab, payloadConfig, textCompat, })
|
|
|
127
127
|
let buf = requireBufferView?.(lockSAB, baseByteOffset);
|
|
128
128
|
let f64 = new Float64Array(lockSAB, baseByteOffset + DYNAMIC_HEADER_BYTES, Math.max(0, backingByteLength - DYNAMIC_HEADER_BYTES) >>> 3);
|
|
129
129
|
const capacityBytes = () => backingByteLength - DYNAMIC_HEADER_BYTES;
|
|
130
|
+
const adoptBuffer = () => {
|
|
131
|
+
baseByteOffset = 0;
|
|
132
|
+
backingByteLength = lockSAB.byteLength;
|
|
133
|
+
u8 = new Uint8Array(lockSAB, baseByteOffset + DYNAMIC_HEADER_BYTES, backingByteLength - DYNAMIC_HEADER_BYTES);
|
|
134
|
+
buf = requireBufferView?.(lockSAB, baseByteOffset);
|
|
135
|
+
f64 = new Float64Array(lockSAB, baseByteOffset + DYNAMIC_HEADER_BYTES, (backingByteLength - DYNAMIC_HEADER_BYTES) >>> 3);
|
|
136
|
+
};
|
|
137
|
+
/** Refresh views after the other endpoint grows the shared arena. */
|
|
138
|
+
const syncGrowth = () => {
|
|
139
|
+
if (!canGrow)
|
|
140
|
+
return;
|
|
141
|
+
if (lockSAB.byteLength === backingByteLength)
|
|
142
|
+
return;
|
|
143
|
+
adoptBuffer();
|
|
144
|
+
};
|
|
130
145
|
const ensureCapacity = (neededBytes) => {
|
|
131
146
|
if (capacityBytes() >= neededBytes)
|
|
132
147
|
return true;
|
|
133
148
|
if (!canGrow)
|
|
134
149
|
return false;
|
|
150
|
+
syncGrowth();
|
|
151
|
+
if (capacityBytes() >= neededBytes)
|
|
152
|
+
return true;
|
|
135
153
|
try {
|
|
136
154
|
if (!(lockSAB instanceof SharedArrayBuffer))
|
|
137
155
|
return false;
|
|
@@ -140,11 +158,7 @@ export const createSharedDynamicBufferIO = ({ sab, payloadConfig, textCompat, })
|
|
|
140
158
|
catch {
|
|
141
159
|
return false;
|
|
142
160
|
}
|
|
143
|
-
|
|
144
|
-
backingByteLength = lockSAB.byteLength;
|
|
145
|
-
u8 = new Uint8Array(lockSAB, baseByteOffset + DYNAMIC_HEADER_BYTES, backingByteLength - DYNAMIC_HEADER_BYTES);
|
|
146
|
-
buf = requireBufferView?.(lockSAB, baseByteOffset);
|
|
147
|
-
f64 = new Float64Array(lockSAB, baseByteOffset + DYNAMIC_HEADER_BYTES, (backingByteLength - DYNAMIC_HEADER_BYTES) >>> 3);
|
|
161
|
+
adoptBuffer();
|
|
148
162
|
return true;
|
|
149
163
|
};
|
|
150
164
|
const readUtf8 = (start, end) => {
|
|
@@ -228,6 +242,13 @@ export const createSharedDynamicBufferIO = ({ sab, payloadConfig, textCompat, })
|
|
|
228
242
|
return written;
|
|
229
243
|
};
|
|
230
244
|
return {
|
|
245
|
+
ensureCapacity,
|
|
246
|
+
syncGrowth,
|
|
247
|
+
capacityBytes,
|
|
248
|
+
/** The buffer the dynamic views currently sit on; growth can replace it. */
|
|
249
|
+
currentBuffer: () => lockSAB,
|
|
250
|
+
/** Byte offset of region 0 inside that buffer; growth can move it. */
|
|
251
|
+
regionBase: () => u8.byteOffset,
|
|
231
252
|
readUtf8,
|
|
232
253
|
writeBinary,
|
|
233
254
|
writeBuffer,
|
|
@@ -244,7 +265,7 @@ export const createSharedDynamicBufferIO = ({ sab, payloadConfig, textCompat, })
|
|
|
244
265
|
writeUtf8,
|
|
245
266
|
};
|
|
246
267
|
};
|
|
247
|
-
//
|
|
268
|
+
// Static payload words are stored in eight-byte units.
|
|
248
269
|
export const createSharedStaticBufferIO = ({ headersBuffer, slotStrideU32, textCompat, }) => {
|
|
249
270
|
const bufferCtor = getBufferCtor();
|
|
250
271
|
const buffer = headersBuffer instanceof Uint32Array
|
|
@@ -291,9 +312,14 @@ export const createSharedStaticBufferIO = ({ headersBuffer, slotStrideU32, textC
|
|
|
291
312
|
return out;
|
|
292
313
|
};
|
|
293
314
|
const canWrite = (start, length) => (start | 0) >= 0 && (start + length) <= writableBytes;
|
|
315
|
+
// Reuse one fixed view per slot; writes no longer allocate a subarray.
|
|
316
|
+
const slotUtf8Targets = new Array(LockBound.slots);
|
|
317
|
+
for (let i = 0; i < LockBound.slots; i++) {
|
|
318
|
+
const start = slotByteOffsets[i];
|
|
319
|
+
slotUtf8Targets[i] = baseU8.subarray(start, start + writableBytes);
|
|
320
|
+
}
|
|
294
321
|
const writeUtf8 = (str, at) => {
|
|
295
|
-
const
|
|
296
|
-
const target = baseU8.subarray(start, start + writableBytes);
|
|
322
|
+
const target = slotUtf8Targets[at];
|
|
297
323
|
if (!baseBuf) {
|
|
298
324
|
const { read, written } = sharedBufferEncodeInto(str, target, textCompat);
|
|
299
325
|
if (read !== str.length)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type DenoCompletionDoorbell = {
|
|
2
|
+
/** A process-local callback pointer a Deno thread worker may invoke. */
|
|
3
|
+
pointer: bigint;
|
|
4
|
+
/** Routes a worker lane's ring to its host dispatcher. */
|
|
5
|
+
listen: (lane: number, notify: () => void) => void;
|
|
6
|
+
/** Lets an unclean shutdown exit without invalidating a live worker pointer. */
|
|
7
|
+
unref: () => void;
|
|
8
|
+
/** Invalidates the callback only after every worker that holds it has exited. */
|
|
9
|
+
close: () => void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates Deno's native thread-to-event-loop bridge without loading a custom
|
|
13
|
+
* library. `threadSafe()` is essential: ordinary UnsafeCallbacks may be
|
|
14
|
+
* called cross-thread, but do not wake an idle Deno event loop.
|
|
15
|
+
*
|
|
16
|
+
* The default pool configuration asks Deno for FFI permission once, so the
|
|
17
|
+
* native wake path is used whenever the caller approves it. `--allow-ffi`
|
|
18
|
+
* skips the prompt; `--deny-ffi`, `--no-prompt`, or a rejection preserves the
|
|
19
|
+
* portable polling dispatcher. Set `host.doorbell` to `false` to avoid asking.
|
|
20
|
+
*/
|
|
21
|
+
export declare const createDenoCompletionDoorbell: () => DenoCompletionDoorbell | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Reconstruct a host-owned thread-safe callback in a Deno worker. The pointer
|
|
24
|
+
* stays process-local and is never included in process-worker boot payloads.
|
|
25
|
+
*/
|
|
26
|
+
export declare const createDenoCompletionNotifier: (pointer: bigint | undefined) => ((lane: number) => void) | undefined;
|