Skip to content

Commit 49df7fa

Browse files
cjihrigtargos
authored andcommitted
wasi: update start() behavior to match spec
_start() and _initialize() shouldn't be called from the same function, as they have different behavior. Furthermore, Node should throw if both are provided. This commit updates the implementation, docs, and tests accordingly. PR-URL: #33073 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Gus Caplan <[email protected]>
1 parent dd80022 commit 49df7fa

File tree

3 files changed

+111
-77
lines changed

3 files changed

+111
-77
lines changed

doc/api/wasi.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,15 @@ added: v12.16.0
7070

7171
* `instance` {WebAssembly.Instance}
7272

73-
Attempt to begin execution of `instance` by invoking its `_start()` export.
74-
If `instance` does not contain a `_start()` export, then `start()` attempts to
75-
invoke the `_initialize()` export. If neither of those exports is present on
76-
`instance`, then `start()` does nothing.
73+
Attempt to begin execution of `instance` as a WASI command by invoking its
74+
`_start()` export. If `instance` does not contain a `_start()` export, or if
75+
`instance` contains an `_initialize()` export, then an exception is thrown.
7776

7877
`start()` requires that `instance` exports a [`WebAssembly.Memory`][] named
7978
`memory`. If `instance` does not have a `memory` export an exception is thrown.
8079

80+
If `start()` is called more than once, an exception is thrown.
81+
8182
### `wasi.wasiImport`
8283
<!-- YAML
8384
added: v12.16.0

lib/wasi.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,17 @@ class WASI {
7979

8080
validateObject(exports, 'instance.exports');
8181

82-
const { memory } = exports;
82+
const { _initialize, _start, memory } = exports;
83+
84+
if (typeof _start !== 'function') {
85+
throw new ERR_INVALID_ARG_TYPE(
86+
'instance.exports._start', 'function', _start);
87+
}
88+
89+
if (_initialize !== undefined) {
90+
throw new ERR_INVALID_ARG_TYPE(
91+
'instance.exports._initialize', 'undefined', _initialize);
92+
}
8393

8494
if (!(memory instanceof WebAssembly.Memory)) {
8595
throw new ERR_INVALID_ARG_TYPE(
@@ -94,10 +104,7 @@ class WASI {
94104
this[kSetMemory](memory);
95105

96106
try {
97-
if (exports._start)
98-
exports._start();
99-
else if (exports._initialize)
100-
exports._initialize();
107+
exports._start();
101108
} catch (err) {
102109
if (err !== kExitCode) {
103110
throw err;

test/wasi/test-wasi-start-validation.js

Lines changed: 94 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,85 +8,111 @@ const { WASI } = require('wasi');
88
const fixtures = require('../common/fixtures');
99
const bufferSource = fixtures.readSync('simple.wasm');
1010

11-
{
12-
const wasi = new WASI();
13-
assert.throws(
14-
() => {
15-
wasi.start();
16-
},
17-
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bWebAssembly\.Instance\b/ }
18-
);
19-
}
20-
2111
(async () => {
22-
const wasi = new WASI({});
23-
const wasm = await WebAssembly.compile(bufferSource);
24-
const instance = await WebAssembly.instantiate(wasm);
12+
{
13+
// Verify that a WebAssembly.Instance is passed in.
14+
const wasi = new WASI();
2515

26-
assert.throws(
27-
() => { wasi.start(instance); },
28-
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bWebAssembly\.Memory\b/ }
29-
);
30-
})();
16+
assert.throws(
17+
() => { wasi.start(); },
18+
{ code: 'ERR_INVALID_ARG_TYPE', message: /\bWebAssembly\.Instance\b/ }
19+
);
20+
}
3121

32-
(async () => {
33-
const wasi = new WASI();
34-
const wasm = await WebAssembly.compile(bufferSource);
35-
const instance = await WebAssembly.instantiate(wasm);
36-
const values = [undefined, null, 'foo', 42, true, false, () => {}];
37-
let cnt = 0;
22+
{
23+
// Verify that the passed instance has an exports objects.
24+
const wasi = new WASI({});
25+
const wasm = await WebAssembly.compile(bufferSource);
26+
const instance = await WebAssembly.instantiate(wasm);
27+
28+
Object.defineProperty(instance, 'exports', { get() { return null; } });
29+
assert.throws(
30+
() => { wasi.start(instance); },
31+
{
32+
code: 'ERR_INVALID_ARG_TYPE',
33+
message: /"instance\.exports" property must be of type object/
34+
}
35+
);
36+
}
3837

39-
// Mock instance.exports to trigger start() validation.
40-
Object.defineProperty(instance, 'exports', {
41-
get() { return values[cnt++]; }
42-
});
38+
{
39+
// Verify that a _start() export was passed.
40+
const wasi = new WASI({});
41+
const wasm = await WebAssembly.compile(bufferSource);
42+
const instance = await WebAssembly.instantiate(wasm);
4343

44-
values.forEach((val) => {
44+
Object.defineProperty(instance, 'exports', { get() { return {}; } });
4545
assert.throws(
4646
() => { wasi.start(instance); },
47-
{ code: 'ERR_INVALID_ARG_TYPE', message: /\binstance\.exports\b/ }
47+
{
48+
code: 'ERR_INVALID_ARG_TYPE',
49+
message: /"instance\.exports\._start" property must be of type function/
50+
}
4851
);
49-
});
50-
})();
52+
}
5153

52-
(async () => {
53-
const wasi = new WASI();
54-
const wasm = await WebAssembly.compile(bufferSource);
55-
const instance = await WebAssembly.instantiate(wasm);
54+
{
55+
// Verify that an _initialize export was not passed.
56+
const wasi = new WASI({});
57+
const wasm = await WebAssembly.compile(bufferSource);
58+
const instance = await WebAssembly.instantiate(wasm);
5659

57-
// Mock instance.exports.memory to bypass start() validation.
58-
Object.defineProperty(instance, 'exports', {
59-
get() {
60-
return {
61-
memory: new WebAssembly.Memory({ initial: 1 })
62-
};
63-
}
64-
});
60+
Object.defineProperty(instance, 'exports', {
61+
get() {
62+
return {
63+
_start() {},
64+
_initialize() {}
65+
};
66+
}
67+
});
68+
assert.throws(
69+
() => { wasi.start(instance); },
70+
{
71+
code: 'ERR_INVALID_ARG_TYPE',
72+
message: /"instance\.exports\._initialize" property must be undefined/
73+
}
74+
);
75+
}
6576

66-
wasi.start(instance);
67-
assert.throws(
68-
() => { wasi.start(instance); },
69-
{
70-
code: 'ERR_WASI_ALREADY_STARTED',
71-
message: /^WASI instance has already started$/
72-
}
73-
);
74-
})();
77+
{
78+
// Verify that a memory export was passed.
79+
const wasi = new WASI({});
80+
const wasm = await WebAssembly.compile(bufferSource);
81+
const instance = await WebAssembly.instantiate(wasm);
7582

76-
(async () => {
77-
const wasi = new WASI();
78-
const wasm = await WebAssembly.compile(bufferSource);
79-
const instance = await WebAssembly.instantiate(wasm);
83+
Object.defineProperty(instance, 'exports', {
84+
get() { return { _start() {} }; }
85+
});
86+
assert.throws(
87+
() => { wasi.start(instance); },
88+
{
89+
code: 'ERR_INVALID_ARG_TYPE',
90+
message: /"instance\.exports\.memory" property .+ WebAssembly\.Memory/
91+
}
92+
);
93+
}
8094

81-
// Mock instance.exports to bypass start() validation.
82-
Object.defineProperty(instance, 'exports', {
83-
get() {
84-
return {
85-
memory: new WebAssembly.Memory({ initial: 1 }),
86-
_initialize: common.mustCall()
87-
};
88-
}
89-
});
95+
{
96+
// Verify that start() can only be called once.
97+
const wasi = new WASI({});
98+
const wasm = await WebAssembly.compile(bufferSource);
99+
const instance = await WebAssembly.instantiate(wasm);
90100

91-
wasi.start(instance);
92-
})();
101+
Object.defineProperty(instance, 'exports', {
102+
get() {
103+
return {
104+
_start() {},
105+
memory: new WebAssembly.Memory({ initial: 1 })
106+
};
107+
}
108+
});
109+
wasi.start(instance);
110+
assert.throws(
111+
() => { wasi.start(instance); },
112+
{
113+
code: 'ERR_WASI_ALREADY_STARTED',
114+
message: /^WASI instance has already started$/
115+
}
116+
);
117+
}
118+
})().then(common.mustCall());

0 commit comments

Comments
 (0)