mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
wasi: add reactor support
PR-URL: https://github.com/nodejs/node/pull/34046 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
parent
da8a1ef21b
commit
9e135accbb
@ -132,6 +132,23 @@ Attempt to begin execution of `instance` as a WASI command by invoking its
|
||||
|
||||
If `start()` is called more than once, an exception is thrown.
|
||||
|
||||
### `wasi.initialize(instance)`
|
||||
<!-- YAML
|
||||
added:
|
||||
- REPLACEME
|
||||
-->
|
||||
|
||||
* `instance` {WebAssembly.Instance}
|
||||
|
||||
Attempt to initialize `instance` as a WASI reactor by invoking its
|
||||
`_initialize()` export, if it is present. If `instance` contains a `_start()`
|
||||
export, then an exception is thrown.
|
||||
|
||||
`initialize()` requires that `instance` exports a [`WebAssembly.Memory`][] named
|
||||
`memory`. If `instance` does not have a `memory` export an exception is thrown.
|
||||
|
||||
If `initialize()` is called more than once, an exception is thrown.
|
||||
|
||||
### `wasi.wasiImport`
|
||||
<!-- YAML
|
||||
added:
|
||||
|
92
lib/wasi.js
92
lib/wasi.js
@ -20,13 +20,36 @@ const {
|
||||
validateObject,
|
||||
} = require('internal/validators');
|
||||
const { WASI: _WASI } = internalBinding('wasi');
|
||||
const kExitCode = Symbol('exitCode');
|
||||
const kSetMemory = Symbol('setMemory');
|
||||
const kStarted = Symbol('started');
|
||||
const kExitCode = Symbol('kExitCode');
|
||||
const kSetMemory = Symbol('kSetMemory');
|
||||
const kStarted = Symbol('kStarted');
|
||||
const kInstance = Symbol('kInstance');
|
||||
|
||||
emitExperimentalWarning('WASI');
|
||||
|
||||
|
||||
function setupInstance(self, instance) {
|
||||
validateObject(instance, 'instance');
|
||||
validateObject(instance.exports, 'instance.exports');
|
||||
|
||||
// WASI::_SetMemory() in src/node_wasi.cc only expects that |memory| is
|
||||
// an object. It will try to look up the .buffer property when needed
|
||||
// and fail with UVWASI_EINVAL when the property is missing or is not
|
||||
// an ArrayBuffer. Long story short, we don't need much validation here
|
||||
// but we type-check anyway because it helps catch bugs in the user's
|
||||
// code early.
|
||||
validateObject(instance.exports.memory, 'instance.exports.memory');
|
||||
if (!isArrayBuffer(instance.exports.memory.buffer)) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'instance.exports.memory.buffer',
|
||||
['WebAssembly.Memory'],
|
||||
instance.exports.memory.buffer);
|
||||
}
|
||||
|
||||
self[kInstance] = instance;
|
||||
self[kSetMemory](instance.exports.memory);
|
||||
}
|
||||
|
||||
class WASI {
|
||||
constructor(options = {}) {
|
||||
validateObject(options, 'options');
|
||||
@ -75,51 +98,31 @@ class WASI {
|
||||
this.wasiImport = wrap;
|
||||
this[kStarted] = false;
|
||||
this[kExitCode] = 0;
|
||||
this[kInstance] = undefined;
|
||||
}
|
||||
|
||||
// Must not export _initialize, must export _start
|
||||
start(instance) {
|
||||
validateObject(instance, 'instance');
|
||||
if (this[kStarted]) {
|
||||
throw new ERR_WASI_ALREADY_STARTED();
|
||||
}
|
||||
this[kStarted] = true;
|
||||
|
||||
const exports = instance.exports;
|
||||
setupInstance(this, instance);
|
||||
|
||||
validateObject(exports, 'instance.exports');
|
||||
|
||||
const { _initialize, _start, memory } = exports;
|
||||
const { _start, _initialize } = this[kInstance].exports;
|
||||
|
||||
if (typeof _start !== 'function') {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'instance.exports._start', 'function', _start);
|
||||
}
|
||||
|
||||
if (_initialize !== undefined) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'instance.exports._initialize', 'undefined', _initialize);
|
||||
}
|
||||
|
||||
// WASI::_SetMemory() in src/node_wasi.cc only expects that |memory| is
|
||||
// an object. It will try to look up the .buffer property when needed
|
||||
// and fail with UVWASI_EINVAL when the property is missing or is not
|
||||
// an ArrayBuffer. Long story short, we don't need much validation here
|
||||
// but we type-check anyway because it helps catch bugs in the user's
|
||||
// code early.
|
||||
validateObject(memory, 'instance.exports.memory');
|
||||
|
||||
if (!isArrayBuffer(memory.buffer)) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'instance.exports.memory.buffer',
|
||||
['WebAssembly.Memory'],
|
||||
memory.buffer);
|
||||
}
|
||||
|
||||
if (this[kStarted]) {
|
||||
throw new ERR_WASI_ALREADY_STARTED();
|
||||
}
|
||||
|
||||
this[kStarted] = true;
|
||||
this[kSetMemory](memory);
|
||||
|
||||
try {
|
||||
exports._start();
|
||||
_start();
|
||||
} catch (err) {
|
||||
if (err !== kExitCode) {
|
||||
throw err;
|
||||
@ -128,6 +131,31 @@ class WASI {
|
||||
|
||||
return this[kExitCode];
|
||||
}
|
||||
|
||||
// Must not export _start, may optionally export _initialize
|
||||
initialize(instance) {
|
||||
if (this[kStarted]) {
|
||||
throw new ERR_WASI_ALREADY_STARTED();
|
||||
}
|
||||
this[kStarted] = true;
|
||||
|
||||
setupInstance(this, instance);
|
||||
|
||||
const { _start, _initialize } = this[kInstance].exports;
|
||||
|
||||
if (typeof _initialize !== 'function' && _initialize !== undefined) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'instance.exports._initialize', 'function', _initialize);
|
||||
}
|
||||
if (_start !== undefined) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'instance.exports._start', 'undefined', _initialize);
|
||||
}
|
||||
|
||||
if (_initialize !== undefined) {
|
||||
_initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
195
test/wasi/test-wasi-initialize-validation.js
Normal file
195
test/wasi/test-wasi-initialize-validation.js
Normal file
@ -0,0 +1,195 @@
|
||||
// Flags: --experimental-wasi-unstable-preview1
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
const { WASI } = require('wasi');
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
const bufferSource = fixtures.readSync('simple.wasm');
|
||||
|
||||
(async () => {
|
||||
{
|
||||
// Verify that a WebAssembly.Instance is passed in.
|
||||
const wasi = new WASI();
|
||||
|
||||
assert.throws(
|
||||
() => { wasi.initialize(); },
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /"instance" argument must be of type object/
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// Verify that the passed instance has an exports objects.
|
||||
const wasi = new WASI({});
|
||||
const wasm = await WebAssembly.compile(bufferSource);
|
||||
const instance = await WebAssembly.instantiate(wasm);
|
||||
|
||||
Object.defineProperty(instance, 'exports', { get() { return null; } });
|
||||
assert.throws(
|
||||
() => { wasi.initialize(instance); },
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /"instance\.exports" property must be of type object/
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// Verify that a _initialize() export was passed.
|
||||
const wasi = new WASI({});
|
||||
const wasm = await WebAssembly.compile(bufferSource);
|
||||
const instance = await WebAssembly.instantiate(wasm);
|
||||
|
||||
Object.defineProperty(instance, 'exports', {
|
||||
get() {
|
||||
return { _initialize: 5, memory: new Uint8Array() };
|
||||
},
|
||||
});
|
||||
assert.throws(
|
||||
() => { wasi.initialize(instance); },
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /"instance\.exports\._initialize" property must be of type function/
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// Verify that a _start export was not passed.
|
||||
const wasi = new WASI({});
|
||||
const wasm = await WebAssembly.compile(bufferSource);
|
||||
const instance = await WebAssembly.instantiate(wasm);
|
||||
|
||||
Object.defineProperty(instance, 'exports', {
|
||||
get() {
|
||||
return {
|
||||
_start() {},
|
||||
_initialize() {},
|
||||
memory: new Uint8Array(),
|
||||
};
|
||||
}
|
||||
});
|
||||
assert.throws(
|
||||
() => { wasi.initialize(instance); },
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /"instance\.exports\._start" property must be undefined/
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// Verify that a memory export was passed.
|
||||
const wasi = new WASI({});
|
||||
const wasm = await WebAssembly.compile(bufferSource);
|
||||
const instance = await WebAssembly.instantiate(wasm);
|
||||
|
||||
Object.defineProperty(instance, 'exports', {
|
||||
get() { return { _initialize() {} }; }
|
||||
});
|
||||
assert.throws(
|
||||
() => { wasi.initialize(instance); },
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /"instance\.exports\.memory" property must be of type object/
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// Verify that a non-ArrayBuffer memory.buffer is rejected.
|
||||
const wasi = new WASI({});
|
||||
const wasm = await WebAssembly.compile(bufferSource);
|
||||
const instance = await WebAssembly.instantiate(wasm);
|
||||
|
||||
Object.defineProperty(instance, 'exports', {
|
||||
get() {
|
||||
return {
|
||||
_initialize() {},
|
||||
memory: {},
|
||||
};
|
||||
}
|
||||
});
|
||||
// The error message is a little white lie because any object
|
||||
// with a .buffer property of type ArrayBuffer is accepted,
|
||||
// but 99% of the time a WebAssembly.Memory object is used.
|
||||
assert.throws(
|
||||
() => { wasi.initialize(instance); },
|
||||
{
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /"instance\.exports\.memory\.buffer" property must be an WebAssembly\.Memory/
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
// Verify that an argument that duck-types as a WebAssembly.Instance
|
||||
// is accepted.
|
||||
const wasi = new WASI({});
|
||||
const wasm = await WebAssembly.compile(bufferSource);
|
||||
const instance = await WebAssembly.instantiate(wasm);
|
||||
|
||||
Object.defineProperty(instance, 'exports', {
|
||||
get() {
|
||||
return {
|
||||
_initialize() {},
|
||||
memory: { buffer: new ArrayBuffer(0) },
|
||||
};
|
||||
}
|
||||
});
|
||||
wasi.initialize(instance);
|
||||
}
|
||||
|
||||
{
|
||||
// Verify that a WebAssembly.Instance from another VM context is accepted.
|
||||
const wasi = new WASI({});
|
||||
const instance = await vm.runInNewContext(`
|
||||
(async () => {
|
||||
const wasm = await WebAssembly.compile(bufferSource);
|
||||
const instance = await WebAssembly.instantiate(wasm);
|
||||
|
||||
Object.defineProperty(instance, 'exports', {
|
||||
get() {
|
||||
return {
|
||||
_initialize() {},
|
||||
memory: new WebAssembly.Memory({ initial: 1 })
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return instance;
|
||||
})()
|
||||
`, { bufferSource });
|
||||
|
||||
wasi.initialize(instance);
|
||||
}
|
||||
|
||||
{
|
||||
// Verify that initialize() can only be called once.
|
||||
const wasi = new WASI({});
|
||||
const wasm = await WebAssembly.compile(bufferSource);
|
||||
const instance = await WebAssembly.instantiate(wasm);
|
||||
|
||||
Object.defineProperty(instance, 'exports', {
|
||||
get() {
|
||||
return {
|
||||
_initialize() {},
|
||||
memory: new WebAssembly.Memory({ initial: 1 })
|
||||
};
|
||||
}
|
||||
});
|
||||
wasi.initialize(instance);
|
||||
assert.throws(
|
||||
() => { wasi.initialize(instance); },
|
||||
{
|
||||
code: 'ERR_WASI_ALREADY_STARTED',
|
||||
message: /^WASI instance has already started$/
|
||||
}
|
||||
);
|
||||
}
|
||||
})().then(common.mustCall());
|
@ -45,7 +45,11 @@ const bufferSource = fixtures.readSync('simple.wasm');
|
||||
const wasm = await WebAssembly.compile(bufferSource);
|
||||
const instance = await WebAssembly.instantiate(wasm);
|
||||
|
||||
Object.defineProperty(instance, 'exports', { get() { return {}; } });
|
||||
Object.defineProperty(instance, 'exports', {
|
||||
get() {
|
||||
return { memory: new Uint8Array() };
|
||||
},
|
||||
});
|
||||
assert.throws(
|
||||
() => { wasi.start(instance); },
|
||||
{
|
||||
@ -65,7 +69,8 @@ const bufferSource = fixtures.readSync('simple.wasm');
|
||||
get() {
|
||||
return {
|
||||
_start() {},
|
||||
_initialize() {}
|
||||
_initialize() {},
|
||||
memory: new Uint8Array(),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user