bootstrap: include code cache in the embedded snapshot
Since V8 code cache encodes indices to the read-only space it is safer to make sure that the code cache is generated in the same heap used to generate the embdded snapshot. This patch merges the code cache builder into the snapshot builder and makes the code cache part of node::SnapshotData that is deserialized into the native module loader during bootstrap. PR-URL: https://github.com/nodejs/node/pull/43023 Fixes: https://github.com/nodejs/node/issues/31074 Refs: https://github.com/nodejs/node/issues/35711 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
parent
de0b6dc710
commit
fbe1478077
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -107,10 +107,8 @@
|
||||
|
||||
/benchmark/misc/startup.js @nodejs/startup
|
||||
/src/node.cc @nodejs/startup
|
||||
/src/node_code_cache_stub.cc @nodejs/startup
|
||||
/src/node_native_module* @nodejs/startup
|
||||
/lib/internal/bootstrap/* @nodejs/startup
|
||||
/tools/code_cache/* @nodejs/startup
|
||||
/tools/snapshot/* @nodejs/startup
|
||||
|
||||
# V8
|
||||
|
@ -1249,7 +1249,7 @@ def configure_node(o):
|
||||
o['variables']['node_use_node_snapshot'] = b(
|
||||
not cross_compiling and not options.shared)
|
||||
|
||||
if options.without_node_code_cache or options.node_builtin_modules_path:
|
||||
if options.without_node_code_cache or options.without_node_snapshot or options.node_builtin_modules_path:
|
||||
o['variables']['node_use_node_code_cache'] = 'false'
|
||||
else:
|
||||
# TODO(refack): fix this when implementing embedded code-cache when cross-compiling.
|
||||
|
@ -270,9 +270,8 @@ const features = {
|
||||
tls_sni: hasOpenSSL,
|
||||
tls_ocsp: hasOpenSSL,
|
||||
tls: hasOpenSSL,
|
||||
// This needs to be dynamic because snapshot is built without code cache.
|
||||
// TODO(joyeecheung): https://github.com/nodejs/node/issues/31074
|
||||
// Make it possible to build snapshot with code cache
|
||||
// This needs to be dynamic because --no-node-snapshot disables the
|
||||
// code cache even if the binary is built with embedded code cache.
|
||||
get cached_builtins() {
|
||||
return nativeModule.hasCachedBuiltins();
|
||||
}
|
||||
|
104
node.gyp
104
node.gyp
@ -55,7 +55,6 @@
|
||||
'deps/undici/undici.js',
|
||||
],
|
||||
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
|
||||
'mkcodecache_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)mkcodecache<(EXECUTABLE_SUFFIX)',
|
||||
'conditions': [
|
||||
['GENERATOR == "ninja"', {
|
||||
'node_text_start_object_path': 'src/large_pages/node_text_start.node_text_start.o'
|
||||
@ -304,31 +303,6 @@
|
||||
},
|
||||
},
|
||||
}],
|
||||
['node_use_node_code_cache=="true"', {
|
||||
'dependencies': [
|
||||
'mkcodecache',
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'run_mkcodecache',
|
||||
'process_outputs_as_sources': 1,
|
||||
'inputs': [
|
||||
'<(mkcodecache_exec)',
|
||||
],
|
||||
'outputs': [
|
||||
'<(SHARED_INTERMEDIATE_DIR)/node_code_cache.cc',
|
||||
],
|
||||
'action': [
|
||||
'<@(_inputs)',
|
||||
'<@(_outputs)',
|
||||
],
|
||||
},
|
||||
],
|
||||
}, {
|
||||
'sources': [
|
||||
'src/node_code_cache_stub.cc'
|
||||
],
|
||||
}],
|
||||
['node_use_node_snapshot=="true"', {
|
||||
'dependencies': [
|
||||
'node_mksnapshot',
|
||||
@ -737,7 +711,6 @@
|
||||
[ 'node_shared=="true"', {
|
||||
'sources': [
|
||||
'src/node_snapshot_stub.cc',
|
||||
'src/node_code_cache_stub.cc',
|
||||
]
|
||||
}],
|
||||
[ 'node_shared=="true" and node_module_version!="" and OS!="win"', {
|
||||
@ -747,6 +720,11 @@
|
||||
'@rpath/lib<(node_core_target_name).<(shlib_suffix)'
|
||||
},
|
||||
}],
|
||||
[ 'node_use_node_code_cache=="true"', {
|
||||
'defines': [
|
||||
'NODE_USE_NODE_CODE_CACHE=1',
|
||||
],
|
||||
}],
|
||||
['node_shared=="true" and OS=="aix"', {
|
||||
'product_name': 'node_base',
|
||||
}],
|
||||
@ -1147,7 +1125,6 @@
|
||||
],
|
||||
'sources': [
|
||||
'src/node_snapshot_stub.cc',
|
||||
'src/node_code_cache_stub.cc',
|
||||
'test/fuzzers/fuzz_url.cc',
|
||||
],
|
||||
'conditions': [
|
||||
@ -1190,7 +1167,6 @@
|
||||
],
|
||||
'sources': [
|
||||
'src/node_snapshot_stub.cc',
|
||||
'src/node_code_cache_stub.cc',
|
||||
'test/fuzzers/fuzz_env.cc',
|
||||
],
|
||||
'conditions': [
|
||||
@ -1240,7 +1216,6 @@
|
||||
|
||||
'sources': [
|
||||
'src/node_snapshot_stub.cc',
|
||||
'src/node_code_cache_stub.cc',
|
||||
'test/cctest/node_test_fixture.cc',
|
||||
'test/cctest/node_test_fixture.h',
|
||||
'test/cctest/test_aliased_buffer.cc',
|
||||
@ -1333,7 +1308,6 @@
|
||||
|
||||
'sources': [
|
||||
'src/node_snapshot_stub.cc',
|
||||
'src/node_code_cache_stub.cc',
|
||||
'test/embedding/embedtest.cc',
|
||||
],
|
||||
|
||||
@ -1377,68 +1351,6 @@
|
||||
}],
|
||||
]
|
||||
}, # overlapped-checker
|
||||
|
||||
# TODO(joyeecheung): do not depend on node_lib,
|
||||
# instead create a smaller static library node_lib_base that does
|
||||
# just enough for node_native_module.cc and the cache builder to
|
||||
# compile without compiling the generated code cache C++ file.
|
||||
# So generate_code_cache -> mkcodecache -> node_lib_base,
|
||||
# node_lib -> node_lib_base & generate_code_cache
|
||||
{
|
||||
'target_name': 'mkcodecache',
|
||||
'type': 'executable',
|
||||
|
||||
'dependencies': [
|
||||
'<(node_lib_target_name)',
|
||||
'deps/histogram/histogram.gyp:histogram',
|
||||
'deps/uvwasi/uvwasi.gyp:uvwasi',
|
||||
],
|
||||
|
||||
'includes': [
|
||||
'node.gypi'
|
||||
],
|
||||
|
||||
'include_dirs': [
|
||||
'src',
|
||||
'tools/msvs/genfiles',
|
||||
'deps/v8/include',
|
||||
'deps/cares/include',
|
||||
'deps/uv/include',
|
||||
'deps/uvwasi/include',
|
||||
],
|
||||
|
||||
'defines': [
|
||||
'NODE_WANT_INTERNALS=1'
|
||||
],
|
||||
'sources': [
|
||||
'src/node_snapshot_stub.cc',
|
||||
'src/node_code_cache_stub.cc',
|
||||
'tools/code_cache/mkcodecache.cc',
|
||||
'tools/code_cache/cache_builder.cc',
|
||||
'tools/code_cache/cache_builder.h',
|
||||
],
|
||||
|
||||
'conditions': [
|
||||
[ 'node_use_openssl=="true"', {
|
||||
'defines': [
|
||||
'HAVE_OPENSSL=1',
|
||||
],
|
||||
}],
|
||||
['v8_enable_inspector==1', {
|
||||
'defines': [
|
||||
'HAVE_INSPECTOR=1',
|
||||
],
|
||||
}],
|
||||
['OS=="win"', {
|
||||
'libraries': [
|
||||
'dbghelp.lib',
|
||||
'PsApi.lib',
|
||||
'winmm.lib',
|
||||
'Ws2_32.lib',
|
||||
],
|
||||
}],
|
||||
],
|
||||
}, # mkcodecache
|
||||
{
|
||||
'target_name': 'node_mksnapshot',
|
||||
'type': 'executable',
|
||||
@ -1466,7 +1378,6 @@
|
||||
|
||||
'sources': [
|
||||
'src/node_snapshot_stub.cc',
|
||||
'src/node_code_cache_stub.cc',
|
||||
'tools/snapshot/node_mksnapshot.cc',
|
||||
],
|
||||
|
||||
@ -1476,6 +1387,11 @@
|
||||
'HAVE_OPENSSL=1',
|
||||
],
|
||||
}],
|
||||
[ 'node_use_node_code_cache=="true"', {
|
||||
'defines': [
|
||||
'NODE_USE_NODE_CODE_CACHE=1',
|
||||
],
|
||||
}],
|
||||
['v8_enable_inspector==1', {
|
||||
'defines': [
|
||||
'HAVE_INSPECTOR=1',
|
||||
|
@ -34,7 +34,6 @@
|
||||
#include "handle_wrap.h"
|
||||
#include "node.h"
|
||||
#include "node_binding.h"
|
||||
#include "node_external_reference.h"
|
||||
#include "node_main_instance.h"
|
||||
#include "node_native_module.h"
|
||||
#include "node_options.h"
|
||||
@ -996,6 +995,11 @@ struct SnapshotData {
|
||||
// TODO(joyeecheung): there should be a vector of env_info once we snapshot
|
||||
// the worker environments.
|
||||
EnvSerializeInfo env_info;
|
||||
// A vector of built-in ids and v8::ScriptCompiler::CachedData, this can be
|
||||
// shared across Node.js instances because they are supposed to share the
|
||||
// read only space. We use native_module::CodeCacheInfo because
|
||||
// v8::ScriptCompiler::CachedData is not copyable.
|
||||
std::vector<native_module::CodeCacheInfo> code_cache;
|
||||
};
|
||||
|
||||
class Environment : public MemoryRetainer {
|
||||
|
@ -979,8 +979,6 @@ int InitializeNodeWithArgs(std::vector<std::string>* argv,
|
||||
|
||||
#endif // defined(NODE_HAVE_I18N_SUPPORT)
|
||||
|
||||
NativeModuleEnv::InitializeCodeCache();
|
||||
|
||||
// We should set node_is_initialized here instead of in node::Start,
|
||||
// otherwise embedders using node::Init to initialize everything will not be
|
||||
// able to set it and native modules will not load for them.
|
||||
@ -1174,6 +1172,10 @@ int Start(int argc, char** argv) {
|
||||
: nullptr;
|
||||
uv_loop_configure(uv_default_loop(), UV_METRICS_IDLE_TIME);
|
||||
|
||||
if (snapshot_data != nullptr) {
|
||||
native_module::NativeModuleEnv::RefreshCodeCache(
|
||||
snapshot_data->code_cache);
|
||||
}
|
||||
NodeMainInstance main_instance(snapshot_data,
|
||||
uv_default_loop(),
|
||||
per_process::v8_platform.Platform(),
|
||||
|
@ -1,22 +0,0 @@
|
||||
// This file is part of the embedder test, which is intentionally built without
|
||||
// NODE_WANT_INTERNALS, so we define it here manually.
|
||||
#define NODE_WANT_INTERNALS 1
|
||||
|
||||
#include "node_native_module_env.h"
|
||||
|
||||
// The stub here is used when configure is run without `--code-cache-path`.
|
||||
// When --code-cache-path is set this stub will not be used and instead
|
||||
// an implementation will be generated. See tools/code_cache/README.md for
|
||||
// more information.
|
||||
|
||||
namespace node {
|
||||
namespace native_module {
|
||||
|
||||
const bool has_code_cache = false;
|
||||
|
||||
// The generated source code would insert <std::string, UnionString> pairs
|
||||
// into NativeModuleLoader::instance.code_cache_.
|
||||
void NativeModuleEnv::InitializeCodeCache() {}
|
||||
|
||||
} // namespace native_module
|
||||
} // namespace node
|
@ -6,6 +6,7 @@
|
||||
#include "debug_utils-inl.h"
|
||||
#include "node_external_reference.h"
|
||||
#include "node_internals.h"
|
||||
#include "node_native_module_env.h"
|
||||
#include "node_options-inl.h"
|
||||
#include "node_snapshot_builder.h"
|
||||
#include "node_snapshotable.h"
|
||||
@ -189,6 +190,7 @@ NodeMainInstance::CreateMainEnvironment(int* exit_code) {
|
||||
|
||||
CHECK(!context.IsEmpty());
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
CHECK(InitializeContextRuntime(context).IsJust());
|
||||
SetIsolateErrorHandlers(isolate_, {});
|
||||
env->InitializeMainContext(context, &(snapshot_data_->env_info));
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "node_native_module.h"
|
||||
#include "util-inl.h"
|
||||
#include "debug_utils-inl.h"
|
||||
#include "node_internals.h"
|
||||
#include "util-inl.h"
|
||||
|
||||
namespace node {
|
||||
namespace native_module {
|
||||
@ -277,6 +278,11 @@ MaybeLocal<Function> NativeModuleLoader::LookupAndCompile(
|
||||
: ScriptCompiler::kEagerCompile;
|
||||
ScriptCompiler::Source script_source(source, origin, cached_data);
|
||||
|
||||
per_process::Debug(DebugCategory::CODE_CACHE,
|
||||
"Compiling %s %s code cache\n",
|
||||
id,
|
||||
has_cache ? "with" : "without");
|
||||
|
||||
MaybeLocal<Function> maybe_fun =
|
||||
ScriptCompiler::CompileFunctionInContext(context,
|
||||
&script_source,
|
||||
@ -304,6 +310,19 @@ MaybeLocal<Function> NativeModuleLoader::LookupAndCompile(
|
||||
*result = (has_cache && !script_source.GetCachedData()->rejected)
|
||||
? Result::kWithCache
|
||||
: Result::kWithoutCache;
|
||||
|
||||
if (has_cache) {
|
||||
per_process::Debug(DebugCategory::CODE_CACHE,
|
||||
"Code cache of %s (%s) %s\n",
|
||||
id,
|
||||
script_source.GetCachedData()->buffer_policy ==
|
||||
ScriptCompiler::CachedData::BufferNotOwned
|
||||
? "BufferNotOwned"
|
||||
: "BufferOwned",
|
||||
script_source.GetCachedData()->rejected ? "is rejected"
|
||||
: "is accepted");
|
||||
}
|
||||
|
||||
// Generate new cache for next compilation
|
||||
std::unique_ptr<ScriptCompiler::CachedData> new_cached_data(
|
||||
ScriptCompiler::CreateCodeCacheForFunction(fun));
|
||||
@ -311,10 +330,14 @@ MaybeLocal<Function> NativeModuleLoader::LookupAndCompile(
|
||||
|
||||
{
|
||||
Mutex::ScopedLock lock(code_cache_mutex_);
|
||||
// The old entry should've been erased by now so we can just emplace.
|
||||
// If another thread did the same thing in the meantime, that should not
|
||||
// be an issue.
|
||||
const auto it = code_cache_.find(id);
|
||||
// TODO(joyeecheung): it's safer for each thread to have its own
|
||||
// copy of the code cache map.
|
||||
if (it == code_cache_.end()) {
|
||||
code_cache_.emplace(id, std::move(new_cached_data));
|
||||
} else {
|
||||
it->second.reset(new_cached_data.release());
|
||||
}
|
||||
}
|
||||
|
||||
return scope.Escape(fun);
|
||||
|
@ -15,6 +15,7 @@
|
||||
class PerProcessTest;
|
||||
|
||||
namespace node {
|
||||
class SnapshotBuilder;
|
||||
namespace native_module {
|
||||
|
||||
using NativeModuleRecordMap = std::map<std::string, UnionBytes>;
|
||||
@ -22,6 +23,11 @@ using NativeModuleCacheMap =
|
||||
std::unordered_map<std::string,
|
||||
std::unique_ptr<v8::ScriptCompiler::CachedData>>;
|
||||
|
||||
struct CodeCacheInfo {
|
||||
std::string id;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
// The native (C++) side of the NativeModule in JS land, which
|
||||
// handles compilation and caching of builtin modules (NativeModule)
|
||||
// and bootstrappers, whose source are bundled into the binary
|
||||
@ -66,6 +72,8 @@ class NODE_EXTERN_PRIVATE NativeModuleLoader {
|
||||
bool CannotBeRequired(const char* id);
|
||||
|
||||
NativeModuleCacheMap* code_cache();
|
||||
const Mutex& code_cache_mutex() const { return code_cache_mutex_; }
|
||||
|
||||
v8::ScriptCompiler::CachedData* GetCodeCache(const char* id) const;
|
||||
enum class Result { kWithCache, kWithoutCache };
|
||||
v8::MaybeLocal<v8::String> LoadBuiltinModuleSource(v8::Isolate* isolate,
|
||||
|
@ -1,6 +1,9 @@
|
||||
#include "node_native_module_env.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include "debug_utils-inl.h"
|
||||
#include "env-inl.h"
|
||||
#include "node_external_reference.h"
|
||||
#include "node_native_module_env.h"
|
||||
|
||||
namespace node {
|
||||
namespace native_module {
|
||||
@ -22,6 +25,8 @@ using v8::SideEffectType;
|
||||
using v8::String;
|
||||
using v8::Value;
|
||||
|
||||
bool NativeModuleEnv::has_code_cache_ = false;
|
||||
|
||||
bool NativeModuleEnv::Add(const char* id, const UnionBytes& source) {
|
||||
return NativeModuleLoader::GetInstance()->Add(id, source);
|
||||
}
|
||||
@ -38,6 +43,61 @@ Local<String> NativeModuleEnv::GetConfigString(Isolate* isolate) {
|
||||
return NativeModuleLoader::GetInstance()->GetConfigString(isolate);
|
||||
}
|
||||
|
||||
bool NativeModuleEnv::CompileAllModules(Local<Context> context) {
|
||||
NativeModuleLoader* loader = NativeModuleLoader::GetInstance();
|
||||
std::vector<std::string> ids = loader->GetModuleIds();
|
||||
bool all_succeeded = true;
|
||||
for (const auto& id : ids) {
|
||||
// TODO(joyeecheung): compile non-module scripts here too.
|
||||
if (!loader->CanBeRequired(id.c_str())) {
|
||||
continue;
|
||||
}
|
||||
v8::TryCatch bootstrapCatch(context->GetIsolate());
|
||||
native_module::NativeModuleLoader::Result result;
|
||||
USE(loader->CompileAsModule(context, id.c_str(), &result));
|
||||
if (bootstrapCatch.HasCaught()) {
|
||||
per_process::Debug(DebugCategory::CODE_CACHE,
|
||||
"Failed to compile code cache for %s\n",
|
||||
id.c_str());
|
||||
all_succeeded = false;
|
||||
PrintCaughtException(context->GetIsolate(), context, bootstrapCatch);
|
||||
}
|
||||
}
|
||||
return all_succeeded;
|
||||
}
|
||||
|
||||
void NativeModuleEnv::CopyCodeCache(std::vector<CodeCacheInfo>* out) {
|
||||
NativeModuleLoader* loader = NativeModuleLoader::GetInstance();
|
||||
Mutex::ScopedLock lock(loader->code_cache_mutex());
|
||||
auto in = loader->code_cache();
|
||||
for (auto const& item : *in) {
|
||||
out->push_back(
|
||||
{item.first,
|
||||
{item.second->data, item.second->data + item.second->length}});
|
||||
}
|
||||
}
|
||||
|
||||
void NativeModuleEnv::RefreshCodeCache(const std::vector<CodeCacheInfo>& in) {
|
||||
NativeModuleLoader* loader = NativeModuleLoader::GetInstance();
|
||||
Mutex::ScopedLock lock(loader->code_cache_mutex());
|
||||
auto out = loader->code_cache();
|
||||
for (auto const& item : in) {
|
||||
size_t length = item.data.size();
|
||||
uint8_t* buffer = new uint8_t[length];
|
||||
memcpy(buffer, item.data.data(), length);
|
||||
auto new_cache = std::make_unique<v8::ScriptCompiler::CachedData>(
|
||||
buffer, length, v8::ScriptCompiler::CachedData::BufferOwned);
|
||||
auto cache_it = out->find(item.id);
|
||||
if (cache_it != out->end()) {
|
||||
// Release the old cache and replace it with the new copy.
|
||||
cache_it->second.reset(new_cache.release());
|
||||
} else {
|
||||
out->emplace(item.id, new_cache.release());
|
||||
}
|
||||
}
|
||||
NativeModuleEnv::has_code_cache_ = true;
|
||||
}
|
||||
|
||||
void NativeModuleEnv::GetModuleCategories(
|
||||
Local<Name> property, const PropertyCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
@ -185,9 +245,10 @@ MaybeLocal<Function> NativeModuleEnv::LookupAndCompile(
|
||||
return maybe;
|
||||
}
|
||||
|
||||
void HasCachedBuiltins(const FunctionCallbackInfo<Value>& args) {
|
||||
void NativeModuleEnv::HasCachedBuiltins(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
args.GetReturnValue().Set(
|
||||
v8::Boolean::New(args.GetIsolate(), has_code_cache));
|
||||
v8::Boolean::New(args.GetIsolate(), NativeModuleEnv::has_code_cache_));
|
||||
}
|
||||
|
||||
// TODO(joyeecheung): It is somewhat confusing that Class::Initialize
|
||||
|
@ -3,16 +3,17 @@
|
||||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#include <vector>
|
||||
#include "node_native_module.h"
|
||||
|
||||
namespace node {
|
||||
class Environment;
|
||||
class ExternalReferenceRegistry;
|
||||
|
||||
namespace native_module {
|
||||
|
||||
extern const bool has_code_cache;
|
||||
|
||||
// TODO(joyeecheung): since it's safer to make the code cache part of the
|
||||
// embedded snapshot, there is no need to separate NativeModuleEnv and
|
||||
// NativeModuleLoader. Merge the two.
|
||||
class NativeModuleEnv {
|
||||
public:
|
||||
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
|
||||
@ -33,11 +34,9 @@ class NativeModuleEnv {
|
||||
static bool Exists(const char* id);
|
||||
static bool Add(const char* id, const UnionBytes& source);
|
||||
|
||||
// Loads data into NativeModuleLoader::.instance.code_cache_
|
||||
// Generated by mkcodecache as node_code_cache.cc when
|
||||
// the build is configured with --code-cache-path=.... They are noops
|
||||
// in node_code_cache_stub.cc
|
||||
static void InitializeCodeCache();
|
||||
static bool CompileAllModules(v8::Local<v8::Context> context);
|
||||
static void RefreshCodeCache(const std::vector<CodeCacheInfo>& in);
|
||||
static void CopyCodeCache(std::vector<CodeCacheInfo>* out);
|
||||
|
||||
private:
|
||||
static void RecordResult(const char* id,
|
||||
@ -57,6 +56,10 @@ class NativeModuleEnv {
|
||||
const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
// Compile a specific native module as a function
|
||||
static void CompileFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void HasCachedBuiltins(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static bool has_code_cache_;
|
||||
};
|
||||
|
||||
} // namespace native_module
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "node_file.h"
|
||||
#include "node_internals.h"
|
||||
#include "node_main_instance.h"
|
||||
#include "node_native_module_env.h"
|
||||
#include "node_process.h"
|
||||
#include "node_snapshot_builder.h"
|
||||
#include "node_v8.h"
|
||||
@ -45,6 +46,47 @@ void WriteVector(std::ostringstream* ss, const T* vec, size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
static std::string GetCodeCacheDefName(const std::string& id) {
|
||||
char buf[64] = {0};
|
||||
size_t size = id.size();
|
||||
CHECK_LT(size, sizeof(buf));
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
char ch = id[i];
|
||||
buf[i] = (ch == '-' || ch == '/') ? '_' : ch;
|
||||
}
|
||||
return std::string(buf) + std::string("_cache_data");
|
||||
}
|
||||
|
||||
static std::string FormatSize(int size) {
|
||||
char buf[64] = {0};
|
||||
if (size < 1024) {
|
||||
snprintf(buf, sizeof(buf), "%.2fB", static_cast<double>(size));
|
||||
} else if (size < 1024 * 1024) {
|
||||
snprintf(buf, sizeof(buf), "%.2fKB", static_cast<double>(size / 1024));
|
||||
} else {
|
||||
snprintf(
|
||||
buf, sizeof(buf), "%.2fMB", static_cast<double>(size / 1024 / 1024));
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void WriteStaticCodeCacheData(std::ostringstream* ss,
|
||||
const native_module::CodeCacheInfo& info) {
|
||||
*ss << "static const uint8_t " << GetCodeCacheDefName(info.id) << "[] = {\n";
|
||||
WriteVector(ss, info.data.data(), info.data.size());
|
||||
*ss << "};";
|
||||
}
|
||||
|
||||
static void WriteCodeCacheInitializer(std::ostringstream* ss,
|
||||
const std::string& id) {
|
||||
std::string def_name = GetCodeCacheDefName(id);
|
||||
*ss << " { \"" << id << "\",\n";
|
||||
*ss << " {" << def_name << ",\n";
|
||||
*ss << " " << def_name << " + arraysize(" << def_name << "),\n";
|
||||
*ss << " }\n";
|
||||
*ss << " },\n";
|
||||
}
|
||||
|
||||
std::string FormatBlob(SnapshotData* data) {
|
||||
std::ostringstream ss;
|
||||
|
||||
@ -57,20 +99,26 @@ std::string FormatBlob(SnapshotData* data) {
|
||||
|
||||
namespace node {
|
||||
|
||||
static const char blob_data[] = {
|
||||
static const char v8_snapshot_blob_data[] = {
|
||||
)";
|
||||
WriteVector(&ss,
|
||||
data->v8_snapshot_blob_data.data,
|
||||
data->v8_snapshot_blob_data.raw_size);
|
||||
ss << R"(};
|
||||
|
||||
static const int blob_size = )"
|
||||
<< data->v8_snapshot_blob_data.raw_size << R"(;
|
||||
static const int v8_snapshot_blob_size = )"
|
||||
<< data->v8_snapshot_blob_data.raw_size << ";";
|
||||
|
||||
SnapshotData snapshot_data {
|
||||
// -- blob begins --
|
||||
{ blob_data, blob_size },
|
||||
// -- blob ends --
|
||||
// Windows can't deal with too many large vector initializers.
|
||||
// Store the data into static arrays first.
|
||||
for (const auto& item : data->code_cache) {
|
||||
WriteStaticCodeCacheData(&ss, item);
|
||||
}
|
||||
|
||||
ss << R"(SnapshotData snapshot_data {
|
||||
// -- v8_snapshot_blob_data begins --
|
||||
{ v8_snapshot_blob_data, v8_snapshot_blob_size },
|
||||
// -- v8_snapshot_blob_data ends --
|
||||
// -- isolate_data_indices begins --
|
||||
{
|
||||
)";
|
||||
@ -83,6 +131,15 @@ SnapshotData snapshot_data {
|
||||
)" << data->env_info
|
||||
<< R"(
|
||||
// -- env_info ends --
|
||||
,
|
||||
// -- code_cache begins --
|
||||
{)";
|
||||
for (const auto& item : data->code_cache) {
|
||||
WriteCodeCacheInitializer(&ss, item.id);
|
||||
}
|
||||
ss << R"(
|
||||
}
|
||||
// -- code_cache ends --
|
||||
};
|
||||
|
||||
const SnapshotData* SnapshotBuilder::GetEmbeddedSnapshotData() {
|
||||
@ -226,6 +283,19 @@ void SnapshotBuilder::Generate(SnapshotData* out,
|
||||
size_t index = creator.AddContext(
|
||||
main_context, {SerializeNodeContextInternalFields, env});
|
||||
CHECK_EQ(index, SnapshotData::kNodeMainContextIndex);
|
||||
|
||||
#ifdef NODE_USE_NODE_CODE_CACHE
|
||||
// Regenerate all the code cache.
|
||||
CHECK(native_module::NativeModuleEnv::CompileAllModules(main_context));
|
||||
native_module::NativeModuleEnv::CopyCodeCache(&(out->code_cache));
|
||||
for (const auto& item : out->code_cache) {
|
||||
std::string size_str = FormatSize(item.data.size());
|
||||
per_process::Debug(DebugCategory::MKSNAPSHOT,
|
||||
"Generated code cache for %d: %s\n",
|
||||
item.id.c_str(),
|
||||
size_str.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "memory_tracker-inl.h"
|
||||
#include "node_errors.h"
|
||||
#include "node_external_reference.h"
|
||||
|
||||
namespace node {
|
||||
namespace wasm_web_api {
|
||||
|
@ -36,8 +36,8 @@ const loadedModules = extractModules(process.moduleLoadList);
|
||||
// Cross-compiled binaries do not have code cache, verifies that the builtins
|
||||
// are all compiled without cache and we are doing the bookkeeping right.
|
||||
if (!process.features.cached_builtins) {
|
||||
console.log('The binary is not configured with code cache');
|
||||
assert(!process.config.variables.node_use_node_code_cache);
|
||||
assert(!process.config.variables.node_use_node_code_cache ||
|
||||
process.execArgv.includes('--no-node-snapshot'));
|
||||
|
||||
if (isMainThread) {
|
||||
assert.deepStrictEqual(compiledWithCache, new Set());
|
||||
|
@ -1,38 +0,0 @@
|
||||
# Node.js code cache builder
|
||||
|
||||
This is the V8 code cache builder of Node.js. It pre-compiles all the
|
||||
JavaScript native modules of Node.js and serializes the code cache (including
|
||||
the bytecodes) that will be embedded into the Node.js executable. When a Node.js
|
||||
JavaScript native module is `require`d at runtime, Node.js can deserialize from
|
||||
the code cache instead of parsing the source code and generating the bytecode
|
||||
for it before execution, which should reduce the load time of these JavaScript
|
||||
native modules.
|
||||
|
||||
## How it's built and used
|
||||
|
||||
The code cache builder is built with the `mkcodecache` target in `node.gyp`
|
||||
when `node_use_node_code_cache` is set to true, which is currently done by
|
||||
default.
|
||||
|
||||
In the default build of the Node.js executable, to embed the V8 code cache of
|
||||
the native modules into the Node.js executable, `libnode` is first built with
|
||||
these unresolved symbols:
|
||||
|
||||
- `node::native_module::has_code_cache`
|
||||
- `node::native_module::NativeModuleEnv::InitializeCodeCache`
|
||||
|
||||
Then the `mkcodecache` executable is built with C++ files in this directory,
|
||||
as well as `src/node_code_cache_stub.cc` which defines the unresolved symbols.
|
||||
|
||||
`mkcodecache` is run to generate a C++ file
|
||||
`<(SHARED_INTERMEDIATE_DIR)/node_code_cache.cc` that is similar to
|
||||
`src/node_code_cache_stub.cc` in structure, but contains the code cache data
|
||||
written as static char array literals. Then `libnode` is built with
|
||||
`node_code_cache.cc` to produce the final Node.js executable with the code
|
||||
cache data embedded.
|
||||
|
||||
For debugging, Node.js can be built without code cache if
|
||||
`--without-node-code-cache` is passed to `configure`. Note that even if the
|
||||
code cache is not pre-compiled and embedded into the Node.js executable, the
|
||||
internal infrastructure is still used to share code cache between the main
|
||||
thread and worker threads (if there is any).
|
@ -1,148 +0,0 @@
|
||||
#include "cache_builder.h"
|
||||
#include "debug_utils-inl.h"
|
||||
#include "node_native_module.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace node {
|
||||
namespace native_module {
|
||||
|
||||
using v8::Context;
|
||||
using v8::Local;
|
||||
using v8::ScriptCompiler;
|
||||
|
||||
static std::string GetDefName(const std::string& id) {
|
||||
char buf[64] = {0};
|
||||
size_t size = id.size();
|
||||
CHECK_LT(size, sizeof(buf));
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
char ch = id[i];
|
||||
buf[i] = (ch == '-' || ch == '/') ? '_' : ch;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static std::string FormatSize(size_t size) {
|
||||
char buf[64] = {0};
|
||||
if (size < 1024) {
|
||||
snprintf(buf, sizeof(buf), "%.2fB", static_cast<double>(size));
|
||||
} else if (size < 1024 * 1024) {
|
||||
snprintf(buf, sizeof(buf), "%.2fKB", static_cast<double>(size / 1024));
|
||||
} else {
|
||||
snprintf(
|
||||
buf, sizeof(buf), "%.2fMB", static_cast<double>(size / 1024 / 1024));
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static std::string GetDefinition(const std::string& id,
|
||||
size_t size,
|
||||
const uint8_t* data) {
|
||||
std::stringstream ss;
|
||||
ss << "static const uint8_t " << GetDefName(id) << "[] = {\n";
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
uint8_t ch = data[i];
|
||||
ss << std::to_string(ch) << (i == size - 1 ? '\n' : ',');
|
||||
}
|
||||
ss << "};";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static void GetInitializer(const std::string& id, std::stringstream& ss) {
|
||||
std::string def_name = GetDefName(id);
|
||||
ss << " code_cache.emplace(\n";
|
||||
ss << " \"" << id << "\",\n";
|
||||
ss << " std::make_unique<v8::ScriptCompiler::CachedData>(\n";
|
||||
ss << " " << def_name << ",\n";
|
||||
ss << " static_cast<int>(arraysize(" << def_name << ")), policy\n";
|
||||
ss << " )\n";
|
||||
ss << " );";
|
||||
}
|
||||
|
||||
static std::string GenerateCodeCache(
|
||||
const std::map<std::string, ScriptCompiler::CachedData*>& data) {
|
||||
std::stringstream ss;
|
||||
ss << R"(#include <cinttypes>
|
||||
#include "node_native_module_env.h"
|
||||
|
||||
// This file is generated by mkcodecache (tools/code_cache/mkcodecache.cc)
|
||||
|
||||
namespace node {
|
||||
namespace native_module {
|
||||
|
||||
const bool has_code_cache = true;
|
||||
|
||||
)";
|
||||
|
||||
size_t total = 0;
|
||||
for (const auto& x : data) {
|
||||
const std::string& id = x.first;
|
||||
ScriptCompiler::CachedData* cached_data = x.second;
|
||||
total += cached_data->length;
|
||||
std::string def = GetDefinition(id, cached_data->length, cached_data->data);
|
||||
ss << def << "\n\n";
|
||||
std::string size_str = FormatSize(cached_data->length);
|
||||
std::string total_str = FormatSize(total);
|
||||
per_process::Debug(DebugCategory::CODE_CACHE,
|
||||
"Generated cache for %s, size = %s, total = %s\n",
|
||||
id.c_str(),
|
||||
size_str.c_str(),
|
||||
total_str.c_str());
|
||||
}
|
||||
|
||||
ss << R"(void NativeModuleEnv::InitializeCodeCache() {
|
||||
NativeModuleCacheMap& code_cache =
|
||||
*NativeModuleLoader::GetInstance()->code_cache();
|
||||
CHECK(code_cache.empty());
|
||||
auto policy = v8::ScriptCompiler::CachedData::BufferPolicy::BufferNotOwned;
|
||||
)";
|
||||
|
||||
for (const auto& x : data) {
|
||||
GetInitializer(x.first, ss);
|
||||
ss << "\n\n";
|
||||
}
|
||||
|
||||
ss << R"(
|
||||
}
|
||||
|
||||
} // namespace native_module
|
||||
} // namespace node
|
||||
)";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string CodeCacheBuilder::Generate(Local<Context> context) {
|
||||
NativeModuleLoader* loader = NativeModuleLoader::GetInstance();
|
||||
std::vector<std::string> ids = loader->GetModuleIds();
|
||||
|
||||
std::map<std::string, ScriptCompiler::CachedData*> data;
|
||||
|
||||
for (const auto& id : ids) {
|
||||
// TODO(joyeecheung): we can only compile the modules that can be
|
||||
// required here because the parameters for other types of builtins
|
||||
// are still very flexible. We should look into auto-generating
|
||||
// the parameters from the source somehow.
|
||||
if (loader->CanBeRequired(id.c_str())) {
|
||||
NativeModuleLoader::Result result;
|
||||
USE(loader->CompileAsModule(context, id.c_str(), &result));
|
||||
ScriptCompiler::CachedData* cached_data =
|
||||
loader->GetCodeCache(id.c_str());
|
||||
if (cached_data == nullptr) {
|
||||
// TODO(joyeecheung): display syntax errors
|
||||
std::cerr << "Failed to compile " << id << "\n";
|
||||
} else {
|
||||
data.emplace(id, cached_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GenerateCodeCache(data);
|
||||
}
|
||||
|
||||
} // namespace native_module
|
||||
} // namespace node
|
@ -1,16 +0,0 @@
|
||||
#ifndef TOOLS_CODE_CACHE_CACHE_BUILDER_H_
|
||||
#define TOOLS_CODE_CACHE_CACHE_BUILDER_H_
|
||||
|
||||
#include <string>
|
||||
#include "v8.h"
|
||||
|
||||
namespace node {
|
||||
namespace native_module {
|
||||
class CodeCacheBuilder {
|
||||
public:
|
||||
static std::string Generate(v8::Local<v8::Context> context);
|
||||
};
|
||||
} // namespace native_module
|
||||
} // namespace node
|
||||
|
||||
#endif // TOOLS_CODE_CACHE_CACHE_BUILDER_H_
|
@ -1,74 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "cache_builder.h"
|
||||
#include "debug_utils-inl.h"
|
||||
#include "libplatform/libplatform.h"
|
||||
#include "v8.h"
|
||||
|
||||
using node::native_module::CodeCacheBuilder;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::Context;
|
||||
using v8::HandleScope;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <VersionHelpers.h>
|
||||
#include <WinError.h>
|
||||
#include <windows.h>
|
||||
|
||||
int wmain(int argc, wchar_t* argv[]) {
|
||||
#else // UNIX
|
||||
int main(int argc, char* argv[]) {
|
||||
argv = uv_setup_args(argc, argv);
|
||||
#endif // _WIN32
|
||||
|
||||
v8::V8::SetFlagsFromString("--random_seed=42");
|
||||
v8::V8::SetFlagsFromString("--harmony-import-assertions");
|
||||
|
||||
if (argc < 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <path/to/output.cc>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::ofstream out;
|
||||
out.open(argv[1], std::ios::out | std::ios::binary);
|
||||
if (!out.is_open()) {
|
||||
std::cerr << "Cannot open " << argv[1] << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
node::per_process::enabled_debug_list.Parse(nullptr);
|
||||
|
||||
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
|
||||
v8::V8::InitializePlatform(platform.get());
|
||||
v8::V8::Initialize();
|
||||
|
||||
// Create a new Isolate and make it the current one.
|
||||
Isolate::CreateParams create_params;
|
||||
create_params.array_buffer_allocator_shared.reset(
|
||||
ArrayBuffer::Allocator::NewDefaultAllocator());
|
||||
Isolate* isolate = Isolate::New(create_params);
|
||||
{
|
||||
Isolate::Scope isolate_scope(isolate);
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
v8::Local<v8::Context> context = v8::Context::New(isolate);
|
||||
v8::Context::Scope context_scope(context);
|
||||
|
||||
// The command line flags are part of the code cache's checksum so reset
|
||||
// --random_seed= to its default value before creating the code cache.
|
||||
v8::V8::SetFlagsFromString("--random_seed=0");
|
||||
std::string cache = CodeCacheBuilder::Generate(context);
|
||||
out << cache;
|
||||
out.close();
|
||||
}
|
||||
isolate->Dispose();
|
||||
|
||||
v8::V8::Dispose();
|
||||
v8::V8::DisposePlatform();
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user