diff --git a/lib/internal/main/mksnapshot.js b/lib/internal/main/mksnapshot.js index d7ad9324114637..6607d34138b910 100644 --- a/lib/internal/main/mksnapshot.js +++ b/lib/internal/main/mksnapshot.js @@ -12,6 +12,7 @@ const { const binding = internalBinding('mksnapshot'); const { BuiltinModule } = require('internal/bootstrap/loaders'); const { + getEmbedderEntryFunction, compileSerializeMain, } = binding; @@ -119,14 +120,21 @@ function main() { prepareMainThreadExecution } = require('internal/process/pre_execution'); - prepareMainThreadExecution(true, false); + let serializeMainFunction = getEmbedderEntryFunction(); + const serializeMainArgs = [requireForUserSnapshot]; - const file = process.argv[1]; - const path = require('path'); - const filename = path.resolve(file); - const dirname = path.dirname(filename); - const source = readFileSync(file, 'utf-8'); - const serializeMainFunction = compileSerializeMain(filename, source); + if (serializeMainFunction) { // embedded case + prepareMainThreadExecution(false, false); + } else { + prepareMainThreadExecution(true, false); + const file = process.argv[1]; + const path = require('path'); + const filename = path.resolve(file); + const dirname = path.dirname(filename); + const source = readFileSync(file, 'utf-8'); + serializeMainFunction = compileSerializeMain(filename, source); + serializeMainArgs.push(filename, dirname); + } const { initializeCallbacks, @@ -146,10 +154,9 @@ function main() { if (getOptionValue('--inspect-brk')) { internalBinding('inspector').callAndPauseOnStart( - serializeMainFunction, undefined, - requireForUserSnapshot, filename, dirname); + serializeMainFunction, undefined, ...serializeMainArgs); } else { - serializeMainFunction(requireForUserSnapshot, filename, dirname); + serializeMainFunction(...serializeMainArgs); } addSerializeCallback(() => { diff --git a/src/api/embed_helpers.cc b/src/api/embed_helpers.cc index 3944890e443885..02c4fdf1b53a44 100644 --- a/src/api/embed_helpers.cc +++ b/src/api/embed_helpers.cc @@ -14,6 +14,7 @@ using v8::Locker; using v8::Maybe; using v8::Nothing; using v8::SealHandleScope; +using v8::SnapshotCreator; namespace node { @@ -78,16 +79,18 @@ struct CommonEnvironmentSetup::Impl { MultiIsolatePlatform* platform = nullptr; uv_loop_t loop; std::shared_ptr allocator; + std::optional snapshot_creator; Isolate* isolate = nullptr; DeleteFnPtr isolate_data; DeleteFnPtr env; - Global context; + Global main_context; }; CommonEnvironmentSetup::CommonEnvironmentSetup( MultiIsolatePlatform* platform, std::vector* errors, const EmbedderSnapshotData* snapshot_data, + uint32_t flags, std::function make_env) : impl_(new Impl()) { CHECK_NOT_NULL(platform); @@ -105,28 +108,43 @@ CommonEnvironmentSetup::CommonEnvironmentSetup( } loop->data = this; - impl_->allocator = ArrayBufferAllocator::Create(); - impl_->isolate = - NewIsolate(impl_->allocator, &impl_->loop, platform, snapshot_data); - Isolate* isolate = impl_->isolate; + Isolate* isolate; + if (flags & Flags::kIsForSnapshotting) { + const std::vector& external_references = + SnapshotBuilder::CollectExternalReferences(); + isolate = impl_->isolate = Isolate::Allocate(); + // Must be done before the SnapshotCreator creation so that the + // memory reducer can be initialized. + platform->RegisterIsolate(isolate, loop); + impl_->snapshot_creator.emplace(isolate, external_references.data()); + isolate->SetCaptureStackTraceForUncaughtExceptions( + true, 10, v8::StackTrace::StackTraceOptions::kDetailed); + SetIsolateMiscHandlers(isolate, {}); + } else { + impl_->allocator = ArrayBufferAllocator::Create(); + isolate = impl_->isolate = + NewIsolate(impl_->allocator, &impl_->loop, platform, snapshot_data); + } { Locker locker(isolate); Isolate::Scope isolate_scope(isolate); impl_->isolate_data.reset(CreateIsolateData( isolate, loop, platform, impl_->allocator.get(), snapshot_data)); + impl_->isolate_data->options()->build_snapshot = + impl_->snapshot_creator.has_value(); HandleScope handle_scope(isolate); if (snapshot_data) { impl_->env.reset(make_env(this)); if (impl_->env) { - impl_->context.Reset(isolate, impl_->env->context()); + impl_->main_context.Reset(isolate, impl_->env->context()); } return; } Local context = NewContext(isolate); - impl_->context.Reset(isolate, context); + impl_->main_context.Reset(isolate, context); if (context.IsEmpty()) { errors->push_back("Failed to initialize V8 Context"); return; @@ -141,7 +159,37 @@ CommonEnvironmentSetup::CommonEnvironmentSetup( MultiIsolatePlatform* platform, std::vector* errors, std::function make_env) - : CommonEnvironmentSetup(platform, errors, nullptr, make_env) {} + : CommonEnvironmentSetup(platform, errors, nullptr, false, make_env) {} + +std::unique_ptr +CommonEnvironmentSetup::CreateForSnapshotting( + MultiIsolatePlatform* platform, + std::vector* errors, + const std::vector& args, + const std::vector& exec_args) { + // It's not guaranteed that a context that goes through + // v8_inspector::V8Inspector::contextCreated() is runtime-independent, + // so do not start the inspector on the main context when building + // the default snapshot. + uint64_t env_flags = + EnvironmentFlags::kDefaultFlags | EnvironmentFlags::kNoCreateInspector; + + auto ret = std::unique_ptr(new CommonEnvironmentSetup( + platform, + errors, + nullptr, + true, + [&](const CommonEnvironmentSetup* setup) -> Environment* { + return CreateEnvironment( + setup->isolate_data(), + setup->context(), + args, + exec_args, + static_cast(env_flags)); + })); + if (!errors->empty()) ret.reset(); + return ret; +} CommonEnvironmentSetup::~CommonEnvironmentSetup() { if (impl_->isolate != nullptr) { @@ -150,7 +198,7 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() { Locker locker(isolate); Isolate::Scope isolate_scope(isolate); - impl_->context.Reset(); + impl_->main_context.Reset(); impl_->env.reset(); impl_->isolate_data.reset(); } @@ -160,7 +208,10 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() { *static_cast(data) = true; }, &platform_finished); impl_->platform->UnregisterIsolate(isolate); - isolate->Dispose(); + if (impl_->snapshot_creator.has_value()) + impl_->snapshot_creator.reset(); + else + isolate->Dispose(); // Wait until the platform has cleaned up all relevant resources. while (!platform_finished) @@ -173,6 +224,21 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() { delete impl_; } +EmbedderSnapshotData::Pointer CommonEnvironmentSetup::CreateSnapshot() { + CHECK_NOT_NULL(snapshot_creator()); + SnapshotData* snapshot_data = new SnapshotData(); + EmbedderSnapshotData::Pointer result{ + new EmbedderSnapshotData(snapshot_data, true)}; + + auto exit_code = SnapshotBuilder::CreateSnapshot( + snapshot_data, + this, + static_cast(SnapshotMetadata::Type::kFullyCustomized)); + if (exit_code != ExitCode::kNoFailure) return {}; + + return result; +} + Maybe SpinEventLoop(Environment* env) { Maybe result = SpinEventLoopInternal(env); if (result.IsNothing()) { @@ -203,7 +269,11 @@ Environment* CommonEnvironmentSetup::env() const { } v8::Local CommonEnvironmentSetup::context() const { - return impl_->context.Get(impl_->isolate); + return impl_->main_context.Get(impl_->isolate); +} + +v8::SnapshotCreator* CommonEnvironmentSetup::snapshot_creator() { + return impl_->snapshot_creator ? &impl_->snapshot_creator.value() : nullptr; } void EmbedderSnapshotData::DeleteSnapshotData::operator()( @@ -232,6 +302,10 @@ EmbedderSnapshotData::Pointer EmbedderSnapshotData::FromFile(FILE* in) { return result; } +void EmbedderSnapshotData::ToFile(FILE* out) const { + impl_->ToBlob(out); +} + EmbedderSnapshotData::EmbedderSnapshotData(const SnapshotData* impl, bool owns_impl) : impl_(impl), owns_impl_(owns_impl) {} diff --git a/src/env-inl.h b/src/env-inl.h index 93a454541cc3f0..839833f684a4bb 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -438,6 +438,16 @@ inline builtins::BuiltinLoader* Environment::builtin_loader() { return &builtin_loader_; } +inline const StartExecutionCallback& +Environment::embedder_mksnapshot_entry_point() const { + return embedder_mksnapshot_entry_point_; +} + +inline void Environment::set_embedder_mksnapshot_entry_point( + StartExecutionCallback&& fn) { + embedder_mksnapshot_entry_point_ = std::move(fn); +} + inline double Environment::new_async_id() { async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1; return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter]; diff --git a/src/env.h b/src/env.h index 0133e646b8e5bb..aa0050c580a86b 100644 --- a/src/env.h +++ b/src/env.h @@ -959,6 +959,9 @@ class Environment : public MemoryRetainer { #endif // HAVE_INSPECTOR + inline const StartExecutionCallback& embedder_mksnapshot_entry_point() const; + inline void set_embedder_mksnapshot_entry_point(StartExecutionCallback&& fn); + inline void set_process_exit_handler( std::function&& handler); @@ -1134,6 +1137,7 @@ class Environment : public MemoryRetainer { std::unique_ptr principal_realm_ = nullptr; builtins::BuiltinLoader builtin_loader_; + StartExecutionCallback embedder_mksnapshot_entry_point_; // Used by allocate_managed_buffer() and release_managed_buffer() to keep // track of the BackingStore for a given pointer. diff --git a/src/node.cc b/src/node.cc index af680ed5b658d9..c9fb6afd05b178 100644 --- a/src/node.cc +++ b/src/node.cc @@ -276,14 +276,21 @@ MaybeLocal StartExecution(Environment* env, StartExecutionCallback cb) { if (cb != nullptr) { EscapableHandleScope scope(env->isolate()); - if (StartExecution(env, "internal/main/environment").IsEmpty()) return {}; + if (env->isolate_data()->options()->build_snapshot) { + // TODO(addaleax): pass the callback to the main script more directly, + // e.g. by making StartExecution(env, builtin) parametrizable + env->set_embedder_mksnapshot_entry_point(std::move(cb)); + auto reset_entry_point = + OnScopeLeave([&]() { env->set_embedder_mksnapshot_entry_point({}); }); + + return StartExecution(env, "internal/main/mksnapshot"); + } - StartExecutionCallbackInfo info = { + if (StartExecution(env, "internal/main/environment").IsEmpty()) return {}; + return scope.EscapeMaybe(cb({ env->process_object(), env->builtin_module_require(), - }; - - return scope.EscapeMaybe(cb(info)); + })); } // TODO(joyeecheung): move these conditions into JS land and let the diff --git a/src/node.h b/src/node.h index f536217f5504b4..bc81391c96bb02 100644 --- a/src/node.h +++ b/src/node.h @@ -514,11 +514,16 @@ class EmbedderSnapshotData { 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. + // Calling this method will 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); + // Write this EmbedderSnapshotData object to an output file. + // Calling this method will not close the FILE* handle. + // The FILE* handle can be closed immediately following this call. + void ToFile(FILE* out) const; + // Returns whether custom snapshots can be used. Currently, this means // that V8 was configured without the shared-readonly-heap feature. static bool CanUseCustomSnapshotPerIsolate(); @@ -535,6 +540,7 @@ class EmbedderSnapshotData { const SnapshotData* impl_; bool owns_impl_; friend struct SnapshotData; + friend class CommonEnvironmentSetup; }; // Overriding IsolateSettings may produce unexpected behavior @@ -820,13 +826,35 @@ class NODE_EXTERN CommonEnvironmentSetup { std::vector* errors, EnvironmentArgs&&... env_args); template - static std::unique_ptr CreateWithSnapshot( + static std::unique_ptr CreateFromSnapshot( MultiIsolatePlatform* platform, std::vector* errors, const EmbedderSnapshotData* snapshot_data, EnvironmentArgs&&... env_args); + // Create an embedding setup which will be used for creating a snapshot + // using CreateSnapshot(). + // + // This will create and attach a v8::SnapshotCreator to this instance, + // and the same restrictions apply to this instance that also apply to + // other V8 snapshotting environments. + // Not all Node.js APIs are supported in this case. Currently, there is + // no support for native/host objects other than Node.js builtins + // in the snapshot. + // + // 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. + static std::unique_ptr CreateForSnapshotting( + MultiIsolatePlatform* platform, + std::vector* errors, + const std::vector& args = {}, + const std::vector& exec_args = {}); + EmbedderSnapshotData::Pointer CreateSnapshot(); + struct uv_loop_s* event_loop() const; + v8::SnapshotCreator* snapshot_creator(); + // Empty for snapshotting environments. std::shared_ptr array_buffer_allocator() const; v8::Isolate* isolate() const; IsolateData* isolate_data() const; @@ -839,8 +867,14 @@ class NODE_EXTERN CommonEnvironmentSetup { CommonEnvironmentSetup& operator=(CommonEnvironmentSetup&&) = delete; private: + enum Flags : uint32_t { + kNoFlags = 0, + kIsForSnapshotting = 1, + }; + struct Impl; Impl* impl_; + CommonEnvironmentSetup( MultiIsolatePlatform*, std::vector*, @@ -849,6 +883,7 @@ class NODE_EXTERN CommonEnvironmentSetup { MultiIsolatePlatform*, std::vector*, const EmbedderSnapshotData*, + uint32_t flags, std::function); }; @@ -868,11 +903,11 @@ std::unique_ptr CommonEnvironmentSetup::Create( if (!errors->empty()) ret.reset(); return ret; } -// Implementation for ::CreateWithSnapshot -- the ::Create() method +// Implementation for ::CreateFromSnapshot -- the ::Create() method // could call this with a nullptr snapshot_data in a major version. template std::unique_ptr -CommonEnvironmentSetup::CreateWithSnapshot( +CommonEnvironmentSetup::CreateFromSnapshot( MultiIsolatePlatform* platform, std::vector* errors, const EmbedderSnapshotData* snapshot_data, @@ -881,6 +916,7 @@ CommonEnvironmentSetup::CreateWithSnapshot( platform, errors, snapshot_data, + Flags::kNoFlags, [&](const CommonEnvironmentSetup* setup) -> Environment* { return CreateEnvironment(setup->isolate_data(), setup->context(), diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index 81804656c51093..5a4d127ffe3e43 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -29,34 +29,6 @@ using v8::Isolate; using v8::Local; using v8::Locker; -NodeMainInstance::NodeMainInstance(Isolate* isolate, - uv_loop_t* event_loop, - MultiIsolatePlatform* platform, - const std::vector& args, - const std::vector& exec_args) - : args_(args), - exec_args_(exec_args), - array_buffer_allocator_(nullptr), - isolate_(isolate), - platform_(platform), - isolate_data_(nullptr), - snapshot_data_(nullptr) { - isolate_data_ = - std::make_unique(isolate_, event_loop, platform, nullptr); - - SetIsolateMiscHandlers(isolate_, {}); -} - -std::unique_ptr NodeMainInstance::Create( - Isolate* isolate, - uv_loop_t* event_loop, - MultiIsolatePlatform* platform, - const std::vector& args, - const std::vector& exec_args) { - return std::unique_ptr( - new NodeMainInstance(isolate, event_loop, platform, args, exec_args)); -} - NodeMainInstance::NodeMainInstance(const SnapshotData* snapshot_data, uv_loop_t* event_loop, MultiIsolatePlatform* platform, @@ -88,13 +60,6 @@ NodeMainInstance::NodeMainInstance(const SnapshotData* snapshot_data, isolate_params_->constraints.max_young_generation_size_in_bytes(); } -void NodeMainInstance::Dispose() { - // This should only be called on a main instance that does not own its - // isolate. - CHECK_NULL(isolate_params_); - platform_->DrainTasks(isolate_); -} - NodeMainInstance::~NodeMainInstance() { if (isolate_params_ == nullptr) { return; diff --git a/src/node_main_instance.h b/src/node_main_instance.h index 43b6a8d64e409f..2752ff4d0ebc07 100644 --- a/src/node_main_instance.h +++ b/src/node_main_instance.h @@ -22,33 +22,6 @@ struct SnapshotData; // We may be able to create an abstract class to reuse some of the routines. class NodeMainInstance { public: - // To create a main instance that does not own the isolate, - // The caller needs to do: - // - // Isolate* isolate = Isolate::Allocate(); - // platform->RegisterIsolate(isolate, loop); - // isolate->Initialize(...); - // isolate->Enter(); - // std::unique_ptr main_instance = - // NodeMainInstance::Create(isolate, loop, args, exec_args); - // - // When tearing it down: - // - // main_instance->Cleanup(); // While the isolate is entered - // isolate->Exit(); - // isolate->Dispose(); - // platform->UnregisterIsolate(isolate); - // - // After calling Dispose() the main_instance is no longer accessible. - static std::unique_ptr Create( - v8::Isolate* isolate, - uv_loop_t* event_loop, - MultiIsolatePlatform* platform, - const std::vector& args, - const std::vector& exec_args); - - void Dispose(); - // Create a main instance that owns the isolate NodeMainInstance(const SnapshotData* snapshot_data, uv_loop_t* event_loop, diff --git a/src/node_options.cc b/src/node_options.cc index e9c5ac2b190728..c1f97a5d9207eb 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -778,8 +778,7 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( Implies("--harmony-shadow-realm", "--experimental-shadow-realm"); ImpliesNot("--no-harmony-shadow-realm", "--experimental-shadow-realm"); AddOption("--build-snapshot", - "Generate a snapshot blob when the process exits." - " Currently only supported in the node_mksnapshot binary.", + "Generate a snapshot blob when the process exits.", &PerIsolateOptions::build_snapshot, kDisallowedInEnvvar); diff --git a/src/node_snapshot_builder.h b/src/node_snapshot_builder.h index 3367cd3d7a6c68..f8cd900b2bdaa4 100644 --- a/src/node_snapshot_builder.h +++ b/src/node_snapshot_builder.h @@ -31,9 +31,14 @@ class NODE_EXTERN_PRIVATE SnapshotBuilder { static void InitializeIsolateParams(const SnapshotData* data, v8::Isolate::CreateParams* params); - private: static const std::vector& CollectExternalReferences(); + static ExitCode CreateSnapshot( + SnapshotData* out, + CommonEnvironmentSetup* setup, + /*SnapshotMetadata::Type*/ uint8_t snapshot_type); + + private: static std::unique_ptr registry_; }; } // namespace node diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index aec96199130c6b..598519c7ba03b1 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -33,6 +33,7 @@ using v8::FunctionCallbackInfo; using v8::HandleScope; using v8::Isolate; using v8::Local; +using v8::MaybeLocal; using v8::Object; using v8::ObjectTemplate; using v8::ScriptCompiler; @@ -1101,38 +1102,15 @@ void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data, ExitCode SnapshotBuilder::Generate(SnapshotData* out, const std::vector args, const std::vector exec_args) { - const std::vector& external_references = - CollectExternalReferences(); - Isolate* isolate = Isolate::Allocate(); - // Must be done before the SnapshotCreator creation so that the - // memory reducer can be initialized. - per_process::v8_platform.Platform()->RegisterIsolate(isolate, - uv_default_loop()); - - SnapshotCreator creator(isolate, external_references.data()); - - isolate->SetCaptureStackTraceForUncaughtExceptions( - true, 10, v8::StackTrace::StackTraceOptions::kDetailed); - - Environment* env = nullptr; - std::unique_ptr main_instance = - NodeMainInstance::Create(isolate, - uv_default_loop(), - per_process::v8_platform.Platform(), - args, - exec_args); - - // The cleanups should be done in case of an early exit due to errors. - auto cleanup = OnScopeLeave([&]() { - // Must be done while the snapshot creator isolate is entered i.e. the - // creator is still alive. The snapshot creator destructor will destroy - // the isolate. - if (env != nullptr) { - FreeEnvironment(env); - } - main_instance->Dispose(); - per_process::v8_platform.Platform()->UnregisterIsolate(isolate); - }); + std::vector errors; + auto setup = CommonEnvironmentSetup::CreateForSnapshotting( + per_process::v8_platform.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()); + return ExitCode::kBootstrapFailure; + } + Isolate* isolate = setup->isolate(); // It's only possible to be kDefault in node_mksnapshot. SnapshotMetadata::Type snapshot_type = @@ -1151,6 +1129,51 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out, } }); + // Initialize the main instance context. + { + Context::Scope context_scope(setup->context()); + Environment* env = setup->env(); + + // If --build-snapshot is true, lib/internal/main/mksnapshot.js would be + // loaded via LoadEnvironment() to execute process.argv[1] as the entry + // point (we currently only support this kind of entry point, but we + // could also explore snapshotting other kinds of execution modes + // in the future). + if (snapshot_type == SnapshotMetadata::Type::kFullyCustomized) { +#if HAVE_INSPECTOR + env->InitializeInspector({}); +#endif + if (LoadEnvironment(env, StartExecutionCallback{}).IsEmpty()) { + return ExitCode::kGenericUserError; + } + // FIXME(joyeecheung): right now running the loop in the snapshot + // builder seems to introduces inconsistencies in JS land that need to + // be synchronized again after snapshot restoration. + ExitCode exit_code = + SpinEventLoopInternal(env).FromMaybe(ExitCode::kGenericUserError); + if (exit_code != ExitCode::kNoFailure) { + return exit_code; + } + } + } + } + + return CreateSnapshot(out, setup.get(), static_cast(snapshot_type)); +} + +ExitCode SnapshotBuilder::CreateSnapshot(SnapshotData* out, + CommonEnvironmentSetup* setup, + uint8_t snapshot_type_u8) { + SnapshotMetadata::Type snapshot_type = + static_cast(snapshot_type_u8); + Isolate* isolate = setup->isolate(); + Environment* env = setup->env(); + SnapshotCreator* creator = setup->snapshot_creator(); + + { + HandleScope scope(isolate); + Local main_context = setup->context(); + // The default context with only things created by V8. Local default_context = Context::New(isolate); @@ -1158,7 +1181,7 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out, Local vm_context; { Local global_template = - main_instance->isolate_data()->contextify_global_template(); + setup->isolate_data()->contextify_global_template(); CHECK(!global_template.IsEmpty()); if (!contextify::ContextifyContext::CreateV8Context( isolate, global_template, nullptr, nullptr) @@ -1176,63 +1199,17 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out, } ResetContextSettingsBeforeSnapshot(base_context); - Local main_context = NewContext(isolate); - if (main_context.IsEmpty()) { - return ExitCode::kBootstrapFailure; - } - // Initialize the main instance context. { Context::Scope context_scope(main_context); - // Create the environment. - // It's not guaranteed that a context that goes through - // v8_inspector::V8Inspector::contextCreated() is runtime-independent, - // so do not start the inspector on the main context when building - // the default snapshot. - uint64_t env_flags = EnvironmentFlags::kDefaultFlags | - EnvironmentFlags::kNoCreateInspector; - - env = CreateEnvironment(main_instance->isolate_data(), - main_context, - args, - exec_args, - static_cast(env_flags)); - - // This already ran scripts in lib/internal/bootstrap/, if it fails return - if (env == nullptr) { - return ExitCode::kBootstrapFailure; - } - // If --build-snapshot is true, lib/internal/main/mksnapshot.js would be - // loaded via LoadEnvironment() to execute process.argv[1] as the entry - // point (we currently only support this kind of entry point, but we - // could also explore snapshotting other kinds of execution modes - // in the future). - if (snapshot_type == SnapshotMetadata::Type::kFullyCustomized) { -#if HAVE_INSPECTOR - env->InitializeInspector({}); -#endif - if (LoadEnvironment(env, StartExecutionCallback{}).IsEmpty()) { - return ExitCode::kGenericUserError; - } - // FIXME(joyeecheung): right now running the loop in the snapshot - // builder seems to introduces inconsistencies in JS land that need to - // be synchronized again after snapshot restoration. - ExitCode exit_code = - SpinEventLoopInternal(env).FromMaybe(ExitCode::kGenericUserError); - if (exit_code != ExitCode::kNoFailure) { - return exit_code; - } - } - if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) { env->ForEachRealm([](Realm* realm) { realm->PrintInfoForSnapshot(); }); printf("Environment = %p\n", env); } // Serialize the native states - out->isolate_data_info = - main_instance->isolate_data()->Serialize(&creator); - out->env_info = env->Serialize(&creator); + out->isolate_data_info = setup->isolate_data()->Serialize(creator); + out->env_info = env->Serialize(creator); #ifdef NODE_USE_NODE_CODE_CACHE // Regenerate all the code cache. @@ -1255,19 +1232,19 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out, // Global handles to the contexts can't be disposed before the // blob is created. So initialize all the contexts before adding them. // TODO(joyeecheung): figure out how to remove this restriction. - creator.SetDefaultContext(default_context); - size_t index = creator.AddContext(vm_context); + creator->SetDefaultContext(default_context); + size_t index = creator->AddContext(vm_context); CHECK_EQ(index, SnapshotData::kNodeVMContextIndex); - index = creator.AddContext(base_context); + index = creator->AddContext(base_context); CHECK_EQ(index, SnapshotData::kNodeBaseContextIndex); - index = creator.AddContext(main_context, - {SerializeNodeContextInternalFields, env}); + index = creator->AddContext(main_context, + {SerializeNodeContextInternalFields, env}); CHECK_EQ(index, SnapshotData::kNodeMainContextIndex); } // Must be out of HandleScope out->v8_snapshot_blob_data = - creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kKeep); + creator->CreateBlob(SnapshotCreator::FunctionCodeHandling::kKeep); // We must be able to rehash the blob when we restore it or otherwise // the hash seed would be fixed by V8, introducing a vulnerability. @@ -1468,6 +1445,23 @@ void SerializeSnapshotableObjects(Realm* realm, namespace mksnapshot { +void GetEmbedderEntryFunction(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + if (!env->embedder_mksnapshot_entry_point()) return; + MaybeLocal jsfn = + Function::New(isolate->GetCurrentContext(), + [](const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Local require_fn = args[0]; + CHECK(require_fn->IsFunction()); + CHECK(env->embedder_mksnapshot_entry_point()); + env->embedder_mksnapshot_entry_point()( + {env->process_object(), require_fn.As()}); + }); + if (!jsfn.IsEmpty()) args.GetReturnValue().Set(jsfn.ToLocalChecked()); +} + void CompileSerializeMain(const FunctionCallbackInfo& args) { CHECK(args[0]->IsString()); Local filename = args[0].As(); @@ -1521,6 +1515,8 @@ void Initialize(Local target, Local unused, Local context, void* priv) { + SetMethod( + context, target, "getEmbedderEntryFunction", GetEmbedderEntryFunction); SetMethod(context, target, "compileSerializeMain", CompileSerializeMain); SetMethod(context, target, "setSerializeCallback", SetSerializeCallback); SetMethod(context, target, "setDeserializeCallback", SetDeserializeCallback); @@ -1531,6 +1527,7 @@ void Initialize(Local target, } void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + registry->Register(GetEmbedderEntryFunction); registry->Register(CompileSerializeMain); registry->Register(SetSerializeCallback); registry->Register(SetDeserializeCallback); diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc index ea3af217319ef7..ef07056d5bd688 100644 --- a/test/embedding/embedtest.cc +++ b/test/embedding/embedtest.cc @@ -1,3 +1,6 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif #include "node.h" #include "uv.h" #include @@ -58,9 +61,12 @@ int RunNodeInstance(MultiIsolatePlatform* platform, int exit_code = 0; node::EmbedderSnapshotData::Pointer snapshot; + auto snapshot_build_mode_it = + std::find(args.begin(), args.end(), "--embedder-snapshot-create"); auto snapshot_arg_it = std::find(args.begin(), args.end(), "--embedder-snapshot-blob"); - if (snapshot_arg_it < args.end() - 1) { + if (snapshot_arg_it < args.end() - 1 && + snapshot_build_mode_it == args.end()) { FILE* fp = fopen((snapshot_arg_it + 1)->c_str(), "r"); assert(fp != nullptr); snapshot = node::EmbedderSnapshotData::FromFile(fp); @@ -69,9 +75,11 @@ int RunNodeInstance(MultiIsolatePlatform* platform, std::vector errors; std::unique_ptr setup = - snapshot - ? CommonEnvironmentSetup::CreateWithSnapshot( - platform, &errors, snapshot.get(), args, exec_args) + snapshot ? CommonEnvironmentSetup::CreateFromSnapshot( + platform, &errors, snapshot.get(), args, exec_args) + : snapshot_build_mode_it != args.end() + ? CommonEnvironmentSetup::CreateForSnapshotting( + platform, &errors, args, exec_args) : CommonEnvironmentSetup::Create(platform, &errors, args, exec_args); if (!setup) { for (const std::string& err : errors) @@ -94,9 +102,12 @@ int RunNodeInstance(MultiIsolatePlatform* platform, } else { loadenv_ret = node::LoadEnvironment( env, - "const publicRequire =" - " require('module').createRequire(process.cwd() + '/');" - "globalThis.require = publicRequire;" + // Snapshots do not support userland require()s (yet) + "if (!require('v8').startupSnapshot.isBuildingSnapshot()) {" + " const publicRequire =" + " require('module').createRequire(process.cwd() + '/');" + " globalThis.require = publicRequire;" + "} else globalThis.require = require;" "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };" "require('vm').runInThisContext(process.argv[1]);"); } @@ -105,9 +116,20 @@ int RunNodeInstance(MultiIsolatePlatform* platform, return 1; exit_code = node::SpinEventLoop(env).FromMaybe(1); + } + + if (snapshot_arg_it < args.end() - 1 && + snapshot_build_mode_it != args.end()) { + snapshot = setup->CreateSnapshot(); + assert(snapshot); - node::Stop(env); + FILE* fp = fopen((snapshot_arg_it + 1)->c_str(), "w"); + assert(fp != nullptr); + snapshot->ToFile(fp); + fclose(fp); } + node::Stop(env); + return exit_code; } diff --git a/test/embedding/test-embedding.js b/test/embedding/test-embedding.js index c21c39b28e99dd..197158e78f2e03 100644 --- a/test/embedding/test-embedding.js +++ b/test/embedding/test-embedding.js @@ -20,7 +20,6 @@ function resolveBuiltBinary(bin) { } const binary = resolveBuiltBinary('embedtest'); -const standaloneNodeBinary = resolveBuiltBinary('node'); assert.strictEqual( child_process.spawnSync(binary, ['console.log(42)']) @@ -51,22 +50,30 @@ assert.strictEqual( child_process.spawnSync(binary, [`require(${fixturePath})`, 92]).status, 92); +function getReadFileCodeForPath(path) { + return `(require("fs").readFileSync(${JSON.stringify(path)}, "utf8"))`; +} + // Basic snapshot support { + // readSync + eval since snapshots don't support userland require() (yet) const snapshotFixture = fixtures.path('snapshot', 'echo-args.js'); const blobPath = path.join(tmpdir.path, 'embedder-snapshot.blob'); - const buildSnapshotArgs = [snapshotFixture, 'arg1', 'arg2']; + const buildSnapshotArgs = [ + `eval(${getReadFileCodeForPath(snapshotFixture)})`, 'arg1', 'arg2', + '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', + ]; 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, + assert.strictEqual(child_process.spawnSync(binary, [ + '--', ...buildSnapshotArgs, ], { cwd: tmpdir.path, }).status, 0); const spawnResult = child_process.spawnSync(binary, ['--', ...runEmbeddedArgs]); assert.deepStrictEqual(JSON.parse(spawnResult.stdout), { - originalArgv: [standaloneNodeBinary, ...buildSnapshotArgs], + originalArgv: [binary, ...buildSnapshotArgs], currentArgv: [binary, ...runEmbeddedArgs], }); } @@ -75,10 +82,14 @@ assert.strictEqual( { const snapshotFixture = fixtures.path('snapshot', 'create-worker-and-vm.js'); const blobPath = path.join(tmpdir.path, 'embedder-snapshot.blob'); + const buildSnapshotArgs = [ + `eval(${getReadFileCodeForPath(snapshotFixture)})`, + '--embedder-snapshot-blob', blobPath, '--embedder-snapshot-create', + ]; fs.rmSync(blobPath, { force: true }); - assert.strictEqual(child_process.spawnSync(standaloneNodeBinary, [ - '--snapshot-blob', blobPath, '--build-snapshot', snapshotFixture, + assert.strictEqual(child_process.spawnSync(binary, [ + '--', ...buildSnapshotArgs, ], { cwd: tmpdir.path, }).status, 0);