@agoric/swingset-liveslots 0.10.3-other-dev-8f8782b.0 → 0.10.3-other-dev-fbe72e7.0.fbe72e7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.md +2 -0
  2. package/package.json +34 -26
  3. package/src/boyd-gc.d.ts +12 -0
  4. package/src/boyd-gc.d.ts.map +1 -0
  5. package/src/boyd-gc.js +598 -0
  6. package/src/cache.d.ts +71 -0
  7. package/src/cache.d.ts.map +1 -0
  8. package/src/cache.js +3 -2
  9. package/src/capdata.d.ts +16 -0
  10. package/src/capdata.d.ts.map +1 -0
  11. package/src/capdata.js +17 -10
  12. package/src/collectionManager.d.ts +47 -0
  13. package/src/collectionManager.d.ts.map +1 -0
  14. package/src/collectionManager.js +220 -103
  15. package/src/facetiousness.d.ts +25 -0
  16. package/src/facetiousness.d.ts.map +1 -0
  17. package/src/facetiousness.js +1 -1
  18. package/src/index.d.ts +4 -0
  19. package/src/index.d.ts.map +1 -0
  20. package/src/index.js +4 -2
  21. package/src/kdebug.d.ts +7 -0
  22. package/src/kdebug.d.ts.map +1 -0
  23. package/src/liveslots.d.ts +42 -0
  24. package/src/liveslots.d.ts.map +1 -0
  25. package/src/liveslots.js +137 -305
  26. package/src/message.d.ts +49 -0
  27. package/src/message.d.ts.map +1 -0
  28. package/src/message.js +9 -5
  29. package/src/parseVatSlots.d.ts +125 -0
  30. package/src/parseVatSlots.d.ts.map +1 -0
  31. package/src/parseVatSlots.js +1 -1
  32. package/src/types-index.d.ts +4 -0
  33. package/src/types-index.js +2 -0
  34. package/src/types.d.ts +81 -0
  35. package/src/types.d.ts.map +1 -0
  36. package/src/types.js +14 -7
  37. package/src/vatDataTypes.d.ts +170 -0
  38. package/src/vatDataTypes.d.ts.map +1 -0
  39. package/src/vatDataTypes.ts +272 -0
  40. package/src/vatstore-iterators.d.ts +4 -0
  41. package/src/vatstore-iterators.d.ts.map +1 -0
  42. package/src/vatstore-iterators.js +2 -0
  43. package/src/vatstore-usage.md +198 -0
  44. package/src/virtualObjectManager.d.ts +44 -0
  45. package/src/virtualObjectManager.d.ts.map +1 -0
  46. package/src/virtualObjectManager.js +254 -84
  47. package/src/virtualReferences.d.ts +61 -0
  48. package/src/virtualReferences.d.ts.map +1 -0
  49. package/src/virtualReferences.js +135 -26
  50. package/src/vpid-tracking.md +92 -0
  51. package/src/watchedPromises.d.ts +31 -0
  52. package/src/watchedPromises.d.ts.map +1 -0
  53. package/src/watchedPromises.js +81 -24
  54. package/test/{test-baggage.js → baggage.test.js} +1 -2
  55. package/test/{test-cache.js → cache.test.js} +0 -1
  56. package/test/clear-collection.test.js +586 -0
  57. package/test/{test-collection-schema-refcount.js → collection-schema-refcount.test.js} +1 -2
  58. package/test/{test-collection-upgrade.js → collection-upgrade.test.js} +1 -3
  59. package/test/{test-collections.js → collections.test.js} +183 -18
  60. package/test/{test-dropped-collection-weakrefs.js → dropped-collection-weakrefs.test.js} +1 -2
  61. package/test/dropped-weakset-9939.test.js +80 -0
  62. package/test/dummyMeterControl.d.ts +2 -0
  63. package/test/dummyMeterControl.d.ts.map +1 -0
  64. package/test/dummyMeterControl.js +1 -1
  65. package/test/{test-durabilityChecks.js → durabilityChecks.test.js} +4 -4
  66. package/test/engine-gc.d.ts +3 -0
  67. package/test/engine-gc.d.ts.map +1 -0
  68. package/test/exo-utils.js +70 -0
  69. package/test/{test-facetiousness.js → facetiousness.test.js} +1 -2
  70. package/test/gc-and-finalize.d.ts +5 -0
  71. package/test/gc-and-finalize.d.ts.map +1 -0
  72. package/test/gc-and-finalize.js +30 -1
  73. package/test/gc-before-finalizer.test.js +230 -0
  74. package/test/gc-helpers.js +4 -5
  75. package/test/{test-gc-sensitivity.js → gc-sensitivity.test.js} +2 -2
  76. package/test/handled-promises.test.js +872 -0
  77. package/test/{test-initial-vrefs.js → initial-vrefs.test.js} +13 -20
  78. package/test/liveslots-helpers.d.ts +64 -0
  79. package/test/liveslots-helpers.d.ts.map +1 -0
  80. package/test/liveslots-helpers.js +13 -7
  81. package/test/{test-liveslots-mock-gc.js → liveslots-mock-gc.test.js} +101 -2
  82. package/test/{test-liveslots-real-gc.js → liveslots-real-gc.test.js} +73 -46
  83. package/test/{test-liveslots.js → liveslots.test.js} +17 -18
  84. package/test/mock-gc.js +1 -0
  85. package/test/storeGC/{test-lifecycle.js → lifecycle.test.js} +15 -14
  86. package/test/storeGC/{test-refcount-management.js → refcount-management.test.js} +1 -2
  87. package/test/storeGC/{test-scalar-store-kind.js → scalar-store-kind.test.js} +0 -1
  88. package/test/storeGC/{test-weak-key.js → weak-key.test.js} +1 -2
  89. package/test/strict-test-env-upgrade.test.js +94 -0
  90. package/test/util.d.ts +25 -0
  91. package/test/util.d.ts.map +1 -0
  92. package/test/util.js +4 -4
  93. package/test/vat-environment.test.js +65 -0
  94. package/test/vat-util.d.ts +9 -0
  95. package/test/vat-util.d.ts.map +1 -0
  96. package/test/vat-util.js +2 -2
  97. package/test/virtual-objects/{test-cease-recognition.js → cease-recognition.test.js} +2 -2
  98. package/test/virtual-objects/{test-cross-facet.js → cross-facet.test.js} +5 -4
  99. package/test/virtual-objects/{test-empty-data.js → empty-data.test.js} +1 -2
  100. package/test/virtual-objects/{test-facets.js → facets.test.js} +1 -2
  101. package/test/virtual-objects/{test-kind-changes.js → kind-changes.test.js} +2 -2
  102. package/test/virtual-objects/{test-reachable-vrefs.js → reachable-vrefs.test.js} +2 -2
  103. package/test/virtual-objects/{test-rep-tostring.js → rep-tostring.test.js} +3 -5
  104. package/test/virtual-objects/{test-retain-remotable.js → retain-remotable.test.js} +25 -24
  105. package/test/virtual-objects/set-debug-label-instances.js +1 -1
  106. package/test/virtual-objects/state-shape.test.js +389 -0
  107. package/test/virtual-objects/{test-virtualObjectGC.js → virtualObjectGC.test.js} +39 -38
  108. package/test/virtual-objects/{test-virtualObjectManager.js → virtualObjectManager.test.js} +104 -8
  109. package/test/virtual-objects/{test-vo-real-gc.js → vo-real-gc.test.js} +8 -8
  110. package/test/virtual-objects/{test-weakcollections-vref-handling.js → weakcollections-vref-handling.test.js} +1 -2
  111. package/test/{test-vo-test-harness.js → vo-test-harness.test.js} +13 -10
  112. package/test/{test-vpid-liveslots.js → vpid-liveslots.test.js} +105 -5
  113. package/test/waitUntilQuiescent.d.ts +3 -0
  114. package/test/waitUntilQuiescent.d.ts.map +1 -0
  115. package/test/waitUntilQuiescent.js +2 -1
  116. package/test/weakset-dropped-remotable.test.js +50 -0
  117. package/tools/fakeCollectionManager.d.ts +14 -0
  118. package/tools/fakeCollectionManager.d.ts.map +1 -0
  119. package/tools/fakeCollectionManager.js +44 -0
  120. package/tools/fakeVirtualObjectManager.d.ts +32 -0
  121. package/tools/fakeVirtualObjectManager.d.ts.map +1 -0
  122. package/tools/fakeVirtualObjectManager.js +62 -0
  123. package/tools/fakeVirtualSupport.d.ts +278 -0
  124. package/tools/fakeVirtualSupport.d.ts.map +1 -0
  125. package/tools/fakeVirtualSupport.js +389 -0
  126. package/tools/prepare-strict-test-env.d.ts +37 -0
  127. package/tools/prepare-strict-test-env.d.ts.map +1 -0
  128. package/tools/prepare-strict-test-env.js +124 -0
  129. package/tools/prepare-test-env.d.ts +2 -0
  130. package/tools/prepare-test-env.d.ts.map +1 -0
  131. package/tools/prepare-test-env.js +13 -0
  132. package/tools/setup-vat-data.d.ts +9 -0
  133. package/tools/setup-vat-data.d.ts.map +1 -0
  134. package/tools/setup-vat-data.js +95 -0
  135. package/tools/vo-test-harness.d.ts +33 -0
  136. package/tools/vo-test-harness.d.ts.map +1 -0
  137. package/tools/vo-test-harness.js +164 -0
  138. package/CHANGELOG.md +0 -61
  139. package/test/kmarshal.js +0 -79
  140. package/test/test-handled-promises.js +0 -360
  141. package/test/virtual-objects/test-state-shape.js +0 -298
package/src/liveslots.js CHANGED
@@ -1,10 +1,7 @@
1
- import {
2
- Remotable,
3
- passStyleOf,
4
- getInterfaceOf,
5
- makeMarshal,
6
- } from '@endo/marshal';
7
- import { assert, Fail } from '@agoric/assert';
1
+ import { annotateError, assert, Fail, makeError, X } from '@endo/errors';
2
+ import { passStyleOf } from '@endo/pass-style';
3
+ import { PassStyleOfEndowmentSymbol } from '@endo/pass-style/endow.js';
4
+ import { Remotable, getInterfaceOf, makeMarshal } from '@endo/marshal';
8
5
  import { isPromise } from '@endo/promise-kit';
9
6
  import { E, HandledPromise } from '@endo/eventual-send';
10
7
  import { insistVatType, makeVatSlot, parseVatSlot } from './parseVatSlots.js';
@@ -15,12 +12,13 @@ import { makeVirtualReferenceManager } from './virtualReferences.js';
15
12
  import { makeVirtualObjectManager } from './virtualObjectManager.js';
16
13
  import { makeCollectionManager } from './collectionManager.js';
17
14
  import { makeWatchedPromiseManager } from './watchedPromises.js';
15
+ import { makeBOYDKit } from './boyd-gc.js';
16
+
17
+ /** @import {LimitedConsole} from '@agoric/internal/src/js-utils.js'; */
18
18
 
19
19
  const SYSCALL_CAPDATA_BODY_SIZE_LIMIT = 10_000_000;
20
20
  const SYSCALL_CAPDATA_SLOTS_LENGTH_LIMIT = 10_000;
21
21
 
22
- const { details: X } = assert;
23
-
24
22
  // 'makeLiveSlots' is a dispatcher which uses javascript Maps to keep track
25
23
  // of local objects which have been exported. These cannot be persisted
26
24
  // beyond the runtime of the javascript environment, so this mechanism is not
@@ -33,10 +31,10 @@ const { details: X } = assert;
33
31
  * @param {*} syscall Kernel syscall interface that the vat will have access to
34
32
  * @param {*} forVatID Vat ID label, for use in debug diagnostics
35
33
  * @param {*} vatPowers
36
- * @param {import('./types').LiveSlotsOptions} liveSlotsOptions
34
+ * @param {import('./types.js').LiveSlotsOptions} liveSlotsOptions
37
35
  * @param {*} gcTools { WeakRef, FinalizationRegistry, waitUntilQuiescent, gcAndFinalize,
38
36
  * meterControl }
39
- * @param {Pick<Console, 'debug' | 'log' | 'info' | 'warn' | 'error'>} console
37
+ * @param {LimitedConsole} console
40
38
  * @param {*} buildVatNamespace
41
39
  *
42
40
  * @returns {*} { dispatch }
@@ -159,10 +157,8 @@ function build(
159
157
  const { type, allocatedByVat, virtual, durable } = parseVatSlot(vref);
160
158
  if (type === 'object' && allocatedByVat) {
161
159
  if (virtual || durable) {
162
- // eslint-disable-next-line no-use-before-define
163
160
  vrm.setExportStatus(vref, 'reachable');
164
161
  } else {
165
- // eslint-disable-next-line no-use-before-define
166
162
  const remotable = requiredValForSlot(vref);
167
163
  exportedRemotables.add(remotable);
168
164
  kernelRecognizableRemotables.add(vref);
@@ -170,52 +166,6 @@ function build(
170
166
  }
171
167
  }
172
168
 
173
- /*
174
- Imports are in one of 5 states: UNKNOWN, REACHABLE, UNREACHABLE,
175
- COLLECTED, FINALIZED. Note that there's no actual state machine with those
176
- values, and we can't observe all of the transitions from JavaScript, but
177
- we can describe what operations could cause a transition, and what our
178
- observations allow us to deduce about the state:
179
-
180
- * UNKNOWN moves to REACHABLE when a crank introduces a new import
181
- * userspace holds a reference only in REACHABLE
182
- * REACHABLE moves to UNREACHABLE only during a userspace crank
183
- * UNREACHABLE moves to COLLECTED when GC runs, which queues the finalizer
184
- * COLLECTED moves to FINALIZED when a new turn runs the finalizer
185
- * liveslots moves from FINALIZED to UNKNOWN by syscalling dropImports
186
-
187
- convertSlotToVal either imports a vref for the first time, or
188
- re-introduces a previously-seen vref. It transitions from:
189
-
190
- * UNKNOWN to REACHABLE by creating a new Presence
191
- * UNREACHABLE to REACHABLE by re-using the old Presence that userspace
192
- forgot about
193
- * COLLECTED/FINALIZED to REACHABLE by creating a new Presence
194
-
195
- Our tracking tables hold data that depends on the current state:
196
-
197
- * slotToVal holds a WeakRef in [REACHABLE, UNREACHABLE, COLLECTED]
198
- * that WeakRef .deref()s into something in [REACHABLE, UNREACHABLE]
199
- * deadSet holds the vref only in FINALIZED
200
- * re-introduction must ensure the vref is not in the deadSet
201
-
202
- Each state thus has a set of perhaps-measurable properties:
203
-
204
- * UNKNOWN: slotToVal[baseRef] is missing, baseRef not in deadSet
205
- * REACHABLE: slotToVal has live weakref, userspace can reach
206
- * UNREACHABLE: slotToVal has live weakref, userspace cannot reach
207
- * COLLECTED: slotToVal[baseRef] has dead weakref
208
- * FINALIZED: slotToVal[baseRef] is missing, baseRef is in deadSet
209
-
210
- Our finalizer callback is queued by the engine's transition from
211
- UNREACHABLE to COLLECTED, but the baseRef might be re-introduced before the
212
- callback has a chance to run. There might even be multiple copies of the
213
- finalizer callback queued. So the callback must deduce the current state
214
- and only perform cleanup (i.e. delete the slotToVal entry and add the
215
- baseRef to the deadSet) in the COLLECTED state.
216
-
217
- */
218
-
219
169
  function finalizeDroppedObject(baseRef) {
220
170
  // TODO: Ideally this function should assert that it is not metered. This
221
171
  // appears to be fine in practice, but it breaks a number of unit tests in
@@ -233,124 +183,12 @@ function build(
233
183
 
234
184
  if (wr && !wr.deref()) {
235
185
  // we're in the COLLECTED state, or FINALIZED after a re-introduction
236
- // eslint-disable-next-line no-use-before-define
237
186
  addToPossiblyDeadSet(baseRef);
238
187
  slotToVal.delete(baseRef);
239
188
  }
240
189
  }
241
190
  const vreffedObjectRegistry = new FinalizationRegistry(finalizeDroppedObject);
242
191
 
243
- async function scanForDeadObjects() {
244
- // `possiblyDeadSet` accumulates vrefs which have lost a supporting
245
- // pillar (in-memory, export, or virtualized data refcount) since the
246
- // last call to scanForDeadObjects. The vref might still be supported
247
- // by a remaining pillar, or the pillar which was dropped might be back
248
- // (e.g., given a new in-memory manifestation).
249
-
250
- const importsToDrop = new Set();
251
- const importsToRetire = new Set();
252
- const exportsToRetire = new Set();
253
- let doMore;
254
- do {
255
- doMore = false;
256
-
257
- // Yes, we know this is an await inside a loop. Too bad. (Also, it's a
258
- // `do {} while` loop, which means there's no conditional bypass of the
259
- // await.)
260
- // eslint-disable-next-line no-await-in-loop, @jessie.js/no-nested-await
261
- await gcTools.gcAndFinalize();
262
-
263
- // possiblyDeadSet contains a baseref for everything (Presences,
264
- // Remotables, Representatives) that might have lost a
265
- // pillar. The object might still be supported by other pillars,
266
- // and the lost pillar might have been reinstantiated by the
267
- // time we get here. The first step is to filter this down to a
268
- // list of definitely dead baserefs.
269
-
270
- const deadSet = new Set();
271
-
272
- for (const baseRef of possiblyDeadSet) {
273
- // eslint-disable-next-line no-use-before-define
274
- if (slotToVal.has(baseRef)) {
275
- continue; // RAM pillar remains
276
- }
277
- const { virtual, durable, type } = parseVatSlot(baseRef);
278
- assert(type === 'object', `unprepared to track ${type}`);
279
- if (virtual || durable) {
280
- // eslint-disable-next-line no-use-before-define
281
- if (vrm.isVirtualObjectReachable(baseRef)) {
282
- continue; // vdata or export pillar remains
283
- }
284
- }
285
- deadSet.add(baseRef);
286
- }
287
- possiblyDeadSet.clear();
288
-
289
- // deadSet now contains objects which are certainly dead
290
-
291
- // possiblyRetiredSet holds (a subset of??) baserefs which have
292
- // lost a recognizer recently. TODO recheck this
293
-
294
- for (const vref of possiblyRetiredSet) {
295
- // eslint-disable-next-line no-use-before-define
296
- if (!getValForSlot(vref) && !deadSet.has(vref)) {
297
- // Don't retire things that haven't yet made the transition to dead,
298
- // i.e., always drop before retiring
299
- // eslint-disable-next-line no-use-before-define
300
- if (!vrm.isVrefRecognizable(vref)) {
301
- importsToRetire.add(vref);
302
- }
303
- }
304
- }
305
- possiblyRetiredSet.clear();
306
-
307
- const deadBaseRefs = Array.from(deadSet);
308
- deadBaseRefs.sort();
309
- for (const baseRef of deadBaseRefs) {
310
- const { virtual, durable, allocatedByVat, type } =
311
- parseVatSlot(baseRef);
312
- type === 'object' || Fail`unprepared to track ${type}`;
313
- if (virtual || durable) {
314
- // Representative: send nothing, but perform refcount checking
315
- // eslint-disable-next-line no-use-before-define
316
- const [gcAgain, retirees] = vrm.deleteVirtualObject(baseRef);
317
- if (retirees) {
318
- retirees.map(retiree => exportsToRetire.add(retiree));
319
- }
320
- doMore = doMore || gcAgain;
321
- } else if (allocatedByVat) {
322
- // Remotable: send retireExport
323
- // for remotables, vref === baseRef
324
- if (kernelRecognizableRemotables.has(baseRef)) {
325
- kernelRecognizableRemotables.delete(baseRef);
326
- exportsToRetire.add(baseRef);
327
- }
328
- } else {
329
- // Presence: send dropImport unless reachable by VOM
330
- // eslint-disable-next-line no-lonely-if, no-use-before-define
331
- if (!vrm.isPresenceReachable(baseRef)) {
332
- importsToDrop.add(baseRef);
333
- // eslint-disable-next-line no-use-before-define
334
- if (!vrm.isVrefRecognizable(baseRef)) {
335
- // for presences, baseRef === vref
336
- importsToRetire.add(baseRef);
337
- }
338
- }
339
- }
340
- }
341
- } while (possiblyDeadSet.size > 0 || possiblyRetiredSet.size > 0 || doMore);
342
-
343
- if (importsToDrop.size) {
344
- syscall.dropImports(Array.from(importsToDrop).sort());
345
- }
346
- if (importsToRetire.size) {
347
- syscall.retireImports(Array.from(importsToRetire).sort());
348
- }
349
- if (exportsToRetire.size) {
350
- syscall.retireExports(Array.from(exportsToRetire).sort());
351
- }
352
- }
353
-
354
192
  /**
355
193
  * Remember disavowed Presences which will kill the vat if you try to talk
356
194
  * to them
@@ -370,11 +208,9 @@ function build(
370
208
  // Support: o~.[prop](...args) remote method invocation
371
209
  lsdebug(`makeImportedPresence handler.applyMethod (${slot})`);
372
210
  if (disavowedPresences.has(o)) {
373
- // eslint-disable-next-line no-use-before-define
374
211
  exitVatWithFailure(disavowalError);
375
212
  throw disavowalError;
376
213
  }
377
- // eslint-disable-next-line no-use-before-define
378
214
  return queueMessage(slot, prop, args, returnedP);
379
215
  },
380
216
  applyFunction(o, args, returnedP) {
@@ -383,7 +219,6 @@ function build(
383
219
  get(o, prop) {
384
220
  lsdebug(`makeImportedPresence handler.get (${slot})`);
385
221
  if (disavowedPresences.has(o)) {
386
- // eslint-disable-next-line no-use-before-define
387
222
  exitVatWithFailure(disavowalError);
388
223
  throw disavowalError;
389
224
  }
@@ -455,7 +290,6 @@ function build(
455
290
  console.error(`mIPromise handler called after resolution`);
456
291
  Fail`mIPromise handler called after resolution`;
457
292
  }
458
- // eslint-disable-next-line no-use-before-define
459
293
  return queueMessage(vpid, prop, args, returnedP);
460
294
  },
461
295
  get(p, prop) {
@@ -500,54 +334,6 @@ function build(
500
334
  return Remotable(iface);
501
335
  }
502
336
 
503
- /**
504
- * Counters to track the next number for various categories of allocation.
505
- * `exportID` starts at 1 because 'o+0' is always automatically
506
- * pre-assigned to the root object.
507
- * `promiseID` starts at 5 as a very minor aid to debugging: when puzzling
508
- * over trace logs and the like, it helps for the numbers in various species
509
- * of IDs that are jumbled together to be a little out of sync and thus a
510
- * little less similar to each other.
511
- */
512
- const initialIDCounters = { exportID: 1, collectionID: 1, promiseID: 5 };
513
- /** @type {Record<string, number>} */
514
- let idCounters;
515
- let idCountersAreDirty = false;
516
-
517
- function initializeIDCounters() {
518
- if (!idCounters) {
519
- // the saved value might be missing, or from an older liveslots
520
- // (with fewer counters), so merge it with our initial values
521
- const saved = JSON.parse(syscall.vatstoreGet('idCounters') || '{}');
522
- idCounters = { ...initialIDCounters, ...saved };
523
- idCountersAreDirty = true;
524
- }
525
- }
526
-
527
- function allocateNextID(name) {
528
- if (!idCounters) {
529
- // Normally `initializeIDCounters` would be called from startVat, but some
530
- // tests bypass that so this is a backstop. Note that the invocation from
531
- // startVat is there to make vatStore access patterns a bit more
532
- // consistent from one vat to another, principally as a confusion
533
- // reduction measure in service of debugging; it is not a correctness
534
- // issue.
535
- initializeIDCounters();
536
- }
537
- const result = idCounters[name];
538
- result !== undefined || Fail`unknown idCounters[${name}]`;
539
- idCounters[name] += 1;
540
- idCountersAreDirty = true;
541
- return result;
542
- }
543
-
544
- function flushIDCounters() {
545
- if (idCountersAreDirty) {
546
- syscall.vatstoreSet('idCounters', JSON.stringify(idCounters));
547
- idCountersAreDirty = false;
548
- }
549
- }
550
-
551
337
  // TODO: fix awkward non-orthogonality: allocateExportID() returns a number,
552
338
  // allocatePromiseID() returns a slot, registerPromise() uses the slot from
553
339
  // allocatePromiseID(), exportPassByPresence() generates a slot itself using
@@ -556,15 +342,15 @@ function build(
556
342
  // use a slot from the corresponding allocateX
557
343
 
558
344
  function allocateExportID() {
559
- return allocateNextID('exportID');
345
+ return vrm.allocateNextID('exportID');
560
346
  }
561
347
 
562
348
  function allocateCollectionID() {
563
- return allocateNextID('collectionID');
349
+ return vrm.allocateNextID('collectionID');
564
350
  }
565
351
 
566
352
  function allocatePromiseID() {
567
- const promiseID = allocateNextID('promiseID');
353
+ const promiseID = vrm.allocateNextID('promiseID');
568
354
  return makeVatSlot('promise', true, promiseID);
569
355
  }
570
356
 
@@ -591,10 +377,8 @@ function build(
591
377
  // do a syscall.resolve when it fires. The caller must finish
592
378
  // doing their syscall before this turn finishes, to ensure the
593
379
  // kernel isn't surprised by a spurious resolution.
594
- // eslint-disable-next-line no-use-before-define
595
380
  const p = requiredValForSlot(vpid);
596
381
  // if (!knownResolutions.has(p)) { // TODO really?
597
- // eslint-disable-next-line no-use-before-define
598
382
  followForKernel(vpid, p);
599
383
  return true;
600
384
  }
@@ -606,20 +390,18 @@ function build(
606
390
  return makeVatSlot('object', true, exportID);
607
391
  }
608
392
 
609
- // eslint-disable-next-line no-use-before-define
610
393
  const m = makeMarshal(convertValToSlot, convertSlotToVal, {
611
394
  marshalName: `liveSlots:${forVatID}`,
612
395
  serializeBodyFormat: 'smallcaps',
613
396
  // TODO Temporary hack.
614
397
  // See https://github.com/Agoric/agoric-sdk/issues/2780
615
- errorIdNum: 70000,
398
+ errorIdNum: 70_000,
616
399
  marshalSaveError: err =>
617
400
  // By sending this to `console.warn`, under cosmic-swingset this is
618
401
  // controlled by the `console` option given to makeLiveSlots.
619
402
  console.warn('Logging sent error stack', err),
620
403
  });
621
404
  const unmeteredUnserialize = meterControl.unmetered(m.unserialize);
622
- // eslint-disable-next-line no-use-before-define
623
405
  const unmeteredConvertSlotToVal = meterControl.unmetered(convertSlotToVal);
624
406
 
625
407
  function getSlotForVal(val) {
@@ -664,7 +446,6 @@ function build(
664
446
  allocateExportID,
665
447
  getSlotForVal,
666
448
  requiredValForSlot,
667
- // eslint-disable-next-line no-use-before-define
668
449
  registerValue,
669
450
  m.serialize,
670
451
  unmeteredUnserialize,
@@ -677,10 +458,8 @@ function build(
677
458
  vrm,
678
459
  allocateExportID,
679
460
  allocateCollectionID,
680
- // eslint-disable-next-line no-use-before-define
681
461
  convertValToSlot,
682
462
  unmeteredConvertSlotToVal,
683
- // eslint-disable-next-line no-use-before-define
684
463
  registerValue,
685
464
  m.serialize,
686
465
  unmeteredUnserialize,
@@ -692,7 +471,6 @@ function build(
692
471
  vrm,
693
472
  vom,
694
473
  collectionManager,
695
- // eslint-disable-next-line no-use-before-define
696
474
  convertValToSlot,
697
475
  convertSlotToVal: unmeteredConvertSlotToVal,
698
476
  maybeExportPromise,
@@ -722,7 +500,6 @@ function build(
722
500
  slot = allocatePromiseID();
723
501
  } else {
724
502
  if (disavowedPresences.has(val)) {
725
- // eslint-disable-next-line no-use-before-define
726
503
  exitVatWithFailure(disavowalError);
727
504
  throw disavowalError; // cannot reference a disavowed object
728
505
  }
@@ -759,9 +536,9 @@ function build(
759
536
  Fail`registerValue(${baseRef} should not receive individual facets`;
760
537
  slotToVal.set(baseRef, new WeakRef(val));
761
538
  if (valIsCohort) {
762
- vrm.getFacetNames(id).forEach((name, index) => {
539
+ for (const [index, name] of vrm.getFacetNames(id).entries()) {
763
540
  valToSlot.set(val[name], `${baseRef}:${index}`);
764
- });
541
+ }
765
542
  } else {
766
543
  valToSlot.set(val, baseRef);
767
544
  }
@@ -796,39 +573,50 @@ function build(
796
573
  try {
797
574
  val = vrm.reanimate(baseRef);
798
575
  } catch (err) {
799
- const wrappedError = assert.error(X`failed to reanimate ${iface}`);
800
- assert.note(wrappedError, X`Original error: ${err}`);
576
+ const wrappedError = makeError(X`failed to reanimate ${iface}`);
577
+ annotateError(wrappedError, X`Original error: ${err}`);
801
578
  throw wrappedError;
802
579
  }
803
580
  if (facet !== undefined) {
804
581
  result = vrm.getFacet(id, val, facet);
805
582
  }
806
- } else {
583
+ } else if (type === 'object') {
584
+ // Note: an abandonned (e.g. by an upgrade) exported ephemeral or virtual
585
+ // object would appear as an import if re-introduced. In the future we
586
+ // may need to change that if we want to keep recognizing such references
587
+ // In that case we'd need to create an imported presence for these
588
+ // unknown vrefs allocated by the vat.
589
+ // See https://github.com/Agoric/agoric-sdk/issues/9746
807
590
  !allocatedByVat || Fail`I don't remember allocating ${slot}`;
808
- if (type === 'object') {
809
- // this is a new import value
810
- val = makeImportedPresence(slot, iface);
811
- } else if (type === 'promise') {
812
- const pRec = makePipelinablePromise(slot);
813
- importedVPIDs.set(slot, pRec);
814
- val = pRec.promise;
815
- // ideally we'd wait until .then is called on p before subscribing,
816
- // but the current Promise API doesn't give us a way to discover
817
- // this, so we must subscribe right away. If we were using Vows or
818
- // some other then-able, we could just hook then() to notify us.
819
- if (importedPromises) {
820
- // leave the subscribe() up to dispatch.notify()
821
- importedPromises.add(slot);
822
- } else {
823
- // probably in dispatch.deliver(), so subscribe now
824
- syscall.subscribe(slot);
825
- }
826
- } else if (type === 'device') {
827
- val = makeDeviceNode(slot, iface);
828
- importedDevices.add(val);
591
+ // this is a new import value
592
+ val = makeImportedPresence(slot, iface);
593
+ } else if (type === 'promise') {
594
+ // We unconditionally create a promise record, even if the promise looks
595
+ // like it was allocated by us. This can happen when re-importing a
596
+ // promise created by the previous incarnation. We may or may not have
597
+ // been the decider of the promise. If we were, the kernel will be
598
+ // rejecting the promise on our behalf. We may have previously been
599
+ // subscribed to that promise, but subscription is idempotent.
600
+ const pRec = makePipelinablePromise(slot);
601
+ importedVPIDs.set(slot, pRec);
602
+ val = pRec.promise;
603
+ // ideally we'd wait until .then is called on p before subscribing,
604
+ // but the current Promise API doesn't give us a way to discover
605
+ // this, so we must subscribe right away. If we were using Vows or
606
+ // some other then-able, we could just hook then() to notify us.
607
+ if (importedPromises) {
608
+ // leave the subscribe() up to dispatch.notify()
609
+ importedPromises.add(slot);
829
610
  } else {
830
- Fail`unrecognized slot type '${type}'`;
611
+ // probably in dispatch.deliver(), so subscribe now
612
+ syscall.subscribe(slot);
831
613
  }
614
+ } else if (type === 'device') {
615
+ !allocatedByVat || Fail`unexpected device ${slot} allocated by vat`;
616
+ val = makeDeviceNode(slot, iface);
617
+ importedDevices.add(val);
618
+ } else {
619
+ Fail`unrecognized slot type '${type}'`;
832
620
  }
833
621
  registerValue(baseRef, val, facet !== undefined);
834
622
  if (!result) {
@@ -841,7 +629,25 @@ function build(
841
629
  meterControl.assertNotMetered();
842
630
  const { type } = parseVatSlot(slot);
843
631
  type === 'promise' || Fail`revivePromise called on non-promise ${slot}`;
844
- !getValForSlot(slot) || Fail`revivePromise called on pre-existing ${slot}`;
632
+ const val = getValForSlot(slot);
633
+ if (val) {
634
+ // revivePromise is only called by loadWatchedPromiseTable, which runs
635
+ // after buildRootObject(), which is given the deserialized vatParameters.
636
+ // The only way revivePromise() might encounter a pre-existing vpid is if
637
+ // these vatParameters include a promise that the previous incarnation
638
+ // watched, but that `buildRootObject` in the new incarnation didn't
639
+ // explicitly watch again. This can be either a previously imported
640
+ // promise, or a promise the previous incarnation exported, regardless of
641
+ // who the decider now is.
642
+ //
643
+ // In that case convertSlotToVal() has already deserialized the vpid, but
644
+ // since `buildRootObject` didn't explicitely call watchPromise on it, no
645
+ // registration exists so loadWatchedPromiseTable attempts to revive the
646
+ // promise.
647
+ return val;
648
+ }
649
+ // NOTE: it is important that this code not do anything *more*
650
+ // than what convertSlotToVal(vpid) would do
845
651
  const pRec = makePipelinablePromise(slot);
846
652
  importedVPIDs.set(slot, pRec);
847
653
  const p = pRec.promise;
@@ -863,7 +669,6 @@ function build(
863
669
  const priorResolution = knownResolutions.get(p);
864
670
  if (priorResolution && !doneResolutions.has(slot)) {
865
671
  const [priorRejected, priorRes] = priorResolution;
866
- // eslint-disable-next-line no-use-before-define
867
672
  collect(slot, priorRejected, priorRes);
868
673
  }
869
674
  }
@@ -983,12 +788,21 @@ function build(
983
788
  return null;
984
789
  }
985
790
  syscall.resolve(resolutions);
986
- resolutions.forEach(([_xvpid, _isReject, resolutionCD]) => {
987
- resolutionCD.slots.forEach(vref => maybeNewVPIDs.add(vref));
988
- });
989
- resolutions.forEach(([xvpid]) => maybeNewVPIDs.delete(xvpid));
791
+ for (const resolution of resolutions) {
792
+ const [_xvpid, _isReject, resolutionCD] = resolution;
793
+ for (const vref of resolutionCD.slots) {
794
+ maybeNewVPIDs.add(vref);
795
+ }
796
+ }
797
+ for (const resolution of resolutions) {
798
+ const [xvpid] = resolution;
799
+ maybeNewVPIDs.delete(xvpid);
800
+ unregisterUnreferencedVPID(xvpid);
801
+ }
802
+ }
803
+ for (const newVPID of Array.from(maybeNewVPIDs).sort()) {
804
+ maybeExportPromise(newVPID);
990
805
  }
991
- Array.from(maybeNewVPIDs).sort().forEach(maybeExportPromise);
992
806
 
993
807
  // ideally we'd wait until .then is called on p before subscribing, but
994
808
  // the current Promise API doesn't give us a way to discover this, so we
@@ -1141,7 +955,6 @@ function build(
1141
955
  }
1142
956
  // in both cases, we are now the decider, so treat it like an
1143
957
  // exported promise
1144
- // eslint-disable-next-line no-use-before-define
1145
958
  followForKernel(resultVPID, p);
1146
959
  }
1147
960
  }
@@ -1190,16 +1003,23 @@ function build(
1190
1003
 
1191
1004
  const maybeNewVPIDs = new Set();
1192
1005
  // if we mention a vpid, we might need to track it
1193
- resolutions.forEach(([_xvpid, _isReject, resolutionCD]) => {
1194
- resolutionCD.slots.forEach(vref => maybeNewVPIDs.add(vref));
1195
- });
1006
+ for (const resolution of resolutions) {
1007
+ const [_xvpid, _isReject, resolutionCD] = resolution;
1008
+ for (const vref of resolutionCD.slots) {
1009
+ maybeNewVPIDs.add(vref);
1010
+ }
1011
+ }
1196
1012
  // but not if we just resolved it (including the primary)
1197
- resolutions.forEach(([xvpid]) => maybeNewVPIDs.delete(xvpid));
1013
+ for (const resolution of resolutions) {
1014
+ const [xvpid] = resolution;
1015
+ maybeNewVPIDs.delete(xvpid);
1016
+ unregisterUnreferencedVPID(xvpid);
1017
+ }
1198
1018
  // track everything that's left
1199
- Array.from(maybeNewVPIDs).sort().forEach(maybeExportPromise);
1019
+ for (const newVPID of Array.from(maybeNewVPIDs).sort()) {
1020
+ maybeExportPromise(newVPID);
1021
+ }
1200
1022
 
1201
- // only the primary can possibly be newly resolved
1202
- unregisterUnreferencedVPID(vpid);
1203
1023
  exportedVPIDs.delete(vpid);
1204
1024
  }
1205
1025
 
@@ -1254,11 +1074,11 @@ function build(
1254
1074
  // 'imports' is an exclusively-owned Set that holds all new
1255
1075
  // promise vpids, both resolved and unresolved
1256
1076
  const imports = finishCollectingPromiseImports();
1257
- retiredVPIDs.forEach(vpid => {
1077
+ for (const vpid of retiredVPIDs) {
1258
1078
  unregisterUnreferencedVPID(vpid); // unregisters if not in vdata
1259
1079
  importedVPIDs.delete(vpid);
1260
1080
  imports.delete(vpid); // resolved, so don't subscribe()
1261
- });
1081
+ }
1262
1082
  for (const vpid of Array.from(imports).sort()) {
1263
1083
  syscall.subscribe(vpid);
1264
1084
  }
@@ -1298,14 +1118,18 @@ function build(
1298
1118
 
1299
1119
  function retireExports(vrefs) {
1300
1120
  assert(Array.isArray(vrefs));
1301
- vrefs.forEach(retireOneExport);
1121
+ for (const vref of vrefs) {
1122
+ retireOneExport(vref);
1123
+ }
1302
1124
  }
1303
1125
 
1304
1126
  function retireImports(vrefs) {
1305
1127
  assert(Array.isArray(vrefs));
1306
1128
  vrefs.map(vref => insistVatType('object', vref));
1307
1129
  vrefs.map(vref => assert(!parseVatSlot(vref).allocatedByVat));
1308
- vrefs.forEach(vrm.ceaseRecognition);
1130
+ for (const vref of vrefs) {
1131
+ vrm.ceaseRecognition(vref);
1132
+ }
1309
1133
  }
1310
1134
 
1311
1135
  // TODO: when we add notifyForward, guard against cycles
@@ -1370,6 +1194,7 @@ function build(
1370
1194
  const inescapableGlobalProperties = harden({
1371
1195
  WeakMap: vom.VirtualObjectAwareWeakMap,
1372
1196
  WeakSet: vom.VirtualObjectAwareWeakSet,
1197
+ [PassStyleOfEndowmentSymbol]: passStyleOf,
1373
1198
  });
1374
1199
 
1375
1200
  function getRetentionStats() {
@@ -1405,7 +1230,6 @@ function build(
1405
1230
  possiblyRetiredSet,
1406
1231
  slotToVal,
1407
1232
  valToSlot,
1408
- // eslint-disable-next-line no-use-before-define
1409
1233
  afterDispatchActions,
1410
1234
  });
1411
1235
 
@@ -1450,7 +1274,7 @@ function build(
1450
1274
  }
1451
1275
  harden(vpow);
1452
1276
 
1453
- initializeIDCounters();
1277
+ vrm.initializeIDCounters();
1454
1278
  vom.initializeKindHandleKind();
1455
1279
  collectionManager.initializeStoreKindInfo();
1456
1280
 
@@ -1496,8 +1320,8 @@ function build(
1496
1320
  }
1497
1321
 
1498
1322
  /**
1499
- * @param {import('./types').VatDeliveryObject} delivery
1500
- * @returns {void | Promise<void>}
1323
+ * @param {import('./types.js').VatDeliveryObject} delivery
1324
+ * @returns {undefined | ReturnType<startVat>}
1501
1325
  */
1502
1326
  function dispatchToUserspace(delivery) {
1503
1327
  let result;
@@ -1549,19 +1373,18 @@ function build(
1549
1373
  // metered
1550
1374
  const unmeteredDispatch = meterControl.unmetered(dispatchToUserspace);
1551
1375
 
1552
- async function bringOutYourDead() {
1553
- await scanForDeadObjects();
1554
- // Now flush all the vatstore changes (deletions and refcounts) we
1555
- // made. dispatch() calls afterDispatchActions() automatically for
1556
- // most methods, but not bringOutYourDead().
1557
- // eslint-disable-next-line no-use-before-define
1558
- afterDispatchActions();
1559
- // XXX TODO: make this conditional on a config setting
1560
- return getRetentionStats();
1561
- }
1376
+ const { scanForDeadObjects } = makeBOYDKit({
1377
+ gcTools,
1378
+ slotToVal,
1379
+ vrm,
1380
+ kernelRecognizableRemotables,
1381
+ syscall,
1382
+ possiblyDeadSet,
1383
+ possiblyRetiredSet,
1384
+ });
1562
1385
 
1563
1386
  /**
1564
- * @param { import('./types').SwingSetCapData } _disconnectObjectCapData
1387
+ * @param { import('./types.js').SwingSetCapData } _disconnectObjectCapData
1565
1388
  * @returns {Promise<void>}
1566
1389
  */
1567
1390
  async function stopVat(_disconnectObjectCapData) {
@@ -1573,11 +1396,21 @@ function build(
1573
1396
  * dispatch has completed and user code has relinquished agency.
1574
1397
  */
1575
1398
  function afterDispatchActions() {
1576
- flushIDCounters();
1399
+ vrm.flushIDCounters();
1577
1400
  collectionManager.flushSchemaCache();
1578
1401
  vom.flushStateCache();
1579
1402
  }
1580
1403
 
1404
+ const bringOutYourDead = async () => {
1405
+ await scanForDeadObjects();
1406
+ // Now flush all the vatstore changes (deletions and refcounts) we
1407
+ // made. dispatch() calls afterDispatchActions() automatically for
1408
+ // most methods, but not bringOutYourDead().
1409
+ afterDispatchActions();
1410
+ // XXX TODO: make this conditional on a config setting
1411
+ return getRetentionStats();
1412
+ };
1413
+
1581
1414
  /**
1582
1415
  * This 'dispatch' function is the entry point for the vat as a whole: the
1583
1416
  * vat-worker supervisor gives us VatDeliveryObjects (like
@@ -1613,7 +1446,7 @@ function build(
1613
1446
  * terminate the vat). Userspace should not be able to cause the delivery
1614
1447
  * to fail: only a bug in liveslots should trigger a failure.
1615
1448
  *
1616
- * @param {import('./types').VatDeliveryObject} delivery
1449
+ * @param {import('./types.js').VatDeliveryObject} delivery
1617
1450
  * @returns {Promise<void>}
1618
1451
  */
1619
1452
  async function dispatch(delivery) {
@@ -1624,12 +1457,10 @@ function build(
1624
1457
  } else if (delivery[0] === 'stopVat') {
1625
1458
  return meterControl.runWithoutMeteringAsync(() => stopVat(delivery[1]));
1626
1459
  } else {
1627
- let complete = false;
1628
1460
  // Start user code running, record any internal liveslots errors. We do
1629
1461
  // *not* directly wait for the userspace function to complete, nor for
1630
1462
  // any promise it returns to fire.
1631
1463
  const p = Promise.resolve(delivery).then(unmeteredDispatch);
1632
- p.finally(() => (complete = true));
1633
1464
 
1634
1465
  // Instead, we wait for userspace to become idle by draining the
1635
1466
  // promise queue. We clean up and then examine/return 'p' so any
@@ -1640,10 +1471,11 @@ function build(
1640
1471
  return gcTools.waitUntilQuiescent().then(() => {
1641
1472
  afterDispatchActions();
1642
1473
  // eslint-disable-next-line prefer-promise-reject-errors
1643
- return complete ? p : Promise.reject('buildRootObject unresolved');
1474
+ return Promise.race([p, Promise.reject('buildRootObject unresolved')]);
1644
1475
  // the only delivery that pays attention to a user-provided
1645
1476
  // Promise is startVat, so the error message is specialized to
1646
- // the only user problem that could cause complete===false
1477
+ // the only user problem that could cause the promise to not be
1478
+ // settled.
1647
1479
  });
1648
1480
  }
1649
1481
  }
@@ -1664,9 +1496,9 @@ function build(
1664
1496
  * @param {*} syscall Kernel syscall interface that the vat will have access to
1665
1497
  * @param {*} forVatID Vat ID label, for use in debug diagostics
1666
1498
  * @param {*} vatPowers
1667
- * @param {import('./types').LiveSlotsOptions} liveSlotsOptions
1499
+ * @param {import('./types.js').LiveSlotsOptions} liveSlotsOptions
1668
1500
  * @param {*} gcTools { WeakRef, FinalizationRegistry, waitUntilQuiescent }
1669
- * @param {Pick<Console, 'debug' | 'log' | 'info' | 'warn' | 'error'>} [liveSlotsConsole]
1501
+ * @param {LimitedConsole} [liveSlotsConsole]
1670
1502
  * @param {*} [buildVatNamespace]
1671
1503
  *
1672
1504
  * @returns {*} { dispatch }