Skip to content

Commit af893f5

Browse files
committed
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.
1 parent 022e65f commit af893f5

15 files changed

+421
-102
lines changed

src/api/embed_helpers.cc

Lines changed: 60 additions & 5 deletions
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

Lines changed: 92 additions & 14 deletions
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"
@@ -307,9 +308,15 @@ void SetIsolateUpForNode(v8::Isolate* isolate) {
307308
Isolate* NewIsolate(Isolate::CreateParams* params,
308309
uv_loop_t* event_loop,
309310
MultiIsolatePlatform* platform,
310-
bool has_snapshot_data) {
311+
const SnapshotData* snapshot_data,
312+
const IsolateSettings& settings) {
311313
Isolate* isolate = Isolate::Allocate();
312314
if (isolate == nullptr) return nullptr;
315+
316+
if (snapshot_data != nullptr) {
317+
SnapshotBuilder::InitializeIsolateParams(snapshot_data, params);
318+
}
319+
313320
#ifdef NODE_V8_SHARED_RO_HEAP
314321
{
315322
// In shared-readonly-heap mode, V8 requires all snapshots used for
@@ -328,38 +335,73 @@ Isolate* NewIsolate(Isolate::CreateParams* params,
328335

329336
SetIsolateCreateParamsForNode(params);
330337
Isolate::Initialize(isolate, *params);
331-
if (!has_snapshot_data) {
338+
if (snapshot_data == nullptr) {
332339
// If in deserialize mode, delay until after the deserialization is
333340
// complete.
334-
SetIsolateUpForNode(isolate);
341+
SetIsolateUpForNode(isolate, settings);
335342
} else {
336-
SetIsolateMiscHandlers(isolate, {});
343+
SetIsolateMiscHandlers(isolate, settings);
337344
}
338345

339346
return isolate;
340347
}
341348

342349
Isolate* NewIsolate(ArrayBufferAllocator* allocator,
343350
uv_loop_t* event_loop,
344-
MultiIsolatePlatform* platform) {
351+
MultiIsolatePlatform* platform,
352+
const EmbedderSnapshotData* snapshot_data,
353+
const IsolateSettings& settings) {
345354
Isolate::CreateParams params;
346355
if (allocator != nullptr) params.array_buffer_allocator = allocator;
347-
return NewIsolate(&params, event_loop, platform);
356+
return NewIsolate(&params,
357+
event_loop,
358+
platform,
359+
SnapshotData::FromEmbedderWrapper(snapshot_data),
360+
settings);
348361
}
349362

350363
Isolate* NewIsolate(std::shared_ptr<ArrayBufferAllocator> allocator,
351364
uv_loop_t* event_loop,
352-
MultiIsolatePlatform* platform) {
365+
MultiIsolatePlatform* platform,
366+
const EmbedderSnapshotData* snapshot_data,
367+
const IsolateSettings& settings) {
353368
Isolate::CreateParams params;
354369
if (allocator) params.array_buffer_allocator_shared = allocator;
355-
return NewIsolate(&params, event_loop, platform);
370+
return NewIsolate(&params,
371+
event_loop,
372+
platform,
373+
SnapshotData::FromEmbedderWrapper(snapshot_data),
374+
settings);
375+
}
376+
377+
Isolate* NewIsolate(ArrayBufferAllocator* allocator,
378+
uv_loop_t* event_loop,
379+
MultiIsolatePlatform* platform) {
380+
return NewIsolate(allocator, event_loop, platform, nullptr);
381+
}
382+
383+
Isolate* NewIsolate(std::shared_ptr<ArrayBufferAllocator> allocator,
384+
uv_loop_t* event_loop,
385+
MultiIsolatePlatform* platform) {
386+
return NewIsolate(allocator, event_loop, platform, nullptr);
387+
}
388+
389+
IsolateData* CreateIsolateData(
390+
Isolate* isolate,
391+
uv_loop_t* loop,
392+
MultiIsolatePlatform* platform,
393+
ArrayBufferAllocator* allocator,
394+
const EmbedderSnapshotData* embedder_snapshot_data) {
395+
const SnapshotData* snapshot_data =
396+
SnapshotData::FromEmbedderWrapper(embedder_snapshot_data);
397+
return new IsolateData(isolate, loop, platform, allocator, snapshot_data);
356398
}
357399

358400
IsolateData* CreateIsolateData(Isolate* isolate,
359401
uv_loop_t* loop,
360402
MultiIsolatePlatform* platform,
361403
ArrayBufferAllocator* allocator) {
362-
return new IsolateData(isolate, loop, platform, allocator);
404+
return CreateIsolateData(isolate, loop, platform, allocator, nullptr);
363405
}
364406

365407
void FreeIsolateData(IsolateData* isolate_data) {
@@ -387,13 +429,45 @@ Environment* CreateEnvironment(
387429
EnvironmentFlags::Flags flags,
388430
ThreadId thread_id,
389431
std::unique_ptr<InspectorParentHandle> inspector_parent_handle) {
390-
Isolate* isolate = context->GetIsolate();
432+
Isolate* isolate = isolate_data->isolate();
391433
HandleScope handle_scope(isolate);
392-
Context::Scope context_scope(context);
434+
435+
const bool use_snapshot = context.IsEmpty();
436+
const EnvSerializeInfo* env_snapshot_info = nullptr;
437+
if (use_snapshot) {
438+
CHECK_NOT_NULL(isolate_data->snapshot_data());
439+
env_snapshot_info = &isolate_data->snapshot_data()->env_info;
440+
}
441+
393442
// TODO(addaleax): This is a much better place for parsing per-Environment
394443
// options than the global parse call.
395-
Environment* env = new Environment(
396-
isolate_data, context, args, exec_args, nullptr, flags, thread_id);
444+
Environment* env = new Environment(isolate_data,
445+
isolate,
446+
args,
447+
exec_args,
448+
env_snapshot_info,
449+
flags,
450+
thread_id);
451+
CHECK_NOT_NULL(env);
452+
453+
if (use_snapshot) {
454+
context = Context::FromSnapshot(isolate,
455+
SnapshotData::kNodeMainContextIndex,
456+
{DeserializeNodeInternalFields, env})
457+
.ToLocalChecked();
458+
459+
CHECK(!context.IsEmpty());
460+
Context::Scope context_scope(context);
461+
462+
if (InitializeContextRuntime(context).IsNothing()) {
463+
FreeEnvironment(env);
464+
return nullptr;
465+
}
466+
SetIsolateErrorHandlers(isolate, {});
467+
}
468+
469+
Context::Scope context_scope(context);
470+
env->InitializeMainContext(context, env_snapshot_info);
397471

398472
auto initialize_inspector = [&]() {
399473
#if HAVE_INSPECTOR
@@ -413,7 +487,7 @@ Environment* CreateEnvironment(
413487
if (!(flags & EnvironmentFlags::kInspectorOnlyAfterBootstrap))
414488
initialize_inspector();
415489

416-
if (env->principal_realm()->RunBootstrapping().IsEmpty()) {
490+
if (!use_snapshot && env->principal_realm()->RunBootstrapping().IsEmpty()) {
417491
FreeEnvironment(env);
418492
return nullptr;
419493
}
@@ -509,6 +583,10 @@ ArrayBufferAllocator* GetArrayBufferAllocator(IsolateData* isolate_data) {
509583
return isolate_data->node_allocator();
510584
}
511585

586+
Local<Context> GetMainContext(Environment* env) {
587+
return env->context();
588+
}
589+
512590
MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env) {
513591
return GetMultiIsolatePlatform(env->isolate_data());
514592
}

src/env-inl.h

Lines changed: 4 additions & 0 deletions
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

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -468,19 +468,20 @@ IsolateData::IsolateData(Isolate* isolate,
468468
uv_loop_t* event_loop,
469469
MultiIsolatePlatform* platform,
470470
ArrayBufferAllocator* node_allocator,
471-
const IsolateDataSerializeInfo* isolate_data_info)
471+
const SnapshotData* snapshot_data)
472472
: isolate_(isolate),
473473
event_loop_(event_loop),
474474
node_allocator_(node_allocator == nullptr ? nullptr
475475
: node_allocator->GetImpl()),
476-
platform_(platform) {
476+
platform_(platform),
477+
snapshot_data_(snapshot_data) {
477478
options_.reset(
478479
new PerIsolateOptions(*(per_process::cli_options->per_isolate)));
479480

480-
if (isolate_data_info == nullptr) {
481+
if (snapshot_data == nullptr) {
481482
CreateProperties();
482483
} else {
483-
DeserializeProperties(isolate_data_info);
484+
DeserializeProperties(&snapshot_data->isolate_data_info);
484485
}
485486
}
486487

@@ -733,23 +734,6 @@ Environment::Environment(IsolateData* isolate_data,
733734
}
734735
}
735736

736-
Environment::Environment(IsolateData* isolate_data,
737-
Local<Context> context,
738-
const std::vector<std::string>& args,
739-
const std::vector<std::string>& exec_args,
740-
const EnvSerializeInfo* env_info,
741-
EnvironmentFlags::Flags flags,
742-
ThreadId thread_id)
743-
: Environment(isolate_data,
744-
context->GetIsolate(),
745-
args,
746-
exec_args,
747-
env_info,
748-
flags,
749-
thread_id) {
750-
InitializeMainContext(context, env_info);
751-
}
752-
753737
void Environment::InitializeMainContext(Local<Context> context,
754738
const EnvSerializeInfo* env_info) {
755739
principal_realm_ = std::make_unique<Realm>(

src/env.h

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,15 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
126126
uv_loop_t* event_loop,
127127
MultiIsolatePlatform* platform = nullptr,
128128
ArrayBufferAllocator* node_allocator = nullptr,
129-
const IsolateDataSerializeInfo* isolate_data_info = nullptr);
129+
const SnapshotData* snapshot_data = nullptr);
130130
SET_MEMORY_INFO_NAME(IsolateData)
131131
SET_SELF_SIZE(IsolateData)
132132
void MemoryInfo(MemoryTracker* tracker) const override;
133133
IsolateDataSerializeInfo Serialize(v8::SnapshotCreator* creator);
134134

135135
inline uv_loop_t* event_loop() const;
136136
inline MultiIsolatePlatform* platform() const;
137+
inline const SnapshotData* snapshot_data() const;
137138
inline std::shared_ptr<PerIsolateOptions> options();
138139
inline void set_options(std::shared_ptr<PerIsolateOptions> options);
139140

@@ -200,6 +201,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
200201
uv_loop_t* const event_loop_;
201202
NodeArrayBufferAllocator* const node_allocator_;
202203
MultiIsolatePlatform* platform_;
204+
const SnapshotData* snapshot_data_;
203205
std::shared_ptr<PerIsolateOptions> options_;
204206
worker::Worker* worker_context_ = nullptr;
205207
};
@@ -515,6 +517,9 @@ struct SnapshotData {
515517
// and the caller should not consume the snapshot data.
516518
bool Check() const;
517519
static bool FromBlob(SnapshotData* out, FILE* in);
520+
static const SnapshotData* FromEmbedderWrapper(
521+
const EmbedderSnapshotData* data);
522+
EmbedderSnapshotData::Pointer AsEmbedderWrapper() const;
518523

519524
~SnapshotData();
520525
};
@@ -605,14 +610,6 @@ class Environment : public MemoryRetainer {
605610
ThreadId thread_id);
606611
void InitializeMainContext(v8::Local<v8::Context> context,
607612
const EnvSerializeInfo* env_info);
608-
// Create an Environment and initialize the provided principal context for it.
609-
Environment(IsolateData* isolate_data,
610-
v8::Local<v8::Context> context,
611-
const std::vector<std::string>& args,
612-
const std::vector<std::string>& exec_args,
613-
const EnvSerializeInfo* env_info,
614-
EnvironmentFlags::Flags flags,
615-
ThreadId thread_id);
616613
~Environment() override;
617614

618615
void InitializeLibuv();

0 commit comments

Comments
 (0)