blob: 9f16186d13b8e95c49f223320f0d94ac74eb89f0 [file] [log] [blame]
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/api/api.h"
#include "src/builtins/builtins-utils-gen.h"
#include "src/codegen/code-stub-assembler.h"
#include "src/execution/microtask-queue.h"
#include "src/objects/js-weak-refs.h"
#include "src/objects/microtask-inl.h"
#include "src/objects/promise.h"
#include "src/objects/smi-inl.h"
namespace v8 {
namespace internal {
using compiler::ScopedExceptionHandler;
class MicrotaskQueueBuiltinsAssembler : public CodeStubAssembler {
public:
explicit MicrotaskQueueBuiltinsAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
TNode<RawPtrT> GetMicrotaskQueue(TNode<Context> context);
TNode<RawPtrT> GetMicrotaskRingBuffer(TNode<RawPtrT> microtask_queue);
TNode<IntPtrT> GetMicrotaskQueueCapacity(TNode<RawPtrT> microtask_queue);
TNode<IntPtrT> GetMicrotaskQueueSize(TNode<RawPtrT> microtask_queue);
void SetMicrotaskQueueSize(TNode<RawPtrT> microtask_queue,
TNode<IntPtrT> new_size);
TNode<IntPtrT> GetMicrotaskQueueStart(TNode<RawPtrT> microtask_queue);
void SetMicrotaskQueueStart(TNode<RawPtrT> microtask_queue,
TNode<IntPtrT> new_start);
TNode<IntPtrT> CalculateRingBufferOffset(TNode<IntPtrT> capacity,
TNode<IntPtrT> start,
TNode<IntPtrT> index);
void PrepareForContext(TNode<Context> microtask_context, Label* bailout);
void RunSingleMicrotask(TNode<Context> current_context,
TNode<Microtask> microtask);
void IncrementFinishedMicrotaskCount(TNode<RawPtrT> microtask_queue);
TNode<Context> GetCurrentContext();
void SetCurrentContext(TNode<Context> context);
TNode<IntPtrT> GetEnteredContextCount();
void EnterMicrotaskContext(TNode<Context> native_context);
void RewindEnteredContext(TNode<IntPtrT> saved_entered_context_count);
void RunPromiseHook(Runtime::FunctionId id, TNode<Context> context,
TNode<HeapObject> promise_or_capability);
};
TNode<RawPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueue(
TNode<Context> native_context) {
CSA_ASSERT(this, IsNativeContext(native_context));
return LoadExternalPointerFromObject(native_context,
NativeContext::kMicrotaskQueueOffset,
kNativeContextMicrotaskQueueTag);
}
TNode<RawPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskRingBuffer(
TNode<RawPtrT> microtask_queue) {
return Load<RawPtrT>(microtask_queue,
IntPtrConstant(MicrotaskQueue::kRingBufferOffset));
}
TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueCapacity(
TNode<RawPtrT> microtask_queue) {
return Load<IntPtrT>(microtask_queue,
IntPtrConstant(MicrotaskQueue::kCapacityOffset));
}
TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueSize(
TNode<RawPtrT> microtask_queue) {
return Load<IntPtrT>(microtask_queue,
IntPtrConstant(MicrotaskQueue::kSizeOffset));
}
void MicrotaskQueueBuiltinsAssembler::SetMicrotaskQueueSize(
TNode<RawPtrT> microtask_queue, TNode<IntPtrT> new_size) {
StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue,
IntPtrConstant(MicrotaskQueue::kSizeOffset), new_size);
}
TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetMicrotaskQueueStart(
TNode<RawPtrT> microtask_queue) {
return Load<IntPtrT>(microtask_queue,
IntPtrConstant(MicrotaskQueue::kStartOffset));
}
void MicrotaskQueueBuiltinsAssembler::SetMicrotaskQueueStart(
TNode<RawPtrT> microtask_queue, TNode<IntPtrT> new_start) {
StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue,
IntPtrConstant(MicrotaskQueue::kStartOffset), new_start);
}
TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::CalculateRingBufferOffset(
TNode<IntPtrT> capacity, TNode<IntPtrT> start, TNode<IntPtrT> index) {
return TimesSystemPointerSize(
WordAnd(IntPtrAdd(start, index), IntPtrSub(capacity, IntPtrConstant(1))));
}
void MicrotaskQueueBuiltinsAssembler::PrepareForContext(
TNode<Context> native_context, Label* bailout) {
CSA_ASSERT(this, IsNativeContext(native_context));
// Skip the microtask execution if the associated context is shutdown.
GotoIf(WordEqual(GetMicrotaskQueue(native_context), IntPtrConstant(0)),
bailout);
EnterMicrotaskContext(native_context);
SetCurrentContext(native_context);
}
void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask(
TNode<Context> current_context, TNode<Microtask> microtask) {
CSA_ASSERT(this, TaggedIsNotSmi(microtask));
StoreRoot(RootIndex::kCurrentMicrotask, microtask);
TNode<IntPtrT> saved_entered_context_count = GetEnteredContextCount();
TNode<Map> microtask_map = LoadMap(microtask);
TNode<Uint16T> microtask_type = LoadMapInstanceType(microtask_map);
TVARIABLE(Object, var_exception);
Label if_exception(this, Label::kDeferred);
Label is_callable(this), is_callback(this),
is_promise_fulfill_reaction_job(this),
is_promise_reject_reaction_job(this),
is_promise_resolve_thenable_job(this),
is_unreachable(this, Label::kDeferred), done(this);
int32_t case_values[] = {CALLABLE_TASK_TYPE, CALLBACK_TASK_TYPE,
PROMISE_FULFILL_REACTION_JOB_TASK_TYPE,
PROMISE_REJECT_REACTION_JOB_TASK_TYPE,
PROMISE_RESOLVE_THENABLE_JOB_TASK_TYPE};
Label* case_labels[] = {
&is_callable, &is_callback, &is_promise_fulfill_reaction_job,
&is_promise_reject_reaction_job, &is_promise_resolve_thenable_job};
static_assert(arraysize(case_values) == arraysize(case_labels), "");
Switch(microtask_type, &is_unreachable, case_values, case_labels,
arraysize(case_labels));
BIND(&is_callable);
{
// Enter the context of the {microtask}.
TNode<Context> microtask_context =
LoadObjectField<Context>(microtask, CallableTask::kContextOffset);
TNode<NativeContext> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context, &done);
TNode<JSReceiver> callable =
LoadObjectField<JSReceiver>(microtask, CallableTask::kCallableOffset);
{
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
Call(microtask_context, callable, UndefinedConstant());
}
RewindEnteredContext(saved_entered_context_count);
SetCurrentContext(current_context);
Goto(&done);
}
BIND(&is_callback);
{
const TNode<Object> microtask_callback =
LoadObjectField(microtask, CallbackTask::kCallbackOffset);
const TNode<Object> microtask_data =
LoadObjectField(microtask, CallbackTask::kDataOffset);
// If this turns out to become a bottleneck because of the calls
// to C++ via CEntry, we can choose to speed them up using a
// similar mechanism that we use for the CallApiFunction stub,
// except that calling the MicrotaskCallback is even easier, since
// it doesn't accept any tagged parameters, doesn't return a value
// and ignores exceptions.
//
// But from our current measurements it doesn't seem to be a
// serious performance problem, even if the microtask is full
// of CallHandlerTasks (which is not a realistic use case anyways).
{
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
CallRuntime(Runtime::kRunMicrotaskCallback, current_context,
microtask_callback, microtask_data);
}
Goto(&done);
}
BIND(&is_promise_resolve_thenable_job);
{
// Enter the context of the {microtask}.
TNode<Context> microtask_context = LoadObjectField<Context>(
microtask, PromiseResolveThenableJobTask::kContextOffset);
TNode<NativeContext> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context, &done);
const TNode<Object> promise_to_resolve = LoadObjectField(
microtask, PromiseResolveThenableJobTask::kPromiseToResolveOffset);
const TNode<Object> then =
LoadObjectField(microtask, PromiseResolveThenableJobTask::kThenOffset);
const TNode<Object> thenable = LoadObjectField(
microtask, PromiseResolveThenableJobTask::kThenableOffset);
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
CAST(promise_to_resolve));
{
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
CallBuiltin(Builtins::kPromiseResolveThenableJob, native_context,
promise_to_resolve, thenable, then);
}
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
CAST(promise_to_resolve));
RewindEnteredContext(saved_entered_context_count);
SetCurrentContext(current_context);
Goto(&done);
}
BIND(&is_promise_fulfill_reaction_job);
{
// Enter the context of the {microtask}.
TNode<Context> microtask_context = LoadObjectField<Context>(
microtask, PromiseReactionJobTask::kContextOffset);
TNode<NativeContext> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context, &done);
const TNode<Object> argument =
LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
const TNode<Object> job_handler =
LoadObjectField(microtask, PromiseReactionJobTask::kHandlerOffset);
const TNode<HeapObject> promise_or_capability = CAST(LoadObjectField(
microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset));
TNode<Object> preserved_embedder_data = LoadObjectField(
microtask,
PromiseReactionJobTask::kContinuationPreservedEmbedderDataOffset);
Label preserved_data_done(this);
GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_done);
StoreContextElement(native_context,
Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX,
preserved_embedder_data);
Goto(&preserved_data_done);
BIND(&preserved_data_done);
// Run the promise before/debug hook if enabled.
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
promise_or_capability);
{
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
CallBuiltin(Builtins::kPromiseFulfillReactionJob, microtask_context,
argument, job_handler, promise_or_capability);
}
// Run the promise after/debug hook if enabled.
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
promise_or_capability);
Label preserved_data_reset_done(this);
GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done);
StoreContextElement(native_context,
Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX,
UndefinedConstant());
Goto(&preserved_data_reset_done);
BIND(&preserved_data_reset_done);
RewindEnteredContext(saved_entered_context_count);
SetCurrentContext(current_context);
Goto(&done);
}
BIND(&is_promise_reject_reaction_job);
{
// Enter the context of the {microtask}.
TNode<Context> microtask_context = LoadObjectField<Context>(
microtask, PromiseReactionJobTask::kContextOffset);
TNode<NativeContext> native_context = LoadNativeContext(microtask_context);
PrepareForContext(native_context, &done);
const TNode<Object> argument =
LoadObjectField(microtask, PromiseReactionJobTask::kArgumentOffset);
const TNode<Object> job_handler =
LoadObjectField(microtask, PromiseReactionJobTask::kHandlerOffset);
const TNode<HeapObject> promise_or_capability = CAST(LoadObjectField(
microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset));
TNode<Object> preserved_embedder_data = LoadObjectField(
microtask,
PromiseReactionJobTask::kContinuationPreservedEmbedderDataOffset);
Label preserved_data_done(this);
GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_done);
StoreContextElement(native_context,
Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX,
preserved_embedder_data);
Goto(&preserved_data_done);
BIND(&preserved_data_done);
// Run the promise before/debug hook if enabled.
RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context,
promise_or_capability);
{
ScopedExceptionHandler handler(this, &if_exception, &var_exception);
CallBuiltin(Builtins::kPromiseRejectReactionJob, microtask_context,
argument, job_handler, promise_or_capability);
}
// Run the promise after/debug hook if enabled.
RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context,
promise_or_capability);
Label preserved_data_reset_done(this);
GotoIf(IsUndefined(preserved_embedder_data), &preserved_data_reset_done);
StoreContextElement(native_context,
Context::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX,
UndefinedConstant());
Goto(&preserved_data_reset_done);
BIND(&preserved_data_reset_done);
RewindEnteredContext(saved_entered_context_count);
SetCurrentContext(current_context);
Goto(&done);
}
BIND(&is_unreachable);
Unreachable();
BIND(&if_exception);
{
// Report unhandled exceptions from microtasks.
CallRuntime(Runtime::kReportMessageFromMicrotask, current_context,
var_exception.value());
RewindEnteredContext(saved_entered_context_count);
SetCurrentContext(current_context);
Goto(&done);
}
BIND(&done);
}
void MicrotaskQueueBuiltinsAssembler::IncrementFinishedMicrotaskCount(
TNode<RawPtrT> microtask_queue) {
TNode<IntPtrT> count = Load<IntPtrT>(
microtask_queue,
IntPtrConstant(MicrotaskQueue::kFinishedMicrotaskCountOffset));
TNode<IntPtrT> new_count = IntPtrAdd(count, IntPtrConstant(1));
StoreNoWriteBarrier(
MachineType::PointerRepresentation(), microtask_queue,
IntPtrConstant(MicrotaskQueue::kFinishedMicrotaskCountOffset), new_count);
}
TNode<Context> MicrotaskQueueBuiltinsAssembler::GetCurrentContext() {
auto ref = ExternalReference::Create(kContextAddress, isolate());
// TODO(delphick): Add a checked cast. For now this is not possible as context
// can actually be Smi(0).
return TNode<Context>::UncheckedCast(LoadFullTagged(ExternalConstant(ref)));
}
void MicrotaskQueueBuiltinsAssembler::SetCurrentContext(
TNode<Context> context) {
auto ref = ExternalReference::Create(kContextAddress, isolate());
StoreFullTaggedNoWriteBarrier(ExternalConstant(ref), context);
}
TNode<IntPtrT> MicrotaskQueueBuiltinsAssembler::GetEnteredContextCount() {
auto ref = ExternalReference::handle_scope_implementer_address(isolate());
TNode<RawPtrT> hsi = Load<RawPtrT>(ExternalConstant(ref));
using ContextStack = DetachableVector<Context>;
TNode<IntPtrT> size_offset =
IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
ContextStack::kSizeOffset);
return Load<IntPtrT>(hsi, size_offset);
}
void MicrotaskQueueBuiltinsAssembler::EnterMicrotaskContext(
TNode<Context> native_context) {
CSA_ASSERT(this, IsNativeContext(native_context));
auto ref = ExternalReference::handle_scope_implementer_address(isolate());
TNode<RawPtrT> hsi = Load<RawPtrT>(ExternalConstant(ref));
using ContextStack = DetachableVector<Context>;
TNode<IntPtrT> capacity_offset =
IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
ContextStack::kCapacityOffset);
TNode<IntPtrT> size_offset =
IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
ContextStack::kSizeOffset);
TNode<IntPtrT> capacity = Load<IntPtrT>(hsi, capacity_offset);
TNode<IntPtrT> size = Load<IntPtrT>(hsi, size_offset);
Label if_append(this), if_grow(this, Label::kDeferred), done(this);
Branch(WordEqual(size, capacity), &if_grow, &if_append);
BIND(&if_append);
{
TNode<IntPtrT> data_offset =
IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
ContextStack::kDataOffset);
TNode<RawPtrT> data = Load<RawPtrT>(hsi, data_offset);
StoreFullTaggedNoWriteBarrier(data, TimesSystemPointerSize(size),
native_context);
TNode<IntPtrT> new_size = IntPtrAdd(size, IntPtrConstant(1));
StoreNoWriteBarrier(MachineType::PointerRepresentation(), hsi, size_offset,
new_size);
using FlagStack = DetachableVector<int8_t>;
TNode<IntPtrT> flag_data_offset =
IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset +
FlagStack::kDataOffset);
TNode<RawPtrT> flag_data = Load<RawPtrT>(hsi, flag_data_offset);
StoreNoWriteBarrier(MachineRepresentation::kWord8, flag_data, size,
BoolConstant(true));
StoreNoWriteBarrier(
MachineType::PointerRepresentation(), hsi,
IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset +
FlagStack::kSizeOffset),
new_size);
Goto(&done);
}
BIND(&if_grow);
{
TNode<ExternalReference> function =
ExternalConstant(ExternalReference::call_enter_context_function());
CallCFunction(function, MachineType::Int32(),
std::make_pair(MachineType::Pointer(), hsi),
std::make_pair(MachineType::Pointer(),
BitcastTaggedToWord(native_context)));
Goto(&done);
}
BIND(&done);
}
void MicrotaskQueueBuiltinsAssembler::RewindEnteredContext(
TNode<IntPtrT> saved_entered_context_count) {
auto ref = ExternalReference::handle_scope_implementer_address(isolate());
TNode<RawPtrT> hsi = Load<RawPtrT>(ExternalConstant(ref));
using ContextStack = DetachableVector<Context>;
TNode<IntPtrT> size_offset =
IntPtrConstant(HandleScopeImplementer::kEnteredContextsOffset +
ContextStack::kSizeOffset);
#ifdef ENABLE_VERIFY_CSA
{
TNode<IntPtrT> size = Load<IntPtrT>(hsi, size_offset);
CSA_CHECK(this, IntPtrLessThan(IntPtrConstant(0), size));
CSA_CHECK(this, IntPtrLessThanOrEqual(saved_entered_context_count, size));
}
#endif
StoreNoWriteBarrier(MachineType::PointerRepresentation(), hsi, size_offset,
saved_entered_context_count);
using FlagStack = DetachableVector<int8_t>;
StoreNoWriteBarrier(
MachineType::PointerRepresentation(), hsi,
IntPtrConstant(HandleScopeImplementer::kIsMicrotaskContextOffset +
FlagStack::kSizeOffset),
saved_entered_context_count);
}
void MicrotaskQueueBuiltinsAssembler::RunPromiseHook(
Runtime::FunctionId id, TNode<Context> context,
TNode<HeapObject> promise_or_capability) {
Label hook(this, Label::kDeferred), done_hook(this);
Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), &hook,
&done_hook);
BIND(&hook);
{
// Get to the underlying JSPromise instance.
TNode<HeapObject> promise = Select<HeapObject>(
IsPromiseCapability(promise_or_capability),
[=] {
return CAST(LoadObjectField(promise_or_capability,
PromiseCapability::kPromiseOffset));
},
[=] { return promise_or_capability; });
GotoIf(IsUndefined(promise), &done_hook);
CallRuntime(id, context, promise);
Goto(&done_hook);
}
BIND(&done_hook);
}
TF_BUILTIN(EnqueueMicrotask, MicrotaskQueueBuiltinsAssembler) {
auto microtask = Parameter<Microtask>(Descriptor::kMicrotask);
auto context = Parameter<Context>(Descriptor::kContext);
TNode<NativeContext> native_context = LoadNativeContext(context);
TNode<RawPtrT> microtask_queue = GetMicrotaskQueue(native_context);
// Do not store the microtask if MicrotaskQueue is not available, that may
// happen when the context shutdown.
Label if_shutdown(this, Label::kDeferred);
GotoIf(WordEqual(microtask_queue, IntPtrConstant(0)), &if_shutdown);
TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue);
TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue);
TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue);
TNode<IntPtrT> start = GetMicrotaskQueueStart(microtask_queue);
Label if_grow(this, Label::kDeferred);
GotoIf(IntPtrEqual(size, capacity), &if_grow);
// |microtask_queue| has an unused slot to store |microtask|.
{
StoreNoWriteBarrier(MachineType::PointerRepresentation(), ring_buffer,
CalculateRingBufferOffset(capacity, start, size),
BitcastTaggedToWord(microtask));
StoreNoWriteBarrier(MachineType::PointerRepresentation(), microtask_queue,
IntPtrConstant(MicrotaskQueue::kSizeOffset),
IntPtrAdd(size, IntPtrConstant(1)));
Return(UndefinedConstant());
}
// |microtask_queue| has no space to store |microtask|. Fall back to C++
// implementation to grow the buffer.
BIND(&if_grow);
{
TNode<ExternalReference> isolate_constant =
ExternalConstant(ExternalReference::isolate_address(isolate()));
TNode<ExternalReference> function =
ExternalConstant(ExternalReference::call_enqueue_microtask_function());
CallCFunction(function, MachineType::AnyTagged(),
std::make_pair(MachineType::Pointer(), isolate_constant),
std::make_pair(MachineType::IntPtr(), microtask_queue),
std::make_pair(MachineType::AnyTagged(), microtask));
Return(UndefinedConstant());
}
Bind(&if_shutdown);
Return(UndefinedConstant());
}
TF_BUILTIN(RunMicrotasks, MicrotaskQueueBuiltinsAssembler) {
// Load the current context from the isolate.
TNode<Context> current_context = GetCurrentContext();
auto microtask_queue =
UncheckedParameter<RawPtrT>(Descriptor::kMicrotaskQueue);
Label loop(this), done(this);
Goto(&loop);
BIND(&loop);
TNode<IntPtrT> size = GetMicrotaskQueueSize(microtask_queue);
// Exit if the queue is empty.
GotoIf(WordEqual(size, IntPtrConstant(0)), &done);
TNode<RawPtrT> ring_buffer = GetMicrotaskRingBuffer(microtask_queue);
TNode<IntPtrT> capacity = GetMicrotaskQueueCapacity(microtask_queue);
TNode<IntPtrT> start = GetMicrotaskQueueStart(microtask_queue);
TNode<IntPtrT> offset =
CalculateRingBufferOffset(capacity, start, IntPtrConstant(0));
TNode<RawPtrT> microtask_pointer = Load<RawPtrT>(ring_buffer, offset);
TNode<Microtask> microtask = CAST(BitcastWordToTagged(microtask_pointer));
TNode<IntPtrT> new_size = IntPtrSub(size, IntPtrConstant(1));
TNode<IntPtrT> new_start = WordAnd(IntPtrAdd(start, IntPtrConstant(1)),
IntPtrSub(capacity, IntPtrConstant(1)));
// Remove |microtask| from |ring_buffer| before running it, since its
// invocation may add another microtask into |ring_buffer|.
SetMicrotaskQueueSize(microtask_queue, new_size);
SetMicrotaskQueueStart(microtask_queue, new_start);
RunSingleMicrotask(current_context, microtask);
IncrementFinishedMicrotaskCount(microtask_queue);
Goto(&loop);
BIND(&done);
{
// Reset the "current microtask" on the isolate.
StoreRoot(RootIndex::kCurrentMicrotask, UndefinedConstant());
Return(UndefinedConstant());
}
}
} // namespace internal
} // namespace v8