Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: add snapshot support for embedder API #45888

Closed
wants to merge 7 commits into from
Closed
27 changes: 17 additions & 10 deletions lib/internal/main/mksnapshot.js
Expand Up @@ -12,6 +12,7 @@ const {
const binding = internalBinding('mksnapshot');
const { BuiltinModule } = require('internal/bootstrap/loaders');
const {
getEmbedderEntryFunction,
compileSerializeMain,
} = binding;

Expand Down Expand Up @@ -119,14 +120,21 @@ function main() {
prepareMainThreadExecution
} = require('internal/process/pre_execution');

prepareMainThreadExecution(true, false);
let serializeMainFunction = getEmbedderEntryFunction();
const serializeMainArgs = [requireForUserSnapshot];

const file = process.argv[1];
const path = require('path');
const filename = path.resolve(file);
const dirname = path.dirname(filename);
const source = readFileSync(file, 'utf-8');
const serializeMainFunction = compileSerializeMain(filename, source);
if (serializeMainFunction) { // embedded case
prepareMainThreadExecution(false, false);
} else {
prepareMainThreadExecution(true, false);
const file = process.argv[1];
const path = require('path');
const filename = path.resolve(file);
const dirname = path.dirname(filename);
const source = readFileSync(file, 'utf-8');
serializeMainFunction = compileSerializeMain(filename, source);
serializeMainArgs.push(filename, dirname);
}

const {
initializeCallbacks,
Expand All @@ -146,10 +154,9 @@ function main() {

if (getOptionValue('--inspect-brk')) {
internalBinding('inspector').callAndPauseOnStart(
serializeMainFunction, undefined,
requireForUserSnapshot, filename, dirname);
serializeMainFunction, undefined, ...serializeMainArgs);
} else {
serializeMainFunction(requireForUserSnapshot, filename, dirname);
serializeMainFunction(...serializeMainArgs);
}

addSerializeCallback(() => {
Expand Down
153 changes: 141 additions & 12 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 All @@ -13,6 +14,7 @@ using v8::Locker;
using v8::Maybe;
using v8::Nothing;
using v8::SealHandleScope;
using v8::SnapshotCreator;

namespace node {

Expand Down Expand Up @@ -77,17 +79,20 @@ 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,
uint32_t flags,
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
: impl_(new Impl()) {
: impl_(new Impl()) {
CHECK_NOT_NULL(platform);
CHECK_NOT_NULL(errors);

Expand All @@ -103,19 +108,43 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
}
loop->data = this;

impl_->allocator = ArrayBufferAllocator::Create();
impl_->isolate = NewIsolate(impl_->allocator, &impl_->loop, platform);
Isolate* isolate = impl_->isolate;
Isolate* isolate;
if (flags & Flags::kIsForSnapshotting) {
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()));
isolate, loop, platform, impl_->allocator.get(), snapshot_data));
impl_->isolate_data->options()->build_snapshot =
impl_->snapshot_creator.has_value();

HandleScope handle_scope(isolate);
if (snapshot_data) {
impl_->env.reset(make_env(this));
if (impl_->env) {
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 @@ -126,14 +155,50 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
}
}

CommonEnvironmentSetup::CommonEnvironmentSetup(
MultiIsolatePlatform* platform,
std::vector<std::string>* errors,
std::function<Environment*(const CommonEnvironmentSetup*)> 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) {
Isolate* isolate = impl_->isolate;
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);

impl_->context.Reset();
impl_->main_context.Reset();
impl_->env.reset();
impl_->isolate_data.reset();
}
Expand All @@ -143,7 +208,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 @@ -156,6 +224,21 @@ 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 @@ -186,7 +269,53 @@ 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()(
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;
}

void EmbedderSnapshotData::ToFile(FILE* out) const {
impl_->ToBlob(out);
}

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