bun-shared-memory 0.1.4 → 0.1.6
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 +129 -0
- package/package.json +4 -5
- package/src/growable-buffer.ts +197 -29
- package/src/memory.ts +159 -160
- package/src/refcount.ts +3 -3
- package/src/shared.ts +250 -196
- package/src/worker.ts +13 -2
package/src/memory.ts
CHANGED
|
@@ -175,19 +175,35 @@ export interface SharedMemoryOptions {
|
|
|
175
175
|
maxByteLength?: number;
|
|
176
176
|
/** Total memory budget shared across all buffers (see below). */
|
|
177
177
|
maxTotalBytes?: number;
|
|
178
|
+
/** Size of each growable chunk (the minimum growth step). Default 32 MiB. */
|
|
179
|
+
chunkSize?: number;
|
|
178
180
|
lock?: Mutex;
|
|
179
181
|
internCapacity?: number;
|
|
180
182
|
internMax?: number;
|
|
181
183
|
buffers?: {
|
|
182
|
-
indexTable?: SharedArrayBuffer;
|
|
183
|
-
dataTable?: SharedArrayBuffer;
|
|
184
|
-
stringTable?: SharedArrayBuffer;
|
|
185
|
-
arrayTable?: SharedArrayBuffer;
|
|
184
|
+
indexTable?: SharedArrayBuffer | SharedArrayBuffer[];
|
|
185
|
+
dataTable?: SharedArrayBuffer | SharedArrayBuffer[];
|
|
186
|
+
stringTable?: SharedArrayBuffer | SharedArrayBuffer[];
|
|
187
|
+
arrayTable?: SharedArrayBuffer | SharedArrayBuffer[];
|
|
186
188
|
layoutVersion?: SharedArrayBuffer;
|
|
187
189
|
refCount?: SharedArrayBuffer;
|
|
188
190
|
};
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
/** Build a GrowableBuffer from an optional single SAB, chunk list, or nothing. */
|
|
194
|
+
function makeBuffer(
|
|
195
|
+
buf: SharedArrayBuffer | SharedArrayBuffer[] | undefined,
|
|
196
|
+
initial: number,
|
|
197
|
+
maxByteLength: number,
|
|
198
|
+
budget?: SharedBudget,
|
|
199
|
+
chunkSize?: number,
|
|
200
|
+
): GrowableBuffer {
|
|
201
|
+
if (buf === undefined) {
|
|
202
|
+
return new GrowableBuffer(initial, maxByteLength, budget, chunkSize);
|
|
203
|
+
}
|
|
204
|
+
return new GrowableBuffer(buf, maxByteLength, budget, chunkSize);
|
|
205
|
+
}
|
|
206
|
+
|
|
191
207
|
/**
|
|
192
208
|
* Manages the four shared buffers and provides operations to store and read
|
|
193
209
|
* strings, arrays, and objects.
|
|
@@ -246,44 +262,48 @@ export class SharedMemory {
|
|
|
246
262
|
|
|
247
263
|
constructor(options?: SharedMemoryOptions) {
|
|
248
264
|
const initial = options?.initialSize ?? DEFAULT_INITIAL;
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
265
|
+
// `maxTotalBytes` is a *total* budget shared across all buffers: each
|
|
266
|
+
// buffer's own maxByteLength is capped at maxTotalBytes, and the sum of
|
|
267
|
+
// all buffers' physical sizes is additionally capped at maxTotalBytes.
|
|
268
|
+
// Without maxTotalBytes, each buffer grows up to its own maxByteLength.
|
|
269
|
+
// A single SharedArrayBuffer is limited to 4 GiB, but the chunked
|
|
270
|
+
// GrowableBuffer splits a buffer across multiple chunks, so a buffer can
|
|
271
|
+
// exceed 4 GiB.
|
|
254
272
|
const maxTotal = options?.maxTotalBytes;
|
|
255
|
-
const perBufferMax =
|
|
256
|
-
4 * 1024 * 1024 * 1024,
|
|
257
|
-
maxTotal ?? options?.maxByteLength ?? DEFAULT_MAX,
|
|
258
|
-
);
|
|
273
|
+
const perBufferMax = maxTotal ?? options?.maxByteLength ?? DEFAULT_MAX;
|
|
259
274
|
const budget =
|
|
260
275
|
maxTotal !== undefined ? new SharedBudget(maxTotal) : undefined;
|
|
276
|
+
const chunkSize = options?.chunkSize;
|
|
261
277
|
const buffers = options?.buffers;
|
|
262
|
-
this.indexTable =
|
|
263
|
-
buffers?.indexTable
|
|
264
|
-
|
|
278
|
+
this.indexTable = makeBuffer(
|
|
279
|
+
buffers?.indexTable,
|
|
280
|
+
initial,
|
|
265
281
|
perBufferMax,
|
|
266
282
|
budget,
|
|
283
|
+
chunkSize,
|
|
267
284
|
);
|
|
268
|
-
this.dataTable =
|
|
269
|
-
buffers?.dataTable
|
|
270
|
-
|
|
285
|
+
this.dataTable = makeBuffer(
|
|
286
|
+
buffers?.dataTable,
|
|
287
|
+
initial,
|
|
271
288
|
perBufferMax,
|
|
272
289
|
budget,
|
|
290
|
+
chunkSize,
|
|
273
291
|
);
|
|
274
|
-
this.stringTable =
|
|
275
|
-
buffers?.stringTable
|
|
276
|
-
|
|
292
|
+
this.stringTable = makeBuffer(
|
|
293
|
+
buffers?.stringTable,
|
|
294
|
+
initial,
|
|
277
295
|
perBufferMax,
|
|
278
296
|
budget,
|
|
297
|
+
chunkSize,
|
|
279
298
|
);
|
|
280
|
-
this.arrayTable =
|
|
281
|
-
buffers?.arrayTable
|
|
282
|
-
|
|
299
|
+
this.arrayTable = makeBuffer(
|
|
300
|
+
buffers?.arrayTable,
|
|
301
|
+
initial,
|
|
283
302
|
perBufferMax,
|
|
284
303
|
budget,
|
|
304
|
+
chunkSize,
|
|
285
305
|
);
|
|
286
|
-
this.versionView = new Int32Array(this.indexTable.
|
|
306
|
+
this.versionView = new Int32Array(this.indexTable.buffers[0]!);
|
|
287
307
|
this.layoutVersion = new Int32Array(
|
|
288
308
|
buffers?.layoutVersion ??
|
|
289
309
|
new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT),
|
|
@@ -313,21 +333,24 @@ export class SharedMemory {
|
|
|
313
333
|
/**
|
|
314
334
|
* Export all shared buffers (including the lock) so they can be sent to a
|
|
315
335
|
* worker via `postMessage` and reconstructed there with `fromBuffers()`.
|
|
336
|
+
*
|
|
337
|
+
* Each table is exported as a list of SharedArrayBuffers (chunks), since a
|
|
338
|
+
* single SharedArrayBuffer is limited to 4 GiB.
|
|
316
339
|
*/
|
|
317
340
|
exportBuffers(): {
|
|
318
|
-
indexTable: SharedArrayBuffer;
|
|
319
|
-
dataTable: SharedArrayBuffer;
|
|
320
|
-
stringTable: SharedArrayBuffer;
|
|
321
|
-
arrayTable: SharedArrayBuffer;
|
|
341
|
+
indexTable: SharedArrayBuffer[];
|
|
342
|
+
dataTable: SharedArrayBuffer[];
|
|
343
|
+
stringTable: SharedArrayBuffer[];
|
|
344
|
+
arrayTable: SharedArrayBuffer[];
|
|
322
345
|
layoutVersion: SharedArrayBuffer;
|
|
323
346
|
lock: SharedArrayBuffer;
|
|
324
347
|
refCount: SharedArrayBuffer | null;
|
|
325
348
|
} {
|
|
326
349
|
return {
|
|
327
|
-
indexTable: this.indexTable.
|
|
328
|
-
dataTable: this.dataTable.
|
|
329
|
-
stringTable: this.stringTable.
|
|
330
|
-
arrayTable: this.arrayTable.
|
|
350
|
+
indexTable: this.indexTable.buffers,
|
|
351
|
+
dataTable: this.dataTable.buffers,
|
|
352
|
+
stringTable: this.stringTable.buffers,
|
|
353
|
+
arrayTable: this.arrayTable.buffers,
|
|
331
354
|
layoutVersion: this.layoutVersion.buffer as SharedArrayBuffer,
|
|
332
355
|
lock: this.lock.buffer as SharedArrayBuffer,
|
|
333
356
|
refCount: this.refCountBuffer,
|
|
@@ -339,10 +362,10 @@ export class SharedMemory {
|
|
|
339
362
|
* `exportBuffers()` (e.g. received from the main thread in a worker).
|
|
340
363
|
*/
|
|
341
364
|
static fromBuffers(buffers: {
|
|
342
|
-
indexTable: SharedArrayBuffer;
|
|
343
|
-
dataTable: SharedArrayBuffer;
|
|
344
|
-
stringTable: SharedArrayBuffer;
|
|
345
|
-
arrayTable: SharedArrayBuffer;
|
|
365
|
+
indexTable: SharedArrayBuffer | SharedArrayBuffer[];
|
|
366
|
+
dataTable: SharedArrayBuffer | SharedArrayBuffer[];
|
|
367
|
+
stringTable: SharedArrayBuffer | SharedArrayBuffer[];
|
|
368
|
+
arrayTable: SharedArrayBuffer | SharedArrayBuffer[];
|
|
346
369
|
layoutVersion: SharedArrayBuffer;
|
|
347
370
|
lock: SharedArrayBuffer;
|
|
348
371
|
refCount?: SharedArrayBuffer | null;
|
|
@@ -393,10 +416,7 @@ export class SharedMemory {
|
|
|
393
416
|
const bytes = this.encoder.encode(value);
|
|
394
417
|
const offset = this.stringTable.byteLength;
|
|
395
418
|
this.stringTable.ensureCapacity(offset + bytes.byteLength);
|
|
396
|
-
|
|
397
|
-
for (let i = 0; i < bytes.byteLength; i++) {
|
|
398
|
-
view.setUint8(offset + i, bytes[i]!);
|
|
399
|
-
}
|
|
419
|
+
this.stringTable.writeBytes(offset, bytes);
|
|
400
420
|
this.stringTable.advance(bytes.byteLength);
|
|
401
421
|
const ref: StringRef = { offset, length: bytes.byteLength };
|
|
402
422
|
|
|
@@ -431,12 +451,7 @@ export class SharedMemory {
|
|
|
431
451
|
|
|
432
452
|
/** Read a string from the StringTable by reference. */
|
|
433
453
|
readString(ref: StringRef): string {
|
|
434
|
-
const
|
|
435
|
-
const bytes = new Uint8Array(
|
|
436
|
-
this.stringTable.buffer,
|
|
437
|
-
ref.offset,
|
|
438
|
-
ref.length,
|
|
439
|
-
);
|
|
454
|
+
const bytes = this.stringTable.readBytes(ref.offset, ref.length);
|
|
440
455
|
return this.decoder.decode(bytes);
|
|
441
456
|
}
|
|
442
457
|
|
|
@@ -458,11 +473,14 @@ export class SharedMemory {
|
|
|
458
473
|
const cap = Math.max(capacity ?? 0, count, 1);
|
|
459
474
|
const offset = this.arrayTable.byteLength;
|
|
460
475
|
this.arrayTable.ensureCapacity(offset + ARRAY_HEADER + cap * SLOT_SIZE);
|
|
461
|
-
const view = this.arrayTable.getView();
|
|
462
476
|
const objectId = this.nextObjectId++;
|
|
463
|
-
|
|
477
|
+
this.arrayTable.setInt32(offset, objectId);
|
|
464
478
|
for (let i = 0; i < count; i++) {
|
|
465
|
-
writeSlot(
|
|
479
|
+
writeSlot(
|
|
480
|
+
this.arrayTable,
|
|
481
|
+
offset + ARRAY_HEADER + i * SLOT_SIZE,
|
|
482
|
+
slots[i]!,
|
|
483
|
+
);
|
|
466
484
|
}
|
|
467
485
|
this.arrayTable.advance(ARRAY_HEADER + cap * SLOT_SIZE);
|
|
468
486
|
return { offset, count, capacity: cap };
|
|
@@ -470,10 +488,11 @@ export class SharedMemory {
|
|
|
470
488
|
|
|
471
489
|
/** Read an array of value slots from the ArrayTable by reference. */
|
|
472
490
|
readArray(ref: ArrayRef): ValueSlot[] {
|
|
473
|
-
const view = this.arrayTable.getView();
|
|
474
491
|
const slots: ValueSlot[] = [];
|
|
475
492
|
for (let i = 0; i < ref.count; i++) {
|
|
476
|
-
slots.push(
|
|
493
|
+
slots.push(
|
|
494
|
+
readSlot(this.arrayTable, ref.offset + ARRAY_HEADER + i * SLOT_SIZE),
|
|
495
|
+
);
|
|
477
496
|
}
|
|
478
497
|
return slots;
|
|
479
498
|
}
|
|
@@ -481,7 +500,7 @@ export class SharedMemory {
|
|
|
481
500
|
/** Update a single value slot of an array in the ArrayTable in place. */
|
|
482
501
|
updateArraySlot(ref: ArrayRef, index: number, slot: ValueSlot): void {
|
|
483
502
|
writeSlot(
|
|
484
|
-
this.arrayTable
|
|
503
|
+
this.arrayTable,
|
|
485
504
|
ref.offset + ARRAY_HEADER + index * SLOT_SIZE,
|
|
486
505
|
slot,
|
|
487
506
|
);
|
|
@@ -490,14 +509,14 @@ export class SharedMemory {
|
|
|
490
509
|
/** Read a single value slot of an array from the ArrayTable. */
|
|
491
510
|
readArraySlot(ref: ArrayRef, index: number): ValueSlot {
|
|
492
511
|
return readSlot(
|
|
493
|
-
this.arrayTable
|
|
512
|
+
this.arrayTable,
|
|
494
513
|
ref.offset + ARRAY_HEADER + index * SLOT_SIZE,
|
|
495
514
|
);
|
|
496
515
|
}
|
|
497
516
|
|
|
498
517
|
/** Read the stable objectId of an array block in the ArrayTable. */
|
|
499
518
|
getArrayObjectId(offset: number): number {
|
|
500
|
-
return this.arrayTable.
|
|
519
|
+
return this.arrayTable.getInt32(offset);
|
|
501
520
|
}
|
|
502
521
|
|
|
503
522
|
/**
|
|
@@ -510,14 +529,13 @@ export class SharedMemory {
|
|
|
510
529
|
this.arrayTable.ensureCapacity(
|
|
511
530
|
newOffset + ARRAY_HEADER + newCapacity * SLOT_SIZE,
|
|
512
531
|
);
|
|
513
|
-
const view = this.arrayTable.getView();
|
|
514
532
|
const objectId = this.getArrayObjectId(ref.offset);
|
|
515
|
-
|
|
533
|
+
this.arrayTable.setInt32(newOffset, objectId);
|
|
516
534
|
for (let i = 0; i < ref.count; i++) {
|
|
517
535
|
writeSlot(
|
|
518
|
-
|
|
536
|
+
this.arrayTable,
|
|
519
537
|
newOffset + ARRAY_HEADER + i * SLOT_SIZE,
|
|
520
|
-
readSlot(
|
|
538
|
+
readSlot(this.arrayTable, ref.offset + ARRAY_HEADER + i * SLOT_SIZE),
|
|
521
539
|
);
|
|
522
540
|
}
|
|
523
541
|
this.arrayTable.advance(ARRAY_HEADER + newCapacity * SLOT_SIZE);
|
|
@@ -533,7 +551,7 @@ export class SharedMemory {
|
|
|
533
551
|
appendArraySlot(ref: ArrayRef, slot: ValueSlot): ArrayRef {
|
|
534
552
|
if (ref.count < ref.capacity) {
|
|
535
553
|
writeSlot(
|
|
536
|
-
this.arrayTable
|
|
554
|
+
this.arrayTable,
|
|
537
555
|
ref.offset + ARRAY_HEADER + ref.count * SLOT_SIZE,
|
|
538
556
|
slot,
|
|
539
557
|
);
|
|
@@ -545,7 +563,7 @@ export class SharedMemory {
|
|
|
545
563
|
}
|
|
546
564
|
const moved = this.moveArray(ref, Math.max(ref.capacity * 2, 1));
|
|
547
565
|
writeSlot(
|
|
548
|
-
this.arrayTable
|
|
566
|
+
this.arrayTable,
|
|
549
567
|
moved.offset + ARRAY_HEADER + moved.count * SLOT_SIZE,
|
|
550
568
|
slot,
|
|
551
569
|
);
|
|
@@ -564,19 +582,19 @@ export class SharedMemory {
|
|
|
564
582
|
storeValueSlot(slot: ValueSlot): number {
|
|
565
583
|
const offset = this.dataTable.byteLength;
|
|
566
584
|
this.dataTable.ensureCapacity(offset + SLOT_SIZE);
|
|
567
|
-
writeSlot(this.dataTable
|
|
585
|
+
writeSlot(this.dataTable, offset, slot);
|
|
568
586
|
this.dataTable.advance(SLOT_SIZE);
|
|
569
587
|
return offset;
|
|
570
588
|
}
|
|
571
589
|
|
|
572
590
|
/** Read a single value slot from the DataTable. */
|
|
573
591
|
readValueSlot(offset: number): ValueSlot {
|
|
574
|
-
return readSlot(this.dataTable
|
|
592
|
+
return readSlot(this.dataTable, offset);
|
|
575
593
|
}
|
|
576
594
|
|
|
577
595
|
/** Update a single value slot in the DataTable. */
|
|
578
596
|
updateValueSlot(offset: number, slot: ValueSlot): void {
|
|
579
|
-
writeSlot(this.dataTable
|
|
597
|
+
writeSlot(this.dataTable, offset, slot);
|
|
580
598
|
}
|
|
581
599
|
|
|
582
600
|
/**
|
|
@@ -590,20 +608,19 @@ export class SharedMemory {
|
|
|
590
608
|
OBJECT_HEADER + properties.length * (NAME_REF_SIZE + SLOT_SIZE);
|
|
591
609
|
const offset = this.dataTable.byteLength;
|
|
592
610
|
this.dataTable.ensureCapacity(offset + blockSize);
|
|
593
|
-
const view = this.dataTable.getView();
|
|
594
611
|
|
|
595
612
|
const objectId = this.nextObjectId++;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
613
|
+
this.dataTable.setInt32(offset, TYPE_OBJECT);
|
|
614
|
+
this.dataTable.setInt32(offset + 4, properties.length);
|
|
615
|
+
this.dataTable.setInt32(offset + 8, objectId);
|
|
599
616
|
|
|
600
617
|
let cursor = offset + OBJECT_HEADER;
|
|
601
618
|
for (const [name, slot] of properties) {
|
|
602
619
|
const nameRef = this.storeString(name);
|
|
603
|
-
|
|
604
|
-
|
|
620
|
+
this.dataTable.setInt32(cursor, nameRef.offset);
|
|
621
|
+
this.dataTable.setInt32(cursor + 4, nameRef.length);
|
|
605
622
|
cursor += NAME_REF_SIZE;
|
|
606
|
-
writeSlot(
|
|
623
|
+
writeSlot(this.dataTable, cursor, slot);
|
|
607
624
|
cursor += SLOT_SIZE;
|
|
608
625
|
}
|
|
609
626
|
this.dataTable.advance(blockSize);
|
|
@@ -612,16 +629,15 @@ export class SharedMemory {
|
|
|
612
629
|
|
|
613
630
|
/** Read an object's properties from the DataTable. */
|
|
614
631
|
readObject(offset: number): Array<[string, ValueSlot]> {
|
|
615
|
-
const
|
|
616
|
-
const count = view.getInt32(offset + 4, true);
|
|
632
|
+
const count = this.dataTable.getInt32(offset + 4);
|
|
617
633
|
const properties: Array<[string, ValueSlot]> = [];
|
|
618
634
|
let cursor = offset + OBJECT_HEADER;
|
|
619
635
|
for (let i = 0; i < count; i++) {
|
|
620
|
-
const nameOffset =
|
|
621
|
-
const nameLength =
|
|
636
|
+
const nameOffset = this.dataTable.getInt32(cursor);
|
|
637
|
+
const nameLength = this.dataTable.getInt32(cursor + 4);
|
|
622
638
|
const name = this.readString({ offset: nameOffset, length: nameLength });
|
|
623
639
|
cursor += NAME_REF_SIZE;
|
|
624
|
-
const slot = readSlot(
|
|
640
|
+
const slot = readSlot(this.dataTable, cursor);
|
|
625
641
|
cursor += SLOT_SIZE;
|
|
626
642
|
properties.push([name, slot]);
|
|
627
643
|
}
|
|
@@ -630,7 +646,7 @@ export class SharedMemory {
|
|
|
630
646
|
|
|
631
647
|
/** Read the stable objectId of an object block in the DataTable. */
|
|
632
648
|
getObjectId(offset: number): number {
|
|
633
|
-
return this.dataTable.
|
|
649
|
+
return this.dataTable.getInt32(offset + 8);
|
|
634
650
|
}
|
|
635
651
|
|
|
636
652
|
/**
|
|
@@ -647,14 +663,13 @@ export class SharedMemory {
|
|
|
647
663
|
getObjectLayout(key: string): ObjectLayout | null {
|
|
648
664
|
const offset = this.lookup(key);
|
|
649
665
|
if (offset === null) return null;
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const count = view.getInt32(offset + 4, true);
|
|
666
|
+
if (this.dataTable.getInt32(offset) !== TYPE_OBJECT) return null;
|
|
667
|
+
const count = this.dataTable.getInt32(offset + 4);
|
|
653
668
|
const properties = new Map<string, number>();
|
|
654
669
|
let cursor = offset + OBJECT_HEADER;
|
|
655
670
|
for (let i = 0; i < count; i++) {
|
|
656
|
-
const nameOffset =
|
|
657
|
-
const nameLength =
|
|
671
|
+
const nameOffset = this.dataTable.getInt32(cursor);
|
|
672
|
+
const nameLength = this.dataTable.getInt32(cursor + 4);
|
|
658
673
|
const name = this.readString({ offset: nameOffset, length: nameLength });
|
|
659
674
|
cursor += NAME_REF_SIZE;
|
|
660
675
|
properties.set(name, cursor);
|
|
@@ -669,13 +684,12 @@ export class SharedMemory {
|
|
|
669
684
|
propertyIndex: number,
|
|
670
685
|
slot: ValueSlot,
|
|
671
686
|
): void {
|
|
672
|
-
const view = this.dataTable.getView();
|
|
673
687
|
const slotOffset =
|
|
674
688
|
offset +
|
|
675
689
|
OBJECT_HEADER +
|
|
676
690
|
propertyIndex * (NAME_REF_SIZE + SLOT_SIZE) +
|
|
677
691
|
NAME_REF_SIZE;
|
|
678
|
-
writeSlot(
|
|
692
|
+
writeSlot(this.dataTable, slotOffset, slot);
|
|
679
693
|
}
|
|
680
694
|
|
|
681
695
|
// ---------------------------------------------------------------------------
|
|
@@ -691,11 +705,10 @@ export class SharedMemory {
|
|
|
691
705
|
|
|
692
706
|
/** Read the DataTable offset for a known stable index, or null if absent. */
|
|
693
707
|
lookupByIndex(index: number): number | null {
|
|
694
|
-
const
|
|
695
|
-
const count = view.getInt32(0, true);
|
|
708
|
+
const count = this.indexTable.getInt32(0);
|
|
696
709
|
if (index >= count) return null;
|
|
697
710
|
const entryOffset = INDEX_HEADER + index * INDEX_ENTRY_SIZE;
|
|
698
|
-
return
|
|
711
|
+
return this.indexTable.getInt32(entryOffset + 8);
|
|
699
712
|
}
|
|
700
713
|
|
|
701
714
|
/**
|
|
@@ -706,12 +719,11 @@ export class SharedMemory {
|
|
|
706
719
|
* used to address the key's version cell.
|
|
707
720
|
*/
|
|
708
721
|
lookupIndex(key: string): number | null {
|
|
709
|
-
const
|
|
710
|
-
const count = view.getInt32(0, true);
|
|
722
|
+
const count = this.indexTable.getInt32(0);
|
|
711
723
|
for (let i = 0; i < count; i++) {
|
|
712
724
|
const entryOffset = INDEX_HEADER + i * INDEX_ENTRY_SIZE;
|
|
713
|
-
const keyOffset =
|
|
714
|
-
const keyLength =
|
|
725
|
+
const keyOffset = this.indexTable.getInt32(entryOffset);
|
|
726
|
+
const keyLength = this.indexTable.getInt32(entryOffset + 4);
|
|
715
727
|
const storedKey = this.readString({
|
|
716
728
|
offset: keyOffset,
|
|
717
729
|
length: keyLength,
|
|
@@ -736,21 +748,21 @@ export class SharedMemory {
|
|
|
736
748
|
/** Insert a key → dataOffset mapping into the Index Table. */
|
|
737
749
|
insert(key: string, dataOffset: number): void {
|
|
738
750
|
const keyRef = this.storeString(key);
|
|
739
|
-
const
|
|
740
|
-
const count = view.getInt32(0, true);
|
|
751
|
+
const count = this.indexTable.getInt32(0);
|
|
741
752
|
const entryOffset = INDEX_HEADER + count * INDEX_ENTRY_SIZE;
|
|
742
753
|
this.indexTable.ensureCapacity(entryOffset + INDEX_ENTRY_SIZE);
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
754
|
+
this.indexTable.setInt32(entryOffset, keyRef.offset);
|
|
755
|
+
this.indexTable.setInt32(entryOffset + 4, keyRef.length);
|
|
756
|
+
this.indexTable.setInt32(entryOffset + 8, dataOffset);
|
|
746
757
|
// The 4th field is the key's version cell, initialized to 0.
|
|
747
|
-
|
|
748
|
-
|
|
758
|
+
this.indexTable.setInt32(entryOffset + INDEX_VERSION_OFFSET, 0);
|
|
759
|
+
this.indexTable.setInt32(0, count + 1);
|
|
749
760
|
this.indexTable.advance(INDEX_ENTRY_SIZE);
|
|
750
761
|
|
|
751
|
-
// Rebuild the version view
|
|
752
|
-
//
|
|
753
|
-
|
|
762
|
+
// Rebuild the version view over the first chunk (the Index Table is small
|
|
763
|
+
// and stays within the first chunk for typical workloads; version cells
|
|
764
|
+
// beyond the first chunk are not addressable via the single Int32Array).
|
|
765
|
+
this.versionView = new Int32Array(this.indexTable.buffers[0]!);
|
|
754
766
|
}
|
|
755
767
|
|
|
756
768
|
// ---------------------------------------------------------------------------
|
|
@@ -812,9 +824,10 @@ export class SharedMemory {
|
|
|
812
824
|
const markArray = (offset: number, count: number) => {
|
|
813
825
|
if (liveArrays.has(offset)) return;
|
|
814
826
|
liveArrays.add(offset);
|
|
815
|
-
const view = this.arrayTable.getView();
|
|
816
827
|
for (let i = 0; i < count; i++) {
|
|
817
|
-
markValueSlot(
|
|
828
|
+
markValueSlot(
|
|
829
|
+
readSlot(this.arrayTable, offset + ARRAY_HEADER + i * SLOT_SIZE),
|
|
830
|
+
);
|
|
818
831
|
}
|
|
819
832
|
};
|
|
820
833
|
|
|
@@ -822,40 +835,37 @@ export class SharedMemory {
|
|
|
822
835
|
if (liveObjects.has(offset)) return;
|
|
823
836
|
liveObjects.add(offset);
|
|
824
837
|
liveDataOffsets.add(offset);
|
|
825
|
-
const
|
|
826
|
-
const count = view.getInt32(offset + 4, true);
|
|
838
|
+
const count = this.dataTable.getInt32(offset + 4);
|
|
827
839
|
let cursor = offset + OBJECT_HEADER;
|
|
828
840
|
for (let i = 0; i < count; i++) {
|
|
829
|
-
const nameOffset =
|
|
830
|
-
const nameLength =
|
|
841
|
+
const nameOffset = this.dataTable.getInt32(cursor);
|
|
842
|
+
const nameLength = this.dataTable.getInt32(cursor + 4);
|
|
831
843
|
liveStrings.add(nameOffset);
|
|
832
844
|
cursor += NAME_REF_SIZE;
|
|
833
|
-
markValueSlot(readSlot(
|
|
845
|
+
markValueSlot(readSlot(this.dataTable, cursor));
|
|
834
846
|
cursor += SLOT_SIZE;
|
|
835
847
|
}
|
|
836
848
|
};
|
|
837
849
|
|
|
838
850
|
const markValue = (dataOffset: number) => {
|
|
839
|
-
|
|
840
|
-
if (view.getInt32(dataOffset, true) === TYPE_OBJECT) {
|
|
851
|
+
if (this.dataTable.getInt32(dataOffset) === TYPE_OBJECT) {
|
|
841
852
|
markObject(dataOffset);
|
|
842
853
|
} else {
|
|
843
854
|
liveDataOffsets.add(dataOffset);
|
|
844
|
-
markValueSlot(readSlot(
|
|
855
|
+
markValueSlot(readSlot(this.dataTable, dataOffset));
|
|
845
856
|
}
|
|
846
857
|
};
|
|
847
858
|
|
|
848
859
|
// Phase 1: Mark live data by walking the Index Table.
|
|
849
|
-
const
|
|
850
|
-
const indexCount = indexView.getInt32(0, true);
|
|
860
|
+
const indexCount = this.indexTable.getInt32(0);
|
|
851
861
|
const indexEntries: Array<{ keyRef: StringRef; dataOffset: number }> = [];
|
|
852
862
|
for (let i = 0; i < indexCount; i++) {
|
|
853
863
|
const entryOffset = INDEX_HEADER + i * INDEX_ENTRY_SIZE;
|
|
854
864
|
const keyRef = {
|
|
855
|
-
offset:
|
|
856
|
-
length:
|
|
865
|
+
offset: this.indexTable.getInt32(entryOffset),
|
|
866
|
+
length: this.indexTable.getInt32(entryOffset + 4),
|
|
857
867
|
};
|
|
858
|
-
const dataOffset =
|
|
868
|
+
const dataOffset = this.indexTable.getInt32(entryOffset + 8);
|
|
859
869
|
indexEntries.push({ keyRef, dataOffset });
|
|
860
870
|
liveStrings.add(keyRef.offset);
|
|
861
871
|
markValue(dataOffset);
|
|
@@ -864,14 +874,13 @@ export class SharedMemory {
|
|
|
864
874
|
// Phase 2: Compact the DataTable (objects and single value slots).
|
|
865
875
|
const sortedData = [...liveDataOffsets].sort((a, b) => a - b);
|
|
866
876
|
let newDataOffset = 0;
|
|
867
|
-
const dataView = this.dataTable.getView();
|
|
868
877
|
for (const oldOffset of sortedData) {
|
|
869
878
|
const size =
|
|
870
|
-
|
|
879
|
+
this.dataTable.getInt32(oldOffset) === TYPE_OBJECT
|
|
871
880
|
? OBJECT_HEADER +
|
|
872
|
-
|
|
881
|
+
this.dataTable.getInt32(oldOffset + 4) * (NAME_REF_SIZE + SLOT_SIZE)
|
|
873
882
|
: SLOT_SIZE;
|
|
874
|
-
copyBytes(
|
|
883
|
+
copyBytes(this.dataTable, newDataOffset, oldOffset, size);
|
|
875
884
|
objectRemap.set(oldOffset, newDataOffset);
|
|
876
885
|
newDataOffset += size;
|
|
877
886
|
}
|
|
@@ -880,7 +889,6 @@ export class SharedMemory {
|
|
|
880
889
|
// Phase 3: Compact the ArrayTable.
|
|
881
890
|
const sortedArrays = [...liveArrays].sort((a, b) => a - b);
|
|
882
891
|
let newArrayOffset = 0;
|
|
883
|
-
const arrayView = this.arrayTable.getView();
|
|
884
892
|
for (const oldOffset of sortedArrays) {
|
|
885
893
|
// Determine the array's capacity by scanning forward to the next live
|
|
886
894
|
// array or the end of the used region.
|
|
@@ -888,7 +896,7 @@ export class SharedMemory {
|
|
|
888
896
|
const end = next !== undefined ? next : this.arrayTable.byteLength;
|
|
889
897
|
const capacity = (end - oldOffset - ARRAY_HEADER) / SLOT_SIZE;
|
|
890
898
|
copyBytes(
|
|
891
|
-
|
|
899
|
+
this.arrayTable,
|
|
892
900
|
newArrayOffset,
|
|
893
901
|
oldOffset,
|
|
894
902
|
ARRAY_HEADER + capacity * SLOT_SIZE,
|
|
@@ -901,12 +909,11 @@ export class SharedMemory {
|
|
|
901
909
|
// Phase 4: Compact the StringTable.
|
|
902
910
|
const sortedStrings = [...liveStrings].sort((a, b) => a - b);
|
|
903
911
|
let newStringOffset = 0;
|
|
904
|
-
const stringView = this.stringTable.getView();
|
|
905
912
|
for (const oldOffset of sortedStrings) {
|
|
906
913
|
const next = sortedStrings[sortedStrings.indexOf(oldOffset) + 1];
|
|
907
914
|
const end = next !== undefined ? next : this.stringTable.byteLength;
|
|
908
915
|
const length = end - oldOffset;
|
|
909
|
-
copyBytes(
|
|
916
|
+
copyBytes(this.stringTable, newStringOffset, oldOffset, length);
|
|
910
917
|
stringRemap.set(oldOffset, newStringOffset);
|
|
911
918
|
newStringOffset += length;
|
|
912
919
|
}
|
|
@@ -916,29 +923,29 @@ export class SharedMemory {
|
|
|
916
923
|
for (let i = 0; i < indexCount; i++) {
|
|
917
924
|
const entryOffset = INDEX_HEADER + i * INDEX_ENTRY_SIZE;
|
|
918
925
|
const { keyRef, dataOffset } = indexEntries[i]!;
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
926
|
+
this.indexTable.setInt32(entryOffset, stringRemap.get(keyRef.offset)!);
|
|
927
|
+
this.indexTable.setInt32(entryOffset + 4, keyRef.length);
|
|
928
|
+
this.indexTable.setInt32(entryOffset + 8, objectRemap.get(dataOffset)!);
|
|
922
929
|
}
|
|
923
930
|
|
|
924
931
|
// Phase 6: Rewrite object property names and value slots, and array slots.
|
|
925
932
|
for (const oldOffset of sortedData) {
|
|
926
|
-
if (
|
|
933
|
+
if (this.dataTable.getInt32(oldOffset) !== TYPE_OBJECT) continue;
|
|
927
934
|
const newOff = objectRemap.get(oldOffset)!;
|
|
928
|
-
const count =
|
|
935
|
+
const count = this.dataTable.getInt32(newOff + 4);
|
|
929
936
|
let cursor = newOff + OBJECT_HEADER;
|
|
930
937
|
for (let i = 0; i < count; i++) {
|
|
931
|
-
const nameOffset =
|
|
932
|
-
|
|
938
|
+
const nameOffset = this.dataTable.getInt32(cursor);
|
|
939
|
+
this.dataTable.setInt32(cursor, stringRemap.get(nameOffset)!);
|
|
933
940
|
cursor += NAME_REF_SIZE;
|
|
934
|
-
remapSlot(
|
|
941
|
+
remapSlot(this.dataTable, cursor, stringRemap, arrayRemap);
|
|
935
942
|
cursor += SLOT_SIZE;
|
|
936
943
|
}
|
|
937
944
|
}
|
|
938
945
|
for (const oldOffset of sortedData) {
|
|
939
|
-
if (
|
|
946
|
+
if (this.dataTable.getInt32(oldOffset) === TYPE_OBJECT) continue;
|
|
940
947
|
const newOff = objectRemap.get(oldOffset)!;
|
|
941
|
-
remapSlot(
|
|
948
|
+
remapSlot(this.dataTable, newOff, stringRemap, arrayRemap);
|
|
942
949
|
}
|
|
943
950
|
for (const oldOffset of sortedArrays) {
|
|
944
951
|
const newOff = arrayRemap.get(oldOffset)!;
|
|
@@ -947,7 +954,7 @@ export class SharedMemory {
|
|
|
947
954
|
const capacity = (end - oldOffset - ARRAY_HEADER) / SLOT_SIZE;
|
|
948
955
|
for (let i = 0; i < capacity; i++) {
|
|
949
956
|
remapSlot(
|
|
950
|
-
|
|
957
|
+
this.arrayTable,
|
|
951
958
|
newOff + ARRAY_HEADER + i * SLOT_SIZE,
|
|
952
959
|
stringRemap,
|
|
953
960
|
arrayRemap,
|
|
@@ -960,31 +967,31 @@ export class SharedMemory {
|
|
|
960
967
|
}
|
|
961
968
|
}
|
|
962
969
|
|
|
963
|
-
function writeSlot(
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
970
|
+
function writeSlot(buf: GrowableBuffer, offset: number, slot: ValueSlot): void {
|
|
971
|
+
buf.setInt32(offset, slot.type);
|
|
972
|
+
buf.setInt32(offset + 4, slot.a);
|
|
973
|
+
buf.setInt32(offset + 8, slot.b);
|
|
974
|
+
buf.setInt32(offset + 12, slot.c);
|
|
968
975
|
}
|
|
969
976
|
|
|
970
|
-
function readSlot(
|
|
977
|
+
function readSlot(buf: GrowableBuffer, offset: number): ValueSlot {
|
|
971
978
|
return {
|
|
972
|
-
type:
|
|
973
|
-
a:
|
|
974
|
-
b:
|
|
975
|
-
c:
|
|
979
|
+
type: buf.getInt32(offset),
|
|
980
|
+
a: buf.getInt32(offset + 4),
|
|
981
|
+
b: buf.getInt32(offset + 8),
|
|
982
|
+
c: buf.getInt32(offset + 12),
|
|
976
983
|
};
|
|
977
984
|
}
|
|
978
985
|
|
|
979
986
|
/** Copy `len` bytes from `src` to `dst` in the same buffer (dst <= src). */
|
|
980
987
|
function copyBytes(
|
|
981
|
-
|
|
988
|
+
buf: GrowableBuffer,
|
|
982
989
|
dst: number,
|
|
983
990
|
src: number,
|
|
984
991
|
len: number,
|
|
985
992
|
): void {
|
|
986
993
|
for (let i = 0; i < len; i++) {
|
|
987
|
-
|
|
994
|
+
buf.setUint8(dst + i, buf.getUint8(src + i));
|
|
988
995
|
}
|
|
989
996
|
}
|
|
990
997
|
|
|
@@ -994,23 +1001,15 @@ function copyBytes(
|
|
|
994
1001
|
* via `arrayRemap`.
|
|
995
1002
|
*/
|
|
996
1003
|
function remapSlot(
|
|
997
|
-
|
|
1004
|
+
buf: GrowableBuffer,
|
|
998
1005
|
offset: number,
|
|
999
1006
|
stringRemap: Map<number, number>,
|
|
1000
1007
|
arrayRemap: Map<number, number>,
|
|
1001
1008
|
): void {
|
|
1002
|
-
const type =
|
|
1009
|
+
const type = buf.getInt32(offset);
|
|
1003
1010
|
if (type === TYPE_STRING_REF || type === TYPE_BIGINT_STR) {
|
|
1004
|
-
|
|
1005
|
-
offset + 4,
|
|
1006
|
-
stringRemap.get(view.getInt32(offset + 4, true))!,
|
|
1007
|
-
true,
|
|
1008
|
-
);
|
|
1011
|
+
buf.setInt32(offset + 4, stringRemap.get(buf.getInt32(offset + 4))!);
|
|
1009
1012
|
} else if (type === TYPE_ARRAY_REF) {
|
|
1010
|
-
|
|
1011
|
-
offset + 4,
|
|
1012
|
-
arrayRemap.get(view.getInt32(offset + 4, true))!,
|
|
1013
|
-
true,
|
|
1014
|
-
);
|
|
1013
|
+
buf.setInt32(offset + 4, arrayRemap.get(buf.getInt32(offset + 4))!);
|
|
1015
1014
|
}
|
|
1016
1015
|
}
|