wasi: add support for version when creating WASI
Refs: https://github.com/nodejs/node/issues/46254 - add version to options when creating WASI object - add convenience function to return importObject Signed-off-by: Michael Dawson <mdawson@devrus.com> PR-URL: https://github.com/nodejs/node/pull/46469 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
parent
fda0de4c78
commit
2d35f669ae
@ -16,6 +16,7 @@ import { WASI } from 'wasi';
|
|||||||
import { argv, env } from 'node:process';
|
import { argv, env } from 'node:process';
|
||||||
|
|
||||||
const wasi = new WASI({
|
const wasi = new WASI({
|
||||||
|
version: 'preview1',
|
||||||
args: argv,
|
args: argv,
|
||||||
env,
|
env,
|
||||||
preopens: {
|
preopens: {
|
||||||
@ -23,14 +24,10 @@ const wasi = new WASI({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Some WASI binaries require:
|
|
||||||
// const importObject = { wasi_unstable: wasi.wasiImport };
|
|
||||||
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
|
|
||||||
|
|
||||||
const wasm = await WebAssembly.compile(
|
const wasm = await WebAssembly.compile(
|
||||||
await readFile(new URL('./demo.wasm', import.meta.url)),
|
await readFile(new URL('./demo.wasm', import.meta.url)),
|
||||||
);
|
);
|
||||||
const instance = await WebAssembly.instantiate(wasm, importObject);
|
const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject());
|
||||||
|
|
||||||
wasi.start(instance);
|
wasi.start(instance);
|
||||||
```
|
```
|
||||||
@ -43,6 +40,7 @@ const { argv, env } = require('node:process');
|
|||||||
const { join } = require('node:path');
|
const { join } = require('node:path');
|
||||||
|
|
||||||
const wasi = new WASI({
|
const wasi = new WASI({
|
||||||
|
version: 'preview1',
|
||||||
args: argv,
|
args: argv,
|
||||||
env,
|
env,
|
||||||
preopens: {
|
preopens: {
|
||||||
@ -50,15 +48,11 @@ const wasi = new WASI({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Some WASI binaries require:
|
|
||||||
// const importObject = { wasi_unstable: wasi.wasiImport };
|
|
||||||
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const wasm = await WebAssembly.compile(
|
const wasm = await WebAssembly.compile(
|
||||||
await readFile(join(__dirname, 'demo.wasm')),
|
await readFile(join(__dirname, 'demo.wasm')),
|
||||||
);
|
);
|
||||||
const instance = await WebAssembly.instantiate(wasm, importObject);
|
const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject());
|
||||||
|
|
||||||
wasi.start(instance);
|
wasi.start(instance);
|
||||||
})();
|
})();
|
||||||
@ -126,6 +120,10 @@ sandbox directory structure configured explicitly.
|
|||||||
added:
|
added:
|
||||||
- v13.3.0
|
- v13.3.0
|
||||||
- v12.16.0
|
- v12.16.0
|
||||||
|
changes:
|
||||||
|
- version: REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/46469
|
||||||
|
description: version field added to options.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
* `options` {Object}
|
* `options` {Object}
|
||||||
@ -148,6 +146,30 @@ added:
|
|||||||
WebAssembly application. **Default:** `1`.
|
WebAssembly application. **Default:** `1`.
|
||||||
* `stderr` {integer} The file descriptor used as standard error in the
|
* `stderr` {integer} The file descriptor used as standard error in the
|
||||||
WebAssembly application. **Default:** `2`.
|
WebAssembly application. **Default:** `2`.
|
||||||
|
* `version` {string} The version of WASI requested. Currently the only
|
||||||
|
supported versions are `unstable` and `preview1`. **Default:** `preview1`.
|
||||||
|
|
||||||
|
### `wasi.getImportObject()`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
Return an import object that can be passed to `WebAssembly.instantiate()` if
|
||||||
|
no other WASM imports are needed beyond those provided by WASI.
|
||||||
|
|
||||||
|
If version `unstable` was passed into the constructor it will return:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ wasi_unstable: wasi.wasiImport }
|
||||||
|
```
|
||||||
|
|
||||||
|
If version `preview1` was passed into the constructor or no version was
|
||||||
|
specified it will return:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ wasi_snapshot_preview1: wasi.wasiImport }
|
||||||
|
```
|
||||||
|
|
||||||
### `wasi.start(instance)`
|
### `wasi.start(instance)`
|
||||||
|
|
||||||
|
34
lib/wasi.js
34
lib/wasi.js
@ -10,6 +10,7 @@ const {
|
|||||||
} = primordials;
|
} = primordials;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
ERR_INVALID_ARG_VALUE,
|
||||||
ERR_WASI_ALREADY_STARTED
|
ERR_WASI_ALREADY_STARTED
|
||||||
} = require('internal/errors').codes;
|
} = require('internal/errors').codes;
|
||||||
const {
|
const {
|
||||||
@ -22,13 +23,14 @@ const {
|
|||||||
validateFunction,
|
validateFunction,
|
||||||
validateInt32,
|
validateInt32,
|
||||||
validateObject,
|
validateObject,
|
||||||
|
validateString,
|
||||||
validateUndefined,
|
validateUndefined,
|
||||||
} = require('internal/validators');
|
} = require('internal/validators');
|
||||||
const { WASI: _WASI } = internalBinding('wasi');
|
|
||||||
const kExitCode = Symbol('kExitCode');
|
const kExitCode = Symbol('kExitCode');
|
||||||
const kSetMemory = Symbol('kSetMemory');
|
const kSetMemory = Symbol('kSetMemory');
|
||||||
const kStarted = Symbol('kStarted');
|
const kStarted = Symbol('kStarted');
|
||||||
const kInstance = Symbol('kInstance');
|
const kInstance = Symbol('kInstance');
|
||||||
|
const kBindingName = Symbol('kBindingName');
|
||||||
|
|
||||||
emitExperimentalWarning('WASI');
|
emitExperimentalWarning('WASI');
|
||||||
|
|
||||||
@ -45,6 +47,31 @@ class WASI {
|
|||||||
constructor(options = kEmptyObject) {
|
constructor(options = kEmptyObject) {
|
||||||
validateObject(options, 'options');
|
validateObject(options, 'options');
|
||||||
|
|
||||||
|
let _WASI;
|
||||||
|
if (options.version !== undefined) {
|
||||||
|
validateString(options.version, 'options.version');
|
||||||
|
switch (options.version) {
|
||||||
|
case 'unstable':
|
||||||
|
({ WASI: _WASI } = internalBinding('wasi'));
|
||||||
|
this[kBindingName] = 'wasi_unstable';
|
||||||
|
break;
|
||||||
|
// When adding support for additional wasi versions add case here
|
||||||
|
case 'preview1':
|
||||||
|
({ WASI: _WASI } = internalBinding('wasi'));
|
||||||
|
this[kBindingName] = 'wasi_snapshot_preview1';
|
||||||
|
break;
|
||||||
|
// When adding support for additional wasi versions add case here
|
||||||
|
default:
|
||||||
|
throw new ERR_INVALID_ARG_VALUE('options.version',
|
||||||
|
options.version,
|
||||||
|
'unsupported WASI version');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO(mdawson): Remove this in a SemVer major PR before Node.js 20
|
||||||
|
({ WASI: _WASI } = internalBinding('wasi'));
|
||||||
|
this[kBindingName] = 'wasi_snapshot_preview1';
|
||||||
|
}
|
||||||
|
|
||||||
if (options.args !== undefined)
|
if (options.args !== undefined)
|
||||||
validateArray(options.args, 'options.args');
|
validateArray(options.args, 'options.args');
|
||||||
const args = ArrayPrototypeMap(options.args || [], String);
|
const args = ArrayPrototypeMap(options.args || [], String);
|
||||||
@ -138,8 +165,11 @@ class WASI {
|
|||||||
_initialize();
|
_initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
getImportObject() {
|
||||||
|
return { [this[kBindingName]]: this.wasiImport };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = { WASI };
|
module.exports = { WASI };
|
||||||
|
|
||||||
|
@ -1247,10 +1247,10 @@ void WASI::_SetMemory(const FunctionCallbackInfo<Value>& args) {
|
|||||||
wasi->memory_.Reset(wasi->env()->isolate(), args[0].As<WasmMemoryObject>());
|
wasi->memory_.Reset(wasi->env()->isolate(), args[0].As<WasmMemoryObject>());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Initialize(Local<Object> target,
|
static void InitializePreview1(Local<Object> target,
|
||||||
Local<Value> unused,
|
Local<Value> unused,
|
||||||
Local<Context> context,
|
Local<Context> context,
|
||||||
void* priv) {
|
void* priv) {
|
||||||
Environment* env = Environment::GetCurrent(context);
|
Environment* env = Environment::GetCurrent(context);
|
||||||
Isolate* isolate = env->isolate();
|
Isolate* isolate = env->isolate();
|
||||||
|
|
||||||
@ -1313,8 +1313,7 @@ static void Initialize(Local<Object> target,
|
|||||||
SetConstructorFunction(context, target, "WASI", tmpl);
|
SetConstructorFunction(context, target, "WASI", tmpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace wasi
|
} // namespace wasi
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
NODE_BINDING_CONTEXT_AWARE_INTERNAL(wasi, node::wasi::Initialize)
|
NODE_BINDING_CONTEXT_AWARE_INTERNAL(wasi, node::wasi::InitializePreview1)
|
||||||
|
@ -47,3 +47,12 @@ assert.throws(() => { new WASI({ stderr: 'fhqwhgads' }); },
|
|||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
new WASI({ preopens: { '/sandbox': '__/not/real/path' } });
|
new WASI({ preopens: { '/sandbox': '__/not/real/path' } });
|
||||||
}, { code: 'UVWASI_ENOENT', message: /uvwasi_init/ });
|
}, { code: 'UVWASI_ENOENT', message: /uvwasi_init/ });
|
||||||
|
|
||||||
|
// If version is not a string, it should throw
|
||||||
|
assert.throws(() => { new WASI({ version: { x: 'y' } }); },
|
||||||
|
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bversion\b/ });
|
||||||
|
|
||||||
|
|
||||||
|
// If version is an unsupported version, it should throw
|
||||||
|
assert.throws(() => { new WASI({ version: 'not_a_version' }); },
|
||||||
|
{ code: 'ERR_INVALID_ARG_VALUE', message: /\bversion\b/ });
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const common = require('../common');
|
const common = require('../common');
|
||||||
|
|
||||||
if (process.argv[2] === 'wasi-child') {
|
if (process.argv[2] === 'wasi-child-default') {
|
||||||
|
// test default case
|
||||||
const fixtures = require('../common/fixtures');
|
const fixtures = require('../common/fixtures');
|
||||||
const tmpdir = require('../common/tmpdir');
|
const tmpdir = require('../common/tmpdir');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
@ -30,12 +31,49 @@ if (process.argv[2] === 'wasi-child') {
|
|||||||
|
|
||||||
wasi.start(instance);
|
wasi.start(instance);
|
||||||
})().then(common.mustCall());
|
})().then(common.mustCall());
|
||||||
|
} else if (process.argv[2] === 'wasi-child-preview1') {
|
||||||
|
// Test version set to preview1
|
||||||
|
const assert = require('assert');
|
||||||
|
const fixtures = require('../common/fixtures');
|
||||||
|
const tmpdir = require('../common/tmpdir');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
common.expectWarning('ExperimentalWarning',
|
||||||
|
'WASI is an experimental feature and might change at any time');
|
||||||
|
|
||||||
|
const { WASI } = require('wasi');
|
||||||
|
tmpdir.refresh();
|
||||||
|
const wasmDir = path.join(__dirname, 'wasm');
|
||||||
|
const wasiPreview1 = new WASI({
|
||||||
|
version: 'preview1',
|
||||||
|
args: ['foo', '-bar', '--baz=value'],
|
||||||
|
env: process.env,
|
||||||
|
preopens: {
|
||||||
|
'/sandbox': fixtures.path('wasi'),
|
||||||
|
'/tmp': tmpdir.path,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validate the getImportObject helper
|
||||||
|
assert.strictEqual(wasiPreview1.wasiImport,
|
||||||
|
wasiPreview1.getImportObject().wasi_snapshot_preview1);
|
||||||
|
const modulePathPreview1 = path.join(wasmDir, `${process.argv[3]}.wasm`);
|
||||||
|
const bufferPreview1 = fs.readFileSync(modulePathPreview1);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const { instance: instancePreview1 } =
|
||||||
|
await WebAssembly.instantiate(bufferPreview1,
|
||||||
|
wasiPreview1.getImportObject());
|
||||||
|
|
||||||
|
wasiPreview1.start(instancePreview1);
|
||||||
|
})().then(common.mustCall());
|
||||||
} else {
|
} else {
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const cp = require('child_process');
|
const cp = require('child_process');
|
||||||
const { checkoutEOL } = common;
|
const { checkoutEOL } = common;
|
||||||
|
|
||||||
function innerRunWASI(options, args) {
|
function innerRunWASI(options, args, flavor = 'default') {
|
||||||
console.log('executing', options.test);
|
console.log('executing', options.test);
|
||||||
const opts = {
|
const opts = {
|
||||||
env: {
|
env: {
|
||||||
@ -52,7 +90,7 @@ if (process.argv[2] === 'wasi-child') {
|
|||||||
...args,
|
...args,
|
||||||
'--experimental-wasi-unstable-preview1',
|
'--experimental-wasi-unstable-preview1',
|
||||||
__filename,
|
__filename,
|
||||||
'wasi-child',
|
'wasi-child-' + flavor,
|
||||||
options.test,
|
options.test,
|
||||||
], opts);
|
], opts);
|
||||||
console.log(child.stderr.toString());
|
console.log(child.stderr.toString());
|
||||||
@ -64,6 +102,7 @@ if (process.argv[2] === 'wasi-child') {
|
|||||||
function runWASI(options) {
|
function runWASI(options) {
|
||||||
innerRunWASI(options, ['--no-turbo-fast-api-calls']);
|
innerRunWASI(options, ['--no-turbo-fast-api-calls']);
|
||||||
innerRunWASI(options, ['--turbo-fast-api-calls']);
|
innerRunWASI(options, ['--turbo-fast-api-calls']);
|
||||||
|
innerRunWASI(options, ['--turbo-fast-api-calls'], 'preview1');
|
||||||
}
|
}
|
||||||
|
|
||||||
runWASI({ test: 'cant_dotdot' });
|
runWASI({ test: 'cant_dotdot' });
|
||||||
|
Loading…
x
Reference in New Issue
Block a user