Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: compile code eagerly in snapshot builder #51672

Merged
merged 1 commit into from
Feb 20, 2024
Merged
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/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,9 @@ Maybe<bool> InitializePrimordials(Local<Context> context) {
// relatively cheap and all the scripts that we may want to run at
// startup are always present in it.
thread_local builtins::BuiltinLoader builtin_loader;
// Primordials can always be just eagerly compiled.
builtin_loader.SetEagerCompile();

for (const char** module = context_files; *module != nullptr; module++) {
Local<Value> arguments[] = {exports, primordials};
if (builtin_loader
Expand Down
9 changes: 9 additions & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,15 @@ Environment::Environment(IsolateData* isolate_data,
}
}

// We are supposed to call builtin_loader_.SetEagerCompile() in
// snapshot mode here because it's beneficial to compile built-ins
// loaded in the snapshot eagerly and include the code of inner functions
// that are likely to be used by user since they are part of the core
// startup. But this requires us to start the coverage collections
// before Environment/Context creation which is not currently possible.
// TODO(joyeecheung): refactor V8ProfilerConnection classes to parse
// JSON without v8 and lift this restriction.

// We'll be creating new objects so make sure we've entered the context.
HandleScope handle_scope(isolate);

Expand Down
42 changes: 34 additions & 8 deletions src/node_builtins.cc
Original file line number Diff line number Diff line change
Expand Up @@ -286,15 +286,24 @@ MaybeLocal<Function> BuiltinLoader::LookupAndCompileInternal(
ScriptCompiler::CompileOptions options =
has_cache ? ScriptCompiler::kConsumeCodeCache
: ScriptCompiler::kNoCompileOptions;
if (should_eager_compile_) {
options = ScriptCompiler::kEagerCompile;
} else if (!to_eager_compile_.empty()) {
if (to_eager_compile_.find(id) != to_eager_compile_.end()) {
options = ScriptCompiler::kEagerCompile;
}
}
ScriptCompiler::Source script_source(
source,
origin,
has_cache ? cached_data.AsCachedData().release() : nullptr);

per_process::Debug(DebugCategory::CODE_CACHE,
"Compiling %s %s code cache\n",
id,
has_cache ? "with" : "without");
per_process::Debug(
DebugCategory::CODE_CACHE,
"Compiling %s %s code cache %s\n",
id,
has_cache ? "with" : "without",
options == ScriptCompiler::kEagerCompile ? "eagerly" : "lazily");

MaybeLocal<Function> maybe_fun =
ScriptCompiler::CompileFunction(context,
Expand Down Expand Up @@ -481,14 +490,33 @@ MaybeLocal<Value> BuiltinLoader::CompileAndCall(Local<Context> context,
return fn->Call(context, undefined, argc, argv);
}

bool BuiltinLoader::CompileAllBuiltins(Local<Context> context) {
bool BuiltinLoader::CompileAllBuiltinsAndCopyCodeCache(
Local<Context> context,
const std::vector<std::string>& eager_builtins,
std::vector<CodeCacheInfo>* out) {
std::vector<std::string_view> ids = GetBuiltinIds();
bool all_succeeded = true;
std::string v8_tools_prefix = "internal/deps/v8/tools/";
std::string primordial_prefix = "internal/per_context/";
std::string bootstrap_prefix = "internal/bootstrap/";
std::string main_prefix = "internal/main/";
to_eager_compile_ = std::unordered_set<std::string>(eager_builtins.begin(),
eager_builtins.end());

for (const auto& id : ids) {
if (id.compare(0, v8_tools_prefix.size(), v8_tools_prefix) == 0) {
// No need to generate code cache for v8 scripts.
continue;
}

// Eagerly compile primordials/boostrap/main scripts during code cache
// generation.
if (id.compare(0, primordial_prefix.size(), primordial_prefix) == 0 ||
id.compare(0, bootstrap_prefix.size(), bootstrap_prefix) == 0 ||
id.compare(0, main_prefix.size(), main_prefix) == 0) {
to_eager_compile_.emplace(id);
}

v8::TryCatch bootstrapCatch(context->GetIsolate());
auto fn = LookupAndCompile(context, id.data(), nullptr);
if (bootstrapCatch.HasCaught()) {
Expand All @@ -503,14 +531,12 @@ bool BuiltinLoader::CompileAllBuiltins(Local<Context> context) {
SaveCodeCache(id.data(), fn.ToLocalChecked());
}
}
return all_succeeded;
}

void BuiltinLoader::CopyCodeCache(std::vector<CodeCacheInfo>* out) const {
RwLock::ScopedReadLock lock(code_cache_->mutex);
for (auto const& item : code_cache_->map) {
out->push_back({item.first, item.second});
}
return all_succeeded;
}

void BuiltinLoader::RefreshCodeCache(const std::vector<CodeCacheInfo>& in) {
Expand Down
27 changes: 22 additions & 5 deletions src/node_builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <unordered_set>
#include <vector>
#include "node_external_reference.h"
#include "node_mutex.h"
Expand Down Expand Up @@ -112,12 +112,18 @@ class NODE_EXTERN_PRIVATE BuiltinLoader {
bool Exists(const char* id);
bool Add(const char* id, const UnionBytes& source);

bool CompileAllBuiltins(v8::Local<v8::Context> context);
bool CompileAllBuiltinsAndCopyCodeCache(
v8::Local<v8::Context> context,
const std::vector<std::string>& lazy_builtins,
std::vector<CodeCacheInfo>* out);
void RefreshCodeCache(const std::vector<CodeCacheInfo>& in);
void CopyCodeCache(std::vector<CodeCacheInfo>* out) const;

void CopySourceAndCodeCacheReferenceFrom(const BuiltinLoader* other);

std::vector<std::string_view> GetBuiltinIds() const;

void SetEagerCompile() { should_eager_compile_ = true; }

private:
// Only allow access from friends.
friend class CodeCacheBuilder;
Expand All @@ -126,8 +132,6 @@ class NODE_EXTERN_PRIVATE BuiltinLoader {
void LoadJavaScriptSource(); // Loads data into source_
UnionBytes GetConfig(); // Return data for config.gypi

std::vector<std::string_view> GetBuiltinIds() const;

struct BuiltinCategories {
std::set<std::string> can_be_required;
std::set<std::string> cannot_be_required;
Expand Down Expand Up @@ -179,6 +183,18 @@ class NODE_EXTERN_PRIVATE BuiltinLoader {

const UnionBytes config_;

// If any bulitins should be eagerly compiled i.e. with inner functions
// compiled too, either use should_eager_compile_ to compile all builtins
// eagerly, or use to_eager_compile_ to compile specific builtins eagerly.
// Currently we set should_eager_compile_ to true when compiling primordials,
// and use to_eager_compile_ to compile code cache that complements the
// snapshot, where builtins already loaded in the snapshot and a few extras
// are compiled eagerly (other less-essential built-ins are compiled lazily to
// avoid bloating the binary size). At runtime any additional compilation is
// done lazily.
bool should_eager_compile_ = false;
std::unordered_set<std::string> to_eager_compile_;

struct BuiltinCodeCache {
RwLock mutex;
BuiltinCodeCacheMap map;
Expand All @@ -188,6 +204,7 @@ class NODE_EXTERN_PRIVATE BuiltinLoader {

friend class ::PerProcessTest;
};

} // namespace builtins

} // namespace node
Expand Down
9 changes: 7 additions & 2 deletions src/node_snapshotable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1069,10 +1069,12 @@ ExitCode BuildCodeCacheFromSnapshot(SnapshotData* out,
Context::Scope context_scope(context);
builtins::BuiltinLoader builtin_loader;
// Regenerate all the code cache.
if (!builtin_loader.CompileAllBuiltins(context)) {
if (!builtin_loader.CompileAllBuiltinsAndCopyCodeCache(
context,
out->env_info.principal_realm.builtins,
&(out->code_cache))) {
return ExitCode::kGenericUserError;
}
builtin_loader.CopyCodeCache(&(out->code_cache));
if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
for (const auto& item : out->code_cache) {
std::string size_str = FormatSize(item.data.length);
Expand All @@ -1098,6 +1100,9 @@ ExitCode SnapshotBuilder::Generate(
}

if (!WithoutCodeCache(snapshot_config)) {
per_process::Debug(
DebugCategory::CODE_CACHE,
"---\nGenerate code cache to complement snapshot\n---\n");
// Deserialize the snapshot to recompile code cache. We need to do this in
// the second pass because V8 requires the code cache to be compiled with a
// finalized read-only space.
Expand Down