Skip to content

Commit

Permalink
src: add snapshot support for embedder API
Browse files Browse the repository at this point in the history
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: nodejs#45888
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
  • Loading branch information
addaleax authored and nodejs-github-bot committed Feb 3, 2023
1 parent d523bfe commit 06bb6b4
Show file tree
Hide file tree
Showing 15 changed files with 433 additions and 110 deletions.
65 changes: 60 additions & 5 deletions src/api/embed_helpers.cc
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -86,8 +87,9 @@ struct CommonEnvironmentSetup::Impl {
CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const EmbedderSnapshotData* snapshot_data,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
: impl_(new Impl()) {
: impl_(new Impl()) {
CHECK_NOT_NULL(platform);
CHECK_NOT_NULL(errors);

Expand All @@ -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> context = NewContext(isolate);
impl_->context.Reset(isolate, context);
if (context.IsEmpty()) {
Expand All @@ -126,6 +137,12 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
}
}

CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
: CommonEnvironmentSetup(platform, errors, nullptr, make_env) {}

CommonEnvironmentSetup::~CommonEnvironmentSetup() {
if (impl_->isolate != nullptr) {
Isolate* isolate = impl_->isolate;
Expand Down Expand Up @@ -189,4 +206,42 @@ v8::Local<v8::Context> 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
106 changes: 92 additions & 14 deletions src/api/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -336,38 +343,73 @@ 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;
}

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(&params, event_loop, platform);
return NewIsolate(&params,
event_loop,
platform,
SnapshotData::FromEmbedderWrapper(snapshot_data),
settings);
}

Isolate* NewIsolate(std::shared_ptr<ArrayBufferAllocator> 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(&params, event_loop, platform);
return NewIsolate(&params,
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<ArrayBufferAllocator> 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) {
Expand Down Expand Up @@ -395,13 +437,45 @@ Environment* CreateEnvironment(
EnvironmentFlags::Flags flags,
ThreadId thread_id,
std::unique_ptr<InspectorParentHandle> 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()) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -500,6 +574,10 @@ ArrayBufferAllocator* GetArrayBufferAllocator(IsolateData* isolate_data) {
return isolate_data->node_allocator();
}

Local<Context> GetMainContext(Environment* env) {
return env->context();
}

MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env) {
return GetMultiIsolatePlatform(env->isolate_data());
}
Expand Down
4 changes: 4 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
41 changes: 17 additions & 24 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -675,14 +676,23 @@ Environment::Environment(IsolateData* isolate_data,
thread_id_(thread_id.id == static_cast<uint64_t>(-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);
Expand Down Expand Up @@ -747,23 +757,6 @@ Environment::Environment(IsolateData* isolate_data,
}
}

Environment::Environment(IsolateData* isolate_data,
Local<Context> context,
const std::vector<std::string>& args,
const std::vector<std::string>& 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> context,
const EnvSerializeInfo* env_info) {
principal_realm_ = std::make_unique<Realm>(
Expand Down
Loading

0 comments on commit 06bb6b4

Please sign in to comment.