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 : 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 0 : 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 224 : 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 224 : 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 224 : IntPtrConstant(MicrotaskQueue::kSizeOffset)));
80 : }
81 :
82 56 : void MicrotaskQueueBuiltinsAssembler::SetMicrotaskQueueSize(
83 : TNode<RawPtrT> microtask_queue, TNode<IntPtrT> new_size) {
84 : 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 224 : IntPtrConstant(MicrotaskQueue::kStartOffset)));
93 : }
94 :
95 56 : void MicrotaskQueueBuiltinsAssembler::SetMicrotaskQueueStart(
96 : TNode<RawPtrT> microtask_queue, TNode<IntPtrT> new_start) {
97 : 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 336 : 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 840 : 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 168 : VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());
129 56 : Label if_exception(this, Label::kDeferred);
130 56 : Label is_callable(this), is_callback(this),
131 56 : is_promise_fulfill_reaction_job(this),
132 56 : is_promise_reject_reaction_job(this),
133 56 : is_promise_resolve_thenable_job(this),
134 56 : is_finalization_group_cleanup_job(this),
135 56 : 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 : 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 : 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 : LoadObjectField<JSReceiver>(microtask, CallableTask::kCallableOffset);
163 : 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 : LoadObjectField(microtask, CallbackTask::kCallbackOffset);
176 : Node* const microtask_data =
177 : 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 : CallRuntime(Runtime::kRunMicrotaskCallback, current_context,
191 : 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 : microtask, PromiseResolveThenableJobTask::kContextOffset);
201 56 : TNode<Context> native_context = LoadNativeContext(microtask_context);
202 56 : PrepareForContext(native_context, &done);
203 :
204 : Node* const promise_to_resolve = LoadObjectField(
205 : microtask, PromiseResolveThenableJobTask::kPromiseToResolveOffset);
206 : Node* const then =
207 : LoadObjectField(microtask, PromiseResolveThenableJobTask::kThenOffset);
208 : Node* const thenable = LoadObjectField(
209 : microtask, PromiseResolveThenableJobTask::kThenableOffset);
210 :
211 : Node* const result =
212 112 : CallBuiltin(Builtins::kPromiseResolveThenableJob, native_context,
213 56 : 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 : microtask, PromiseReactionJobTask::kContextOffset);
225 56 : TNode<Context> native_context = LoadNativeContext(microtask_context);
226 56 : PrepareForContext(native_context, &done);
227 :
228 : Node* const argument =
229 : LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
230 : Node* const handler =
231 : LoadObjectField(microtask, PromiseReactionJobTask::kHandlerOffset);
232 : Node* const promise_or_capability = LoadObjectField(
233 : microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset);
234 :
235 : // Run the promise before/debug hook if enabled.
236 56 : RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
237 56 : promise_or_capability);
238 :
239 : Node* const result =
240 112 : CallBuiltin(Builtins::kPromiseFulfillReactionJob, microtask_context,
241 56 : argument, handler, promise_or_capability);
242 56 : GotoIfException(result, &if_exception, &var_exception);
243 :
244 : // Run the promise after/debug hook if enabled.
245 56 : 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 : microtask, PromiseReactionJobTask::kContextOffset);
258 56 : TNode<Context> native_context = LoadNativeContext(microtask_context);
259 56 : PrepareForContext(native_context, &done);
260 :
261 : Node* const argument =
262 : LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
263 : Node* const handler =
264 : LoadObjectField(microtask, PromiseReactionJobTask::kHandlerOffset);
265 : Node* const promise_or_capability = LoadObjectField(
266 : microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset);
267 :
268 : // Run the promise before/debug hook if enabled.
269 56 : RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
270 56 : promise_or_capability);
271 :
272 : Node* const result =
273 112 : CallBuiltin(Builtins::kPromiseRejectReactionJob, microtask_context,
274 56 : argument, handler, promise_or_capability);
275 56 : GotoIfException(result, &if_exception, &var_exception);
276 :
277 : // Run the promise after/debug hook if enabled.
278 56 : 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 : FinalizationGroupCleanupJobTask::kFinalizationGroupOffset);
293 : TNode<Context> native_context = LoadObjectField<Context>(
294 : finalization_group, JSFinalizationGroup::kNativeContextOffset);
295 56 : PrepareForContext(native_context, &done);
296 :
297 : Node* const result = CallRuntime(Runtime::kFinalizationGroupCleanupJob,
298 : 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 112 : IntPtrConstant(MicrotaskQueue::kFinishedMicrotaskCountOffset)));
327 56 : TNode<IntPtrT> new_count = IntPtrAdd(count, IntPtrConstant(1));
328 : 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 112 : 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 1232 : StoreFullTaggedNoWriteBarrier(ExternalConstant(ref), context);
342 616 : }
343 :
344 56 : TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetEnteredContextCount() {
345 56 : auto ref = ExternalReference::handle_scope_implementer_address(isolate());
346 112 : 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 560 : 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 280 : Label if_append(this), if_grow(this, Label::kDeferred), done(this);
378 560 : 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 : StoreFullTaggedNoWriteBarrier(data, TimesSystemPointerSize(size),
386 280 : native_context);
387 :
388 280 : TNode<IntPtrT> new_size = IntPtrAdd(size, IntPtrConstant(1));
389 : 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 : StoreNoWriteBarrier(MachineRepresentation::kWord8, flag_data, size,
398 560 : BoolConstant(true));
399 : 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 560 : ExternalConstant(ExternalReference::call_enter_context_function());
412 : CallCFunction(function, MachineType::Int32(),
413 : std::make_pair(MachineType::Pointer(), hsi),
414 : std::make_pair(MachineType::Pointer(),
415 560 : BitcastTaggedToWord(native_context)));
416 280 : Goto(&done);
417 : }
418 :
419 280 : BIND(&done);
420 280 : }
421 :
422 336 : void MicrotaskQueueBuiltinsAssembler::RewindEnteredContext(
423 : TNode<IntPtrT> saved_entered_context_count) {
424 336 : auto ref = ExternalReference::handle_scope_implementer_address(isolate());
425 672 : Node* hsi = Load(MachineType::Pointer(), ExternalConstant(ref));
426 :
427 : using ContextStack = DetachableVector<Context>;
428 : TNode<IntPtrT> size_offset =
429 336 : IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
430 336 : ContextStack::kSizeOffset);
431 :
432 : #ifdef ENABLE_VERIFY_CSA
433 : TNode<IntPtrT> size =
434 : UncheckedCast<IntPtrT>(Load(MachineType::IntPtr(), hsi, size_offset));
435 : CSA_ASSERT(this, IntPtrLessThan(IntPtrConstant(0), size));
436 : CSA_ASSERT(this, IntPtrLessThanOrEqual(saved_entered_context_count, size));
437 : #endif
438 :
439 : StoreNoWriteBarrier(MachineType::PointerRepresentation(), hsi, size_offset,
440 336 : saved_entered_context_count);
441 :
442 : using FlagStack = DetachableVector<int8_t>;
443 : StoreNoWriteBarrier(
444 : MachineType::PointerRepresentation(), hsi,
445 1008 : IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset +
446 336 : FlagStack::kSizeOffset),
447 336 : saved_entered_context_count);
448 336 : }
449 :
450 224 : void MicrotaskQueueBuiltinsAssembler::RunPromiseHook(
451 : Runtime::FunctionId id, TNode<Context> context,
452 : SloppyTNode<HeapObject> promise_or_capability) {
453 448 : Label hook(this, Label::kDeferred), done_hook(this);
454 448 : Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), &hook,
455 224 : &done_hook);
456 224 : BIND(&hook);
457 : {
458 : // Get to the underlying JSPromise instance.
459 : TNode<HeapObject> promise = Select<HeapObject>(
460 448 : IsPromiseCapability(promise_or_capability),
461 224 : [=] {
462 224 : return CAST(LoadObjectField(promise_or_capability,
463 : PromiseCapability::kPromiseOffset));
464 224 : },
465 :
466 896 : [=] { return promise_or_capability; });
467 448 : GotoIf(IsUndefined(promise), &done_hook);
468 : CallRuntime(id, context, promise);
469 224 : Goto(&done_hook);
470 : }
471 224 : BIND(&done_hook);
472 224 : }
473 :
474 224 : TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) {
475 : TNode<Microtask> microtask =
476 : UncheckedCast<Microtask>(Parameter(Descriptor::kMicrotask));
477 : TNode<Context> context = CAST(Parameter(Descriptor::kContext));
478 56 : TNode<Context> native_context = LoadNativeContext(context);
479 : TNode<RawPtrT> microtask_queue = GetMicrotaskQueue(native_context);
480 :
481 : // Do not store the microtask if MicrotaskQueue is not available, that may
482 : // happen when the context shutdown.
483 56 : Label if_shutdown(this, Label::kDeferred);
484 168 : GotoIf(WordEqual(microtask_queue, IntPtrConstant(0)), &if_shutdown);
485 :
486 56 : TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue);
487 56 : TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue);
488 56 : TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue);
489 56 : TNode<IntPtrT> start = GetMicrotaskQueueStart(microtask_queue);
490 :
491 56 : Label if_grow(this, Label::kDeferred);
492 112 : GotoIf(IntPtrEqual(size, capacity), &if_grow);
493 :
494 : // |microtask_queue| has an unused slot to store |microtask|.
495 : {
496 : StoreNoWriteBarrier(MachineType::PointerRepresentation(), ring_buffer,
497 112 : CalculateRingBufferOffset(capacity, start, size),
498 168 : BitcastTaggedToWord(microtask));
499 : StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue,
500 112 : IntPtrConstant(MicrotaskQueue::kSizeOffset),
501 112 : IntPtrAdd(size, IntPtrConstant(1)));
502 112 : Return(UndefinedConstant());
503 : }
504 :
505 : // |microtask_queue| has no space to store |microtask|. Fall back to C++
506 : // implementation to grow the buffer.
507 56 : BIND(&if_grow);
508 : {
509 : Node* isolate_constant =
510 112 : ExternalConstant(ExternalReference::isolate_address(isolate()));
511 : Node* function =
512 112 : ExternalConstant(ExternalReference::call_enqueue_microtask_function());
513 : CallCFunction(function, MachineType::AnyTagged(),
514 : std::make_pair(MachineType::Pointer(), isolate_constant),
515 : std::make_pair(MachineType::IntPtr(), microtask_queue),
516 56 : std::make_pair(MachineType::AnyTagged(), microtask));
517 112 : Return(UndefinedConstant());
518 : }
519 :
520 56 : Bind(&if_shutdown);
521 112 : Return(UndefinedConstant());
522 56 : }
523 :
524 280 : TF_BUILTIN(RunMicrotasks, MicrotaskQueueBuiltinsAssembler) {
525 : // Load the current context from the isolate.
526 56 : TNode<Context> current_context = GetCurrentContext();
527 :
528 : TNode<RawPtrT> microtask_queue =
529 56 : UncheckedCast<RawPtrT>(Parameter(Descriptor::kMicrotaskQueue));
530 :
531 56 : Label loop(this), done(this);
532 56 : Goto(&loop);
533 56 : BIND(&loop);
534 :
535 56 : TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue);
536 :
537 : // Exit if the queue is empty.
538 168 : GotoIf(WordEqual(size, IntPtrConstant(0)), &done);
539 :
540 56 : TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue);
541 56 : TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue);
542 56 : TNode<IntPtrT> start = GetMicrotaskQueueStart(microtask_queue);
543 :
544 : TNode<IntPtrT> offset =
545 56 : CalculateRingBufferOffset(capacity, start, IntPtrConstant(0));
546 : TNode<RawPtrT> microtask_pointer =
547 56 : UncheckedCast<RawPtrT>(Load(MachineType::Pointer(), ring_buffer, offset));
548 56 : TNode<Microtask> microtask = CAST(BitcastWordToTagged(microtask_pointer));
549 :
550 112 : TNode<IntPtrT> new_size = IntPtrSub(size, IntPtrConstant(1));
551 : TNode<IntPtrT> new_start = WordAnd(IntPtrAdd(start, IntPtrConstant(1)),
552 168 : IntPtrSub(capacity, IntPtrConstant(1)));
553 :
554 : // Remove |microtask| from |ring_buffer| before running it, since its
555 : // invocation may add another microtask into |ring_buffer|.
556 56 : SetMicrotaskQueueSize(microtask_queue, new_size);
557 56 : SetMicrotaskQueueStart(microtask_queue, new_start);
558 :
559 56 : RunSingleMicrotask(current_context, microtask);
560 56 : IncrementFinishedMicrotaskCount(microtask_queue);
561 56 : Goto(&loop);
562 :
563 56 : BIND(&done);
564 : {
565 : // Reset the "current microtask" on the isolate.
566 112 : StoreRoot(RootIndex::kCurrentMicrotask, UndefinedConstant());
567 112 : Return(UndefinedConstant());
568 : }
569 56 : }
570 :
571 : } // namespace internal
572 59456 : } // namespace v8
|