| // 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/api/api.h" |
| #include "src/builtins/builtins-utils-gen.h" |
| #include "src/codegen/code-stub-assembler.h" |
| #include "src/execution/microtask-queue.h" |
| #include "src/objects/js-weak-refs.h" |
| #include "src/objects/microtask-inl.h" |
| #include "src/objects/promise.h" |
| #include "src/objects/smi-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| using compiler::ScopedExceptionHandler; |
| |
| class MicrotaskQueueBuiltinsAssembler : public CodeStubAssembler { |
| public: |
| explicit MicrotaskQueueBuiltinsAssembler(compiler::CodeAssemblerState* state) |
| : CodeStubAssembler(state) {} |
| |
| TNode<RawPtrT> GetMicrotaskQueue(TNode<Context> context); |
| TNode<RawPtrT> GetMicrotaskRingBuffer(TNode<RawPtrT> microtask_queue); |
| TNode<IntPtrT> GetMicrotaskQueueCapacity(TNode<RawPtrT> microtask_queue); |
| TNode<IntPtrT> GetMicrotaskQueueSize(TNode<RawPtrT> microtask_queue); |
| void SetMicrotaskQueueSize(TNode<RawPtrT> microtask_queue, |
| TNode<IntPtrT> new_size); |
| TNode<IntPtrT> GetMicrotaskQueueStart(TNode<RawPtrT> microtask_queue); |
| void SetMicrotaskQueueStart(TNode<RawPtrT> microtask_queue, |
| TNode<IntPtrT> new_start); |
| TNode<IntPtrT> CalculateRingBufferOffset(TNode<IntPtrT> capacity, |
| TNode<IntPtrT> start, |
| TNode<IntPtrT> index); |
| |
| void PrepareForContext(TNode<Context> microtask_context, Label* bailout); |
| void RunSingleMicrotask(TNode<Context> current_context, |
| TNode<Microtask> microtask); |
| void IncrementFinishedMicrotaskCount(TNode<RawPtrT> microtask_queue); |
| |
| TNode<Context> GetCurrentContext(); |
| void SetCurrentContext(TNode<Context> context); |
| |
| TNode<IntPtrT> GetEnteredContextCount(); |
| void EnterMicrotaskContext(TNode<Context> native_context); |
| void RewindEnteredContext(TNode<IntPtrT> saved_entered_context_count); |
| |
| void RunPromiseHook(Runtime::FunctionId id, TNode<Context> context, |
| TNode<HeapObject> promise_or_capability); |
| }; |
| |
| TNode<RawPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueue( |
| TNode<Context> native_context) { |
| CSA_ASSERT(this, IsNativeContext(native_context)); |
| return LoadExternalPointerFromObject(native_context, |
| NativeContext::kMicrotaskQueueOffset, |
| kNativeContextMicrotaskQueueTag); |
| } |
| |
| TNode<RawPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskRingBuffer( |
| TNode<RawPtrT> microtask_queue) { |
| return Load<RawPtrT>(microtask_queue, |
| IntPtrConstant(MicrotaskQueue::kRingBufferOffset)); |
| } |
| |
| TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueCapacity( |
| TNode<RawPtrT> microtask_queue) { |
| return Load<IntPtrT>(microtask_queue, |
| IntPtrConstant(MicrotaskQueue::kCapacityOffset)); |
| } |
| |
| TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueSize( |
| TNode<RawPtrT> microtask_queue) { |
| return Load<IntPtrT>(microtask_queue, |
| IntPtrConstant(MicrotaskQueue::kSizeOffset)); |
| } |
| |
| void MicrotaskQueueBuiltinsAssembler::SetMicrotaskQueueSize( |
| TNode<RawPtrT> microtask_queue, TNode<IntPtrT> new_size) { |
| StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue, |
| IntPtrConstant(MicrotaskQueue::kSizeOffset), new_size); |
| } |
| |
| TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueStart( |
| TNode<RawPtrT> microtask_queue) { |
| return Load<IntPtrT>(microtask_queue, |
| IntPtrConstant(MicrotaskQueue::kStartOffset)); |
| } |
| |
| void MicrotaskQueueBuiltinsAssembler::SetMicrotaskQueueStart( |
| TNode<RawPtrT> microtask_queue, TNode<IntPtrT> new_start) { |
| StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue, |
| IntPtrConstant(MicrotaskQueue::kStartOffset), new_start); |
| } |
| |
| TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::CalculateRingBufferOffset( |
| TNode<IntPtrT> capacity, TNode<IntPtrT> start, TNode<IntPtrT> index) { |
| return TimesSystemPointerSize( |
| WordAnd(IntPtrAdd(start, index), IntPtrSub(capacity, IntPtrConstant(1)))); |
| } |
| |
| void MicrotaskQueueBuiltinsAssembler::PrepareForContext( |
| TNode<Context> native_context, Label* bailout) { |
| CSA_ASSERT(this, IsNativeContext(native_context)); |
| |
| // Skip the microtask execution if the associated context is shutdown. |
| GotoIf(WordEqual(GetMicrotaskQueue(native_context), IntPtrConstant(0)), |
| bailout); |
| |
| EnterMicrotaskContext(native_context); |
| SetCurrentContext(native_context); |
| } |
| |
| void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( |
| TNode<Context> current_context, TNode<Microtask> microtask) { |
| CSA_ASSERT(this, TaggedIsNotSmi(microtask)); |
| |
| StoreRoot(RootIndex::kCurrentMicrotask, microtask); |
| TNode<IntPtrT> saved_entered_context_count = GetEnteredContextCount(); |
| TNode<Map> microtask_map = LoadMap(microtask); |
| TNode<Uint16T> microtask_type = LoadMapInstanceType(microtask_map); |
| |
| TVARIABLE(Object, var_exception); |
| Label if_exception(this, Label::kDeferred); |
| Label is_callable(this), is_callback(this), |
| is_promise_fulfill_reaction_job(this), |
| is_promise_reject_reaction_job(this), |
| is_promise_resolve_thenable_job(this), |
| is_unreachable(this, Label::kDeferred), done(this); |
| |
| int32_t case_values[] = {CALLABLE_TASK_TYPE, CALLBACK_TASK_TYPE, |
| PROMISE_FULFILL_REACTION_JOB_TASK_TYPE, |
| PROMISE_REJECT_REACTION_JOB_TASK_TYPE, |
| PROMISE_RESOLVE_THENABLE_JOB_TASK_TYPE}; |
| Label* case_labels[] = { |
| &is_callable, &is_callback, &is_promise_fulfill_reaction_job, |
| &is_promise_reject_reaction_job, &is_promise_resolve_thenable_job}; |
| static_assert(arraysize(case_values) == arraysize(case_labels), ""); |
| Switch(microtask_type, &is_unreachable, case_values, case_labels, |
| arraysize(case_labels)); |
| |
| BIND(&is_callable); |
| { |
| // Enter the context of the {microtask}. |
| TNode<Context> microtask_context = |
| LoadObjectField<Context>(microtask, CallableTask::kContextOffset); |
| TNode<NativeContext> native_context = LoadNativeContext(microtask_context); |
| PrepareForContext(native_context, &done); |
| |
| TNode<JSReceiver> callable = |
| LoadObjectField<JSReceiver>(microtask, CallableTask::kCallableOffset); |
| { |
| ScopedExceptionHandler handler(this, &if_exception, &var_exception); |
| Call(microtask_context, callable, UndefinedConstant()); |
| } |
| RewindEnteredContext(saved_entered_context_count); |
| SetCurrentContext(current_context); |
| Goto(&done); |
| } |
| |
| BIND(&is_callback); |
| { |
| const TNode<Object> microtask_callback = |
| LoadObjectField(microtask, CallbackTask::kCallbackOffset); |
| const TNode<Object> microtask_data = |
| LoadObjectField(microtask, CallbackTask::kDataOffset); |
| |
| // If this turns out to become a bottleneck because of the calls |
| // to C++ via CEntry, we can choose to speed them up using a |
| // similar mechanism that we use for the CallApiFunction stub, |
| // except that calling the MicrotaskCallback is even easier, since |
| // it doesn't accept any tagged parameters, doesn't return a value |
| // and ignores exceptions. |
| // |
| // But from our current measurements it doesn't seem to be a |
| // serious performance problem, even if the microtask is full |
| // of CallHandlerTasks (which is not a realistic use case anyways). |
| { |
| ScopedExceptionHandler handler(this, &if_exception, &var_exception); |
| CallRuntime(Runtime::kRunMicrotaskCallback, current_context, |
| microtask_callback, microtask_data); |
| } |
| Goto(&done); |
| } |
| |
| BIND(&is_promise_resolve_thenable_job); |
| { |
| // Enter the context of the {microtask}. |
| TNode<Context> microtask_context = LoadObjectField<Context>( |
| microtask, PromiseResolveThenableJobTask::kContextOffset); |
| TNode<NativeContext> native_context = LoadNativeContext(microtask_context); |
| PrepareForContext(native_context, &done); |
| |
| const TNode<Object> promise_to_resolve = LoadObjectField( |
| microtask, PromiseResolveThenableJobTask::kPromiseToResolveOffset); |
| const TNode<Object> then = |
| LoadObjectField(microtask, PromiseResolveThenableJobTask::kThenOffset); |
| const TNode<Object> thenable = LoadObjectField( |
| microtask, PromiseResolveThenableJobTask::kThenableOffset); |
| |
| RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, |
| CAST(promise_to_resolve)); |
| |
| { |
| ScopedExceptionHandler handler(this, &if_exception, &var_exception); |
| CallBuiltin(Builtins::kPromiseResolveThenableJob, native_context, |
| promise_to_resolve, thenable, then); |
| } |
| |
| RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, |
| CAST(promise_to_resolve)); |
| |
| RewindEnteredContext(saved_entered_context_count); |
| SetCurrentContext(current_context); |
| Goto(&done); |
| } |
| |
| BIND(&is_promise_fulfill_reaction_job); |
| { |
| // Enter the context of the {microtask}. |
| TNode<Context> microtask_context = LoadObjectField<Context>( |
| microtask, PromiseReactionJobTask::kContextOffset); |
| TNode<NativeContext> native_context = LoadNativeContext(microtask_context); |
| PrepareForContext(native_context, &done); |
| |
| const TNode<Object> argument = |
| LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset); |
| const TNode<Object> job_handler = |
| LoadObjectField(microtask, PromiseReactionJobTask::kHandlerOffset); |
| const TNode<HeapObject> promise_or_capability = CAST(LoadObjectField( |
| microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset)); |
| |
| TNode<Object> preserved_embedder_data = LoadObjectField( |
| microtask, |
| PromiseReactionJobTask::kContinuationPreservedEmbedderDataOffset); |
| Label preserved_data_done(this); |
| GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_done); |
| StoreContextElement(native_context, |
| Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX, |
| preserved_embedder_data); |
| Goto(&preserved_data_done); |
| BIND(&preserved_data_done); |
| |
| // Run the promise before/debug hook if enabled. |
| RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, |
| promise_or_capability); |
| |
| { |
| ScopedExceptionHandler handler(this, &if_exception, &var_exception); |
| CallBuiltin(Builtins::kPromiseFulfillReactionJob, microtask_context, |
| argument, job_handler, promise_or_capability); |
| } |
| |
| // Run the promise after/debug hook if enabled. |
| RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, |
| promise_or_capability); |
| |
| Label preserved_data_reset_done(this); |
| GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done); |
| StoreContextElement(native_context, |
| Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX, |
| UndefinedConstant()); |
| Goto(&preserved_data_reset_done); |
| BIND(&preserved_data_reset_done); |
| |
| RewindEnteredContext(saved_entered_context_count); |
| SetCurrentContext(current_context); |
| Goto(&done); |
| } |
| |
| BIND(&is_promise_reject_reaction_job); |
| { |
| // Enter the context of the {microtask}. |
| TNode<Context> microtask_context = LoadObjectField<Context>( |
| microtask, PromiseReactionJobTask::kContextOffset); |
| TNode<NativeContext> native_context = LoadNativeContext(microtask_context); |
| PrepareForContext(native_context, &done); |
| |
| const TNode<Object> argument = |
| LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset); |
| const TNode<Object> job_handler = |
| LoadObjectField(microtask, PromiseReactionJobTask::kHandlerOffset); |
| const TNode<HeapObject> promise_or_capability = CAST(LoadObjectField( |
| microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset)); |
| |
| TNode<Object> preserved_embedder_data = LoadObjectField( |
| microtask, |
| PromiseReactionJobTask::kContinuationPreservedEmbedderDataOffset); |
| Label preserved_data_done(this); |
| GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_done); |
| StoreContextElement(native_context, |
| Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX, |
| preserved_embedder_data); |
| Goto(&preserved_data_done); |
| BIND(&preserved_data_done); |
| |
| // Run the promise before/debug hook if enabled. |
| RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, |
| promise_or_capability); |
| |
| { |
| ScopedExceptionHandler handler(this, &if_exception, &var_exception); |
| CallBuiltin(Builtins::kPromiseRejectReactionJob, microtask_context, |
| argument, job_handler, promise_or_capability); |
| } |
| |
| // Run the promise after/debug hook if enabled. |
| RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, |
| promise_or_capability); |
| |
| Label preserved_data_reset_done(this); |
| GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done); |
| StoreContextElement(native_context, |
| Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX, |
| UndefinedConstant()); |
| Goto(&preserved_data_reset_done); |
| BIND(&preserved_data_reset_done); |
| |
| RewindEnteredContext(saved_entered_context_count); |
| SetCurrentContext(current_context); |
| Goto(&done); |
| } |
| |
| BIND(&is_unreachable); |
| Unreachable(); |
| |
| BIND(&if_exception); |
| { |
| // Report unhandled exceptions from microtasks. |
| CallRuntime(Runtime::kReportMessageFromMicrotask, current_context, |
| var_exception.value()); |
| RewindEnteredContext(saved_entered_context_count); |
| SetCurrentContext(current_context); |
| Goto(&done); |
| } |
| |
| BIND(&done); |
| } |
| |
| void MicrotaskQueueBuiltinsAssembler::IncrementFinishedMicrotaskCount( |
| TNode<RawPtrT> microtask_queue) { |
| TNode<IntPtrT> count = Load<IntPtrT>( |
| microtask_queue, |
| IntPtrConstant(MicrotaskQueue::kFinishedMicrotaskCountOffset)); |
| TNode<IntPtrT> new_count = IntPtrAdd(count, IntPtrConstant(1)); |
| StoreNoWriteBarrier( |
| MachineType::PointerRepresentation(), microtask_queue, |
| IntPtrConstant(MicrotaskQueue::kFinishedMicrotaskCountOffset), new_count); |
| } |
| |
| TNode<Context> MicrotaskQueueBuiltinsAssembler::GetCurrentContext() { |
| auto ref = ExternalReference::Create(kContextAddress, isolate()); |
| // TODO(delphick): Add a checked cast. For now this is not possible as context |
| // can actually be Smi(0). |
| return TNode<Context>::UncheckedCast(LoadFullTagged(ExternalConstant(ref))); |
| } |
| |
| void MicrotaskQueueBuiltinsAssembler::SetCurrentContext( |
| TNode<Context> context) { |
| auto ref = ExternalReference::Create(kContextAddress, isolate()); |
| StoreFullTaggedNoWriteBarrier(ExternalConstant(ref), context); |
| } |
| |
| TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetEnteredContextCount() { |
| auto ref = ExternalReference::handle_scope_implementer_address(isolate()); |
| TNode<RawPtrT> hsi = Load<RawPtrT>(ExternalConstant(ref)); |
| |
| using ContextStack = DetachableVector<Context>; |
| TNode<IntPtrT> size_offset = |
| IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset + |
| ContextStack::kSizeOffset); |
| return Load<IntPtrT>(hsi, size_offset); |
| } |
| |
| void MicrotaskQueueBuiltinsAssembler::EnterMicrotaskContext( |
| TNode<Context> native_context) { |
| CSA_ASSERT(this, IsNativeContext(native_context)); |
| |
| auto ref = ExternalReference::handle_scope_implementer_address(isolate()); |
| TNode<RawPtrT> hsi = Load<RawPtrT>(ExternalConstant(ref)); |
| |
| using ContextStack = DetachableVector<Context>; |
| TNode<IntPtrT> capacity_offset = |
| IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset + |
| ContextStack::kCapacityOffset); |
| TNode<IntPtrT> size_offset = |
| IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset + |
| ContextStack::kSizeOffset); |
| |
| TNode<IntPtrT> capacity = Load<IntPtrT>(hsi, capacity_offset); |
| TNode<IntPtrT> size = Load<IntPtrT>(hsi, size_offset); |
| |
| Label if_append(this), if_grow(this, Label::kDeferred), done(this); |
| Branch(WordEqual(size, capacity), &if_grow, &if_append); |
| BIND(&if_append); |
| { |
| TNode<IntPtrT> data_offset = |
| IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset + |
| ContextStack::kDataOffset); |
| TNode<RawPtrT> data = Load<RawPtrT>(hsi, data_offset); |
| StoreFullTaggedNoWriteBarrier(data, TimesSystemPointerSize(size), |
| native_context); |
| |
| TNode<IntPtrT> new_size = IntPtrAdd(size, IntPtrConstant(1)); |
| StoreNoWriteBarrier(MachineType::PointerRepresentation(), hsi, size_offset, |
| new_size); |
| |
| using FlagStack = DetachableVector<int8_t>; |
| TNode<IntPtrT> flag_data_offset = |
| IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset + |
| FlagStack::kDataOffset); |
| TNode<RawPtrT> flag_data = Load<RawPtrT>(hsi, flag_data_offset); |
| StoreNoWriteBarrier(MachineRepresentation::kWord8, flag_data, size, |
| BoolConstant(true)); |
| StoreNoWriteBarrier( |
| MachineType::PointerRepresentation(), hsi, |
| IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset + |
| FlagStack::kSizeOffset), |
| new_size); |
| |
| Goto(&done); |
| } |
| |
| BIND(&if_grow); |
| { |
| TNode<ExternalReference> function = |
| ExternalConstant(ExternalReference::call_enter_context_function()); |
| CallCFunction(function, MachineType::Int32(), |
| std::make_pair(MachineType::Pointer(), hsi), |
| std::make_pair(MachineType::Pointer(), |
| BitcastTaggedToWord(native_context))); |
| Goto(&done); |
| } |
| |
| BIND(&done); |
| } |
| |
| void MicrotaskQueueBuiltinsAssembler::RewindEnteredContext( |
| TNode<IntPtrT> saved_entered_context_count) { |
| auto ref = ExternalReference::handle_scope_implementer_address(isolate()); |
| TNode<RawPtrT> hsi = Load<RawPtrT>(ExternalConstant(ref)); |
| |
| using ContextStack = DetachableVector<Context>; |
| TNode<IntPtrT> size_offset = |
| IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset + |
| ContextStack::kSizeOffset); |
| |
| #ifdef ENABLE_VERIFY_CSA |
| { |
| TNode<IntPtrT> size = Load<IntPtrT>(hsi, size_offset); |
| CSA_CHECK(this, IntPtrLessThan(IntPtrConstant(0), size)); |
| CSA_CHECK(this, IntPtrLessThanOrEqual(saved_entered_context_count, size)); |
| } |
| #endif |
| |
| StoreNoWriteBarrier(MachineType::PointerRepresentation(), hsi, size_offset, |
| saved_entered_context_count); |
| |
| using FlagStack = DetachableVector<int8_t>; |
| StoreNoWriteBarrier( |
| MachineType::PointerRepresentation(), hsi, |
| IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset + |
| FlagStack::kSizeOffset), |
| saved_entered_context_count); |
| } |
| |
| void MicrotaskQueueBuiltinsAssembler::RunPromiseHook( |
| Runtime::FunctionId id, TNode<Context> context, |
| TNode<HeapObject> promise_or_capability) { |
| Label hook(this, Label::kDeferred), done_hook(this); |
| Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), &hook, |
| &done_hook); |
| BIND(&hook); |
| { |
| // Get to the underlying JSPromise instance. |
| TNode<HeapObject> promise = Select<HeapObject>( |
| IsPromiseCapability(promise_or_capability), |
| [=] { |
| return CAST(LoadObjectField(promise_or_capability, |
| PromiseCapability::kPromiseOffset)); |
| }, |
| |
| [=] { return promise_or_capability; }); |
| GotoIf(IsUndefined(promise), &done_hook); |
| CallRuntime(id, context, promise); |
| Goto(&done_hook); |
| } |
| BIND(&done_hook); |
| } |
| |
| TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) { |
| auto microtask = Parameter<Microtask>(Descriptor::kMicrotask); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| TNode<NativeContext> native_context = LoadNativeContext(context); |
| TNode<RawPtrT> microtask_queue = GetMicrotaskQueue(native_context); |
| |
| // Do not store the microtask if MicrotaskQueue is not available, that may |
| // happen when the context shutdown. |
| Label if_shutdown(this, Label::kDeferred); |
| GotoIf(WordEqual(microtask_queue, IntPtrConstant(0)), &if_shutdown); |
| |
| TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue); |
| TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue); |
| TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue); |
| TNode<IntPtrT> start = GetMicrotaskQueueStart(microtask_queue); |
| |
| Label if_grow(this, Label::kDeferred); |
| GotoIf(IntPtrEqual(size, capacity), &if_grow); |
| |
| // |microtask_queue| has an unused slot to store |microtask|. |
| { |
| StoreNoWriteBarrier(MachineType::PointerRepresentation(), ring_buffer, |
| CalculateRingBufferOffset(capacity, start, size), |
| BitcastTaggedToWord(microtask)); |
| StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue, |
| IntPtrConstant(MicrotaskQueue::kSizeOffset), |
| IntPtrAdd(size, IntPtrConstant(1))); |
| Return(UndefinedConstant()); |
| } |
| |
| // |microtask_queue| has no space to store |microtask|. Fall back to C++ |
| // implementation to grow the buffer. |
| BIND(&if_grow); |
| { |
| TNode<ExternalReference> isolate_constant = |
| ExternalConstant(ExternalReference::isolate_address(isolate())); |
| TNode<ExternalReference> function = |
| ExternalConstant(ExternalReference::call_enqueue_microtask_function()); |
| CallCFunction(function, MachineType::AnyTagged(), |
| std::make_pair(MachineType::Pointer(), isolate_constant), |
| std::make_pair(MachineType::IntPtr(), microtask_queue), |
| std::make_pair(MachineType::AnyTagged(), microtask)); |
| Return(UndefinedConstant()); |
| } |
| |
| Bind(&if_shutdown); |
| Return(UndefinedConstant()); |
| } |
| |
| TF_BUILTIN(RunMicrotasks, MicrotaskQueueBuiltinsAssembler) { |
| // Load the current context from the isolate. |
| TNode<Context> current_context = GetCurrentContext(); |
| |
| auto microtask_queue = |
| UncheckedParameter<RawPtrT>(Descriptor::kMicrotaskQueue); |
| |
| Label loop(this), done(this); |
| Goto(&loop); |
| BIND(&loop); |
| |
| TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue); |
| |
| // Exit if the queue is empty. |
| GotoIf(WordEqual(size, IntPtrConstant(0)), &done); |
| |
| TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue); |
| TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue); |
| TNode<IntPtrT> start = GetMicrotaskQueueStart(microtask_queue); |
| |
| TNode<IntPtrT> offset = |
| CalculateRingBufferOffset(capacity, start, IntPtrConstant(0)); |
| TNode<RawPtrT> microtask_pointer = Load<RawPtrT>(ring_buffer, offset); |
| TNode<Microtask> microtask = CAST(BitcastWordToTagged(microtask_pointer)); |
| |
| TNode<IntPtrT> new_size = IntPtrSub(size, IntPtrConstant(1)); |
| TNode<IntPtrT> new_start = WordAnd(IntPtrAdd(start, IntPtrConstant(1)), |
| IntPtrSub(capacity, IntPtrConstant(1))); |
| |
| // Remove |microtask| from |ring_buffer| before running it, since its |
| // invocation may add another microtask into |ring_buffer|. |
| SetMicrotaskQueueSize(microtask_queue, new_size); |
| SetMicrotaskQueueStart(microtask_queue, new_start); |
| |
| RunSingleMicrotask(current_context, microtask); |
| IncrementFinishedMicrotaskCount(microtask_queue); |
| Goto(&loop); |
| |
| BIND(&done); |
| { |
| // Reset the "current microtask" on the isolate. |
| StoreRoot(RootIndex::kCurrentMicrotask, UndefinedConstant()); |
| Return(UndefinedConstant()); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace v8 |