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