diff --git a/src/api/environment.cc b/src/api/environment.cc index 49f63b60e22d25..404c5dd0a2faa8 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -489,7 +489,7 @@ MaybeLocal LoadEnvironment( return LoadEnvironment( env, [&](const StartExecutionCallbackInfo& info) -> MaybeLocal { std::string name = "embedder_main_" + std::to_string(env->thread_id()); - builtins::BuiltinLoader::Add(name.c_str(), main_script_source_utf8); + env->builtin_loader()->Add(name.c_str(), main_script_source_utf8); Realm* realm = env->principal_realm(); return realm->ExecuteBootstrapper(name.c_str()); @@ -729,10 +729,17 @@ Maybe InitializePrimordials(Local context) { "internal/per_context/messageport", nullptr}; + // We do not have access to a per-Environment BuiltinLoader instance + // at this point, because this code runs before an Environment exists + // in the first place. However, creating BuiltinLoader instances is + // relatively cheap and all the scripts that we may want to run at + // startup are always present in it. + thread_local builtins::BuiltinLoader builtin_loader; for (const char** module = context_files; *module != nullptr; module++) { Local arguments[] = {exports, primordials}; - if (builtins::BuiltinLoader::CompileAndCall( - context, *module, arraysize(arguments), arguments, nullptr) + if (builtin_loader + .CompileAndCall( + context, *module, arraysize(arguments), arguments, nullptr) .IsEmpty()) { // Execution failed during context creation. return Nothing(); diff --git a/src/env-inl.h b/src/env-inl.h index 6d071a9d9098d6..6b0f6f05cf0f11 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -388,6 +388,10 @@ inline std::vector* Environment::destroy_async_id_list() { return &destroy_async_id_list_; } +inline builtins::BuiltinLoader* Environment::builtin_loader() { + return &builtin_loader_; +} + inline double Environment::new_async_id() { async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1; return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter]; diff --git a/src/env.cc b/src/env.cc index 23070ae91adf35..74942f51d60869 100644 --- a/src/env.cc +++ b/src/env.cc @@ -676,6 +676,15 @@ Environment::Environment(IsolateData* isolate_data, thread_id_(thread_id.id == static_cast(-1) ? AllocateEnvironmentThreadId().id : thread_id.id) { +#ifdef NODE_V8_SHARED_RO_HEAP + if (!is_main_thread()) { + CHECK_NOT_NULL(isolate_data->worker_context()); + // TODO(addaleax): Adjust for the embedder API snapshot support changes + builtin_loader()->CopySourceAndCodeCacheReferenceFrom( + isolate_data->worker_context()->env()->builtin_loader()); + } +#endif + // We'll be creating new objects so make sure we've entered the context. HandleScope handle_scope(isolate); diff --git a/src/env.h b/src/env.h index e061b5b9b23d1a..06250f32e14863 100644 --- a/src/env.h +++ b/src/env.h @@ -695,6 +695,8 @@ class Environment : public MemoryRetainer { // List of id's that have been destroyed and need the destroy() cb called. inline std::vector* destroy_async_id_list(); + builtins::BuiltinLoader* builtin_loader(); + std::unordered_multimap hash_to_module_map; std::unordered_map id_to_module_map; std::unordered_map @@ -1099,6 +1101,8 @@ class Environment : public MemoryRetainer { std::unique_ptr principal_realm_ = nullptr; + builtins::BuiltinLoader builtin_loader_; + // Used by allocate_managed_buffer() and release_managed_buffer() to keep // track of the BackingStore for a given pointer. std::unordered_map> diff --git a/src/node.cc b/src/node.cc index a4740977e690ac..0c75b5a08edd05 100644 --- a/src/node.cc +++ b/src/node.cc @@ -132,8 +132,6 @@ namespace node { -using builtins::BuiltinLoader; - using v8::EscapableHandleScope; using v8::Isolate; using v8::Local; @@ -1214,9 +1212,6 @@ int LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, } } - if ((*snapshot_data_ptr) != nullptr) { - BuiltinLoader::RefreshCodeCache((*snapshot_data_ptr)->code_cache); - } NodeMainInstance main_instance(*snapshot_data_ptr, uv_default_loop(), per_process::v8_platform.Platform(), diff --git a/src/node_binding.cc b/src/node_binding.cc index 675e813d23b2a8..502f93845ac154 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -650,13 +650,13 @@ void GetInternalBinding(const FunctionCallbackInfo& args) { CHECK(exports->SetPrototype(context, Null(isolate)).FromJust()); DefineConstants(isolate, exports); } else if (!strcmp(*module_v, "natives")) { - exports = builtins::BuiltinLoader::GetSourceObject(context); + exports = realm->env()->builtin_loader()->GetSourceObject(context); // Legacy feature: process.binding('natives').config contains stringified // config.gypi CHECK(exports ->Set(context, realm->isolate_data()->config_string(), - builtins::BuiltinLoader::GetConfigString(isolate)) + realm->env()->builtin_loader()->GetConfigString(isolate)) .FromJust()); } else { return THROW_ERR_INVALID_MODULE(isolate, "No such binding: %s", *module_v); diff --git a/src/node_builtins.cc b/src/node_builtins.cc index e071bfea5ceadf..363586d4b197f8 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -3,6 +3,7 @@ #include "env-inl.h" #include "node_external_reference.h" #include "node_internals.h" +#include "node_threadsafe_cow-inl.h" #include "simdutf.h" #include "util-inl.h" @@ -32,9 +33,8 @@ using v8::String; using v8::Undefined; using v8::Value; -BuiltinLoader BuiltinLoader::instance_; - -BuiltinLoader::BuiltinLoader() : config_(GetConfig()), has_code_cache_(false) { +BuiltinLoader::BuiltinLoader() + : config_(GetConfig()), code_cache_(std::make_shared()) { LoadJavaScriptSource(); #ifdef NODE_SHARED_BUILTIN_CJS_MODULE_LEXER_LEXER_PATH AddExternalizedBuiltin( @@ -54,25 +54,21 @@ BuiltinLoader::BuiltinLoader() : config_(GetConfig()), has_code_cache_(false) { #endif // NODE_SHARED_BUILTIN_UNDICI_UNDICI_PATH } -BuiltinLoader* BuiltinLoader::GetInstance() { - return &instance_; -} - bool BuiltinLoader::Exists(const char* id) { - auto& source = GetInstance()->source_; - return source.find(id) != source.end(); + auto source = source_.read(); + return source->find(id) != source->end(); } bool BuiltinLoader::Add(const char* id, const UnionBytes& source) { - auto result = GetInstance()->source_.emplace(id, source); + auto result = source_.write()->emplace(id, source); return result.second; } Local BuiltinLoader::GetSourceObject(Local context) { Isolate* isolate = context->GetIsolate(); Local out = Object::New(isolate); - auto& source = GetInstance()->source_; - for (auto const& x : source) { + auto source = source_.read(); + for (auto const& x : *source) { Local key = OneByteString(isolate, x.first.c_str(), x.first.size()); out->Set(context, key, x.second.ToStringChecked(isolate)).FromJust(); } @@ -80,23 +76,21 @@ Local BuiltinLoader::GetSourceObject(Local context) { } Local BuiltinLoader::GetConfigString(Isolate* isolate) { - return GetInstance()->config_.ToStringChecked(isolate); + return config_.ToStringChecked(isolate); } -std::vector BuiltinLoader::GetBuiltinIds() { +std::vector BuiltinLoader::GetBuiltinIds() const { std::vector ids; - ids.reserve(source_.size()); - for (auto const& x : source_) { + auto source = source_.read(); + ids.reserve(source->size()); + for (auto const& x : *source) { ids.emplace_back(x.first); } return ids; } -void BuiltinLoader::InitializeBuiltinCategories() { - if (builtin_categories_.is_initialized) { - DCHECK(!builtin_categories_.can_be_required.empty()); - return; - } +BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const { + BuiltinCategories builtin_categories; std::vector prefixes = { #if !HAVE_OPENSSL @@ -110,10 +104,10 @@ void BuiltinLoader::InitializeBuiltinCategories() { "internal/main/" }; - builtin_categories_.can_be_required.emplace( + builtin_categories.can_be_required.emplace( "internal/deps/cjs-module-lexer/lexer"); - builtin_categories_.cannot_be_required = std::set { + builtin_categories.cannot_be_required = std::set { #if !HAVE_INSPECTOR "inspector", "internal/util/inspector", #endif // !HAVE_INSPECTOR @@ -136,55 +130,35 @@ void BuiltinLoader::InitializeBuiltinCategories() { "internal/v8_prof_processor", }; - for (auto const& x : source_) { + auto source = source_.read(); + for (auto const& x : *source) { const std::string& id = x.first; for (auto const& prefix : prefixes) { if (prefix.length() > id.length()) { continue; } if (id.find(prefix) == 0 && - builtin_categories_.can_be_required.count(id) == 0) { - builtin_categories_.cannot_be_required.emplace(id); + builtin_categories.can_be_required.count(id) == 0) { + builtin_categories.cannot_be_required.emplace(id); } } } - for (auto const& x : source_) { + for (auto const& x : *source) { const std::string& id = x.first; - if (0 == builtin_categories_.cannot_be_required.count(id)) { - builtin_categories_.can_be_required.emplace(id); + if (0 == builtin_categories.cannot_be_required.count(id)) { + builtin_categories.can_be_required.emplace(id); } } - builtin_categories_.is_initialized = true; -} - -const std::set& BuiltinLoader::GetCannotBeRequired() { - InitializeBuiltinCategories(); - return builtin_categories_.cannot_be_required; -} - -const std::set& BuiltinLoader::GetCanBeRequired() { - InitializeBuiltinCategories(); - return builtin_categories_.can_be_required; -} - -bool BuiltinLoader::CanBeRequired(const char* id) { - return GetCanBeRequired().count(id) == 1; -} - -bool BuiltinLoader::CannotBeRequired(const char* id) { - return GetCannotBeRequired().count(id) == 1; -} - -BuiltinCodeCacheMap* BuiltinLoader::code_cache() { - return &code_cache_; + return builtin_categories; } -ScriptCompiler::CachedData* BuiltinLoader::GetCodeCache(const char* id) const { - Mutex::ScopedLock lock(code_cache_mutex_); - const auto it = code_cache_.find(id); - if (it == code_cache_.end()) { +const ScriptCompiler::CachedData* BuiltinLoader::GetCodeCache( + const char* id) const { + RwLock::ScopedReadLock lock(code_cache_->mutex); + const auto it = code_cache_->map.find(id); + if (it == code_cache_->map.end()) { // The module has not been compiled before. return nullptr; } @@ -209,12 +183,13 @@ static std::string OnDiskFileName(const char* id) { #endif // NODE_BUILTIN_MODULES_PATH MaybeLocal BuiltinLoader::LoadBuiltinSource(Isolate* isolate, - const char* id) { + const char* id) const { + auto source = source_.read(); #ifdef NODE_BUILTIN_MODULES_PATH if (strncmp(id, "embedder_main_", strlen("embedder_main_")) == 0) { #endif // NODE_BUILTIN_MODULES_PATH - const auto source_it = source_.find(id); - if (UNLIKELY(source_it == source_.end())) { + const auto source_it = source->find(id); + if (UNLIKELY(source_it == source->end())) { fprintf(stderr, "Cannot find native builtin: \"%s\".\n", id); ABORT(); } @@ -239,14 +214,31 @@ MaybeLocal BuiltinLoader::LoadBuiltinSource(Isolate* isolate, #endif // NODE_BUILTIN_MODULES_PATH } +namespace { +static Mutex externalized_builtins_mutex; +std::unordered_map externalized_builtin_sources; +} // namespace + void BuiltinLoader::AddExternalizedBuiltin(const char* id, const char* filename) { std::string source; - int r = ReadFileSync(&source, filename); - if (r != 0) { - fprintf( - stderr, "Cannot load externalized builtin: \"%s:%s\".\n", id, filename); - ABORT(); + { + Mutex::ScopedLock lock(externalized_builtins_mutex); + auto it = externalized_builtin_sources.find(id); + if (it != externalized_builtin_sources.end()) { + source = it->second; + } + { + int r = ReadFileSync(&source, filename); + if (r != 0) { + fprintf(stderr, + "Cannot load externalized builtin: \"%s:%s\".\n", + id, + filename); + ABORT(); + } + externalized_builtin_sources[id] = source; + } } Add(id, source); @@ -288,12 +280,12 @@ MaybeLocal BuiltinLoader::LookupAndCompileInternal( // `CompileFunction()` call below, because this function may recurse if // there is a syntax error during bootstrap (because the fatal exception // handler is invoked, which may load built-in modules). - Mutex::ScopedLock lock(code_cache_mutex_); - auto cache_it = code_cache_.find(id); - if (cache_it != code_cache_.end()) { + RwLock::ScopedLock lock(code_cache_->mutex); + auto cache_it = code_cache_->map.find(id); + if (cache_it != code_cache_->map.end()) { // Transfer ownership to ScriptCompiler::Source later. cached_data = cache_it->second.release(); - code_cache_.erase(cache_it); + code_cache_->map.erase(cache_it); } } @@ -354,15 +346,8 @@ MaybeLocal BuiltinLoader::LookupAndCompileInternal( CHECK_NOT_NULL(new_cached_data); { - Mutex::ScopedLock lock(code_cache_mutex_); - 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()); - } + RwLock::ScopedLock lock(code_cache_->mutex); + code_cache_->map[id] = std::move(new_cached_data); } return scope.Escape(fun); @@ -425,9 +410,10 @@ MaybeLocal BuiltinLoader::LookupAndCompile(Local context, }; } - MaybeLocal maybe = GetInstance()->LookupAndCompileInternal( - context, id, ¶meters, &result); + MaybeLocal maybe = + LookupAndCompileInternal(context, id, ¶meters, &result); if (optional_realm != nullptr) { + DCHECK_EQ(this, optional_realm->env()->builtin_loader()); RecordResult(id, result, optional_realm); } return maybe; @@ -503,8 +489,7 @@ MaybeLocal BuiltinLoader::CompileAndCall(Local context, } bool BuiltinLoader::CompileAllBuiltins(Local context) { - BuiltinLoader* loader = GetInstance(); - std::vector ids = loader->GetBuiltinIds(); + std::vector ids = GetBuiltinIds(); bool all_succeeded = true; std::string v8_tools_prefix = "internal/deps/v8/tools/"; for (const auto& id : ids) { @@ -512,7 +497,7 @@ bool BuiltinLoader::CompileAllBuiltins(Local context) { continue; } v8::TryCatch bootstrapCatch(context->GetIsolate()); - USE(loader->LookupAndCompile(context, id.c_str(), nullptr)); + USE(LookupAndCompile(context, id.c_str(), nullptr)); if (bootstrapCatch.HasCaught()) { per_process::Debug(DebugCategory::CODE_CACHE, "Failed to compile code cache for %s\n", @@ -524,11 +509,9 @@ bool BuiltinLoader::CompileAllBuiltins(Local context) { return all_succeeded; } -void BuiltinLoader::CopyCodeCache(std::vector* out) { - BuiltinLoader* loader = GetInstance(); - Mutex::ScopedLock lock(loader->code_cache_mutex()); - auto in = loader->code_cache(); - for (auto const& item : *in) { +void BuiltinLoader::CopyCodeCache(std::vector* out) const { + RwLock::ScopedReadLock lock(code_cache_->mutex); + for (auto const& item : code_cache_->map) { out->push_back( {item.first, {item.second->data, item.second->data + item.second->length}}); @@ -536,24 +519,16 @@ void BuiltinLoader::CopyCodeCache(std::vector* out) { } void BuiltinLoader::RefreshCodeCache(const std::vector& in) { - BuiltinLoader* loader = GetInstance(); - Mutex::ScopedLock lock(loader->code_cache_mutex()); - auto out = loader->code_cache(); + RwLock::ScopedLock lock(code_cache_->mutex); 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()); - } + code_cache_->map[item.id] = std::move(new_cache); } - loader->has_code_cache_ = true; + code_cache_->has_code_cache = true; } void BuiltinLoader::GetBuiltinCategories( @@ -563,20 +538,19 @@ void BuiltinLoader::GetBuiltinCategories( Local context = env->context(); Local result = Object::New(isolate); - // Copy from the per-process categories - std::set cannot_be_required = - GetInstance()->GetCannotBeRequired(); - std::set can_be_required = GetInstance()->GetCanBeRequired(); + BuiltinCategories builtin_categories = + env->builtin_loader()->GetBuiltinCategories(); if (!env->owns_process_state()) { - can_be_required.erase("trace_events"); - cannot_be_required.insert("trace_events"); + builtin_categories.can_be_required.erase("trace_events"); + builtin_categories.cannot_be_required.insert("trace_events"); } Local cannot_be_required_js; Local can_be_required_js; - if (!ToV8Value(context, cannot_be_required).ToLocal(&cannot_be_required_js)) + if (!ToV8Value(context, builtin_categories.cannot_be_required) + .ToLocal(&cannot_be_required_js)) return; if (result ->Set(context, @@ -584,7 +558,9 @@ void BuiltinLoader::GetBuiltinCategories( cannot_be_required_js) .IsNothing()) return; - if (!ToV8Value(context, can_be_required).ToLocal(&can_be_required_js)) return; + if (!ToV8Value(context, builtin_categories.can_be_required) + .ToLocal(&can_be_required_js)) + return; if (result ->Set(context, OneByteString(isolate, "canBeRequired"), @@ -645,16 +621,19 @@ void BuiltinLoader::GetCacheUsage(const FunctionCallbackInfo& args) { void BuiltinLoader::BuiltinIdsGetter(Local property, const PropertyCallbackInfo& info) { - Isolate* isolate = info.GetIsolate(); + Environment* env = Environment::GetCurrent(info); + Isolate* isolate = env->isolate(); - std::vector ids = GetInstance()->GetBuiltinIds(); + std::vector ids = env->builtin_loader()->GetBuiltinIds(); info.GetReturnValue().Set( ToV8Value(isolate->GetCurrentContext(), ids).ToLocalChecked()); } void BuiltinLoader::ConfigStringGetter( Local property, const PropertyCallbackInfo& info) { - info.GetReturnValue().Set(GetConfigString(info.GetIsolate())); + Environment* env = Environment::GetCurrent(info); + info.GetReturnValue().Set( + env->builtin_loader()->GetConfigString(info.GetIsolate())); } void BuiltinLoader::RecordResult(const char* id, @@ -672,8 +651,8 @@ void BuiltinLoader::CompileFunction(const FunctionCallbackInfo& args) { CHECK(args[0]->IsString()); node::Utf8Value id_v(realm->isolate(), args[0].As()); const char* id = *id_v; - MaybeLocal maybe = - GetInstance()->LookupAndCompile(realm->context(), id, realm); + MaybeLocal maybe = realm->env()->builtin_loader()->LookupAndCompile( + realm->context(), id, realm); Local fn; if (maybe.ToLocal(&fn)) { args.GetReturnValue().Set(fn); @@ -681,8 +660,16 @@ void BuiltinLoader::CompileFunction(const FunctionCallbackInfo& args) { } void BuiltinLoader::HasCachedBuiltins(const FunctionCallbackInfo& args) { - args.GetReturnValue().Set( - v8::Boolean::New(args.GetIsolate(), GetInstance()->has_code_cache_)); + auto instance = Environment::GetCurrent(args)->builtin_loader(); + RwLock::ScopedReadLock lock(instance->code_cache_->mutex); + args.GetReturnValue().Set(v8::Boolean::New( + args.GetIsolate(), instance->code_cache_->has_code_cache)); +} + +void BuiltinLoader::CopySourceAndCodeCacheReferenceFrom( + const BuiltinLoader* other) { + code_cache_ = other->code_cache_; + source_ = other->source_; } void BuiltinLoader::CreatePerIsolateProperties(IsolateData* isolate_data, diff --git a/src/node_builtins.h b/src/node_builtins.h index f90a4151850d54..81d6d1cca279c2 100644 --- a/src/node_builtins.h +++ b/src/node_builtins.h @@ -6,10 +6,12 @@ #include #include #include +#include #include #include #include #include "node_mutex.h" +#include "node_threadsafe_cow.h" #include "node_union_bytes.h" #include "v8.h" @@ -37,6 +39,7 @@ struct CodeCacheInfo { // bootstrap scripts, whose source are bundled into the binary as static data. class NODE_EXTERN_PRIVATE BuiltinLoader { public: + BuiltinLoader(); BuiltinLoader(const BuiltinLoader&) = delete; BuiltinLoader& operator=(const BuiltinLoader&) = delete; @@ -50,62 +53,56 @@ class NODE_EXTERN_PRIVATE BuiltinLoader { // The parameters used to compile the scripts are detected based on // the pattern of the id. - static v8::MaybeLocal LookupAndCompile( - v8::Local context, const char* id, Realm* optional_realm); + v8::MaybeLocal LookupAndCompile(v8::Local context, + const char* id, + Realm* optional_realm); - static v8::MaybeLocal CompileAndCall( - v8::Local context, - const char* id, - int argc, - v8::Local argv[], - Realm* optional_realm); + v8::MaybeLocal CompileAndCall(v8::Local context, + const char* id, + int argc, + v8::Local argv[], + Realm* optional_realm); - static v8::MaybeLocal CompileAndCall( - v8::Local context, const char* id, Realm* realm); + v8::MaybeLocal CompileAndCall(v8::Local context, + const char* id, + Realm* realm); - static v8::Local GetSourceObject(v8::Local context); + v8::Local GetSourceObject(v8::Local context); // Returns config.gypi as a JSON string - static v8::Local GetConfigString(v8::Isolate* isolate); - static bool Exists(const char* id); - static bool Add(const char* id, const UnionBytes& source); - static bool Add(const char* id, std::string_view utf8source); + v8::Local GetConfigString(v8::Isolate* isolate); + bool Exists(const char* id); + bool Add(const char* id, const UnionBytes& source); + bool Add(const char* id, std::string_view utf8source); - static bool CompileAllBuiltins(v8::Local context); - static void RefreshCodeCache(const std::vector& in); - static void CopyCodeCache(std::vector* out); + bool CompileAllBuiltins(v8::Local context); + void RefreshCodeCache(const std::vector& in); + void CopyCodeCache(std::vector* out) const; + + void CopySourceAndCodeCacheReferenceFrom(const BuiltinLoader* other); private: // Only allow access from friends. friend class CodeCacheBuilder; - BuiltinLoader(); - static BuiltinLoader* GetInstance(); - // Generated by tools/js2c.py as node_javascript.cc void LoadJavaScriptSource(); // Loads data into source_ UnionBytes GetConfig(); // Return data for config.gypi - std::vector GetBuiltinIds(); + std::vector GetBuiltinIds() const; struct BuiltinCategories { - bool is_initialized = false; std::set can_be_required; std::set cannot_be_required; }; - void InitializeBuiltinCategories(); - const std::set& GetCannotBeRequired(); - const std::set& GetCanBeRequired(); - - bool CanBeRequired(const char* id); - bool CannotBeRequired(const char* id); + // This method builds `BuiltinCategories` from scratch every time, + // and is therefore somewhat expensive, but also currently only being + // used for testing, so that should not be an issue. + BuiltinCategories GetBuiltinCategories() const; - BuiltinCodeCacheMap* code_cache(); - const Mutex& code_cache_mutex() const { return code_cache_mutex_; } - - v8::ScriptCompiler::CachedData* GetCodeCache(const char* id) const; + const v8::ScriptCompiler::CachedData* GetCodeCache(const char* id) const; enum class Result { kWithCache, kWithoutCache }; v8::MaybeLocal LoadBuiltinSource(v8::Isolate* isolate, - const char* id); + const char* id) const; // If an exception is encountered (e.g. source code contains // syntax error), the returned value is empty. v8::MaybeLocal LookupAndCompileInternal( @@ -134,18 +131,18 @@ class NODE_EXTERN_PRIVATE BuiltinLoader { static void HasCachedBuiltins( const v8::FunctionCallbackInfo& args); - static void AddExternalizedBuiltin(const char* id, const char* filename); + void AddExternalizedBuiltin(const char* id, const char* filename); - static BuiltinLoader instance_; - BuiltinCategories builtin_categories_; - BuiltinSourceMap source_; - BuiltinCodeCacheMap code_cache_; - UnionBytes config_; + ThreadsafeCopyOnWrite source_; - // Used to synchronize access to the code cache map - Mutex code_cache_mutex_; + const UnionBytes config_; - bool has_code_cache_; + struct BuiltinCodeCache { + RwLock mutex; + BuiltinCodeCacheMap map; + bool has_code_cache = false; + }; + std::shared_ptr code_cache_; friend class ::PerProcessTest; }; diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index a8661c3c2263fc..1d23631780cf90 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -158,6 +158,11 @@ NodeMainInstance::CreateMainEnvironment(int* exit_code) { &(snapshot_data_->env_info), EnvironmentFlags::kDefaultFlags, {})); +#ifdef NODE_V8_SHARED_RO_HEAP + // TODO(addaleax): Do this as part of creating the Environment + // once we store the SnapshotData* itself on IsolateData. + env->builtin_loader()->RefreshCodeCache(snapshot_data_->code_cache); +#endif context = Context::FromSnapshot(isolate_, SnapshotData::kNodeMainContextIndex, {DeserializeNodeInternalFields, env.get()}) diff --git a/src/node_realm.cc b/src/node_realm.cc index de11906b2aa309..ed141f3d6f4db2 100644 --- a/src/node_realm.cc +++ b/src/node_realm.cc @@ -8,7 +8,6 @@ namespace node { -using builtins::BuiltinLoader; using v8::Context; using v8::EscapableHandleScope; using v8::Function; @@ -168,7 +167,8 @@ void Realm::DeserializeProperties(const RealmSerializeInfo* info) { MaybeLocal Realm::ExecuteBootstrapper(const char* id) { EscapableHandleScope scope(isolate()); Local ctx = context(); - MaybeLocal result = BuiltinLoader::CompileAndCall(ctx, id, this); + MaybeLocal result = + env()->builtin_loader()->CompileAndCall(ctx, id, this); // If there was an error during bootstrap, it must be unrecoverable // (e.g. max call stack exceeded). Clear the stack so that the diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 508f47caa9756a..281073121cce78 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -1231,10 +1231,10 @@ int SnapshotBuilder::Generate(SnapshotData* out, #ifdef NODE_USE_NODE_CODE_CACHE // Regenerate all the code cache. - if (!builtins::BuiltinLoader::CompileAllBuiltins(main_context)) { + if (!env->builtin_loader()->CompileAllBuiltins(main_context)) { return UNCAUGHT_EXCEPTION_ERROR; } - builtins::BuiltinLoader::CopyCodeCache(&(out->code_cache)); + env->builtin_loader()->CopyCodeCache(&(out->code_cache)); for (const auto& item : out->code_cache) { std::string size_str = FormatSize(item.data.size()); per_process::Debug(DebugCategory::MKSNAPSHOT, diff --git a/src/node_threadsafe_cow.h b/src/node_threadsafe_cow.h new file mode 100644 index 00000000000000..8cfdd006b12f57 --- /dev/null +++ b/src/node_threadsafe_cow.h @@ -0,0 +1,105 @@ +#ifndef SRC_NODE_THREADSAFE_COW_H_ +#define SRC_NODE_THREADSAFE_COW_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "util.h" +#include "uv.h" + +#include // std::shared_ptr +#include // std::forward + +namespace node { + +// Copy-on-write utility. Not threadsafe, i.e. there is no synchronization +// of the copy operation with other operations. +template +class CopyOnWrite final { + public: + template + explicit CopyOnWrite(Args&&... args) + : data_(std::make_shared(std::forward(args)...)) {} + + CopyOnWrite(const CopyOnWrite& other) = default; + CopyOnWrite& operator=(const CopyOnWrite& other) = default; + CopyOnWrite(CopyOnWrite&& other) = default; + CopyOnWrite& operator=(CopyOnWrite&& other) = default; + + const T* read() const { return data_.get(); } + T* write(); + + const T& operator*() const { return *read(); } + const T* operator->() const { return read(); } + + private: + std::shared_ptr data_; +}; + +// Threadsafe copy-on-write utility. Consumers need to use the Read and +// Write helpers to access the target data structure. +template +class ThreadsafeCopyOnWrite final { + private: + // Define this early since some of the public members depend on it + // and some compilers need it to be defined first in that case. + struct Impl { + explicit Impl(const T& data) : data(data) {} + explicit Impl(T&& data) : data(std::move(data)) {} + + Impl(const Impl& other); + Impl& operator=(const Impl& other) = delete; + Impl(Impl&& other) = delete; + Impl& operator=(Impl&& other) = delete; + + RwLock mutex; + T data; + }; + + public: + template + ThreadsafeCopyOnWrite(Args&&... args) + : impl_(T(std::forward(args)...)) {} + + ThreadsafeCopyOnWrite(const ThreadsafeCopyOnWrite& other) = default; + ThreadsafeCopyOnWrite& operator=(const ThreadsafeCopyOnWrite& other) = + default; + ThreadsafeCopyOnWrite(ThreadsafeCopyOnWrite&& other) = default; + ThreadsafeCopyOnWrite& operator=(ThreadsafeCopyOnWrite&& other) = default; + + class Read { + public: + explicit Read(const ThreadsafeCopyOnWrite* cow); + + const T& operator*() const; + const T* operator->() const; + + private: + const ThreadsafeCopyOnWrite* cow_; + RwLock::ScopedReadLock lock_; + }; + + class Write { + public: + explicit Write(ThreadsafeCopyOnWrite* cow); + + T& operator*(); + T* operator->(); + + private: + ThreadsafeCopyOnWrite* cow_; + typename ThreadsafeCopyOnWrite::Impl* impl_; + RwLock::ScopedLock lock_; + }; + + Read read() const { return Read(this); } + Write write() { return Write(this); } + + private: + CopyOnWrite impl_; +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_THREADSAFE_COW_H_ diff --git a/test/cctest/test_per_process.cc b/test/cctest/test_per_process.cc index 1e3dff7114e337..34cf163add7904 100644 --- a/test/cctest/test_per_process.cc +++ b/test/cctest/test_per_process.cc @@ -1,4 +1,5 @@ #include "node_builtins.h" +#include "node_threadsafe_cow-inl.h" #include "gtest/gtest.h" #include "node_test_fixture.h" @@ -11,7 +12,7 @@ using node::builtins::BuiltinSourceMap; class PerProcessTest : public ::testing::Test { protected: static const BuiltinSourceMap get_sources_for_test() { - return BuiltinLoader::instance_.source_; + return *BuiltinLoader().source_.read(); } }; diff --git a/tools/js2c.py b/tools/js2c.py index 54c7dfce363100..50f34c070ac099 100755 --- a/tools/js2c.py +++ b/tools/js2c.py @@ -57,8 +57,14 @@ def ReadFile(filename): {0} +namespace {{ +const ThreadsafeCopyOnWrite global_source_map {{ + BuiltinSourceMap{{ {1} }} +}}; +}} + void BuiltinLoader::LoadJavaScriptSource() {{ - {1} + source_ = global_source_map; }} UnionBytes BuiltinLoader::GetConfig() {{ @@ -82,7 +88,7 @@ def ReadFile(filename): }}; """ -INITIALIZER = 'source_.emplace("{0}", UnionBytes{{{1}, {2}}});' +INITIALIZER = '{{"{0}", UnionBytes{{{1}, {2}}} }},' CONFIG_GYPI_ID = 'config_raw'