diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 60fa7e79d19693..8a9a33b65cc419 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -127,6 +127,7 @@ let hasLoadedAnyUserCJSModule = false; const { codes: { + ERR_INTERNAL_ASSERTION, ERR_INVALID_ARG_VALUE, ERR_INVALID_MODULE_SPECIFIER, ERR_REQUIRE_ESM, @@ -1145,7 +1146,7 @@ Module.prototype.require = function(id) { let resolvedArgv; let hasPausedEntry = false; let Script; -function wrapSafe(filename, content, cjsModuleInstance) { +function wrapSafe(filename, content, cjsModuleInstance, codeCache) { if (patched) { const wrapper = Module.wrap(content); if (Script === undefined) { @@ -1180,6 +1181,7 @@ function wrapSafe(filename, content, cjsModuleInstance) { '__dirname', ], { filename, + cachedData: codeCache, importModuleDynamically(specifier, _, importAssertions) { const cascadedLoader = getCascadedLoader(); return cascadedLoader.import(specifier, normalizeReferrerURL(filename), @@ -1187,6 +1189,10 @@ function wrapSafe(filename, content, cjsModuleInstance) { }, }); + if (codeCache && result.cachedDataRejected !== false) { + throw new ERR_INTERNAL_ASSERTION('Code cache data rejected.'); + } + // Cache the source map for the module if present. if (result.sourceMapURL) { maybeCacheSourceMap(filename, content, this, false, undefined, result.sourceMapURL); diff --git a/lib/internal/util/embedding.js b/lib/internal/util/embedding.js index e2e67202477bc7..09e77988966f29 100644 --- a/lib/internal/util/embedding.js +++ b/lib/internal/util/embedding.js @@ -1,7 +1,8 @@ 'use strict'; -const { codes: { ERR_UNKNOWN_BUILTIN_MODULE } } = require('internal/errors'); 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 } = internalBinding('sea'); // This is roughly the same as: // @@ -15,7 +16,7 @@ const { Module, wrapSafe } = require('internal/modules/cjs/loader'); function embedderRunCjs(contents) { const filename = process.execPath; - const compiledWrapper = wrapSafe(filename, contents); + const compiledWrapper = wrapSafe(filename, contents, undefined, getCodeCache()); const customModule = new Module(filename, null); customModule.filename = filename; diff --git a/src/json_parser.cc b/src/json_parser.cc index a9973c099087e5..1e19e174833fa5 100644 --- a/src/json_parser.cc +++ b/src/json_parser.cc @@ -4,7 +4,6 @@ #include "util-inl.h" namespace node { -using v8::ArrayBuffer; using v8::Context; using v8::Isolate; using v8::Local; @@ -12,26 +11,8 @@ using v8::Object; using v8::String; using v8::Value; -static Isolate* NewIsolate(v8::ArrayBuffer::Allocator* allocator) { - Isolate* isolate = Isolate::Allocate(); - CHECK_NOT_NULL(isolate); - per_process::v8_platform.Platform()->RegisterIsolate(isolate, - uv_default_loop()); - Isolate::CreateParams params; - params.array_buffer_allocator = allocator; - Isolate::Initialize(isolate, params); - return isolate; -} - -void JSONParser::FreeIsolate(Isolate* isolate) { - per_process::v8_platform.Platform()->UnregisterIsolate(isolate); - isolate->Dispose(); -} - JSONParser::JSONParser() - : allocator_(ArrayBuffer::Allocator::NewDefaultAllocator()), - isolate_(NewIsolate(allocator_.get())), - handle_scope_(isolate_.get()), + : handle_scope_(isolate_.get()), context_(isolate_.get(), Context::New(isolate_.get())), context_scope_(context_.Get(isolate_.get())) {} diff --git a/src/json_parser.h b/src/json_parser.h index 555f539acf3076..3978a24222eb03 100644 --- a/src/json_parser.h +++ b/src/json_parser.h @@ -24,9 +24,7 @@ class JSONParser { private: // We might want a lighter-weight JSON parser for this use case. But for now // using V8 is good enough. - static void FreeIsolate(v8::Isolate* isolate); - std::unique_ptr allocator_; - DeleteFnPtr isolate_; + RAIIIsolate isolate_; v8::HandleScope handle_scope_; v8::Global context_; v8::Context::Scope context_scope_; diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 75cb88fce8e8d3..885d1606594289 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -934,6 +934,30 @@ Maybe StoreCodeCacheResult( return Just(true); } +// TODO(RaisinTen): Reuse in ContextifyContext::CompileFunction(). +MaybeLocal CompileFunction(Isolate* isolate, + Local context, + Local filename, + Local content, + std::vector> parameters) { + ScriptOrigin script_origin(isolate, filename, 0, 0, true); + ScriptCompiler::Source script_source(content, script_origin); + + Local fn; + if (!ScriptCompiler::CompileFunction(context, + &script_source, + parameters.size(), + parameters.data(), + 0, + nullptr, + ScriptCompiler::kEagerCompile) + .ToLocal(&fn)) { + return {}; + } + + return fn; +} + bool ContextifyScript::InstanceOf(Environment* env, const Local& value) { return !value.IsEmpty() && diff --git a/src/node_contextify.h b/src/node_contextify.h index 3160160521e0fe..ed94504bf50dd7 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -210,6 +210,13 @@ v8::Maybe StoreCodeCacheResult( bool produce_cached_data, std::unique_ptr new_cached_data); +v8::MaybeLocal CompileFunction( + v8::Isolate* isolate, + v8::Local context, + v8::Local filename, + v8::Local content, + std::vector> parameters); + } // namespace contextify } // namespace node diff --git a/src/node_sea.cc b/src/node_sea.cc index 88741a5fce9d48..556646a5ef1b93 100644 --- a/src/node_sea.cc +++ b/src/node_sea.cc @@ -4,9 +4,13 @@ #include "debug_utils-inl.h" #include "env-inl.h" #include "json_parser.h" +#include "node_contextify.h" +#include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" #include "node_union_bytes.h" +#include "node_v8_platform-inl.h" +#include "util-inl.h" // The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by // the Node.js project that is present only once in the entire binary. It is @@ -26,9 +30,14 @@ using node::ExitCode; using v8::Context; +using v8::Function; using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Isolate; using v8::Local; using v8::Object; +using v8::ScriptCompiler; +using v8::String; using v8::Value; namespace node { @@ -78,6 +87,11 @@ size_t SeaSerializer::Write(const SeaResource& sea) { sea.code.data(), sea.code.size()); written_total += WriteStringView(sea.code, StringLogMode::kAddressAndContent); + + Debug("Write SEA resource code cache %p, size=%zu\n", + sea.code_cache.data(), + sea.code_cache.size()); + written_total += WriteStringView(sea.code_cache, StringLogMode::kAddressOnly); return written_total; } @@ -105,7 +119,12 @@ SeaResource SeaDeserializer::Read() { std::string_view code = ReadStringView(StringLogMode::kAddressAndContent); Debug("Read SEA resource code %p, size=%zu\n", code.data(), code.size()); - return {flags, code}; + + std::string_view code_cache = ReadStringView(StringLogMode::kAddressOnly); + Debug("Read SEA resource code cache %p, size=%zu\n", + code_cache.data(), + code_cache.size()); + return {flags, code, code_cache}; } std::string_view FindSingleExecutableBlob() { @@ -161,6 +180,27 @@ void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo& args) { sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning)); } +void GetCodeCache(const FunctionCallbackInfo& args) { + if (!IsSingleExecutable()) { + return; + } + + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); + + SeaResource sea_resource = FindSingleExecutableResource(); + + Local buf = + Buffer::Copy( + env, + reinterpret_cast(sea_resource.code_cache.data()), + sea_resource.code_cache.length()) + .ToLocalChecked(); + + args.GetReturnValue().Set(buf); +} + std::tuple FixupArgsForSEA(int argc, char** argv) { // Repeats argv[0] at position 1 on argv as a replacement for the missing // entry point file path. @@ -238,6 +278,49 @@ std::optional ParseSingleExecutableConfig( return result; } +std::optional GenerateCodeCache(std::string_view main_path, + std::string_view main_script) { + RAIIIsolate raii_isolate; + Isolate* isolate = raii_isolate.get(); + + HandleScope handle_scope(isolate); + Local context = Context::New(isolate); + Context::Scope context_scope(context); + + errors::PrinterTryCatch bootstrapCatch( + isolate, errors::PrinterTryCatch::kPrintSourceLine); + + Local filename; + if (!String::NewFromUtf8(isolate, main_path.data()).ToLocal(&filename)) { + return {}; + } + + Local content; + if (!String::NewFromUtf8(isolate, main_script.data()).ToLocal(&content)) { + return {}; + } + + std::vector> parameters = { + FIXED_ONE_BYTE_STRING(isolate, "exports"), + FIXED_ONE_BYTE_STRING(isolate, "require"), + FIXED_ONE_BYTE_STRING(isolate, "module"), + FIXED_ONE_BYTE_STRING(isolate, "__filename"), + FIXED_ONE_BYTE_STRING(isolate, "__dirname"), + }; + + Local fn; + if (!contextify::CompileFunction( + isolate, context, filename, content, std::move(parameters)) + .ToLocal(&fn)) { + return {}; + } + + std::unique_ptr cache{ + ScriptCompiler::CreateCodeCacheForFunction(fn)}; + std::string code_cache(cache->data, cache->data + cache->length); + return code_cache; +} + ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) { std::string main_script; // TODO(joyeecheung): unify the file utils. @@ -248,7 +331,15 @@ ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) { return ExitCode::kGenericUserError; } - SeaResource sea{config.flags, main_script}; + std::optional optional_code_cache = + GenerateCodeCache(config.main_path, main_script); + if (!optional_code_cache.has_value()) { + FPrintF(stderr, "Cannot generate V8 code cache\n"); + return ExitCode::kGenericUserError; + } + std::string code_cache = optional_code_cache.value(); + + SeaResource sea{config.flags, main_script, code_cache}; SeaSerializer serializer; serializer.Write(sea); @@ -288,10 +379,12 @@ void Initialize(Local target, target, "isExperimentalSeaWarningNeeded", IsExperimentalSeaWarningNeeded); + SetMethod(context, target, "getCodeCache", GetCodeCache); } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(IsExperimentalSeaWarningNeeded); + registry->Register(GetCodeCache); } } // namespace sea diff --git a/src/node_sea.h b/src/node_sea.h index 8b0877df3eb0d7..cb72cd01f7640b 100644 --- a/src/node_sea.h +++ b/src/node_sea.h @@ -26,6 +26,7 @@ enum class SeaFlags : uint32_t { struct SeaResource { SeaFlags flags = SeaFlags::kDefault; std::string_view code; + std::string_view code_cache; static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags); }; diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 9d27a7c66b2aa2..8bf2a9c834df23 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -42,7 +42,6 @@ using v8::MaybeLocal; using v8::Object; using v8::ObjectTemplate; using v8::ScriptCompiler; -using v8::ScriptOrigin; using v8::SnapshotCreator; using v8::StartupData; using v8::String; @@ -1207,7 +1206,6 @@ void CompileSerializeMain(const FunctionCallbackInfo& args) { Local source = args[1].As(); Isolate* isolate = args.GetIsolate(); Local context = isolate->GetCurrentContext(); - ScriptOrigin origin(isolate, filename, 0, 0, true); // TODO(joyeecheung): do we need all of these? Maybe we would want a less // internal version of them. std::vector> parameters = { @@ -1215,15 +1213,9 @@ void CompileSerializeMain(const FunctionCallbackInfo& args) { FIXED_ONE_BYTE_STRING(isolate, "__filename"), FIXED_ONE_BYTE_STRING(isolate, "__dirname"), }; - ScriptCompiler::Source script_source(source, origin); Local fn; - if (ScriptCompiler::CompileFunction(context, - &script_source, - parameters.size(), - parameters.data(), - 0, - nullptr, - ScriptCompiler::kEagerCompile) + if (contextify::CompileFunction( + isolate, context, filename, source, std::move(parameters)) .ToLocal(&fn)) { args.GetReturnValue().Set(fn); } diff --git a/src/util.cc b/src/util.cc index 7aaa5e2be5b880..121385dbdc3e60 100644 --- a/src/util.cc +++ b/src/util.cc @@ -28,6 +28,7 @@ #include "node_errors.h" #include "node_internals.h" #include "node_util.h" +#include "node_v8_platform-inl.h" #include "string_bytes.h" #include "uv.h" @@ -55,6 +56,7 @@ static std::atomic_int seq = {0}; // Sequence number for diagnostic filenames. namespace node { +using v8::ArrayBuffer; using v8::ArrayBufferView; using v8::Context; using v8::FunctionTemplate; @@ -616,4 +618,20 @@ Local UnionBytes::ToStringChecked(Isolate* isolate) const { } } +RAIIIsolate::RAIIIsolate() + : allocator_{ArrayBuffer::Allocator::NewDefaultAllocator()} { + isolate_ = Isolate::Allocate(); + CHECK_NOT_NULL(isolate_); + per_process::v8_platform.Platform()->RegisterIsolate(isolate_, + uv_default_loop()); + Isolate::CreateParams params; + params.array_buffer_allocator = allocator_.get(); + Isolate::Initialize(isolate_, params); +} + +RAIIIsolate::~RAIIIsolate() { + per_process::v8_platform.Platform()->UnregisterIsolate(isolate_); + isolate_->Dispose(); +} + } // namespace node diff --git a/src/util.h b/src/util.h index 52dd334d07d8b1..025a3b641a66da 100644 --- a/src/util.h +++ b/src/util.h @@ -965,6 +965,19 @@ void SetConstructorFunction(v8::Isolate* isolate, SetConstructorFunctionFlag flag = SetConstructorFunctionFlag::SET_CLASS_NAME); +// Simple RAII class to spin up a v8::Isolate instance. +class RAIIIsolate { + public: + RAIIIsolate(); + ~RAIIIsolate(); + + v8::Isolate* get() const { return isolate_; } + + private: + std::unique_ptr allocator_; + v8::Isolate* isolate_; +}; + } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/test/fixtures/errors/force_colors.snapshot b/test/fixtures/errors/force_colors.snapshot index a38745e396a8d9..33ee0af4638dd3 100644 --- a/test/fixtures/errors/force_colors.snapshot +++ b/test/fixtures/errors/force_colors.snapshot @@ -4,10 +4,10 @@ throw new Error('Should include grayed stack trace') Error: Should include grayed stack trace at Object. (/test*force_colors.js:1:7) - at Module._compile (node:internal*modules*cjs*loader:1255:14) - at Module._extensions..js (node:internal*modules*cjs*loader:1309:10) - at Module.load (node:internal*modules*cjs*loader:1113:32) - at Module._load (node:internal*modules*cjs*loader:960:12) + at Module._compile (node:internal*modules*cjs*loader:1261:14) + at Module._extensions..js (node:internal*modules*cjs*loader:1315:10) + at Module.load (node:internal*modules*cjs*loader:1114:32) + at Module._load (node:internal*modules*cjs*loader:961:12)  at Function.executeUserEntryPoint [as runMain] (node:internal*modules*run_main:83:12)  at node:internal*main*run_main_module:23:47