From d0296c0db4e14df6e7d8802831fe55d3c3c222fd Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 28 Feb 2024 22:09:21 +0100 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 --- lib/internal/modules/cjs/loader.js | 23 ++--- lib/internal/modules/esm/translators.js | 29 ++---- lib/internal/util/embedding.js | 6 +- src/env_properties.h | 2 + src/node_contextify.cc | 117 ++++++++++++++++++++++-- src/node_contextify.h | 2 - src/node_sea.cc | 36 +------- src/node_sea.h | 2 + 8 files changed, 131 insertions(+), 86 deletions(-) diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 40eefff40c5b65..0d76fa0613b7a9 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'); @@ -1274,23 +1276,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 ca547699d00ed1..7815a11047ed52 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'); @@ -57,7 +61,6 @@ const moduleWrap = internalBinding('module_wrap'); const { ModuleWrap } = moduleWrap; const asyncESM = require('internal/process/esm_loader'); const { emitWarningSync } = require('internal/process/warning'); -const { internalCompileFunction } = require('internal/vm'); const { vm_dynamic_import_default_internal, } = internalBinding('symbols'); @@ -210,28 +213,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 1ecd06e1a3546d..26c4dd2d24711e 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -56,6 +56,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 4585e759c0ec8d..9a5b34c60e5977 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" @@ -1280,8 +1281,8 @@ void ContextifyContext::CompileFunction( args.GetReturnValue().Set(result); } -Local ContextifyContext::GetHostDefinedOptions( - Isolate* isolate, Local id_symbol) { +static Local GetHostDefinedOptions(Isolate* isolate, + Local id_symbol) { Local host_defined_options = PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength); host_defined_options->Set( @@ -1322,6 +1323,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, @@ -1450,12 +1461,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); @@ -1485,6 +1491,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); @@ -1537,6 +1633,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, @@ -1576,6 +1676,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); };