Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 60e5f45

Browse filesBrowse files
mhdawsontargos
authored andcommitted
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 <mdawson@devrus.com> PR-URL: #46469 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 694659c commit 60e5f45
Copy full SHA for 60e5f45

File tree

Expand file treeCollapse file tree

5 files changed

+120
-21
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+120
-21
lines changed
Open diff view settings
Collapse file

‎doc/api/wasi.md‎

Copy file name to clipboardExpand all lines: doc/api/wasi.md
+32-10Lines changed: 32 additions & 10 deletions
  • Display the source diff
  • Display the rich diff
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
Collapse file

‎lib/wasi.js‎

Copy file name to clipboardExpand all lines: lib/wasi.js
+32-2Lines changed: 32 additions & 2 deletions
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

Collapse file

‎src/node_wasi.cc‎

Copy file name to clipboardExpand all lines: src/node_wasi.cc
+5-6Lines changed: 5 additions & 6 deletions
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)
Collapse file

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

Copy file name to clipboardExpand all lines: test/wasi/test-wasi-options-validation.js
+9Lines changed: 9 additions & 0 deletions
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/ });
Collapse file

‎test/wasi/test-wasi.js‎

Copy file name to clipboardExpand all lines: test/wasi/test-wasi.js
+42-3Lines changed: 42 additions & 3 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.