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:
Joyee Cheung 2021-11-18 11:56:39 +08:00
parent de0b6dc710
commit fbe1478077
No known key found for this signature in database
GPG Key ID: 92B78A53C8303B8D
19 changed files with 216 additions and 427 deletions

2
.github/CODEOWNERS vendored
View File

@ -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

View File

@ -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.

View File

@ -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
View File

@ -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',

View File

@ -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 {

View File

@ -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(),

View File

@ -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

View File

@ -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));

View File

@ -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);

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -2,6 +2,7 @@
#include "memory_tracker-inl.h"
#include "node_errors.h"
#include "node_external_reference.h"
namespace node {
namespace wasm_web_api {

View File

@ -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());

View File

@ -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).

View File

@ -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

View File

@ -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_

View File

@ -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;
}