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 
14 namespace v8 {
15 namespace internal {
16 
17 using compiler::ScopedExceptionHandler;
18 
19 class MicrotaskQueueBuiltinsAssembler : public CodeStubAssembler {
20  public:
MicrotaskQueueBuiltinsAssembler(compiler::CodeAssemblerState * state)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 
GetMicrotaskQueue(TNode<Context> native_context)56 TNode<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 
GetMicrotaskRingBuffer(TNode<RawPtrT> microtask_queue)64 TNode<RawPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskRingBuffer(
65     TNode<RawPtrT> microtask_queue) {
66   return Load<RawPtrT>(microtask_queue,
67                        IntPtrConstant(MicrotaskQueue::kRingBufferOffset));
68 }
69 
GetMicrotaskQueueCapacity(TNode<RawPtrT> microtask_queue)70 TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueCapacity(
71     TNode<RawPtrT> microtask_queue) {
72   return Load<IntPtrT>(microtask_queue,
73                        IntPtrConstant(MicrotaskQueue::kCapacityOffset));
74 }
75 
GetMicrotaskQueueSize(TNode<RawPtrT> microtask_queue)76 TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueSize(
77     TNode<RawPtrT> microtask_queue) {
78   return Load<IntPtrT>(microtask_queue,
79                        IntPtrConstant(MicrotaskQueue::kSizeOffset));
80 }
81 
SetMicrotaskQueueSize(TNode<RawPtrT> microtask_queue,TNode<IntPtrT> new_size)82 void 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 
GetMicrotaskQueueStart(TNode<RawPtrT> microtask_queue)88 TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueStart(
89     TNode<RawPtrT> microtask_queue) {
90   return Load<IntPtrT>(microtask_queue,
91                        IntPtrConstant(MicrotaskQueue::kStartOffset));
92 }
93 
SetMicrotaskQueueStart(TNode<RawPtrT> microtask_queue,TNode<IntPtrT> new_start)94 void 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 
CalculateRingBufferOffset(TNode<IntPtrT> capacity,TNode<IntPtrT> start,TNode<IntPtrT> index)100 TNode<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 
PrepareForContext(TNode<Context> native_context,Label * bailout)106 void 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 
RunSingleMicrotask(TNode<Context> current_context,TNode<Microtask> microtask)118 void 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, current_context,
335                 var_exception.value());
336     RewindEnteredContext(saved_entered_context_count);
337     SetCurrentContext(current_context);
338     Goto(&done);
339   }
340 
341   BIND(&done);
342 }
343 
IncrementFinishedMicrotaskCount(TNode<RawPtrT> microtask_queue)344 void 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 
GetCurrentContext()355 TNode<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 
SetCurrentContext(TNode<Context> context)362 void MicrotaskQueueBuiltinsAssembler::SetCurrentContext(
363     TNode<Context> context) {
364   auto ref = ExternalReference::Create(kContextAddress, isolate());
365   StoreFullTaggedNoWriteBarrier(ExternalConstant(ref), context);
366 }
367 
GetEnteredContextCount()368 TNode<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 
EnterMicrotaskContext(TNode<Context> native_context)379 void 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<RawPtrT> flag_data = Load<RawPtrT>(hsi, flag_data_offset);
417     StoreNoWriteBarrier(MachineRepresentation::kWord8, flag_data, size,
418                         BoolConstant(true));
419     StoreNoWriteBarrier(
420         MachineType::PointerRepresentation(), hsi,
421         IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset +
422                        FlagStack::kSizeOffset),
423         new_size);
424 
425     Goto(&done);
426   }
427 
428   BIND(&if_grow);
429   {
430     TNode<ExternalReference> function =
431         ExternalConstant(ExternalReference::call_enter_context_function());
432     CallCFunction(function, MachineType::Int32(),
433                   std::make_pair(MachineType::Pointer(), hsi),
434                   std::make_pair(MachineType::Pointer(),
435                                  BitcastTaggedToWord(native_context)));
436     Goto(&done);
437   }
438 
439   BIND(&done);
440 }
441 
RewindEnteredContext(TNode<IntPtrT> saved_entered_context_count)442 void MicrotaskQueueBuiltinsAssembler::RewindEnteredContext(
443     TNode<IntPtrT> saved_entered_context_count) {
444   auto ref = ExternalReference::handle_scope_implementer_address(isolate());
445   TNode<RawPtrT> hsi = Load<RawPtrT>(ExternalConstant(ref));
446 
447   using ContextStack = DetachableVector<Context>;
448   TNode<IntPtrT> size_offset =
449       IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
450                      ContextStack::kSizeOffset);
451 
452 #ifdef ENABLE_VERIFY_CSA
453   {
454     TNode<IntPtrT> size = Load<IntPtrT>(hsi, size_offset);
455     CSA_CHECK(this, IntPtrLessThan(IntPtrConstant(0), size));
456     CSA_CHECK(this, IntPtrLessThanOrEqual(saved_entered_context_count, size));
457   }
458 #endif
459 
460   StoreNoWriteBarrier(MachineType::PointerRepresentation(), hsi, size_offset,
461                       saved_entered_context_count);
462 
463   using FlagStack = DetachableVector<int8_t>;
464   StoreNoWriteBarrier(
465       MachineType::PointerRepresentation(), hsi,
466       IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset +
467                      FlagStack::kSizeOffset),
468       saved_entered_context_count);
469 }
470 
RunAllPromiseHooks(PromiseHookType type,TNode<Context> context,TNode<HeapObject> promise_or_capability)471 void MicrotaskQueueBuiltinsAssembler::RunAllPromiseHooks(
472     PromiseHookType type, TNode<Context> context,
473     TNode<HeapObject> promise_or_capability) {
474   Label hook(this, Label::kDeferred), done_hook(this);
475   TNode<Uint32T> promiseHookFlags = PromiseHookFlags();
476   Branch(NeedsAnyPromiseHooks(promiseHookFlags), &hook, &done_hook);
477   BIND(&hook);
478   {
479     switch (type) {
480       case PromiseHookType::kBefore:
481         RunContextPromiseHookBefore(context, promise_or_capability,
482                                     promiseHookFlags);
483         RunPromiseHook(Runtime::kPromiseHookBefore, context,
484                        promise_or_capability, promiseHookFlags);
485         break;
486       case PromiseHookType::kAfter:
487         RunContextPromiseHookAfter(context, promise_or_capability,
488                                    promiseHookFlags);
489         RunPromiseHook(Runtime::kPromiseHookAfter, context,
490                        promise_or_capability, promiseHookFlags);
491         break;
492       default:
493         UNREACHABLE();
494     }
495     Goto(&done_hook);
496   }
497   BIND(&done_hook);
498 }
499 
RunPromiseHook(Runtime::FunctionId id,TNode<Context> context,TNode<HeapObject> promise_or_capability,TNode<Uint32T> promiseHookFlags)500 void MicrotaskQueueBuiltinsAssembler::RunPromiseHook(
501     Runtime::FunctionId id, TNode<Context> context,
502     TNode<HeapObject> promise_or_capability,
503     TNode<Uint32T> promiseHookFlags) {
504   Label hook(this, Label::kDeferred), done_hook(this);
505   Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
506       promiseHookFlags), &hook, &done_hook);
507   BIND(&hook);
508   {
509     // Get to the underlying JSPromise instance.
510     TNode<HeapObject> promise = Select<HeapObject>(
511         IsPromiseCapability(promise_or_capability),
512         [=] {
513           return CAST(LoadObjectField(promise_or_capability,
514                                       PromiseCapability::kPromiseOffset));
515         },
516 
517         [=] { return promise_or_capability; });
518     GotoIf(IsUndefined(promise), &done_hook);
519     CallRuntime(id, context, promise);
520     Goto(&done_hook);
521   }
522   BIND(&done_hook);
523 }
524 
TF_BUILTIN(EnqueueMicrotask,MicrotaskQueueBuiltinsAssembler)525 TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) {
526   auto microtask = Parameter<Microtask>(Descriptor::kMicrotask);
527   auto context = Parameter<Context>(Descriptor::kContext);
528   TNode<NativeContext> native_context = LoadNativeContext(context);
529   TNode<RawPtrT> microtask_queue = GetMicrotaskQueue(native_context);
530 
531   // Do not store the microtask if MicrotaskQueue is not available, that may
532   // happen when the context shutdown.
533   Label if_shutdown(this, Label::kDeferred);
534   GotoIf(WordEqual(microtask_queue, IntPtrConstant(0)), &if_shutdown);
535 
536   TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue);
537   TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue);
538   TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue);
539   TNode<IntPtrT> start = GetMicrotaskQueueStart(microtask_queue);
540 
541   Label if_grow(this, Label::kDeferred);
542   GotoIf(IntPtrEqual(size, capacity), &if_grow);
543 
544   // |microtask_queue| has an unused slot to store |microtask|.
545   {
546     StoreNoWriteBarrier(MachineType::PointerRepresentation(), ring_buffer,
547                         CalculateRingBufferOffset(capacity, start, size),
548                         BitcastTaggedToWord(microtask));
549     StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue,
550                         IntPtrConstant(MicrotaskQueue::kSizeOffset),
551                         IntPtrAdd(size, IntPtrConstant(1)));
552     Return(UndefinedConstant());
553   }
554 
555   // |microtask_queue| has no space to store |microtask|. Fall back to C++
556   // implementation to grow the buffer.
557   BIND(&if_grow);
558   {
559     TNode<ExternalReference> isolate_constant =
560         ExternalConstant(ExternalReference::isolate_address(isolate()));
561     TNode<ExternalReference> function =
562         ExternalConstant(ExternalReference::call_enqueue_microtask_function());
563     CallCFunction(function, MachineType::AnyTagged(),
564                   std::make_pair(MachineType::Pointer(), isolate_constant),
565                   std::make_pair(MachineType::IntPtr(), microtask_queue),
566                   std::make_pair(MachineType::AnyTagged(), microtask));
567     Return(UndefinedConstant());
568   }
569 
570   Bind(&if_shutdown);
571   Return(UndefinedConstant());
572 }
573 
TF_BUILTIN(RunMicrotasks,MicrotaskQueueBuiltinsAssembler)574 TF_BUILTIN(RunMicrotasks, MicrotaskQueueBuiltinsAssembler) {
575   // Load the current context from the isolate.
576   TNode<Context> current_context = GetCurrentContext();
577 
578   auto microtask_queue =
579       UncheckedParameter<RawPtrT>(Descriptor::kMicrotaskQueue);
580 
581   Label loop(this), done(this);
582   Goto(&loop);
583   BIND(&loop);
584 
585   TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue);
586 
587   // Exit if the queue is empty.
588   GotoIf(WordEqual(size, IntPtrConstant(0)), &done);
589 
590   TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue);
591   TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue);
592   TNode<IntPtrT> start = GetMicrotaskQueueStart(microtask_queue);
593 
594   TNode<IntPtrT> offset =
595       CalculateRingBufferOffset(capacity, start, IntPtrConstant(0));
596   TNode<RawPtrT> microtask_pointer = Load<RawPtrT>(ring_buffer, offset);
597   TNode<Microtask> microtask = CAST(BitcastWordToTagged(microtask_pointer));
598 
599   TNode<IntPtrT> new_size = IntPtrSub(size, IntPtrConstant(1));
600   TNode<IntPtrT> new_start = WordAnd(IntPtrAdd(start, IntPtrConstant(1)),
601                                      IntPtrSub(capacity, IntPtrConstant(1)));
602 
603   // Remove |microtask| from |ring_buffer| before running it, since its
604   // invocation may add another microtask into |ring_buffer|.
605   SetMicrotaskQueueSize(microtask_queue, new_size);
606   SetMicrotaskQueueStart(microtask_queue, new_start);
607 
608   RunSingleMicrotask(current_context, microtask);
609   IncrementFinishedMicrotaskCount(microtask_queue);
610   Goto(&loop);
611 
612   BIND(&done);
613   {
614     // Reset the "current microtask" on the isolate.
615     StoreRoot(RootIndex::kCurrentMicrotask, UndefinedConstant());
616     Return(UndefinedConstant());
617   }
618 }
619 
620 }  // namespace internal
621 }  // namespace v8
622