1// Copyright 2018 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/execution/microtask-queue.h" 6 7#include <algorithm> 8#include <cstddef> 9 10#include "src/api/api-inl.h" 11#include "src/base/logging.h" 12#include "src/execution/isolate.h" 13#include "src/handles/handles-inl.h" 14#include "src/objects/microtask-inl.h" 15#include "src/objects/visitors.h" 16#include "src/roots/roots-inl.h" 17#include "src/tracing/trace-event.h" 18 19namespace v8 { 20namespace internal { 21 22const size_t MicrotaskQueue::kRingBufferOffset = 23 OFFSET_OF(MicrotaskQueue, ring_buffer_); 24const size_t MicrotaskQueue::kCapacityOffset = 25 OFFSET_OF(MicrotaskQueue, capacity_); 26const size_t MicrotaskQueue::kSizeOffset = OFFSET_OF(MicrotaskQueue, size_); 27const size_t MicrotaskQueue::kStartOffset = OFFSET_OF(MicrotaskQueue, start_); 28const size_t MicrotaskQueue::kFinishedMicrotaskCountOffset = 29 OFFSET_OF(MicrotaskQueue, finished_microtask_count_); 30 31const intptr_t MicrotaskQueue::kMinimumCapacity = 8; 32 33// static 34void MicrotaskQueue::SetUpDefaultMicrotaskQueue(Isolate* isolate) { 35 DCHECK_NULL(isolate->default_microtask_queue()); 36 37 MicrotaskQueue* microtask_queue = new MicrotaskQueue; 38 microtask_queue->next_ = microtask_queue; 39 microtask_queue->prev_ = microtask_queue; 40 isolate->set_default_microtask_queue(microtask_queue); 41} 42 43// static 44std::unique_ptr<MicrotaskQueue> MicrotaskQueue::New(Isolate* isolate) { 45 DCHECK_NOT_NULL(isolate->default_microtask_queue()); 46 47 std::unique_ptr<MicrotaskQueue> microtask_queue(new MicrotaskQueue); 48 49 // Insert the new instance to the next of last MicrotaskQueue instance. 50 MicrotaskQueue* last = isolate->default_microtask_queue()->prev_; 51 microtask_queue->next_ = last->next_; 52 microtask_queue->prev_ = last; 53 last->next_->prev_ = microtask_queue.get(); 54 last->next_ = microtask_queue.get(); 55 56 return microtask_queue; 57} 58 59MicrotaskQueue::MicrotaskQueue() = default; 60 61MicrotaskQueue::~MicrotaskQueue() { 62 if (next_ != this) { 63 DCHECK_NE(prev_, this); 64 next_->prev_ = prev_; 65 prev_->next_ = next_; 66 } 67 delete[] ring_buffer_; 68} 69 70// static 71Address MicrotaskQueue::CallEnqueueMicrotask(Isolate* isolate, 72 intptr_t microtask_queue_pointer, 73 Address raw_microtask) { 74 Microtask microtask = Microtask::cast(Object(raw_microtask)); 75 reinterpret_cast<MicrotaskQueue*>(microtask_queue_pointer) 76 ->EnqueueMicrotask(microtask); 77 return Smi::zero().ptr(); 78} 79 80void MicrotaskQueue::EnqueueMicrotask(v8::Isolate* v8_isolate, 81 v8::Local<Function> function) { 82 Isolate* isolate = reinterpret_cast<Isolate*>(v8_isolate); 83 HandleScope scope(isolate); 84 Handle<CallableTask> microtask = isolate->factory()->NewCallableTask( 85 Utils::OpenHandle(*function), isolate->native_context()); 86 EnqueueMicrotask(*microtask); 87} 88 89void MicrotaskQueue::EnqueueMicrotask(v8::Isolate* v8_isolate, 90 v8::MicrotaskCallback callback, 91 void* data) { 92 Isolate* isolate = reinterpret_cast<Isolate*>(v8_isolate); 93 HandleScope scope(isolate); 94 Handle<CallbackTask> microtask = isolate->factory()->NewCallbackTask( 95 isolate->factory()->NewForeign(reinterpret_cast<Address>(callback)), 96 isolate->factory()->NewForeign(reinterpret_cast<Address>(data))); 97 EnqueueMicrotask(*microtask); 98} 99 100void MicrotaskQueue::EnqueueMicrotask(Microtask microtask) { 101 if (size_ == capacity_) { 102 // Keep the capacity of |ring_buffer_| power of 2, so that the JIT 103 // implementation can calculate the modulo easily. 104 intptr_t new_capacity = std::max(kMinimumCapacity, capacity_ << 1); 105 ResizeBuffer(new_capacity); 106 } 107 108 DCHECK_LT(size_, capacity_); 109 ring_buffer_[(start_ + size_) % capacity_] = microtask.ptr(); 110 ++size_; 111} 112 113void MicrotaskQueue::PerformCheckpointInternal(v8::Isolate* v8_isolate) { 114 DCHECK(ShouldPerfomCheckpoint()); 115 std::unique_ptr<MicrotasksScope> microtasks_scope; 116 if (microtasks_policy_ == v8::MicrotasksPolicy::kScoped) { 117 // If we're using microtask scopes to schedule microtask execution, V8 118 // API calls will check that there's always a microtask scope on the 119 // stack. As the microtasks we're about to execute could invoke embedder 120 // callbacks which then calls back into V8, we create an artificial 121 // microtask scope here to avoid running into the CallDepthScope check. 122 microtasks_scope.reset(new v8::MicrotasksScope( 123 v8_isolate, this, v8::MicrotasksScope::kDoNotRunMicrotasks)); 124 } 125 Isolate* isolate = reinterpret_cast<Isolate*>(v8_isolate); 126 RunMicrotasks(isolate); 127 isolate->ClearKeptObjects(); 128} 129 130namespace { 131 132class SetIsRunningMicrotasks { 133 public: 134 explicit SetIsRunningMicrotasks(bool* flag) : flag_(flag) { 135 DCHECK(!*flag_); 136 *flag_ = true; 137 } 138 139 ~SetIsRunningMicrotasks() { 140 DCHECK(*flag_); 141 *flag_ = false; 142 } 143 144 private: 145 bool* flag_; 146}; 147 148} // namespace 149 150int MicrotaskQueue::RunMicrotasks(Isolate* isolate) { 151 if (!size()) { 152 OnCompleted(isolate); 153 return 0; 154 } 155 156 intptr_t base_count = finished_microtask_count_; 157 158 HandleScope handle_scope(isolate); 159 MaybeHandle<Object> maybe_exception; 160 161 MaybeHandle<Object> maybe_result; 162 163 int processed_microtask_count; 164 { 165 SetIsRunningMicrotasks scope(&is_running_microtasks_); 166 v8::Isolate::SuppressMicrotaskExecutionScope suppress( 167 reinterpret_cast<v8::Isolate*>(isolate)); 168 HandleScopeImplementer::EnteredContextRewindScope rewind_scope( 169 isolate->handle_scope_implementer()); 170 TRACE_EVENT_BEGIN0("v8.execute", "RunMicrotasks"); 171 { 172 TRACE_EVENT_CALL_STATS_SCOPED(isolate, "v8", "V8.RunMicrotasks"); 173 maybe_result = Execution::TryRunMicrotasks(isolate, this, 174 &maybe_exception); 175 processed_microtask_count = 176 static_cast<int>(finished_microtask_count_ - base_count); 177 } 178 TRACE_EVENT_END1("v8.execute", "RunMicrotasks", "microtask_count", 179 processed_microtask_count); 180 } 181 182 // If execution is terminating, clean up and propagate that to TryCatch scope. 183 if (maybe_result.is_null() && maybe_exception.is_null()) { 184 delete[] ring_buffer_; 185 ring_buffer_ = nullptr; 186 capacity_ = 0; 187 size_ = 0; 188 start_ = 0; 189 DCHECK(isolate->has_scheduled_exception()); 190 isolate->OnTerminationDuringRunMicrotasks(); 191 OnCompleted(isolate); 192 return -1; 193 } 194 DCHECK_EQ(0, size()); 195 OnCompleted(isolate); 196 197 return processed_microtask_count; 198} 199 200void MicrotaskQueue::IterateMicrotasks(RootVisitor* visitor) { 201 if (size_) { 202 // Iterate pending Microtasks as root objects to avoid the write barrier for 203 // all single Microtask. If this hurts the GC performance, use a FixedArray. 204 visitor->VisitRootPointers( 205 Root::kStrongRoots, nullptr, FullObjectSlot(ring_buffer_ + start_), 206 FullObjectSlot(ring_buffer_ + std::min(start_ + size_, capacity_))); 207 visitor->VisitRootPointers( 208 Root::kStrongRoots, nullptr, FullObjectSlot(ring_buffer_), 209 FullObjectSlot(ring_buffer_ + std::max(start_ + size_ - capacity_, 210 static_cast<intptr_t>(0)))); 211 } 212 213 if (capacity_ <= kMinimumCapacity) { 214 return; 215 } 216 217 intptr_t new_capacity = capacity_; 218 while (new_capacity > 2 * size_) { 219 new_capacity >>= 1; 220 } 221 new_capacity = std::max(new_capacity, kMinimumCapacity); 222 if (new_capacity < capacity_) { 223 ResizeBuffer(new_capacity); 224 } 225} 226 227void MicrotaskQueue::AddMicrotasksCompletedCallback( 228 MicrotasksCompletedCallbackWithData callback, void* data) { 229 CallbackWithData callback_with_data(callback, data); 230 auto pos = 231 std::find(microtasks_completed_callbacks_.begin(), 232 microtasks_completed_callbacks_.end(), callback_with_data); 233 if (pos != microtasks_completed_callbacks_.end()) return; 234 microtasks_completed_callbacks_.push_back(callback_with_data); 235} 236 237void MicrotaskQueue::RemoveMicrotasksCompletedCallback( 238 MicrotasksCompletedCallbackWithData callback, void* data) { 239 CallbackWithData callback_with_data(callback, data); 240 auto pos = 241 std::find(microtasks_completed_callbacks_.begin(), 242 microtasks_completed_callbacks_.end(), callback_with_data); 243 if (pos == microtasks_completed_callbacks_.end()) return; 244 microtasks_completed_callbacks_.erase(pos); 245} 246 247void MicrotaskQueue::OnCompleted(Isolate* isolate) const { 248 std::vector<CallbackWithData> callbacks(microtasks_completed_callbacks_); 249 for (auto& callback : callbacks) { 250 callback.first(reinterpret_cast<v8::Isolate*>(isolate), callback.second); 251 } 252} 253 254Microtask MicrotaskQueue::get(intptr_t index) const { 255 DCHECK_LT(index, size_); 256 Object microtask(ring_buffer_[(index + start_) % capacity_]); 257 return Microtask::cast(microtask); 258} 259 260void MicrotaskQueue::ResizeBuffer(intptr_t new_capacity) { 261 DCHECK_LE(size_, new_capacity); 262 Address* new_ring_buffer = new Address[new_capacity]; 263 for (intptr_t i = 0; i < size_; ++i) { 264 new_ring_buffer[i] = ring_buffer_[(start_ + i) % capacity_]; 265 } 266 267 delete[] ring_buffer_; 268 ring_buffer_ = new_ring_buffer; 269 capacity_ = new_capacity; 270 start_ = 0; 271} 272 273} // namespace internal 274} // namespace v8 275