Skip to content

Commit

Permalink
feat(swingset): comms state: add object refcounts
Browse files Browse the repository at this point in the history
* add both `reachable` and `recognizable` reference counts for each object
* add helper functions to update those refcounts
* improve incrementRefCount / decrementRefCount to update objects, not just
  promises: the "mode" affects how the counts are changed
  * `data` (e.g. promise resolution data) increments both
  * `clist-import`: increment `recognizable`, but leave `reachable` to the
    code that sets the `isReachable` flag
  * `clist-export`: do not modify any refcounts
  • Loading branch information
warner committed Jun 20, 2021
1 parent 51b1c0c commit dee65f7
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 9 deletions.
63 changes: 57 additions & 6 deletions packages/SwingSet/src/vats/comms/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export function makeState(syscall, identifierBase = 0) {
//
// lo.nextID = $NN // local object identifier allocation counter (loNN)
// lo$NN.owner = r$NN | kernel // owners of local objects (loNN -> rNN)
// lo$NN.reachable = $NN // refcount
// lo$NN.recognizable = $NN // refcount
//
// lp.nextID = $NN // local promise identifier allocation counter (lpNN)
// lp$NN.status = unresolved | fulfilled | rejected
Expand Down Expand Up @@ -156,6 +158,29 @@ export function makeState(syscall, identifierBase = 0) {
deleteLocalPromiseState(lpid);
}

/* A mode of 'clist-import' means we increment recognizable, but not
* reachable, because the translation function will call setReachable in a
* moment, and the count should only be changed if it wasn't already
* reachable. 'clist-export' means we don't touch the count at all. 'other'
* is used by resolved promise data and auxdata, and means we increment
* both.
*/
const referenceModes = harden(['data', 'clist-export', 'clist-import']);

function changeRecognizable(lref, delta) {
const key = `${lref}.recognizable`;
const recognizable = Nat(BigInt(store.getRequired(key))) + delta;
store.set(key, `${recognizable}`);
return recognizable;
}

function changeReachable(lref, delta) {
const key = `${lref}.reachable`;
const reachable = Nat(BigInt(store.getRequired(key))) + delta;
store.set(key, `${reachable}`);
return reachable;
}

const maybeFree = new Set(); // lrefs

function lrefMightBeFree(lref) {
Expand All @@ -170,27 +195,36 @@ export function makeState(syscall, identifierBase = 0) {
*
* @param {string} lref Ref of the local object whose refcount is to be incremented.
* @param {string} _tag Descriptive label for use in diagnostics
* @param {string} mode Reference type
*/
function incrementRefCount(lref, _tag) {
function incrementRefCount(lref, _tag, mode = 'data') {
assert(referenceModes.includes(mode), `unknown reference mode ${mode}`);
const { type } = parseLocalSlot(lref);
if (type === 'promise') {
const refCount = parseInt(store.get(`${lref}.refCount`), 10) + 1;
// cdebug(`++ ${lref} ${tag} ${refCount}`);
store.set(`${lref}.refCount`, `${refCount}`);
}
if (type === 'object') {
if (mode === 'clist-import' || mode === 'data') {
changeRecognizable(lref, 1n);
}
if (mode === 'data') {
changeReachable(lref, 1n);
}
}
}

/**
* Decrement the reference count associated with some local object.
*
* Note that currently we are only reference counting promises, but ultimately
* we intend to keep track of all local objects.
* Decrement the reference counts associated with some local object/promise.
*
* @param {string} lref Ref of the local object whose refcount is to be decremented.
* @param {string} tag Descriptive label for use in diagnostics
* @param {string} mode Reference type
* @throws if this tries to decrement a reference count below zero.
*/
function decrementRefCount(lref, tag) {
function decrementRefCount(lref, tag, mode = 'data') {
assert(referenceModes.includes(mode), `unknown reference mode ${mode}`);
const { type } = parseLocalSlot(lref);
if (type === 'promise') {
let refCount = parseInt(store.get(`${lref}.refCount`), 10);
Expand All @@ -212,6 +246,20 @@ export function makeState(syscall, identifierBase = 0) {
maybeFree.add(lref);
}
}
if (type === 'object') {
if (mode === 'clist-import' || mode === 'data') {
const recognizable = changeRecognizable(lref, -1n);
if (!recognizable) {
// maybeFree.add(lref);
}
}
if (mode === 'data') {
const reachable = changeReachable(lref, -1n);
if (!reachable) {
// maybeFree.add(lref);
}
}
}
}

/**
Expand Down Expand Up @@ -304,6 +352,8 @@ export function makeState(syscall, identifierBase = 0) {
store.set('lo.nextID', `${index + 1n}`);
const loid = makeLocalSlot('object', index);
store.set(`${loid}.owner`, owner);
store.set(`${loid}.reachable`, `0`);
store.set(`${loid}.recognizable`, `0`);
return loid;
}

Expand Down Expand Up @@ -518,6 +568,7 @@ export function makeState(syscall, identifierBase = 0) {
getPromiseData,
allocatePromise,

changeReachable,
lrefMightBeFree,
incrementRefCount,
decrementRefCount,
Expand Down
8 changes: 5 additions & 3 deletions packages/SwingSet/test/test-comms.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ test('provideRemoteForLocal', t => {
const clistKit = makeCListKit(s, fakeSyscall);
const { provideRemoteForLocal } = clistKit;
const { remoteID } = s.addRemote('remote1', 'o-1');
const lo4 = s.allocateObject('remote0');
const lo5 = s.allocateObject('remote0');

// n.b.: duplicated provideRemoteForLocal() call is not a cut-n-paste error
// but a test that translation is stable
t.is(provideRemoteForLocal(remoteID, 'lo4'), 'ro-20');
t.is(provideRemoteForLocal(remoteID, 'lo4'), 'ro-20');
t.is(provideRemoteForLocal(remoteID, 'lo5'), 'ro-21');
t.is(provideRemoteForLocal(remoteID, lo4), 'ro-20');
t.is(provideRemoteForLocal(remoteID, lo4), 'ro-20');
t.is(provideRemoteForLocal(remoteID, lo5), 'ro-21');
});

function mockSyscall() {
Expand Down

0 comments on commit dee65f7

Please sign in to comment.