From fbe14780778a11062ba2d44d9714c6b62007a913 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 18 Nov 2021 11:56:39 +0800 Subject: [PATCH] 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 Reviewed-By: Chengzhong Wu --- .github/CODEOWNERS | 2 - configure.py | 2 +- lib/internal/bootstrap/node.js | 5 +- node.gyp | 106 +++------------------ src/env.h | 6 +- src/node.cc | 6 +- src/node_code_cache_stub.cc | 22 ----- src/node_main_instance.cc | 2 + src/node_native_module.cc | 33 ++++++- src/node_native_module.h | 8 ++ src/node_native_module_env.cc | 67 +++++++++++++- src/node_native_module_env.h | 19 ++-- src/node_snapshotable.cc | 84 +++++++++++++++-- src/node_wasm_web_api.cc | 1 + test/parallel/test-code-cache.js | 4 +- tools/code_cache/README.md | 38 -------- tools/code_cache/cache_builder.cc | 148 ------------------------------ tools/code_cache/cache_builder.h | 16 ---- tools/code_cache/mkcodecache.cc | 74 --------------- 19 files changed, 216 insertions(+), 427 deletions(-) delete mode 100644 src/node_code_cache_stub.cc delete mode 100644 tools/code_cache/README.md delete mode 100644 tools/code_cache/cache_builder.cc delete mode 100644 tools/code_cache/cache_builder.h delete mode 100644 tools/code_cache/mkcodecache.cc diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4170038a0ac0fc..6d69bbf54be1d6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 diff --git a/configure.py b/configure.py index 17ff53ef645493..187c381660b369 100755 --- a/configure.py +++ b/configure.py @@ -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. diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 1fc59fdeffd510..0be64e136d19a6 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -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(); } diff --git a/node.gyp b/node.gyp index 4ed067649035ac..e36617232dd7cc 100644 --- a/node.gyp +++ b/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,32 +303,7 @@ }, }, }], - ['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"', { + ['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', diff --git a/src/env.h b/src/env.h index 961d0a70c778fb..c188b263470819 100644 --- a/src/env.h +++ b/src/env.h @@ -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 code_cache; }; class Environment : public MemoryRetainer { diff --git a/src/node.cc b/src/node.cc index 9148e1b87ad7ca..9919ddb5f1b37b 100644 --- a/src/node.cc +++ b/src/node.cc @@ -979,8 +979,6 @@ int InitializeNodeWithArgs(std::vector* 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(), diff --git a/src/node_code_cache_stub.cc b/src/node_code_cache_stub.cc deleted file mode 100644 index 9d9901738f63b0..00000000000000 --- a/src/node_code_cache_stub.cc +++ /dev/null @@ -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 pairs -// into NativeModuleLoader::instance.code_cache_. -void NativeModuleEnv::InitializeCodeCache() {} - -} // namespace native_module -} // namespace node diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index 951a55f3568a82..ad4f6a7d95fb32 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -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)); diff --git a/src/node_native_module.cc b/src/node_native_module.cc index 5d20e1d6a86416..cea80e36b7287d 100644 --- a/src/node_native_module.cc +++ b/src/node_native_module.cc @@ -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 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 maybe_fun = ScriptCompiler::CompileFunctionInContext(context, &script_source, @@ -304,6 +310,19 @@ MaybeLocal 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 new_cached_data( ScriptCompiler::CreateCodeCacheForFunction(fun)); @@ -311,10 +330,14 @@ MaybeLocal 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. - code_cache_.emplace(id, std::move(new_cached_data)); + 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); diff --git a/src/node_native_module.h b/src/node_native_module.h index 7acd154d419de8..1bbb400586e070 100644 --- a/src/node_native_module.h +++ b/src/node_native_module.h @@ -15,6 +15,7 @@ class PerProcessTest; namespace node { +class SnapshotBuilder; namespace native_module { using NativeModuleRecordMap = std::map; @@ -22,6 +23,11 @@ using NativeModuleCacheMap = std::unordered_map>; +struct CodeCacheInfo { + std::string id; + std::vector 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 LoadBuiltinModuleSource(v8::Isolate* isolate, diff --git a/src/node_native_module_env.cc b/src/node_native_module_env.cc index b388b8cd07e583..05513b89f8f188 100644 --- a/src/node_native_module_env.cc +++ b/src/node_native_module_env.cc @@ -1,6 +1,9 @@ -#include "node_native_module_env.h" +#include + +#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 NativeModuleEnv::GetConfigString(Isolate* isolate) { return NativeModuleLoader::GetInstance()->GetConfigString(isolate); } +bool NativeModuleEnv::CompileAllModules(Local context) { + NativeModuleLoader* loader = NativeModuleLoader::GetInstance(); + std::vector 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* 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& 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( + 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 property, const PropertyCallbackInfo& info) { Environment* env = Environment::GetCurrent(info); @@ -185,9 +245,10 @@ MaybeLocal NativeModuleEnv::LookupAndCompile( return maybe; } -void HasCachedBuiltins(const FunctionCallbackInfo& args) { +void NativeModuleEnv::HasCachedBuiltins( + const FunctionCallbackInfo& 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 diff --git a/src/node_native_module_env.h b/src/node_native_module_env.h index 0a53771ff5d1ca..adcfc7b6fb4954 100644 --- a/src/node_native_module_env.h +++ b/src/node_native_module_env.h @@ -3,16 +3,17 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#include #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 context); + static void RefreshCodeCache(const std::vector& in); + static void CopyCodeCache(std::vector* out); private: static void RecordResult(const char* id, @@ -57,6 +56,10 @@ class NativeModuleEnv { const v8::PropertyCallbackInfo& info); // Compile a specific native module as a function static void CompileFunction(const v8::FunctionCallbackInfo& args); + static void HasCachedBuiltins( + const v8::FunctionCallbackInfo& args); + + static bool has_code_cache_; }; } // namespace native_module diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 2c91c0a11ce78c..1fc374842ff5c6 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -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(size)); + } else if (size < 1024 * 1024) { + snprintf(buf, sizeof(buf), "%.2fKB", static_cast(size / 1024)); + } else { + snprintf( + buf, sizeof(buf), "%.2fMB", static_cast(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 << ";"; + + // 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); + } -SnapshotData snapshot_data { - // -- blob begins -- - { blob_data, blob_size }, - // -- blob ends -- + 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 } } diff --git a/src/node_wasm_web_api.cc b/src/node_wasm_web_api.cc index 1d3febdca6e52b..67437034bbee34 100644 --- a/src/node_wasm_web_api.cc +++ b/src/node_wasm_web_api.cc @@ -2,6 +2,7 @@ #include "memory_tracker-inl.h" #include "node_errors.h" +#include "node_external_reference.h" namespace node { namespace wasm_web_api { diff --git a/test/parallel/test-code-cache.js b/test/parallel/test-code-cache.js index f61ed9f5c54077..00deafd6d49465 100644 --- a/test/parallel/test-code-cache.js +++ b/test/parallel/test-code-cache.js @@ -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()); diff --git a/tools/code_cache/README.md b/tools/code_cache/README.md deleted file mode 100644 index 8feb280caae585..00000000000000 --- a/tools/code_cache/README.md +++ /dev/null @@ -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). diff --git a/tools/code_cache/cache_builder.cc b/tools/code_cache/cache_builder.cc deleted file mode 100644 index 837357a0fbda76..00000000000000 --- a/tools/code_cache/cache_builder.cc +++ /dev/null @@ -1,148 +0,0 @@ -#include "cache_builder.h" -#include "debug_utils-inl.h" -#include "node_native_module.h" -#include "util.h" - -#include -#include -#include -#include -#include - -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(size)); - } else if (size < 1024 * 1024) { - snprintf(buf, sizeof(buf), "%.2fKB", static_cast(size / 1024)); - } else { - snprintf( - buf, sizeof(buf), "%.2fMB", static_cast(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(\n"; - ss << " " << def_name << ",\n"; - ss << " static_cast(arraysize(" << def_name << ")), policy\n"; - ss << " )\n"; - ss << " );"; -} - -static std::string GenerateCodeCache( - const std::map& data) { - std::stringstream ss; - ss << R"(#include -#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) { - NativeModuleLoader* loader = NativeModuleLoader::GetInstance(); - std::vector ids = loader->GetModuleIds(); - - std::map 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 diff --git a/tools/code_cache/cache_builder.h b/tools/code_cache/cache_builder.h deleted file mode 100644 index d5a6cd4241dfb0..00000000000000 --- a/tools/code_cache/cache_builder.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef TOOLS_CODE_CACHE_CACHE_BUILDER_H_ -#define TOOLS_CODE_CACHE_CACHE_BUILDER_H_ - -#include -#include "v8.h" - -namespace node { -namespace native_module { -class CodeCacheBuilder { - public: - static std::string Generate(v8::Local context); -}; -} // namespace native_module -} // namespace node - -#endif // TOOLS_CODE_CACHE_CACHE_BUILDER_H_ diff --git a/tools/code_cache/mkcodecache.cc b/tools/code_cache/mkcodecache.cc deleted file mode 100644 index 68690252a147cd..00000000000000 --- a/tools/code_cache/mkcodecache.cc +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include -#include -#include - -#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 -#include -#include - -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] << " \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 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 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; -}