Files
node/src/node_builtins.h
T
Joyee Cheung 3e57b93963 src: compile code eagerly in snapshot builder
By default V8 only compiles the top-level function and
skips code generation for inner functions - that would
only be done when those inner functions are invoked.
Since builtins are compiled as wrapped functions, most
functions that look visually top-level are not actually
included in the built-in code cache. For most of the
builtins this is not too bad because usually only a subset of
all builtin functions are needed by a particular
application and including all their code in the binary
would incur an unnecessary size overhead. But there is also
a subset of more commonly used builtins and it would be
better to include the inner functions in the built-in
code cache because they are more universally used by
most applications.

This patch changes the compilation strategy to eager compilation
(including inner functions) for the following scripts:

1. Primordials (internal/per_context/*), in all situations.
2. Bootstrap scripts (internal/bootstrap/*) and main scripts
   (internal/main/*), when being compiled for built-in code
   cache.
3. Any scripts loaded during built-in snapshot generation.

We can't compile the code eagerly during snapshot generation
and include them into the V8 snapshot itself just now because
we need to start the inspector before context deserialization
for coverage collection to work. So leave that as a TODO.

With this patch the binary size increases by about 666KB
(~0.6% increase) in return the worker startup can be 18-19% faster.

PR-URL: https://github.com/nodejs/node/pull/51672
Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
2024-02-20 21:40:12 +00:00

215 lines
8.0 KiB
C++

#ifndef SRC_NODE_BUILTINS_H_
#define SRC_NODE_BUILTINS_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <unordered_set>
#include <vector>
#include "node_external_reference.h"
#include "node_mutex.h"
#include "node_threadsafe_cow.h"
#include "node_union_bytes.h"
#include "v8.h"
// Forward declare test fixture for `friend` declaration.
class PerProcessTest;
namespace node {
class SnapshotBuilder;
class ExternalReferenceRegistry;
class Realm;
namespace builtins {
class BuiltinCodeCacheData {
public:
BuiltinCodeCacheData() : data(nullptr), length(0), owning_ptr(nullptr) {}
explicit BuiltinCodeCacheData(
std::shared_ptr<v8::ScriptCompiler::CachedData> cached_data)
: data(cached_data->data),
length(cached_data->length),
owning_ptr(cached_data) {}
explicit BuiltinCodeCacheData(
std::shared_ptr<std::vector<uint8_t>> cached_data)
: data(cached_data->data()),
length(cached_data->size()),
owning_ptr(cached_data) {}
BuiltinCodeCacheData(const uint8_t* data, size_t length)
: data(data), length(length), owning_ptr(nullptr) {}
const uint8_t* data;
size_t length;
// Returns a v8::ScriptCompiler::CachedData corresponding to this
// BuiltinCodeCacheData. The lifetime of the returned
// v8::ScriptCompiler::CachedData must not outlive that of the data.
std::unique_ptr<v8::ScriptCompiler::CachedData> AsCachedData() {
return std::make_unique<v8::ScriptCompiler::CachedData>(
data, length, v8::ScriptCompiler::CachedData::BufferNotOwned);
}
private:
// If not null, represents a pointer which owns data. Otherwise indicates
// that data has static lifetime.
std::shared_ptr<void> owning_ptr;
};
struct CodeCacheInfo {
std::string id;
BuiltinCodeCacheData data;
};
using BuiltinSourceMap = std::map<std::string, UnionBytes>;
using BuiltinCodeCacheMap =
std::unordered_map<std::string, BuiltinCodeCacheData>;
// Generated by tools/js2c.py as node_javascript.cc
void RegisterExternalReferencesForInternalizedBuiltinCode(
ExternalReferenceRegistry* registry);
// Handles compilation and caching of built-in JavaScript modules and
// bootstrap scripts, whose source are bundled into the binary as static data.
class NODE_EXTERN_PRIVATE BuiltinLoader {
public:
BuiltinLoader();
BuiltinLoader(const BuiltinLoader&) = delete;
BuiltinLoader& operator=(const BuiltinLoader&) = delete;
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
static void CreatePerIsolateProperties(IsolateData* isolate_data,
v8::Local<v8::ObjectTemplate> target);
static void CreatePerContextProperties(v8::Local<v8::Object> target,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv);
// The parameters used to compile the scripts are detected based on
// the pattern of the id.
v8::MaybeLocal<v8::Function> LookupAndCompile(v8::Local<v8::Context> context,
const char* id,
Realm* optional_realm);
v8::MaybeLocal<v8::Value> CompileAndCall(v8::Local<v8::Context> context,
const char* id,
int argc,
v8::Local<v8::Value> argv[],
Realm* optional_realm);
v8::MaybeLocal<v8::Value> CompileAndCall(v8::Local<v8::Context> context,
const char* id,
Realm* realm);
// Returns config.gypi as a JSON string
v8::Local<v8::String> GetConfigString(v8::Isolate* isolate);
bool Exists(const char* id);
bool Add(const char* id, const UnionBytes& source);
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 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;
// Generated by tools/js2c.py as node_javascript.cc
void LoadJavaScriptSource(); // Loads data into source_
UnionBytes GetConfig(); // Return data for config.gypi
struct BuiltinCategories {
std::set<std::string> can_be_required;
std::set<std::string> cannot_be_required;
};
// This method builds `BuiltinCategories` from scratch every time,
// and is therefore somewhat expensive, but also currently only being
// used for testing, so that should not be an issue.
BuiltinCategories GetBuiltinCategories() const;
const v8::ScriptCompiler::CachedData* GetCodeCache(const char* id) const;
enum class Result { kWithCache, kWithoutCache };
v8::MaybeLocal<v8::String> LoadBuiltinSource(v8::Isolate* isolate,
const char* id) const;
// If an exception is encountered (e.g. source code contains
// syntax error), the returned value is empty.
v8::MaybeLocal<v8::Function> LookupAndCompileInternal(
v8::Local<v8::Context> context,
const char* id,
std::vector<v8::Local<v8::String>>* parameters,
Realm* optional_realm);
void SaveCodeCache(const char* id, v8::Local<v8::Function> fn);
static void RecordResult(const char* id,
BuiltinLoader::Result result,
Realm* realm);
static void GetBuiltinCategories(
v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info);
static void GetCacheUsage(const v8::FunctionCallbackInfo<v8::Value>& args);
// Passing ids of built-in source code into JS land as
// internalBinding('builtins').builtinIds
static void BuiltinIdsGetter(v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info);
// Passing config.gypi into JS land as internalBinding('builtins').config
static void ConfigStringGetter(
v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info);
// Compile a specific built-in as a function
static void CompileFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
static void HasCachedBuiltins(
const v8::FunctionCallbackInfo<v8::Value>& args);
// For legacy process.binding('natives')
static void GetNatives(v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info);
void AddExternalizedBuiltin(const char* id, const char* filename);
ThreadsafeCopyOnWrite<BuiltinSourceMap> source_;
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;
bool has_code_cache = false;
};
std::shared_ptr<BuiltinCodeCache> code_cache_;
friend class ::PerProcessTest;
};
} // namespace builtins
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_NODE_BUILTINS_H_