diff --git a/src/api/embed_helpers.cc b/src/api/embed_helpers.cc index f0f92c690eb63c..3944890e443885 100644 --- a/src/api/embed_helpers.cc +++ b/src/api/embed_helpers.cc @@ -1,6 +1,7 @@ -#include "node.h" -#include "env-inl.h" #include "debug_utils-inl.h" +#include "env-inl.h" +#include "node.h" +#include "node_snapshot_builder.h" using v8::Context; using v8::Function; @@ -86,8 +87,9 @@ struct CommonEnvironmentSetup::Impl { CommonEnvironmentSetup::CommonEnvironmentSetup( MultiIsolatePlatform* platform, std::vector* errors, + const EmbedderSnapshotData* snapshot_data, std::function make_env) - : impl_(new Impl()) { + : impl_(new Impl()) { CHECK_NOT_NULL(platform); CHECK_NOT_NULL(errors); @@ -104,16 +106,25 @@ CommonEnvironmentSetup::CommonEnvironmentSetup( loop->data = this; impl_->allocator = ArrayBufferAllocator::Create(); - impl_->isolate = NewIsolate(impl_->allocator, &impl_->loop, platform); + impl_->isolate = + NewIsolate(impl_->allocator, &impl_->loop, platform, snapshot_data); Isolate* isolate = impl_->isolate; { Locker locker(isolate); Isolate::Scope isolate_scope(isolate); impl_->isolate_data.reset(CreateIsolateData( - isolate, loop, platform, impl_->allocator.get())); + isolate, loop, platform, impl_->allocator.get(), snapshot_data)); HandleScope handle_scope(isolate); + if (snapshot_data) { + impl_->env.reset(make_env(this)); + if (impl_->env) { + impl_->context.Reset(isolate, impl_->env->context()); + } + return; + } + Local context = NewContext(isolate); impl_->context.Reset(isolate, context); if (context.IsEmpty()) { @@ -126,6 +137,12 @@ CommonEnvironmentSetup::CommonEnvironmentSetup( } } +CommonEnvironmentSetup::CommonEnvironmentSetup( + MultiIsolatePlatform* platform, + std::vector* errors, + std::function make_env) + : CommonEnvironmentSetup(platform, errors, nullptr, make_env) {} + CommonEnvironmentSetup::~CommonEnvironmentSetup() { if (impl_->isolate != nullptr) { Isolate* isolate = impl_->isolate; @@ -189,4 +206,42 @@ v8::Local CommonEnvironmentSetup::context() const { return impl_->context.Get(impl_->isolate); } +void EmbedderSnapshotData::DeleteSnapshotData::operator()( + const EmbedderSnapshotData* data) const { + CHECK_IMPLIES(data->owns_impl_, data->impl_); + if (data->owns_impl_ && + data->impl_->data_ownership == SnapshotData::DataOwnership::kOwned) { + delete data->impl_; + } + delete data; +} + +EmbedderSnapshotData::Pointer EmbedderSnapshotData::BuiltinSnapshotData() { + return EmbedderSnapshotData::Pointer{new EmbedderSnapshotData( + SnapshotBuilder::GetEmbeddedSnapshotData(), false)}; +} + +EmbedderSnapshotData::Pointer EmbedderSnapshotData::FromFile(FILE* in) { + SnapshotData* snapshot_data = new SnapshotData(); + CHECK_EQ(snapshot_data->data_ownership, SnapshotData::DataOwnership::kOwned); + EmbedderSnapshotData::Pointer result{ + new EmbedderSnapshotData(snapshot_data, true)}; + if (!SnapshotData::FromBlob(snapshot_data, in)) { + return {}; + } + return result; +} + +EmbedderSnapshotData::EmbedderSnapshotData(const SnapshotData* impl, + bool owns_impl) + : impl_(impl), owns_impl_(owns_impl) {} + +bool EmbedderSnapshotData::CanUseCustomSnapshotPerIsolate() { +#ifdef NODE_V8_SHARED_RO_HEAP + return false; +#else + return true; +#endif +} + } // namespace node diff --git a/src/api/environment.cc b/src/api/environment.cc index dec67219d02067..389c6666dc6966 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -9,6 +9,7 @@ #include "node_platform.h" #include "node_realm-inl.h" #include "node_shadow_realm.h" +#include "node_snapshot_builder.h" #include "node_v8_platform-inl.h" #include "node_wasm_web_api.h" #include "uv.h" @@ -315,9 +316,15 @@ void SetIsolateUpForNode(v8::Isolate* isolate) { Isolate* NewIsolate(Isolate::CreateParams* params, uv_loop_t* event_loop, MultiIsolatePlatform* platform, - bool has_snapshot_data) { + const SnapshotData* snapshot_data, + const IsolateSettings& settings) { Isolate* isolate = Isolate::Allocate(); if (isolate == nullptr) return nullptr; + + if (snapshot_data != nullptr) { + SnapshotBuilder::InitializeIsolateParams(snapshot_data, params); + } + #ifdef NODE_V8_SHARED_RO_HEAP { // In shared-readonly-heap mode, V8 requires all snapshots used for @@ -336,12 +343,12 @@ Isolate* NewIsolate(Isolate::CreateParams* params, SetIsolateCreateParamsForNode(params); Isolate::Initialize(isolate, *params); - if (!has_snapshot_data) { + if (snapshot_data == nullptr) { // If in deserialize mode, delay until after the deserialization is // complete. - SetIsolateUpForNode(isolate); + SetIsolateUpForNode(isolate, settings); } else { - SetIsolateMiscHandlers(isolate, {}); + SetIsolateMiscHandlers(isolate, settings); } return isolate; @@ -349,25 +356,60 @@ Isolate* NewIsolate(Isolate::CreateParams* params, Isolate* NewIsolate(ArrayBufferAllocator* allocator, uv_loop_t* event_loop, - MultiIsolatePlatform* platform) { + MultiIsolatePlatform* platform, + const EmbedderSnapshotData* snapshot_data, + const IsolateSettings& settings) { Isolate::CreateParams params; if (allocator != nullptr) params.array_buffer_allocator = allocator; - return NewIsolate(¶ms, event_loop, platform); + return NewIsolate(¶ms, + event_loop, + platform, + SnapshotData::FromEmbedderWrapper(snapshot_data), + settings); } Isolate* NewIsolate(std::shared_ptr allocator, uv_loop_t* event_loop, - MultiIsolatePlatform* platform) { + MultiIsolatePlatform* platform, + const EmbedderSnapshotData* snapshot_data, + const IsolateSettings& settings) { Isolate::CreateParams params; if (allocator) params.array_buffer_allocator_shared = allocator; - return NewIsolate(¶ms, event_loop, platform); + return NewIsolate(¶ms, + event_loop, + platform, + SnapshotData::FromEmbedderWrapper(snapshot_data), + settings); +} + +Isolate* NewIsolate(ArrayBufferAllocator* allocator, + uv_loop_t* event_loop, + MultiIsolatePlatform* platform) { + return NewIsolate(allocator, event_loop, platform, nullptr); +} + +Isolate* NewIsolate(std::shared_ptr allocator, + uv_loop_t* event_loop, + MultiIsolatePlatform* platform) { + return NewIsolate(allocator, event_loop, platform, nullptr); +} + +IsolateData* CreateIsolateData( + Isolate* isolate, + uv_loop_t* loop, + MultiIsolatePlatform* platform, + ArrayBufferAllocator* allocator, + const EmbedderSnapshotData* embedder_snapshot_data) { + const SnapshotData* snapshot_data = + SnapshotData::FromEmbedderWrapper(embedder_snapshot_data); + return new IsolateData(isolate, loop, platform, allocator, snapshot_data); } IsolateData* CreateIsolateData(Isolate* isolate, uv_loop_t* loop, MultiIsolatePlatform* platform, ArrayBufferAllocator* allocator) { - return new IsolateData(isolate, loop, platform, allocator); + return CreateIsolateData(isolate, loop, platform, allocator, nullptr); } void FreeIsolateData(IsolateData* isolate_data) { @@ -395,13 +437,45 @@ Environment* CreateEnvironment( EnvironmentFlags::Flags flags, ThreadId thread_id, std::unique_ptr inspector_parent_handle) { - Isolate* isolate = context->GetIsolate(); + Isolate* isolate = isolate_data->isolate(); HandleScope handle_scope(isolate); - Context::Scope context_scope(context); + + const bool use_snapshot = context.IsEmpty(); + const EnvSerializeInfo* env_snapshot_info = nullptr; + if (use_snapshot) { + CHECK_NOT_NULL(isolate_data->snapshot_data()); + env_snapshot_info = &isolate_data->snapshot_data()->env_info; + } + // TODO(addaleax): This is a much better place for parsing per-Environment // options than the global parse call. - Environment* env = new Environment( - isolate_data, context, args, exec_args, nullptr, flags, thread_id); + Environment* env = new Environment(isolate_data, + isolate, + args, + exec_args, + env_snapshot_info, + flags, + thread_id); + CHECK_NOT_NULL(env); + + if (use_snapshot) { + context = Context::FromSnapshot(isolate, + SnapshotData::kNodeMainContextIndex, + {DeserializeNodeInternalFields, env}) + .ToLocalChecked(); + + CHECK(!context.IsEmpty()); + Context::Scope context_scope(context); + + if (InitializeContextRuntime(context).IsNothing()) { + FreeEnvironment(env); + return nullptr; + } + SetIsolateErrorHandlers(isolate, {}); + } + + Context::Scope context_scope(context); + env->InitializeMainContext(context, env_snapshot_info); #if HAVE_INSPECTOR if (env->should_create_inspector()) { @@ -415,7 +489,7 @@ Environment* CreateEnvironment( } #endif - if (env->principal_realm()->RunBootstrapping().IsEmpty()) { + if (!use_snapshot && env->principal_realm()->RunBootstrapping().IsEmpty()) { FreeEnvironment(env); return nullptr; } @@ -500,6 +574,10 @@ ArrayBufferAllocator* GetArrayBufferAllocator(IsolateData* isolate_data) { return isolate_data->node_allocator(); } +Local GetMainContext(Environment* env) { + return env->context(); +} + MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env) { return GetMultiIsolatePlatform(env->isolate_data()); } diff --git a/src/env-inl.h b/src/env-inl.h index fe31465ef0ba46..93a454541cc3f0 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -69,6 +69,10 @@ inline MultiIsolatePlatform* IsolateData::platform() const { return platform_; } +inline const SnapshotData* IsolateData::snapshot_data() const { + return snapshot_data_; +} + inline void IsolateData::set_worker_context(worker::Worker* context) { CHECK_NULL(worker_context_); // Should be set only once. worker_context_ = context; diff --git a/src/env.cc b/src/env.cc index 1698d259996692..c5d4f43ecebd8f 100644 --- a/src/env.cc +++ b/src/env.cc @@ -472,19 +472,20 @@ IsolateData::IsolateData(Isolate* isolate, uv_loop_t* event_loop, MultiIsolatePlatform* platform, ArrayBufferAllocator* node_allocator, - const IsolateDataSerializeInfo* isolate_data_info) + const SnapshotData* snapshot_data) : isolate_(isolate), event_loop_(event_loop), node_allocator_(node_allocator == nullptr ? nullptr : node_allocator->GetImpl()), - platform_(platform) { + platform_(platform), + snapshot_data_(snapshot_data) { options_.reset( new PerIsolateOptions(*(per_process::cli_options->per_isolate))); - if (isolate_data_info == nullptr) { + if (snapshot_data == nullptr) { CreateProperties(); } else { - DeserializeProperties(isolate_data_info); + DeserializeProperties(&snapshot_data->isolate_data_info); } } @@ -674,14 +675,23 @@ Environment::Environment(IsolateData* isolate_data, thread_id_(thread_id.id == static_cast(-1) ? AllocateEnvironmentThreadId().id : thread_id.id) { + constexpr bool is_shared_ro_heap = #ifdef NODE_V8_SHARED_RO_HEAP - if (!is_main_thread()) { + true; +#else + false; +#endif + if (is_shared_ro_heap && !is_main_thread()) { + // If this is a Worker thread and we are in shared-readonly-heap mode, + // we can always safely use the parent's Isolate's code cache. CHECK_NOT_NULL(isolate_data->worker_context()); - // TODO(addaleax): Adjust for the embedder API snapshot support changes builtin_loader()->CopySourceAndCodeCacheReferenceFrom( isolate_data->worker_context()->env()->builtin_loader()); + } else if (isolate_data->snapshot_data() != nullptr) { + // ... otherwise, if a snapshot was provided, use its code cache. + builtin_loader()->RefreshCodeCache( + isolate_data->snapshot_data()->code_cache); } -#endif // We'll be creating new objects so make sure we've entered the context. HandleScope handle_scope(isolate); @@ -746,23 +756,6 @@ Environment::Environment(IsolateData* isolate_data, } } -Environment::Environment(IsolateData* isolate_data, - Local context, - const std::vector& args, - const std::vector& exec_args, - const EnvSerializeInfo* env_info, - EnvironmentFlags::Flags flags, - ThreadId thread_id) - : Environment(isolate_data, - context->GetIsolate(), - args, - exec_args, - env_info, - flags, - thread_id) { - InitializeMainContext(context, env_info); -} - void Environment::InitializeMainContext(Local context, const EnvSerializeInfo* env_info) { principal_realm_ = std::make_unique( diff --git a/src/env.h b/src/env.h index c70b835862d901..0133e646b8e5bb 100644 --- a/src/env.h +++ b/src/env.h @@ -126,7 +126,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { uv_loop_t* event_loop, MultiIsolatePlatform* platform = nullptr, ArrayBufferAllocator* node_allocator = nullptr, - const IsolateDataSerializeInfo* isolate_data_info = nullptr); + const SnapshotData* snapshot_data = nullptr); SET_MEMORY_INFO_NAME(IsolateData) SET_SELF_SIZE(IsolateData) void MemoryInfo(MemoryTracker* tracker) const override; @@ -134,6 +134,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { inline uv_loop_t* event_loop() const; inline MultiIsolatePlatform* platform() const; + inline const SnapshotData* snapshot_data() const; inline std::shared_ptr options(); inline void set_options(std::shared_ptr options); @@ -205,6 +206,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { uv_loop_t* const event_loop_; NodeArrayBufferAllocator* const node_allocator_; MultiIsolatePlatform* platform_; + const SnapshotData* snapshot_data_; std::shared_ptr options_; worker::Worker* worker_context_ = nullptr; }; @@ -520,6 +522,9 @@ struct SnapshotData { // and the caller should not consume the snapshot data. bool Check() const; static bool FromBlob(SnapshotData* out, FILE* in); + static const SnapshotData* FromEmbedderWrapper( + const EmbedderSnapshotData* data); + EmbedderSnapshotData::Pointer AsEmbedderWrapper() const; ~SnapshotData(); }; @@ -610,14 +615,6 @@ class Environment : public MemoryRetainer { ThreadId thread_id); void InitializeMainContext(v8::Local context, const EnvSerializeInfo* env_info); - // Create an Environment and initialize the provided principal context for it. - Environment(IsolateData* isolate_data, - v8::Local context, - const std::vector& args, - const std::vector& exec_args, - const EnvSerializeInfo* env_info, - EnvironmentFlags::Flags flags, - ThreadId thread_id); ~Environment() override; void InitializeLibuv(); diff --git a/src/node.h b/src/node.h index 36e717e96bf57b..f536217f5504b4 100644 --- a/src/node.h +++ b/src/node.h @@ -127,6 +127,8 @@ struct napi_module; // terminally confused when it's done in node_internals.h namespace node { +struct SnapshotData; + namespace tracing { class TracingController; @@ -476,6 +478,65 @@ struct IsolateSettings { modify_code_generation_from_strings_callback = nullptr; }; +// Represents a startup snapshot blob, e.g. created by passing +// --node-snapshot-main=entry.js to the configure script at build time, +// or by running Node.js with the --build-snapshot option. +// +// If used, the snapshot *must* have been built with the same Node.js +// version and V8 flags as the version that is currently running, and will +// be rejected otherwise. +// The same EmbedderSnapshotData instance *must* be passed to both +// `NewIsolate()` and `CreateIsolateData()`. The first `Environment` instance +// should be created with an empty `context` argument and will then +// use the main context included in the snapshot blob. It can be retrieved +// using `GetMainContext()`. `LoadEnvironment` can receive an empty +// `StartExecutionCallback` in this case. +// If V8 was configured with the shared-readonly-heap option, it requires +// all snapshots used to create `Isolate` instances to be identical. +// This option *must* be unset by embedders who wish to use the startup +// feature during the build step by passing the --disable-shared-readonly-heap +// flag to the configure script. +// +// Snapshots are an *experimental* feature. In particular, the embedder API +// exposed through this class is subject to change or removal between Node.js +// versions, including possible API and ABI breakage. +class EmbedderSnapshotData { + public: + struct DeleteSnapshotData { + void operator()(const EmbedderSnapshotData*) const; + }; + using Pointer = + std::unique_ptr; + + // Return an EmbedderSnapshotData object that refers to the built-in + // snapshot of Node.js. This can have been configured through e.g. + // --node-snapshot-main=entry.js. + static Pointer BuiltinSnapshotData(); + + // Return an EmbedderSnapshotData object that is based on an input file. + // Calling this method will not consume but not close the FILE* handle. + // The FILE* handle can be closed immediately following this call. + // If the snapshot is invalid, this returns an empty pointer. + static Pointer FromFile(FILE* in); + + // Returns whether custom snapshots can be used. Currently, this means + // that V8 was configured without the shared-readonly-heap feature. + static bool CanUseCustomSnapshotPerIsolate(); + + EmbedderSnapshotData(const EmbedderSnapshotData&) = delete; + EmbedderSnapshotData& operator=(const EmbedderSnapshotData&) = delete; + EmbedderSnapshotData(EmbedderSnapshotData&&) = delete; + EmbedderSnapshotData& operator=(EmbedderSnapshotData&&) = delete; + + protected: + EmbedderSnapshotData(const SnapshotData* impl, bool owns_impl); + + private: + const SnapshotData* impl_; + bool owns_impl_; + friend struct SnapshotData; +}; + // Overriding IsolateSettings may produce unexpected behavior // in Node.js core functionality, so proceed at your own risk. NODE_EXTERN void SetIsolateUpForNode(v8::Isolate* isolate, @@ -492,10 +553,23 @@ NODE_EXTERN void SetIsolateUpForNode(v8::Isolate* isolate); NODE_EXTERN v8::Isolate* NewIsolate(ArrayBufferAllocator* allocator, struct uv_loop_s* event_loop, MultiIsolatePlatform* platform = nullptr); +// TODO(addaleax): Merge with the function definition above. +NODE_EXTERN v8::Isolate* NewIsolate(ArrayBufferAllocator* allocator, + struct uv_loop_s* event_loop, + MultiIsolatePlatform* platform, + const EmbedderSnapshotData* snapshot_data, + const IsolateSettings& settings = {}); NODE_EXTERN v8::Isolate* NewIsolate( std::shared_ptr allocator, struct uv_loop_s* event_loop, MultiIsolatePlatform* platform); +// TODO(addaleax): Merge with the function definition above. +NODE_EXTERN v8::Isolate* NewIsolate( + std::shared_ptr allocator, + struct uv_loop_s* event_loop, + MultiIsolatePlatform* platform, + const EmbedderSnapshotData* snapshot_data, + const IsolateSettings& settings = {}); // Creates a new context with Node.js-specific tweaks. NODE_EXTERN v8::Local NewContext( @@ -515,6 +589,13 @@ NODE_EXTERN IsolateData* CreateIsolateData( struct uv_loop_s* loop, MultiIsolatePlatform* platform = nullptr, ArrayBufferAllocator* allocator = nullptr); +// TODO(addaleax): Merge with the function definition above. +NODE_EXTERN IsolateData* CreateIsolateData( + v8::Isolate* isolate, + struct uv_loop_s* loop, + MultiIsolatePlatform* platform, + ArrayBufferAllocator* allocator, + const EmbedderSnapshotData* snapshot_data); NODE_EXTERN void FreeIsolateData(IsolateData* isolate_data); struct ThreadId { @@ -574,6 +655,8 @@ struct InspectorParentHandle { // TODO(addaleax): Maybe move per-Environment options parsing here. // Returns nullptr when the Environment cannot be created e.g. there are // pending JavaScript exceptions. +// `context` may be empty if an `EmbedderSnapshotData` instance was provided +// to `NewIsolate()` and `CreateIsolateData()`. NODE_EXTERN Environment* CreateEnvironment( IsolateData* isolate_data, v8::Local context, @@ -627,6 +710,9 @@ NODE_EXTERN void DefaultProcessExitHandler(Environment* env, int exit_code); NODE_EXTERN Environment* GetCurrentEnvironment(v8::Local context); NODE_EXTERN IsolateData* GetEnvironmentIsolateData(Environment* env); NODE_EXTERN ArrayBufferAllocator* GetArrayBufferAllocator(IsolateData* data); +// This is mostly useful for Environment* instances that were created through +// a snapshot and have a main context that was read from that snapshot. +NODE_EXTERN v8::Local GetMainContext(Environment* env); NODE_EXTERN void OnFatalError(const char* location, const char* message); NODE_EXTERN void PromiseRejectCallback(v8::PromiseRejectMessage message); @@ -733,6 +819,12 @@ class NODE_EXTERN CommonEnvironmentSetup { MultiIsolatePlatform* platform, std::vector* errors, EnvironmentArgs&&... env_args); + template + static std::unique_ptr CreateWithSnapshot( + MultiIsolatePlatform* platform, + std::vector* errors, + const EmbedderSnapshotData* snapshot_data, + EnvironmentArgs&&... env_args); struct uv_loop_s* event_loop() const; std::shared_ptr array_buffer_allocator() const; @@ -753,6 +845,11 @@ class NODE_EXTERN CommonEnvironmentSetup { MultiIsolatePlatform*, std::vector*, std::function); + CommonEnvironmentSetup( + MultiIsolatePlatform*, + std::vector*, + const EmbedderSnapshotData*, + std::function); }; // Implementation for CommonEnvironmentSetup::Create @@ -771,6 +868,27 @@ std::unique_ptr CommonEnvironmentSetup::Create( if (!errors->empty()) ret.reset(); return ret; } +// Implementation for ::CreateWithSnapshot -- the ::Create() method +// could call this with a nullptr snapshot_data in a major version. +template +std::unique_ptr +CommonEnvironmentSetup::CreateWithSnapshot( + MultiIsolatePlatform* platform, + std::vector* errors, + const EmbedderSnapshotData* snapshot_data, + EnvironmentArgs&&... env_args) { + auto ret = std::unique_ptr(new CommonEnvironmentSetup( + platform, + errors, + snapshot_data, + [&](const CommonEnvironmentSetup* setup) -> Environment* { + return CreateEnvironment(setup->isolate_data(), + setup->context(), + std::forward(env_args)...); + })); + if (!errors->empty()) ret.reset(); + return ret; +} /* Converts a unixtime to V8 Date */ NODE_DEPRECATED("Use v8::Date::New() directly", diff --git a/src/node_contextify.cc b/src/node_contextify.cc index ed552ddd559f51..28b3266f20b070 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -117,9 +117,7 @@ BaseObjectPtr ContextifyContext::New( InitializeGlobalTemplates(env->isolate_data()); Local object_template = env->contextify_global_template(); DCHECK(!object_template.IsEmpty()); - bool use_node_snapshot = per_process::cli_options->node_snapshot; - const SnapshotData* snapshot_data = - use_node_snapshot ? SnapshotBuilder::GetEmbeddedSnapshotData() : nullptr; + const SnapshotData* snapshot_data = env->isolate_data()->snapshot_data(); MicrotaskQueue* queue = options.microtask_queue_wrap diff --git a/src/node_internals.h b/src/node_internals.h index 23b8c37c614c40..df90781f58e9a2 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -304,7 +304,8 @@ void DefineZlibConstants(v8::Local target); v8::Isolate* NewIsolate(v8::Isolate::CreateParams* params, uv_loop_t* event_loop, MultiIsolatePlatform* platform, - bool has_snapshot_data = false); + const SnapshotData* snapshot_data = nullptr, + const IsolateSettings& settings = {}); // This overload automatically picks the right 'main_script_id' if no callback // was provided by the embedder. v8::MaybeLocal StartExecution(Environment* env, diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index a40866b482c326..81804656c51093 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -71,22 +71,18 @@ NodeMainInstance::NodeMainInstance(const SnapshotData* snapshot_data, isolate_params_(std::make_unique()), snapshot_data_(snapshot_data) { isolate_params_->array_buffer_allocator = array_buffer_allocator_.get(); - if (snapshot_data != nullptr) { - SnapshotBuilder::InitializeIsolateParams(snapshot_data, - isolate_params_.get()); - } - isolate_ = NewIsolate( - isolate_params_.get(), event_loop, platform, snapshot_data != nullptr); + isolate_ = + NewIsolate(isolate_params_.get(), event_loop, platform, snapshot_data); CHECK_NOT_NULL(isolate_); // If the indexes are not nullptr, we are not deserializing - isolate_data_ = std::make_unique( - isolate_, - event_loop, - platform, - array_buffer_allocator_.get(), - snapshot_data == nullptr ? nullptr : &(snapshot_data->isolate_data_info)); + isolate_data_.reset( + CreateIsolateData(isolate_, + event_loop, + platform, + array_buffer_allocator_.get(), + snapshot_data->AsEmbedderWrapper().get())); isolate_data_->max_young_gen_size = isolate_params_->constraints.max_young_generation_size_in_bytes(); @@ -152,33 +148,10 @@ NodeMainInstance::CreateMainEnvironment(ExitCode* exit_code) { DeleteFnPtr env; if (snapshot_data_ != nullptr) { - env.reset(new Environment(isolate_data_.get(), - isolate_, - args_, - exec_args_, - &(snapshot_data_->env_info), - EnvironmentFlags::kDefaultFlags, - {})); -#ifdef NODE_V8_SHARED_RO_HEAP - // TODO(addaleax): Do this as part of creating the Environment - // once we store the SnapshotData* itself on IsolateData. - env->builtin_loader()->RefreshCodeCache(snapshot_data_->code_cache); -#endif - context = Context::FromSnapshot(isolate_, - SnapshotData::kNodeMainContextIndex, - {DeserializeNodeInternalFields, env.get()}) - .ToLocalChecked(); - - CHECK(!context.IsEmpty()); - Context::Scope context_scope(context); - - CHECK(InitializeContextRuntime(context).IsJust()); - SetIsolateErrorHandlers(isolate_, {}); - env->InitializeMainContext(context, &(snapshot_data_->env_info)); -#if HAVE_INSPECTOR - env->InitializeInspector({}); -#endif - + env.reset(CreateEnvironment(isolate_data_.get(), + Local(), // read from snapshot + args_, + exec_args_)); #if HAVE_OPENSSL crypto::InitCryptoOnce(isolate_); #endif // HAVE_OPENSSL diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 85b79b7bc53467..eba39064a24fea 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -862,6 +862,15 @@ void SnapshotData::ToBlob(FILE* out) const { w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total); } +const SnapshotData* SnapshotData::FromEmbedderWrapper( + const EmbedderSnapshotData* data) { + return data != nullptr ? data->impl_ : nullptr; +} + +EmbedderSnapshotData::Pointer SnapshotData::AsEmbedderWrapper() const { + return EmbedderSnapshotData::Pointer{new EmbedderSnapshotData(this, false)}; +} + bool SnapshotData::FromBlob(SnapshotData* out, FILE* in) { CHECK_EQ(ftell(in), 0); int err = fseek(in, 0, SEEK_END); @@ -1082,6 +1091,8 @@ const std::vector& SnapshotBuilder::CollectExternalReferences() { void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data, Isolate::CreateParams* params) { + CHECK_NULL(params->external_references); + CHECK_NULL(params->snapshot_blob); params->external_references = CollectExternalReferences().data(); params->snapshot_blob = const_cast(&(data->v8_snapshot_blob_data)); diff --git a/src/node_worker.cc b/src/node_worker.cc index b84a39cce330d7..66ab3dfd8ceb33 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -584,9 +584,7 @@ void Worker::New(const FunctionCallbackInfo& args) { exec_argv_out = env->exec_argv(); } - bool use_node_snapshot = per_process::cli_options->node_snapshot; - const SnapshotData* snapshot_data = - use_node_snapshot ? SnapshotBuilder::GetEmbeddedSnapshotData() : nullptr; + const SnapshotData* snapshot_data = env->isolate_data()->snapshot_data(); Worker* worker = new Worker(env, args.This(), diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc index 2ad8afd42845dd..ea3af217319ef7 100644 --- a/test/embedding/embedtest.cc +++ b/test/embedding/embedtest.cc @@ -4,6 +4,8 @@ // Note: This file is being referred to from doc/api/embedding.md, and excerpts // from it are included in the documentation. Try to keep these in sync. +// Snapshot support is not part of the embedder API docs yet due to its +// experimental nature, although it is of course documented in node.h. using node::CommonEnvironmentSetup; using node::Environment; @@ -55,9 +57,22 @@ int RunNodeInstance(MultiIsolatePlatform* platform, const std::vector& exec_args) { int exit_code = 0; + node::EmbedderSnapshotData::Pointer snapshot; + auto snapshot_arg_it = + std::find(args.begin(), args.end(), "--embedder-snapshot-blob"); + if (snapshot_arg_it < args.end() - 1) { + FILE* fp = fopen((snapshot_arg_it + 1)->c_str(), "r"); + assert(fp != nullptr); + snapshot = node::EmbedderSnapshotData::FromFile(fp); + fclose(fp); + } + std::vector errors; std::unique_ptr setup = - CommonEnvironmentSetup::Create(platform, &errors, args, exec_args); + snapshot + ? CommonEnvironmentSetup::CreateWithSnapshot( + platform, &errors, snapshot.get(), args, exec_args) + : CommonEnvironmentSetup::Create(platform, &errors, args, exec_args); if (!setup) { for (const std::string& err : errors) fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str()); @@ -73,13 +88,18 @@ int RunNodeInstance(MultiIsolatePlatform* platform, HandleScope handle_scope(isolate); Context::Scope context_scope(setup->context()); - MaybeLocal loadenv_ret = node::LoadEnvironment( - env, - "const publicRequire =" - " require('module').createRequire(process.cwd() + '/');" - "globalThis.require = publicRequire;" - "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };" - "require('vm').runInThisContext(process.argv[1]);"); + MaybeLocal loadenv_ret; + if (snapshot) { + loadenv_ret = node::LoadEnvironment(env, node::StartExecutionCallback{}); + } else { + loadenv_ret = node::LoadEnvironment( + env, + "const publicRequire =" + " require('module').createRequire(process.cwd() + '/');" + "globalThis.require = publicRequire;" + "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };" + "require('vm').runInThisContext(process.argv[1]);"); + } if (loadenv_ret.IsEmpty()) // There has been a JS exception. return 1; diff --git a/test/embedding/test-embedding.js b/test/embedding/test-embedding.js index 2fbaaf0ef81a49..c21c39b28e99dd 100644 --- a/test/embedding/test-embedding.js +++ b/test/embedding/test-embedding.js @@ -1,17 +1,26 @@ 'use strict'; const common = require('../common'); const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); const assert = require('assert'); const child_process = require('child_process'); const path = require('path'); +const fs = require('fs'); +tmpdir.refresh(); common.allowGlobals(global.require); common.allowGlobals(global.embedVars); -let binary = `out/${common.buildType}/embedtest`; -if (common.isWindows) { - binary += '.exe'; + +function resolveBuiltBinary(bin) { + let binary = `out/${common.buildType}/${bin}`; + if (common.isWindows) { + binary += '.exe'; + } + return path.resolve(__dirname, '..', '..', binary); } -binary = path.resolve(__dirname, '..', '..', binary); + +const binary = resolveBuiltBinary('embedtest'); +const standaloneNodeBinary = resolveBuiltBinary('node'); assert.strictEqual( child_process.spawnSync(binary, ['console.log(42)']) @@ -41,3 +50,39 @@ const fixturePath = JSON.stringify(fixtures.path('exit.js')); assert.strictEqual( child_process.spawnSync(binary, [`require(${fixturePath})`, 92]).status, 92); + +// Basic snapshot support +{ + const snapshotFixture = fixtures.path('snapshot', 'echo-args.js'); + const blobPath = path.join(tmpdir.path, 'embedder-snapshot.blob'); + const buildSnapshotArgs = [snapshotFixture, 'arg1', 'arg2']; + const runEmbeddedArgs = ['--embedder-snapshot-blob', blobPath, 'arg3', 'arg4']; + + fs.rmSync(blobPath, { force: true }); + assert.strictEqual(child_process.spawnSync(standaloneNodeBinary, [ + '--snapshot-blob', blobPath, '--build-snapshot', ...buildSnapshotArgs, + ], { + cwd: tmpdir.path, + }).status, 0); + const spawnResult = child_process.spawnSync(binary, ['--', ...runEmbeddedArgs]); + assert.deepStrictEqual(JSON.parse(spawnResult.stdout), { + originalArgv: [standaloneNodeBinary, ...buildSnapshotArgs], + currentArgv: [binary, ...runEmbeddedArgs], + }); +} + +// Create workers and vm contexts after deserialization +{ + const snapshotFixture = fixtures.path('snapshot', 'create-worker-and-vm.js'); + const blobPath = path.join(tmpdir.path, 'embedder-snapshot.blob'); + + fs.rmSync(blobPath, { force: true }); + assert.strictEqual(child_process.spawnSync(standaloneNodeBinary, [ + '--snapshot-blob', blobPath, '--build-snapshot', snapshotFixture, + ], { + cwd: tmpdir.path, + }).status, 0); + assert.strictEqual( + child_process.spawnSync(binary, ['--', '--embedder-snapshot-blob', blobPath]).status, + 0); +} diff --git a/test/fixtures/snapshot/create-worker-and-vm.js b/test/fixtures/snapshot/create-worker-and-vm.js new file mode 100644 index 00000000000000..4dd2aa940247af --- /dev/null +++ b/test/fixtures/snapshot/create-worker-and-vm.js @@ -0,0 +1,20 @@ +const { + setDeserializeMainFunction, +} = require('v8').startupSnapshot; +const assert = require('assert'); + +setDeserializeMainFunction(() => { + const vm = require('vm'); + const { Worker } = require('worker_threads'); + assert.strictEqual(vm.runInNewContext('21+21'), 42); + const worker = new Worker( + 'require("worker_threads").parentPort.postMessage({value: 21 + 21})', + { eval: true }); + + const messages = []; + worker.on('message', message => messages.push(message)); + + process.on('beforeExit', () => { + assert.deepStrictEqual(messages, [{value:42}]); + }) +}); diff --git a/test/fixtures/snapshot/echo-args.js b/test/fixtures/snapshot/echo-args.js new file mode 100644 index 00000000000000..0aed46223e92a0 --- /dev/null +++ b/test/fixtures/snapshot/echo-args.js @@ -0,0 +1,12 @@ +const { + setDeserializeMainFunction, +} = require('v8').startupSnapshot; + +const originalArgv = [...process.argv]; + +setDeserializeMainFunction(() => { + console.log(JSON.stringify({ + currentArgv: process.argv, + originalArgv + })); +});