From e161e623139b9a0ad7c6eca298a80e59991d49e0 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 12 Mar 2024 03:18:53 +0800 Subject: [PATCH] src: use dedicated routine to compile function for builtin CJS loader So that we can use it to handle code caching in a central place. Drive-by: use per-isolate persistent strings for the parameters and mark GetHostDefinedOptions() since it's only used in one compilation unit PR-URL: https://github.com/nodejs/node/pull/52016 Refs: https://github.com/nodejs/node/issues/47472 Reviewed-By: Antoine du Hamel Reviewed-By: Geoffrey Booth --- lib/internal/modules/cjs/loader.js | 23 ++--- lib/internal/modules/esm/translators.js | 33 ++---- lib/internal/util/embedding.js | 6 +- src/env_properties.h | 2 + src/node_contextify.cc | 131 +++++++++++++++++++++--- src/node_contextify.h | 2 - src/node_sea.cc | 36 +------ src/node_sea.h | 2 + 8 files changed, 139 insertions(+), 96 deletions(-) diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 2c48ff9512c225..7bbd59e16330b5 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -91,11 +91,13 @@ const { getLazy, } = require('internal/util'); const { - internalCompileFunction, makeContextifyScript, runScriptInThisContext, } = require('internal/vm'); -const { containsModuleSyntax } = internalBinding('contextify'); +const { + containsModuleSyntax, + compileFunctionForCJSLoader, +} = internalBinding('contextify'); const assert = require('internal/assert'); const fs = require('fs'); @@ -1275,23 +1277,10 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache) { return runScriptInThisContext(script, true, false); } - const params = [ 'exports', 'require', 'module', '__filename', '__dirname' ]; try { - const result = internalCompileFunction( - content, // code, - filename, // filename - 0, // lineOffset - 0, // columnOffset, - codeCache, // cachedData - false, // produceCachedData - undefined, // parsingContext - undefined, // contextExtensions - params, // params - hostDefinedOptionId, // hostDefinedOptionId - importModuleDynamically, // importModuleDynamically - ); + const result = compileFunctionForCJSLoader(content, filename); - // The code cache is used for SEAs only. + // cachedDataRejected is only set for cache coming from SEA. if (codeCache && result.cachedDataRejected !== false && internalBinding('sea').isSea()) { diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 61e489a6cedd7a..6772bbffd989d2 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -29,7 +29,11 @@ function lazyTypes() { return _TYPES = require('internal/util/types'); } -const { containsModuleSyntax } = internalBinding('contextify'); +const { + containsModuleSyntax, + compileFunctionForCJSLoader, +} = internalBinding('contextify'); + const { BuiltinModule } = require('internal/bootstrap/realm'); const assert = require('internal/assert'); const { readFileSync } = require('fs'); @@ -56,10 +60,7 @@ const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache'); const moduleWrap = internalBinding('module_wrap'); const { ModuleWrap } = moduleWrap; const { emitWarningSync } = require('internal/process/warning'); -const { internalCompileFunction } = require('internal/vm'); -const { - vm_dynamic_import_default_internal, -} = internalBinding('symbols'); + // Lazy-loading to avoid circular dependencies. let getSourceSync; /** @@ -210,28 +211,8 @@ function enrichCJSError(err, content, filename) { */ function loadCJSModule(module, source, url, filename) { let compileResult; - const hostDefinedOptionId = vm_dynamic_import_default_internal; - const importModuleDynamically = vm_dynamic_import_default_internal; try { - compileResult = internalCompileFunction( - source, // code, - filename, // filename - 0, // lineOffset - 0, // columnOffset, - undefined, // cachedData - false, // produceCachedData - undefined, // parsingContext - undefined, // contextExtensions - [ // params - 'exports', - 'require', - 'module', - '__filename', - '__dirname', - ], - hostDefinedOptionId, // hostDefinedOptionsId - importModuleDynamically, // importModuleDynamically - ); + compileResult = compileFunctionForCJSLoader(source, filename); } catch (err) { enrichCJSError(err, source, filename); throw err; diff --git a/lib/internal/util/embedding.js b/lib/internal/util/embedding.js index be310f401ad115..db9162369aae0d 100644 --- a/lib/internal/util/embedding.js +++ b/lib/internal/util/embedding.js @@ -2,7 +2,7 @@ const { BuiltinModule: { normalizeRequirableId } } = require('internal/bootstrap/realm'); const { Module, wrapSafe } = require('internal/modules/cjs/loader'); const { codes: { ERR_UNKNOWN_BUILTIN_MODULE } } = require('internal/errors'); -const { getCodeCache, getCodePath, isSea } = internalBinding('sea'); +const { getCodePath, isSea } = internalBinding('sea'); // This is roughly the same as: // @@ -18,9 +18,7 @@ function embedderRunCjs(contents) { const filename = process.execPath; const compiledWrapper = wrapSafe( isSea() ? getCodePath() : filename, - contents, - undefined, - getCodeCache()); + contents); const customModule = new Module(filename, null); customModule.filename = filename; diff --git a/src/env_properties.h b/src/env_properties.h index 672b8f8cbf14b6..5b251751892165 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -54,6 +54,8 @@ // Strings are per-isolate primitives but Environment proxies them // for the sake of convenience. Strings should be ASCII-only. #define PER_ISOLATE_STRING_PROPERTIES(V) \ + V(__filename_string, "__filename") \ + V(__dirname_string, "__dirname") \ V(ack_string, "ack") \ V(address_string, "address") \ V(aliases_string, "aliases") \ diff --git a/src/node_contextify.cc b/src/node_contextify.cc index e9d50ac402b012..2514ad629620d6 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -28,6 +28,7 @@ #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" +#include "node_sea.h" #include "node_snapshot_builder.h" #include "node_watchdog.h" #include "util-inl.h" @@ -1151,6 +1152,15 @@ ContextifyScript::ContextifyScript(Environment* env, Local object) ContextifyScript::~ContextifyScript() {} +static Local GetHostDefinedOptions(Isolate* isolate, + Local id_symbol) { + Local host_defined_options = + PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength); + host_defined_options->Set( + isolate, loader::HostDefinedOptions::kID, id_symbol); + return host_defined_options; +} + void ContextifyContext::CompileFunction( const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -1281,15 +1291,6 @@ void ContextifyContext::CompileFunction( args.GetReturnValue().Set(result); } -Local ContextifyContext::GetHostDefinedOptions( - Isolate* isolate, Local id_symbol) { - Local host_defined_options = - PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength); - host_defined_options->Set( - isolate, loader::HostDefinedOptions::kID, id_symbol); - return host_defined_options; -} - ScriptCompiler::Source ContextifyContext::GetCommonJSSourceInstance( Isolate* isolate, Local code, @@ -1323,6 +1324,16 @@ ScriptCompiler::CompileOptions ContextifyContext::GetCompileOptions( return options; } +static std::vector> GetCJSParameters(IsolateData* data) { + return { + data->exports_string(), + data->require_string(), + data->module_string(), + data->__filename_string(), + data->__dirname_string(), + }; +} + Local ContextifyContext::CompileFunctionAndCacheResult( Environment* env, Local parsing_context, @@ -1451,12 +1462,7 @@ void ContextifyContext::ContainsModuleSyntax( isolate, code, filename, 0, 0, host_defined_options, nullptr); ScriptCompiler::CompileOptions options = GetCompileOptions(source); - std::vector> params = { - String::NewFromUtf8(isolate, "exports").ToLocalChecked(), - String::NewFromUtf8(isolate, "require").ToLocalChecked(), - String::NewFromUtf8(isolate, "module").ToLocalChecked(), - String::NewFromUtf8(isolate, "__filename").ToLocalChecked(), - String::NewFromUtf8(isolate, "__dirname").ToLocalChecked()}; + std::vector> params = GetCJSParameters(env->isolate_data()); TryCatchScope try_catch(env); ShouldNotAbortOnUncaughtScope no_abort_scope(env); @@ -1486,6 +1492,96 @@ void ContextifyContext::ContainsModuleSyntax( args.GetReturnValue().Set(found_error_message_caused_by_module_syntax); } +static void CompileFunctionForCJSLoader( + const FunctionCallbackInfo& args) { + CHECK(args[0]->IsString()); + CHECK(args[1]->IsString()); + Local code = args[0].As(); + Local filename = args[1].As(); + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + Environment* env = Environment::GetCurrent(context); + + Local symbol = env->vm_dynamic_import_default_internal(); + Local hdo = GetHostDefinedOptions(isolate, symbol); + ScriptOrigin origin(isolate, + filename, + 0, // line offset + 0, // column offset + true, // is cross origin + -1, // script id + Local(), // source map URL + false, // is opaque + false, // is WASM + false, // is ES Module + hdo); + ScriptCompiler::CachedData* cached_data = nullptr; + +#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION + bool used_cache_from_sea = false; + if (sea::IsSingleExecutable()) { + sea::SeaResource sea = sea::FindSingleExecutableResource(); + if (sea.use_code_cache()) { + std::string_view data = sea.code_cache.value(); + cached_data = new ScriptCompiler::CachedData( + reinterpret_cast(data.data()), + static_cast(data.size()), + v8::ScriptCompiler::CachedData::BufferNotOwned); + used_cache_from_sea = true; + } + } +#endif + ScriptCompiler::Source source(code, origin, cached_data); + + TryCatchScope try_catch(env); + + std::vector> params = GetCJSParameters(env->isolate_data()); + + MaybeLocal maybe_fn = ScriptCompiler::CompileFunction( + context, + &source, + params.size(), + params.data(), + 0, /* context extensions size */ + nullptr, /* context extensions data */ + // TODO(joyeecheung): allow optional eager compilation. + cached_data == nullptr ? ScriptCompiler::kNoCompileOptions + : ScriptCompiler::kConsumeCodeCache, + v8::ScriptCompiler::NoCacheReason::kNoCacheNoReason); + + Local fn; + if (!maybe_fn.ToLocal(&fn)) { + if (try_catch.HasCaught() && !try_catch.HasTerminated()) { + errors::DecorateErrorStack(env, try_catch); + if (!try_catch.HasTerminated()) { + try_catch.ReThrow(); + } + return; + } + } + + bool cache_rejected = false; +#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION + if (used_cache_from_sea) { + cache_rejected = source.GetCachedData()->rejected; + } +#endif + + std::vector> names = { + env->cached_data_rejected_string(), + env->source_map_url_string(), + env->function_string(), + }; + std::vector> values = { + Boolean::New(isolate, cache_rejected), + fn->GetScriptOrigin().SourceMapUrl(), + fn, + }; + Local result = Object::New( + isolate, v8::Null(isolate), names.data(), values.data(), names.size()); + args.GetReturnValue().Set(result); +} + static void StartSigintWatchdog(const FunctionCallbackInfo& args) { int ret = SigintWatchdogHelper::GetInstance()->Start(); args.GetReturnValue().Set(ret == 0); @@ -1538,6 +1634,10 @@ void CreatePerIsolateProperties(IsolateData* isolate_data, isolate, target, "watchdogHasPendingSigint", WatchdogHasPendingSigint); SetMethod(isolate, target, "measureMemory", MeasureMemory); + SetMethod(isolate, + target, + "compileFunctionForCJSLoader", + CompileFunctionForCJSLoader); } static void CreatePerContextProperties(Local target, @@ -1577,6 +1677,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { ContextifyContext::RegisterExternalReferences(registry); ContextifyScript::RegisterExternalReferences(registry); + registry->Register(CompileFunctionForCJSLoader); registry->Register(StartSigintWatchdog); registry->Register(StopSigintWatchdog); registry->Register(WatchdogHasPendingSigint); diff --git a/src/node_contextify.h b/src/node_contextify.h index cce062622b70d8..e96df803b7ec2a 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -94,8 +94,6 @@ class ContextifyContext : public BaseObject { bool produce_cached_data, v8::Local id_symbol, const errors::TryCatchScope& try_catch); - static v8::Local GetHostDefinedOptions( - v8::Isolate* isolate, v8::Local id_symbol); static v8::ScriptCompiler::Source GetCommonJSSourceInstance( v8::Isolate* isolate, v8::Local code, diff --git a/src/node_sea.cc b/src/node_sea.cc index c2b409de731c6e..bef7fe7c22739c 100644 --- a/src/node_sea.cc +++ b/src/node_sea.cc @@ -31,7 +31,6 @@ using node::ExitCode; using v8::ArrayBuffer; using v8::BackingStore; using v8::Context; -using v8::DataView; using v8::Function; using v8::FunctionCallbackInfo; using v8::HandleScope; @@ -219,6 +218,10 @@ bool SeaResource::use_snapshot() const { return static_cast(flags & SeaFlags::kUseSnapshot); } +bool SeaResource::use_code_cache() const { + return static_cast(flags & SeaFlags::kUseCodeCache); +} + SeaResource FindSingleExecutableResource() { static const SeaResource sea_resource = []() -> SeaResource { std::string_view blob = FindSingleExecutableBlob(); @@ -258,35 +261,6 @@ void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo& args) { sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning)); } -void GetCodeCache(const FunctionCallbackInfo& args) { - if (!IsSingleExecutable()) { - return; - } - - Isolate* isolate = args.GetIsolate(); - - SeaResource sea_resource = FindSingleExecutableResource(); - - if (!static_cast(sea_resource.flags & SeaFlags::kUseCodeCache)) { - return; - } - - std::shared_ptr backing_store = ArrayBuffer::NewBackingStore( - const_cast( - static_cast(sea_resource.code_cache->data())), - sea_resource.code_cache->length(), - [](void* /* data */, size_t /* length */, void* /* deleter_data */) { - // The code cache data blob is not freed here because it is a static - // blob which is not allocated by the BackingStore allocator. - }, - nullptr); - Local array_buffer = ArrayBuffer::New(isolate, backing_store); - Local data_view = - DataView::New(array_buffer, 0, array_buffer->ByteLength()); - - args.GetReturnValue().Set(data_view); -} - void GetCodePath(const FunctionCallbackInfo& args) { DCHECK(IsSingleExecutable()); @@ -653,7 +627,6 @@ void Initialize(Local target, "isExperimentalSeaWarningNeeded", IsExperimentalSeaWarningNeeded); SetMethod(context, target, "getCodePath", GetCodePath); - SetMethod(context, target, "getCodeCache", GetCodeCache); SetMethod(context, target, "getAsset", GetAsset); } @@ -661,7 +634,6 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(IsSea); registry->Register(IsExperimentalSeaWarningNeeded); registry->Register(GetCodePath); - registry->Register(GetCodeCache); registry->Register(GetAsset); } diff --git a/src/node_sea.h b/src/node_sea.h index f90ef63cc7fd33..6f2f51d997dc73 100644 --- a/src/node_sea.h +++ b/src/node_sea.h @@ -37,6 +37,8 @@ struct SeaResource { std::unordered_map assets; bool use_snapshot() const; + bool use_code_cache() const; + static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags); };