Skip to content

Commit

Permalink
feat: wrap WeakMap and WeakSet to hide virtual object non-determinism
Browse files Browse the repository at this point in the history
  • Loading branch information
FUDCo committed May 18, 2021
1 parent cbabcc9 commit bd421ff
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 11 deletions.
36 changes: 31 additions & 5 deletions packages/SwingSet/src/kernel/liveSlots.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const DEFAULT_VIRTUAL_OBJECT_CACHE_SIZE = 3; // XXX ridiculously small value to
* @param {*} vatParameters
* @param {*} gcTools { WeakRef, FinalizationRegistry, waitUntilQuiescent }
* @param {Console} console
* @returns {*} { vatGlobals, dispatch, setBuildRootObject }
* @returns {*} { vatGlobals, inescapableGlobalLexicals, dispatch, setBuildRootObject }
*
* setBuildRootObject should be called, once, with a function that will
* create a root object for the new vat The caller provided buildRootObject
Expand Down Expand Up @@ -342,6 +342,8 @@ function build(
makeVirtualObjectRepresentative,
makeWeakStore,
makeKind,
RepairedWeakMap,
RepairedWeakSet,
} = makeVirtualObjectManager(
syscall,
allocateExportID,
Expand Down Expand Up @@ -801,6 +803,11 @@ function build(
makeKind,
});

const inescapableGlobalLexicals = harden({
WeakMap: RepairedWeakMap,
WeakSet: RepairedWeakSet,
});

function setBuildRootObject(buildRootObject) {
assert(!didRoot);
didRoot = true;
Expand Down Expand Up @@ -892,7 +899,14 @@ function build(
harden(dispatch);

// we return 'deadSet' for unit tests
return harden({ vatGlobals, setBuildRootObject, dispatch, m, deadSet });
return harden({
vatGlobals,
inescapableGlobalLexicals,
setBuildRootObject,
dispatch,
m,
deadSet,
});
}

/**
Expand All @@ -907,7 +921,7 @@ function build(
* @param {boolean} enableDisavow
* @param {*} gcTools { WeakRef, FinalizationRegistry, waitUntilQuiescent }
* @param {Console} [liveSlotsConsole]
* @returns {*} { vatGlobals, dispatch, setBuildRootObject }
* @returns {*} { vatGlobals, inescapableGlobalLexicals, dispatch, setBuildRootObject }
*
* setBuildRootObject should be called, once, with a function that will
* create a root object for the new vat The caller provided buildRootObject
Expand Down Expand Up @@ -955,8 +969,20 @@ export function makeLiveSlots(
gcTools,
liveSlotsConsole,
);
const { vatGlobals, dispatch, setBuildRootObject, deadSet } = r; // omit 'm'
return harden({ vatGlobals, dispatch, setBuildRootObject, deadSet });
const {
vatGlobals,
inescapableGlobalLexicals,
dispatch,
setBuildRootObject,
deadSet,
} = r; // omit 'm'
return harden({
vatGlobals,
inescapableGlobalLexicals,
dispatch,
setBuildRootObject,
deadSet,
});
}

// for tests
Expand Down
2 changes: 1 addition & 1 deletion packages/SwingSet/src/kernel/vatManager/manager-local.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export function makeLocalVatManagerFactory(tools) {
assert,
});
const inescapableTransforms = [];
const inescapableGlobalLexicals = {};
const inescapableGlobalLexicals = { ...ls.inescapableGlobalLexicals };
if (metered) {
const getMeter = meterRecord.getMeter;
inescapableTransforms.push(src => transformMetering(src, getMeter));
Expand Down
121 changes: 116 additions & 5 deletions packages/SwingSet/src/kernel/virtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,13 @@ export function makeVirtualObjectManager(

function virtualObjectKey(key) {
const vobjID = valToSlot.get(key);
if (!vobjID) {
return undefined;
} else {
if (vobjID) {
const { type, virtual } = parseVatSlot(vobjID);
if (type === 'object' && virtual) {
return `ws${storeID}.${vobjID}`;
} else {
return undefined;
}
}
return undefined;
}

return harden({
Expand Down Expand Up @@ -318,6 +315,118 @@ export function makeVirtualObjectManager(
});
}

function vrefKey(value) {
const vobjID = valToSlot.get(value);
if (vobjID) {
const { type, virtual } = parseVatSlot(vobjID);
if (type === 'object' && virtual) {
return vobjID;
}
}
return undefined;
}

/* eslint max-classes-per-file: ["error", 2] */

const actualWeakMaps = new WeakMap();
const virtualObjectMaps = new WeakMap();

class RepairedWeakMap {
constructor() {
actualWeakMaps.set(this, new WeakMap());
virtualObjectMaps.set(this, new Map());
}

has(key) {
const vkey = vrefKey(key);
if (vkey) {
return virtualObjectMaps.get(this).has(vkey);
} else {
return actualWeakMaps.get(this).has(key);
}
}

get(key) {
const vkey = vrefKey(key);
if (vkey) {
return virtualObjectMaps.get(this).get(vkey);
} else {
return actualWeakMaps.get(this).get(key);
}
}

set(key, value) {
const vkey = vrefKey(key);
if (vkey) {
virtualObjectMaps.get(this).set(vkey, value);
} else {
actualWeakMaps.get(this).set(key, value);
}
return this;
}

delete(key) {
const vkey = vrefKey(key);
if (vkey) {
return virtualObjectMaps.get(this).delete(vkey);
} else {
return actualWeakMaps.get(this).delete(key);
}
}
}

Object.defineProperty(RepairedWeakMap, Symbol.toStringTag, {
value: 'WeakMap',
writable: false,
enumerable: false,
configurable: true,
});

const actualWeakSets = new WeakMap();
const virtualObjectSets = new WeakMap();

class RepairedWeakSet {
constructor() {
actualWeakSets.set(this, new WeakSet());
virtualObjectSets.set(this, new Set());
}

has(value) {
const vkey = vrefKey(value);
if (vkey) {
return virtualObjectSets.get(this).has(vkey);
} else {
return actualWeakSets.get(this).has(value);
}
}

add(value) {
const vkey = vrefKey(value);
if (vkey) {
virtualObjectSets.get(this).add(vkey);
} else {
actualWeakSets.get(this).add(value);
}
return this;
}

delete(value) {
const vkey = vrefKey(value);
if (vkey) {
return virtualObjectSets.get(this).delete(vkey);
} else {
return actualWeakSets.get(this).delete(value);
}
}
}

Object.defineProperty(RepairedWeakSet, Symbol.toStringTag, {
value: 'WeakSet',
writable: false,
enumerable: false,
configurable: true,
});

/**
* Define a new kind of virtual object.
*
Expand Down Expand Up @@ -495,6 +604,8 @@ export function makeVirtualObjectManager(
return harden({
makeWeakStore,
makeKind,
RepairedWeakMap,
RepairedWeakSet,
flushCache: cache.flush,
makeVirtualObjectRepresentative,
});
Expand Down
73 changes: 73 additions & 0 deletions packages/SwingSet/test/virtualObjects/test-virtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,76 @@ test('weak store operations', t => {
ws1.set(nv2, 'non-virtual object #2 revised');
t.is(ws1.get(nv2), 'non-virtual object #2 revised');
});

test('virtualized weak collection operations', t => {
// TODO: don't yet have a way to test the weakness of the virtualized weak
// collections

const {
RepairedWeakMap,
RepairedWeakSet,
makeKind,
} = makeFakeVirtualObjectManager(3);

const thingMaker = makeKind(makeThingInstance);
const zotMaker = makeKind(makeZotInstance);

const thing1 = thingMaker('t1');
const thing2 = thingMaker('t2');

const zot1 = zotMaker(1, 'z1');
const zot2 = zotMaker(2, 'z2');
const zot3 = zotMaker(3, 'z3');
const zot4 = zotMaker(4, 'z4');

const wm1 = new RepairedWeakMap();
const wm2 = new RepairedWeakMap();
const nv1 = {};
const nv2 = { a: 47 };
wm1.set(zot1, 'zot #1');
wm2.set(zot2, 'zot #2');
wm1.set(zot3, 'zot #3');
wm2.set(zot4, 'zot #4');
wm1.set(thing1, 'thing #1');
wm2.set(thing2, 'thing #2');
wm1.set(nv1, 'non-virtual object #1');
wm1.set(nv2, 'non-virtual object #2');
t.is(wm1.get(zot1), 'zot #1');
t.is(wm1.has(zot1), true);
t.is(wm2.has(zot1), false);
wm1.set(zot3, 'zot #3 revised');
wm2.delete(zot4);
t.is(wm1.get(nv1), 'non-virtual object #1');
t.is(wm1.get(nv2), 'non-virtual object #2');
t.is(wm2.has(zot4), false);
t.is(wm1.get(zot3), 'zot #3 revised');
wm1.delete(nv1);
t.is(wm1.has(nv1), false);
wm1.set(nv2, 'non-virtual object #2 revised');
t.is(wm1.get(nv2), 'non-virtual object #2 revised');

const ws1 = new RepairedWeakSet();
const ws2 = new RepairedWeakSet();
ws1.add(zot1);
ws2.add(zot2);
ws1.add(zot3);
ws2.add(zot4);
ws1.add(thing1);
ws2.add(thing2);
ws1.add(nv1);
ws1.add(nv2);
t.is(ws1.has(zot1), true);
t.is(ws2.has(zot1), false);
ws1.add(zot3);
ws2.delete(zot4);
t.is(ws1.has(nv1), true);
t.is(ws1.has(nv2), true);
t.is(ws2.has(nv1), false);
t.is(ws2.has(nv2), false);
t.is(ws2.has(zot4), false);
t.is(ws1.has(zot3), true);
ws1.delete(nv1);
t.is(ws1.has(nv1), false);
ws1.add(nv2);
t.is(ws1.has(nv2), true);
});
4 changes: 4 additions & 0 deletions packages/SwingSet/tools/fakeVirtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export function makeFakeVirtualObjectManager(cacheSize = 100) {
makeVirtualObjectRepresentative,
makeWeakStore,
makeKind,
RepairedWeakMap,
RepairedWeakSet,
flushCache,
} = makeVirtualObjectManager(
fakeSyscall,
Expand All @@ -77,6 +79,8 @@ export function makeFakeVirtualObjectManager(cacheSize = 100) {
return {
makeKind,
makeWeakStore,
RepairedWeakMap,
RepairedWeakSet,
flushCache,
dumpStore,
};
Expand Down

0 comments on commit bd421ff

Please sign in to comment.