| // Copyright 2018 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/microtask-queue.h" |
| |
| #include <stddef.h> |
| #include <algorithm> |
| |
| #include "src/base/logging.h" |
| #include "src/handles-inl.h" |
| #include "src/isolate.h" |
| #include "src/objects/microtask.h" |
| #include "src/roots-inl.h" |
| #include "src/visitors.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| const size_t MicrotaskQueue::kRingBufferOffset = |
| offsetof(MicrotaskQueue, ring_buffer_); |
| const size_t MicrotaskQueue::kCapacityOffset = |
| offsetof(MicrotaskQueue, capacity_); |
| const size_t MicrotaskQueue::kSizeOffset = offsetof(MicrotaskQueue, size_); |
| const size_t MicrotaskQueue::kStartOffset = offsetof(MicrotaskQueue, start_); |
| |
| const intptr_t MicrotaskQueue::kMinimumCapacity = 8; |
| |
| // static |
| void MicrotaskQueue::SetUpDefaultMicrotaskQueue(Isolate* isolate) { |
| DCHECK_NULL(isolate->default_microtask_queue()); |
| |
| MicrotaskQueue* microtask_queue = new MicrotaskQueue; |
| microtask_queue->next_ = microtask_queue; |
| microtask_queue->prev_ = microtask_queue; |
| isolate->set_default_microtask_queue(microtask_queue); |
| } |
| |
| // static |
| std::unique_ptr<MicrotaskQueue> MicrotaskQueue::New(Isolate* isolate) { |
| DCHECK_NOT_NULL(isolate->default_microtask_queue()); |
| |
| std::unique_ptr<MicrotaskQueue> microtask_queue(new MicrotaskQueue); |
| |
| // Insert the new instance to the next of last MicrotaskQueue instance. |
| MicrotaskQueue* last = isolate->default_microtask_queue()->prev_; |
| microtask_queue->next_ = last->next_; |
| microtask_queue->prev_ = last; |
| last->next_->prev_ = microtask_queue.get(); |
| last->next_ = microtask_queue.get(); |
| |
| return microtask_queue; |
| } |
| |
| MicrotaskQueue::MicrotaskQueue() = default; |
| |
| MicrotaskQueue::~MicrotaskQueue() { |
| if (next_ != this) { |
| DCHECK_NE(prev_, this); |
| next_->prev_ = prev_; |
| prev_->next_ = next_; |
| } |
| delete[] ring_buffer_; |
| } |
| |
| // static |
| Object* MicrotaskQueue::CallEnqueueMicrotask(Isolate* isolate, |
| intptr_t microtask_queue_pointer, |
| Microtask microtask) { |
| reinterpret_cast<MicrotaskQueue*>(microtask_queue_pointer) |
| ->EnqueueMicrotask(microtask); |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| void MicrotaskQueue::EnqueueMicrotask(Microtask microtask) { |
| if (size_ == capacity_) { |
| // Keep the capacity of |ring_buffer_| power of 2, so that the JIT |
| // implementation can calculate the modulo easily. |
| intptr_t new_capacity = std::max(kMinimumCapacity, capacity_ << 1); |
| ResizeBuffer(new_capacity); |
| } |
| |
| DCHECK_LT(size_, capacity_); |
| ring_buffer_[(start_ + size_) % capacity_] = microtask.ptr(); |
| ++size_; |
| } |
| |
| int MicrotaskQueue::RunMicrotasks(Isolate* isolate) { |
| HandleScope scope(isolate); |
| MaybeHandle<Object> maybe_exception; |
| |
| // TODO(tzik): Execution::RunMicrotasks() runs default_microtask_queue. |
| // Give it as a parameter to support non-default MicrotaskQueue. |
| DCHECK_EQ(this, isolate->default_microtask_queue()); |
| MaybeHandle<Object> maybe_result = Execution::RunMicrotasks( |
| isolate, Execution::MessageHandling::kReport, &maybe_exception); |
| |
| // If execution is terminating, clean up and propagate that to the caller. |
| if (maybe_result.is_null() && maybe_exception.is_null()) { |
| delete[] ring_buffer_; |
| ring_buffer_ = nullptr; |
| capacity_ = 0; |
| size_ = 0; |
| start_ = 0; |
| return -1; |
| } |
| |
| // TODO(tzik): Return the number of microtasks run in this round. |
| return 0; |
| } |
| |
| void MicrotaskQueue::IterateMicrotasks(RootVisitor* visitor) { |
| if (size_) { |
| // Iterate pending Microtasks as root objects to avoid the write barrier for |
| // all single Microtask. If this hurts the GC performance, use a FixedArray. |
| visitor->VisitRootPointers( |
| Root::kStrongRoots, nullptr, FullObjectSlot(ring_buffer_ + start_), |
| FullObjectSlot(ring_buffer_ + std::min(start_ + size_, capacity_))); |
| visitor->VisitRootPointers( |
| Root::kStrongRoots, nullptr, FullObjectSlot(ring_buffer_), |
| FullObjectSlot(ring_buffer_ + std::max(start_ + size_ - capacity_, |
| static_cast<intptr_t>(0)))); |
| } |
| |
| if (capacity_ <= kMinimumCapacity) { |
| return; |
| } |
| |
| intptr_t new_capacity = capacity_; |
| while (new_capacity > 2 * size_) { |
| new_capacity >>= 1; |
| } |
| new_capacity = std::max(new_capacity, kMinimumCapacity); |
| if (new_capacity < capacity_) { |
| ResizeBuffer(new_capacity); |
| } |
| } |
| |
| void MicrotaskQueue::ResizeBuffer(intptr_t new_capacity) { |
| DCHECK_LE(size_, new_capacity); |
| Address* new_ring_buffer = new Address[new_capacity]; |
| for (intptr_t i = 0; i < size_; ++i) { |
| new_ring_buffer[i] = ring_buffer_[(start_ + i) % capacity_]; |
| } |
| |
| delete[] ring_buffer_; |
| ring_buffer_ = new_ring_buffer; |
| capacity_ = new_capacity; |
| start_ = 0; |
| } |
| |
| } // namespace internal |
| } // namespace v8 |