Skip to content

Commit

Permalink
feat: yet another overhaul of the defineKind API
Browse files Browse the repository at this point in the history
Closes #4905
  • Loading branch information
FUDCo committed Mar 31, 2022
1 parent fd0db1b commit 554c743
Show file tree
Hide file tree
Showing 22 changed files with 286 additions and 348 deletions.
4 changes: 2 additions & 2 deletions packages/ERTP/src/payment.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export const makePaymentMaker = (allegedName, brand) => {
const makePayment = defineKind(
`${allegedName} payment`,
() => ({}),
() => ({
{
getAllegedBrand: () => brand,
}),
},
);
return makePayment;
};
Expand Down
20 changes: 8 additions & 12 deletions packages/SwingSet/src/liveslots/collectionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -788,24 +788,20 @@ export function makeCollectionManager(
);
}

function reanimateScalarMapStore(vobjID, proForma) {
return proForma ? null : collectionToMapStore(reanimateCollection(vobjID));
function reanimateScalarMapStore(vobjID) {
return collectionToMapStore(reanimateCollection(vobjID));
}

function reanimateScalarWeakMapStore(vobjID, proForma) {
return proForma
? null
: collectionToWeakMapStore(reanimateCollection(vobjID));
function reanimateScalarWeakMapStore(vobjID) {
return collectionToWeakMapStore(reanimateCollection(vobjID));
}

function reanimateScalarSetStore(vobjID, proForma) {
return proForma ? null : collectionToSetStore(reanimateCollection(vobjID));
function reanimateScalarSetStore(vobjID) {
return collectionToSetStore(reanimateCollection(vobjID));
}

function reanimateScalarWeakSetStore(vobjID, proForma) {
return proForma
? null
: collectionToWeakSetStore(reanimateCollection(vobjID));
function reanimateScalarWeakSetStore(vobjID) {
return collectionToWeakSetStore(reanimateCollection(vobjID));
}

const testHooks = { obtainStoreKindID, storeSizeInternal, makeCollection };
Expand Down
12 changes: 1 addition & 11 deletions packages/SwingSet/src/liveslots/liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -691,16 +691,6 @@ function build(
let val = getValForSlot(baseRef);
if (val) {
if (virtual) {
// If it's a virtual object for which we already have a representative,
// we are going to use that existing representative to preserve ===
// equality and WeakMap key usability, BUT we are going to ask the user
// code to make a new representative anyway (which we'll discard) so
// that as far as the user code is concerned we are making a new
// representative with each act of deserialization. This way they can't
// detect reanimation by playing games inside their kind definition to
// try to observe when new representatives are created (e.g., by
// counting calls or squirreling things away in hidden WeakMaps).
vrm.reanimate(baseRef, true); // N.b.: throwing away the result
if (facet !== undefined) {
return val[facet];
}
Expand All @@ -710,7 +700,7 @@ function build(
let result;
if (virtual) {
assert.equal(type, 'object');
val = vrm.reanimate(baseRef, false);
val = vrm.reanimate(baseRef);
if (facet !== undefined) {
result = val[facet];
}
Expand Down
163 changes: 98 additions & 65 deletions packages/SwingSet/src/liveslots/virtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,24 @@ export function makeVirtualObjectManager(
}
}

function copyMethods(behaviorTemplate) {
const obj = {};
for (const [name, func] of Object.entries(behaviorTemplate)) {
assert.typeof(func, 'function');
obj[name] = func;
}
return obj;
}

function bindMethods(context, behaviorTemplate) {
const obj = {};
for (const [name, func] of Object.entries(behaviorTemplate)) {
assert.typeof(func, 'function');
obj[name] = (...args) => Reflect.apply(func, null, [context, ...args]);
}
return obj;
}

/**
* Define a new kind of virtual object.
*
Expand All @@ -421,13 +439,14 @@ export function makeVirtualObjectManager(
* @param {*} init An initialization function that will return the initial
* state of a new instance of the kind of virtual object being defined.
*
* @param {*} actualize An actualization function that will provide the
* in-memory representative object that wraps behavior around the
* virtualized state of an instance of the object kind being defined.
* @param {*} behavior A bag of functions (in the case of a single-faceted
* object) or a bag of bags of functions (in the case of a multi-faceted
* object) that will become the methods of the object or its facets.
*
* @param {*} finish An optional finisher function that can perform
* post-creation initialization operations, such as inserting the new
* object in a cyclical object graph.
* @param {*} options Additional options to configure the virtual object kind
* being defined. Currently the only supported option is `finish`, an
* optional finisher function that can perform post-creation initialization
* operations, such as inserting the new object in a cyclical object graph.
*
* @param {boolean} durable A flag indicating whether or not the newly defined
* kind should be a durable kind.
Expand Down Expand Up @@ -496,17 +515,60 @@ export function makeVirtualObjectManager(
* reference to the state is nulled out and the object holding the state
* becomes garbage collectable.
*/
function defineKindInternal(kindID, tag, init, actualize, finish, durable) {
function defineKindInternal(kindID, tag, init, behavior, options, durable) {
const finish = options ? options.finish : undefined;
let nextInstanceID = 1;

function makeRepresentative(innerSelf, initializing, proForma) {
if (!proForma) {
let facetNames;
let behaviorTemplate;

const facetiousness = assessFacetiousness(behavior);
switch (facetiousness) {
case 'one': {
facetNames = null;
behaviorTemplate = copyMethods(behavior);
break;
}
case 'many': {
facetNames = Object.getOwnPropertyNames(behavior).sort();
assert(
innerSelf.repCount === 0,
X`${innerSelf.baseRef} already has a representative`,
facetNames.length > 1,
'a multi-facet object must have multiple facets',
);
innerSelf.repCount += 1;
behaviorTemplate = {};
for (const name of facetNames) {
behaviorTemplate[name] = copyMethods(behavior[name]);
}
break;
}
case 'not':
assert.fail(X`invalid behavior specifier for ${q(tag)}`);
default:
assert.fail(X`unexepected facetiousness: ${q(facetiousness)}`);
}
vrm.registerKind(kindID, reanimate, deleteStoredVO, durable);
vrm.rememberFacetNames(kindID, facetNames);
harden(behaviorTemplate);

function actualize(state) {
if (facetNames === null) {
const context = { state };
context.self = bindMethods(context, behaviorTemplate);
return context.self;
} else {
const context = { state, facets: {} };
for (const name of facetNames) {
context.facets[name] = bindMethods(context, behaviorTemplate[name]);
}
return context.facets;
}
}

function makeRepresentative(innerSelf, initializing) {
assert(
innerSelf.repCount === 0,
X`${innerSelf.baseRef} already has a representative`,
);
innerSelf.repCount += 1;

function ensureState() {
if (innerSelf.rawState) {
Expand Down Expand Up @@ -552,53 +614,30 @@ export function makeVirtualObjectManager(
const self = actualize(wrappedState);
let toHold;
let toExpose;
const facetiousness = assessFacetiousness(self);
switch (facetiousness) {
case 'one': {
toHold = Far(tag, self);
vrm.checkOrAcquireFacetNames(kindID, null);
toExpose = toHold;
break;
}
case 'many': {
toExpose = {};
toHold = [];
const facetNames = Object.getOwnPropertyNames(self).sort();
assert(
facetNames.length > 1,
'a multi-facet object must have multiple facets',
);
vrm.checkOrAcquireFacetNames(kindID, facetNames);
for (const facetName of facetNames) {
const facet = Far(`${tag} ${facetName}`, self[facetName]);
toExpose[facetName] = facet;
toHold.push(facet);
facetToCohort.set(facet, toHold);
}
harden(toExpose);
break;
if (facetNames === null) {
toHold = Far(tag, self);
toExpose = toHold;
} else {
toExpose = {};
toHold = [];
for (const facetName of facetNames) {
const facet = Far(`${tag} ${facetName}`, self[facetName]);
toExpose[facetName] = facet;
toHold.push(facet);
facetToCohort.set(facet, toHold);
}
case 'not':
assert.fail(X`invalid self actualization for ${q(tag)}`);
default:
assert.fail(X`unexepected facetiousness: ${q(facetiousness)}`);
}
if (!proForma) {
innerSelf.representative = toHold;
stateToRepresentative.set(wrappedState, toHold);
harden(toExpose);
}
innerSelf.representative = toHold;
stateToRepresentative.set(wrappedState, toHold);
return [toHold, toExpose, wrappedState];
}

function reanimate(baseRef, proForma) {
function reanimate(baseRef) {
// kdebug(`vo reanimate ${baseRef}`);
const innerSelf = cache.lookup(baseRef, false);
const [toHold] = makeRepresentative(innerSelf, false, proForma);
if (proForma) {
return null;
} else {
return toHold;
}
const [toHold] = makeRepresentative(innerSelf, false);
return toHold;
}

function deleteStoredVO(baseRef) {
Expand All @@ -615,8 +654,6 @@ export function makeVirtualObjectManager(
return doMoreGC;
}

vrm.registerKind(kindID, reanimate, deleteStoredVO, durable);

function makeNewInstance(...args) {
const baseRef = `o+${kindID}/${nextInstanceID}`;
nextInstanceID += 1;
Expand All @@ -635,11 +672,7 @@ export function makeVirtualObjectManager(
rawState[prop] = data;
}
const innerSelf = { baseRef, rawState, repCount: 0 };
const [toHold, toExpose, state] = makeRepresentative(
innerSelf,
true,
false,
);
const [toHold, toExpose, state] = makeRepresentative(innerSelf, true);
registerValue(baseRef, toHold, Array.isArray(toHold));
if (finish) {
finish(state, toExpose);
Expand All @@ -651,15 +684,15 @@ export function makeVirtualObjectManager(
return makeNewInstance;
}

function defineKind(tag, init, actualize, finish) {
function defineKind(tag, init, behavior, options) {
const kindID = `${allocateExportID()}`;
return defineKindInternal(kindID, tag, init, actualize, finish, false);
return defineKindInternal(kindID, tag, init, behavior, options, false);
}

let kindIDID;
const kindDescriptors = new WeakMap();

function reanimateDurableKindID(vobjID, _proforma) {
function reanimateDurableKindID(vobjID) {
const { subid: kindID } = parseVatSlot(vobjID);
const raw = syscall.vatstoreGet(`vom.kind.${kindID}`);
assert(raw, X`unknown kind ID ${kindID}`);
Expand Down Expand Up @@ -688,11 +721,11 @@ export function makeVirtualObjectManager(
return kindHandle;
};

function defineDurableKind(kindHandle, init, actualize, finish) {
function defineDurableKind(kindHandle, init, behavior, options) {
const durableKindDescriptor = kindDescriptors.get(kindHandle);
assert(durableKindDescriptor);
const { kindID, tag } = durableKindDescriptor;
return defineKindInternal(kindID, tag, init, actualize, finish, true);
return defineKindInternal(kindID, tag, init, behavior, options, true);
}

function countWeakKeysForCollection(collection) {
Expand Down
47 changes: 8 additions & 39 deletions packages/SwingSet/src/liveslots/virtualReferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,46 +219,17 @@ export function makeVirtualReferenceManager(
}

/**
* Compare two arrays (shallowly) for equality.
*
* @template T
* @param {T[]} a1
* @param {T[]} a2
* @returns {boolean}
*/
const arrayEquals = (a1, a2) => {
assert(Array.isArray(a1));
assert(Array.isArray(a2));
if (a1.length !== a2.length) {
return false;
}
return a1.every((elem, idx) => Object.is(a2[idx], elem));
};

/**
* Check a list of facet names against what's already been established for a
* kind. If they don't match, it's an error. If nothing has been established
* yet, establish it now.
* Record the names of the facets of a multi-faceted virtual object.
*
* @param {string} kindID The kind we're talking about
* @param {string[]|null} facetNames A sorted array of facet names to be
* checked or acquired, or null if the kind is unfaceted
* recorded, or null if the kind is unfaceted
*/
function checkOrAcquireFacetNames(kindID, facetNames) {
function rememberFacetNames(kindID, facetNames) {
const kindInfo = kindInfoTable.get(`${kindID}`);
assert(kindInfo, `no kind info for ${kindID}`);
if (kindInfo.facetNames !== undefined) {
if (facetNames === null) {
assert(kindInfo.facetNames === null);
} else {
assert(
arrayEquals(facetNames, kindInfo.facetNames),
'all virtual objects of the same kind must have the same facet names',
);
}
} else {
kindInfo.facetNames = facetNames;
}
assert(kindInfo.facetNames === undefined);
kindInfo.facetNames = facetNames;
}

/**
Expand Down Expand Up @@ -303,19 +274,17 @@ export function makeVirtualReferenceManager(
* persistent storage. Used for deserializing.
*
* @param {string} baseRef The baseRef of the object being reanimated
* @param {boolean} proForma If true, representative creation is for formal
* use only and result will be ignored.
*
* @returns {Object} A representative of the object identified by `baseRef`
*/
function reanimate(baseRef, proForma) {
function reanimate(baseRef) {
const { id } = parseVatSlot(baseRef);
const kindID = `${id}`;
const kindInfo = kindInfoTable.get(kindID);
assert(kindInfo, `no kind info for ${kindID}, call defineDurableKind`);
const { reanimator } = kindInfo;
if (reanimator) {
return reanimator(baseRef, proForma);
return reanimator(baseRef);
} else {
assert.fail(X`unknown kind ${kindID}`);
}
Expand Down Expand Up @@ -618,7 +587,7 @@ export function makeVirtualReferenceManager(
droppedCollectionRegistry,
isDurable,
registerKind,
checkOrAcquireFacetNames,
rememberFacetNames,
reanimate,
addReachableVref,
removeReachableVref,
Expand Down
Loading

0 comments on commit 554c743

Please sign in to comment.