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, 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
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<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
RewindEnteredContext(TNode<IntPtrT> saved_entered_context_count)451 void 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
RunAllPromiseHooks(PromiseHookType type,TNode<Context> context,TNode<HeapObject> promise_or_capability)478 void 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
RunPromiseHook(Runtime::FunctionId id,TNode<Context> context,TNode<HeapObject> promise_or_capability,TNode<Uint32T> promiseHookFlags)515 void 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
TF_BUILTIN(EnqueueMicrotask,MicrotaskQueueBuiltinsAssembler)540 TF_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
TF_BUILTIN(RunMicrotasks,MicrotaskQueueBuiltinsAssembler)589 TF_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