Skip to content

Commit 0dee86f

Browse files
joyeecheungrichardlau
authored andcommitted
src: support configurable snapshot
- Add support for --build-snapshot-config which allows passing snapshot configurations via a JSON configuration file. - Add support for node::SnapshotConfig in the embedder API The initial configurable options are: - "builder" (SnapshotConfig::builder_script_path): path to the builder script. - "withoutCodeCache" (SnapshotFlags::kWithoutCodeCache): disable code cache generation. PR-URL: #50453 Refs: #42566 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Stephen Belanger <[email protected]>
1 parent 54a29ee commit 0dee86f

17 files changed

+454
-103
lines changed

src/api/embed_helpers.cc

+8-9
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
9292
std::vector<std::string>* errors,
9393
const EmbedderSnapshotData* snapshot_data,
9494
uint32_t flags,
95-
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
95+
std::function<Environment*(const CommonEnvironmentSetup*)> make_env,
96+
const SnapshotConfig* snapshot_config)
9697
: impl_(new Impl()) {
9798
CHECK_NOT_NULL(platform);
9899
CHECK_NOT_NULL(errors);
@@ -142,8 +143,7 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
142143

143144
impl_->isolate_data.reset(CreateIsolateData(
144145
isolate, loop, platform, impl_->allocator.get(), snapshot_data));
145-
impl_->isolate_data->set_is_building_snapshot(
146-
impl_->snapshot_creator.has_value());
146+
impl_->isolate_data->set_snapshot_config(snapshot_config);
147147

148148
if (snapshot_data) {
149149
impl_->env.reset(make_env(this));
@@ -176,7 +176,8 @@ CommonEnvironmentSetup::CreateForSnapshotting(
176176
MultiIsolatePlatform* platform,
177177
std::vector<std::string>* errors,
178178
const std::vector<std::string>& args,
179-
const std::vector<std::string>& exec_args) {
179+
const std::vector<std::string>& exec_args,
180+
const SnapshotConfig& snapshot_config) {
180181
// It's not guaranteed that a context that goes through
181182
// v8_inspector::V8Inspector::contextCreated() is runtime-independent,
182183
// so do not start the inspector on the main context when building
@@ -196,7 +197,8 @@ CommonEnvironmentSetup::CreateForSnapshotting(
196197
args,
197198
exec_args,
198199
static_cast<EnvironmentFlags::Flags>(env_flags));
199-
}));
200+
},
201+
&snapshot_config));
200202
if (!errors->empty()) ret.reset();
201203
return ret;
202204
}
@@ -240,10 +242,7 @@ EmbedderSnapshotData::Pointer CommonEnvironmentSetup::CreateSnapshot() {
240242
EmbedderSnapshotData::Pointer result{
241243
new EmbedderSnapshotData(snapshot_data, true)};
242244

243-
auto exit_code = SnapshotBuilder::CreateSnapshot(
244-
snapshot_data,
245-
this,
246-
static_cast<uint8_t>(SnapshotMetadata::Type::kFullyCustomized));
245+
auto exit_code = SnapshotBuilder::CreateSnapshot(snapshot_data, this);
247246
if (exit_code != ExitCode::kNoFailure) return {};
248247

249248
return result;

src/env.cc

+15-2
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,12 @@ std::ostream& operator<<(std::ostream& output,
289289
return output;
290290
}
291291

292+
std::ostream& operator<<(std::ostream& output, const SnapshotFlags& flags) {
293+
output << "static_cast<SnapshotFlags>(" << static_cast<uint32_t>(flags)
294+
<< ")";
295+
return output;
296+
}
297+
292298
std::ostream& operator<<(std::ostream& output, const SnapshotMetadata& i) {
293299
output << "{\n"
294300
<< " "
@@ -300,6 +306,7 @@ std::ostream& operator<<(std::ostream& output, const SnapshotMetadata& i) {
300306
<< " \"" << i.node_arch << "\", // node_arch\n"
301307
<< " \"" << i.node_platform << "\", // node_platform\n"
302308
<< " " << i.v8_cache_version_tag << ", // v8_cache_version_tag\n"
309+
<< " " << i.flags << ", // flags\n"
303310
<< "}";
304311
return output;
305312
}
@@ -810,8 +817,14 @@ Environment::Environment(IsolateData* isolate_data,
810817
isolate_data->worker_context()->env()->builtin_loader());
811818
} else if (isolate_data->snapshot_data() != nullptr) {
812819
// ... otherwise, if a snapshot was provided, use its code cache.
813-
builtin_loader()->RefreshCodeCache(
814-
isolate_data->snapshot_data()->code_cache);
820+
size_t cache_size = isolate_data->snapshot_data()->code_cache.size();
821+
per_process::Debug(DebugCategory::CODE_CACHE,
822+
"snapshot contains %zu code cache\n",
823+
cache_size);
824+
if (cache_size > 0) {
825+
builtin_loader()->RefreshCodeCache(
826+
isolate_data->snapshot_data()->code_cache);
827+
}
815828
}
816829

817830
// We'll be creating new objects so make sure we've entered the context.

src/env.h

+13-3
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,15 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
147147
void MemoryInfo(MemoryTracker* tracker) const override;
148148
IsolateDataSerializeInfo Serialize(v8::SnapshotCreator* creator);
149149

150-
bool is_building_snapshot() const { return is_building_snapshot_; }
151-
void set_is_building_snapshot(bool value) { is_building_snapshot_ = value; }
150+
bool is_building_snapshot() const { return snapshot_config_.has_value(); }
151+
const SnapshotConfig* snapshot_config() const {
152+
return snapshot_config_.has_value() ? &(snapshot_config_.value()) : nullptr;
153+
}
154+
void set_snapshot_config(const SnapshotConfig* config) {
155+
if (config != nullptr) {
156+
snapshot_config_ = *config; // Copy the config.
157+
}
158+
}
152159

153160
uint16_t* embedder_id_for_cppgc() const;
154161
uint16_t* embedder_id_for_non_cppgc() const;
@@ -237,11 +244,13 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
237244
uv_loop_t* const event_loop_;
238245
NodeArrayBufferAllocator* const node_allocator_;
239246
MultiIsolatePlatform* platform_;
247+
240248
const SnapshotData* snapshot_data_;
249+
std::optional<SnapshotConfig> snapshot_config_;
250+
241251
std::unique_ptr<v8::CppHeap> cpp_heap_;
242252
std::shared_ptr<PerIsolateOptions> options_;
243253
worker::Worker* worker_context_ = nullptr;
244-
bool is_building_snapshot_ = false;
245254
PerIsolateWrapperData* wrapper_data_;
246255

247256
static Mutex isolate_data_mutex_;
@@ -526,6 +535,7 @@ struct SnapshotMetadata {
526535
std::string node_platform;
527536
// Result of v8::ScriptCompiler::CachedDataVersionTag().
528537
uint32_t v8_cache_version_tag;
538+
SnapshotFlags flags;
529539
};
530540

531541
struct SnapshotData {

src/node.cc

+41-10
Original file line numberDiff line numberDiff line change
@@ -1209,10 +1209,39 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,
12091209
// nullptr indicates there's no snapshot data.
12101210
DCHECK_NULL(*snapshot_data_ptr);
12111211

1212+
SnapshotConfig snapshot_config;
1213+
const std::string& config_path =
1214+
per_process::cli_options->per_isolate->build_snapshot_config;
1215+
// For snapshot config read from JSON, we fix up process.argv[1] using the
1216+
// "builder" field.
1217+
std::vector<std::string> args_maybe_patched;
1218+
args_maybe_patched.reserve(result->args().size() + 1);
1219+
if (!config_path.empty()) {
1220+
std::optional<SnapshotConfig> optional_config =
1221+
ReadSnapshotConfig(config_path.c_str());
1222+
if (!optional_config.has_value()) {
1223+
return ExitCode::kGenericUserError;
1224+
}
1225+
snapshot_config = std::move(optional_config.value());
1226+
DCHECK(snapshot_config.builder_script_path.has_value());
1227+
args_maybe_patched.emplace_back(result->args()[0]);
1228+
args_maybe_patched.emplace_back(
1229+
snapshot_config.builder_script_path.value());
1230+
if (result->args().size() > 1) {
1231+
args_maybe_patched.insert(args_maybe_patched.end(),
1232+
result->args().begin() + 1,
1233+
result->args().end());
1234+
}
1235+
} else {
1236+
snapshot_config.builder_script_path = result->args()[1];
1237+
args_maybe_patched = result->args();
1238+
}
1239+
DCHECK(snapshot_config.builder_script_path.has_value());
1240+
const std::string& builder_script =
1241+
snapshot_config.builder_script_path.value();
12121242
// node:embedded_snapshot_main indicates that we are using the
12131243
// embedded snapshot and we are not supposed to clean it up.
1214-
const std::string& main_script = result->args()[1];
1215-
if (main_script == "node:embedded_snapshot_main") {
1244+
if (builder_script == "node:embedded_snapshot_main") {
12161245
*snapshot_data_ptr = SnapshotBuilder::GetEmbeddedSnapshotData();
12171246
if (*snapshot_data_ptr == nullptr) {
12181247
// The Node.js binary is built without embedded snapshot
@@ -1224,24 +1253,25 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,
12241253
return exit_code;
12251254
}
12261255
} else {
1227-
// Otherwise, load and run the specified main script.
1256+
// Otherwise, load and run the specified builder script.
12281257
std::unique_ptr<SnapshotData> generated_data =
12291258
std::make_unique<SnapshotData>();
1230-
std::string main_script_content;
1231-
int r = ReadFileSync(&main_script_content, main_script.c_str());
1259+
std::string builder_script_content;
1260+
int r = ReadFileSync(&builder_script_content, builder_script.c_str());
12321261
if (r != 0) {
12331262
FPrintF(stderr,
1234-
"Cannot read main script %s for building snapshot. %s: %s",
1235-
main_script,
1263+
"Cannot read builder script %s for building snapshot. %s: %s",
1264+
builder_script,
12361265
uv_err_name(r),
12371266
uv_strerror(r));
12381267
return ExitCode::kGenericUserError;
12391268
}
12401269

12411270
exit_code = node::SnapshotBuilder::Generate(generated_data.get(),
1242-
result->args(),
1271+
args_maybe_patched,
12431272
result->exec_args(),
1244-
main_script_content);
1273+
builder_script_content,
1274+
snapshot_config);
12451275
if (exit_code == ExitCode::kNoFailure) {
12461276
*snapshot_data_ptr = generated_data.release();
12471277
} else {
@@ -1371,7 +1401,8 @@ static ExitCode StartInternal(int argc, char** argv) {
13711401

13721402
// --build-snapshot indicates that we are in snapshot building mode.
13731403
if (per_process::cli_options->per_isolate->build_snapshot) {
1374-
if (result->args().size() < 2) {
1404+
if (per_process::cli_options->per_isolate->build_snapshot_config.empty() &&
1405+
result->args().size() < 2) {
13751406
fprintf(stderr,
13761407
"--build-snapshot must be used with an entry point script.\n"
13771408
"Usage: node --build-snapshot /path/to/entry.js\n");

src/node.h

+32-2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080

8181
#include <functional>
8282
#include <memory>
83+
#include <optional>
8384
#include <ostream>
8485

8586
// We cannot use __POSIX__ in this header because that's only defined when
@@ -659,6 +660,33 @@ enum Flags : uint64_t {
659660
};
660661
} // namespace EnvironmentFlags
661662

663+
enum class SnapshotFlags : uint32_t {
664+
kDefault = 0,
665+
// Whether code cache should be generated as part of the snapshot.
666+
// Code cache reduces the time spent on compiling functions included
667+
// in the snapshot at the expense of a bigger snapshot size and
668+
// potentially breaking portability of the snapshot.
669+
kWithoutCodeCache = 1 << 0,
670+
};
671+
672+
struct SnapshotConfig {
673+
SnapshotFlags flags = SnapshotFlags::kDefault;
674+
675+
// When builder_script_path is std::nullopt, the snapshot is generated as a
676+
// built-in snapshot instead of a custom one, and it's expected that the
677+
// built-in snapshot only contains states that reproduce in every run of the
678+
// application. The event loop won't be run when generating a built-in
679+
// snapshot, so asynchronous operations should be avoided.
680+
//
681+
// When builder_script_path is an std::string, it should match args[1]
682+
// passed to CreateForSnapshotting(). The embedder is also expected to use
683+
// LoadEnvironment() to run a script matching this path. In that case the
684+
// snapshot is generated as a custom snapshot and the event loop is run, so
685+
// the snapshot builder can execute asynchronous operations as long as they
686+
// are run to completion when the snapshot is taken.
687+
std::optional<std::string> builder_script_path;
688+
};
689+
662690
struct InspectorParentHandle {
663691
virtual ~InspectorParentHandle() = default;
664692
};
@@ -870,7 +898,8 @@ class NODE_EXTERN CommonEnvironmentSetup {
870898
MultiIsolatePlatform* platform,
871899
std::vector<std::string>* errors,
872900
const std::vector<std::string>& args = {},
873-
const std::vector<std::string>& exec_args = {});
901+
const std::vector<std::string>& exec_args = {},
902+
const SnapshotConfig& snapshot_config = {});
874903
EmbedderSnapshotData::Pointer CreateSnapshot();
875904

876905
struct uv_loop_s* event_loop() const;
@@ -905,7 +934,8 @@ class NODE_EXTERN CommonEnvironmentSetup {
905934
std::vector<std::string>*,
906935
const EmbedderSnapshotData*,
907936
uint32_t flags,
908-
std::function<Environment*(const CommonEnvironmentSetup*)>);
937+
std::function<Environment*(const CommonEnvironmentSetup*)>,
938+
const SnapshotConfig* config = nullptr);
909939
};
910940

911941
// Implementation for CommonEnvironmentSetup::Create

src/node_internals.h

+1
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ std::string Basename(const std::string& str, const std::string& extension);
417417

418418
node_module napi_module_to_node_module(const napi_module* mod);
419419

420+
std::ostream& operator<<(std::ostream& output, const SnapshotFlags& flags);
420421
std::ostream& operator<<(std::ostream& output,
421422
const std::vector<SnapshotIndex>& v);
422423
std::ostream& operator<<(std::ostream& output,

src/node_main_instance.cc

-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ NodeMainInstance::NodeMainInstance(const SnapshotData* snapshot_data,
5656
platform,
5757
array_buffer_allocator_.get(),
5858
snapshot_data->AsEmbedderWrapper().get()));
59-
isolate_data_->set_is_building_snapshot(
60-
per_process::cli_options->per_isolate->build_snapshot);
6159

6260
isolate_data_->max_young_gen_size =
6361
isolate_params_->constraints.max_young_generation_size_in_bytes();

src/node_options.cc

+6
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,12 @@ PerIsolateOptionsParser::PerIsolateOptionsParser(
849849
"Generate a snapshot blob when the process exits.",
850850
&PerIsolateOptions::build_snapshot,
851851
kDisallowedInEnvvar);
852+
AddOption("--build-snapshot-config",
853+
"Generate a snapshot blob when the process exits using a"
854+
"JSON configuration in the specified path.",
855+
&PerIsolateOptions::build_snapshot_config,
856+
kDisallowedInEnvvar);
857+
Implies("--build-snapshot-config", "--build-snapshot");
852858

853859
Insert(eop, &PerIsolateOptions::get_per_env_options);
854860
}

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ class PerIsolateOptions : public Options {
235235
bool experimental_shadow_realm = false;
236236
std::string report_signal = "SIGUSR2";
237237
bool build_snapshot = false;
238+
std::string build_snapshot_config;
238239
inline EnvironmentOptions* get_per_env_options();
239240
void CheckOptions(std::vector<std::string>* errors,
240241
std::vector<std::string>* argv) override;

src/node_sea.cc

+11-4
Original file line numberDiff line numberDiff line change
@@ -377,14 +377,18 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
377377
ExitCode GenerateSnapshotForSEA(const SeaConfig& config,
378378
const std::vector<std::string>& args,
379379
const std::vector<std::string>& exec_args,
380-
const std::string& main_script,
380+
const std::string& builder_script_content,
381+
const SnapshotConfig& snapshot_config,
381382
std::vector<char>* snapshot_blob) {
382383
SnapshotData snapshot;
383384
// TODO(joyeecheung): make the arguments configurable through the JSON
384385
// config or a programmatic API.
385386
std::vector<std::string> patched_args = {args[0], config.main_path};
386-
ExitCode exit_code = SnapshotBuilder::Generate(
387-
&snapshot, patched_args, exec_args, main_script);
387+
ExitCode exit_code = SnapshotBuilder::Generate(&snapshot,
388+
patched_args,
389+
exec_args,
390+
builder_script_content,
391+
snapshot_config);
388392
if (exit_code != ExitCode::kNoFailure) {
389393
return exit_code;
390394
}
@@ -481,8 +485,11 @@ ExitCode GenerateSingleExecutableBlob(
481485
bool builds_snapshot_from_main =
482486
static_cast<bool>(config.flags & SeaFlags::kUseSnapshot);
483487
if (builds_snapshot_from_main) {
488+
// TODO(joyeecheung): allow passing snapshot configuration in SEA configs.
489+
SnapshotConfig snapshot_config;
490+
snapshot_config.builder_script_path = main_script;
484491
ExitCode exit_code = GenerateSnapshotForSEA(
485-
config, args, exec_args, main_script, &snapshot_blob);
492+
config, args, exec_args, main_script, snapshot_config, &snapshot_blob);
486493
if (exit_code != ExitCode::kNoFailure) {
487494
return exit_code;
488495
}

src/node_snapshot_builder.h

+17-14
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,25 @@ namespace node {
1616
class ExternalReferenceRegistry;
1717
struct SnapshotData;
1818

19+
std::optional<SnapshotConfig> ReadSnapshotConfig(const char* path);
20+
1921
class NODE_EXTERN_PRIVATE SnapshotBuilder {
2022
public:
21-
static ExitCode GenerateAsSource(
22-
const char* out_path,
23+
static ExitCode GenerateAsSource(const char* out_path,
24+
const std::vector<std::string>& args,
25+
const std::vector<std::string>& exec_args,
26+
const SnapshotConfig& config,
27+
bool use_array_literals = false);
28+
29+
// Generate the snapshot into out. builder_script_content should match
30+
// config.builder_script_path. This is passed separately
31+
// in case the script is already read for other purposes.
32+
static ExitCode Generate(
33+
SnapshotData* out,
2334
const std::vector<std::string>& args,
2435
const std::vector<std::string>& exec_args,
25-
std::optional<std::string_view> main_script_path = std::nullopt,
26-
bool use_array_literals = false);
27-
28-
// Generate the snapshot into out.
29-
static ExitCode Generate(SnapshotData* out,
30-
const std::vector<std::string>& args,
31-
const std::vector<std::string>& exec_args,
32-
std::optional<std::string_view> main_script);
36+
std::optional<std::string_view> builder_script_content,
37+
const SnapshotConfig& config);
3338

3439
// If nullptr is returned, the binary is not built with embedded
3540
// snapshot.
@@ -39,10 +44,8 @@ class NODE_EXTERN_PRIVATE SnapshotBuilder {
3944

4045
static const std::vector<intptr_t>& CollectExternalReferences();
4146

42-
static ExitCode CreateSnapshot(
43-
SnapshotData* out,
44-
CommonEnvironmentSetup* setup,
45-
/*SnapshotMetadata::Type*/ uint8_t snapshot_type);
47+
static ExitCode CreateSnapshot(SnapshotData* out,
48+
CommonEnvironmentSetup* setup);
4649

4750
private:
4851
static std::unique_ptr<ExternalReferenceRegistry> registry_;

0 commit comments

Comments
 (0)