Skip to content

Commit

Permalink
[WIP] allow snapshotting from the embedder API
Browse files Browse the repository at this point in the history
  • Loading branch information
addaleax committed Jan 24, 2023
1 parent d2c0f4d commit d2746af
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 171 deletions.
91 changes: 80 additions & 11 deletions src/api/embed_helpers.cc
Expand Up @@ -14,6 +14,7 @@ using v8::Locker;
using v8::Maybe;
using v8::Nothing;
using v8::SealHandleScope;
using v8::SnapshotCreator;

namespace node {

Expand Down Expand Up @@ -78,16 +79,18 @@ struct CommonEnvironmentSetup::Impl {
MultiIsolatePlatform* platform = nullptr;
uv_loop_t loop;
std::shared_ptr<ArrayBufferAllocator> allocator;
std::optional<SnapshotCreator> snapshot_creator;
Isolate* isolate = nullptr;
DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data;
DeleteFnPtr<Environment, FreeEnvironment> env;
Global<Context> context;
Global<Context> main_context;
};

CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const EmbedderSnapshotData* snapshot_data,
bool is_snapshotting,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
: impl_(new Impl()) {
CHECK_NOT_NULL(platform);
Expand All @@ -105,28 +108,42 @@ 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 (is_snapshotting) {
const std::vector<intptr_t>& 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 = is_snapshotting;

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> 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;
Expand All @@ -141,7 +158,34 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
: CommonEnvironmentSetup(platform, errors, nullptr, make_env) {}
: CommonEnvironmentSetup(platform, errors, nullptr, false, make_env) {}

std::unique_ptr<CommonEnvironmentSetup>
CommonEnvironmentSetup::CreateForSnapshotting(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const std::vector<std::string>& args,
const std::vector<std::string>& 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<CommonEnvironmentSetup>(new CommonEnvironmentSetup(
platform, errors, nullptr, true,
[&](const CommonEnvironmentSetup* setup) -> Environment* {
return CreateEnvironment(
setup->isolate_data(),
setup->context(),
args,
exec_args,
static_cast<EnvironmentFlags::Flags>(env_flags));
}));
if (!errors->empty()) ret.reset();
return ret;
}

CommonEnvironmentSetup::~CommonEnvironmentSetup() {
if (impl_->isolate != nullptr) {
Expand All @@ -150,7 +194,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();
}
Expand All @@ -160,7 +204,10 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() {
*static_cast<bool*>(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)
Expand All @@ -173,6 +220,20 @@ 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<uint8_t>(SnapshotMetadata::Type::kFullyCustomized));
if (exit_code != ExitCode::kNoFailure) return {};

return result;
}

Maybe<int> SpinEventLoop(Environment* env) {
Maybe<ExitCode> result = SpinEventLoopInternal(env);
if (result.IsNothing()) {
Expand Down Expand Up @@ -203,7 +264,11 @@ Environment* CommonEnvironmentSetup::env() const {
}

v8::Local<v8::Context> 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()(
Expand Down Expand Up @@ -232,6 +297,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) {}
Expand Down
32 changes: 31 additions & 1 deletion src/node.h
Expand Up @@ -511,11 +511,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();
Expand All @@ -532,6 +537,7 @@ class EmbedderSnapshotData {
const SnapshotData* impl_;
bool owns_impl_;
friend struct SnapshotData;
friend class CommonEnvironmentSetup;
};

// Overriding IsolateSettings may produce unexpected behavior
Expand Down Expand Up @@ -823,7 +829,29 @@ class NODE_EXTERN CommonEnvironmentSetup {
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<CommonEnvironmentSetup> CreateForSnapshotting(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
const std::vector<std::string>& args = {},
const std::vector<std::string>& exec_args = {});
EmbedderSnapshotData::Pointer CreateSnapshot();

struct uv_loop_s* event_loop() const;
v8::SnapshotCreator* snapshot_creator();
// Empty for snapshotting environments.
std::shared_ptr<ArrayBufferAllocator> array_buffer_allocator() const;
v8::Isolate* isolate() const;
IsolateData* isolate_data() const;
Expand All @@ -846,6 +874,7 @@ class NODE_EXTERN CommonEnvironmentSetup {
MultiIsolatePlatform*,
std::vector<std::string>*,
const EmbedderSnapshotData*,
bool is_snapshotting,
std::function<Environment*(const CommonEnvironmentSetup*)>);
};

Expand Down Expand Up @@ -878,6 +907,7 @@ CommonEnvironmentSetup::CreateWithSnapshot(
platform,
errors,
snapshot_data,
false,
[&](const CommonEnvironmentSetup* setup) -> Environment* {
return CreateEnvironment(setup->isolate_data(),
setup->context(),
Expand Down
35 changes: 0 additions & 35 deletions src/node_main_instance.cc
Expand Up @@ -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<std::string>& args,
const std::vector<std::string>& 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<IsolateData>(isolate_, event_loop, platform, nullptr);

SetIsolateMiscHandlers(isolate_, {});
}

std::unique_ptr<NodeMainInstance> NodeMainInstance::Create(
Isolate* isolate,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
return std::unique_ptr<NodeMainInstance>(
new NodeMainInstance(isolate, event_loop, platform, args, exec_args));
}

NodeMainInstance::NodeMainInstance(const SnapshotData* snapshot_data,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
Expand Down Expand Up @@ -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;
Expand Down
27 changes: 0 additions & 27 deletions src/node_main_instance.h
Expand Up @@ -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<NodeMainInstance> 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<NodeMainInstance> Create(
v8::Isolate* isolate,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args);

void Dispose();

// Create a main instance that owns the isolate
NodeMainInstance(const SnapshotData* snapshot_data,
uv_loop_t* event_loop,
Expand Down
9 changes: 7 additions & 2 deletions src/node_snapshot_builder.h
Expand Up @@ -31,10 +31,15 @@ class NODE_EXTERN_PRIVATE SnapshotBuilder {
static void InitializeIsolateParams(const SnapshotData* data,
v8::Isolate::CreateParams* params);

private:
static const std::vector<intptr_t>& CollectExternalReferences();

static std::unique_ptr<ExternalReferenceRegistry> registry_;
static ExitCode CreateSnapshot(
SnapshotData* out,
CommonEnvironmentSetup* setup,
/*SnapshotMetadata::Type*/uint8_t snapshot_type);

private:
static std::unique_ptr<ExternalReferenceRegistry> registry_;
};
} // namespace node

Expand Down

0 comments on commit d2746af

Please sign in to comment.