Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/api/embed_helpers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
isolate = impl_->isolate = Isolate::Allocate(GetOrCreateIsolateGroup());
platform->RegisterIsolate(isolate, loop);

if (snapshot_config != nullptr && snapshot_config->base_blob != nullptr) {
params.snapshot_blob = snapshot_config->base_blob;
}
impl_->snapshot_creator.emplace(isolate, params);
isolate->SetCaptureStackTraceForUncaughtExceptions(
true,
Expand Down
7 changes: 7 additions & 0 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,13 @@ struct SnapshotConfig {
// the snapshot builder can execute asynchronous operations as long as they
// are run to completion when the snapshot is taken.
std::optional<std::string> builder_script_path;

// A V8 startup blob (as produced by V8's mksnapshot) to build the snapshot
// on top of, instead of setting up the V8 heap from scratch. Needed when
// the V8 that Node.js is linked against can only deserialize (external
// startup data), and to keep the result on the same read-only heap lineage
// as the embedder's other isolates. Caller-owned; must outlive the setup.
const v8::StartupData* base_blob = nullptr;
};

struct InspectorParentHandle {
Expand Down
67 changes: 67 additions & 0 deletions test/embedding/embedtest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,52 @@ 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);
Expand Down Expand Up @@ -112,6 +158,27 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
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);
}
Expand Down
49 changes: 49 additions & 0 deletions test/embedding/test-embedding-snapshot-base-blob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

// SnapshotConfig::base_blob: the embedder snapshot can be built on top of an
// existing V8 startup blob instead of a heap set up from scratch.

const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
const fixtures = require('../common/fixtures');
const {
spawnSyncAndAssert,
spawnSyncAndExitWithoutError,
} = require('../common/child_process');

const embedtest = common.resolveBuiltBinary('embedtest');
const snapshotFixture = fixtures.path('snapshot', 'echo-args.js');
const v8Blob = tmpdir.resolve('v8.blob');
const nodeBlob = tmpdir.resolve('node-on-v8.blob');
const buildSnapshotExecArgs = [
`eval(require("fs").readFileSync(${JSON.stringify(snapshotFixture)}, "utf8"))`,
'arg1', 'arg2',
];

tmpdir.refresh();

spawnSyncAndExitWithoutError(embedtest, ['--', '--create-v8-startup-blob', v8Blob], { cwd: tmpdir.path });
assert.ok(fs.statSync(v8Blob).size > 0);

spawnSyncAndExitWithoutError(
embedtest,
['--', ...buildSnapshotExecArgs, '--embedder-snapshot-blob', nodeBlob,
'--embedder-snapshot-base-blob', v8Blob, '--embedder-snapshot-create'],
{ cwd: tmpdir.path });
assert.ok(fs.statSync(nodeBlob).size > fs.statSync(v8Blob).size);

spawnSyncAndAssert(
embedtest,
['--', 'arg3', 'arg4', '--embedder-snapshot-blob', nodeBlob],
{ cwd: tmpdir.path },
{
stdout(output) {
assert.deepStrictEqual(JSON.parse(output), {
originalArgv: [embedtest, '__node_anonymous_main', ...buildSnapshotExecArgs],
currentArgv: [embedtest, embedtest, 'arg3', 'arg4'],
});
return true;
},
});
34 changes: 29 additions & 5 deletions tools/snapshot/node_mksnapshot.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,41 @@ int main(int argc, char* argv[]) {
return BuildSnapshot(argc, argv);
}

static const char kBaseBlobFlag[] = "--v8-snapshot-blob=";

int BuildSnapshot(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <path/to/output.cc>\n";
std::cerr << " " << argv[0] << " --build-snapshot "
std::vector<std::string> args(argv, argv + argc);
// --v8-snapshot-blob=<file>: build on top of this V8 startup blob (for
// hosts whose V8 uses external startup data) instead of from scratch.
std::string base_blob_bytes;
v8::StartupData base_blob{nullptr, 0};
for (auto it = args.begin(); it != args.end(); ++it) {
if (it->starts_with(kBaseBlobFlag)) {
std::string path = it->substr(sizeof(kBaseBlobFlag) - 1);
args.erase(it);
if (node::ReadFileSync(path.c_str(), &base_blob_bytes) != 0) {
std::cerr << "Cannot read V8 snapshot blob " << path << "\n";
return 1;
}
base_blob = {base_blob_bytes.data(),
static_cast<int>(base_blob_bytes.size())};
break;
}
}

if (args.size() < 2) {
std::cerr
<< "Usage: " << argv[0]
<< " [--v8-snapshot-blob=<path/to/blob.bin>] <path/to/output.cc>\n";
std::cerr << " " << argv[0]
<< " [--v8-snapshot-blob=<path/to/blob.bin>] --build-snapshot "
<< "<path/to/script.js> <path/to/output.cc>\n";
return 1;
}

std::shared_ptr<node::InitializationResult> result =
node::InitializeOncePerProcess(
std::vector<std::string>(argv, argv + argc),
node::ProcessInitializationFlags::kGeneratePredictableSnapshot);
args, node::ProcessInitializationFlags::kGeneratePredictableSnapshot);

if (result->exit_code() != 0) {
for (const std::string& error : result->errors()) {
Expand Down Expand Up @@ -94,6 +117,7 @@ int BuildSnapshot(int argc, char* argv[]) {

node::SnapshotConfig snapshot_config;
snapshot_config.builder_script_path = builder_script_path;
if (base_blob.data != nullptr) snapshot_config.base_blob = &base_blob;

#ifdef NODE_USE_NODE_CODE_CACHE
snapshot_config.flags = node::SnapshotFlags::kDefault;
Expand Down
Loading