1 // Copyright 2017 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/heap/array-buffer-sweeper.h"
6
7 #include <atomic>
8
9 #include "src/heap/gc-tracer.h"
10 #include "src/heap/heap-inl.h"
11 #include "src/objects/js-array-buffer.h"
12 #include "src/tasks/cancelable-task.h"
13 #include "src/tasks/task-utils.h"
14
15 namespace v8 {
16 namespace internal {
17
Append(ArrayBufferExtension * extension)18 void ArrayBufferList::Append(ArrayBufferExtension* extension) {
19 if (head_ == nullptr) {
20 DCHECK_NULL(tail_);
21 head_ = tail_ = extension;
22 } else {
23 tail_->set_next(extension);
24 tail_ = extension;
25 }
26
27 bytes_ += extension->accounting_length();
28 extension->set_next(nullptr);
29 }
30
Append(ArrayBufferList * list)31 void ArrayBufferList::Append(ArrayBufferList* list) {
32 if (head_ == nullptr) {
33 DCHECK_NULL(tail_);
34 head_ = list->head_;
35 tail_ = list->tail_;
36 } else if (list->head_) {
37 DCHECK_NOT_NULL(list->tail_);
38 tail_->set_next(list->head_);
39 tail_ = list->tail_;
40 } else {
41 DCHECK_NULL(list->tail_);
42 }
43
44 bytes_ += list->Bytes();
45 list->Reset();
46 }
47
Contains(ArrayBufferExtension * extension)48 bool ArrayBufferList::Contains(ArrayBufferExtension* extension) {
49 ArrayBufferExtension* current = head_;
50
51 while (current) {
52 if (current == extension) return true;
53 current = current->next();
54 }
55
56 return false;
57 }
58
BytesSlow()59 size_t ArrayBufferList::BytesSlow() {
60 ArrayBufferExtension* current = head_;
61 size_t sum = 0;
62
63 while (current) {
64 sum += current->accounting_length();
65 current = current->next();
66 }
67
68 return sum;
69 }
70
EnsureFinished()71 void ArrayBufferSweeper::EnsureFinished() {
72 if (!sweeping_in_progress_) return;
73
74 TryAbortResult abort_result =
75 heap_->isolate()->cancelable_task_manager()->TryAbort(job_->id_);
76
77 switch (abort_result) {
78 case TryAbortResult::kTaskAborted: {
79 job_->Sweep();
80 Merge();
81 break;
82 }
83
84 case TryAbortResult::kTaskRemoved: {
85 if (job_->state_ == SweepingState::kInProgress) job_->Sweep();
86 if (job_->state_ == SweepingState::kDone) Merge();
87 break;
88 }
89
90 case TryAbortResult::kTaskRunning: {
91 base::MutexGuard guard(&sweeping_mutex_);
92 // Wait until task is finished with its work.
93 while (job_->state_ != SweepingState::kDone) {
94 job_finished_.Wait(&sweeping_mutex_);
95 }
96 Merge();
97 break;
98 }
99
100 default:
101 UNREACHABLE();
102 }
103
104 DecrementExternalMemoryCounters();
105 sweeping_in_progress_ = false;
106 }
107
AdjustCountersAndMergeIfPossible()108 void ArrayBufferSweeper::AdjustCountersAndMergeIfPossible() {
109 if (sweeping_in_progress_) {
110 DCHECK(job_.has_value());
111 if (job_->state_ == SweepingState::kDone) {
112 Merge();
113 sweeping_in_progress_ = false;
114 } else {
115 DecrementExternalMemoryCounters();
116 }
117 }
118 }
119
DecrementExternalMemoryCounters()120 void ArrayBufferSweeper::DecrementExternalMemoryCounters() {
121 size_t freed_bytes = freed_bytes_.exchange(0, std::memory_order_relaxed);
122
123 if (freed_bytes > 0) {
124 heap_->DecrementExternalBackingStoreBytes(
125 ExternalBackingStoreType::kArrayBuffer, freed_bytes);
126 heap_->update_external_memory(-static_cast<int64_t>(freed_bytes));
127 }
128 }
129
RequestSweepYoung()130 void ArrayBufferSweeper::RequestSweepYoung() {
131 RequestSweep(SweepingScope::kYoung);
132 }
133
RequestSweepFull()134 void ArrayBufferSweeper::RequestSweepFull() {
135 RequestSweep(SweepingScope::kFull);
136 }
137
YoungBytes()138 size_t ArrayBufferSweeper::YoungBytes() { return young_bytes_; }
139
OldBytes()140 size_t ArrayBufferSweeper::OldBytes() { return old_bytes_; }
141
RequestSweep(SweepingScope scope)142 void ArrayBufferSweeper::RequestSweep(SweepingScope scope) {
143 DCHECK(!sweeping_in_progress_);
144
145 if (young_.IsEmpty() && (old_.IsEmpty() || scope == SweepingScope::kYoung))
146 return;
147
148 if (!heap_->IsTearingDown() && !heap_->ShouldReduceMemory() &&
149 FLAG_concurrent_array_buffer_sweeping) {
150 Prepare(scope);
151
152 auto task = MakeCancelableTask(heap_->isolate(), [this] {
153 TRACE_BACKGROUND_GC(
154 heap_->tracer(),
155 GCTracer::BackgroundScope::BACKGROUND_ARRAY_BUFFER_SWEEP);
156 base::MutexGuard guard(&sweeping_mutex_);
157 job_->Sweep();
158 job_finished_.NotifyAll();
159 });
160 job_->id_ = task->id();
161 V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(task));
162 sweeping_in_progress_ = true;
163 } else {
164 Prepare(scope);
165 job_->Sweep();
166 Merge();
167 DecrementExternalMemoryCounters();
168 }
169 }
170
Prepare(SweepingScope scope)171 void ArrayBufferSweeper::Prepare(SweepingScope scope) {
172 DCHECK(!job_.has_value());
173
174 if (scope == SweepingScope::kYoung) {
175 job_.emplace(this, young_, ArrayBufferList(), SweepingScope::kYoung);
176 young_.Reset();
177 young_bytes_ = 0;
178 } else {
179 CHECK_EQ(scope, SweepingScope::kFull);
180 job_.emplace(this, young_, old_, SweepingScope::kFull);
181 young_.Reset();
182 old_.Reset();
183 young_bytes_ = old_bytes_ = 0;
184 }
185 }
186
Merge()187 void ArrayBufferSweeper::Merge() {
188 DCHECK(job_.has_value());
189 CHECK_EQ(job_->state_, SweepingState::kDone);
190 young_.Append(&job_->young_);
191 old_.Append(&job_->old_);
192 young_bytes_ = young_.Bytes();
193 old_bytes_ = old_.Bytes();
194
195 job_.reset();
196 }
197
ReleaseAll()198 void ArrayBufferSweeper::ReleaseAll() {
199 EnsureFinished();
200 ReleaseAll(&old_);
201 ReleaseAll(&young_);
202 old_bytes_ = young_bytes_ = 0;
203 }
204
ReleaseAll(ArrayBufferList * list)205 void ArrayBufferSweeper::ReleaseAll(ArrayBufferList* list) {
206 ArrayBufferExtension* current = list->head_;
207
208 while (current) {
209 ArrayBufferExtension* next = current->next();
210 delete current;
211 current = next;
212 }
213
214 list->Reset();
215 }
216
Append(JSArrayBuffer object,ArrayBufferExtension * extension)217 void ArrayBufferSweeper::Append(JSArrayBuffer object,
218 ArrayBufferExtension* extension) {
219 size_t bytes = extension->accounting_length();
220
221 if (Heap::InYoungGeneration(object)) {
222 young_.Append(extension);
223 young_bytes_ += bytes;
224 } else {
225 old_.Append(extension);
226 old_bytes_ += bytes;
227 }
228
229 AdjustCountersAndMergeIfPossible();
230 DecrementExternalMemoryCounters();
231 IncrementExternalMemoryCounters(bytes);
232 }
233
IncrementExternalMemoryCounters(size_t bytes)234 void ArrayBufferSweeper::IncrementExternalMemoryCounters(size_t bytes) {
235 heap_->IncrementExternalBackingStoreBytes(
236 ExternalBackingStoreType::kArrayBuffer, bytes);
237 reinterpret_cast<v8::Isolate*>(heap_->isolate())
238 ->AdjustAmountOfExternalAllocatedMemory(static_cast<int64_t>(bytes));
239 }
240
IncrementFreedBytes(size_t bytes)241 void ArrayBufferSweeper::IncrementFreedBytes(size_t bytes) {
242 if (bytes == 0) return;
243 freed_bytes_.fetch_add(bytes, std::memory_order_relaxed);
244 }
245
Sweep()246 void ArrayBufferSweeper::SweepingJob::Sweep() {
247 CHECK_EQ(state_, SweepingState::kInProgress);
248
249 if (scope_ == SweepingScope::kYoung) {
250 SweepYoung();
251 } else {
252 CHECK_EQ(scope_, SweepingScope::kFull);
253 SweepFull();
254 }
255 state_ = SweepingState::kDone;
256 }
257
SweepFull()258 void ArrayBufferSweeper::SweepingJob::SweepFull() {
259 CHECK_EQ(scope_, SweepingScope::kFull);
260 ArrayBufferList promoted = SweepListFull(&young_);
261 ArrayBufferList survived = SweepListFull(&old_);
262
263 old_ = promoted;
264 old_.Append(&survived);
265 }
266
SweepListFull(ArrayBufferList * list)267 ArrayBufferList ArrayBufferSweeper::SweepingJob::SweepListFull(
268 ArrayBufferList* list) {
269 ArrayBufferExtension* current = list->head_;
270 ArrayBufferList survivor_list;
271
272 while (current) {
273 ArrayBufferExtension* next = current->next();
274
275 if (!current->IsMarked()) {
276 size_t bytes = current->accounting_length();
277 delete current;
278 sweeper_->IncrementFreedBytes(bytes);
279 } else {
280 current->Unmark();
281 survivor_list.Append(current);
282 }
283
284 current = next;
285 }
286
287 list->Reset();
288 return survivor_list;
289 }
290
SweepYoung()291 void ArrayBufferSweeper::SweepingJob::SweepYoung() {
292 CHECK_EQ(scope_, SweepingScope::kYoung);
293 ArrayBufferExtension* current = young_.head_;
294
295 ArrayBufferList new_young;
296 ArrayBufferList new_old;
297
298 while (current) {
299 ArrayBufferExtension* next = current->next();
300
301 if (!current->IsYoungMarked()) {
302 size_t bytes = current->accounting_length();
303 delete current;
304 sweeper_->IncrementFreedBytes(bytes);
305 } else if (current->IsYoungPromoted()) {
306 current->YoungUnmark();
307 new_old.Append(current);
308 } else {
309 current->YoungUnmark();
310 new_young.Append(current);
311 }
312
313 current = next;
314 }
315
316 old_ = new_old;
317 young_ = new_young;
318 }
319
320 } // namespace internal
321 } // namespace v8
322