xref: /third_party/node/lib/wasi.js (revision 1cb0ef41)
1'use strict';
2const {
3  ArrayPrototypeForEach,
4  ArrayPrototypeMap,
5  ArrayPrototypePush,
6  FunctionPrototypeBind,
7  ObjectEntries,
8  String,
9  Symbol,
10} = primordials;
11
12const {
13  ERR_WASI_ALREADY_STARTED,
14} = require('internal/errors').codes;
15const {
16  emitExperimentalWarning,
17  kEmptyObject,
18} = require('internal/util');
19const {
20  validateArray,
21  validateBoolean,
22  validateFunction,
23  validateInt32,
24  validateObject,
25  validateUndefined,
26} = require('internal/validators');
27const { WASI: _WASI } = internalBinding('wasi');
28const kExitCode = Symbol('kExitCode');
29const kSetMemory = Symbol('kSetMemory');
30const kStarted = Symbol('kStarted');
31const kInstance = Symbol('kInstance');
32
33emitExperimentalWarning('WASI');
34
35
36function setupInstance(self, instance) {
37  validateObject(instance, 'instance');
38  validateObject(instance.exports, 'instance.exports');
39
40  self[kInstance] = instance;
41  self[kSetMemory](instance.exports.memory);
42}
43
44class WASI {
45  constructor(options = kEmptyObject) {
46    validateObject(options, 'options');
47
48    if (options.args !== undefined)
49      validateArray(options.args, 'options.args');
50    const args = ArrayPrototypeMap(options.args || [], String);
51
52    const env = [];
53    if (options.env !== undefined) {
54      validateObject(options.env, 'options.env');
55      ArrayPrototypeForEach(
56        ObjectEntries(options.env),
57        ({ 0: key, 1: value }) => {
58          if (value !== undefined)
59            ArrayPrototypePush(env, `${key}=${value}`);
60        });
61    }
62
63    const preopens = [];
64    if (options.preopens !== undefined) {
65      validateObject(options.preopens, 'options.preopens');
66      ArrayPrototypeForEach(
67        ObjectEntries(options.preopens),
68        ({ 0: key, 1: value }) =>
69          ArrayPrototypePush(preopens, String(key), String(value)),
70      );
71    }
72
73    const { stdin = 0, stdout = 1, stderr = 2 } = options;
74    validateInt32(stdin, 'options.stdin', 0);
75    validateInt32(stdout, 'options.stdout', 0);
76    validateInt32(stderr, 'options.stderr', 0);
77    const stdio = [stdin, stdout, stderr];
78
79    const wrap = new _WASI(args, env, preopens, stdio);
80
81    for (const prop in wrap) {
82      wrap[prop] = FunctionPrototypeBind(wrap[prop], wrap);
83    }
84
85    if (options.returnOnExit !== undefined) {
86      validateBoolean(options.returnOnExit, 'options.returnOnExit');
87      if (options.returnOnExit)
88        wrap.proc_exit = FunctionPrototypeBind(wasiReturnOnProcExit, this);
89    }
90
91    this[kSetMemory] = wrap._setMemory;
92    delete wrap._setMemory;
93    this.wasiImport = wrap;
94    this[kStarted] = false;
95    this[kExitCode] = 0;
96    this[kInstance] = undefined;
97  }
98
99  // Must not export _initialize, must export _start
100  start(instance) {
101    if (this[kStarted]) {
102      throw new ERR_WASI_ALREADY_STARTED();
103    }
104    this[kStarted] = true;
105
106    setupInstance(this, instance);
107
108    const { _start, _initialize } = this[kInstance].exports;
109
110    validateFunction(_start, 'instance.exports._start');
111    validateUndefined(_initialize, 'instance.exports._initialize');
112
113    try {
114      _start();
115    } catch (err) {
116      if (err !== kExitCode) {
117        throw err;
118      }
119    }
120
121    return this[kExitCode];
122  }
123
124  // Must not export _start, may optionally export _initialize
125  initialize(instance) {
126    if (this[kStarted]) {
127      throw new ERR_WASI_ALREADY_STARTED();
128    }
129    this[kStarted] = true;
130
131    setupInstance(this, instance);
132
133    const { _start, _initialize } = this[kInstance].exports;
134
135    validateUndefined(_start, 'instance.exports._start');
136    if (_initialize !== undefined) {
137      validateFunction(_initialize, 'instance.exports._initialize');
138      _initialize();
139    }
140  }
141}
142
143
144module.exports = { WASI };
145
146
147function wasiReturnOnProcExit(rval) {
148  // If __wasi_proc_exit() does not terminate the process, an assertion is
149  // triggered in the wasm runtime. Node can sidestep the assertion and return
150  // an exit code by recording the exit code, and throwing a JavaScript
151  // exception that WebAssembly cannot catch.
152  this[kExitCode] = rval;
153  throw kExitCode;
154}
155