Skip to content

Commit

Permalink
feat(swingset): xsnap vat manager
Browse files Browse the repository at this point in the history
 - build xsnap bootstrap bundles
 - bytes to tagged array and back
 - setBundle,  importBundle
 - syscall
 - delivery success symbol is ok, not deliverDone
 - Use Tagged type consistently;
   don't constrain tag to be string.
 - clean up logging: use parentLog(), trace(), ...
 - static typing for doProcess: capture dispatch while
   it's known to be not null
 - silence parentLog, workerLog for xsnap
 - no, handleSyscall doesn't return Tagged
  • Loading branch information
dckc committed Jan 21, 2021
1 parent 21bd045 commit 240ae15
Show file tree
Hide file tree
Showing 9 changed files with 491 additions and 23 deletions.
1 change: 1 addition & 0 deletions packages/SwingSet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@agoric/transform-eventual-send": "^1.4.0",
"@agoric/transform-metering": "^1.4.0",
"@agoric/xs-vat-worker": "^0.4.0",
"@agoric/xsnap": "^0.1.0",
"@babel/core": "^7.5.0",
"@babel/generator": "^7.6.4",
"anylogger": "^0.21.0",
Expand Down
39 changes: 31 additions & 8 deletions packages/SwingSet/src/controller.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import fs from 'fs';
import path from 'path';
import process from 'process';
import re2 from 're2';
import { spawn } from 'child_process';
import { type as osType } from 'os';
import { Worker } from 'worker_threads';
import * as babelCore from '@babel/core';
import * as babelParser from '@agoric/babel-parser';
Expand All @@ -11,10 +12,11 @@ import anylogger from 'anylogger';
import { assert } from '@agoric/assert';
import { isTamed, tameMetering } from '@agoric/tame-metering';
import { importBundle } from '@agoric/import-bundle';
import bundleSource from '@agoric/bundle-source';
import { initSwingStore } from '@agoric/swing-store-simple';
import { makeMeteringTransformer } from '@agoric/transform-metering';
import { makeTransform } from '@agoric/transform-eventual-send';
import { locateWorkerBin } from '@agoric/xs-vat-worker';
import { xsnap } from '@agoric/xsnap';

import { WeakRef, FinalizationRegistry } from './weakref';
import { startSubprocessWorker } from './spawnSubprocessWorker';
Expand All @@ -36,6 +38,20 @@ function makeConsole(tag) {
return harden(cons);
}

async function buildXsBundles() {
const zip = (xs, ys) => xs.map((x, i) => [x, ys[i]]);
const { keys, values, fromEntries } = Object;
const allValues = async obj =>
fromEntries(zip(keys(obj), await Promise.all(values(obj))));
const src = rel => bundleSource(require.resolve(rel), 'getExport');
return harden(
await allValues({
lockdown: src('./kernel/vatManager/lockdown-subprocess-xsnap.js'),
supervisor: src('./kernel/vatManager/supervisor-subprocess-xsnap.js'),
}),
);
}

export async function makeSwingsetController(
hostStorage = initSwingStore().storage,
deviceEndowments = {},
Expand Down Expand Up @@ -157,11 +173,18 @@ export async function makeSwingsetController(
return startSubprocessWorker(process.execPath, ['-r', 'esm', supercode]);
}

let startSubprocessWorkerXS;
const xsWorkerBin = locateWorkerBin({ resolve: path.resolve });
if (fs.existsSync(xsWorkerBin)) {
startSubprocessWorkerXS = () => startSubprocessWorker(xsWorkerBin);
}
const { xsnapBundles = await buildXsBundles() } = {}; // @@@ options?
const startXSnap = (name, handleCommand) => {
console.log('starting xsnap for', name);
const worker = xsnap({
os: osType(),
spawn,
handleCommand,
name,
});

return harden({ worker, bundles: xsnapBundles });
};

const slogF =
slogFile && (await fs.createWriteStream(slogFile, { flags: 'a' })); // append
Expand All @@ -186,7 +209,7 @@ export async function makeSwingsetController(
transformTildot,
makeNodeWorker,
startSubprocessWorkerNode,
startSubprocessWorkerXS,
startXSnap,
writeSlogObject,
WeakRef,
FinalizationRegistry,
Expand Down
4 changes: 2 additions & 2 deletions packages/SwingSet/src/kernel/kernel.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default function buildKernel(
transformTildot,
makeNodeWorker,
startSubprocessWorkerNode,
startSubprocessWorkerXS,
startXSnap,
writeSlogObject,
WeakRef,
FinalizationRegistry,
Expand Down Expand Up @@ -553,7 +553,7 @@ export default function buildKernel(
waitUntilQuiescent,
makeNodeWorker,
startSubprocessWorkerNode,
startSubprocessWorkerXS,
startXSnap,
gcTools,
});

Expand Down
10 changes: 4 additions & 6 deletions packages/SwingSet/src/kernel/vatManager/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { assertKnownOptions } from '../../assertOptions';
import { makeLocalVatManagerFactory } from './manager-local';
import { makeNodeWorkerVatManagerFactory } from './manager-nodeworker';
import { makeNodeSubprocessFactory } from './manager-subprocess-node';
import { makeXsSubprocessFactory } from './manager-subprocess-xsnap';

export function makeVatManagerFactory({
allVatPowers,
Expand All @@ -13,7 +14,7 @@ export function makeVatManagerFactory({
waitUntilQuiescent,
makeNodeWorker,
startSubprocessWorkerNode,
startSubprocessWorkerXS,
startXSnap,
gcTools,
}) {
const localFactory = makeLocalVatManagerFactory({
Expand All @@ -40,8 +41,8 @@ export function makeVatManagerFactory({
decref: gcTools.decref,
});

const xsWorkerFactory = makeNodeSubprocessFactory({
startSubprocessWorker: startSubprocessWorkerXS,
const xsWorkerFactory = makeXsSubprocessFactory({
startXSnap,
kernelKeeper,
testLog: allVatPowers.testLog,
decref: gcTools.decref,
Expand Down Expand Up @@ -98,9 +99,6 @@ export function makeVatManagerFactory({
}

if (managerType === 'xs-worker') {
if (!startSubprocessWorkerXS) {
throw new Error('manager type xs-worker not available');
}
return xsWorkerFactory.createFromBundle(vatID, bundle, managerOptions);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@agoric/xs-vat-worker/src/bootstrap';
191 changes: 191 additions & 0 deletions packages/SwingSet/src/kernel/vatManager/manager-subprocess-xsnap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// @ts-check
import { assert, details } from '@agoric/assert';
import { makeTranscriptManager } from './transcript';
import { createSyscall } from './syscall';

// eslint-disable-next-line no-unused-vars
function parentLog(first, ...args) {
// console.error(`--parent: ${first}`, ...args);
}

const trace = label => x => {
parentLog(label, x);
return x;
};

const encoder = new TextEncoder();
const decoder = new TextDecoder();

/**
* @param {{
* startXSnap: (name: string, handleCommand: SyncHandler) => { worker: XSnap, bundles: Record<string, ExportBundle> },
* kernelKeeper: KernelKeeper,
* testLog: (...args: unknown[]) => void,
* decref: (vatID: unknown, vref: unknown, count: number) => void,
* }} tools
* @returns { VatManagerFactory }
*
* @typedef { { moduleFormat: 'getExport', source: string } } ExportBundle
* @typedef { (msg: Uint8Array) => Uint8Array } SyncHandler
* @typedef { ReturnType<typeof import('@agoric/xsnap').xsnap> } XSnap
* @typedef { ReturnType<typeof import('../state/kernelKeeper').default> } KernelKeeper
* @typedef { ReturnType<typeof import('./manager-nodeworker').makeNodeWorkerVatManagerFactory> } VatManagerFactory
* @typedef { [unknown, ...unknown[]] } Tagged
*/
export function makeXsSubprocessFactory({
startXSnap,
kernelKeeper,
testLog,
decref,
}) {
/**
* @param { unknown } vatID
* @param { unknown } bundle
* @param { ManagerOptions } managerOptions
*/
async function createFromBundle(vatID, bundle, managerOptions) {
parentLog('createFromBundle', { vatID });
const { vatParameters, virtualObjectCacheSize } = managerOptions;
assert(!managerOptions.metered, 'not supported yet');
assert(!managerOptions.enableSetup, 'not supported at all');
if (managerOptions.enableInternalMetering) {
// TODO: warn+ignore, rather than throw, because the kernel enables it
// for all vats, because the Spawner still needs it. When the kernel
// stops doing that, turn this into a regular assert
console.log(`xsnap worker does not support enableInternalMetering`);
}
const vatKeeper = kernelKeeper.getVatKeeper(vatID);
const transcriptManager = makeTranscriptManager(
kernelKeeper,
vatKeeper,
vatID,
);

const { doSyscall, setVatSyscallHandler } = createSyscall(
transcriptManager,
);

/** @type { (vatSyscallObject: Tagged) => unknown } */
function handleSyscall(vatSyscallObject) {
return trace('doSyscall')(doSyscall(vatSyscallObject));
}

/** @type { (vref: unknown, count: number) => void } */
function vatDecref(vref, count) {
decref(vatID, vref, count);
}

/** @type { (item: Tagged) => unknown } */
function handleUpstream([type, ...args]) {
parentLog(`handleUpstream`, type, args.length);
switch (type) {
case 'syscall': {
parentLog(`syscall`, args);
const [scTag, ...vatSyscallArgs] = args;
return handleSyscall([scTag, ...vatSyscallArgs]);
}
case 'testLog':
testLog(...args);
return ['OK'];
case 'decref': {
const [vref, count] = args;
assert(typeof count === 'number');
vatDecref(vref, count);
return ['OK'];
}
default:
throw new Error(`unrecognized uplink message ${type}`);
}
}

/** @type { (msg: Uint8Array) => Uint8Array } */
function handleCommand(msg) {
parentLog('handleCommand', { length: msg.byteLength });
const tagged = handleUpstream(JSON.parse(decoder.decode(msg)));
return encoder.encode(JSON.stringify(tagged));
}

// start the worker and establish a connection
const { worker, bundles } = startXSnap(`${vatID}`, handleCommand);
for await (const [it, superCode] of Object.entries(bundles)) {
parentLog('bundle', it);
assert(
superCode.moduleFormat === 'getExport',
details`${it} unexpected: ${superCode.moduleFormat}`,
);
await worker.evaluate(
`(${superCode.source}
)()`.trim(),
);
}

/** @type { (item: Tagged) => Promise<Tagged> } */
async function issueTagged(item) {
parentLog('issueTagged', item[0]);
const txt = await worker.issueStringCommand(JSON.stringify(item));
const reply = JSON.parse(txt);
assert(Array.isArray(reply));
const [tag, ...rest] = reply;
return [tag, ...rest];
}

let seq = 0;
/** @type { (item: Tagged) => Promise<Tagged> } */
async function commandResult(item) {
const [tag, ...rest] = await issueTagged(item);
seq += 1;
if (tag === 'err') return [tag, ...rest];
return issueTagged(['getResult', seq]);
}

parentLog(`instructing worker to load bundle..`);
const bundleReply = await commandResult([
'setBundle',
vatID,
bundle,
vatParameters,
virtualObjectCacheSize,
]);
if (bundleReply[0] === 'dispatchReady') {
parentLog(`bundle loaded. dispatch ready.`);
} else {
throw new Error(`failed to setBundle: ${bundleReply}`);
}

/** @type { (item: Tagged) => Promise<Tagged> } */
async function deliver(delivery) {
parentLog(`sending delivery`, delivery);
const result = await commandResult(['deliver', ...delivery]);
parentLog(`deliverDone`, result[0], result.length);
return result;
}

async function replayTranscript() {
transcriptManager.startReplay();
for (const t of vatKeeper.getTranscript()) {
transcriptManager.checkReplayError();
transcriptManager.startReplayDelivery(t.syscalls);
// eslint-disable-next-line no-await-in-loop
await deliver(t.d);
}
transcriptManager.checkReplayError();
transcriptManager.finishReplay();
}

function shutdown() {
return worker.close();
}

const manager = harden({
replayTranscript,
setVatSyscallHandler,
deliver,
shutdown,
});

parentLog('manager', Object.keys(manager));
return manager;
}

return harden({ createFromBundle });
}
Loading

0 comments on commit 240ae15

Please sign in to comment.