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