vm: use default HDO when importModuleDynamically is not set

This makes it possile to hit the in-isolate compilation cache when
host-defined options are not necessary.

PR-URL: https://github.com/nodejs/node/pull/49950
Refs: https://github.com/nodejs/node/issues/35375
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
This commit is contained in:
Joyee Cheung 2023-10-05 02:11:04 +02:00 committed by GitHub
parent 1643adf771
commit 1d220b55ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 6 deletions

View File

@ -0,0 +1,35 @@
'use strict';
// This benchmarks compiling scripts that hit the in-isolate compilation
// cache (by having the same source).
const common = require('../common.js');
const fs = require('fs');
const vm = require('vm');
const fixtures = require('../../test/common/fixtures.js');
const scriptPath = fixtures.path('snapshot', 'typescript.js');
const bench = common.createBenchmark(main, {
type: ['with-dynamic-import-callback', 'without-dynamic-import-callback'],
n: [100],
});
const scriptSource = fs.readFileSync(scriptPath, 'utf8');
function main({ n, type }) {
let script;
bench.start();
const options = {};
switch (type) {
case 'with-dynamic-import-callback':
// Use a dummy callback for now until we really need to benchmark it.
options.importModuleDynamically = async () => {};
break;
case 'without-dynamic-import-callback':
break;
}
for (let i = 0; i < n; i++) {
script = new vm.Script(scriptSource, options);
}
bench.end(n);
script.runInThisContext();
}

View File

@ -12,6 +12,10 @@ const {
host_defined_option_symbol,
},
} = internalBinding('util');
const {
default_host_defined_options,
} = internalBinding('symbols');
const {
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
ERR_INVALID_ARG_VALUE,
@ -128,6 +132,13 @@ const moduleRegistries = new SafeWeakMap();
*/
function registerModule(referrer, registry) {
const idSymbol = referrer[host_defined_option_symbol];
if (idSymbol === default_host_defined_options) {
// The referrer is compiled without custom callbacks, so there is
// no registry to hold on to. We'll throw
// ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING when a callback is
// needed.
return;
}
// To prevent it from being GC'ed.
registry.callbackReferrer ??= referrer;
moduleRegistries.set(idSymbol, registry);

View File

@ -86,6 +86,12 @@ class Script extends ContextifyScript {
}
validateBoolean(produceCachedData, 'options.produceCachedData');
if (importModuleDynamically !== undefined) {
// Check that it's either undefined or a function before we pass
// it into the native constructor.
validateFunction(importModuleDynamically,
'options.importModuleDynamically');
}
// Calling `ReThrow()` on a native TryCatch does not generate a new
// abort-on-uncaught-exception check. A dummy try/catch in JS land
// protects against that.
@ -96,14 +102,13 @@ class Script extends ContextifyScript {
columnOffset,
cachedData,
produceCachedData,
parsingContext);
parsingContext,
importModuleDynamically !== undefined);
} catch (e) {
throw e; /* node-do-not-add-exception-line */
}
if (importModuleDynamically !== undefined) {
validateFunction(importModuleDynamically,
'options.importModuleDynamically');
const { importModuleDynamicallyWrap } = require('internal/vm/module');
const { registerModule } = require('internal/modules/esm/utils');
registerModule(this, {

View File

@ -33,6 +33,7 @@
// Symbols are per-isolate primitives but Environment proxies them
// for the sake of convenience.
#define PER_ISOLATE_SYMBOL_PROPERTIES(V) \
V(default_host_defined_options, "default_host_defined_options") \
V(fs_use_promises_symbol, "fs_use_promises_symbol") \
V(async_id_symbol, "async_id_symbol") \
V(handle_onclose_symbol, "handle_onclose") \

View File

@ -771,10 +771,12 @@ void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
bool produce_cached_data = false;
Local<Context> parsing_context = context;
bool needs_custom_host_defined_options = false;
if (argc > 2) {
// new ContextifyScript(code, filename, lineOffset, columnOffset,
// cachedData, produceCachedData, parsingContext)
CHECK_EQ(argc, 7);
// cachedData, produceCachedData, parsingContext,
// needsCustomHostDefinedOptions)
CHECK_EQ(argc, 8);
CHECK(args[2]->IsNumber());
line_offset = args[2].As<Int32>()->Value();
CHECK(args[3]->IsNumber());
@ -793,6 +795,9 @@ void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
CHECK_NOT_NULL(sandbox);
parsing_context = sandbox->context();
}
if (args[7]->IsTrue()) {
needs_custom_host_defined_options = true;
}
}
ContextifyScript* contextify_script =
@ -816,7 +821,12 @@ void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
Local<PrimitiveArray> host_defined_options =
PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
Local<Symbol> id_symbol = Symbol::New(isolate, filename);
// We need a default host defined options that's the same for all scripts
// not needing custom module callbacks for so that the isolate compilation
// cache can be hit.
Local<Symbol> id_symbol = needs_custom_host_defined_options
? Symbol::New(isolate, filename)
: env->default_host_defined_options();
host_defined_options->Set(
isolate, loader::HostDefinedOptions::kID, id_symbol);

View File

@ -0,0 +1,13 @@
'use strict';
require('../common');
const { Script } = require('vm');
const assert = require('assert');
assert.rejects(async () => {
const script = new Script('import("fs")');
const imported = script.runInThisContext();
await imported;
}, {
code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING'
});