blob: 5bc8fcb720af3c49635f1284a9e8bc51a4450bb8 [file] [log] [blame]
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/heap/array-buffer-sweeper.h"
#include <atomic>
#include "src/heap/gc-tracer.h"
#include "src/heap/heap-inl.h"
#include "src/objects/js-array-buffer.h"
#include "src/tasks/cancelable-task.h"
#include "src/tasks/task-utils.h"
namespace v8 {
namespace internal {
void ArrayBufferList::Append(ArrayBufferExtension* extension) {
if (head_ == nullptr) {
DCHECK_NULL(tail_);
head_ = tail_ = extension;
} else {
tail_->set_next(extension);
tail_ = extension;
}
bytes_ += extension->accounting_length();
extension->set_next(nullptr);
}
void ArrayBufferList::Append(ArrayBufferList* list) {
if (head_ == nullptr) {
DCHECK_NULL(tail_);
head_ = list->head_;
tail_ = list->tail_;
} else if (list->head_) {
DCHECK_NOT_NULL(list->tail_);
tail_->set_next(list->head_);
tail_ = list->tail_;
} else {
DCHECK_NULL(list->tail_);
}
bytes_ += list->Bytes();
list->Reset();
}
bool ArrayBufferList::Contains(ArrayBufferExtension* extension) {
ArrayBufferExtension* current = head_;
while (current) {
if (current == extension) return true;
current = current->next();
}
return false;
}
size_t ArrayBufferList::BytesSlow() {
ArrayBufferExtension* current = head_;
size_t sum = 0;
while (current) {
sum += current->accounting_length();
current = current->next();
}
return sum;
}
void ArrayBufferSweeper::EnsureFinished() {
if (!sweeping_in_progress_) return;
TryAbortResult abort_result =
heap_->isolate()->cancelable_task_manager()->TryAbort(job_->id_);
switch (abort_result) {
case TryAbortResult::kTaskAborted: {
job_->Sweep();
Merge();
break;
}
case TryAbortResult::kTaskRemoved: {
if (job_->state_ == SweepingState::kInProgress) job_->Sweep();
if (job_->state_ == SweepingState::kDone) Merge();
break;
}
case TryAbortResult::kTaskRunning: {
base::MutexGuard guard(&sweeping_mutex_);
// Wait until task is finished with its work.
while (job_->state_ != SweepingState::kDone) {
job_finished_.Wait(&sweeping_mutex_);
}
Merge();
break;
}
default:
UNREACHABLE();
}
DecrementExternalMemoryCounters();
sweeping_in_progress_ = false;
}
void ArrayBufferSweeper::AdjustCountersAndMergeIfPossible() {
if (sweeping_in_progress_) {
DCHECK(job_.has_value());
if (job_->state_ == SweepingState::kDone) {
Merge();
sweeping_in_progress_ = false;
} else {
DecrementExternalMemoryCounters();
}
}
}
void ArrayBufferSweeper::DecrementExternalMemoryCounters() {
size_t freed_bytes = freed_bytes_.exchange(0, std::memory_order_relaxed);
if (freed_bytes > 0) {
heap_->DecrementExternalBackingStoreBytes(
ExternalBackingStoreType::kArrayBuffer, freed_bytes);
heap_->update_external_memory(-static_cast<int64_t>(freed_bytes));
}
}
void ArrayBufferSweeper::RequestSweepYoung() {
RequestSweep(SweepingScope::kYoung);
}
void ArrayBufferSweeper::RequestSweepFull() {
RequestSweep(SweepingScope::kFull);
}
size_t ArrayBufferSweeper::YoungBytes() { return young_bytes_; }
size_t ArrayBufferSweeper::OldBytes() { return old_bytes_; }
void ArrayBufferSweeper::RequestSweep(SweepingScope scope) {
DCHECK(!sweeping_in_progress_);
if (young_.IsEmpty() && (old_.IsEmpty() || scope == SweepingScope::kYoung))
return;
if (!heap_->IsTearingDown() && !heap_->ShouldReduceMemory() &&
FLAG_concurrent_array_buffer_sweeping) {
Prepare(scope);
auto task = MakeCancelableTask(heap_->isolate(), [this] {
TRACE_BACKGROUND_GC(
heap_->tracer(),
GCTracer::BackgroundScope::BACKGROUND_ARRAY_BUFFER_SWEEP);
base::MutexGuard guard(&sweeping_mutex_);
job_->Sweep();
job_finished_.NotifyAll();
});
job_->id_ = task->id();
V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(task));
sweeping_in_progress_ = true;
} else {
Prepare(scope);
job_->Sweep();
Merge();
DecrementExternalMemoryCounters();
}
}
void ArrayBufferSweeper::Prepare(SweepingScope scope) {
DCHECK(!job_.has_value());
if (scope == SweepingScope::kYoung) {
job_.emplace(this, young_, ArrayBufferList(), SweepingScope::kYoung);
young_.Reset();
young_bytes_ = 0;
} else {
CHECK_EQ(scope, SweepingScope::kFull);
job_.emplace(this, young_, old_, SweepingScope::kFull);
young_.Reset();
old_.Reset();
young_bytes_ = old_bytes_ = 0;
}
}
void ArrayBufferSweeper::Merge() {
DCHECK(job_.has_value());
CHECK_EQ(job_->state_, SweepingState::kDone);
young_.Append(&job_->young_);
old_.Append(&job_->old_);
young_bytes_ = young_.Bytes();
old_bytes_ = old_.Bytes();
job_.reset();
}
void ArrayBufferSweeper::ReleaseAll() {
EnsureFinished();
ReleaseAll(&old_);
ReleaseAll(&young_);
old_bytes_ = young_bytes_ = 0;
}
void ArrayBufferSweeper::ReleaseAll(ArrayBufferList* list) {
ArrayBufferExtension* current = list->head_;
while (current) {
ArrayBufferExtension* next = current->next();
delete current;
current = next;
}
list->Reset();
}
void ArrayBufferSweeper::Append(JSArrayBuffer object,
ArrayBufferExtension* extension) {
size_t bytes = extension->accounting_length();
if (Heap::InYoungGeneration(object)) {
young_.Append(extension);
young_bytes_ += bytes;
} else {
old_.Append(extension);
old_bytes_ += bytes;
}
AdjustCountersAndMergeIfPossible();
DecrementExternalMemoryCounters();
IncrementExternalMemoryCounters(bytes);
}
void ArrayBufferSweeper::IncrementExternalMemoryCounters(size_t bytes) {
heap_->IncrementExternalBackingStoreBytes(
ExternalBackingStoreType::kArrayBuffer, bytes);
reinterpret_cast<v8::Isolate*>(heap_->isolate())
->AdjustAmountOfExternalAllocatedMemory(static_cast<int64_t>(bytes));
}
void ArrayBufferSweeper::IncrementFreedBytes(size_t bytes) {
if (bytes == 0) return;
freed_bytes_.fetch_add(bytes, std::memory_order_relaxed);
}
void ArrayBufferSweeper::SweepingJob::Sweep() {
CHECK_EQ(state_, SweepingState::kInProgress);
if (scope_ == SweepingScope::kYoung) {
SweepYoung();
} else {
CHECK_EQ(scope_, SweepingScope::kFull);
SweepFull();
}
state_ = SweepingState::kDone;
}
void ArrayBufferSweeper::SweepingJob::SweepFull() {
CHECK_EQ(scope_, SweepingScope::kFull);
ArrayBufferList promoted = SweepListFull(&young_);
ArrayBufferList survived = SweepListFull(&old_);
old_ = promoted;
old_.Append(&survived);
}
ArrayBufferList ArrayBufferSweeper::SweepingJob::SweepListFull(
ArrayBufferList* list) {
ArrayBufferExtension* current = list->head_;
ArrayBufferList survivor_list;
while (current) {
ArrayBufferExtension* next = current->next();
if (!current->IsMarked()) {
size_t bytes = current->accounting_length();
delete current;
sweeper_->IncrementFreedBytes(bytes);
} else {
current->Unmark();
survivor_list.Append(current);
}
current = next;
}
list->Reset();
return survivor_list;
}
void ArrayBufferSweeper::SweepingJob::SweepYoung() {
CHECK_EQ(scope_, SweepingScope::kYoung);
ArrayBufferExtension* current = young_.head_;
ArrayBufferList new_young;
ArrayBufferList new_old;
while (current) {
ArrayBufferExtension* next = current->next();
if (!current->IsYoungMarked()) {
size_t bytes = current->accounting_length();
delete current;
sweeper_->IncrementFreedBytes(bytes);
} else if (current->IsYoungPromoted()) {
current->YoungUnmark();
new_old.Append(current);
} else {
current->YoungUnmark();
new_young.Append(current);
}
current = next;
}
old_ = new_old;
young_ = new_young;
}
} // namespace internal
} // namespace v8