Commit 2be674e7 authored by Thibaud Michaud's avatar Thibaud Michaud Committed by Commit Bot

[wasm] Move native module cache in its own class

Move caching logic out of the {WasmEngine} and in its own
{NativeModuleCache} class, with its own mutex.

R=clemensb@chromium.org

Bug: v8:6847
Change-Id: I73067fd9f0556e57c28782088dcb772a14265154
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2004613Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65895}
parent 9914f26d
......@@ -126,6 +126,70 @@ class WasmGCForegroundTask : public CancelableTask {
} // namespace
std::shared_ptr<NativeModule> NativeModuleCache::MaybeGetNativeModule(
ModuleOrigin origin, Vector<const uint8_t> wire_bytes) {
if (origin != kWasmOrigin) return nullptr;
base::MutexGuard lock(&mutex_);
while (true) {
auto it = map_.find(wire_bytes);
if (it == map_.end()) {
// Insert a {nullopt} entry to let other threads know that this
// {NativeModule} is already being created on another thread.
map_.emplace(wire_bytes, base::nullopt);
return nullptr;
}
auto maybe_native_module = it->second;
if (maybe_native_module.has_value()) {
auto weak_ptr = maybe_native_module.value();
if (auto shared_native_module = weak_ptr.lock()) {
return shared_native_module;
}
}
cache_cv_.Wait(&mutex_);
}
}
void NativeModuleCache::Update(std::shared_ptr<NativeModule> native_module,
bool error) {
DCHECK_NOT_NULL(native_module);
if (native_module->module()->origin != kWasmOrigin) return;
Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
base::MutexGuard lock(&mutex_);
auto it = map_.find(wire_bytes);
DCHECK_NE(it, map_.end());
DCHECK(!it->second.has_value());
// The lifetime of the temporary entry's bytes is unknown. Use the new native
// module's owned copy of the bytes for the key instead.
map_.erase(it);
if (!error) {
map_.emplace(wire_bytes, base::Optional<std::weak_ptr<NativeModule>>(
std::move(native_module)));
}
cache_cv_.NotifyAll();
}
void NativeModuleCache::Erase(NativeModule* native_module) {
base::MutexGuard lock(&mutex_);
auto cache_it = map_.find(native_module->wire_bytes());
// Not all native modules are stored in the cache currently. In particular
// streaming compilation and asmjs compilation results are not. So make
// sure that we only delete existing and expired entries.
// Do not erase {nullopt} values either, as they indicate that the
// {NativeModule} is currently being created in another thread.
if (cache_it != map_.end() && cache_it->second.has_value() &&
cache_it->second.value().expired()) {
map_.erase(cache_it);
cache_cv_.NotifyAll();
}
}
size_t NativeModuleCache::WireBytesHasher::operator()(
const Vector<const uint8_t>& bytes) const {
return StringHasher::HashSequentialString(
reinterpret_cast<const char*>(bytes.begin()), bytes.length(),
kZeroHashSeed);
}
struct WasmEngine::CurrentGCInfo {
explicit CurrentGCInfo(int8_t gc_sequence_index)
: gc_sequence_index(gc_sequence_index) {
......@@ -693,45 +757,12 @@ std::shared_ptr<NativeModule> WasmEngine::NewNativeModule(
std::shared_ptr<NativeModule> WasmEngine::MaybeGetNativeModule(
ModuleOrigin origin, Vector<const uint8_t> wire_bytes) {
if (origin != kWasmOrigin) return nullptr;
base::MutexGuard lock(&mutex_);
while (true) {
auto it = native_module_cache_.find(wire_bytes);
if (it == native_module_cache_.end()) {
// Insert a {nullopt} entry to let other threads know that this
// {NativeModule} is already being created on another thread.
native_module_cache_.emplace(wire_bytes, base::nullopt);
return nullptr;
}
auto maybe_native_module = it->second;
if (maybe_native_module.has_value()) {
auto weak_ptr = maybe_native_module.value();
if (auto shared_native_module = weak_ptr.lock()) {
return shared_native_module;
}
}
cache_cv_.Wait(&mutex_);
}
return native_module_cache_.MaybeGetNativeModule(origin, wire_bytes);
}
void WasmEngine::UpdateNativeModuleCache(
std::shared_ptr<NativeModule> native_module, bool error) {
DCHECK_NOT_NULL(native_module);
if (native_module->module()->origin != kWasmOrigin) return;
Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
base::MutexGuard lock(&mutex_);
auto it = native_module_cache_.find(wire_bytes);
DCHECK_NE(it, native_module_cache_.end());
DCHECK(!it->second.has_value());
// The lifetime of the temporary entry's bytes is unknown. Use the new native
// module's owned copy of the bytes for the key instead.
native_module_cache_.erase(it);
if (!error) {
native_module_cache_.emplace(
wire_bytes,
base::Optional<std::weak_ptr<NativeModule>>(std::move(native_module)));
}
cache_cv_.NotifyAll();
native_module_cache_.Update(native_module, error);
}
void WasmEngine::FreeNativeModule(NativeModule* native_module) {
......@@ -774,17 +805,7 @@ void WasmEngine::FreeNativeModule(NativeModule* native_module) {
TRACE_CODE_GC("Native module %p died, reducing dead code objects to %zu.\n",
native_module, current_gc_info_->dead_code.size());
}
auto cache_it = native_module_cache_.find(native_module->wire_bytes());
// Not all native modules are stored in the cache currently. In particular
// streaming compilation and asmjs compilation results are not. So make
// sure that we only delete existing and expired entries.
// Do not erase {nullopt} values either, as they indicate that the
// {NativeModule} is currently being created in another thread.
if (cache_it != native_module_cache_.end() && cache_it->second.has_value() &&
cache_it->second.value().expired()) {
native_module_cache_.erase(cache_it);
cache_cv_.NotifyAll();
}
native_module_cache_.Erase(native_module);
native_modules_.erase(it);
}
......@@ -1022,13 +1043,6 @@ std::shared_ptr<WasmEngine> WasmEngine::GetWasmEngine() {
return *GetSharedWasmEngine();
}
size_t WasmEngine::WireBytesHasher::operator()(
const Vector<const uint8_t>& bytes) const {
return StringHasher::HashSequentialString(
reinterpret_cast<const char*>(bytes.begin()), bytes.length(),
kZeroHashSeed);
}
// {max_mem_pages} is declared in wasm-limits.h.
uint32_t max_mem_pages() {
STATIC_ASSERT(kV8MaxWasmMemoryPages <= kMaxUInt32);
......
......@@ -48,6 +48,43 @@ class V8_EXPORT_PRIVATE InstantiationResultResolver {
virtual ~InstantiationResultResolver() = default;
};
// Native modules cached by their wire bytes.
class NativeModuleCache {
public:
struct WireBytesHasher {
size_t operator()(const Vector<const uint8_t>& bytes) const;
};
std::shared_ptr<NativeModule> MaybeGetNativeModule(
ModuleOrigin origin, Vector<const uint8_t> wire_bytes);
void Update(std::shared_ptr<NativeModule> native_module, bool error);
void Erase(NativeModule* native_module);
private:
// Each key points to the corresponding native module's wire bytes, so they
// should always be valid as long as the native module is alive. When
// the native module dies, {FreeNativeModule} deletes the entry from the
// map, so that we do not leave any dangling key pointing to an expired
// weak_ptr. This also serves as a way to regularly clean up the map, which
// would otherwise accumulate expired entries.
// A {nullopt} value is inserted to indicate that this native module is
// currently being created in some thread, and that other threads should wait
// before trying to get it from the cache.
// By contrast, an expired {weak_ptr} indicates that the native module died
// and will soon be cleaned up from the cache.
std::unordered_map<Vector<const uint8_t>,
base::Optional<std::weak_ptr<NativeModule>>,
WireBytesHasher>
map_;
base::Mutex mutex_;
// This condition variable is used to synchronize threads compiling the same
// module. Only one thread will create the {NativeModule}. Other threads
// will wait on this variable until the first thread wakes them up.
base::ConditionVariable cache_cv_;
};
// The central data structure that represents an engine instance capable of
// loading, instantiating, and executing WASM code.
class V8_EXPORT_PRIVATE WasmEngine {
......@@ -294,35 +331,7 @@ class V8_EXPORT_PRIVATE WasmEngine {
// about that.
std::unique_ptr<CurrentGCInfo> current_gc_info_;
struct WireBytesHasher {
size_t operator()(const Vector<const uint8_t>& bytes) const;
};
// Native modules cached by their wire bytes.
//
// Each key points to the corresponding native module's wire bytes, so they
// should always be valid as long as the native module is alive. When
// the native module dies, {FreeNativeModule} deletes the entry from the
// map, so that we do not leave any dangling key pointing to an expired
// weak_ptr. This also serves as a way to regularly clean up the map, which
// would otherwise accumulate expired entries.
//
// A {nullopt} is used to indicate that a native module is currently being
// created and will soon be inserted in the cache, provided that no errors
// occurred.
// By contrast, an expired {weak_ptr} indicates that the native module died
// and will soon be cleaned up from the cache.
// TODO(thibaudm): This distinction should not be necessary when all native
// modules are cached.
std::unordered_map<Vector<const uint8_t>,
base::Optional<std::weak_ptr<NativeModule>>,
WireBytesHasher>
native_module_cache_;
// This condition variable is used to synchronize threads compiling the same
// module. Only one thread will create the {NativeModule}. The other threads
// will wait on this variable until the first thread wakes them up.
base::ConditionVariable cache_cv_;
NativeModuleCache native_module_cache_;
// End of fields protected by {mutex_}.
//////////////////////////////////////////////////////////////////////////////
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment