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 Mar 2, 2023
1 parent a8c3d03 commit e94ac97
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 6 deletions.
6 changes: 6 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -493,13 +493,17 @@ struct SnapshotMetadata {

struct SnapshotData {
enum class DataOwnership { kOwned, kNotOwned };
// TODO(joyeecheung): consider setting this in the blob. For now it's
// only set at deserialization time.
enum class IsForSea { kDefault, kIsSea };

static const uint32_t kMagic = 0x143da19;
static const SnapshotIndex kNodeVMContextIndex = 0;
static const SnapshotIndex kNodeBaseContextIndex = kNodeVMContextIndex + 1;
static const SnapshotIndex kNodeMainContextIndex = kNodeBaseContextIndex + 1;

DataOwnership data_ownership = DataOwnership::kOwned;
IsForSea is_for_sea = IsForSea::kDefault;

SnapshotMetadata metadata;

Expand All @@ -525,10 +529,12 @@ struct SnapshotData {
bool Check() const;
static bool FromFile(SnapshotData* out, FILE* in);
static bool FromBlob(SnapshotData* out, const std::vector<char>& in);
static bool FromBlob(SnapshotData* out, std::string_view in);
static const SnapshotData* FromEmbedderWrapper(
const EmbedderSnapshotData* data);
EmbedderSnapshotData::Pointer AsEmbedderWrapper() const;

static bool IsSnapshotBlob(std::string_view data);
~SnapshotData();
};

Expand Down
27 changes: 25 additions & 2 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1158,8 +1158,28 @@ ExitCode LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr,
ExitCode exit_code = result->exit_code_enum();
// nullptr indicates there's no snapshot data.
DCHECK_NULL(*snapshot_data_ptr);
// --snapshot-blob indicates that we are reading a customized snapshot.
if (!per_process::cli_options->snapshot_blob.empty()) {

bool has_cli_snapshot_blob = !per_process::cli_options->snapshot_blob.empty();
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
// TODO(joyeecheung): make the snapshot part of the blob, not **the** blob.
// https://github.com/nodejs/single-executable/discussions/58
if (sea::IsSingleExecutable()) {
const std::string_view sea_code = sea::FindSingleExecutableCode();
if (SnapshotData::IsSnapshotBlob(sea_code)) {
std::unique_ptr<SnapshotData> read_data =
std::make_unique<SnapshotData>();
if (SnapshotData::FromBlob(read_data.get(), sea_code)) {
read_data->is_for_sea = SnapshotData::IsForSea::kIsSea;
*snapshot_data_ptr = read_data.release();
} else {
fprintf(stderr, "Invalid snapshot data in single executable binary\n");
return ExitCode::kGenericUserError;
}
}
} else if (has_cli_snapshot_blob) {
#else
if (has_cli_snapshot_blob) {
#endif
std::string filename = per_process::cli_options->snapshot_blob;
FILE* fp = fopen(filename.c_str(), "rb");
if (fp == nullptr) {
Expand Down Expand Up @@ -1239,6 +1259,9 @@ static ExitCode StartInternal(int argc, char** argv) {
return GenerateAndWriteSnapshotData(&snapshot_data, result.get());
}

// TODO(joyeecheung): parse SEA config here and generate a blob that
// can be injected into SEA if a certain flag is set.

// Without --build-snapshot, we are in snapshot loading mode.
return LoadSnapshotDataAndRun(&snapshot_data, result.get());
}
Expand Down
15 changes: 14 additions & 1 deletion src/node_main_instance.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,20 @@ void NodeMainInstance::Run(ExitCode* exit_code, Environment* env) {
if (*exit_code == ExitCode::kNoFailure) {
bool is_sea = false;
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
if (sea::IsSingleExecutable()) {
if (snapshot_data_->is_for_sea == SnapshotData::IsForSea::kIsSea) {
is_sea = true;
if (env->snapshot_deserialize_main().IsEmpty()) {
fprintf(
stderr,
"No deserialized main function found for the snapshot blob found"
" in single executable binary.\n");
*exit_code = ExitCode::kStartupSnapshotFailure;
return;
}
// LoadEnvironment would load and run the deserialized snapshot main
// function.
LoadEnvironment(env, StartExecutionCallback{});
} else if (sea::IsSingleExecutable()) {
is_sea = true;
LoadEnvironment(env, sea::FindSingleExecutableCode());
}
Expand Down
2 changes: 1 addition & 1 deletion src/node_sea.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace sea {
bool IsSingleExecutable();
std::string_view FindSingleExecutableCode();
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv);

std::string_view FindSingleExecutableCode();
} // namespace sea
} // namespace node

Expand Down
21 changes: 19 additions & 2 deletions src/node_snapshotable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class SnapshotSerializerDeserializer {

class SnapshotDeserializer : public SnapshotSerializerDeserializer {
public:
explicit SnapshotDeserializer(const std::vector<char>& s)
explicit SnapshotDeserializer(const std::string_view s)
: SnapshotSerializerDeserializer(), sink(s) {}
~SnapshotDeserializer() {}

Expand Down Expand Up @@ -247,7 +247,7 @@ class SnapshotDeserializer : public SnapshotSerializerDeserializer {
}

size_t read_total = 0;
const std::vector<char>& sink;
const std::string_view sink;

private:
// Helper for reading an array of numeric types.
Expand Down Expand Up @@ -884,10 +884,19 @@ bool SnapshotData::FromFile(SnapshotData* out, FILE* in) {
}

bool SnapshotData::FromBlob(SnapshotData* out, const std::vector<char>& in) {
return FromBlob(out, std::string_view(in.data(), in.size()));
}

bool SnapshotData::FromBlob(SnapshotData* out, const std::string_view in) {
SnapshotDeserializer r(in);
r.Debug("SnapshotData::FromBlob()\n");

DCHECK_EQ(out->data_ownership, SnapshotData::DataOwnership::kOwned);
// For now we don't serialize this bit.
// XXX(joyeecheung): should we require snapshots being injected into
// SEA to be explicitly built for SEA, and check this bit at SEA
// start up?
DCHECK_EQ(out->is_for_sea, SnapshotData::IsForSea::kDefault);

// Metadata
uint32_t magic = r.Read<uint32_t>();
Expand All @@ -910,6 +919,11 @@ bool SnapshotData::FromBlob(SnapshotData* out, const std::vector<char>& in) {
return true;
}

bool SnapshotData::IsSnapshotBlob(const std::string_view data) {
const uint32_t* ptr = reinterpret_cast<const uint32_t*>(data.data());
return (ptr[0] == kMagic);
}

bool SnapshotData::Check() const {
if (metadata.node_version != per_process::metadata.versions.node) {
fprintf(stderr,
Expand Down Expand Up @@ -1041,6 +1055,9 @@ static const int v8_snapshot_blob_size = )"
// -- data_ownership begins --
SnapshotData::DataOwnership::kNotOwned,
// -- data_ownership ends --
// -- is_for_sea begins --
SnapshotData::IsForSea::kDefault,
// -- is_for_sea ends --
// -- metadata begins --
)" << data->metadata
<< R"(,
Expand Down

0 comments on commit e94ac97

Please sign in to comment.