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/api/api.h"
6#include "src/builtins/builtins-utils-gen.h"
7#include "src/codegen/code-stub-assembler.h"
8#include "src/execution/microtask-queue.h"
9#include "src/objects/js-weak-refs.h"
10#include "src/objects/microtask-inl.h"
11#include "src/objects/promise.h"
12#include "src/objects/smi-inl.h"
13
14namespace v8 {
15namespace internal {
16
17using compiler::ScopedExceptionHandler;
18
19class MicrotaskQueueBuiltinsAssembler : public CodeStubAssembler {
20 public:
21  explicit MicrotaskQueueBuiltinsAssembler(compiler::CodeAssemblerState* state)
22      : CodeStubAssembler(state) {}
23
24  TNode<RawPtrT> GetMicrotaskQueue(TNode<Context> context);
25  TNode<RawPtrT> GetMicrotaskRingBuffer(TNode<RawPtrT> microtask_queue);
26  TNode<IntPtrT> GetMicrotaskQueueCapacity(TNode<RawPtrT> microtask_queue);
27  TNode<IntPtrT> GetMicrotaskQueueSize(TNode<RawPtrT> microtask_queue);
28  void SetMicrotaskQueueSize(TNode<RawPtrT> microtask_queue,
29                             TNode<IntPtrT> new_size);
30  TNode<IntPtrT> GetMicrotaskQueueStart(TNode<RawPtrT> microtask_queue);
31  void SetMicrotaskQueueStart(TNode<RawPtrT> microtask_queue,
32                              TNode<IntPtrT> new_start);
33  TNode<IntPtrT> CalculateRingBufferOffset(TNode<IntPtrT> capacity,
34                                           TNode<IntPtrT> start,
35                                           TNode<IntPtrT> index);
36
37  void PrepareForContext(TNode<Context> microtask_context, Label* bailout);
38  void RunSingleMicrotask(TNode<Context> current_context,
39                          TNode<Microtask> microtask);
40  void IncrementFinishedMicrotaskCount(TNode<RawPtrT> microtask_queue);
41
42  TNode<Context> GetCurrentContext();
43  void SetCurrentContext(TNode<Context> context);
44
45  TNode<IntPtrT> GetEnteredContextCount();
46  void EnterMicrotaskContext(TNode<Context> native_context);
47  void RewindEnteredContext(TNode<IntPtrT> saved_entered_context_count);
48
49  void RunAllPromiseHooks(PromiseHookType type, TNode<Context> context,
50                          TNode<HeapObject> promise_or_capability);
51  void RunPromiseHook(Runtime::FunctionId id, TNode<Context> context,
52                      TNode<HeapObject> promise_or_capability,
53                      TNode<Uint32T> promiseHookFlags);
54};
55
56TNode<RawPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueue(
57    TNode<Context> native_context) {
58  CSA_DCHECK(this, IsNativeContext(native_context));
59  return LoadExternalPointerFromObject(native_context,
60                                       NativeContext::kMicrotaskQueueOffset,
61                                       kNativeContextMicrotaskQueueTag);
62}
63
64TNode<RawPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskRingBuffer(
65    TNode<RawPtrT> microtask_queue) {
66  return Load<RawPtrT>(microtask_queue,
67                       IntPtrConstant(MicrotaskQueue::kRingBufferOffset));
68}
69
70TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueCapacity(
71    TNode<RawPtrT> microtask_queue) {
72  return Load<IntPtrT>(microtask_queue,
73                       IntPtrConstant(MicrotaskQueue::kCapacityOffset));
74}
75
76TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueSize(
77    TNode<RawPtrT> microtask_queue) {
78  return Load<IntPtrT>(microtask_queue,
79                       IntPtrConstant(MicrotaskQueue::kSizeOffset));
80}
81
82void MicrotaskQueueBuiltinsAssembler::SetMicrotaskQueueSize(
83    TNode<RawPtrT> microtask_queue, TNode<IntPtrT> new_size) {
84  StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue,
85                      IntPtrConstant(MicrotaskQueue::kSizeOffset), new_size);
86}
87
88TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueStart(
89    TNode<RawPtrT> microtask_queue) {
90  return Load<IntPtrT>(microtask_queue,
91                       IntPtrConstant(MicrotaskQueue::kStartOffset));
92}
93
94void MicrotaskQueueBuiltinsAssembler::SetMicrotaskQueueStart(
95    TNode<RawPtrT> microtask_queue, TNode<IntPtrT> new_start) {
96  StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue,
97                      IntPtrConstant(MicrotaskQueue::kStartOffset), new_start);
98}
99
100TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::CalculateRingBufferOffset(
101    TNode<IntPtrT> capacity, TNode<IntPtrT> start, TNode<IntPtrT> index) {
102  return TimesSystemPointerSize(
103      WordAnd(IntPtrAdd(start, index), IntPtrSub(capacity, IntPtrConstant(1))));
104}
105
106void MicrotaskQueueBuiltinsAssembler::PrepareForContext(
107    TNode<Context> native_context, Label* bailout) {
108  CSA_DCHECK(this, IsNativeContext(native_context));
109
110  // Skip the microtask execution if the associated context is shutdown.
111  GotoIf(WordEqual(GetMicrotaskQueue(native_context), IntPtrConstant(0)),
112         bailout);
113
114  EnterMicrotaskContext(native_context);
115  SetCurrentContext(native_context);
116}
117
118void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
119    TNode<Context> current_context, TNode<Microtask> microtask) {
120  CSA_DCHECK(this, TaggedIsNotSmi(microtask));
121
122  StoreRoot(RootIndex::kCurrentMicrotask, microtask);
123  TNode<IntPtrT> saved_entered_context_count = GetEnteredContextCount();
124  TNode<Map> microtask_map = LoadMap(microtask);
125  TNode<Uint16T> microtask_type = LoadMapInstanceType(microtask_map);
126
127  TVARIABLE(Object, var_exception);
128  Label if_exception(this, Label::kDeferred);
129  Label is_callable(this), is_callback(this),
130      is_promise_fulfill_reaction_job(this),
131      is_promise_reject_reaction_job(this),
132      is_promise_resolve_thenable_job(this),
133      is_unreachable(this, Label::kDeferred), done(this);
134
135  int32_t case_values[] = {CALLABLE_TASK_TYPE, CALLBACK_TASK_TYPE,
136                           PROMISE_FULFILL_REACTION_JOB_TASK_TYPE,
137                           PROMISE_REJECT_REACTION_JOB_TASK_TYPE,
138                           PROMISE_RESOLVE_THENABLE_JOB_TASK_TYPE};
139  Label* case_labels[] = {
140      &is_callable, &is_callback, &is_promise_fulfill_reaction_job,
141      &is_promise_reject_reaction_job, &is_promise_resolve_thenable_job};
142  static_assert(arraysize(case_values) == arraysize(case_labels), "");
143  Switch(microtask_type, &is_unreachable, case_values, case_labels,
144         arraysize(case_labels));
145
146  BIND(&is_callable);
147  {
148    // Enter the context of the {microtask}.
149    TNode<Context> microtask_context =
150        LoadObjectField<Context>(microtask, CallableTask::kContextOffset);
151    TNode<NativeContext> native_context = LoadNativeContext(microtask_context);
152    PrepareForContext(native_context, &done);
153
154    TNode<JSReceiver> callable =
155        LoadObjectField<JSReceiver>(microtask, CallableTask::kCallableOffset);
156    {
157      ScopedExceptionHandler handler(this, &if_exception, &var_exception);
158      Call(microtask_context, callable, UndefinedConstant());
159    }
160    RewindEnteredContext(saved_entered_context_count);
161    SetCurrentContext(current_context);
162    Goto(&done);
163  }
164
165  BIND(&is_callback);
166  {
167    const TNode<Object> microtask_callback =
168        LoadObjectField(microtask, CallbackTask::kCallbackOffset);
169    const TNode<Object> microtask_data =
170        LoadObjectField(microtask, CallbackTask::kDataOffset);
171
172    // If this turns out to become a bottleneck because of the calls
173    // to C++ via CEntry, we can choose to speed them up using a
174    // similar mechanism that we use for the CallApiFunction stub,
175    // except that calling the MicrotaskCallback is even easier, since
176    // it doesn't accept any tagged parameters, doesn't return a value
177    // and ignores exceptions.
178    //
179    // But from our current measurements it doesn't seem to be a
180    // serious performance problem, even if the microtask is full
181    // of CallHandlerTasks (which is not a realistic use case anyways).
182    {
183      ScopedExceptionHandler handler(this, &if_exception, &var_exception);
184      CallRuntime(Runtime::kRunMicrotaskCallback, current_context,
185                  microtask_callback, microtask_data);
186    }
187    Goto(&done);
188  }
189
190  BIND(&is_promise_resolve_thenable_job);
191  {
192    // Enter the context of the {microtask}.
193    TNode<Context> microtask_context = LoadObjectField<Context>(
194        microtask, PromiseResolveThenableJobTask::kContextOffset);
195    TNode<NativeContext> native_context = LoadNativeContext(microtask_context);
196    PrepareForContext(native_context, &done);
197
198    const TNode<Object> promise_to_resolve = LoadObjectField(
199        microtask, PromiseResolveThenableJobTask::kPromiseToResolveOffset);
200    const TNode<Object> then =
201        LoadObjectField(microtask, PromiseResolveThenableJobTask::kThenOffset);
202    const TNode<Object> thenable = LoadObjectField(
203        microtask, PromiseResolveThenableJobTask::kThenableOffset);
204
205    RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
206                   CAST(promise_to_resolve));
207
208    {
209      ScopedExceptionHandler handler(this, &if_exception, &var_exception);
210      CallBuiltin(Builtin::kPromiseResolveThenableJob, native_context,
211                  promise_to_resolve, thenable, then);
212    }
213
214    RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
215                   CAST(promise_to_resolve));
216
217    RewindEnteredContext(saved_entered_context_count);
218    SetCurrentContext(current_context);
219    Goto(&done);
220  }
221
222  BIND(&is_promise_fulfill_reaction_job);
223  {
224    // Enter the context of the {microtask}.
225    TNode<Context> microtask_context = LoadObjectField<Context>(
226        microtask, PromiseReactionJobTask::kContextOffset);
227    TNode<NativeContext> native_context = LoadNativeContext(microtask_context);
228    PrepareForContext(native_context, &done);
229
230    const TNode<Object> argument =
231        LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
232    const TNode<Object> job_handler =
233        LoadObjectField(microtask, PromiseReactionJobTask::kHandlerOffset);
234    const TNode<HeapObject> promise_or_capability = CAST(LoadObjectField(
235        microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset));
236
237    TNode<Object> preserved_embedder_data = LoadObjectField(
238        microtask,
239        PromiseReactionJobTask::kContinuationPreservedEmbedderDataOffset);
240    Label preserved_data_done(this);
241    GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_done);
242    StoreContextElement(native_context,
243                        Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX,
244                        preserved_embedder_data);
245    Goto(&preserved_data_done);
246    BIND(&preserved_data_done);
247
248    // Run the promise before/debug hook if enabled.
249    RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
250                       promise_or_capability);
251
252    {
253      ScopedExceptionHandler handler(this, &if_exception, &var_exception);
254      CallBuiltin(Builtin::kPromiseFulfillReactionJob, microtask_context,
255                  argument, job_handler, promise_or_capability);
256    }
257
258    // Run the promise after/debug hook if enabled.
259    RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
260                       promise_or_capability);
261
262    Label preserved_data_reset_done(this);
263    GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done);
264    StoreContextElement(native_context,
265                        Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX,
266                        UndefinedConstant());
267    Goto(&preserved_data_reset_done);
268    BIND(&preserved_data_reset_done);
269
270    RewindEnteredContext(saved_entered_context_count);
271    SetCurrentContext(current_context);
272    Goto(&done);
273  }
274
275  BIND(&is_promise_reject_reaction_job);
276  {
277    // Enter the context of the {microtask}.
278    TNode<Context> microtask_context = LoadObjectField<Context>(
279        microtask, PromiseReactionJobTask::kContextOffset);
280    TNode<NativeContext> native_context = LoadNativeContext(microtask_context);
281    PrepareForContext(native_context, &done);
282
283    const TNode<Object> argument =
284        LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
285    const TNode<Object> job_handler =
286        LoadObjectField(microtask, PromiseReactionJobTask::kHandlerOffset);
287    const TNode<HeapObject> promise_or_capability = CAST(LoadObjectField(
288        microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset));
289
290    TNode<Object> preserved_embedder_data = LoadObjectField(
291        microtask,
292        PromiseReactionJobTask::kContinuationPreservedEmbedderDataOffset);
293    Label preserved_data_done(this);
294    GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_done);
295    StoreContextElement(native_context,
296                        Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX,
297                        preserved_embedder_data);
298    Goto(&preserved_data_done);
299    BIND(&preserved_data_done);
300
301    // Run the promise before/debug hook if enabled.
302    RunAllPromiseHooks(PromiseHookType::kBefore, microtask_context,
303                       promise_or_capability);
304
305    {
306      ScopedExceptionHandler handler(this, &if_exception, &var_exception);
307      CallBuiltin(Builtin::kPromiseRejectReactionJob, microtask_context,
308                  argument, job_handler, promise_or_capability);
309    }
310
311    // Run the promise after/debug hook if enabled.
312    RunAllPromiseHooks(PromiseHookType::kAfter, microtask_context,
313                       promise_or_capability);
314
315    Label preserved_data_reset_done(this);
316    GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done);
317    StoreContextElement(native_context,
318                        Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX,
319                        UndefinedConstant());
320    Goto(&preserved_data_reset_done);
321    BIND(&preserved_data_reset_done);
322
323    RewindEnteredContext(saved_entered_context_count);
324    SetCurrentContext(current_context);
325    Goto(&done);
326  }
327
328  BIND(&is_unreachable);
329  Unreachable();
330
331  BIND(&if_exception);
332  {
333    // Report unhandled exceptions from microtasks.
334    CallRuntime(Runtime::kReportMessageFromMicrotask, GetCurrentContext(),
335                var_exception.value());
336    RewindEnteredContext(saved_entered_context_count);
337    SetCurrentContext(current_context);
338    Goto(&done);
339  }
340
341  BIND(&done);
342}
343
344void MicrotaskQueueBuiltinsAssembler::IncrementFinishedMicrotaskCount(
345    TNode<RawPtrT> microtask_queue) {
346  TNode<IntPtrT> count = Load<IntPtrT>(
347      microtask_queue,
348      IntPtrConstant(MicrotaskQueue::kFinishedMicrotaskCountOffset));
349  TNode<IntPtrT> new_count = IntPtrAdd(count, IntPtrConstant(1));
350  StoreNoWriteBarrier(
351      MachineType::PointerRepresentation(), microtask_queue,
352      IntPtrConstant(MicrotaskQueue::kFinishedMicrotaskCountOffset), new_count);
353}
354
355TNode<Context> MicrotaskQueueBuiltinsAssembler::GetCurrentContext() {
356  auto ref = ExternalReference::Create(kContextAddress, isolate());
357  // TODO(delphick): Add a checked cast. For now this is not possible as context
358  // can actually be Smi(0).
359  return TNode<Context>::UncheckedCast(LoadFullTagged(ExternalConstant(ref)));
360}
361
362void MicrotaskQueueBuiltinsAssembler::SetCurrentContext(
363    TNode<Context> context) {
364  auto ref = ExternalReference::Create(kContextAddress, isolate());
365  StoreFullTaggedNoWriteBarrier(ExternalConstant(ref), context);
366}
367
368TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetEnteredContextCount() {
369  auto ref = ExternalReference::handle_scope_implementer_address(isolate());
370  TNode<RawPtrT> hsi = Load<RawPtrT>(ExternalConstant(ref));
371
372  using ContextStack = DetachableVector<Context>;
373  TNode<IntPtrT> size_offset =
374      IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
375                     ContextStack::kSizeOffset);
376  return Load<IntPtrT>(hsi, size_offset);
377}
378
379void MicrotaskQueueBuiltinsAssembler::EnterMicrotaskContext(
380    TNode<Context> native_context) {
381  CSA_DCHECK(this, IsNativeContext(native_context));
382
383  auto ref = ExternalReference::handle_scope_implementer_address(isolate());
384  TNode<RawPtrT> hsi = Load<RawPtrT>(ExternalConstant(ref));
385
386  using ContextStack = DetachableVector<Context>;
387  TNode<IntPtrT> capacity_offset =
388      IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
389                     ContextStack::kCapacityOffset);
390  TNode<IntPtrT> size_offset =
391      IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
392                     ContextStack::kSizeOffset);
393
394  TNode<IntPtrT> capacity = Load<IntPtrT>(hsi, capacity_offset);
395  TNode<IntPtrT> size = Load<IntPtrT>(hsi, size_offset);
396
397  Label if_append(this), if_grow(this, Label::kDeferred), done(this);
398  Branch(WordEqual(size, capacity), &if_grow, &if_append);
399  BIND(&if_append);
400  {
401    TNode<IntPtrT> data_offset =
402        IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
403                       ContextStack::kDataOffset);
404    TNode<RawPtrT> data = Load<RawPtrT>(hsi, data_offset);
405    StoreFullTaggedNoWriteBarrier(data, TimesSystemPointerSize(size),
406                                  native_context);
407
408    TNode<IntPtrT> new_size = IntPtrAdd(size, IntPtrConstant(1));
409    StoreNoWriteBarrier(MachineType::PointerRepresentation(), hsi, size_offset,
410                        new_size);
411
412    using FlagStack = DetachableVector<int8_t>;
413    TNode<IntPtrT> flag_data_offset =
414        IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset +
415                       FlagStack::kDataOffset);
416    TNode<IntPtrT> flag_capacity_offset =
417        IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset +
418                       FlagStack::kCapacityOffset);
419    TNode<IntPtrT> flag_size_offset =
420        IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset +
421                       FlagStack::kSizeOffset);
422    // Ensure both stacks are in sync.
423    USE(flag_capacity_offset);
424    CSA_DCHECK(this,
425               WordEqual(capacity, Load<IntPtrT>(hsi, flag_capacity_offset)));
426    CSA_DCHECK(this, WordEqual(size, Load<IntPtrT>(hsi, flag_size_offset)));
427
428    TNode<RawPtrT> flag_data = Load<RawPtrT>(hsi, flag_data_offset);
429    StoreNoWriteBarrier(MachineRepresentation::kWord8, flag_data, size,
430                        BoolConstant(true));
431    StoreNoWriteBarrier(MachineType::PointerRepresentation(), hsi,
432                        flag_size_offset, new_size);
433
434    Goto(&done);
435  }
436
437  BIND(&if_grow);
438  {
439    TNode<ExternalReference> function =
440        ExternalConstant(ExternalReference::call_enter_context_function());
441    CallCFunction(function, MachineType::Int32(),
442                  std::make_pair(MachineType::Pointer(), hsi),
443                  std::make_pair(MachineType::Pointer(),
444                                 BitcastTaggedToWord(native_context)));
445    Goto(&done);
446  }
447
448  BIND(&done);
449}
450
451void MicrotaskQueueBuiltinsAssembler::RewindEnteredContext(
452    TNode<IntPtrT> saved_entered_context_count) {
453  auto ref = ExternalReference::handle_scope_implementer_address(isolate());
454  TNode<RawPtrT> hsi = Load<RawPtrT>(ExternalConstant(ref));
455
456  using ContextStack = DetachableVector<Context>;
457  TNode<IntPtrT> size_offset =
458      IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
459                     ContextStack::kSizeOffset);
460
461  if (DEBUG_BOOL) {
462    TNode<IntPtrT> size = Load<IntPtrT>(hsi, size_offset);
463    CSA_CHECK(this, IntPtrLessThan(IntPtrConstant(0), size));
464    CSA_CHECK(this, IntPtrLessThanOrEqual(saved_entered_context_count, size));
465  }
466
467  StoreNoWriteBarrier(MachineType::PointerRepresentation(), hsi, size_offset,
468                      saved_entered_context_count);
469
470  using FlagStack = DetachableVector<int8_t>;
471  StoreNoWriteBarrier(
472      MachineType::PointerRepresentation(), hsi,
473      IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset +
474                     FlagStack::kSizeOffset),
475      saved_entered_context_count);
476}
477
478void MicrotaskQueueBuiltinsAssembler::RunAllPromiseHooks(
479    PromiseHookType type, TNode<Context> context,
480    TNode<HeapObject> promise_or_capability) {
481  TNode<Uint32T> promiseHookFlags = PromiseHookFlags();
482#ifdef V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS
483  Label hook(this, Label::kDeferred), done_hook(this);
484  Branch(NeedsAnyPromiseHooks(promiseHookFlags), &hook, &done_hook);
485  BIND(&hook);
486  {
487#endif
488    switch (type) {
489      case PromiseHookType::kBefore:
490#ifdef V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS
491        RunContextPromiseHookBefore(context, promise_or_capability,
492                                    promiseHookFlags);
493#endif
494        RunPromiseHook(Runtime::kPromiseHookBefore, context,
495                       promise_or_capability, promiseHookFlags);
496        break;
497      case PromiseHookType::kAfter:
498#ifdef V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS
499        RunContextPromiseHookAfter(context, promise_or_capability,
500                                   promiseHookFlags);
501#endif
502        RunPromiseHook(Runtime::kPromiseHookAfter, context,
503                       promise_or_capability, promiseHookFlags);
504        break;
505      default:
506        UNREACHABLE();
507    }
508#ifdef V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS
509    Goto(&done_hook);
510  }
511  BIND(&done_hook);
512#endif
513}
514
515void MicrotaskQueueBuiltinsAssembler::RunPromiseHook(
516    Runtime::FunctionId id, TNode<Context> context,
517    TNode<HeapObject> promise_or_capability,
518    TNode<Uint32T> promiseHookFlags) {
519  Label hook(this, Label::kDeferred), done_hook(this);
520  Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
521      promiseHookFlags), &hook, &done_hook);
522  BIND(&hook);
523  {
524    // Get to the underlying JSPromise instance.
525    TNode<HeapObject> promise = Select<HeapObject>(
526        IsPromiseCapability(promise_or_capability),
527        [=] {
528          return CAST(LoadObjectField(promise_or_capability,
529                                      PromiseCapability::kPromiseOffset));
530        },
531
532        [=] { return promise_or_capability; });
533    GotoIf(IsUndefined(promise), &done_hook);
534    CallRuntime(id, context, promise);
535    Goto(&done_hook);
536  }
537  BIND(&done_hook);
538}
539
540TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) {
541  auto microtask = Parameter<Microtask>(Descriptor::kMicrotask);
542  auto context = Parameter<Context>(Descriptor::kContext);
543  TNode<NativeContext> native_context = LoadNativeContext(context);
544  TNode<RawPtrT> microtask_queue = GetMicrotaskQueue(native_context);
545
546  // Do not store the microtask if MicrotaskQueue is not available, that may
547  // happen when the context shutdown.
548  Label if_shutdown(this, Label::kDeferred);
549  GotoIf(WordEqual(microtask_queue, IntPtrConstant(0)), &if_shutdown);
550
551  TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue);
552  TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue);
553  TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue);
554  TNode<IntPtrT> start = GetMicrotaskQueueStart(microtask_queue);
555
556  Label if_grow(this, Label::kDeferred);
557  GotoIf(IntPtrEqual(size, capacity), &if_grow);
558
559  // |microtask_queue| has an unused slot to store |microtask|.
560  {
561    StoreNoWriteBarrier(MachineType::PointerRepresentation(), ring_buffer,
562                        CalculateRingBufferOffset(capacity, start, size),
563                        BitcastTaggedToWord(microtask));
564    StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue,
565                        IntPtrConstant(MicrotaskQueue::kSizeOffset),
566                        IntPtrAdd(size, IntPtrConstant(1)));
567    Return(UndefinedConstant());
568  }
569
570  // |microtask_queue| has no space to store |microtask|. Fall back to C++
571  // implementation to grow the buffer.
572  BIND(&if_grow);
573  {
574    TNode<ExternalReference> isolate_constant =
575        ExternalConstant(ExternalReference::isolate_address(isolate()));
576    TNode<ExternalReference> function =
577        ExternalConstant(ExternalReference::call_enqueue_microtask_function());
578    CallCFunction(function, MachineType::AnyTagged(),
579                  std::make_pair(MachineType::Pointer(), isolate_constant),
580                  std::make_pair(MachineType::IntPtr(), microtask_queue),
581                  std::make_pair(MachineType::AnyTagged(), microtask));
582    Return(UndefinedConstant());
583  }
584
585  Bind(&if_shutdown);
586  Return(UndefinedConstant());
587}
588
589TF_BUILTIN(RunMicrotasks, MicrotaskQueueBuiltinsAssembler) {
590  // Load the current context from the isolate.
591  TNode<Context> current_context = GetCurrentContext();
592
593  auto microtask_queue =
594      UncheckedParameter<RawPtrT>(Descriptor::kMicrotaskQueue);
595
596  Label loop(this), done(this);
597  Goto(&loop);
598  BIND(&loop);
599
600  TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue);
601
602  // Exit if the queue is empty.
603  GotoIf(WordEqual(size, IntPtrConstant(0)), &done);
604
605  TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue);
606  TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue);
607  TNode<IntPtrT> start = GetMicrotaskQueueStart(microtask_queue);
608
609  TNode<IntPtrT> offset =
610      CalculateRingBufferOffset(capacity, start, IntPtrConstant(0));
611  TNode<RawPtrT> microtask_pointer = Load<RawPtrT>(ring_buffer, offset);
612  TNode<Microtask> microtask = CAST(BitcastWordToTagged(microtask_pointer));
613
614  TNode<IntPtrT> new_size = IntPtrSub(size, IntPtrConstant(1));
615  TNode<IntPtrT> new_start = WordAnd(IntPtrAdd(start, IntPtrConstant(1)),
616                                     IntPtrSub(capacity, IntPtrConstant(1)));
617
618  // Remove |microtask| from |ring_buffer| before running it, since its
619  // invocation may add another microtask into |ring_buffer|.
620  SetMicrotaskQueueSize(microtask_queue, new_size);
621  SetMicrotaskQueueStart(microtask_queue, new_start);
622
623  RunSingleMicrotask(current_context, microtask);
624  IncrementFinishedMicrotaskCount(microtask_queue);
625  Goto(&loop);
626
627  BIND(&done);
628  {
629    // Reset the "current microtask" on the isolate.
630    StoreRoot(RootIndex::kCurrentMicrotask, UndefinedConstant());
631    Return(UndefinedConstant());
632  }
633}
634
635}  // namespace internal
636}  // namespace v8
637