Skip to content

Commit 2d35f66

Browse files
committed
wasi: add support for version when creating WASI
Refs: #46254 - add version to options when creating WASI object - add convenience function to return importObject Signed-off-by: Michael Dawson <[email protected]> PR-URL: #46469 Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent fda0de4 commit 2d35f66

File tree

5 files changed

+120
-21
lines changed

5 files changed

+120
-21
lines changed

doc/api/wasi.md

+32-10
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,18 @@ import { WASI } from 'wasi';
1616
import { argv, env } from 'node:process';
1717

1818
const wasi = new WASI({
19+
version: 'preview1',
1920
args: argv,
2021
env,
2122
preopens: {
2223
'/sandbox': '/some/real/path/that/wasm/can/access',
2324
},
2425
});
2526

26-
// Some WASI binaries require:
27-
// const importObject = { wasi_unstable: wasi.wasiImport };
28-
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
29-
3027
const wasm = await WebAssembly.compile(
3128
await readFile(new URL('./demo.wasm', import.meta.url)),
3229
);
33-
const instance = await WebAssembly.instantiate(wasm, importObject);
30+
const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject());
3431

3532
wasi.start(instance);
3633
```
@@ -43,22 +40,19 @@ const { argv, env } = require('node:process');
4340
const { join } = require('node:path');
4441

4542
const wasi = new WASI({
43+
version: 'preview1',
4644
args: argv,
4745
env,
4846
preopens: {
4947
'/sandbox': '/some/real/path/that/wasm/can/access',
5048
},
5149
});
5250

53-
// Some WASI binaries require:
54-
// const importObject = { wasi_unstable: wasi.wasiImport };
55-
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
56-
5751
(async () => {
5852
const wasm = await WebAssembly.compile(
5953
await readFile(join(__dirname, 'demo.wasm')),
6054
);
61-
const instance = await WebAssembly.instantiate(wasm, importObject);
55+
const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject());
6256

6357
wasi.start(instance);
6458
})();
@@ -126,6 +120,10 @@ sandbox directory structure configured explicitly.
126120
added:
127121
- v13.3.0
128122
- v12.16.0
123+
changes:
124+
- version: REPLACEME
125+
pr-url: https://github.com/nodejs/node/pull/46469
126+
description: version field added to options.
129127
-->
130128

131129
* `options` {Object}
@@ -148,6 +146,30 @@ added:
148146
WebAssembly application. **Default:** `1`.
149147
* `stderr` {integer} The file descriptor used as standard error in the
150148
WebAssembly application. **Default:** `2`.
149+
* `version` {string} The version of WASI requested. Currently the only
150+
supported versions are `unstable` and `preview1`. **Default:** `preview1`.
151+
152+
### `wasi.getImportObject()`
153+
154+
<!-- YAML
155+
added: REPLACEME
156+
-->
157+
158+
Return an import object that can be passed to `WebAssembly.instantiate()` if
159+
no other WASM imports are needed beyond those provided by WASI.
160+
161+
If version `unstable` was passed into the constructor it will return:
162+
163+
```json
164+
{ wasi_unstable: wasi.wasiImport }
165+
```
166+
167+
If version `preview1` was passed into the constructor or no version was
168+
specified it will return:
169+
170+
```json
171+
{ wasi_snapshot_preview1: wasi.wasiImport }
172+
```
151173
152174
### `wasi.start(instance)`
153175

lib/wasi.js

+32-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const {
1010
} = primordials;
1111

1212
const {
13+
ERR_INVALID_ARG_VALUE,
1314
ERR_WASI_ALREADY_STARTED
1415
} = require('internal/errors').codes;
1516
const {
@@ -22,13 +23,14 @@ const {
2223
validateFunction,
2324
validateInt32,
2425
validateObject,
26+
validateString,
2527
validateUndefined,
2628
} = require('internal/validators');
27-
const { WASI: _WASI } = internalBinding('wasi');
2829
const kExitCode = Symbol('kExitCode');
2930
const kSetMemory = Symbol('kSetMemory');
3031
const kStarted = Symbol('kStarted');
3132
const kInstance = Symbol('kInstance');
33+
const kBindingName = Symbol('kBindingName');
3234

3335
emitExperimentalWarning('WASI');
3436

@@ -45,6 +47,31 @@ class WASI {
4547
constructor(options = kEmptyObject) {
4648
validateObject(options, 'options');
4749

50+
let _WASI;
51+
if (options.version !== undefined) {
52+
validateString(options.version, 'options.version');
53+
switch (options.version) {
54+
case 'unstable':
55+
({ WASI: _WASI } = internalBinding('wasi'));
56+
this[kBindingName] = 'wasi_unstable';
57+
break;
58+
// When adding support for additional wasi versions add case here
59+
case 'preview1':
60+
({ WASI: _WASI } = internalBinding('wasi'));
61+
this[kBindingName] = 'wasi_snapshot_preview1';
62+
break;
63+
// When adding support for additional wasi versions add case here
64+
default:
65+
throw new ERR_INVALID_ARG_VALUE('options.version',
66+
options.version,
67+
'unsupported WASI version');
68+
}
69+
} else {
70+
// TODO(mdawson): Remove this in a SemVer major PR before Node.js 20
71+
({ WASI: _WASI } = internalBinding('wasi'));
72+
this[kBindingName] = 'wasi_snapshot_preview1';
73+
}
74+
4875
if (options.args !== undefined)
4976
validateArray(options.args, 'options.args');
5077
const args = ArrayPrototypeMap(options.args || [], String);
@@ -138,8 +165,11 @@ class WASI {
138165
_initialize();
139166
}
140167
}
141-
}
142168

169+
getImportObject() {
170+
return { [this[kBindingName]]: this.wasiImport };
171+
}
172+
}
143173

144174
module.exports = { WASI };
145175

src/node_wasi.cc

+5-6
Original file line numberDiff line numberDiff line change
@@ -1247,10 +1247,10 @@ void WASI::_SetMemory(const FunctionCallbackInfo<Value>& args) {
12471247
wasi->memory_.Reset(wasi->env()->isolate(), args[0].As<WasmMemoryObject>());
12481248
}
12491249

1250-
static void Initialize(Local<Object> target,
1251-
Local<Value> unused,
1252-
Local<Context> context,
1253-
void* priv) {
1250+
static void InitializePreview1(Local<Object> target,
1251+
Local<Value> unused,
1252+
Local<Context> context,
1253+
void* priv) {
12541254
Environment* env = Environment::GetCurrent(context);
12551255
Isolate* isolate = env->isolate();
12561256

@@ -1313,8 +1313,7 @@ static void Initialize(Local<Object> target,
13131313
SetConstructorFunction(context, target, "WASI", tmpl);
13141314
}
13151315

1316-
13171316
} // namespace wasi
13181317
} // namespace node
13191318

1320-
NODE_BINDING_CONTEXT_AWARE_INTERNAL(wasi, node::wasi::Initialize)
1319+
NODE_BINDING_CONTEXT_AWARE_INTERNAL(wasi, node::wasi::InitializePreview1)

test/wasi/test-wasi-options-validation.js

+9
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,12 @@ assert.throws(() => { new WASI({ stderr: 'fhqwhgads' }); },
4747
assert.throws(() => {
4848
new WASI({ preopens: { '/sandbox': '__/not/real/path' } });
4949
}, { code: 'UVWASI_ENOENT', message: /uvwasi_init/ });
50+
51+
// If version is not a string, it should throw
52+
assert.throws(() => { new WASI({ version: { x: 'y' } }); },
53+
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bversion\b/ });
54+
55+
56+
// If version is an unsupported version, it should throw
57+
assert.throws(() => { new WASI({ version: 'not_a_version' }); },
58+
{ code: 'ERR_INVALID_ARG_VALUE', message: /\bversion\b/ });

test/wasi/test-wasi.js

+42-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use strict';
22
const common = require('../common');
33

4-
if (process.argv[2] === 'wasi-child') {
4+
if (process.argv[2] === 'wasi-child-default') {
5+
// test default case
56
const fixtures = require('../common/fixtures');
67
const tmpdir = require('../common/tmpdir');
78
const fs = require('fs');
@@ -30,12 +31,49 @@ if (process.argv[2] === 'wasi-child') {
3031

3132
wasi.start(instance);
3233
})().then(common.mustCall());
34+
} else if (process.argv[2] === 'wasi-child-preview1') {
35+
// Test version set to preview1
36+
const assert = require('assert');
37+
const fixtures = require('../common/fixtures');
38+
const tmpdir = require('../common/tmpdir');
39+
const fs = require('fs');
40+
const path = require('path');
41+
42+
common.expectWarning('ExperimentalWarning',
43+
'WASI is an experimental feature and might change at any time');
44+
45+
const { WASI } = require('wasi');
46+
tmpdir.refresh();
47+
const wasmDir = path.join(__dirname, 'wasm');
48+
const wasiPreview1 = new WASI({
49+
version: 'preview1',
50+
args: ['foo', '-bar', '--baz=value'],
51+
env: process.env,
52+
preopens: {
53+
'/sandbox': fixtures.path('wasi'),
54+
'/tmp': tmpdir.path,
55+
},
56+
});
57+
58+
// Validate the getImportObject helper
59+
assert.strictEqual(wasiPreview1.wasiImport,
60+
wasiPreview1.getImportObject().wasi_snapshot_preview1);
61+
const modulePathPreview1 = path.join(wasmDir, `${process.argv[3]}.wasm`);
62+
const bufferPreview1 = fs.readFileSync(modulePathPreview1);
63+
64+
(async () => {
65+
const { instance: instancePreview1 } =
66+
await WebAssembly.instantiate(bufferPreview1,
67+
wasiPreview1.getImportObject());
68+
69+
wasiPreview1.start(instancePreview1);
70+
})().then(common.mustCall());
3371
} else {
3472
const assert = require('assert');
3573
const cp = require('child_process');
3674
const { checkoutEOL } = common;
3775

38-
function innerRunWASI(options, args) {
76+
function innerRunWASI(options, args, flavor = 'default') {
3977
console.log('executing', options.test);
4078
const opts = {
4179
env: {
@@ -52,7 +90,7 @@ if (process.argv[2] === 'wasi-child') {
5290
...args,
5391
'--experimental-wasi-unstable-preview1',
5492
__filename,
55-
'wasi-child',
93+
'wasi-child-' + flavor,
5694
options.test,
5795
], opts);
5896
console.log(child.stderr.toString());
@@ -64,6 +102,7 @@ if (process.argv[2] === 'wasi-child') {
64102
function runWASI(options) {
65103
innerRunWASI(options, ['--no-turbo-fast-api-calls']);
66104
innerRunWASI(options, ['--turbo-fast-api-calls']);
105+
innerRunWASI(options, ['--turbo-fast-api-calls'], 'preview1');
67106
}
68107

69108
runWASI({ test: 'cant_dotdot' });

0 commit comments

Comments
 (0)