Skip to content

Commit

Permalink
src: support snapshot in single executable applications
Browse files Browse the repository at this point in the history
  • Loading branch information
joyeecheung committed Jun 10, 2023
1 parent 718f62b commit 8dec8b7
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 40 deletions.
2 changes: 1 addition & 1 deletion lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ function patchProcessObject(expandArgv1) {
__proto__: null,
enumerable: true,
// Only set it to true during snapshot building.
configurable: getOptionValue('--build-snapshot'),
configurable: isBuildingSnapshot(),
value: process.argv[0],
});

Expand Down
1 change: 1 addition & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ struct SnapshotData {

void ToFile(FILE* out) const;
std::vector<char> ToBlob() const;
void ToBlob(std::vector<char>* out) const;
// If returns false, the metadata doesn't match the current Node.js binary,
// and the caller should not consume the snapshot data.
bool Check() const;
Expand Down
87 changes: 64 additions & 23 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,21 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {

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

#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
if (sea::IsSingleExecutable()) {
sea::SeaResource sea = sea::FindSingleExecutableResource();
if (sea.use_snapshot()) {
if (env->snapshot_deserialize_main().IsEmpty()) {
fprintf(
stderr,
"No deserialized main function found for the snapshot blob found"
" in single executable binary.\n");
return MaybeLocal<Value>();
}
}
}
#endif

// TODO(joyeecheung): move these conditions into JS land and let the
// deserialize main function take precedence. For workers, we need to
// move the pre-execution part into a different file that can be
Expand Down Expand Up @@ -1176,49 +1191,66 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,
return exit_code;
}

ExitCode LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr,
const InitializationResultImpl* result) {
ExitCode exit_code = result->exit_code_enum();
bool LoadSnapshotData(const SnapshotData** snapshot_data_ptr) {
// nullptr indicates there's no snapshot data.
DCHECK_NULL(*snapshot_data_ptr);

bool is_sea = false;
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
if (sea::IsSingleExecutable()) {
is_sea = true;
sea::SeaResource sea = sea::FindSingleExecutableResource();
if (sea.use_snapshot()) {
std::unique_ptr<SnapshotData> read_data =
std::make_unique<SnapshotData>();
std::string_view snapshot = sea.main_code_or_snapshot;
if (SnapshotData::FromBlob(read_data.get(), snapshot)) {
*snapshot_data_ptr = read_data.release();
return true;
} else {
fprintf(stderr, "Invalid snapshot data in single executable binary\n");
return false;
}
}
}
#endif

// --snapshot-blob indicates that we are reading a customized snapshot.
if (!per_process::cli_options->snapshot_blob.empty()) {
// Ignore it when we are loading from SEA.
if (!is_sea && !per_process::cli_options->snapshot_blob.empty()) {
std::string filename = per_process::cli_options->snapshot_blob;
FILE* fp = fopen(filename.c_str(), "rb");
if (fp == nullptr) {
fprintf(stderr, "Cannot open %s", filename.c_str());
exit_code = ExitCode::kStartupSnapshotFailure;
return exit_code;
return false;
}
std::unique_ptr<SnapshotData> read_data = std::make_unique<SnapshotData>();
bool ok = SnapshotData::FromFile(read_data.get(), fp);
fclose(fp);
if (!ok) {
// If we fail to read the customized snapshot,
// simply exit with kStartupSnapshotFailure.
exit_code = ExitCode::kStartupSnapshotFailure;
return exit_code;
return false;
}
*snapshot_data_ptr = read_data.release();
} else if (per_process::cli_options->node_snapshot) {
// If --snapshot-blob is not specified, we are reading the embedded
// snapshot, but we will skip it if --no-node-snapshot is specified.
return true;
}

if (per_process::cli_options->node_snapshot) {
// If --snapshot-blob is not specified or if the SEA contains not snapshot,
// we are reading the embedded snapshot, but we will skip it if
// --no-node-snapshot is specified.
const node::SnapshotData* read_data =
SnapshotBuilder::GetEmbeddedSnapshotData();
if (read_data != nullptr && read_data->Check()) {
if (read_data != nullptr) {
if (!read_data->Check()) {
return false;
}
// If we fail to read the embedded snapshot, treat it as if Node.js
// was built without one.
*snapshot_data_ptr = read_data;
}
}

NodeMainInstance main_instance(*snapshot_data_ptr,
uv_default_loop(),
per_process::v8_platform.Platform(),
result->args(),
result->exec_args());
exit_code = main_instance.Run();
return exit_code;
return true;
}

static ExitCode StartInternal(int argc, char** argv) {
Expand Down Expand Up @@ -1253,7 +1285,8 @@ static ExitCode StartInternal(int argc, char** argv) {

std::string sea_config = per_process::cli_options->experimental_sea_config;
if (!sea_config.empty()) {
return sea::BuildSingleExecutableBlob(sea_config);
return sea::BuildSingleExecutableBlob(
sea_config, result->args(), result->exec_args());
}

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

// Without --build-snapshot, we are in snapshot loading mode.
return LoadSnapshotDataAndRun(&snapshot_data, result.get());
if (!LoadSnapshotData(&snapshot_data)) {
return ExitCode::kStartupSnapshotFailure;
}
NodeMainInstance main_instance(snapshot_data,
uv_default_loop(),
per_process::v8_platform.Platform(),
result->args(),
result->exec_args());
return main_instance.Run();
}

int Start(int argc, char** argv) {
Expand Down
10 changes: 7 additions & 3 deletions src/node_main_instance.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,16 @@ void NodeMainInstance::Run(ExitCode* exit_code, Environment* env) {
bool runs_sea_code = false;
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
if (sea::IsSingleExecutable()) {
runs_sea_code = true;
sea::SeaResource sea = sea::FindSingleExecutableResource();
std::string_view code = sea.code;
LoadEnvironment(env, code);
if (!sea.use_snapshot()) {
runs_sea_code = true;
std::string_view code = sea.main_code_or_snapshot;
LoadEnvironment(env, code);
}
}
#endif
// Either there is already a snapshot main function from SEA, or it's not
// a SEA at all.
if (!runs_sea_code) {
LoadEnvironment(env, StartExecutionCallback{});
}
Expand Down
67 changes: 58 additions & 9 deletions src/node_sea.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#include "json_parser.h"
#include "node_external_reference.h"
#include "node_internals.h"
#include "node_snapshot_builder.h"
#include "node_union_bytes.h"
#include "node_v8_platform-inl.h"

// The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by
// the Node.js project that is present only once in the entire binary. It is
Expand All @@ -27,7 +29,10 @@
using node::ExitCode;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Locker;
using v8::Object;
using v8::Value;

Expand Down Expand Up @@ -64,7 +69,7 @@ class SeaSerializer : public BlobSerializer<SeaSerializer> {

template <>
size_t SeaSerializer::Write(const SeaResource& sea) {
sink.reserve(SeaResource::kHeaderSize + sea.code.size());
sink.reserve(SeaResource::kHeaderSize + sea.main_code_or_snapshot.size());

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

Debug("Write SEA resource code %p, size=%zu\n",
sea.code.data(),
sea.code.size());
written_total += WriteStringView(sea.code, StringLogMode::kAddressAndContent);
sea.main_code_or_snapshot.data(),
sea.main_code_or_snapshot.size());
written_total +=
WriteStringView(sea.main_code_or_snapshot,
sea.use_snapshot() ? StringLogMode::kAddressOnly
: StringLogMode::kAddressAndContent);
return written_total;
}

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

std::string_view code = ReadStringView(StringLogMode::kAddressAndContent);
std::string_view code =
ReadStringView(static_cast<bool>(flags & SeaFlags::kuseSnapshot)
? StringLogMode::kAddressOnly
: StringLogMode::kAddressAndContent);
Debug("Read SEA resource code %p, size=%zu\n", code.data(), code.size());
return {flags, code};
}
Expand Down Expand Up @@ -133,6 +144,10 @@ std::string_view FindSingleExecutableBlob() {

} // anonymous namespace

bool SeaResource::use_snapshot() const {
return static_cast<bool>(flags & SeaFlags::kuseSnapshot);
}

SeaResource FindSingleExecutableResource() {
static const SeaResource sea_resource = []() -> SeaResource {
std::string_view blob = FindSingleExecutableBlob();
Expand Down Expand Up @@ -235,10 +250,23 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
result.flags |= SeaFlags::kDisableExperimentalSeaWarning;
}

std::optional<bool> use_snapshot = parser.GetTopLevelBoolField("useSnapshot");
if (!use_snapshot.has_value()) {
FPrintF(
stderr, "\"useSnapshot\" field of %s is not a Boolean\n", config_path);
return std::nullopt;
}
if (use_snapshot.value()) {
result.flags |= SeaFlags::kuseSnapshot;
}

return result;
}

ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) {
ExitCode GenerateSingleExecutableBlob(
const SeaConfig& config,
const std::vector<std::string> args,
const std::vector<std::string> exec_args) {
std::string main_script;
// TODO(joyeecheung): unify the file utils.
int r = ReadFileSync(&main_script, config.main_path.c_str());
Expand All @@ -248,7 +276,25 @@ ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) {
return ExitCode::kGenericUserError;
}

SeaResource sea{config.flags, main_script};
std::vector<char> snapshot_blob;
bool builds_snapshot_from_main =
static_cast<bool>(config.flags & SeaFlags::kuseSnapshot);
if (builds_snapshot_from_main) {
SnapshotData snapshot;
std::vector<std::string> patched_args = {args[0], GetAnonymousMainPath()};
ExitCode exit_code = SnapshotBuilder::Generate(
&snapshot, patched_args, exec_args, main_script);
if (exit_code != ExitCode::kNoFailure) {
return exit_code;
}
snapshot.ToBlob(&snapshot_blob);
}

SeaResource sea{
config.flags,
builds_snapshot_from_main
? std::string_view{snapshot_blob.data(), snapshot_blob.size()}
: std::string_view{main_script.data(), main_script.size()}};

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

} // anonymous namespace

ExitCode BuildSingleExecutableBlob(const std::string& config_path) {
ExitCode BuildSingleExecutableBlob(const std::string& config_path,
const std::vector<std::string> args,
const std::vector<std::string> exec_args) {
std::optional<SeaConfig> config_opt =
ParseSingleExecutableConfig(config_path);
if (config_opt.has_value()) {
ExitCode code = GenerateSingleExecutableBlob(config_opt.value());
ExitCode code =
GenerateSingleExecutableBlob(config_opt.value(), args, exec_args);
return code;
}

Expand Down
9 changes: 7 additions & 2 deletions src/node_sea.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,24 @@ const uint32_t kMagic = 0x143da20;
enum class SeaFlags : uint32_t {
kDefault = 0,
kDisableExperimentalSeaWarning = 1 << 0,
kuseSnapshot = 1 << 1,
};

struct SeaResource {
SeaFlags flags = SeaFlags::kDefault;
std::string_view code;
std::string_view main_code_or_snapshot;

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

bool IsSingleExecutable();
SeaResource FindSingleExecutableResource();
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv);
node::ExitCode BuildSingleExecutableBlob(const std::string& config_path);
node::ExitCode BuildSingleExecutableBlob(
const std::string& config_path,
const std::vector<std::string> args,
const std::vector<std::string> exec_args);
} // namespace sea
} // namespace node

Expand Down
11 changes: 9 additions & 2 deletions src/node_snapshotable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ size_t SnapshotSerializer::Write(const SnapshotMetadata& data) {
// [ ... ] env_info
// [ ... ] code_cache

std::vector<char> SnapshotData::ToBlob() const {
void SnapshotData::ToBlob(std::vector<char>* out) const {
SnapshotSerializer w;
w.Debug("SnapshotData::ToBlob()\n");

Expand All @@ -603,7 +603,14 @@ std::vector<char> SnapshotData::ToBlob() const {
w.Debug("Write code_cache\n");
written_total += w.WriteVector<builtins::CodeCacheInfo>(code_cache);
w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total);
return w.sink;

*out = std::move(w.sink);
}

std::vector<char> SnapshotData::ToBlob() const {
std::vector<char> result;
ToBlob(&result);
return result;
}

void SnapshotData::ToFile(FILE* out) const {
Expand Down
Loading

0 comments on commit 8dec8b7

Please # to comment.