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 b4ce6188da8525..353129bce49a3c 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', ], @@ -739,7 +713,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"', { @@ -749,6 +722,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', }], @@ -1149,7 +1127,6 @@ ], 'sources': [ 'src/node_snapshot_stub.cc', - 'src/node_code_cache_stub.cc', 'test/fuzzers/fuzz_url.cc', ], 'conditions': [ @@ -1192,7 +1169,6 @@ ], 'sources': [ 'src/node_snapshot_stub.cc', - 'src/node_code_cache_stub.cc', 'test/fuzzers/fuzz_env.cc', ], 'conditions': [ @@ -1242,7 +1218,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', @@ -1335,7 +1310,6 @@ 'sources': [ 'src/node_snapshot_stub.cc', - 'src/node_code_cache_stub.cc', 'test/embedding/embedtest.cc', ], @@ -1379,68 +1353,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', @@ -1468,7 +1380,6 @@ 'sources': [ 'src/node_snapshot_stub.cc', - 'src/node_code_cache_stub.cc', 'tools/snapshot/node_mksnapshot.cc', ], @@ -1478,6 +1389,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 475ca4d8b7cd71..e55bb70daa72d7 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" @@ -973,9 +972,22 @@ struct EnvSerializeInfo { }; struct SnapshotData { - v8::StartupData blob; + // The result of v8::SnapshotCreator::CreateBlob() during the snapshot + // building process. + v8::StartupData v8_snapshot_blob_data; + + static const size_t kNodeBaseContextIndex = 0; + static const size_t kNodeMainContextIndex = kNodeBaseContextIndex + 1; + std::vector isolate_data_indices; + // 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 5910a400c25c25..76a26adc97053e 100644 --- a/src/node.cc +++ b/src/node.cc @@ -980,8 +980,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. @@ -1175,6 +1173,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 f5e7ef61fb57f6..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" @@ -183,12 +184,13 @@ NodeMainInstance::CreateMainEnvironment(int* exit_code) { EnvironmentFlags::kDefaultFlags, {})); context = Context::FromSnapshot(isolate_, - SnapshotBuilder::kNodeMainContextIndex, + SnapshotData::kNodeMainContextIndex, {DeserializeNodeInternalFields, env.get()}) .ToLocalChecked(); 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_snapshot_builder.h b/src/node_snapshot_builder.h index 2714293fbc9976..c5d2ee2a4bcd83 100644 --- a/src/node_snapshot_builder.h +++ b/src/node_snapshot_builder.h @@ -29,9 +29,6 @@ class NODE_EXTERN_PRIVATE SnapshotBuilder { static void InitializeIsolateParams(const SnapshotData* data, v8::Isolate::CreateParams* params); - static const size_t kNodeBaseContextIndex = 0; - static const size_t kNodeMainContextIndex = kNodeBaseContextIndex + 1; - private: // Used to synchronize access to the snapshot data static Mutex snapshot_data_mutex_; diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 9cb5985ea20841..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,18 +99,26 @@ std::string FormatBlob(SnapshotData* data) { namespace node { -static const char blob_data[] = { +static const char v8_snapshot_blob_data[] = { )"; - WriteVector(&ss, data->blob.data, data->blob.raw_size); + WriteVector(&ss, + data->v8_snapshot_blob_data.data, + data->v8_snapshot_blob_data.raw_size); ss << R"(}; -static const int blob_size = )" - << data->blob.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 -- { )"; @@ -81,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() { @@ -103,7 +162,8 @@ const std::vector& SnapshotBuilder::CollectExternalReferences() { void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data, Isolate::CreateParams* params) { params->external_references = CollectExternalReferences().data(); - params->snapshot_blob = const_cast(&(data->blob)); + params->snapshot_blob = + const_cast(&(data->v8_snapshot_blob_data)); } void SnapshotBuilder::Generate(SnapshotData* out, @@ -153,7 +213,7 @@ void SnapshotBuilder::Generate(SnapshotData* out, // without breaking compatibility. { size_t index = creator.AddContext(CreateBaseContext()); - CHECK_EQ(index, SnapshotBuilder::kNodeBaseContextIndex); + CHECK_EQ(index, SnapshotData::kNodeBaseContextIndex); } // The main instance context. @@ -222,17 +282,30 @@ void SnapshotBuilder::Generate(SnapshotData* out, // Serialize the context size_t index = creator.AddContext( main_context, {SerializeNodeContextInternalFields, env}); - CHECK_EQ(index, SnapshotBuilder::kNodeMainContextIndex); + 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 } } // Must be out of HandleScope - out->blob = + out->v8_snapshot_blob_data = creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kClear); // We must be able to rehash the blob when we restore it or otherwise // the hash seed would be fixed by V8, introducing a vulnerability. - CHECK(out->blob.CanBeRehashed()); + CHECK(out->v8_snapshot_blob_data.CanBeRehashed()); // We cannot resurrect the handles from the snapshot, so make sure that // no handles are left open in the environment after the blob is created @@ -260,7 +333,7 @@ std::string SnapshotBuilder::Generate( SnapshotData data; Generate(&data, args, exec_args); std::string result = FormatBlob(&data); - delete[] data.blob.data; + delete[] data.v8_snapshot_blob_data.data; return result; } 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/src/node_worker.cc b/src/node_worker.cc index acc1d56d6ad329..1009c0788cc624 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -301,8 +301,8 @@ void Worker::Run() { // though. TryCatch try_catch(isolate_); if (snapshot_data_ != nullptr) { - context = Context::FromSnapshot( - isolate_, SnapshotBuilder::kNodeBaseContextIndex) + context = Context::FromSnapshot(isolate_, + SnapshotData::kNodeBaseContextIndex) .ToLocalChecked(); if (!context.IsEmpty() && !InitializeContextRuntime(context).IsJust()) { 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; -}