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