From 8d4b96439fe973b502603338ee14f1157285f9d0 Mon Sep 17 00:00:00 2001 From: Ohad Rau Date: Thu, 1 Aug 2019 10:58:33 -0700 Subject: [PATCH] Add WASM native module loader --- lib/internal/bootstrap/node.js | 1 + lib/internal/modules/cjs/loader.js | 14 ++++ lib/internal/modules/esm/default_resolve.js | 5 +- src/module_wrap.cc | 12 +++- src/node_binding.cc | 80 +++++++++++++++++++++ src/node_binding.h | 1 + src/node_process_methods.cc | 1 + 7 files changed, 111 insertions(+), 3 deletions(-) diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index e8735c2049c..448197ca023 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -95,6 +95,7 @@ if (isMainThread) { // Set up methods on the process object for all threads { process.dlopen = rawMethods.dlopen; + process.wasmOpen = rawMethods.wasmOpen; process.uptime = rawMethods.uptime; // TODO(joyeecheung): either remove them or make them public diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 639877434e2..d893fbff59e 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -808,6 +808,20 @@ Module._extensions['.json'] = function(module, filename) { } }; +Module._extensions['.node.wasm'] = function(module, filename) { + const content = fs.readFileSync(filename); + const mod = new WebAssembly.Module(content); + let imports = WebAssembly.embedderBuiltins(); + // TODO(ohadrau): Load WASI + imports.wasi_unstable = { + fd_close: () => 0, + fd_write: () => 0, + fd_seek: () => 0, + fd_fdstat_get: () => 0 + } + const instance = new WebAssembly.Instance(mod, imports); + return process.wasmOpen(module, instance.exports.main); +} // Native extension for .node Module._extensions['.node'] = function(module, filename) { diff --git a/lib/internal/modules/esm/default_resolve.js b/lib/internal/modules/esm/default_resolve.js index 46e7b2415a9..67f6d4c1fca 100644 --- a/lib/internal/modules/esm/default_resolve.js +++ b/lib/internal/modules/esm/default_resolve.js @@ -29,7 +29,7 @@ const extensionFormatMap = { '.cjs': 'commonjs', '.js': 'module', '.json': 'json', - '.mjs': 'module' + '.mjs': 'module', }; const legacyExtensionFormatMap = { @@ -38,7 +38,8 @@ const legacyExtensionFormatMap = { '.js': 'commonjs', '.json': 'json', '.mjs': 'module', - '.node': 'commonjs' + '.node': 'commonjs', + '.node.wasm': 'commonjs' }; if (experimentalWasmModules) diff --git a/src/module_wrap.cc b/src/module_wrap.cc index e104afb736c..6df6a6c5a0b 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -52,7 +52,8 @@ static const char* const EXTENSIONS[] = { ".cjs", ".js", ".json", - ".node" + ".node", + ".node.wasm" }; ModuleWrap::ModuleWrap(Environment* env, @@ -681,6 +682,9 @@ Maybe LegacyMainResolve(const URL& pjson_url, if (FileExists(guess = URL("./" + pcfg.main + ".node", pjson_url))) { return Just(guess); } + if (FileExists(guess = URL("./" + pcfg.main + ".node.wasm", pjson_url))) { + return Just(guess); + } if (FileExists(guess = URL("./" + pcfg.main + "/index.js", pjson_url))) { return Just(guess); } @@ -691,6 +695,9 @@ Maybe LegacyMainResolve(const URL& pjson_url, if (FileExists(guess = URL("./" + pcfg.main + "/index.node", pjson_url))) { return Just(guess); } + if (FileExists(guess = URL("./" + pcfg.main + "/index.node.wasm", pjson_url))) { + return Just(guess); + } // Fallthrough. } if (FileExists(guess = URL("./index.js", pjson_url))) { @@ -703,6 +710,9 @@ Maybe LegacyMainResolve(const URL& pjson_url, if (FileExists(guess = URL("./index.node", pjson_url))) { return Just(guess); } + if (FileExists(guess = URL("./index.node.wasm", pjson_url))) { + return Just(guess); + } // Not found. return Nothing(); } diff --git a/src/node_binding.cc b/src/node_binding.cc index 99c2406036e..27194c8c2ed 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -538,6 +538,86 @@ void DLOpen(const FunctionCallbackInfo& args) { // coverity[leaked_storage] } +// WASMOpen is process.wasmOpen(module, instance, flags). +// Used to load 'module.node' dynamically shared objects. +// +// FIXME(bnoordhuis) Not multi-context ready. TBD how to resolve the conflict +// when two contexts try to load the same shared object. Maybe have a shadow +// cache that's a plain C list or hash table that's shared across contexts? +void WASMOpen(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + auto context = env->context(); + + uv_once(&init_modpending_once, InitModpendingOnce); + CHECK_NULL(uv_key_get(&thread_local_modpending)); + + if (args.Length() < 2) { + env->ThrowError("process.wasmOpen needs at least 2 arguments."); + return; + } + + Local module; + Local exports; + Local exports_v; + if (!args[0]->ToObject(context).ToLocal(&module) || + !module->Get(context, env->exports_string()).ToLocal(&exports_v) || + !exports_v->ToObject(context).ToLocal(&exports)) { + return; // Exception pending. + } + + Local mainFunction; + if (!args[1]->ToObject(context).ToLocal(&mainFunction) || + !mainFunction->IsFunction()) { + return; // Exception pending. + } + + int argc = 2; + Local argv[2]; + argv[0] = v8::Integer::New(context->GetIsolate(), 0); // int argc + argv[1] = v8::Integer::New(context->GetIsolate(), 0); // char** argv + mainFunction->CallAsFunction(context, context->Global(), argc, argv); + + // Objects containing v14 or later modules will have registered themselves + // on the pending list. Activate all of them now. At present, only one + // module per object is supported. + node_module* mp = + static_cast(uv_key_get(&thread_local_modpending)); + uv_key_set(&thread_local_modpending, nullptr); + + if (mp == nullptr || mp->nm_context_register_func == nullptr) { + env->ThrowError("Module did not self-register."); + return; + } + + // -1 is used for N-API modules. WASM loading ONLY supports N-API modules. + if (mp->nm_version != -1) { + char errmsg[1024]; + snprintf(errmsg, + sizeof(errmsg), + "The module '%s'" + "\nwas compiled against a different Node.js version using" + "\nNODE_MODULE_VERSION %d. This version of Node.js requires" + "\nNODE_MODULE_VERSION %d. Please try re-compiling or " + "re-installing\nthe module (for instance, using `npm rebuild` " + "or `npm install`).", + mp->nm_modname, + mp->nm_version, + NODE_MODULE_VERSION); + + // NOTE: `mp` is allocated inside of Node's memory, so we should free it + free(mp); + env->ThrowError(errmsg); + return; + } + CHECK_EQ(mp->nm_flags & NM_F_BUILTIN, 0); + + if (mp->nm_context_register_func != nullptr) { + mp->nm_context_register_func(exports, module, context, mp->nm_priv); + } else { + env->ThrowError("Module has no declared entry point."); + } +} + inline struct node_module* FindModule(struct node_module* list, const char* name, int flag) { diff --git a/src/node_binding.h b/src/node_binding.h index dd94fab36a0..7572734931e 100644 --- a/src/node_binding.h +++ b/src/node_binding.h @@ -92,6 +92,7 @@ void RegisterBuiltinModules(); void GetInternalBinding(const v8::FunctionCallbackInfo& args); void GetLinkedBinding(const v8::FunctionCallbackInfo& args); void DLOpen(const v8::FunctionCallbackInfo& args); +void WASMOpen(const v8::FunctionCallbackInfo& args); } // namespace binding diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index b34cbf89b6c..e80ed330a03 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -433,6 +433,7 @@ static void InitializeProcessMethods(Local target, env->SetMethodNoSideEffect(target, "cwd", Cwd); env->SetMethod(target, "dlopen", binding::DLOpen); + env->SetMethod(target, "wasmOpen", binding::WASMOpen); env->SetMethod(target, "reallyExit", ReallyExit); env->SetMethodNoSideEffect(target, "uptime", Uptime); env->SetMethod(target, "patchProcessObject", PatchProcessObject);