Skip to content

Commit

Permalink
feat(xsnap): Pivot terms from syscalls to commands
Browse files Browse the repository at this point in the history
  • Loading branch information
kriskowal committed Jan 11, 2021
1 parent 619a4de commit 3576b5c
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 70 deletions.
24 changes: 12 additions & 12 deletions packages/xsnap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Xsnap provides a Node.js API for controlling Xsnap workers.
const worker = xsnap();
await worker.evaluate(`
// Incrementer, running on XS.
function answerSysCall(message) {
function handleCommand(message) {
const number = Number(String.fromArrayBuffer(message));
return ArrayBuffer.fromString(String(number + 1));
}
Expand All @@ -23,18 +23,18 @@ Some time later, possibly on a different computer…
```js
const decoder = new TextDecoder();
const worker = xsnap({ snapshot: 'bootstrap.xss' });
const answer = await worker.sysCall('1');
console.log(decoder.decode(answer)); // 2
const response = await worker.issueCommand('1');
console.log(decoder.decode(response)); // 2
await worker.close();
```

The parent and child communicate using "syscalls".
The parent and child communicate using "commands".

- The XS child uses the synchronous `sysCall` function to send a request and
receive as response from the Node.js parent.
- The XS child can implement a synchronous `answserSysCall` function to respond
to syscalls from the Node.js parent.
- The Node.js parent uses an asynchronous `sysCall` method to send a request
and receive a response from the XS child.
- The Node.js parent can implement an asynchronous `answerSysCall` function to
respond to syscalls from the XS child.
- The XS child uses the synchronous `issueCommand` function to send a request
and receive as response from the Node.js parent.
- The XS child can implement a synchronous `handleCommand` function to respond
to commands from the Node.js parent.
- The Node.js parent uses an asynchronous `issueCommand` method to send a
request and receive a response from the XS child.
- The Node.js parent can implement an asynchronous `handleCommand` function to
respond to commands from the XS child.
10 changes: 5 additions & 5 deletions packages/xsnap/src/xsnap.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ static void fxFreezeBuiltIns(txMachine* the);
static void fxPatchBuiltIns(txMachine* the);
static void fxPrintUsage();

static void fx_sysCall(xsMachine *the);
static void fx_issueCommand(xsMachine *the);
static void fx_Array_prototype_meter(xsMachine* the);

// extern void fx_clearTimer(txMachine* the);
Expand Down Expand Up @@ -75,7 +75,7 @@ static char* fxWriteNetStringError(int code);
// upgrade.
#define mxSnapshotCallbackCount 2
txCallback gxSnapshotCallbacks[mxSnapshotCallbackCount] = {
fx_sysCall, // 0
fx_issueCommand, // 0
fx_Array_prototype_meter, // 1
// fx_gc,
// fx_evalScript,
Expand Down Expand Up @@ -325,7 +325,7 @@ int main(int argc, char* argv[])
xsVar(1) = xsArrayBuffer(nsbuf + 1, nslen - 1);
xsTry {
if (command == '?') {
xsVar(2) = xsCall1(xsGlobal, xsID("answerSysCall"), xsVar(1));
xsVar(2) = xsCall1(xsGlobal, xsID("handleCommand"), xsVar(1));
if (xsTypeOf(xsVar(2)) != xsUndefinedType) {
responseLength = fxGetArrayBufferLength(machine, &xsVar(2));
response = malloc(responseLength);
Expand Down Expand Up @@ -461,7 +461,7 @@ void fxBuildAgent(xsMachine* the)
txSlot* slot;
mxPush(mxGlobal);
slot = fxLastProperty(the, fxToInstance(the, the->stack));
slot = fxNextHostFunctionProperty(the, slot, fx_sysCall, 1, xsID("sysCall"), XS_DONT_ENUM_FLAG);
slot = fxNextHostFunctionProperty(the, slot, fx_issueCommand, 1, xsID("issueCommand"), XS_DONT_ENUM_FLAG);
// slot = fxNextHostFunctionProperty(the, slot, fx_clearTimer, 1, xsID("clearImmediate"), XS_DONT_ENUM_FLAG);
// slot = fxNextHostFunctionProperty(the, slot, fx_clearTimer, 1, xsID("clearInterval"), XS_DONT_ENUM_FLAG);
// slot = fxNextHostFunctionProperty(the, slot, fx_clearTimer, 1, xsID("clearTimeout"), XS_DONT_ENUM_FLAG);
Expand Down Expand Up @@ -1304,7 +1304,7 @@ static char* fxWriteNetStringError(int code)
}
}

static void fx_sysCall(xsMachine *the)
static void fx_issueCommand(xsMachine *the)
{
int argc = xsToInteger(xsArgc);
if (argc < 1) {
Expand Down
18 changes: 9 additions & 9 deletions packages/xsnap/src/xsnap.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ const decoder = new TextDecoder();
* @param {Uint8Array} arg
* @returns {Uint8Array}
*/
function echoSysCall(arg) {
function echoCommand(arg) {
return arg;
}

/**
* @param {Object} options
* @param {string} options.os
* @param {Spawn} options.spawn
* @param {(request:Uint8Array) => Promise<Uint8Array>} [options.answerSysCall]
* @param {(request:Uint8Array) => Promise<Uint8Array>} [options.handleCommand]
* @param {string=} [options.name]
* @param {boolean=} [options.debug]
* @param {string=} [options.snapshot]
Expand All @@ -47,7 +47,7 @@ export function xsnap(options) {
os,
spawn,
name = '<unnamed xsnap worker>',
answerSysCall = echoSysCall,
handleCommand = echoCommand,
debug = false,
snapshot = undefined,
stdout = 'inherit',
Expand Down Expand Up @@ -126,7 +126,7 @@ export function xsnap(options) {
} else if (message[0] === ERROR) {
throw new Error(`Uncaught exception in ${name}`);
} else if (message[0] === QUERY) {
await messagesToXsnap.next(await answerSysCall(message.subarray(1)));
await messagesToXsnap.next(await handleCommand(message.subarray(1)));
}
}
}
Expand Down Expand Up @@ -177,7 +177,7 @@ export function xsnap(options) {
* @param {Uint8Array} message
* @returns {Promise<Uint8Array>}
*/
async function sysCall(message) {
async function issueCommand(message) {
const result = baton.then(async () => {
const request = new Uint8Array(message.length + 1);
request[0] = QUERY;
Expand All @@ -196,8 +196,8 @@ export function xsnap(options) {
* @param {string} message
* @returns {Promise<string>}
*/
async function stringSysCall(message) {
return decoder.decode(await sysCall(encoder.encode(message)));
async function issueStringCommand(message) {
return decoder.decode(await issueCommand(encoder.encode(message)));
}

/**
Expand All @@ -223,8 +223,8 @@ export function xsnap(options) {
}

return {
sysCall,
stringSysCall,
issueCommand,
issueStringCommand,
close,
evaluate,
execute,
Expand Down
12 changes: 6 additions & 6 deletions packages/xsnap/src/xsrepl.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ async function main() {
};

/**
* For the purposes of the REPL, the only syscall is effectively `print`.
* For the purposes of the REPL, the only command is effectively `print`.
*
* @param {Uint8Array} message
* @returns {Promise<Uint8Array>}
*/
async function answerSysCall(message) {
async function handleCommand(message) {
console.log(decoder.decode(message));
return new Uint8Array();
}
Expand All @@ -36,17 +36,17 @@ async function main() {
output: process.stdout,
});

let vat = xsnap({ ...xsnapOptions, answerSysCall });
let vat = xsnap({ ...xsnapOptions, handleCommand });

await vat.evaluate(`
const compartment = new Compartment();
function answerSysCall(request) {
function handleCommand(request) {
const command = String.fromArrayBuffer(request);
let result = compartment.evaluate(command);
if (result === undefined) {
result = null;
}
sysCall(ArrayBuffer.fromString(JSON.stringify(result, null, 4)));
issueCommand(ArrayBuffer.fromString(JSON.stringify(result, null, 4)));
}
`);

Expand All @@ -67,7 +67,7 @@ async function main() {
} else if (answer === 'load') {
const file = await ask('file> ');
await vat.close();
vat = xsnap({ ...xsnapOptions, answerSysCall, snapshot: file });
vat = xsnap({ ...xsnapOptions, handleCommand, snapshot: file });
} else if (answer === 'save') {
const file = await ask('file> ');
await vat.snapshot(file);
Expand Down
4 changes: 2 additions & 2 deletions packages/xsnap/test/fixture-xsnap-script.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global sysCall */
/* global issueCommand */
(async () => {
sysCall(ArrayBuffer.fromString('Hello, World!'));
issueCommand(ArrayBuffer.fromString('Hello, World!'));
})();
72 changes: 36 additions & 36 deletions packages/xsnap/test/test-xsnap.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,28 @@ const xsnapOptions = {
os: os.type(),
};

test('evaluate and sysCall', async t => {
test('evaluate and issueCommand', async t => {
const messages = [];
async function answerSysCall(message) {
async function handleCommand(message) {
messages.push(decoder.decode(message));
return new Uint8Array();
}
const vat = xsnap({ ...xsnapOptions, answerSysCall });
await vat.evaluate(`sysCall(ArrayBuffer.fromString("Hello, World!"));`);
const vat = xsnap({ ...xsnapOptions, handleCommand });
await vat.evaluate(`issueCommand(ArrayBuffer.fromString("Hello, World!"));`);
await vat.close();
t.deepEqual(['Hello, World!'], messages);
});

test('evaluate until idle', async t => {
const messages = [];
async function answerSysCall(message) {
async function handleCommand(message) {
messages.push(decoder.decode(message));
return new Uint8Array();
}
const vat = xsnap({ ...xsnapOptions, answerSysCall });
const vat = xsnap({ ...xsnapOptions, handleCommand });
await vat.evaluate(`
(async () => {
sysCall(ArrayBuffer.fromString("Hello, World!"));
issueCommand(ArrayBuffer.fromString("Hello, World!"));
})();
`);
await vat.close();
Expand All @@ -43,71 +43,71 @@ test('evaluate until idle', async t => {

test('run script until idle', async t => {
const messages = [];
async function answerSysCall(message) {
async function handleCommand(message) {
messages.push(decoder.decode(message));
return new Uint8Array();
}
const vat = xsnap({ ...xsnapOptions, answerSysCall });
const vat = xsnap({ ...xsnapOptions, handleCommand });
await vat.execute(new URL('fixture-xsnap-script.js', importMetaUrl).pathname);
await vat.close();
t.deepEqual(['Hello, World!'], messages);
});

test('sysCall is synchronous inside, async outside', async t => {
test('issueCommand is synchronous inside, async outside', async t => {
const messages = [];
async function answerSysCall(request) {
async function handleCommand(request) {
const number = +decoder.decode(request);
await Promise.resolve(null);
messages.push(number);
await Promise.resolve(null);
return encoder.encode(`${number + 1}`);
}
const vat = xsnap({ ...xsnapOptions, answerSysCall });
const vat = xsnap({ ...xsnapOptions, handleCommand });
await vat.evaluate(`
const response = sysCall(ArrayBuffer.fromString('0'));
const response = issueCommand(ArrayBuffer.fromString('0'));
const number = +String.fromArrayBuffer(response);
sysCall(ArrayBuffer.fromString(String(number + 1)));
issueCommand(ArrayBuffer.fromString(String(number + 1)));
`);
await vat.close();
t.deepEqual([0, 2], messages);
});

test('deliver a message', async t => {
const messages = [];
async function answerSysCall(message) {
async function handleCommand(message) {
messages.push(+decoder.decode(message));
return new Uint8Array();
}
const vat = xsnap({ ...xsnapOptions, answerSysCall });
const vat = xsnap({ ...xsnapOptions, handleCommand });
await vat.evaluate(`
function answerSysCall(message) {
function handleCommand(message) {
const number = +String.fromArrayBuffer(message);
sysCall(ArrayBuffer.fromString(String(number + 1)));
issueCommand(ArrayBuffer.fromString(String(number + 1)));
};
`);
await vat.stringSysCall('0');
await vat.stringSysCall('1');
await vat.stringSysCall('2');
await vat.issueStringCommand('0');
await vat.issueStringCommand('1');
await vat.issueStringCommand('2');
await vat.close();
t.deepEqual([1, 2, 3], messages);
});

test.only('receive a response', async t => {
const messages = [];
async function answerSysCall(message) {
async function handleCommand(message) {
messages.push(+decoder.decode(message));
return new Uint8Array();
}
const vat = xsnap({ ...xsnapOptions, answerSysCall });
const vat = xsnap({ ...xsnapOptions, handleCommand });
await vat.evaluate(`
function answerSysCall(message) {
function handleCommand(message) {
const number = +String.fromArrayBuffer(message);
return ArrayBuffer.fromString(String(number + 1));
};
`);
t.is('1', await vat.stringSysCall('0'));
t.is('2', await vat.stringSysCall('1'));
t.is('3', await vat.stringSysCall('2'));
t.is('1', await vat.issueStringCommand('0'));
t.is('2', await vat.issueStringCommand('1'));
t.is('3', await vat.issueStringCommand('2'));
await vat.close();
});

Expand All @@ -119,41 +119,41 @@ function* count(end, start = 0, stride = 1) {

test('serialize concurrent messages', async t => {
const messages = [];
async function answerSysCall(message) {
async function handleCommand(message) {
messages.push(+decoder.decode(message));
return new Uint8Array();
}
const vat = xsnap({ ...xsnapOptions, answerSysCall });
const vat = xsnap({ ...xsnapOptions, handleCommand });
await vat.evaluate(`
globalThis.answerSysCall = message => {
globalThis.handleCommand = message => {
const number = +String.fromArrayBuffer(message);
sysCall(ArrayBuffer.fromString(String(number + 1)));
issueCommand(ArrayBuffer.fromString(String(number + 1)));
};
`);
await Promise.all([...count(100)].map(n => vat.stringSysCall(`${n}`)));
await Promise.all([...count(100)].map(n => vat.issueStringCommand(`${n}`)));
await vat.close();
t.deepEqual([...count(101, 1)], messages);
});

test('write and read snapshot', async t => {
const messages = [];
async function answerSysCall(message) {
async function handleCommand(message) {
messages.push(decoder.decode(message));
return new Uint8Array();
}

const snapshot = new URL('fixture-snapshot.xss', importMetaUrl).pathname;

const vat0 = xsnap({ ...xsnapOptions, answerSysCall });
const vat0 = xsnap({ ...xsnapOptions, handleCommand });
await vat0.evaluate(`
globalThis.hello = "Hello, World!";
`);
await vat0.snapshot(snapshot);
await vat0.close();

const vat1 = xsnap({ ...xsnapOptions, answerSysCall, snapshot });
const vat1 = xsnap({ ...xsnapOptions, handleCommand, snapshot });
await vat1.evaluate(`
sysCall(ArrayBuffer.fromString(hello));
issueCommand(ArrayBuffer.fromString(hello));
`);
await vat1.close();

Expand Down

0 comments on commit 3576b5c

Please sign in to comment.