blob: cdab2a9aabf6a432a61e3bd139eba536baee7d4c [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 <memory>
#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;
}
const size_t accounting_length = extension->accounting_length();
DCHECK_GE(bytes_ + accounting_length, bytes_);
bytes_ += 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->ApproximateBytes();
*list = ArrayBufferList();
}
bool ArrayBufferList::ContainsSlow(ArrayBufferExtension* extension) const {
for (ArrayBufferExtension* current = head_; current;
current = current->next()) {
if (current == extension) return true;
}
return false;
}
size_t ArrayBufferList::BytesSlow() const {
ArrayBufferExtension* current = head_;
size_t sum = 0;
while (current) {
sum += current->accounting_length();
current = current->next();
}
DCHECK_GE(sum, ApproximateBytes());
return sum;
}
bool ArrayBufferList::IsEmpty() const {
DCHECK_IMPLIES(head_, tail_);
DCHECK_IMPLIES(!head_, bytes_ == 0);
return head_ == nullptr;
}
struct ArrayBufferSweeper::SweepingJob final {
SweepingJob(ArrayBufferList young, ArrayBufferList old, SweepingType type)
: state_(SweepingState::kInProgress),
young_(std::move(young)),
old_(std::move(old)),
type_(type) {}
void Sweep();
void SweepYoung();
void SweepFull();
ArrayBufferList SweepListFull(ArrayBufferList* list);
private:
CancelableTaskManager::Id id_ = CancelableTaskManager::kInvalidTaskId;
std::atomic<SweepingState> state_;
ArrayBufferList young_;
ArrayBufferList old_;
const SweepingType type_;
std::atomic<size_t> freed_bytes_{0};
friend class ArrayBufferSweeper;
};
ArrayBufferSweeper::ArrayBufferSweeper(Heap* heap) : heap_(heap) {}
ArrayBufferSweeper::~ArrayBufferSweeper() {
EnsureFinished();
ReleaseAll(&old_);
ReleaseAll(&young_);
}
void ArrayBufferSweeper::EnsureFinished() {
if (!sweeping_in_progress()) return;
TRACE_GC(heap_->tracer(), GCTracer::Scope::MC_COMPLETE_SWEEP_ARRAY_BUFFERS);
TryAbortResult abort_result =
heap_->isolate()->cancelable_task_manager()->TryAbort(job_->id_);
switch (abort_result) {
case TryAbortResult::kTaskAborted:
// Task has not run, so we need to run it synchronously here.
job_->Sweep();
break;
case TryAbortResult::kTaskRemoved:
// Task was removed, but did actually run, just ensure we are in the right
// state.
CHECK_EQ(SweepingState::kDone, job_->state_);
break;
case TryAbortResult::kTaskRunning: {
// Task is running. Wait until task is finished with its work.
base::MutexGuard guard(&sweeping_mutex_);
while (job_->state_ != SweepingState::kDone) {
job_finished_.Wait(&sweeping_mutex_);
}
break;
}
}
Finalize();
DCHECK_LE(heap_->backing_store_bytes(), SIZE_MAX);
DCHECK(!sweeping_in_progress());
}
void ArrayBufferSweeper::FinishIfDone() {
if (sweeping_in_progress()) {
DCHECK(job_);
if (job_->state_ == SweepingState::kDone) {
Finalize();
}
}
}
void ArrayBufferSweeper::RequestSweep(SweepingType type) {
DCHECK(!sweeping_in_progress());
if (young_.IsEmpty() && (old_.IsEmpty() || type == SweepingType::kYoung))
return;
Prepare(type);
if (!heap_->IsTearingDown() && !heap_->ShouldReduceMemory() &&
FLAG_concurrent_array_buffer_sweeping) {
auto task = MakeCancelableTask(heap_->isolate(), [this, type] {
GCTracer::Scope::ScopeId scope_id =
type == SweepingType::kYoung
? GCTracer::Scope::BACKGROUND_YOUNG_ARRAY_BUFFER_SWEEP
: GCTracer::Scope::BACKGROUND_FULL_ARRAY_BUFFER_SWEEP;
TRACE_GC_EPOCH(heap_->tracer(), scope_id, ThreadKind::kBackground);
base::MutexGuard guard(&sweeping_mutex_);
job_->Sweep();
job_finished_.NotifyAll();
});
job_->id_ = task->id();
V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(task));
} else {
job_->Sweep();
Finalize();
}
}
void ArrayBufferSweeper::Prepare(SweepingType type) {
DCHECK(!sweeping_in_progress());
switch (type) {
case SweepingType::kYoung: {
job_ = std::make_unique<SweepingJob>(std::move(young_), ArrayBufferList(),
type);
young_ = ArrayBufferList();
} break;
case SweepingType::kFull: {
job_ = std::make_unique<SweepingJob>(std::move(young_), std::move(old_),
type);
young_ = ArrayBufferList();
old_ = ArrayBufferList();
} break;
}
DCHECK(sweeping_in_progress());
}
void ArrayBufferSweeper::Finalize() {
DCHECK(sweeping_in_progress());
CHECK_EQ(job_->state_, SweepingState::kDone);
young_.Append(&job_->young_);
old_.Append(&job_->old_);
const size_t freed_bytes =
job_->freed_bytes_.exchange(0, std::memory_order_relaxed);
DecrementExternalMemoryCounters(freed_bytes);
job_.reset();
DCHECK(!sweeping_in_progress());
}
void ArrayBufferSweeper::ReleaseAll(ArrayBufferList* list) {
ArrayBufferExtension* current = list->head_;
while (current) {
ArrayBufferExtension* next = current->next();
delete current;
current = next;
}
*list = ArrayBufferList();
}
void ArrayBufferSweeper::Append(JSArrayBuffer object,
ArrayBufferExtension* extension) {
size_t bytes = extension->accounting_length();
FinishIfDone();
if (Heap::InYoungGeneration(object)) {
young_.Append(extension);
} else {
old_.Append(extension);
}
IncrementExternalMemoryCounters(bytes);
}
void ArrayBufferSweeper::Detach(JSArrayBuffer object,
ArrayBufferExtension* extension) {
size_t bytes = extension->ClearAccountingLength();
// We cannot free the extension eagerly here, since extensions are tracked in
// a singly linked list. The next GC will remove it automatically.
FinishIfDone();
if (!sweeping_in_progress()) {
// If concurrent sweeping isn't running at the moment, we can also adjust
// the respective bytes in the corresponding ArraybufferLists as they are
// only approximate.
if (Heap::InYoungGeneration(object)) {
DCHECK_GE(young_.bytes_, bytes);
young_.bytes_ -= bytes;
} else {
DCHECK_GE(old_.bytes_, bytes);
old_.bytes_ -= bytes;
}
}
DecrementExternalMemoryCounters(bytes);
}
void ArrayBufferSweeper::IncrementExternalMemoryCounters(size_t bytes) {
if (bytes == 0) return;
heap_->IncrementExternalBackingStoreBytes(
ExternalBackingStoreType::kArrayBuffer, bytes);
reinterpret_cast<v8::Isolate*>(heap_->isolate())
->AdjustAmountOfExternalAllocatedMemory(static_cast<int64_t>(bytes));
}
void ArrayBufferSweeper::DecrementExternalMemoryCounters(size_t bytes) {
if (bytes == 0) return;
heap_->DecrementExternalBackingStoreBytes(
ExternalBackingStoreType::kArrayBuffer, bytes);
// Unlike IncrementExternalMemoryCounters we don't use
// AdjustAmountOfExternalAllocatedMemory such that we never start a GC here.
heap_->update_external_memory(-static_cast<int64_t>(bytes));
}
void ArrayBufferSweeper::SweepingJob::Sweep() {
CHECK_EQ(state_, SweepingState::kInProgress);
switch (type_) {
case SweepingType::kYoung:
SweepYoung();
break;
case SweepingType::kFull:
SweepFull();
break;
}
state_ = SweepingState::kDone;
}
void ArrayBufferSweeper::SweepingJob::SweepFull() {
DCHECK_EQ(SweepingType::kFull, type_);
ArrayBufferList promoted = SweepListFull(&young_);
ArrayBufferList survived = SweepListFull(&old_);
old_ = std::move(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()) {
const size_t bytes = current->accounting_length();
delete current;
if (bytes) freed_bytes_.fetch_add(bytes, std::memory_order_relaxed);
} else {
current->Unmark();
survivor_list.Append(current);
}
current = next;
}
*list = ArrayBufferList();
return survivor_list;
}
void ArrayBufferSweeper::SweepingJob::SweepYoung() {
DCHECK_EQ(SweepingType::kYoung, type_);
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;
if (bytes) freed_bytes_.fetch_add(bytes, std::memory_order_relaxed);
} 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