Skip to content

Commit 8dec8b7

Browse files
committed
src: support snapshot in single executable applications
1 parent 718f62b commit 8dec8b7

File tree

8 files changed

+204
-40
lines changed

8 files changed

+204
-40
lines changed

lib/internal/process/pre_execution.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ function patchProcessObject(expandArgv1) {
143143
__proto__: null,
144144
enumerable: true,
145145
// Only set it to true during snapshot building.
146-
configurable: getOptionValue('--build-snapshot'),
146+
configurable: isBuildingSnapshot(),
147147
value: process.argv[0],
148148
});
149149

src/env.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ struct SnapshotData {
533533

534534
void ToFile(FILE* out) const;
535535
std::vector<char> ToBlob() const;
536+
void ToBlob(std::vector<char>* out) const;
536537
// If returns false, the metadata doesn't match the current Node.js binary,
537538
// and the caller should not consume the snapshot data.
538539
bool Check() const;

src/node.cc

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,21 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
292292

293293
CHECK(!env->isolate_data()->is_building_snapshot());
294294

295+
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
296+
if (sea::IsSingleExecutable()) {
297+
sea::SeaResource sea = sea::FindSingleExecutableResource();
298+
if (sea.use_snapshot()) {
299+
if (env->snapshot_deserialize_main().IsEmpty()) {
300+
fprintf(
301+
stderr,
302+
"No deserialized main function found for the snapshot blob found"
303+
" in single executable binary.\n");
304+
return MaybeLocal<Value>();
305+
}
306+
}
307+
}
308+
#endif
309+
295310
// TODO(joyeecheung): move these conditions into JS land and let the
296311
// deserialize main function take precedence. For workers, we need to
297312
// move the pre-execution part into a different file that can be
@@ -1176,49 +1191,66 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,
11761191
return exit_code;
11771192
}
11781193

1179-
ExitCode LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr,
1180-
const InitializationResultImpl* result) {
1181-
ExitCode exit_code = result->exit_code_enum();
1194+
bool LoadSnapshotData(const SnapshotData** snapshot_data_ptr) {
11821195
// nullptr indicates there's no snapshot data.
11831196
DCHECK_NULL(*snapshot_data_ptr);
1197+
1198+
bool is_sea = false;
1199+
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
1200+
if (sea::IsSingleExecutable()) {
1201+
is_sea = true;
1202+
sea::SeaResource sea = sea::FindSingleExecutableResource();
1203+
if (sea.use_snapshot()) {
1204+
std::unique_ptr<SnapshotData> read_data =
1205+
std::make_unique<SnapshotData>();
1206+
std::string_view snapshot = sea.main_code_or_snapshot;
1207+
if (SnapshotData::FromBlob(read_data.get(), snapshot)) {
1208+
*snapshot_data_ptr = read_data.release();
1209+
return true;
1210+
} else {
1211+
fprintf(stderr, "Invalid snapshot data in single executable binary\n");
1212+
return false;
1213+
}
1214+
}
1215+
}
1216+
#endif
1217+
11841218
// --snapshot-blob indicates that we are reading a customized snapshot.
1185-
if (!per_process::cli_options->snapshot_blob.empty()) {
1219+
// Ignore it when we are loading from SEA.
1220+
if (!is_sea && !per_process::cli_options->snapshot_blob.empty()) {
11861221
std::string filename = per_process::cli_options->snapshot_blob;
11871222
FILE* fp = fopen(filename.c_str(), "rb");
11881223
if (fp == nullptr) {
11891224
fprintf(stderr, "Cannot open %s", filename.c_str());
1190-
exit_code = ExitCode::kStartupSnapshotFailure;
1191-
return exit_code;
1225+
return false;
11921226
}
11931227
std::unique_ptr<SnapshotData> read_data = std::make_unique<SnapshotData>();
11941228
bool ok = SnapshotData::FromFile(read_data.get(), fp);
11951229
fclose(fp);
11961230
if (!ok) {
1197-
// If we fail to read the customized snapshot,
1198-
// simply exit with kStartupSnapshotFailure.
1199-
exit_code = ExitCode::kStartupSnapshotFailure;
1200-
return exit_code;
1231+
return false;
12011232
}
12021233
*snapshot_data_ptr = read_data.release();
1203-
} else if (per_process::cli_options->node_snapshot) {
1204-
// If --snapshot-blob is not specified, we are reading the embedded
1205-
// snapshot, but we will skip it if --no-node-snapshot is specified.
1234+
return true;
1235+
}
1236+
1237+
if (per_process::cli_options->node_snapshot) {
1238+
// If --snapshot-blob is not specified or if the SEA contains not snapshot,
1239+
// we are reading the embedded snapshot, but we will skip it if
1240+
// --no-node-snapshot is specified.
12061241
const node::SnapshotData* read_data =
12071242
SnapshotBuilder::GetEmbeddedSnapshotData();
1208-
if (read_data != nullptr && read_data->Check()) {
1243+
if (read_data != nullptr) {
1244+
if (!read_data->Check()) {
1245+
return false;
1246+
}
12091247
// If we fail to read the embedded snapshot, treat it as if Node.js
12101248
// was built without one.
12111249
*snapshot_data_ptr = read_data;
12121250
}
12131251
}
12141252

1215-
NodeMainInstance main_instance(*snapshot_data_ptr,
1216-
uv_default_loop(),
1217-
per_process::v8_platform.Platform(),
1218-
result->args(),
1219-
result->exec_args());
1220-
exit_code = main_instance.Run();
1221-
return exit_code;
1253+
return true;
12221254
}
12231255

12241256
static ExitCode StartInternal(int argc, char** argv) {
@@ -1253,7 +1285,8 @@ static ExitCode StartInternal(int argc, char** argv) {
12531285

12541286
std::string sea_config = per_process::cli_options->experimental_sea_config;
12551287
if (!sea_config.empty()) {
1256-
return sea::BuildSingleExecutableBlob(sea_config);
1288+
return sea::BuildSingleExecutableBlob(
1289+
sea_config, result->args(), result->exec_args());
12571290
}
12581291

12591292
// --build-snapshot indicates that we are in snapshot building mode.
@@ -1268,7 +1301,15 @@ static ExitCode StartInternal(int argc, char** argv) {
12681301
}
12691302

12701303
// Without --build-snapshot, we are in snapshot loading mode.
1271-
return LoadSnapshotDataAndRun(&snapshot_data, result.get());
1304+
if (!LoadSnapshotData(&snapshot_data)) {
1305+
return ExitCode::kStartupSnapshotFailure;
1306+
}
1307+
NodeMainInstance main_instance(snapshot_data,
1308+
uv_default_loop(),
1309+
per_process::v8_platform.Platform(),
1310+
result->args(),
1311+
result->exec_args());
1312+
return main_instance.Run();
12721313
}
12731314

12741315
int Start(int argc, char** argv) {

src/node_main_instance.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,16 @@ void NodeMainInstance::Run(ExitCode* exit_code, Environment* env) {
9292
bool runs_sea_code = false;
9393
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
9494
if (sea::IsSingleExecutable()) {
95-
runs_sea_code = true;
9695
sea::SeaResource sea = sea::FindSingleExecutableResource();
97-
std::string_view code = sea.code;
98-
LoadEnvironment(env, code);
96+
if (!sea.use_snapshot()) {
97+
runs_sea_code = true;
98+
std::string_view code = sea.main_code_or_snapshot;
99+
LoadEnvironment(env, code);
100+
}
99101
}
100102
#endif
103+
// Either there is already a snapshot main function from SEA, or it's not
104+
// a SEA at all.
101105
if (!runs_sea_code) {
102106
LoadEnvironment(env, StartExecutionCallback{});
103107
}

src/node_sea.cc

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
#include "json_parser.h"
77
#include "node_external_reference.h"
88
#include "node_internals.h"
9+
#include "node_snapshot_builder.h"
910
#include "node_union_bytes.h"
11+
#include "node_v8_platform-inl.h"
1012

1113
// The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by
1214
// the Node.js project that is present only once in the entire binary. It is
@@ -27,7 +29,10 @@
2729
using node::ExitCode;
2830
using v8::Context;
2931
using v8::FunctionCallbackInfo;
32+
using v8::HandleScope;
33+
using v8::Isolate;
3034
using v8::Local;
35+
using v8::Locker;
3136
using v8::Object;
3237
using v8::Value;
3338

@@ -64,7 +69,7 @@ class SeaSerializer : public BlobSerializer<SeaSerializer> {
6469

6570
template <>
6671
size_t SeaSerializer::Write(const SeaResource& sea) {
67-
sink.reserve(SeaResource::kHeaderSize + sea.code.size());
72+
sink.reserve(SeaResource::kHeaderSize + sea.main_code_or_snapshot.size());
6873

6974
Debug("Write SEA magic %x\n", kMagic);
7075
size_t written_total = WriteArithmetic<uint32_t>(kMagic);
@@ -75,9 +80,12 @@ size_t SeaSerializer::Write(const SeaResource& sea) {
7580
DCHECK_EQ(written_total, SeaResource::kHeaderSize);
7681

7782
Debug("Write SEA resource code %p, size=%zu\n",
78-
sea.code.data(),
79-
sea.code.size());
80-
written_total += WriteStringView(sea.code, StringLogMode::kAddressAndContent);
83+
sea.main_code_or_snapshot.data(),
84+
sea.main_code_or_snapshot.size());
85+
written_total +=
86+
WriteStringView(sea.main_code_or_snapshot,
87+
sea.use_snapshot() ? StringLogMode::kAddressOnly
88+
: StringLogMode::kAddressAndContent);
8189
return written_total;
8290
}
8391

@@ -103,7 +111,10 @@ SeaResource SeaDeserializer::Read() {
103111
Debug("Read SEA flags %x\n", static_cast<uint32_t>(flags));
104112
CHECK_EQ(read_total, SeaResource::kHeaderSize);
105113

106-
std::string_view code = ReadStringView(StringLogMode::kAddressAndContent);
114+
std::string_view code =
115+
ReadStringView(static_cast<bool>(flags & SeaFlags::kuseSnapshot)
116+
? StringLogMode::kAddressOnly
117+
: StringLogMode::kAddressAndContent);
107118
Debug("Read SEA resource code %p, size=%zu\n", code.data(), code.size());
108119
return {flags, code};
109120
}
@@ -133,6 +144,10 @@ std::string_view FindSingleExecutableBlob() {
133144

134145
} // anonymous namespace
135146

147+
bool SeaResource::use_snapshot() const {
148+
return static_cast<bool>(flags & SeaFlags::kuseSnapshot);
149+
}
150+
136151
SeaResource FindSingleExecutableResource() {
137152
static const SeaResource sea_resource = []() -> SeaResource {
138153
std::string_view blob = FindSingleExecutableBlob();
@@ -235,10 +250,23 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
235250
result.flags |= SeaFlags::kDisableExperimentalSeaWarning;
236251
}
237252

253+
std::optional<bool> use_snapshot = parser.GetTopLevelBoolField("useSnapshot");
254+
if (!use_snapshot.has_value()) {
255+
FPrintF(
256+
stderr, "\"useSnapshot\" field of %s is not a Boolean\n", config_path);
257+
return std::nullopt;
258+
}
259+
if (use_snapshot.value()) {
260+
result.flags |= SeaFlags::kuseSnapshot;
261+
}
262+
238263
return result;
239264
}
240265

241-
ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) {
266+
ExitCode GenerateSingleExecutableBlob(
267+
const SeaConfig& config,
268+
const std::vector<std::string> args,
269+
const std::vector<std::string> exec_args) {
242270
std::string main_script;
243271
// TODO(joyeecheung): unify the file utils.
244272
int r = ReadFileSync(&main_script, config.main_path.c_str());
@@ -248,7 +276,25 @@ ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) {
248276
return ExitCode::kGenericUserError;
249277
}
250278

251-
SeaResource sea{config.flags, main_script};
279+
std::vector<char> snapshot_blob;
280+
bool builds_snapshot_from_main =
281+
static_cast<bool>(config.flags & SeaFlags::kuseSnapshot);
282+
if (builds_snapshot_from_main) {
283+
SnapshotData snapshot;
284+
std::vector<std::string> patched_args = {args[0], GetAnonymousMainPath()};
285+
ExitCode exit_code = SnapshotBuilder::Generate(
286+
&snapshot, patched_args, exec_args, main_script);
287+
if (exit_code != ExitCode::kNoFailure) {
288+
return exit_code;
289+
}
290+
snapshot.ToBlob(&snapshot_blob);
291+
}
292+
293+
SeaResource sea{
294+
config.flags,
295+
builds_snapshot_from_main
296+
? std::string_view{snapshot_blob.data(), snapshot_blob.size()}
297+
: std::string_view{main_script.data(), main_script.size()}};
252298

253299
SeaSerializer serializer;
254300
serializer.Write(sea);
@@ -269,11 +315,14 @@ ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) {
269315

270316
} // anonymous namespace
271317

272-
ExitCode BuildSingleExecutableBlob(const std::string& config_path) {
318+
ExitCode BuildSingleExecutableBlob(const std::string& config_path,
319+
const std::vector<std::string> args,
320+
const std::vector<std::string> exec_args) {
273321
std::optional<SeaConfig> config_opt =
274322
ParseSingleExecutableConfig(config_path);
275323
if (config_opt.has_value()) {
276-
ExitCode code = GenerateSingleExecutableBlob(config_opt.value());
324+
ExitCode code =
325+
GenerateSingleExecutableBlob(config_opt.value(), args, exec_args);
277326
return code;
278327
}
279328

src/node_sea.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,24 @@ const uint32_t kMagic = 0x143da20;
2121
enum class SeaFlags : uint32_t {
2222
kDefault = 0,
2323
kDisableExperimentalSeaWarning = 1 << 0,
24+
kuseSnapshot = 1 << 1,
2425
};
2526

2627
struct SeaResource {
2728
SeaFlags flags = SeaFlags::kDefault;
28-
std::string_view code;
29+
std::string_view main_code_or_snapshot;
2930

31+
bool use_snapshot() const;
3032
static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags);
3133
};
3234

3335
bool IsSingleExecutable();
3436
SeaResource FindSingleExecutableResource();
3537
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv);
36-
node::ExitCode BuildSingleExecutableBlob(const std::string& config_path);
38+
node::ExitCode BuildSingleExecutableBlob(
39+
const std::string& config_path,
40+
const std::vector<std::string> args,
41+
const std::vector<std::string> exec_args);
3742
} // namespace sea
3843
} // namespace node
3944

src/node_snapshotable.cc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ size_t SnapshotSerializer::Write(const SnapshotMetadata& data) {
584584
// [ ... ] env_info
585585
// [ ... ] code_cache
586586

587-
std::vector<char> SnapshotData::ToBlob() const {
587+
void SnapshotData::ToBlob(std::vector<char>* out) const {
588588
SnapshotSerializer w;
589589
w.Debug("SnapshotData::ToBlob()\n");
590590

@@ -603,7 +603,14 @@ std::vector<char> SnapshotData::ToBlob() const {
603603
w.Debug("Write code_cache\n");
604604
written_total += w.WriteVector<builtins::CodeCacheInfo>(code_cache);
605605
w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total);
606-
return w.sink;
606+
607+
*out = std::move(w.sink);
608+
}
609+
610+
std::vector<char> SnapshotData::ToBlob() const {
611+
std::vector<char> result;
612+
ToBlob(&result);
613+
return result;
607614
}
608615

609616
void SnapshotData::ToFile(FILE* out) const {

0 commit comments

Comments
 (0)