node/src/node_snapshotable.cc
Joyee Cheung 30e8b5e2a2
tools: refactor snapshot builder
This patch:

- Moves the snapshot building code to src/ so that we can reuse it
  later when generating custom snapshots from an entry point accepted
  by the node binary.
- Create a SnapshotData struct that incorporates all the data useful
  for a snapshot blob, including both the V8 data and the Node.js
  data.

PR-URL: https://github.com/nodejs/node/pull/38902
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
2021-06-10 14:17:49 +08:00

299 lines
9.7 KiB
C++

#include "node_snapshotable.h"
#include <iostream>
#include <sstream>
#include "base_object-inl.h"
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "node_errors.h"
#include "node_external_reference.h"
#include "node_file.h"
#include "node_internals.h"
#include "node_main_instance.h"
#include "node_v8.h"
#include "node_v8_platform-inl.h"
namespace node {
using v8::Context;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::SnapshotCreator;
using v8::StartupData;
using v8::TryCatch;
using v8::Value;
template <typename T>
void WriteVector(std::ostringstream* ss, const T* vec, size_t size) {
for (size_t i = 0; i < size; i++) {
*ss << std::to_string(vec[i]) << (i == size - 1 ? '\n' : ',');
}
}
std::string FormatBlob(SnapshotData* data) {
std::ostringstream ss;
ss << R"(#include <cstddef>
#include "env.h"
#include "node_main_instance.h"
#include "v8.h"
// This file is generated by tools/snapshot. Do not edit.
namespace node {
static const char blob_data[] = {
)";
WriteVector(&ss, data->blob.data, data->blob.raw_size);
ss << R"(};
static const int blob_size = )"
<< data->blob.raw_size << R"(;
static v8::StartupData blob = { blob_data, blob_size };
)";
ss << R"(v8::StartupData* NodeMainInstance::GetEmbeddedSnapshotBlob() {
return &blob;
}
static const std::vector<size_t> isolate_data_indices {
)";
WriteVector(&ss,
data->isolate_data_indices.data(),
data->isolate_data_indices.size());
ss << R"(};
const std::vector<size_t>* NodeMainInstance::GetIsolateDataIndices() {
return &isolate_data_indices;
}
static const EnvSerializeInfo env_info )"
<< data->env_info << R"(;
const EnvSerializeInfo* NodeMainInstance::GetEnvSerializeInfo() {
return &env_info;
}
} // namespace node
)";
return ss.str();
}
void SnapshotBuilder::Generate(SnapshotData* out,
const std::vector<std::string> args,
const std::vector<std::string> exec_args) {
Isolate* isolate = Isolate::Allocate();
isolate->SetCaptureStackTraceForUncaughtExceptions(
true, 10, v8::StackTrace::StackTraceOptions::kDetailed);
per_process::v8_platform.Platform()->RegisterIsolate(isolate,
uv_default_loop());
std::unique_ptr<NodeMainInstance> main_instance;
std::string result;
{
const std::vector<intptr_t>& external_references =
NodeMainInstance::CollectExternalReferences();
SnapshotCreator creator(isolate, external_references.data());
Environment* env;
{
main_instance =
NodeMainInstance::Create(isolate,
uv_default_loop(),
per_process::v8_platform.Platform(),
args,
exec_args);
HandleScope scope(isolate);
creator.SetDefaultContext(Context::New(isolate));
out->isolate_data_indices =
main_instance->isolate_data()->Serialize(&creator);
// Run the per-context scripts
Local<Context> context;
{
TryCatch bootstrapCatch(isolate);
context = NewContext(isolate);
if (bootstrapCatch.HasCaught()) {
PrintCaughtException(isolate, context, bootstrapCatch);
abort();
}
}
Context::Scope context_scope(context);
// Create the environment
env = new Environment(main_instance->isolate_data(),
context,
args,
exec_args,
nullptr,
node::EnvironmentFlags::kDefaultFlags,
{});
// Run scripts in lib/internal/bootstrap/
{
TryCatch bootstrapCatch(isolate);
v8::MaybeLocal<Value> result = env->RunBootstrapping();
if (bootstrapCatch.HasCaught()) {
PrintCaughtException(isolate, context, bootstrapCatch);
}
result.ToLocalChecked();
}
if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
env->PrintAllBaseObjects();
printf("Environment = %p\n", env);
}
// Serialize the native states
out->env_info = env->Serialize(&creator);
// Serialize the context
size_t index = creator.AddContext(
context, {SerializeNodeContextInternalFields, env});
CHECK_EQ(index, NodeMainInstance::kNodeContextIndex);
}
// Must be out of HandleScope
out->blob =
creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kClear);
CHECK(out->blob.CanBeRehashed());
// Must be done while the snapshot creator isolate is entered i.e. the
// creator is still alive.
FreeEnvironment(env);
main_instance->Dispose();
}
per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
}
std::string SnapshotBuilder::Generate(
const std::vector<std::string> args,
const std::vector<std::string> exec_args) {
SnapshotData data;
Generate(&data, args, exec_args);
std::string result = FormatBlob(&data);
delete[] data.blob.data;
return result;
}
SnapshotableObject::SnapshotableObject(Environment* env,
Local<Object> wrap,
EmbedderObjectType type)
: BaseObject(env, wrap), type_(type) {
}
const char* SnapshotableObject::GetTypeNameChars() const {
switch (type_) {
#define V(PropertyName, NativeTypeName) \
case EmbedderObjectType::k_##PropertyName: { \
return NativeTypeName::type_name.c_str(); \
}
SERIALIZABLE_OBJECT_TYPES(V)
#undef V
default: { UNREACHABLE(); }
}
}
bool IsSnapshotableType(FastStringKey key) {
#define V(PropertyName, NativeTypeName) \
if (key == NativeTypeName::type_name) { \
return true; \
}
SERIALIZABLE_OBJECT_TYPES(V)
#undef V
return false;
}
void DeserializeNodeInternalFields(Local<Object> holder,
int index,
StartupData payload,
void* env) {
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Deserialize internal field %d of %p, size=%d\n",
static_cast<int>(index),
(*holder),
static_cast<int>(payload.raw_size));
if (payload.raw_size == 0) {
holder->SetAlignedPointerInInternalField(index, nullptr);
return;
}
Environment* env_ptr = static_cast<Environment*>(env);
const InternalFieldInfo* info =
reinterpret_cast<const InternalFieldInfo*>(payload.data);
switch (info->type) {
#define V(PropertyName, NativeTypeName) \
case EmbedderObjectType::k_##PropertyName: { \
per_process::Debug(DebugCategory::MKSNAPSHOT, \
"Object %p is %s\n", \
(*holder), \
NativeTypeName::type_name.c_str()); \
env_ptr->EnqueueDeserializeRequest( \
NativeTypeName::Deserialize, holder, index, info->Copy()); \
break; \
}
SERIALIZABLE_OBJECT_TYPES(V)
#undef V
default: { UNREACHABLE(); }
}
}
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
int index,
void* env) {
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Serialize internal field, index=%d, holder=%p\n",
static_cast<int>(index),
*holder);
void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
if (ptr == nullptr) {
return StartupData{nullptr, 0};
}
DCHECK(static_cast<BaseObject*>(ptr)->is_snapshotable());
SnapshotableObject* obj = static_cast<SnapshotableObject*>(ptr);
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Object %p is %s, ",
*holder,
obj->GetTypeNameChars());
InternalFieldInfo* info = obj->Serialize(index);
per_process::Debug(DebugCategory::MKSNAPSHOT,
"payload size=%d\n",
static_cast<int>(info->length));
return StartupData{reinterpret_cast<const char*>(info),
static_cast<int>(info->length)};
}
void SerializeBindingData(Environment* env,
SnapshotCreator* creator,
EnvSerializeInfo* info) {
size_t i = 0;
env->ForEachBindingData([&](FastStringKey key,
BaseObjectPtr<BaseObject> binding) {
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Serialize binding %i, %p, type=%s\n",
static_cast<int>(i),
*(binding->object()),
key.c_str());
if (IsSnapshotableType(key)) {
size_t index = creator->AddData(env->context(), binding->object());
per_process::Debug(DebugCategory::MKSNAPSHOT,
"Serialized with index=%d\n",
static_cast<int>(index));
info->bindings.push_back({key.c_str(), i, index});
SnapshotableObject* ptr = static_cast<SnapshotableObject*>(binding.get());
ptr->PrepareForSerialization(env->context(), creator);
} else {
UNREACHABLE();
}
i++;
});
}
} // namespace node