[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: Michael Starzinger <mstarzinger@chromium.org> Cr-Commit-Position: refs/heads/master@{#60718}
This commit is contained in:
parent
5abd06f3bd
commit
c52d285408
@ -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) {
|
||||
// Decrement the ref counter of all given code objects. Keep the ones whose
|
||||
// ref count drops to zero.
|
||||
|
@ -151,14 +151,22 @@ class V8_EXPORT_PRIVATE WasmCode final {
|
||||
USE(old_val);
|
||||
}
|
||||
|
||||
// Decrement the ref count. Returns whether this code becomes dead and needs
|
||||
// to be freed.
|
||||
V8_WARN_UNUSED_RESULT bool DecRef() {
|
||||
int old_count = ref_count_.fetch_sub(1, std::memory_order_relaxed);
|
||||
DCHECK_LE(1, old_count);
|
||||
return old_count == 1;
|
||||
int old_count = ref_count_.load(std::memory_order_relaxed);
|
||||
while (true) {
|
||||
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
|
||||
// to different {NativeModule}s.
|
||||
// Decrement the ref count on a set of {WasmCode} objects, potentially
|
||||
// belonging to different {NativeModule}s. Dead code will be deleted.
|
||||
static void DecrementRefCount(Vector<WasmCode*>);
|
||||
|
||||
enum FlushICache : bool { kFlushICache = true, kNoFlushICache = false };
|
||||
@ -210,6 +218,10 @@ class V8_EXPORT_PRIVATE WasmCode final {
|
||||
// trap_handler_index.
|
||||
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_;
|
||||
OwnedVector<const byte> reloc_info_;
|
||||
OwnedVector<const byte> source_position_table_;
|
||||
@ -236,7 +248,7 @@ class V8_EXPORT_PRIVATE WasmCode final {
|
||||
// 1) The jump table.
|
||||
// 2) Function tables.
|
||||
// 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
|
||||
// becomes a candidate for garbage collection. At that point, we add
|
||||
// ref counts for (4) *before* decrementing the counter to ensure the code
|
||||
|
@ -104,6 +104,16 @@ struct WasmEngine::IsolateInfo {
|
||||
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()
|
||||
: code_manager_(&memory_tracker_, FLAG_wasm_max_code_space * MB) {}
|
||||
|
||||
@ -115,7 +125,7 @@ WasmEngine::~WasmEngine() {
|
||||
// All Isolates have been deregistered.
|
||||
DCHECK(isolates_.empty());
|
||||
// All NativeModules did die.
|
||||
DCHECK(isolates_per_native_module_.empty());
|
||||
DCHECK(native_modules_.empty());
|
||||
}
|
||||
|
||||
bool WasmEngine::SyncValidate(Isolate* isolate, const WasmFeatures& enabled,
|
||||
@ -350,8 +360,8 @@ Handle<WasmModuleObject> WasmEngine::ImportNativeModule(
|
||||
base::MutexGuard lock(&mutex_);
|
||||
DCHECK_EQ(1, isolates_.count(isolate));
|
||||
isolates_[isolate]->native_modules.insert(native_module);
|
||||
DCHECK_EQ(1, isolates_per_native_module_.count(native_module));
|
||||
isolates_per_native_module_[native_module].insert(isolate);
|
||||
DCHECK_EQ(1, native_modules_.count(native_module));
|
||||
native_modules_[native_module]->isolates.insert(isolate);
|
||||
}
|
||||
return module_object;
|
||||
}
|
||||
@ -459,8 +469,10 @@ void WasmEngine::RemoveIsolate(Isolate* isolate) {
|
||||
auto it = isolates_.find(isolate);
|
||||
DCHECK_NE(isolates_.end(), it);
|
||||
for (NativeModule* native_module : it->second->native_modules) {
|
||||
DCHECK_EQ(1, isolates_per_native_module_[native_module].count(isolate));
|
||||
isolates_per_native_module_[native_module].erase(isolate);
|
||||
DCHECK_EQ(1, native_modules_.count(native_module));
|
||||
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();
|
||||
isolates_.erase(it);
|
||||
@ -469,8 +481,8 @@ void WasmEngine::RemoveIsolate(Isolate* isolate) {
|
||||
void WasmEngine::LogCode(WasmCode* code) {
|
||||
base::MutexGuard guard(&mutex_);
|
||||
NativeModule* native_module = code->native_module();
|
||||
DCHECK_EQ(1, isolates_per_native_module_.count(native_module));
|
||||
for (Isolate* isolate : isolates_per_native_module_[native_module]) {
|
||||
DCHECK_EQ(1, native_modules_.count(native_module));
|
||||
for (Isolate* isolate : native_modules_[native_module]->isolates) {
|
||||
DCHECK_EQ(1, isolates_.count(isolate));
|
||||
IsolateInfo* info = isolates_[isolate].get();
|
||||
if (info->log_codes == false) continue;
|
||||
@ -498,8 +510,10 @@ std::shared_ptr<NativeModule> WasmEngine::NewNativeModule(
|
||||
code_manager_.NewNativeModule(this, isolate, enabled, code_size_estimate,
|
||||
can_request_more, std::move(module));
|
||||
base::MutexGuard lock(&mutex_);
|
||||
isolates_per_native_module_[native_module.get()].insert(isolate);
|
||||
DCHECK_EQ(1, isolates_.count(isolate));
|
||||
auto pair = native_modules_.insert(std::make_pair(
|
||||
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());
|
||||
return native_module;
|
||||
}
|
||||
@ -507,14 +521,14 @@ std::shared_ptr<NativeModule> WasmEngine::NewNativeModule(
|
||||
void WasmEngine::FreeNativeModule(NativeModule* native_module) {
|
||||
{
|
||||
base::MutexGuard guard(&mutex_);
|
||||
auto it = isolates_per_native_module_.find(native_module);
|
||||
DCHECK_NE(isolates_per_native_module_.end(), it);
|
||||
for (Isolate* isolate : it->second) {
|
||||
auto it = native_modules_.find(native_module);
|
||||
DCHECK_NE(native_modules_.end(), it);
|
||||
for (Isolate* isolate : it->second->isolates) {
|
||||
DCHECK_EQ(1, isolates_.count(isolate));
|
||||
DCHECK_EQ(1, isolates_[isolate]->native_modules.count(native_module));
|
||||
isolates_[isolate]->native_modules.erase(native_module);
|
||||
}
|
||||
isolates_per_native_module_.erase(it);
|
||||
native_modules_.erase(it);
|
||||
}
|
||||
code_manager_.FreeNativeModule(native_module);
|
||||
}
|
||||
@ -544,8 +558,8 @@ class SampleTopTierCodeSizeTask : public CancelableTask {
|
||||
void WasmEngine::SampleTopTierCodeSizeInAllIsolates(
|
||||
const std::shared_ptr<NativeModule>& native_module) {
|
||||
base::MutexGuard lock(&mutex_);
|
||||
DCHECK_EQ(1, isolates_per_native_module_.count(native_module.get()));
|
||||
for (Isolate* isolate : isolates_per_native_module_[native_module.get()]) {
|
||||
DCHECK_EQ(1, native_modules_.count(native_module.get()));
|
||||
for (Isolate* isolate : native_modules_[native_module.get()]->isolates) {
|
||||
DCHECK_EQ(1, isolates_.count(isolate));
|
||||
IsolateInfo* info = isolates_[isolate].get();
|
||||
info->foreground_task_runner->PostTask(
|
||||
@ -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 {
|
||||
|
||||
DEFINE_LAZY_LEAKY_OBJECT_GETTER(std::shared_ptr<WasmEngine>,
|
||||
|
@ -181,6 +181,13 @@ class V8_EXPORT_PRIVATE WasmEngine {
|
||||
// This will spawn foreground tasks that do *not* keep the NativeModule alive.
|
||||
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.
|
||||
static void InitializeOncePerProcess();
|
||||
static void GlobalTearDown();
|
||||
@ -191,6 +198,7 @@ class V8_EXPORT_PRIVATE WasmEngine {
|
||||
|
||||
private:
|
||||
struct IsolateInfo;
|
||||
struct NativeModuleInfo;
|
||||
|
||||
AsyncCompileJob* CreateAsyncCompileJob(
|
||||
Isolate* isolate, const WasmFeatures& enabled,
|
||||
@ -224,10 +232,13 @@ class V8_EXPORT_PRIVATE WasmEngine {
|
||||
// Set of isolates which use this WasmEngine.
|
||||
std::unordered_map<Isolate*, std::unique_ptr<IsolateInfo>> isolates_;
|
||||
|
||||
// Maps each NativeModule to the set of Isolates that have access to that
|
||||
// NativeModule. The isolate sets currently only grow, they never shrink.
|
||||
std::unordered_map<NativeModule*, std::unordered_set<Isolate*>>
|
||||
isolates_per_native_module_;
|
||||
// Set of native modules managed by this engine.
|
||||
std::unordered_map<NativeModule*, std::unique_ptr<NativeModuleInfo>>
|
||||
native_modules_;
|
||||
|
||||
// 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_}.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
Loading…
Reference in New Issue
Block a user