From 09b1228c3a2723c6ecb768b40a507688015a478f Mon Sep 17 00:00:00 2001 From: cjihrig Date: Wed, 4 Sep 2019 17:56:51 -0400 Subject: wasi: introduce initial WASI support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gus Caplan Co-authored-by: Daniel Bevenius Co-authored-by: Jiawen Geng Co-authored-by: Tobias Nießen Co-authored-by: Chengzhong Wu PR-URL: https://github.com/nodejs/node/pull/30258 Refs: https://github.com/nodejs/node/pull/27850 Reviewed-By: Anna Henningsen Reviewed-By: Saúl Ibarra Corretgé Reviewed-By: Guy Bedford Reviewed-By: Richard Lau Reviewed-By: Wyatt Preul Reviewed-By: Matteo Collina Reviewed-By: Tobias Nießen Reviewed-By: Jiawen Geng Reviewed-By: Chengzhong Wu Reviewed-By: Daniel Bevenius --- lib/internal/bootstrap/loaders.js | 3 ++ lib/internal/errors.js | 1 + lib/internal/modules/cjs/helpers.js | 5 ++ lib/wasi.js | 105 ++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 lib/wasi.js (limited to 'lib') diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js index cfefc56bd8..044bea3114 100644 --- a/lib/internal/bootstrap/loaders.js +++ b/lib/internal/bootstrap/loaders.js @@ -157,6 +157,9 @@ function NativeModule(id) { this.loaded = false; this.loading = false; this.canBeRequiredByUsers = !id.startsWith('internal/'); + + if (id === 'wasi') + this.canBeRequiredByUsers = !!internalBinding('config').experimentalWasi; } // To be called during pre-execution when --expose-internals is on. diff --git a/lib/internal/errors.js b/lib/internal/errors.js index ee467f31f3..88a38f5e1d 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1229,6 +1229,7 @@ E('ERR_VM_MODULE_LINKING_ERRORED', E('ERR_VM_MODULE_NOT_MODULE', 'Provided module is not an instance of Module', Error); E('ERR_VM_MODULE_STATUS', 'Module status %s', Error); +E('ERR_WASI_ALREADY_STARTED', 'WASI instance has already started', Error); E('ERR_WORKER_INVALID_EXEC_ARGV', (errors) => `Initiated Worker with invalid execArgv flags: ${errors.join(', ')}`, Error); diff --git a/lib/internal/modules/cjs/helpers.js b/lib/internal/modules/cjs/helpers.js index f7155c4aa1..2d73219a77 100644 --- a/lib/internal/modules/cjs/helpers.js +++ b/lib/internal/modules/cjs/helpers.js @@ -117,6 +117,11 @@ const builtinLibs = [ 'v8', 'vm', 'worker_threads', 'zlib' ]; +if (internalBinding('config').experimentalWasi) { + builtinLibs.push('wasi'); + builtinLibs.sort(); +} + if (typeof internalBinding('inspector').open === 'function') { builtinLibs.push('inspector'); builtinLibs.sort(); diff --git a/lib/wasi.js b/lib/wasi.js new file mode 100644 index 0000000000..65b8473624 --- /dev/null +++ b/lib/wasi.js @@ -0,0 +1,105 @@ +'use strict'; +/* global WebAssembly */ +const { + ArrayIsArray, + ArrayPrototypeForEach, + ArrayPrototypeMap, + FunctionPrototypeBind, + ObjectKeys +} = primordials; +const { + ERR_INVALID_ARG_TYPE, + ERR_WASI_ALREADY_STARTED +} = require('internal/errors').codes; +const { emitExperimentalWarning } = require('internal/util'); +const { WASI: _WASI } = internalBinding('wasi'); +const kSetMemory = Symbol('setMemory'); +const kStarted = Symbol('started'); + +emitExperimentalWarning('WASI'); + + +class WASI { + constructor(options = {}) { + if (options === null || typeof options !== 'object') + throw new ERR_INVALID_ARG_TYPE('options', 'object', options); + + // eslint-disable-next-line prefer-const + let { args, env, preopens } = options; + + if (ArrayIsArray(args)) + args = ArrayPrototypeMap(args, (arg) => { return String(arg); }); + else if (args === undefined) + args = []; + else + throw new ERR_INVALID_ARG_TYPE('options.args', 'Array', args); + + const envPairs = []; + + if (env !== null && typeof env === 'object') { + for (const key in env) { + const value = env[key]; + if (value !== undefined) + envPairs.push(`${key}=${value}`); + } + } else if (env !== undefined) { + throw new ERR_INVALID_ARG_TYPE('options.env', 'Object', env); + } + + const preopenArray = []; + + if (typeof preopens === 'object' && preopens !== null) { + ArrayPrototypeForEach(ObjectKeys(preopens), (key) => { + preopenArray.push(String(key)); + preopenArray.push(String(preopens[key])); + }); + } else if (preopens !== undefined) { + throw new ERR_INVALID_ARG_TYPE('options.preopens', 'Object', preopens); + } + + const wrap = new _WASI(args, envPairs, preopenArray); + + for (const prop in wrap) { + wrap[prop] = FunctionPrototypeBind(wrap[prop], wrap); + } + + this[kSetMemory] = wrap._setMemory; + delete wrap._setMemory; + this.wasiImport = wrap; + this[kStarted] = false; + } + + start(instance) { + if (!(instance instanceof WebAssembly.Instance)) { + throw new ERR_INVALID_ARG_TYPE( + 'instance', 'WebAssembly.Instance', instance); + } + + const exports = instance.exports; + + if (exports === null || typeof exports !== 'object') + throw new ERR_INVALID_ARG_TYPE('instance.exports', 'Object', exports); + + const { memory } = exports; + + if (!(memory instanceof WebAssembly.Memory)) { + throw new ERR_INVALID_ARG_TYPE( + 'instance.exports.memory', 'WebAssembly.Memory', memory); + } + + if (this[kStarted]) { + throw new ERR_WASI_ALREADY_STARTED(); + } + + this[kStarted] = true; + this[kSetMemory](memory); + + if (exports._start) + exports._start(); + else if (exports.__wasi_unstable_reactor_start) + exports.__wasi_unstable_reactor_start(); + } +} + + +module.exports = { WASI }; -- cgit v1.2.3