Skip to content

Commit 06bb6b4

Browse files
addaleaxnodejs-github-bot
authored andcommitted
src: add snapshot support for embedder API
Add experimental support for loading snapshots in the embedder API by adding a public opaque wrapper for our `SnapshotData` struct and allowing embedders to pass it to the relevant setup functions. Where applicable, use these helpers to deduplicate existing code in Node.js’s startup path. This has shown a 40 % startup performance increase for a real-world application, even with the somewhat limited current support for built-in modules. The documentation includes a note about no guarantees for API or ABI stability for this feature while it is experimental. PR-URL: #45888 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent d523bfe commit 06bb6b4

15 files changed

+433
-110
lines changed

src/api/embed_helpers.cc

+60-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
#include "node.h"
2-
#include "env-inl.h"
31
#include "debug_utils-inl.h"
2+
#include "env-inl.h"
3+
#include "node.h"
4+
#include "node_snapshot_builder.h"
45

56
using v8::Context;
67
using v8::Function;
@@ -86,8 +87,9 @@ struct CommonEnvironmentSetup::Impl {
8687
CommonEnvironmentSetup::CommonEnvironmentSetup(
8788
MultiIsolatePlatform* platform,
8889
std::vector<std::string>* errors,
90+
const EmbedderSnapshotData* snapshot_data,
8991
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
90-
: impl_(new Impl()) {
92+
: impl_(new Impl()) {
9193
CHECK_NOT_NULL(platform);
9294
CHECK_NOT_NULL(errors);
9395

@@ -104,16 +106,25 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
104106
loop->data = this;
105107

106108
impl_->allocator = ArrayBufferAllocator::Create();
107-
impl_->isolate = NewIsolate(impl_->allocator, &impl_->loop, platform);
109+
impl_->isolate =
110+
NewIsolate(impl_->allocator, &impl_->loop, platform, snapshot_data);
108111
Isolate* isolate = impl_->isolate;
109112

110113
{
111114
Locker locker(isolate);
112115
Isolate::Scope isolate_scope(isolate);
113116
impl_->isolate_data.reset(CreateIsolateData(
114-
isolate, loop, platform, impl_->allocator.get()));
117+
isolate, loop, platform, impl_->allocator.get(), snapshot_data));
115118

116119
HandleScope handle_scope(isolate);
120+
if (snapshot_data) {
121+
impl_->env.reset(make_env(this));
122+
if (impl_->env) {
123+
impl_->context.Reset(isolate, impl_->env->context());
124+
}
125+
return;
126+
}
127+
117128
Local<Context> context = NewContext(isolate);
118129
impl_->context.Reset(isolate, context);
119130
if (context.IsEmpty()) {
@@ -126,6 +137,12 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
126137
}
127138
}
128139

140+
CommonEnvironmentSetup::CommonEnvironmentSetup(
141+
MultiIsolatePlatform* platform,
142+
std::vector<std::string>* errors,
143+
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
144+
: CommonEnvironmentSetup(platform, errors, nullptr, make_env) {}
145+
129146
CommonEnvironmentSetup::~CommonEnvironmentSetup() {
130147
if (impl_->isolate != nullptr) {
131148
Isolate* isolate = impl_->isolate;
@@ -189,4 +206,42 @@ v8::Local<v8::Context> CommonEnvironmentSetup::context() const {
189206
return impl_->context.Get(impl_->isolate);
190207
}
191208

209+
void EmbedderSnapshotData::DeleteSnapshotData::operator()(
210+
const EmbedderSnapshotData* data) const {
211+
CHECK_IMPLIES(data->owns_impl_, data->impl_);
212+
if (data->owns_impl_ &&
213+
data->impl_->data_ownership == SnapshotData::DataOwnership::kOwned) {
214+
delete data->impl_;
215+
}
216+
delete data;
217+
}
218+
219+
EmbedderSnapshotData::Pointer EmbedderSnapshotData::BuiltinSnapshotData() {
220+
return EmbedderSnapshotData::Pointer{new EmbedderSnapshotData(
221+
SnapshotBuilder::GetEmbeddedSnapshotData(), false)};
222+
}
223+
224+
EmbedderSnapshotData::Pointer EmbedderSnapshotData::FromFile(FILE* in) {
225+
SnapshotData* snapshot_data = new SnapshotData();
226+
CHECK_EQ(snapshot_data->data_ownership, SnapshotData::DataOwnership::kOwned);
227+
EmbedderSnapshotData::Pointer result{
228+
new EmbedderSnapshotData(snapshot_data, true)};
229+
if (!SnapshotData::FromBlob(snapshot_data, in)) {
230+
return {};
231+
}
232+
return result;
233+
}
234+
235+
EmbedderSnapshotData::EmbedderSnapshotData(const SnapshotData* impl,
236+
bool owns_impl)
237+
: impl_(impl), owns_impl_(owns_impl) {}
238+
239+
bool EmbedderSnapshotData::CanUseCustomSnapshotPerIsolate() {
240+
#ifdef NODE_V8_SHARED_RO_HEAP
241+
return false;
242+
#else
243+
return true;
244+
#endif
245+
}
246+
192247
} // namespace node

src/api/environment.cc

+92-14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "node_platform.h"
1010
#include "node_realm-inl.h"
1111
#include "node_shadow_realm.h"
12+
#include "node_snapshot_builder.h"
1213
#include "node_v8_platform-inl.h"
1314
#include "node_wasm_web_api.h"
1415
#include "uv.h"
@@ -315,9 +316,15 @@ void SetIsolateUpForNode(v8::Isolate* isolate) {
315316
Isolate* NewIsolate(Isolate::CreateParams* params,
316317
uv_loop_t* event_loop,
317318
MultiIsolatePlatform* platform,
318-
bool has_snapshot_data) {
319+
const SnapshotData* snapshot_data,
320+
const IsolateSettings& settings) {
319321
Isolate* isolate = Isolate::Allocate();
320322
if (isolate == nullptr) return nullptr;
323+
324+
if (snapshot_data != nullptr) {
325+
SnapshotBuilder::InitializeIsolateParams(snapshot_data, params);
326+
}
327+
321328
#ifdef NODE_V8_SHARED_RO_HEAP
322329
{
323330
// In shared-readonly-heap mode, V8 requires all snapshots used for
@@ -336,38 +343,73 @@ Isolate* NewIsolate(Isolate::CreateParams* params,
336343

337344
SetIsolateCreateParamsForNode(params);
338345
Isolate::Initialize(isolate, *params);
339-
if (!has_snapshot_data) {
346+
if (snapshot_data == nullptr) {
340347
// If in deserialize mode, delay until after the deserialization is
341348
// complete.
342-
SetIsolateUpForNode(isolate);
349+
SetIsolateUpForNode(isolate, settings);
343350
} else {
344-
SetIsolateMiscHandlers(isolate, {});
351+
SetIsolateMiscHandlers(isolate, settings);
345352
}
346353

347354
return isolate;
348355
}
349356

350357
Isolate* NewIsolate(ArrayBufferAllocator* allocator,
351358
uv_loop_t* event_loop,
352-
MultiIsolatePlatform* platform) {
359+
MultiIsolatePlatform* platform,
360+
const EmbedderSnapshotData* snapshot_data,
361+
const IsolateSettings& settings) {
353362
Isolate::CreateParams params;
354363
if (allocator != nullptr) params.array_buffer_allocator = allocator;
355-
return NewIsolate(&params, event_loop, platform);
364+
return NewIsolate(&params,
365+
event_loop,
366+
platform,
367+
SnapshotData::FromEmbedderWrapper(snapshot_data),
368+
settings);
356369
}
357370

358371
Isolate* NewIsolate(std::shared_ptr<ArrayBufferAllocator> allocator,
359372
uv_loop_t* event_loop,
360-
MultiIsolatePlatform* platform) {
373+
MultiIsolatePlatform* platform,
374+
const EmbedderSnapshotData* snapshot_data,
375+
const IsolateSettings& settings) {
361376
Isolate::CreateParams params;
362377
if (allocator) params.array_buffer_allocator_shared = allocator;
363-
return NewIsolate(&params, event_loop, platform);
378+
return NewIsolate(&params,
379+
event_loop,
380+
platform,
381+
SnapshotData::FromEmbedderWrapper(snapshot_data),
382+
settings);
383+
}
384+
385+
Isolate* NewIsolate(ArrayBufferAllocator* allocator,
386+
uv_loop_t* event_loop,
387+
MultiIsolatePlatform* platform) {
388+
return NewIsolate(allocator, event_loop, platform, nullptr);
389+
}
390+
391+
Isolate* NewIsolate(std::shared_ptr<ArrayBufferAllocator> allocator,
392+
uv_loop_t* event_loop,
393+
MultiIsolatePlatform* platform) {
394+
return NewIsolate(allocator, event_loop, platform, nullptr);
395+
}
396+
397+
IsolateData* CreateIsolateData(
398+
Isolate* isolate,
399+
uv_loop_t* loop,
400+
MultiIsolatePlatform* platform,
401+
ArrayBufferAllocator* allocator,
402+
const EmbedderSnapshotData* embedder_snapshot_data) {
403+
const SnapshotData* snapshot_data =
404+
SnapshotData::FromEmbedderWrapper(embedder_snapshot_data);
405+
return new IsolateData(isolate, loop, platform, allocator, snapshot_data);
364406
}
365407

366408
IsolateData* CreateIsolateData(Isolate* isolate,
367409
uv_loop_t* loop,
368410
MultiIsolatePlatform* platform,
369411
ArrayBufferAllocator* allocator) {
370-
return new IsolateData(isolate, loop, platform, allocator);
412+
return CreateIsolateData(isolate, loop, platform, allocator, nullptr);
371413
}
372414

373415
void FreeIsolateData(IsolateData* isolate_data) {
@@ -395,13 +437,45 @@ Environment* CreateEnvironment(
395437
EnvironmentFlags::Flags flags,
396438
ThreadId thread_id,
397439
std::unique_ptr<InspectorParentHandle> inspector_parent_handle) {
398-
Isolate* isolate = context->GetIsolate();
440+
Isolate* isolate = isolate_data->isolate();
399441
HandleScope handle_scope(isolate);
400-
Context::Scope context_scope(context);
442+
443+
const bool use_snapshot = context.IsEmpty();
444+
const EnvSerializeInfo* env_snapshot_info = nullptr;
445+
if (use_snapshot) {
446+
CHECK_NOT_NULL(isolate_data->snapshot_data());
447+
env_snapshot_info = &isolate_data->snapshot_data()->env_info;
448+
}
449+
401450
// TODO(addaleax): This is a much better place for parsing per-Environment
402451
// options than the global parse call.
403-
Environment* env = new Environment(
404-
isolate_data, context, args, exec_args, nullptr, flags, thread_id);
452+
Environment* env = new Environment(isolate_data,
453+
isolate,
454+
args,
455+
exec_args,
456+
env_snapshot_info,
457+
flags,
458+
thread_id);
459+
CHECK_NOT_NULL(env);
460+
461+
if (use_snapshot) {
462+
context = Context::FromSnapshot(isolate,
463+
SnapshotData::kNodeMainContextIndex,
464+
{DeserializeNodeInternalFields, env})
465+
.ToLocalChecked();
466+
467+
CHECK(!context.IsEmpty());
468+
Context::Scope context_scope(context);
469+
470+
if (InitializeContextRuntime(context).IsNothing()) {
471+
FreeEnvironment(env);
472+
return nullptr;
473+
}
474+
SetIsolateErrorHandlers(isolate, {});
475+
}
476+
477+
Context::Scope context_scope(context);
478+
env->InitializeMainContext(context, env_snapshot_info);
405479

406480
#if HAVE_INSPECTOR
407481
if (env->should_create_inspector()) {
@@ -415,7 +489,7 @@ Environment* CreateEnvironment(
415489
}
416490
#endif
417491

418-
if (env->principal_realm()->RunBootstrapping().IsEmpty()) {
492+
if (!use_snapshot && env->principal_realm()->RunBootstrapping().IsEmpty()) {
419493
FreeEnvironment(env);
420494
return nullptr;
421495
}
@@ -500,6 +574,10 @@ ArrayBufferAllocator* GetArrayBufferAllocator(IsolateData* isolate_data) {
500574
return isolate_data->node_allocator();
501575
}
502576

577+
Local<Context> GetMainContext(Environment* env) {
578+
return env->context();
579+
}
580+
503581
MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env) {
504582
return GetMultiIsolatePlatform(env->isolate_data());
505583
}

src/env-inl.h

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ inline MultiIsolatePlatform* IsolateData::platform() const {
6969
return platform_;
7070
}
7171

72+
inline const SnapshotData* IsolateData::snapshot_data() const {
73+
return snapshot_data_;
74+
}
75+
7276
inline void IsolateData::set_worker_context(worker::Worker* context) {
7377
CHECK_NULL(worker_context_); // Should be set only once.
7478
worker_context_ = context;

src/env.cc

+17-24
Original file line numberDiff line numberDiff line change
@@ -472,19 +472,20 @@ IsolateData::IsolateData(Isolate* isolate,
472472
uv_loop_t* event_loop,
473473
MultiIsolatePlatform* platform,
474474
ArrayBufferAllocator* node_allocator,
475-
const IsolateDataSerializeInfo* isolate_data_info)
475+
const SnapshotData* snapshot_data)
476476
: isolate_(isolate),
477477
event_loop_(event_loop),
478478
node_allocator_(node_allocator == nullptr ? nullptr
479479
: node_allocator->GetImpl()),
480-
platform_(platform) {
480+
platform_(platform),
481+
snapshot_data_(snapshot_data) {
481482
options_.reset(
482483
new PerIsolateOptions(*(per_process::cli_options->per_isolate)));
483484

484-
if (isolate_data_info == nullptr) {
485+
if (snapshot_data == nullptr) {
485486
CreateProperties();
486487
} else {
487-
DeserializeProperties(isolate_data_info);
488+
DeserializeProperties(&snapshot_data->isolate_data_info);
488489
}
489490
}
490491

@@ -675,14 +676,23 @@ Environment::Environment(IsolateData* isolate_data,
675676
thread_id_(thread_id.id == static_cast<uint64_t>(-1)
676677
? AllocateEnvironmentThreadId().id
677678
: thread_id.id) {
679+
constexpr bool is_shared_ro_heap =
678680
#ifdef NODE_V8_SHARED_RO_HEAP
679-
if (!is_main_thread()) {
681+
true;
682+
#else
683+
false;
684+
#endif
685+
if (is_shared_ro_heap && !is_main_thread()) {
686+
// If this is a Worker thread and we are in shared-readonly-heap mode,
687+
// we can always safely use the parent's Isolate's code cache.
680688
CHECK_NOT_NULL(isolate_data->worker_context());
681-
// TODO(addaleax): Adjust for the embedder API snapshot support changes
682689
builtin_loader()->CopySourceAndCodeCacheReferenceFrom(
683690
isolate_data->worker_context()->env()->builtin_loader());
691+
} else if (isolate_data->snapshot_data() != nullptr) {
692+
// ... otherwise, if a snapshot was provided, use its code cache.
693+
builtin_loader()->RefreshCodeCache(
694+
isolate_data->snapshot_data()->code_cache);
684695
}
685-
#endif
686696

687697
// We'll be creating new objects so make sure we've entered the context.
688698
HandleScope handle_scope(isolate);
@@ -747,23 +757,6 @@ Environment::Environment(IsolateData* isolate_data,
747757
}
748758
}
749759

750-
Environment::Environment(IsolateData* isolate_data,
751-
Local<Context> context,
752-
const std::vector<std::string>& args,
753-
const std::vector<std::string>& exec_args,
754-
const EnvSerializeInfo* env_info,
755-
EnvironmentFlags::Flags flags,
756-
ThreadId thread_id)
757-
: Environment(isolate_data,
758-
context->GetIsolate(),
759-
args,
760-
exec_args,
761-
env_info,
762-
flags,
763-
thread_id) {
764-
InitializeMainContext(context, env_info);
765-
}
766-
767760
void Environment::InitializeMainContext(Local<Context> context,
768761
const EnvSerializeInfo* env_info) {
769762
principal_realm_ = std::make_unique<Realm>(

0 commit comments

Comments
 (0)