v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
array-buffer-sweeper.cc
Go to the documentation of this file.
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
6
7#include <atomic>
8#include <memory>
9#include <utility>
10
11#include "src/base/logging.h"
14#include "src/heap/gc-tracer.h"
15#include "src/heap/heap-inl.h"
17#include "src/heap/heap.h"
19
20namespace v8 {
21namespace internal {
22
24 if (head_ == nullptr) {
27 } else {
30 }
31
32 const size_t accounting_length = [&] {
34 return extension->SetOld().accounting_length();
35 } else {
36 return extension->SetYoung().accounting_length();
37 }
38 }();
39 DCHECK_GE(bytes_ + accounting_length, bytes_);
40 bytes_ += accounting_length;
41 extension->set_next(nullptr);
42 return accounting_length;
43}
44
46 DCHECK_EQ(age_, list.age_);
47
48 if (head_ == nullptr) {
50 head_ = list.head_;
51 tail_ = list.tail_;
52 } else if (list.head_) {
54 tail_->set_next(list.head_);
55 tail_ = list.tail_;
56 } else {
57 DCHECK_NULL(list.tail_);
58 }
59
60 bytes_ += list.ApproximateBytes();
61 list = ArrayBufferList(age_);
62}
63
65 for (ArrayBufferExtension* current = head_; current;
66 current = current->next()) {
67 if (current == extension) return true;
68 }
69 return false;
70}
71
73 ArrayBufferExtension* current = head_;
74 size_t sum = 0;
75 while (current) {
76 sum += current->accounting_length();
77 current = current->next();
78 }
80 return sum;
81}
82
86 return head_ == nullptr;
87}
88
90 enum class Status { kInProgress, kDone };
91
92 public:
94 SweepingType type,
95 TreatAllYoungAsPromoted treat_all_young_as_promoted,
96 uint64_t trace_id);
97
99
100 void SetDone() { status_.store(Status::kDone, std::memory_order_relaxed); }
101 bool IsDone() const {
102 return status_.load(std::memory_order_relaxed) == Status::kDone;
103 }
104
106 // the worker may see a difference between `young/old_bytes_accounted_` and
107 // `initial_young/old_bytes_` due to concurrent main thread adjustments
108 // (resizing).
115 sweeper->young_.bytes_,
116 0);
118 sweeper->old_.bytes_,
119 0);
120 sweeper->young_.Append(new_young_);
121 sweeper->old_.Append(new_old_);
122 // Apply pending adjustments from resizing and detaching.
123 sweeper->young_.bytes_ +=
124 std::exchange(sweeper->young_bytes_adjustment_while_sweeping_, 0);
125 sweeper->old_.bytes_ +=
126 std::exchange(sweeper->old_bytes_adjustment_while_sweeping_, 0);
128 }
129
130 void StartBackgroundSweeping() { job_handle_->NotifyConcurrencyIncrease(); }
132 DCHECK(job_handle_ && job_handle_->IsValid());
133 job_handle_->Join();
134 }
135
136 private:
137 class SweepingJob;
138
139 std::atomic<Status> status_{Status::kInProgress};
140 ArrayBufferList new_young_{ArrayBufferList::Age::kYoung};
141 ArrayBufferList new_old_{ArrayBufferList::Age::kOld};
142 size_t freed_bytes_{0};
143 const uint64_t initial_young_bytes_{0};
144 const uint64_t initial_old_bytes_{0};
145 // Track bytes accounted bytes during sweeping, including freed and promoted
146 // bytes. This is used to compute adjustment when sweeping finishes.
149 std::unique_ptr<JobHandle> job_handle_;
150};
151
153 public:
156 TreatAllYoungAsPromoted treat_all_young_as_promoted,
157 uint64_t trace_id)
158 : heap_(heap),
159 state_(state),
160 young_(young),
161 old_(old),
162 type_(type),
163 treat_all_young_as_promoted_(treat_all_young_as_promoted),
164 trace_id_(trace_id),
165 local_sweeper_(heap_->sweeper()) {}
166
167 ~SweepingJob() override = default;
168
169 SweepingJob(const SweepingJob&) = delete;
171
172 void Run(JobDelegate* delegate) final;
173
174 size_t GetMaxConcurrency(size_t worker_count) const override {
175 return state_.IsDone() ? 0 : 1;
176 }
177
178 private:
179 void Sweep(JobDelegate* delegate);
180 // Returns true if sweeping finished. Returns false if sweeping yielded while
181 // there are still array buffers left to sweep.
182 bool SweepYoung(JobDelegate* delegate);
183 bool SweepFull(JobDelegate* delegate);
184 bool SweepListFull(JobDelegate* delegate, ArrayBufferList& list,
186
187 Heap* const heap_;
189 ArrayBufferList young_{ArrayBufferList::Age::kYoung};
190 ArrayBufferList old_{ArrayBufferList::Age::kOld};
193 const uint64_t trace_id_;
195};
196
198 JobDelegate* delegate) {
199 // Set the current isolate such that trusted pointer tables etc are
200 // available and the cage base is set correctly for multi-cage mode.
201 SetCurrentIsolateScope isolate_scope(heap_->isolate());
202 const ThreadKind thread_kind =
205 // Waiting for promoted page iteration is only needed when not all young
206 // array buffers are promoted.
207 GCTracer::Scope::ScopeId scope_id =
209 ? thread_kind == ThreadKind::kMain
210 ? GCTracer::Scope::MINOR_MS_SWEEP
211 : GCTracer::Scope::MINOR_MS_BACKGROUND_SWEEPING
212 : thread_kind == ThreadKind::kMain
213 ? GCTracer::Scope::MC_SWEEP
214 : GCTracer::Scope::MC_BACKGROUND_SWEEPING;
216 heap_->tracer(), scope_id, thread_kind,
219 const bool finished =
221 DCHECK_IMPLIES(delegate->IsJoiningThread(), finished);
222 if (!finished) return;
224 }
225 GCTracer::Scope::ScopeId scope_id =
227 ? thread_kind == ThreadKind::kMain
228 ? GCTracer::Scope::YOUNG_ARRAY_BUFFER_SWEEP
229 : GCTracer::Scope::BACKGROUND_YOUNG_ARRAY_BUFFER_SWEEP
230 : thread_kind == ThreadKind::kMain
231 ? GCTracer::Scope::FULL_ARRAY_BUFFER_SWEEP
232 : GCTracer::Scope::BACKGROUND_FULL_ARRAY_BUFFER_SWEEP;
233 TRACE_GC_EPOCH_WITH_FLOW(heap_->tracer(), scope_id, thread_kind, trace_id_,
235 Sweep(delegate);
236}
237
241 ArrayBufferSweeper::TreatAllYoungAsPromoted treat_all_young_as_promoted,
242 uint64_t trace_id)
243 : initial_young_bytes_(young.bytes_),
244 initial_old_bytes_(old.bytes_),
245 job_handle_(V8::GetCurrentPlatform()->CreateJob(
247 std::make_unique<SweepingJob>(
248 heap, *this, std::move(young), std::move(old), type,
249 treat_all_young_as_promoted, trace_id))) {}
250
252
258
260 if (!sweeping_in_progress()) return;
261
262 Finish();
263}
264
266 state_->FinishSweeping();
267
268 Finalize();
269 DCHECK_LE(heap_->backing_store_bytes(), SIZE_MAX);
271}
272
274 if (sweeping_in_progress()) {
275 DCHECK(state_);
276 if (state_->IsDone()) {
277 Finish();
278 }
279 }
280}
281
283 SweepingType type, TreatAllYoungAsPromoted treat_all_young_as_promoted) {
285
286 if (young_.IsEmpty() && (old_.IsEmpty() || type == SweepingType::kYoung))
287 return;
288
289 GCTracer::Scope::ScopeId scope_id =
291 ? v8_flags.minor_ms
292 ? GCTracer::Scope::MINOR_MS_FINISH_SWEEP_ARRAY_BUFFERS
293 : GCTracer::Scope::SCAVENGER_SWEEP_ARRAY_BUFFERS
294 : GCTracer::Scope::MC_FINISH_SWEEP_ARRAY_BUFFERS;
295 auto trace_id = GetTraceIdForFlowEvent(scope_id);
296 TRACE_GC_WITH_FLOW(heap_->tracer(), scope_id, trace_id,
298 Prepare(type, treat_all_young_as_promoted, trace_id);
299 DCHECK_IMPLIES(v8_flags.minor_ms && type == SweepingType::kYoung,
302 v8_flags.concurrent_array_buffer_sweeping &&
304 state_->StartBackgroundSweeping();
305 } else {
306 Finish();
307 }
308}
309
311 SweepingType type, TreatAllYoungAsPromoted treat_all_young_as_promoted,
312 uint64_t trace_id) {
315 treat_all_young_as_promoted == TreatAllYoungAsPromoted::kYes);
316 switch (type) {
318 state_ = std::make_unique<SweepingState>(
319 heap_, std::move(young_), ArrayBufferList(ArrayBufferList::Age::kOld),
320 type, treat_all_young_as_promoted, trace_id);
321 young_ = ArrayBufferList(ArrayBufferList::Age::kYoung);
322 } break;
323 case SweepingType::kFull: {
324 state_ = std::make_unique<SweepingState>(
325 heap_, std::move(young_), std::move(old_), type,
326 treat_all_young_as_promoted, trace_id);
327 young_ = ArrayBufferList(ArrayBufferList::Age::kYoung);
328 old_ = ArrayBufferList(ArrayBufferList::Age::kOld);
329 } break;
330 }
332}
333
336 CHECK(state_->IsDone());
337 state_->MergeTo(this);
338 state_.reset();
340}
341
343 ArrayBufferExtension* current = list->head_;
344 while (current) {
345 ArrayBufferExtension* next = current->next();
346 const size_t bytes = current->ClearAccountingLength().accounting_length();
348 FinalizeAndDelete(current);
349 current = next;
350 }
351 *list = ArrayBufferList(list->age_);
352}
353
355 size_t bytes = extension->accounting_length();
356
357 FinishIfDone();
358
359 switch (extension->age()) {
362 break;
365 break;
366 }
367
369}
370
372 int64_t delta) {
373 FinishIfDone();
374
375 const ArrayBufferExtension::AccountingState previous_value =
376 extension->UpdateAccountingLength(delta);
377 UpdateApproximateBytes(delta, previous_value.age());
378 if (delta > 0) {
380 } else {
382 }
383}
384
386 // Finish sweeping here first such that the code below is guaranteed to
387 // observe the same sweeping state.
388 FinishIfDone();
389
391 extension->ClearAccountingLength();
392
393 // We cannot free the extension eagerly here, since extensions are tracked in
394 // a singly linked list. The next GC will remove it automatically.
395
397 previous_value.age());
399}
400
403 switch (age) {
405 if (!sweeping_in_progress()) {
406 DCHECK_GE(young_.bytes_, -delta);
407 young_.bytes_ += delta;
408 } else {
410 }
411 break;
413 if (!sweeping_in_progress()) {
414 DCHECK_GE(old_.bytes_, -delta);
415 old_.bytes_ += delta;
416 } else {
418 }
419 }
420}
421
429
437
439#ifdef V8_COMPRESS_POINTERS
440 extension->ZapExternalPointerTableEntry();
441#endif // V8_COMPRESS_POINTERS
442 delete extension;
443}
444
446 JobDelegate* delegate) {
447 CHECK(!state_.IsDone());
448 bool is_finished;
449 switch (type_) {
451 is_finished = SweepYoung(delegate);
452 break;
454 is_finished = SweepFull(delegate);
455 break;
456 }
457 if (is_finished) {
458 state_.SetDone();
459 } else {
460 TRACE_GC_NOTE("ArrayBufferSweeper Preempted");
461 }
462}
463
465 JobDelegate* delegate) {
467 if (!SweepListFull(delegate, young_, ArrayBufferExtension::Age::kYoung))
468 return false;
469 return SweepListFull(delegate, old_, ArrayBufferExtension::Age::kOld);
470}
471
473 JobDelegate* delegate, ArrayBufferList& list,
475 static constexpr size_t kYieldCheckInterval = 256;
476 static_assert(base::bits::IsPowerOfTwo(kYieldCheckInterval),
477 "kYieldCheckInterval must be power of 2");
478
479 ArrayBufferExtension* current = list.head_;
480
481 ArrayBufferList& new_old = state_.new_old_;
482 size_t freed_bytes = 0;
483 size_t accounted_bytes = 0;
484 size_t swept_extensions = 0;
485
486 while (current) {
487 DCHECK_EQ(list.age_, current->age());
488 if ((swept_extensions++ & (kYieldCheckInterval - 1)) == 0) {
489 if (delegate->ShouldYield()) break;
490 }
491 ArrayBufferExtension* next = current->next();
492
493 if (!current->IsMarked()) {
494 freed_bytes += current->accounting_length();
495 FinalizeAndDelete(current);
496 } else {
497 current->Unmark();
498 accounted_bytes += new_old.Append(current);
499 }
500
501 current = next;
502 }
503
504 state_.freed_bytes_ += freed_bytes;
506 state_.young_bytes_accounted_ += (freed_bytes + accounted_bytes);
507 } else {
508 state_.old_bytes_accounted_ += (freed_bytes + accounted_bytes);
509 }
510
511 list.head_ = current;
512 return !current;
513}
514
516 JobDelegate* delegate) {
517 static constexpr size_t kYieldCheckInterval = 256;
518 static_assert(base::bits::IsPowerOfTwo(kYieldCheckInterval),
519 "kYieldCheckInterval must be power of 2");
520
523
524 ArrayBufferList& new_old = state_.new_old_;
525 ArrayBufferList& new_young = state_.new_young_;
526 size_t freed_bytes = 0;
527 size_t accounted_bytes = 0;
528 size_t swept_extensions = 0;
529
530 while (current) {
532 if ((swept_extensions++ & (kYieldCheckInterval - 1)) == 0) {
533 if (delegate->ShouldYield()) break;
534 }
535 ArrayBufferExtension* next = current->next();
536
537 if (!current->IsYoungMarked()) {
538 const size_t bytes = current->accounting_length();
539 FinalizeAndDelete(current);
540 if (bytes) freed_bytes += bytes;
541 } else {
542 if ((treat_all_young_as_promoted_ == TreatAllYoungAsPromoted::kYes) ||
543 current->IsYoungPromoted()) {
544 current->YoungUnmark();
545 accounted_bytes += new_old.Append(current);
546 } else {
547 current->YoungUnmark();
548 accounted_bytes += new_young.Append(current);
549 }
550 }
551
552 current = next;
553 }
554
555 state_.freed_bytes_ += freed_bytes;
556 // Update young/old_bytes_accounted_; the worker may see a difference between
557 // this and `initial_young/old_bytes_` due to concurrent main thread
558 // adjustments.
559 state_.young_bytes_accounted_ += (freed_bytes + accounted_bytes);
560
562 return !current;
563}
564
566 GCTracer::Scope::ScopeId scope_id) const {
567 return reinterpret_cast<uint64_t>(this) ^
568 heap_->tracer()->CurrentEpoch(scope_id);
569}
570
571} // namespace internal
572} // namespace v8
void Decrease(Isolate *isolate, size_t size)
Definition api.cc:12391
void Increase(Isolate *isolate, size_t size)
Definition api.cc:12371
virtual bool ShouldYield()=0
virtual bool IsJoiningThread() const =0
ArrayBufferExtension * next() const
void set_next(ArrayBufferExtension *extension)
SweepingJob & operator=(const SweepingJob &)=delete
bool SweepListFull(JobDelegate *delegate, ArrayBufferList &list, ArrayBufferExtension::Age age)
size_t GetMaxConcurrency(size_t worker_count) const override
SweepingJob(Heap *heap, SweepingState &state, ArrayBufferList young, ArrayBufferList old, SweepingType type, TreatAllYoungAsPromoted treat_all_young_as_promoted, uint64_t trace_id)
SweepingState(Heap *heap, ArrayBufferList young, ArrayBufferList old, SweepingType type, TreatAllYoungAsPromoted treat_all_young_as_promoted, uint64_t trace_id)
static void FinalizeAndDelete(ArrayBufferExtension *extension)
V8_NO_UNIQUE_ADDRESS ExternalMemoryAccounter external_memory_accounter_
std::unique_ptr< SweepingState > state_
void Detach(ArrayBufferExtension *extension)
void RequestSweep(SweepingType sweeping_type, TreatAllYoungAsPromoted treat_all_young_as_promoted)
void Resize(ArrayBufferExtension *extension, int64_t delta)
void UpdateApproximateBytes(int64_t delta, ArrayBufferExtension::Age age)
void Append(ArrayBufferExtension *extension)
uint64_t GetTraceIdForFlowEvent(GCTracer::Scope::ScopeId scope_id) const
void Prepare(SweepingType type, TreatAllYoungAsPromoted treat_all_young_as_promoted, uint64_t trace_id)
void ReleaseAll(ArrayBufferList *extension)
V8_INLINE CollectionEpoch CurrentEpoch(Scope::ScopeId id) const
bool IsTearingDown() const
Definition heap.h:525
void IncrementExternalBackingStoreBytes(ExternalBackingStoreType type, size_t amount)
Definition heap-inl.h:410
Sweeper * sweeper()
Definition heap.h:821
void DecrementExternalBackingStoreBytes(ExternalBackingStoreType type, size_t amount)
Definition heap-inl.h:418
uint64_t backing_store_bytes() const
Definition heap.h:625
bool ShouldUseBackgroundThreads() const
Definition heap.cc:456
GCTracer * tracer()
Definition heap.h:800
Isolate * isolate() const
Definition heap-inl.h:61
bool ShouldReduceMemory() const
Definition heap.h:1615
bool ContributeAndWaitForPromotedPagesIteration(JobDelegate *delegate)
Definition sweeper.cc:405
uint64_t GetTraceIdForFlowEvent(GCTracer::Scope::ScopeId scope_id) const
Definition sweeper.cc:1541
bool IsIteratingPromotedPages() const
Definition sweeper.cc:1179
const ObjectRef type_
LineAndColumn current
#define TRACE_GC_EPOCH_WITH_FLOW(tracer, scope_id, thread_kind, bind_id, flow_flags)
Definition gc-tracer.h:84
#define TRACE_GC_WITH_FLOW(tracer, scope_id, bind_id, flow_flags)
Definition gc-tracer.h:52
#define TRACE_GC_NOTE(note)
Definition gc-tracer.h:93
std::string extension
STL namespace.
constexpr bool IsPowerOfTwo(T value)
Definition bits.h:187
V8_EXPORT_PRIVATE FlagValues v8_flags
TaskPriority
Definition v8-platform.h:24
#define DCHECK_LE(v1, v2)
Definition logging.h:490
#define DCHECK_NULL(val)
Definition logging.h:491
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define DCHECK_IMPLIES(v1, v2)
Definition logging.h:493
#define DCHECK_GE(v1, v2)
Definition logging.h:488
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
ArrayBufferExtension::Age age_
V8_EXPORT_PRIVATE bool ContainsSlow(ArrayBufferExtension *extension) const
size_t Append(ArrayBufferExtension *extension)
#define TRACE_EVENT_FLAG_FLOW_OUT
#define TRACE_EVENT_FLAG_FLOW_IN