• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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