Commit c52d2854 authored by Clemens Hammacher's avatar Clemens Hammacher Committed by Commit Bot

[wasm][gc] Track potentially dead code per engine

This adds data structures to track potentially dead code in the wasm
engine. The engine will then trigger an engine-wide GC once the
potentially dead code reaches a certain threshold.

R=mstarzinger@chromium.org

Bug: v8:8217
Change-Id: I13216a66bb8e8e1594b165a65708e53057e9e535
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1559736
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60718}
parent 5abd06f3
...@@ -371,6 +371,26 @@ WasmCode::~WasmCode() { ...@@ -371,6 +371,26 @@ WasmCode::~WasmCode() {
} }
} }
V8_WARN_UNUSED_RESULT bool WasmCode::DecRefOnPotentiallyDeadCode() {
if (native_module_->engine()->AddPotentiallyDeadCode(this)) {
// The code just became potentially dead. The ref count we wanted to
// decrement is now transferred to the set of potentially dead code, and
// will be decremented when the next GC is run.
return false;
}
// If we reach here, the code was already potentially dead. Decrement the ref
// count, and return true if it drops to zero.
int old_count = ref_count_.load(std::memory_order_relaxed);
while (true) {
DCHECK_LE(1, old_count);
if (ref_count_.compare_exchange_weak(old_count, old_count - 1,
std::memory_order_relaxed)) {
return old_count == 1;
}
}
}
// static
void WasmCode::DecrementRefCount(Vector<WasmCode*> code_vec) { void WasmCode::DecrementRefCount(Vector<WasmCode*> code_vec) {
// Decrement the ref counter of all given code objects. Keep the ones whose // Decrement the ref counter of all given code objects. Keep the ones whose
// ref count drops to zero. // ref count drops to zero.
......
...@@ -151,14 +151,22 @@ class V8_EXPORT_PRIVATE WasmCode final { ...@@ -151,14 +151,22 @@ class V8_EXPORT_PRIVATE WasmCode final {
USE(old_val); USE(old_val);
} }
// Decrement the ref count. Returns whether this code becomes dead and needs
// to be freed.
V8_WARN_UNUSED_RESULT bool DecRef() { V8_WARN_UNUSED_RESULT bool DecRef() {
int old_count = ref_count_.fetch_sub(1, std::memory_order_relaxed); int old_count = ref_count_.load(std::memory_order_relaxed);
DCHECK_LE(1, old_count); while (true) {
return old_count == 1; DCHECK_LE(1, old_count);
if (V8_UNLIKELY(old_count == 1)) return DecRefOnPotentiallyDeadCode();
if (ref_count_.compare_exchange_weak(old_count, old_count - 1,
std::memory_order_relaxed)) {
return false;
}
}
} }
// Decrement the ref count on set of {WasmCode} objects, potentially belonging // Decrement the ref count on a set of {WasmCode} objects, potentially
// to different {NativeModule}s. // belonging to different {NativeModule}s. Dead code will be deleted.
static void DecrementRefCount(Vector<WasmCode*>); static void DecrementRefCount(Vector<WasmCode*>);
enum FlushICache : bool { kFlushICache = true, kNoFlushICache = false }; enum FlushICache : bool { kFlushICache = true, kNoFlushICache = false };
...@@ -210,6 +218,10 @@ class V8_EXPORT_PRIVATE WasmCode final { ...@@ -210,6 +218,10 @@ class V8_EXPORT_PRIVATE WasmCode final {
// trap_handler_index. // trap_handler_index.
void RegisterTrapHandlerData(); void RegisterTrapHandlerData();
// Slow path for {DecRef}: The code becomes potentially dead.
// Returns whether this code becomes dead and needs to be freed.
bool DecRefOnPotentiallyDeadCode();
Vector<byte> instructions_; Vector<byte> instructions_;
OwnedVector<const byte> reloc_info_; OwnedVector<const byte> reloc_info_;
OwnedVector<const byte> source_position_table_; OwnedVector<const byte> source_position_table_;
...@@ -236,7 +248,7 @@ class V8_EXPORT_PRIVATE WasmCode final { ...@@ -236,7 +248,7 @@ class V8_EXPORT_PRIVATE WasmCode final {
// 1) The jump table. // 1) The jump table.
// 2) Function tables. // 2) Function tables.
// 3) {WasmCodeRefScope}s. // 3) {WasmCodeRefScope}s.
// 4) Threads currently executing this code. // 4) The set of potentially dead code in the {WasmEngine}.
// If a decrement of (1) or (2) would drop the ref count to 0, that code // If a decrement of (1) or (2) would drop the ref count to 0, that code
// becomes a candidate for garbage collection. At that point, we add // becomes a candidate for garbage collection. At that point, we add
// ref counts for (4) *before* decrementing the counter to ensure the code // ref counts for (4) *before* decrementing the counter to ensure the code
......
...@@ -104,6 +104,16 @@ struct WasmEngine::IsolateInfo { ...@@ -104,6 +104,16 @@ struct WasmEngine::IsolateInfo {
std::shared_ptr<v8::TaskRunner> foreground_task_runner; std::shared_ptr<v8::TaskRunner> foreground_task_runner;
}; };
struct WasmEngine::NativeModuleInfo {
// Set of isolates using this NativeModule.
std::unordered_set<Isolate*> isolates;
// Set of potentially dead code. The ref-count of these code objects was
// incremented for each Isolate that might still execute the code, and is
// decremented on {RemoveIsolate} or on a GC.
std::unordered_set<WasmCode*> potentially_dead_code;
};
WasmEngine::WasmEngine() WasmEngine::WasmEngine()
: code_manager_(&memory_tracker_, FLAG_wasm_max_code_space * MB) {} : code_manager_(&memory_tracker_, FLAG_wasm_max_code_space * MB) {}
...@@ -115,7 +125,7 @@ WasmEngine::~WasmEngine() { ...@@ -115,7 +125,7 @@ WasmEngine::~WasmEngine() {
// All Isolates have been deregistered. // All Isolates have been deregistered.
DCHECK(isolates_.empty()); DCHECK(isolates_.empty());
// All NativeModules did die. // All NativeModules did die.
DCHECK(isolates_per_native_module_.empty()); DCHECK(native_modules_.empty());
} }
bool WasmEngine::SyncValidate(Isolate* isolate, const WasmFeatures& enabled, bool WasmEngine::SyncValidate(Isolate* isolate, const WasmFeatures& enabled,
...@@ -350,8 +360,8 @@ Handle<WasmModuleObject> WasmEngine::ImportNativeModule( ...@@ -350,8 +360,8 @@ Handle<WasmModuleObject> WasmEngine::ImportNativeModule(
base::MutexGuard lock(&mutex_); base::MutexGuard lock(&mutex_);
DCHECK_EQ(1, isolates_.count(isolate)); DCHECK_EQ(1, isolates_.count(isolate));
isolates_[isolate]->native_modules.insert(native_module); isolates_[isolate]->native_modules.insert(native_module);
DCHECK_EQ(1, isolates_per_native_module_.count(native_module)); DCHECK_EQ(1, native_modules_.count(native_module));
isolates_per_native_module_[native_module].insert(isolate); native_modules_[native_module]->isolates.insert(isolate);
} }
return module_object; return module_object;
} }
...@@ -459,8 +469,10 @@ void WasmEngine::RemoveIsolate(Isolate* isolate) { ...@@ -459,8 +469,10 @@ void WasmEngine::RemoveIsolate(Isolate* isolate) {
auto it = isolates_.find(isolate); auto it = isolates_.find(isolate);
DCHECK_NE(isolates_.end(), it); DCHECK_NE(isolates_.end(), it);
for (NativeModule* native_module : it->second->native_modules) { for (NativeModule* native_module : it->second->native_modules) {
DCHECK_EQ(1, isolates_per_native_module_[native_module].count(isolate)); DCHECK_EQ(1, native_modules_.count(native_module));
isolates_per_native_module_[native_module].erase(isolate); DCHECK_EQ(1, native_modules_[native_module]->isolates.count(isolate));
auto* info = native_modules_[native_module].get();
info->isolates.erase(isolate);
} }
if (auto* task = it->second->log_codes_task) task->Cancel(); if (auto* task = it->second->log_codes_task) task->Cancel();
isolates_.erase(it); isolates_.erase(it);
...@@ -469,8 +481,8 @@ void WasmEngine::RemoveIsolate(Isolate* isolate) { ...@@ -469,8 +481,8 @@ void WasmEngine::RemoveIsolate(Isolate* isolate) {
void WasmEngine::LogCode(WasmCode* code) { void WasmEngine::LogCode(WasmCode* code) {
base::MutexGuard guard(&mutex_); base::MutexGuard guard(&mutex_);
NativeModule* native_module = code->native_module(); NativeModule* native_module = code->native_module();
DCHECK_EQ(1, isolates_per_native_module_.count(native_module)); DCHECK_EQ(1, native_modules_.count(native_module));
for (Isolate* isolate : isolates_per_native_module_[native_module]) { for (Isolate* isolate : native_modules_[native_module]->isolates) {
DCHECK_EQ(1, isolates_.count(isolate)); DCHECK_EQ(1, isolates_.count(isolate));
IsolateInfo* info = isolates_[isolate].get(); IsolateInfo* info = isolates_[isolate].get();
if (info->log_codes == false) continue; if (info->log_codes == false) continue;
...@@ -498,8 +510,10 @@ std::shared_ptr<NativeModule> WasmEngine::NewNativeModule( ...@@ -498,8 +510,10 @@ std::shared_ptr<NativeModule> WasmEngine::NewNativeModule(
code_manager_.NewNativeModule(this, isolate, enabled, code_size_estimate, code_manager_.NewNativeModule(this, isolate, enabled, code_size_estimate,
can_request_more, std::move(module)); can_request_more, std::move(module));
base::MutexGuard lock(&mutex_); base::MutexGuard lock(&mutex_);
isolates_per_native_module_[native_module.get()].insert(isolate); auto pair = native_modules_.insert(std::make_pair(
DCHECK_EQ(1, isolates_.count(isolate)); native_module.get(), base::make_unique<NativeModuleInfo>()));
DCHECK(pair.second); // inserted new entry.
pair.first->second.get()->isolates.insert(isolate);
isolates_[isolate]->native_modules.insert(native_module.get()); isolates_[isolate]->native_modules.insert(native_module.get());
return native_module; return native_module;
} }
...@@ -507,14 +521,14 @@ std::shared_ptr<NativeModule> WasmEngine::NewNativeModule( ...@@ -507,14 +521,14 @@ std::shared_ptr<NativeModule> WasmEngine::NewNativeModule(
void WasmEngine::FreeNativeModule(NativeModule* native_module) { void WasmEngine::FreeNativeModule(NativeModule* native_module) {
{ {
base::MutexGuard guard(&mutex_); base::MutexGuard guard(&mutex_);
auto it = isolates_per_native_module_.find(native_module); auto it = native_modules_.find(native_module);
DCHECK_NE(isolates_per_native_module_.end(), it); DCHECK_NE(native_modules_.end(), it);
for (Isolate* isolate : it->second) { for (Isolate* isolate : it->second->isolates) {
DCHECK_EQ(1, isolates_.count(isolate)); DCHECK_EQ(1, isolates_.count(isolate));
DCHECK_EQ(1, isolates_[isolate]->native_modules.count(native_module)); DCHECK_EQ(1, isolates_[isolate]->native_modules.count(native_module));
isolates_[isolate]->native_modules.erase(native_module); isolates_[isolate]->native_modules.erase(native_module);
} }
isolates_per_native_module_.erase(it); native_modules_.erase(it);
} }
code_manager_.FreeNativeModule(native_module); code_manager_.FreeNativeModule(native_module);
} }
...@@ -544,8 +558,8 @@ class SampleTopTierCodeSizeTask : public CancelableTask { ...@@ -544,8 +558,8 @@ class SampleTopTierCodeSizeTask : public CancelableTask {
void WasmEngine::SampleTopTierCodeSizeInAllIsolates( void WasmEngine::SampleTopTierCodeSizeInAllIsolates(
const std::shared_ptr<NativeModule>& native_module) { const std::shared_ptr<NativeModule>& native_module) {
base::MutexGuard lock(&mutex_); base::MutexGuard lock(&mutex_);
DCHECK_EQ(1, isolates_per_native_module_.count(native_module.get())); DCHECK_EQ(1, native_modules_.count(native_module.get()));
for (Isolate* isolate : isolates_per_native_module_[native_module.get()]) { for (Isolate* isolate : native_modules_[native_module.get()]->isolates) {
DCHECK_EQ(1, isolates_.count(isolate)); DCHECK_EQ(1, isolates_.count(isolate));
IsolateInfo* info = isolates_[isolate].get(); IsolateInfo* info = isolates_[isolate].get();
info->foreground_task_runner->PostTask( info->foreground_task_runner->PostTask(
...@@ -553,6 +567,17 @@ void WasmEngine::SampleTopTierCodeSizeInAllIsolates( ...@@ -553,6 +567,17 @@ void WasmEngine::SampleTopTierCodeSizeInAllIsolates(
} }
} }
bool WasmEngine::AddPotentiallyDeadCode(WasmCode* code) {
base::MutexGuard guard(&mutex_);
auto it = native_modules_.find(code->native_module());
DCHECK_NE(native_modules_.end(), it);
auto added = it->second->potentially_dead_code.insert(code);
if (!added.second) return false; // An entry already existed.
new_potentially_dead_code_size_ += code->instructions().size();
// TODO(clemensh): Trigger GC if the size exceeds a certain threshold.
return true;
}
namespace { namespace {
DEFINE_LAZY_LEAKY_OBJECT_GETTER(std::shared_ptr<WasmEngine>, DEFINE_LAZY_LEAKY_OBJECT_GETTER(std::shared_ptr<WasmEngine>,
......
...@@ -181,6 +181,13 @@ class V8_EXPORT_PRIVATE WasmEngine { ...@@ -181,6 +181,13 @@ class V8_EXPORT_PRIVATE WasmEngine {
// This will spawn foreground tasks that do *not* keep the NativeModule alive. // This will spawn foreground tasks that do *not* keep the NativeModule alive.
void SampleTopTierCodeSizeInAllIsolates(const std::shared_ptr<NativeModule>&); void SampleTopTierCodeSizeInAllIsolates(const std::shared_ptr<NativeModule>&);
// Add potentially dead code. The occurrence in the set of potentially dead
// code counts as a reference, and is decremented on the next GC.
// Returns {true} if the code was added to the set of potentially dead code,
// {false} if an entry already exists. The ref count is *unchanged* in any
// case.
V8_WARN_UNUSED_RESULT bool AddPotentiallyDeadCode(WasmCode*);
// Call on process start and exit. // Call on process start and exit.
static void InitializeOncePerProcess(); static void InitializeOncePerProcess();
static void GlobalTearDown(); static void GlobalTearDown();
...@@ -191,6 +198,7 @@ class V8_EXPORT_PRIVATE WasmEngine { ...@@ -191,6 +198,7 @@ class V8_EXPORT_PRIVATE WasmEngine {
private: private:
struct IsolateInfo; struct IsolateInfo;
struct NativeModuleInfo;
AsyncCompileJob* CreateAsyncCompileJob( AsyncCompileJob* CreateAsyncCompileJob(
Isolate* isolate, const WasmFeatures& enabled, Isolate* isolate, const WasmFeatures& enabled,
...@@ -224,10 +232,13 @@ class V8_EXPORT_PRIVATE WasmEngine { ...@@ -224,10 +232,13 @@ class V8_EXPORT_PRIVATE WasmEngine {
// Set of isolates which use this WasmEngine. // Set of isolates which use this WasmEngine.
std::unordered_map<Isolate*, std::unique_ptr<IsolateInfo>> isolates_; std::unordered_map<Isolate*, std::unique_ptr<IsolateInfo>> isolates_;
// Maps each NativeModule to the set of Isolates that have access to that // Set of native modules managed by this engine.
// NativeModule. The isolate sets currently only grow, they never shrink. std::unordered_map<NativeModule*, std::unique_ptr<NativeModuleInfo>>
std::unordered_map<NativeModule*, std::unordered_set<Isolate*>> native_modules_;
isolates_per_native_module_;
// Size of code that became dead since the last GC. If this exceeds a certain
// threshold, a new GC is triggered.
size_t new_potentially_dead_code_size_ = 0;
// End of fields protected by {mutex_}. // 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