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.
  • Loading branch information
addaleax committed Dec 16, 2022
1 parent f62bcdd commit 5e487f1
Show file tree
Hide file tree
Showing 15 changed files with 421 additions and 102 deletions.
65 changes: 60 additions & 5 deletions 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;
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
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 @@ -307,9 +308,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 @@ -328,38 +335,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 @@ -387,13 +429,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 @@ -407,7 +481,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
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
26 changes: 5 additions & 21 deletions src/env.cc
Expand Up @@ -468,19 +468,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 @@ -733,23 +734,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
15 changes: 6 additions & 9 deletions src/env.h
Expand Up @@ -126,14 +126,15 @@ 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;
IsolateDataSerializeInfo Serialize(v8::SnapshotCreator* creator);

inline uv_loop_t* event_loop() const;
inline MultiIsolatePlatform* platform() const;
inline const SnapshotData* snapshot_data() const;
inline std::shared_ptr<PerIsolateOptions> options();
inline void set_options(std::shared_ptr<PerIsolateOptions> options);

Expand Down Expand Up @@ -200,6 +201,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<PerIsolateOptions> options_;
worker::Worker* worker_context_ = nullptr;
};
Expand Down Expand Up @@ -515,6 +517,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();
};
Expand Down Expand Up @@ -605,14 +610,6 @@ class Environment : public MemoryRetainer {
ThreadId thread_id);
void InitializeMainContext(v8::Local<v8::Context> context,
const EnvSerializeInfo* env_info);
// Create an Environment and initialize the provided principal context for it.
Environment(IsolateData* isolate_data,
v8::Local<v8::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() override;

void InitializeLibuv();
Expand Down

0 comments on commit 5e487f1

Please sign in to comment.