mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
CommonEnvironmentSetup::CreateForSnapshotting() always created the SnapshotCreator without a startup blob, so V8 set up its heap from scratch. That is impossible when the V8 that Node.js is linked against only carries the deserializer (external startup data, as Chromium builds it), and it puts the resulting snapshot on a different read-only heap lineage than isolates the embedder creates from its own blob. Embedders in that situation cannot use the snapshot support at all today. Add SnapshotConfig::base_blob, an optional caller-owned v8::StartupData that is handed to the SnapshotCreator so that V8 deserializes its heap from that blob and Node.js adds its isolate data and contexts on top, the way Blink's context snapshot is built. node_mksnapshot accepts --v8-snapshot-blob=<file> for hosts that build Node.js with such a V8; Node.js's own build never passes it and is unchanged. Consuming the result needs no changes. embedtest can create a plain V8 startup blob and build the embedder snapshot on top of one, and a test round-trips argv through a snapshot built that way. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65374 Reviewed-By: James M Snell <jasnell@gmail.com>
303 lines
10 KiB
C++
303 lines
10 KiB
C++
#ifdef NDEBUG
|
|
#undef NDEBUG
|
|
#endif
|
|
#include <assert.h>
|
|
#include "cppgc/platform.h"
|
|
#include "executable_wrapper.h"
|
|
#include "node.h"
|
|
|
|
#include <algorithm>
|
|
|
|
// Note: This file is being referred to from doc/api/embedding.md, and excerpts
|
|
// from it are included in the documentation. Try to keep these in sync.
|
|
// Snapshot support is not part of the embedder API docs yet due to its
|
|
// experimental nature, although it is of course documented in node.h.
|
|
|
|
using node::CommonEnvironmentSetup;
|
|
using node::Environment;
|
|
using node::MultiIsolatePlatform;
|
|
using v8::Context;
|
|
using v8::HandleScope;
|
|
using v8::Isolate;
|
|
using v8::Locker;
|
|
using v8::MaybeLocal;
|
|
using v8::V8;
|
|
using v8::Value;
|
|
|
|
static int RunNodeInstance(MultiIsolatePlatform* platform,
|
|
const std::vector<std::string>& args,
|
|
const std::vector<std::string>& exec_args);
|
|
|
|
// --create-v8-startup-blob <file>: a plain V8 startup blob (what V8's
|
|
// mksnapshot produces), to test building the Node.js snapshot on top of one.
|
|
static int CreateV8StartupBlob(MultiIsolatePlatform* platform,
|
|
const std::string& path) {
|
|
std::unique_ptr<v8::ArrayBuffer::Allocator> allocator(
|
|
v8::ArrayBuffer::Allocator::NewDefaultAllocator());
|
|
v8::Isolate::CreateParams params;
|
|
params.array_buffer_allocator = allocator.get();
|
|
uv_loop_t loop;
|
|
assert(uv_loop_init(&loop) == 0);
|
|
v8::Isolate* isolate = v8::Isolate::Allocate();
|
|
platform->RegisterIsolate(isolate, &loop);
|
|
v8::StartupData blob;
|
|
{
|
|
v8::SnapshotCreator creator(isolate, params);
|
|
{
|
|
v8::HandleScope handle_scope(isolate);
|
|
creator.SetDefaultContext(v8::Context::New(isolate));
|
|
}
|
|
blob =
|
|
creator.CreateBlob(v8::SnapshotCreator::FunctionCodeHandling::kClear);
|
|
}
|
|
bool platform_finished = false;
|
|
platform->AddIsolateFinishedCallback(
|
|
isolate,
|
|
[](void* data) {
|
|
bool* finished = static_cast<bool*>(data);
|
|
*finished = true;
|
|
},
|
|
&platform_finished);
|
|
platform->DisposeIsolate(isolate);
|
|
while (!platform_finished) uv_run(&loop, UV_RUN_ONCE);
|
|
uv_loop_close(&loop);
|
|
assert(blob.data != nullptr);
|
|
FILE* fp = fopen(path.c_str(), "wb");
|
|
assert(fp != nullptr);
|
|
size_t written = fwrite(blob.data, blob.raw_size, 1, fp);
|
|
assert(written == 1);
|
|
fclose(fp);
|
|
delete[] blob.data;
|
|
return 0;
|
|
}
|
|
|
|
static std::vector<char> base_blob_bytes;
|
|
static v8::StartupData base_blob{nullptr, 0};
|
|
|
|
NODE_MAIN(int argc, node::argv_type raw_argv[]) {
|
|
char** argv = nullptr;
|
|
node::FixupMain(argc, raw_argv, &argv);
|
|
|
|
std::vector<std::string> args(argv, argv + argc);
|
|
std::shared_ptr<node::InitializationResult> result =
|
|
node::InitializeOncePerProcess(
|
|
args,
|
|
{
|
|
node::ProcessInitializationFlags::kNoInitializeV8,
|
|
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform,
|
|
// This is used to test NODE_REPL_EXTERNAL_MODULE is disabled with
|
|
// kDisableNodeOptionsEnv. If other tests need NODE_OPTIONS
|
|
// support in the future, split this configuration out as a
|
|
// command line option.
|
|
node::ProcessInitializationFlags::kDisableNodeOptionsEnv,
|
|
node::ProcessInitializationFlags::kNoInitializeCppgc,
|
|
});
|
|
|
|
for (const std::string& error : result->errors())
|
|
fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
|
|
if (result->early_return() != 0) {
|
|
return result->exit_code();
|
|
}
|
|
|
|
std::unique_ptr<MultiIsolatePlatform> platform =
|
|
MultiIsolatePlatform::Create(4);
|
|
V8::InitializePlatform(platform.get());
|
|
cppgc::InitializeProcess(platform->GetPageAllocator());
|
|
V8::Initialize();
|
|
|
|
int ret =
|
|
RunNodeInstance(platform.get(), result->args(), result->exec_args());
|
|
|
|
V8::Dispose();
|
|
V8::DisposePlatform();
|
|
|
|
node::TearDownOncePerProcess();
|
|
return ret;
|
|
}
|
|
|
|
int RunNodeInstance(MultiIsolatePlatform* platform,
|
|
const std::vector<std::string>& args,
|
|
const std::vector<std::string>& exec_args) {
|
|
int exit_code = 0;
|
|
|
|
// Format of the arguments of this binary:
|
|
// Building snapshot:
|
|
// embedtest js_code_to_eval arg1 arg2...
|
|
// --embedder-snapshot-blob blob-path
|
|
// --embedder-snapshot-create
|
|
// [--embedder-snapshot-as-file]
|
|
// [--without-code-cache]
|
|
// Running snapshot:
|
|
// embedtest --embedder-snapshot-blob blob-path
|
|
// [--embedder-snapshot-as-file]
|
|
// arg1 arg2...
|
|
// No snapshot:
|
|
// embedtest arg1 arg2...
|
|
node::EmbedderSnapshotData::Pointer snapshot;
|
|
|
|
std::string binary_path = args[0];
|
|
std::vector<std::string> filtered_args;
|
|
bool is_building_snapshot = false;
|
|
bool snapshot_as_file = false;
|
|
std::optional<node::SnapshotConfig> snapshot_config;
|
|
std::string snapshot_blob_path;
|
|
for (size_t i = 0; i < args.size(); ++i) {
|
|
const std::string& arg = args[i];
|
|
if (arg == "--embedder-snapshot-create") {
|
|
is_building_snapshot = true;
|
|
} else if (arg == "--embedder-snapshot-as-file") {
|
|
snapshot_as_file = true;
|
|
} else if (arg == "--without-code-cache") {
|
|
if (!snapshot_config.has_value()) {
|
|
snapshot_config = node::SnapshotConfig{};
|
|
}
|
|
snapshot_config.value().flags = static_cast<node::SnapshotFlags>(
|
|
static_cast<uint32_t>(snapshot_config.value().flags) |
|
|
static_cast<uint32_t>(node::SnapshotFlags::kWithoutCodeCache));
|
|
} else if (arg == "--embedder-snapshot-blob") {
|
|
assert(i + 1 < args.size());
|
|
snapshot_blob_path = args[i + 1];
|
|
i++;
|
|
} else if (arg == "--create-v8-startup-blob") {
|
|
assert(i + 1 < args.size());
|
|
return CreateV8StartupBlob(platform, args[i + 1]);
|
|
} else if (arg == "--embedder-snapshot-base-blob") {
|
|
assert(i + 1 < args.size());
|
|
FILE* fp = fopen(args[i + 1].c_str(), "rb");
|
|
assert(fp != nullptr);
|
|
fseek(fp, 0, SEEK_END);
|
|
base_blob_bytes.resize(ftell(fp));
|
|
fseek(fp, 0, SEEK_SET);
|
|
size_t read =
|
|
fread(base_blob_bytes.data(), base_blob_bytes.size(), 1, fp);
|
|
assert(read == 1);
|
|
fclose(fp);
|
|
base_blob = {base_blob_bytes.data(),
|
|
static_cast<int>(base_blob_bytes.size())};
|
|
if (!snapshot_config.has_value()) {
|
|
snapshot_config = node::SnapshotConfig{};
|
|
}
|
|
snapshot_config.value().base_blob = &base_blob;
|
|
i++;
|
|
} else {
|
|
filtered_args.push_back(arg);
|
|
}
|
|
}
|
|
|
|
if (!snapshot_blob_path.empty() && !is_building_snapshot) {
|
|
FILE* fp = fopen(snapshot_blob_path.c_str(), "rb");
|
|
assert(fp != nullptr);
|
|
if (snapshot_as_file) {
|
|
snapshot = node::EmbedderSnapshotData::FromFile(fp);
|
|
} else {
|
|
uv_fs_t req = uv_fs_t();
|
|
int statret =
|
|
uv_fs_stat(nullptr, &req, snapshot_blob_path.c_str(), nullptr);
|
|
assert(statret == 0);
|
|
size_t filesize = req.statbuf.st_size;
|
|
uv_fs_req_cleanup(&req);
|
|
|
|
std::vector<char> vec(filesize);
|
|
size_t read = fread(vec.data(), filesize, 1, fp);
|
|
assert(read == 1);
|
|
snapshot = node::EmbedderSnapshotData::FromBlob(vec);
|
|
}
|
|
assert(snapshot);
|
|
int ret = fclose(fp);
|
|
assert(ret == 0);
|
|
}
|
|
|
|
if (is_building_snapshot) {
|
|
// It contains at least the binary path, the code to snapshot,
|
|
// and --embedder-snapshot-create (which is filtered, so at least
|
|
// 2 arguments should remain after filtering).
|
|
assert(filtered_args.size() >= 2);
|
|
// Insert an anonymous filename as process.argv[1].
|
|
filtered_args.insert(filtered_args.begin() + 1,
|
|
node::GetAnonymousMainPath());
|
|
}
|
|
|
|
std::vector<std::string> errors;
|
|
std::unique_ptr<CommonEnvironmentSetup> setup;
|
|
|
|
if (snapshot) {
|
|
setup = CommonEnvironmentSetup::CreateFromSnapshot(
|
|
platform, &errors, snapshot.get(), filtered_args, exec_args);
|
|
} else if (is_building_snapshot) {
|
|
if (snapshot_config.has_value()) {
|
|
setup = CommonEnvironmentSetup::CreateForSnapshotting(
|
|
platform, &errors, filtered_args, exec_args, snapshot_config.value());
|
|
} else {
|
|
setup = CommonEnvironmentSetup::CreateForSnapshotting(
|
|
platform, &errors, filtered_args, exec_args);
|
|
}
|
|
} else {
|
|
setup = CommonEnvironmentSetup::Create(
|
|
platform, &errors, filtered_args, exec_args);
|
|
}
|
|
if (!setup) {
|
|
for (const std::string& err : errors)
|
|
fprintf(stderr, "%s: %s\n", binary_path.c_str(), err.c_str());
|
|
return 1;
|
|
}
|
|
|
|
Isolate* isolate = setup->isolate();
|
|
Environment* env = setup->env();
|
|
|
|
{
|
|
Locker locker(isolate);
|
|
Isolate::Scope isolate_scope(isolate);
|
|
HandleScope handle_scope(isolate);
|
|
Context::Scope context_scope(setup->context());
|
|
|
|
MaybeLocal<Value> loadenv_ret;
|
|
if (snapshot) { // Deserializing snapshot
|
|
loadenv_ret = node::LoadEnvironment(env, node::StartExecutionCallback{});
|
|
} else if (is_building_snapshot) {
|
|
// Environment created for snapshotting must set process.argv[1] to
|
|
// the name of the main script, which was inserted above.
|
|
loadenv_ret = node::LoadEnvironment(
|
|
env,
|
|
"const assert = require('assert');"
|
|
"assert(require('v8').startupSnapshot.isBuildingSnapshot());"
|
|
"globalThis.embedVars = { nön_ascıı: '🏳️🌈' };"
|
|
"globalThis.require = require;"
|
|
"require('vm').runInThisContext(process.argv[2]);");
|
|
} else {
|
|
loadenv_ret = node::LoadEnvironment(
|
|
env,
|
|
"const publicRequire = require('module').createRequire(process.cwd() "
|
|
"+ '/');"
|
|
"globalThis.require = publicRequire;"
|
|
"globalThis.embedVars = { nön_ascıı: '🏳️🌈' };"
|
|
"require('vm').runInThisContext(process.argv[1]);");
|
|
}
|
|
|
|
if (loadenv_ret.IsEmpty()) // There has been a JS exception.
|
|
return 1;
|
|
|
|
exit_code = node::SpinEventLoop(env).FromMaybe(1);
|
|
|
|
if (!snapshot_blob_path.empty() && is_building_snapshot) {
|
|
snapshot = setup->CreateSnapshot();
|
|
assert(snapshot);
|
|
|
|
FILE* fp = fopen(snapshot_blob_path.c_str(), "wb");
|
|
assert(fp != nullptr);
|
|
if (snapshot_as_file) {
|
|
snapshot->ToFile(fp);
|
|
} else {
|
|
const std::vector<char> vec = snapshot->ToBlob();
|
|
size_t written = fwrite(vec.data(), vec.size(), 1, fp);
|
|
assert(written == 1);
|
|
}
|
|
int ret = fclose(fp);
|
|
assert(ret == 0);
|
|
}
|
|
}
|
|
|
|
node::Stop(env);
|
|
|
|
return exit_code;
|
|
}
|