bun-shared-memory 0.1.5 → 0.1.7
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/CHANGELOG.md +100 -0
- package/README.md +41 -28
- package/package.json +1 -1
- package/src/growable-buffer.ts +441 -45
- package/src/memory.ts +306 -166
- package/src/refcount.ts +19 -4
- package/src/shared.ts +5 -5
- package/src/worker.ts +60 -3
package/src/memory.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GrowableBuffer,
|
|
3
|
+
SharedBudget,
|
|
4
|
+
nextPowerOfTwo,
|
|
5
|
+
} from "./growable-buffer.ts";
|
|
2
6
|
import { Mutex } from "./mutex.ts";
|
|
3
7
|
|
|
4
8
|
/**
|
|
@@ -30,10 +34,11 @@ import { Mutex } from "./mutex.ts";
|
|
|
30
34
|
* [12..15] reserved
|
|
31
35
|
* then propertyCount * (nameRef(8 bytes) + valueSlot(16 bytes))
|
|
32
36
|
*
|
|
33
|
-
* Index Table entry (
|
|
37
|
+
* Index Table entry (16 bytes):
|
|
34
38
|
* [0..3] key string offset
|
|
35
39
|
* [4..7] key string length
|
|
36
40
|
* [8..11] data offset (into DataTable)
|
|
41
|
+
* [12..15] key version
|
|
37
42
|
*/
|
|
38
43
|
|
|
39
44
|
export const TYPE_NUMBER = 0;
|
|
@@ -51,11 +56,19 @@ const INDEX_ENTRY_SIZE = 16;
|
|
|
51
56
|
const INDEX_HEADER = 4;
|
|
52
57
|
/** Byte offset of the version field within an index entry. */
|
|
53
58
|
const INDEX_VERSION_OFFSET = 12;
|
|
59
|
+
const MAX_INDEX_TABLE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
54
60
|
/** Size of the objectId header at the start of each array block. */
|
|
55
61
|
const ARRAY_HEADER = 4;
|
|
56
62
|
|
|
57
63
|
const DEFAULT_INITIAL = 1024;
|
|
58
64
|
const DEFAULT_MAX = 64 * 1024 * 1024; // 64 MiB
|
|
65
|
+
const DEFAULT_CHUNK_SIZE = 32 * 1024 * 1024; // 32 MiB per chunk
|
|
66
|
+
/** Minimum size of a single chunk (a chunk is the minimum growth step). */
|
|
67
|
+
const MIN_CHUNK_SIZE = 4 * 1024 * 1024; // 4 MiB
|
|
68
|
+
/** Minimum combined memory budget (maxTotalBytes / maxByteLength). */
|
|
69
|
+
const MIN_MAX_MEMORY = 32 * 1024 * 1024; // 32 MiB
|
|
70
|
+
/** Maximum number of chunks per table (chunkSize must be large enough). */
|
|
71
|
+
const MAX_CHUNKS_PER_TABLE = 1024;
|
|
59
72
|
const DEFAULT_INTERN_CAPACITY = 256;
|
|
60
73
|
const DEFAULT_INTERN_MAX = 1 << 16; // 65536 entries
|
|
61
74
|
|
|
@@ -172,22 +185,65 @@ export function decodeSlot(slot: ValueSlot): unknown {
|
|
|
172
185
|
|
|
173
186
|
export interface SharedMemoryOptions {
|
|
174
187
|
initialSize?: number;
|
|
188
|
+
/** Per-buffer maximum byte length (clamped to at least 32 MiB). */
|
|
175
189
|
maxByteLength?: number;
|
|
176
|
-
/**
|
|
190
|
+
/**
|
|
191
|
+
* Total physical memory budget shared by DataTable, StringTable, and
|
|
192
|
+
* ArrayTable (clamped to at least 32 MiB).
|
|
193
|
+
*/
|
|
177
194
|
maxTotalBytes?: number;
|
|
195
|
+
/** Internal worker reconstruction option; normally inferred from buffers. */
|
|
196
|
+
indexTableMaxByteLength?: number;
|
|
197
|
+
/**
|
|
198
|
+
* Size of each growable chunk (the minimum growth step). Default 32 MiB,
|
|
199
|
+
* clamped to at least 4 MiB and raised as needed so no table needs more
|
|
200
|
+
* than 1024 chunks.
|
|
201
|
+
*/
|
|
202
|
+
chunkSize?: number;
|
|
178
203
|
lock?: Mutex;
|
|
179
204
|
internCapacity?: number;
|
|
180
205
|
internMax?: number;
|
|
181
206
|
buffers?: {
|
|
182
|
-
indexTable?: SharedArrayBuffer;
|
|
183
|
-
dataTable?: SharedArrayBuffer;
|
|
184
|
-
stringTable?: SharedArrayBuffer;
|
|
185
|
-
arrayTable?: SharedArrayBuffer;
|
|
207
|
+
indexTable?: SharedArrayBuffer | SharedArrayBuffer[];
|
|
208
|
+
dataTable?: SharedArrayBuffer | SharedArrayBuffer[];
|
|
209
|
+
stringTable?: SharedArrayBuffer | SharedArrayBuffer[];
|
|
210
|
+
arrayTable?: SharedArrayBuffer | SharedArrayBuffer[];
|
|
186
211
|
layoutVersion?: SharedArrayBuffer;
|
|
187
212
|
refCount?: SharedArrayBuffer;
|
|
213
|
+
budget?: SharedArrayBuffer;
|
|
188
214
|
};
|
|
189
215
|
}
|
|
190
216
|
|
|
217
|
+
/** Build a GrowableBuffer from an optional single SAB, chunk list, or nothing. */
|
|
218
|
+
function makeBuffer(
|
|
219
|
+
buf: SharedArrayBuffer | SharedArrayBuffer[] | undefined,
|
|
220
|
+
initial: number,
|
|
221
|
+
maxByteLength: number,
|
|
222
|
+
budget?: SharedBudget,
|
|
223
|
+
chunkSize?: number,
|
|
224
|
+
singleBuffer = false,
|
|
225
|
+
chargeInitial = true,
|
|
226
|
+
): GrowableBuffer {
|
|
227
|
+
if (buf === undefined) {
|
|
228
|
+
return new GrowableBuffer(
|
|
229
|
+
initial,
|
|
230
|
+
maxByteLength,
|
|
231
|
+
budget,
|
|
232
|
+
chunkSize,
|
|
233
|
+
singleBuffer,
|
|
234
|
+
chargeInitial,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return new GrowableBuffer(
|
|
238
|
+
buf,
|
|
239
|
+
maxByteLength,
|
|
240
|
+
budget,
|
|
241
|
+
chunkSize,
|
|
242
|
+
singleBuffer,
|
|
243
|
+
chargeInitial,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
191
247
|
/**
|
|
192
248
|
* Manages the four shared buffers and provides operations to store and read
|
|
193
249
|
* strings, arrays, and objects.
|
|
@@ -197,6 +253,12 @@ export class SharedMemory {
|
|
|
197
253
|
readonly dataTable: GrowableBuffer;
|
|
198
254
|
readonly stringTable: GrowableBuffer;
|
|
199
255
|
readonly arrayTable: GrowableBuffer;
|
|
256
|
+
/** Per-buffer maximum byte length (clamped to at least 32 MiB). */
|
|
257
|
+
readonly maxByteLength: number;
|
|
258
|
+
private readonly indexTableMaxByteLength: number;
|
|
259
|
+
/** Combined memory budget for Data/String/Array tables, or null if unset. */
|
|
260
|
+
readonly maxTotalBytes: number | null;
|
|
261
|
+
private readonly budgetBuffer: SharedArrayBuffer | null;
|
|
200
262
|
/**
|
|
201
263
|
* Cached Int32Array view over the Index Table, used to read/increment each
|
|
202
264
|
* key's version cell (stored as the 4th field of each index entry). Rebuilt
|
|
@@ -246,44 +308,86 @@ export class SharedMemory {
|
|
|
246
308
|
|
|
247
309
|
constructor(options?: SharedMemoryOptions) {
|
|
248
310
|
const initial = options?.initialSize ?? DEFAULT_INITIAL;
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
311
|
+
// `maxTotalBytes` caps the combined physical size of DataTable,
|
|
312
|
+
// StringTable, and ArrayTable. IndexTable has its independent 4 GiB cap.
|
|
313
|
+
// Without maxTotalBytes, each buffer grows up to its own maxByteLength.
|
|
314
|
+
// The Index Table remains a single growable SharedArrayBuffer and is
|
|
315
|
+
// capped at 4 GiB. The other tables use chunks and can exceed that limit.
|
|
254
316
|
const maxTotal = options?.maxTotalBytes;
|
|
255
|
-
|
|
256
|
-
|
|
317
|
+
if (
|
|
318
|
+
maxTotal !== undefined &&
|
|
319
|
+
(!Number.isSafeInteger(maxTotal) || maxTotal < 0)
|
|
320
|
+
) {
|
|
321
|
+
throw new RangeError("maxTotalBytes must be a non-negative safe integer");
|
|
322
|
+
}
|
|
323
|
+
// Enforce the minimum memory budget: maxTotalBytes / maxByteLength are
|
|
324
|
+
// never below 32 MiB.
|
|
325
|
+
const perBufferMax = Math.max(
|
|
257
326
|
maxTotal ?? options?.maxByteLength ?? DEFAULT_MAX,
|
|
327
|
+
MIN_MAX_MEMORY,
|
|
328
|
+
);
|
|
329
|
+
const indexTableMax = Math.min(
|
|
330
|
+
options?.indexTableMaxByteLength ?? options?.maxByteLength ?? DEFAULT_MAX,
|
|
331
|
+
MAX_INDEX_TABLE_BYTES,
|
|
332
|
+
);
|
|
333
|
+
// Enforce the chunk-size limits: at least 4 MiB, and large enough that no
|
|
334
|
+
// table needs more than MAX_CHUNKS_PER_TABLE chunks.
|
|
335
|
+
const userChunkSize = options?.chunkSize ?? DEFAULT_CHUNK_SIZE;
|
|
336
|
+
const chunkSize = Math.max(
|
|
337
|
+
userChunkSize,
|
|
338
|
+
MIN_CHUNK_SIZE,
|
|
339
|
+
nextPowerOfTwo(perBufferMax / MAX_CHUNKS_PER_TABLE),
|
|
258
340
|
);
|
|
259
|
-
const budget =
|
|
260
|
-
maxTotal !== undefined ? new SharedBudget(maxTotal) : undefined;
|
|
261
341
|
const buffers = options?.buffers;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
342
|
+
// The budget is the clamped maxTotalBytes (never below MIN_MAX_MEMORY), so
|
|
343
|
+
// it stays consistent with perBufferMax.
|
|
344
|
+
const budgetMax = maxTotal !== undefined ? perBufferMax : undefined;
|
|
345
|
+
const budgetBuffer = buffers?.budget;
|
|
346
|
+
const budget =
|
|
347
|
+
budgetMax !== undefined
|
|
348
|
+
? new SharedBudget(budgetMax, budgetBuffer)
|
|
349
|
+
: undefined;
|
|
350
|
+
const chargeInitial = budgetBuffer === undefined;
|
|
351
|
+
this.maxByteLength = perBufferMax;
|
|
352
|
+
this.indexTableMaxByteLength = indexTableMax;
|
|
353
|
+
this.maxTotalBytes = budgetMax ?? null;
|
|
354
|
+
this.budgetBuffer = budget?.buffer ?? null;
|
|
355
|
+
this.indexTable = makeBuffer(
|
|
356
|
+
buffers?.indexTable,
|
|
357
|
+
initial,
|
|
358
|
+
indexTableMax,
|
|
359
|
+
undefined,
|
|
360
|
+
chunkSize,
|
|
361
|
+
true,
|
|
267
362
|
);
|
|
268
|
-
this.dataTable =
|
|
269
|
-
buffers?.dataTable
|
|
270
|
-
|
|
363
|
+
this.dataTable = makeBuffer(
|
|
364
|
+
buffers?.dataTable,
|
|
365
|
+
initial,
|
|
271
366
|
perBufferMax,
|
|
272
367
|
budget,
|
|
368
|
+
chunkSize,
|
|
369
|
+
false,
|
|
370
|
+
chargeInitial,
|
|
273
371
|
);
|
|
274
|
-
this.stringTable =
|
|
275
|
-
buffers?.stringTable
|
|
276
|
-
|
|
372
|
+
this.stringTable = makeBuffer(
|
|
373
|
+
buffers?.stringTable,
|
|
374
|
+
initial,
|
|
277
375
|
perBufferMax,
|
|
278
376
|
budget,
|
|
377
|
+
chunkSize,
|
|
378
|
+
false,
|
|
379
|
+
chargeInitial,
|
|
279
380
|
);
|
|
280
|
-
this.arrayTable =
|
|
281
|
-
buffers?.arrayTable
|
|
282
|
-
|
|
381
|
+
this.arrayTable = makeBuffer(
|
|
382
|
+
buffers?.arrayTable,
|
|
383
|
+
initial,
|
|
283
384
|
perBufferMax,
|
|
284
385
|
budget,
|
|
386
|
+
chunkSize,
|
|
387
|
+
false,
|
|
388
|
+
chargeInitial,
|
|
285
389
|
);
|
|
286
|
-
this.versionView = new Int32Array(this.indexTable.
|
|
390
|
+
this.versionView = new Int32Array(this.indexTable.buffers[0]!);
|
|
287
391
|
this.layoutVersion = new Int32Array(
|
|
288
392
|
buffers?.layoutVersion ??
|
|
289
393
|
new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT),
|
|
@@ -299,6 +403,24 @@ export class SharedMemory {
|
|
|
299
403
|
return this.lock.withLock(fn);
|
|
300
404
|
}
|
|
301
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Apply a buffer update received from the main thread after a
|
|
408
|
+
* defragmentation pass. Replaces each table's buffers and rebuilds the
|
|
409
|
+
* version view. Called by workers on the `__chunkUpdate` message.
|
|
410
|
+
*/
|
|
411
|
+
applyChunkUpdate(buffers: {
|
|
412
|
+
indexTable: SharedArrayBuffer[];
|
|
413
|
+
dataTable: SharedArrayBuffer[];
|
|
414
|
+
stringTable: SharedArrayBuffer[];
|
|
415
|
+
arrayTable: SharedArrayBuffer[];
|
|
416
|
+
}): void {
|
|
417
|
+
this.indexTable.applyChunks(buffers.indexTable);
|
|
418
|
+
this.dataTable.applyChunks(buffers.dataTable);
|
|
419
|
+
this.stringTable.applyChunks(buffers.stringTable);
|
|
420
|
+
this.arrayTable.applyChunks(buffers.arrayTable);
|
|
421
|
+
this.versionView = new Int32Array(this.indexTable.buffers[0]!);
|
|
422
|
+
}
|
|
423
|
+
|
|
302
424
|
/**
|
|
303
425
|
* Attach the reference-counting buffer (5th SAB) to this memory so it is
|
|
304
426
|
* included in `exportBuffers()`. No-op if a buffer is already attached.
|
|
@@ -313,24 +435,37 @@ export class SharedMemory {
|
|
|
313
435
|
/**
|
|
314
436
|
* Export all shared buffers (including the lock) so they can be sent to a
|
|
315
437
|
* worker via `postMessage` and reconstructed there with `fromBuffers()`.
|
|
438
|
+
*
|
|
439
|
+
* Tables are exported as lists for the chunked buffers. The Index Table is
|
|
440
|
+
* a single growable SharedArrayBuffer, exported as a one-element list. The
|
|
441
|
+
* optional shared budget counter is included so workers reserve atomically
|
|
442
|
+
* against the same three-table limit.
|
|
316
443
|
*/
|
|
317
444
|
exportBuffers(): {
|
|
318
|
-
indexTable: SharedArrayBuffer;
|
|
319
|
-
dataTable: SharedArrayBuffer;
|
|
320
|
-
stringTable: SharedArrayBuffer;
|
|
321
|
-
arrayTable: SharedArrayBuffer;
|
|
445
|
+
indexTable: SharedArrayBuffer[];
|
|
446
|
+
dataTable: SharedArrayBuffer[];
|
|
447
|
+
stringTable: SharedArrayBuffer[];
|
|
448
|
+
arrayTable: SharedArrayBuffer[];
|
|
322
449
|
layoutVersion: SharedArrayBuffer;
|
|
323
450
|
lock: SharedArrayBuffer;
|
|
324
451
|
refCount: SharedArrayBuffer | null;
|
|
452
|
+
maxByteLength: number;
|
|
453
|
+
indexTableMaxByteLength: number;
|
|
454
|
+
maxTotalBytes: number | null;
|
|
455
|
+
budget: SharedArrayBuffer | null;
|
|
325
456
|
} {
|
|
326
457
|
return {
|
|
327
|
-
indexTable: this.indexTable.
|
|
328
|
-
dataTable: this.dataTable.
|
|
329
|
-
stringTable: this.stringTable.
|
|
330
|
-
arrayTable: this.arrayTable.
|
|
458
|
+
indexTable: this.indexTable.buffers,
|
|
459
|
+
dataTable: this.dataTable.buffers,
|
|
460
|
+
stringTable: this.stringTable.buffers,
|
|
461
|
+
arrayTable: this.arrayTable.buffers,
|
|
331
462
|
layoutVersion: this.layoutVersion.buffer as SharedArrayBuffer,
|
|
332
463
|
lock: this.lock.buffer as SharedArrayBuffer,
|
|
333
464
|
refCount: this.refCountBuffer,
|
|
465
|
+
maxByteLength: this.maxByteLength,
|
|
466
|
+
indexTableMaxByteLength: this.indexTableMaxByteLength,
|
|
467
|
+
maxTotalBytes: this.maxTotalBytes,
|
|
468
|
+
budget: this.budgetBuffer,
|
|
334
469
|
};
|
|
335
470
|
}
|
|
336
471
|
|
|
@@ -339,15 +474,29 @@ export class SharedMemory {
|
|
|
339
474
|
* `exportBuffers()` (e.g. received from the main thread in a worker).
|
|
340
475
|
*/
|
|
341
476
|
static fromBuffers(buffers: {
|
|
342
|
-
indexTable: SharedArrayBuffer;
|
|
343
|
-
dataTable: SharedArrayBuffer;
|
|
344
|
-
stringTable: SharedArrayBuffer;
|
|
345
|
-
arrayTable: SharedArrayBuffer;
|
|
477
|
+
indexTable: SharedArrayBuffer | SharedArrayBuffer[];
|
|
478
|
+
dataTable: SharedArrayBuffer | SharedArrayBuffer[];
|
|
479
|
+
stringTable: SharedArrayBuffer | SharedArrayBuffer[];
|
|
480
|
+
arrayTable: SharedArrayBuffer | SharedArrayBuffer[];
|
|
346
481
|
layoutVersion: SharedArrayBuffer;
|
|
347
482
|
lock: SharedArrayBuffer;
|
|
348
483
|
refCount?: SharedArrayBuffer | null;
|
|
484
|
+
maxByteLength?: number;
|
|
485
|
+
indexTableMaxByteLength?: number;
|
|
486
|
+
maxTotalBytes?: number | null;
|
|
487
|
+
budget?: SharedArrayBuffer | null;
|
|
349
488
|
}): SharedMemory {
|
|
489
|
+
const indexBuffer = Array.isArray(buffers.indexTable)
|
|
490
|
+
? buffers.indexTable[0]
|
|
491
|
+
: buffers.indexTable;
|
|
350
492
|
return new SharedMemory({
|
|
493
|
+
maxByteLength: buffers.maxByteLength ?? DEFAULT_MAX,
|
|
494
|
+
indexTableMaxByteLength:
|
|
495
|
+
buffers.indexTableMaxByteLength ??
|
|
496
|
+
indexBuffer?.maxByteLength ??
|
|
497
|
+
indexBuffer?.byteLength ??
|
|
498
|
+
DEFAULT_MAX,
|
|
499
|
+
maxTotalBytes: buffers.maxTotalBytes ?? undefined,
|
|
351
500
|
buffers: {
|
|
352
501
|
indexTable: buffers.indexTable,
|
|
353
502
|
dataTable: buffers.dataTable,
|
|
@@ -355,6 +504,7 @@ export class SharedMemory {
|
|
|
355
504
|
arrayTable: buffers.arrayTable,
|
|
356
505
|
layoutVersion: buffers.layoutVersion,
|
|
357
506
|
refCount: buffers.refCount ?? undefined,
|
|
507
|
+
budget: buffers.budget ?? undefined,
|
|
358
508
|
},
|
|
359
509
|
lock: new Mutex(buffers.lock),
|
|
360
510
|
});
|
|
@@ -393,10 +543,7 @@ export class SharedMemory {
|
|
|
393
543
|
const bytes = this.encoder.encode(value);
|
|
394
544
|
const offset = this.stringTable.byteLength;
|
|
395
545
|
this.stringTable.ensureCapacity(offset + bytes.byteLength);
|
|
396
|
-
|
|
397
|
-
for (let i = 0; i < bytes.byteLength; i++) {
|
|
398
|
-
view.setUint8(offset + i, bytes[i]!);
|
|
399
|
-
}
|
|
546
|
+
this.stringTable.writeBytes(offset, bytes);
|
|
400
547
|
this.stringTable.advance(bytes.byteLength);
|
|
401
548
|
const ref: StringRef = { offset, length: bytes.byteLength };
|
|
402
549
|
|
|
@@ -431,12 +578,7 @@ export class SharedMemory {
|
|
|
431
578
|
|
|
432
579
|
/** Read a string from the StringTable by reference. */
|
|
433
580
|
readString(ref: StringRef): string {
|
|
434
|
-
const
|
|
435
|
-
const bytes = new Uint8Array(
|
|
436
|
-
this.stringTable.buffer,
|
|
437
|
-
ref.offset,
|
|
438
|
-
ref.length,
|
|
439
|
-
);
|
|
581
|
+
const bytes = this.stringTable.readBytes(ref.offset, ref.length);
|
|
440
582
|
return this.decoder.decode(bytes);
|
|
441
583
|
}
|
|
442
584
|
|
|
@@ -458,11 +600,14 @@ export class SharedMemory {
|
|
|
458
600
|
const cap = Math.max(capacity ?? 0, count, 1);
|
|
459
601
|
const offset = this.arrayTable.byteLength;
|
|
460
602
|
this.arrayTable.ensureCapacity(offset + ARRAY_HEADER + cap * SLOT_SIZE);
|
|
461
|
-
const view = this.arrayTable.getView();
|
|
462
603
|
const objectId = this.nextObjectId++;
|
|
463
|
-
|
|
604
|
+
this.arrayTable.setInt32(offset, objectId);
|
|
464
605
|
for (let i = 0; i < count; i++) {
|
|
465
|
-
writeSlot(
|
|
606
|
+
writeSlot(
|
|
607
|
+
this.arrayTable,
|
|
608
|
+
offset + ARRAY_HEADER + i * SLOT_SIZE,
|
|
609
|
+
slots[i]!,
|
|
610
|
+
);
|
|
466
611
|
}
|
|
467
612
|
this.arrayTable.advance(ARRAY_HEADER + cap * SLOT_SIZE);
|
|
468
613
|
return { offset, count, capacity: cap };
|
|
@@ -470,10 +615,11 @@ export class SharedMemory {
|
|
|
470
615
|
|
|
471
616
|
/** Read an array of value slots from the ArrayTable by reference. */
|
|
472
617
|
readArray(ref: ArrayRef): ValueSlot[] {
|
|
473
|
-
const view = this.arrayTable.getView();
|
|
474
618
|
const slots: ValueSlot[] = [];
|
|
475
619
|
for (let i = 0; i < ref.count; i++) {
|
|
476
|
-
slots.push(
|
|
620
|
+
slots.push(
|
|
621
|
+
readSlot(this.arrayTable, ref.offset + ARRAY_HEADER + i * SLOT_SIZE),
|
|
622
|
+
);
|
|
477
623
|
}
|
|
478
624
|
return slots;
|
|
479
625
|
}
|
|
@@ -481,7 +627,7 @@ export class SharedMemory {
|
|
|
481
627
|
/** Update a single value slot of an array in the ArrayTable in place. */
|
|
482
628
|
updateArraySlot(ref: ArrayRef, index: number, slot: ValueSlot): void {
|
|
483
629
|
writeSlot(
|
|
484
|
-
this.arrayTable
|
|
630
|
+
this.arrayTable,
|
|
485
631
|
ref.offset + ARRAY_HEADER + index * SLOT_SIZE,
|
|
486
632
|
slot,
|
|
487
633
|
);
|
|
@@ -490,14 +636,14 @@ export class SharedMemory {
|
|
|
490
636
|
/** Read a single value slot of an array from the ArrayTable. */
|
|
491
637
|
readArraySlot(ref: ArrayRef, index: number): ValueSlot {
|
|
492
638
|
return readSlot(
|
|
493
|
-
this.arrayTable
|
|
639
|
+
this.arrayTable,
|
|
494
640
|
ref.offset + ARRAY_HEADER + index * SLOT_SIZE,
|
|
495
641
|
);
|
|
496
642
|
}
|
|
497
643
|
|
|
498
644
|
/** Read the stable objectId of an array block in the ArrayTable. */
|
|
499
645
|
getArrayObjectId(offset: number): number {
|
|
500
|
-
return this.arrayTable.
|
|
646
|
+
return this.arrayTable.getInt32(offset);
|
|
501
647
|
}
|
|
502
648
|
|
|
503
649
|
/**
|
|
@@ -510,14 +656,13 @@ export class SharedMemory {
|
|
|
510
656
|
this.arrayTable.ensureCapacity(
|
|
511
657
|
newOffset + ARRAY_HEADER + newCapacity * SLOT_SIZE,
|
|
512
658
|
);
|
|
513
|
-
const view = this.arrayTable.getView();
|
|
514
659
|
const objectId = this.getArrayObjectId(ref.offset);
|
|
515
|
-
|
|
660
|
+
this.arrayTable.setInt32(newOffset, objectId);
|
|
516
661
|
for (let i = 0; i < ref.count; i++) {
|
|
517
662
|
writeSlot(
|
|
518
|
-
|
|
663
|
+
this.arrayTable,
|
|
519
664
|
newOffset + ARRAY_HEADER + i * SLOT_SIZE,
|
|
520
|
-
readSlot(
|
|
665
|
+
readSlot(this.arrayTable, ref.offset + ARRAY_HEADER + i * SLOT_SIZE),
|
|
521
666
|
);
|
|
522
667
|
}
|
|
523
668
|
this.arrayTable.advance(ARRAY_HEADER + newCapacity * SLOT_SIZE);
|
|
@@ -533,7 +678,7 @@ export class SharedMemory {
|
|
|
533
678
|
appendArraySlot(ref: ArrayRef, slot: ValueSlot): ArrayRef {
|
|
534
679
|
if (ref.count < ref.capacity) {
|
|
535
680
|
writeSlot(
|
|
536
|
-
this.arrayTable
|
|
681
|
+
this.arrayTable,
|
|
537
682
|
ref.offset + ARRAY_HEADER + ref.count * SLOT_SIZE,
|
|
538
683
|
slot,
|
|
539
684
|
);
|
|
@@ -545,7 +690,7 @@ export class SharedMemory {
|
|
|
545
690
|
}
|
|
546
691
|
const moved = this.moveArray(ref, Math.max(ref.capacity * 2, 1));
|
|
547
692
|
writeSlot(
|
|
548
|
-
this.arrayTable
|
|
693
|
+
this.arrayTable,
|
|
549
694
|
moved.offset + ARRAY_HEADER + moved.count * SLOT_SIZE,
|
|
550
695
|
slot,
|
|
551
696
|
);
|
|
@@ -564,19 +709,19 @@ export class SharedMemory {
|
|
|
564
709
|
storeValueSlot(slot: ValueSlot): number {
|
|
565
710
|
const offset = this.dataTable.byteLength;
|
|
566
711
|
this.dataTable.ensureCapacity(offset + SLOT_SIZE);
|
|
567
|
-
writeSlot(this.dataTable
|
|
712
|
+
writeSlot(this.dataTable, offset, slot);
|
|
568
713
|
this.dataTable.advance(SLOT_SIZE);
|
|
569
714
|
return offset;
|
|
570
715
|
}
|
|
571
716
|
|
|
572
717
|
/** Read a single value slot from the DataTable. */
|
|
573
718
|
readValueSlot(offset: number): ValueSlot {
|
|
574
|
-
return readSlot(this.dataTable
|
|
719
|
+
return readSlot(this.dataTable, offset);
|
|
575
720
|
}
|
|
576
721
|
|
|
577
722
|
/** Update a single value slot in the DataTable. */
|
|
578
723
|
updateValueSlot(offset: number, slot: ValueSlot): void {
|
|
579
|
-
writeSlot(this.dataTable
|
|
724
|
+
writeSlot(this.dataTable, offset, slot);
|
|
580
725
|
}
|
|
581
726
|
|
|
582
727
|
/**
|
|
@@ -590,20 +735,19 @@ export class SharedMemory {
|
|
|
590
735
|
OBJECT_HEADER + properties.length * (NAME_REF_SIZE + SLOT_SIZE);
|
|
591
736
|
const offset = this.dataTable.byteLength;
|
|
592
737
|
this.dataTable.ensureCapacity(offset + blockSize);
|
|
593
|
-
const view = this.dataTable.getView();
|
|
594
738
|
|
|
595
739
|
const objectId = this.nextObjectId++;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
740
|
+
this.dataTable.setInt32(offset, TYPE_OBJECT);
|
|
741
|
+
this.dataTable.setInt32(offset + 4, properties.length);
|
|
742
|
+
this.dataTable.setInt32(offset + 8, objectId);
|
|
599
743
|
|
|
600
744
|
let cursor = offset + OBJECT_HEADER;
|
|
601
745
|
for (const [name, slot] of properties) {
|
|
602
746
|
const nameRef = this.storeString(name);
|
|
603
|
-
|
|
604
|
-
|
|
747
|
+
this.dataTable.setInt32(cursor, nameRef.offset);
|
|
748
|
+
this.dataTable.setInt32(cursor + 4, nameRef.length);
|
|
605
749
|
cursor += NAME_REF_SIZE;
|
|
606
|
-
writeSlot(
|
|
750
|
+
writeSlot(this.dataTable, cursor, slot);
|
|
607
751
|
cursor += SLOT_SIZE;
|
|
608
752
|
}
|
|
609
753
|
this.dataTable.advance(blockSize);
|
|
@@ -612,16 +756,15 @@ export class SharedMemory {
|
|
|
612
756
|
|
|
613
757
|
/** Read an object's properties from the DataTable. */
|
|
614
758
|
readObject(offset: number): Array<[string, ValueSlot]> {
|
|
615
|
-
const
|
|
616
|
-
const count = view.getInt32(offset + 4, true);
|
|
759
|
+
const count = this.dataTable.getInt32(offset + 4);
|
|
617
760
|
const properties: Array<[string, ValueSlot]> = [];
|
|
618
761
|
let cursor = offset + OBJECT_HEADER;
|
|
619
762
|
for (let i = 0; i < count; i++) {
|
|
620
|
-
const nameOffset =
|
|
621
|
-
const nameLength =
|
|
763
|
+
const nameOffset = this.dataTable.getInt32(cursor);
|
|
764
|
+
const nameLength = this.dataTable.getInt32(cursor + 4);
|
|
622
765
|
const name = this.readString({ offset: nameOffset, length: nameLength });
|
|
623
766
|
cursor += NAME_REF_SIZE;
|
|
624
|
-
const slot = readSlot(
|
|
767
|
+
const slot = readSlot(this.dataTable, cursor);
|
|
625
768
|
cursor += SLOT_SIZE;
|
|
626
769
|
properties.push([name, slot]);
|
|
627
770
|
}
|
|
@@ -630,7 +773,7 @@ export class SharedMemory {
|
|
|
630
773
|
|
|
631
774
|
/** Read the stable objectId of an object block in the DataTable. */
|
|
632
775
|
getObjectId(offset: number): number {
|
|
633
|
-
return this.dataTable.
|
|
776
|
+
return this.dataTable.getInt32(offset + 8);
|
|
634
777
|
}
|
|
635
778
|
|
|
636
779
|
/**
|
|
@@ -647,14 +790,13 @@ export class SharedMemory {
|
|
|
647
790
|
getObjectLayout(key: string): ObjectLayout | null {
|
|
648
791
|
const offset = this.lookup(key);
|
|
649
792
|
if (offset === null) return null;
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const count = view.getInt32(offset + 4, true);
|
|
793
|
+
if (this.dataTable.getInt32(offset) !== TYPE_OBJECT) return null;
|
|
794
|
+
const count = this.dataTable.getInt32(offset + 4);
|
|
653
795
|
const properties = new Map<string, number>();
|
|
654
796
|
let cursor = offset + OBJECT_HEADER;
|
|
655
797
|
for (let i = 0; i < count; i++) {
|
|
656
|
-
const nameOffset =
|
|
657
|
-
const nameLength =
|
|
798
|
+
const nameOffset = this.dataTable.getInt32(cursor);
|
|
799
|
+
const nameLength = this.dataTable.getInt32(cursor + 4);
|
|
658
800
|
const name = this.readString({ offset: nameOffset, length: nameLength });
|
|
659
801
|
cursor += NAME_REF_SIZE;
|
|
660
802
|
properties.set(name, cursor);
|
|
@@ -669,13 +811,12 @@ export class SharedMemory {
|
|
|
669
811
|
propertyIndex: number,
|
|
670
812
|
slot: ValueSlot,
|
|
671
813
|
): void {
|
|
672
|
-
const view = this.dataTable.getView();
|
|
673
814
|
const slotOffset =
|
|
674
815
|
offset +
|
|
675
816
|
OBJECT_HEADER +
|
|
676
817
|
propertyIndex * (NAME_REF_SIZE + SLOT_SIZE) +
|
|
677
818
|
NAME_REF_SIZE;
|
|
678
|
-
writeSlot(
|
|
819
|
+
writeSlot(this.dataTable, slotOffset, slot);
|
|
679
820
|
}
|
|
680
821
|
|
|
681
822
|
// ---------------------------------------------------------------------------
|
|
@@ -691,11 +832,10 @@ export class SharedMemory {
|
|
|
691
832
|
|
|
692
833
|
/** Read the DataTable offset for a known stable index, or null if absent. */
|
|
693
834
|
lookupByIndex(index: number): number | null {
|
|
694
|
-
const
|
|
695
|
-
const count = view.getInt32(0, true);
|
|
835
|
+
const count = this.indexTable.getInt32(0);
|
|
696
836
|
if (index >= count) return null;
|
|
697
837
|
const entryOffset = INDEX_HEADER + index * INDEX_ENTRY_SIZE;
|
|
698
|
-
return
|
|
838
|
+
return this.indexTable.getInt32(entryOffset + 8);
|
|
699
839
|
}
|
|
700
840
|
|
|
701
841
|
/**
|
|
@@ -706,12 +846,11 @@ export class SharedMemory {
|
|
|
706
846
|
* used to address the key's version cell.
|
|
707
847
|
*/
|
|
708
848
|
lookupIndex(key: string): number | null {
|
|
709
|
-
const
|
|
710
|
-
const count = view.getInt32(0, true);
|
|
849
|
+
const count = this.indexTable.getInt32(0);
|
|
711
850
|
for (let i = 0; i < count; i++) {
|
|
712
851
|
const entryOffset = INDEX_HEADER + i * INDEX_ENTRY_SIZE;
|
|
713
|
-
const keyOffset =
|
|
714
|
-
const keyLength =
|
|
852
|
+
const keyOffset = this.indexTable.getInt32(entryOffset);
|
|
853
|
+
const keyLength = this.indexTable.getInt32(entryOffset + 4);
|
|
715
854
|
const storedKey = this.readString({
|
|
716
855
|
offset: keyOffset,
|
|
717
856
|
length: keyLength,
|
|
@@ -736,21 +875,21 @@ export class SharedMemory {
|
|
|
736
875
|
/** Insert a key → dataOffset mapping into the Index Table. */
|
|
737
876
|
insert(key: string, dataOffset: number): void {
|
|
738
877
|
const keyRef = this.storeString(key);
|
|
739
|
-
const
|
|
740
|
-
const count = view.getInt32(0, true);
|
|
878
|
+
const count = this.indexTable.getInt32(0);
|
|
741
879
|
const entryOffset = INDEX_HEADER + count * INDEX_ENTRY_SIZE;
|
|
742
880
|
this.indexTable.ensureCapacity(entryOffset + INDEX_ENTRY_SIZE);
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
881
|
+
this.indexTable.setInt32(entryOffset, keyRef.offset);
|
|
882
|
+
this.indexTable.setInt32(entryOffset + 4, keyRef.length);
|
|
883
|
+
this.indexTable.setInt32(entryOffset + 8, dataOffset);
|
|
746
884
|
// The 4th field is the key's version cell, initialized to 0.
|
|
747
|
-
|
|
748
|
-
|
|
885
|
+
this.indexTable.setInt32(entryOffset + INDEX_VERSION_OFFSET, 0);
|
|
886
|
+
this.indexTable.setInt32(0, count + 1);
|
|
749
887
|
this.indexTable.advance(INDEX_ENTRY_SIZE);
|
|
750
888
|
|
|
751
|
-
// Rebuild the version view
|
|
752
|
-
//
|
|
753
|
-
|
|
889
|
+
// Rebuild the version view over the first chunk (the Index Table is small
|
|
890
|
+
// and stays within the first chunk for typical workloads; version cells
|
|
891
|
+
// beyond the first chunk are not addressable via the single Int32Array).
|
|
892
|
+
this.versionView = new Int32Array(this.indexTable.buffers[0]!);
|
|
754
893
|
}
|
|
755
894
|
|
|
756
895
|
// ---------------------------------------------------------------------------
|
|
@@ -786,7 +925,12 @@ export class SharedMemory {
|
|
|
786
925
|
*
|
|
787
926
|
* Must be called under the shared lock (or on a single thread).
|
|
788
927
|
*/
|
|
789
|
-
defragment():
|
|
928
|
+
defragment(): {
|
|
929
|
+
indexTable: SharedArrayBuffer[];
|
|
930
|
+
dataTable: SharedArrayBuffer[];
|
|
931
|
+
stringTable: SharedArrayBuffer[];
|
|
932
|
+
arrayTable: SharedArrayBuffer[];
|
|
933
|
+
} {
|
|
790
934
|
const stringRemap = new Map<number, number>();
|
|
791
935
|
const arrayRemap = new Map<number, number>();
|
|
792
936
|
const objectRemap = new Map<number, number>();
|
|
@@ -812,9 +956,10 @@ export class SharedMemory {
|
|
|
812
956
|
const markArray = (offset: number, count: number) => {
|
|
813
957
|
if (liveArrays.has(offset)) return;
|
|
814
958
|
liveArrays.add(offset);
|
|
815
|
-
const view = this.arrayTable.getView();
|
|
816
959
|
for (let i = 0; i < count; i++) {
|
|
817
|
-
markValueSlot(
|
|
960
|
+
markValueSlot(
|
|
961
|
+
readSlot(this.arrayTable, offset + ARRAY_HEADER + i * SLOT_SIZE),
|
|
962
|
+
);
|
|
818
963
|
}
|
|
819
964
|
};
|
|
820
965
|
|
|
@@ -822,40 +967,37 @@ export class SharedMemory {
|
|
|
822
967
|
if (liveObjects.has(offset)) return;
|
|
823
968
|
liveObjects.add(offset);
|
|
824
969
|
liveDataOffsets.add(offset);
|
|
825
|
-
const
|
|
826
|
-
const count = view.getInt32(offset + 4, true);
|
|
970
|
+
const count = this.dataTable.getInt32(offset + 4);
|
|
827
971
|
let cursor = offset + OBJECT_HEADER;
|
|
828
972
|
for (let i = 0; i < count; i++) {
|
|
829
|
-
const nameOffset =
|
|
830
|
-
const nameLength =
|
|
973
|
+
const nameOffset = this.dataTable.getInt32(cursor);
|
|
974
|
+
const nameLength = this.dataTable.getInt32(cursor + 4);
|
|
831
975
|
liveStrings.add(nameOffset);
|
|
832
976
|
cursor += NAME_REF_SIZE;
|
|
833
|
-
markValueSlot(readSlot(
|
|
977
|
+
markValueSlot(readSlot(this.dataTable, cursor));
|
|
834
978
|
cursor += SLOT_SIZE;
|
|
835
979
|
}
|
|
836
980
|
};
|
|
837
981
|
|
|
838
982
|
const markValue = (dataOffset: number) => {
|
|
839
|
-
|
|
840
|
-
if (view.getInt32(dataOffset, true) === TYPE_OBJECT) {
|
|
983
|
+
if (this.dataTable.getInt32(dataOffset) === TYPE_OBJECT) {
|
|
841
984
|
markObject(dataOffset);
|
|
842
985
|
} else {
|
|
843
986
|
liveDataOffsets.add(dataOffset);
|
|
844
|
-
markValueSlot(readSlot(
|
|
987
|
+
markValueSlot(readSlot(this.dataTable, dataOffset));
|
|
845
988
|
}
|
|
846
989
|
};
|
|
847
990
|
|
|
848
991
|
// Phase 1: Mark live data by walking the Index Table.
|
|
849
|
-
const
|
|
850
|
-
const indexCount = indexView.getInt32(0, true);
|
|
992
|
+
const indexCount = this.indexTable.getInt32(0);
|
|
851
993
|
const indexEntries: Array<{ keyRef: StringRef; dataOffset: number }> = [];
|
|
852
994
|
for (let i = 0; i < indexCount; i++) {
|
|
853
995
|
const entryOffset = INDEX_HEADER + i * INDEX_ENTRY_SIZE;
|
|
854
996
|
const keyRef = {
|
|
855
|
-
offset:
|
|
856
|
-
length:
|
|
997
|
+
offset: this.indexTable.getInt32(entryOffset),
|
|
998
|
+
length: this.indexTable.getInt32(entryOffset + 4),
|
|
857
999
|
};
|
|
858
|
-
const dataOffset =
|
|
1000
|
+
const dataOffset = this.indexTable.getInt32(entryOffset + 8);
|
|
859
1001
|
indexEntries.push({ keyRef, dataOffset });
|
|
860
1002
|
liveStrings.add(keyRef.offset);
|
|
861
1003
|
markValue(dataOffset);
|
|
@@ -864,14 +1006,13 @@ export class SharedMemory {
|
|
|
864
1006
|
// Phase 2: Compact the DataTable (objects and single value slots).
|
|
865
1007
|
const sortedData = [...liveDataOffsets].sort((a, b) => a - b);
|
|
866
1008
|
let newDataOffset = 0;
|
|
867
|
-
const dataView = this.dataTable.getView();
|
|
868
1009
|
for (const oldOffset of sortedData) {
|
|
869
1010
|
const size =
|
|
870
|
-
|
|
1011
|
+
this.dataTable.getInt32(oldOffset) === TYPE_OBJECT
|
|
871
1012
|
? OBJECT_HEADER +
|
|
872
|
-
|
|
1013
|
+
this.dataTable.getInt32(oldOffset + 4) * (NAME_REF_SIZE + SLOT_SIZE)
|
|
873
1014
|
: SLOT_SIZE;
|
|
874
|
-
copyBytes(
|
|
1015
|
+
copyBytes(this.dataTable, newDataOffset, oldOffset, size);
|
|
875
1016
|
objectRemap.set(oldOffset, newDataOffset);
|
|
876
1017
|
newDataOffset += size;
|
|
877
1018
|
}
|
|
@@ -880,7 +1021,6 @@ export class SharedMemory {
|
|
|
880
1021
|
// Phase 3: Compact the ArrayTable.
|
|
881
1022
|
const sortedArrays = [...liveArrays].sort((a, b) => a - b);
|
|
882
1023
|
let newArrayOffset = 0;
|
|
883
|
-
const arrayView = this.arrayTable.getView();
|
|
884
1024
|
for (const oldOffset of sortedArrays) {
|
|
885
1025
|
// Determine the array's capacity by scanning forward to the next live
|
|
886
1026
|
// array or the end of the used region.
|
|
@@ -888,7 +1028,7 @@ export class SharedMemory {
|
|
|
888
1028
|
const end = next !== undefined ? next : this.arrayTable.byteLength;
|
|
889
1029
|
const capacity = (end - oldOffset - ARRAY_HEADER) / SLOT_SIZE;
|
|
890
1030
|
copyBytes(
|
|
891
|
-
|
|
1031
|
+
this.arrayTable,
|
|
892
1032
|
newArrayOffset,
|
|
893
1033
|
oldOffset,
|
|
894
1034
|
ARRAY_HEADER + capacity * SLOT_SIZE,
|
|
@@ -901,12 +1041,11 @@ export class SharedMemory {
|
|
|
901
1041
|
// Phase 4: Compact the StringTable.
|
|
902
1042
|
const sortedStrings = [...liveStrings].sort((a, b) => a - b);
|
|
903
1043
|
let newStringOffset = 0;
|
|
904
|
-
const stringView = this.stringTable.getView();
|
|
905
1044
|
for (const oldOffset of sortedStrings) {
|
|
906
1045
|
const next = sortedStrings[sortedStrings.indexOf(oldOffset) + 1];
|
|
907
1046
|
const end = next !== undefined ? next : this.stringTable.byteLength;
|
|
908
1047
|
const length = end - oldOffset;
|
|
909
|
-
copyBytes(
|
|
1048
|
+
copyBytes(this.stringTable, newStringOffset, oldOffset, length);
|
|
910
1049
|
stringRemap.set(oldOffset, newStringOffset);
|
|
911
1050
|
newStringOffset += length;
|
|
912
1051
|
}
|
|
@@ -916,29 +1055,29 @@ export class SharedMemory {
|
|
|
916
1055
|
for (let i = 0; i < indexCount; i++) {
|
|
917
1056
|
const entryOffset = INDEX_HEADER + i * INDEX_ENTRY_SIZE;
|
|
918
1057
|
const { keyRef, dataOffset } = indexEntries[i]!;
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1058
|
+
this.indexTable.setInt32(entryOffset, stringRemap.get(keyRef.offset)!);
|
|
1059
|
+
this.indexTable.setInt32(entryOffset + 4, keyRef.length);
|
|
1060
|
+
this.indexTable.setInt32(entryOffset + 8, objectRemap.get(dataOffset)!);
|
|
922
1061
|
}
|
|
923
1062
|
|
|
924
1063
|
// Phase 6: Rewrite object property names and value slots, and array slots.
|
|
925
1064
|
for (const oldOffset of sortedData) {
|
|
926
|
-
if (
|
|
1065
|
+
if (this.dataTable.getInt32(oldOffset) !== TYPE_OBJECT) continue;
|
|
927
1066
|
const newOff = objectRemap.get(oldOffset)!;
|
|
928
|
-
const count =
|
|
1067
|
+
const count = this.dataTable.getInt32(newOff + 4);
|
|
929
1068
|
let cursor = newOff + OBJECT_HEADER;
|
|
930
1069
|
for (let i = 0; i < count; i++) {
|
|
931
|
-
const nameOffset =
|
|
932
|
-
|
|
1070
|
+
const nameOffset = this.dataTable.getInt32(cursor);
|
|
1071
|
+
this.dataTable.setInt32(cursor, stringRemap.get(nameOffset)!);
|
|
933
1072
|
cursor += NAME_REF_SIZE;
|
|
934
|
-
remapSlot(
|
|
1073
|
+
remapSlot(this.dataTable, cursor, stringRemap, arrayRemap);
|
|
935
1074
|
cursor += SLOT_SIZE;
|
|
936
1075
|
}
|
|
937
1076
|
}
|
|
938
1077
|
for (const oldOffset of sortedData) {
|
|
939
|
-
if (
|
|
1078
|
+
if (this.dataTable.getInt32(oldOffset) === TYPE_OBJECT) continue;
|
|
940
1079
|
const newOff = objectRemap.get(oldOffset)!;
|
|
941
|
-
remapSlot(
|
|
1080
|
+
remapSlot(this.dataTable, newOff, stringRemap, arrayRemap);
|
|
942
1081
|
}
|
|
943
1082
|
for (const oldOffset of sortedArrays) {
|
|
944
1083
|
const newOff = arrayRemap.get(oldOffset)!;
|
|
@@ -947,7 +1086,7 @@ export class SharedMemory {
|
|
|
947
1086
|
const capacity = (end - oldOffset - ARRAY_HEADER) / SLOT_SIZE;
|
|
948
1087
|
for (let i = 0; i < capacity; i++) {
|
|
949
1088
|
remapSlot(
|
|
950
|
-
|
|
1089
|
+
this.arrayTable,
|
|
951
1090
|
newOff + ARRAY_HEADER + i * SLOT_SIZE,
|
|
952
1091
|
stringRemap,
|
|
953
1092
|
arrayRemap,
|
|
@@ -957,34 +1096,43 @@ export class SharedMemory {
|
|
|
957
1096
|
|
|
958
1097
|
// Phase 7: Invalidate all per-worker caches.
|
|
959
1098
|
this.bumpLayoutVersion();
|
|
1099
|
+
|
|
1100
|
+
// Phase 8: Reclaim memory from inactive chunks (beyond the compacted
|
|
1101
|
+
// region). The Index Table is a single growable SAB and is never replaced.
|
|
1102
|
+
return {
|
|
1103
|
+
indexTable: this.indexTable.releaseInactiveChunks(),
|
|
1104
|
+
dataTable: this.dataTable.releaseInactiveChunks(),
|
|
1105
|
+
stringTable: this.stringTable.releaseInactiveChunks(),
|
|
1106
|
+
arrayTable: this.arrayTable.releaseInactiveChunks(),
|
|
1107
|
+
};
|
|
960
1108
|
}
|
|
961
1109
|
}
|
|
962
1110
|
|
|
963
|
-
function writeSlot(
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1111
|
+
function writeSlot(buf: GrowableBuffer, offset: number, slot: ValueSlot): void {
|
|
1112
|
+
buf.setInt32(offset, slot.type);
|
|
1113
|
+
buf.setInt32(offset + 4, slot.a);
|
|
1114
|
+
buf.setInt32(offset + 8, slot.b);
|
|
1115
|
+
buf.setInt32(offset + 12, slot.c);
|
|
968
1116
|
}
|
|
969
1117
|
|
|
970
|
-
function readSlot(
|
|
1118
|
+
function readSlot(buf: GrowableBuffer, offset: number): ValueSlot {
|
|
971
1119
|
return {
|
|
972
|
-
type:
|
|
973
|
-
a:
|
|
974
|
-
b:
|
|
975
|
-
c:
|
|
1120
|
+
type: buf.getInt32(offset),
|
|
1121
|
+
a: buf.getInt32(offset + 4),
|
|
1122
|
+
b: buf.getInt32(offset + 8),
|
|
1123
|
+
c: buf.getInt32(offset + 12),
|
|
976
1124
|
};
|
|
977
1125
|
}
|
|
978
1126
|
|
|
979
1127
|
/** Copy `len` bytes from `src` to `dst` in the same buffer (dst <= src). */
|
|
980
1128
|
function copyBytes(
|
|
981
|
-
|
|
1129
|
+
buf: GrowableBuffer,
|
|
982
1130
|
dst: number,
|
|
983
1131
|
src: number,
|
|
984
1132
|
len: number,
|
|
985
1133
|
): void {
|
|
986
1134
|
for (let i = 0; i < len; i++) {
|
|
987
|
-
|
|
1135
|
+
buf.setUint8(dst + i, buf.getUint8(src + i));
|
|
988
1136
|
}
|
|
989
1137
|
}
|
|
990
1138
|
|
|
@@ -994,23 +1142,15 @@ function copyBytes(
|
|
|
994
1142
|
* via `arrayRemap`.
|
|
995
1143
|
*/
|
|
996
1144
|
function remapSlot(
|
|
997
|
-
|
|
1145
|
+
buf: GrowableBuffer,
|
|
998
1146
|
offset: number,
|
|
999
1147
|
stringRemap: Map<number, number>,
|
|
1000
1148
|
arrayRemap: Map<number, number>,
|
|
1001
1149
|
): void {
|
|
1002
|
-
const type =
|
|
1150
|
+
const type = buf.getInt32(offset);
|
|
1003
1151
|
if (type === TYPE_STRING_REF || type === TYPE_BIGINT_STR) {
|
|
1004
|
-
|
|
1005
|
-
offset + 4,
|
|
1006
|
-
stringRemap.get(view.getInt32(offset + 4, true))!,
|
|
1007
|
-
true,
|
|
1008
|
-
);
|
|
1152
|
+
buf.setInt32(offset + 4, stringRemap.get(buf.getInt32(offset + 4))!);
|
|
1009
1153
|
} else if (type === TYPE_ARRAY_REF) {
|
|
1010
|
-
|
|
1011
|
-
offset + 4,
|
|
1012
|
-
arrayRemap.get(view.getInt32(offset + 4, true))!,
|
|
1013
|
-
true,
|
|
1014
|
-
);
|
|
1154
|
+
buf.setInt32(offset + 4, arrayRemap.get(buf.getInt32(offset + 4))!);
|
|
1015
1155
|
}
|
|
1016
1156
|
}
|