Commit 334510a9 authored by tzik's avatar tzik Committed by Commit Bot

Use handler's context on Promise resolution

V8 used to use the microtask context when it runs EnqueueJob
step 2.
> Let job settings be some appropriate environment settings object.
https://html.spec.whatwg.org/multipage/webappapis.html#enqueuejob(queuename,-job,-arguments)

However, it's being updated to use the handler's context.
https://github.com/whatwg/html/issues/1426#issuecomment-340071080

Change-Id: I24840a28ef2c903539fe4ace74ae59da290f5109
Reviewed-on: https://chromium-review.googlesource.com/c/1465902Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Commit-Queue: Taiju Tsuiki <tzik@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59870}
parent 399a6f53
......@@ -8543,11 +8543,18 @@ void Isolate::RunMicrotasks() {
isolate->default_microtask_queue()->RunMicrotasks(isolate);
}
void Isolate::EnqueueMicrotask(Local<Function> function) {
void Isolate::EnqueueMicrotask(Local<Function> v8_function) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
i::Handle<i::CallableTask> microtask = isolate->factory()->NewCallableTask(
Utils::OpenHandle(*function), isolate->native_context());
isolate->default_microtask_queue()->EnqueueMicrotask(*microtask);
i::Handle<i::JSReceiver> function = Utils::OpenHandle(*v8_function);
i::Handle<i::NativeContext> handler_context;
if (!i::JSReceiver::GetContextForMicrotask(function).ToHandle(
&handler_context))
handler_context = isolate->native_context();
i::Handle<i::CallableTask> microtask =
isolate->factory()->NewCallableTask(function, handler_context);
handler_context->microtask_queue()->EnqueueMicrotask(*microtask);
}
void Isolate::EnqueueMicrotask(MicrotaskCallback callback, void* data) {
......
......@@ -18,7 +18,9 @@
namespace v8 {
namespace internal {
using compiler::Node;
typedef compiler::Node Node;
template <class T>
using TNode = CodeStubAssembler::TNode<T>;
using IteratorRecord = IteratorBuiltinsAssembler::IteratorRecord;
Node* PromiseBuiltinsAssembler::AllocateJSPromise(Node* context) {
......@@ -112,6 +114,65 @@ PromiseBuiltinsAssembler::CreatePromiseResolvingFunctions(
return std::make_pair(resolve, reject);
}
void PromiseBuiltinsAssembler::ExtractHandlerContext(Node* handler,
Variable* var_context) {
VARIABLE(var_handler, MachineRepresentation::kTagged, handler);
Label loop(this, &var_handler), done(this);
Goto(&loop);
BIND(&loop);
{
Label if_bound_function(this), if_proxy(this), if_function(this);
GotoIf(TaggedIsSmi(var_handler.value()), &done);
int32_t case_values[] = {
JS_BOUND_FUNCTION_TYPE,
JS_PROXY_TYPE,
JS_FUNCTION_TYPE,
};
Label* case_labels[] = {
&if_bound_function,
&if_proxy,
&if_function,
};
static_assert(arraysize(case_values) == arraysize(case_labels), "");
TNode<Map> handler_map = LoadMap(var_handler.value());
TNode<Int32T> handler_type = LoadMapInstanceType(handler_map);
Switch(handler_type, &done, case_values, case_labels,
arraysize(case_labels));
BIND(&if_bound_function);
{
// Use the target function's context for JSBoundFunction.
var_handler.Bind(LoadObjectField(
var_handler.value(), JSBoundFunction::kBoundTargetFunctionOffset));
Goto(&loop);
}
BIND(&if_proxy);
{
// Use the target function's context for JSProxy.
// If the proxy is revoked, |var_handler| will be undefined and this
// function will return with unchanged |var_context|.
var_handler.Bind(
LoadObjectField(var_handler.value(), JSProxy::kTargetOffset));
Goto(&loop);
}
BIND(&if_function);
{
// Use the function's context.
Node* handler_context =
LoadObjectField(var_handler.value(), JSFunction::kContextOffset);
var_context->Bind(LoadNativeContext(CAST(handler_context)));
Goto(&done);
}
}
// If no valid context is available, |var_context| is unchanged and the caller
// will use a fallback context.
BIND(&done);
}
// ES #sec-newpromisecapability
TF_BUILTIN(NewPromiseCapability, PromiseBuiltinsAssembler) {
Node* const context = Parameter(Descriptor::kContext);
......@@ -378,13 +439,19 @@ void PromiseBuiltinsAssembler::PerformPromiseThen(
}
BIND(&enqueue);
Node* argument =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
Node* microtask = AllocatePromiseReactionJobTask(
var_map.value(), context, argument, var_handler.value(),
result_promise_or_capability);
CallBuiltin(Builtins::kEnqueueMicrotask, context, microtask);
Goto(&done);
{
VARIABLE(var_handler_context, MachineRepresentation::kTagged, context);
ExtractHandlerContext(var_handler.value(), &var_handler_context);
Node* argument =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
Node* microtask = AllocatePromiseReactionJobTask(
var_map.value(), var_handler_context.value(), argument,
var_handler.value(), result_promise_or_capability);
CallBuiltin(Builtins::kEnqueueMicrotask, var_handler_context.value(),
microtask);
Goto(&done);
}
}
BIND(&done);
......@@ -517,18 +584,23 @@ Node* PromiseBuiltinsAssembler::TriggerPromiseReactions(
GotoIf(TaggedIsSmi(current), &done_loop);
var_current.Bind(LoadObjectField(current, PromiseReaction::kNextOffset));
VARIABLE(var_context, MachineRepresentation::kTagged, context);
// Morph {current} from a PromiseReaction into a PromiseReactionJobTask
// and schedule that on the microtask queue. We try to minimize the number
// of stores here to avoid screwing up the store buffer.
STATIC_ASSERT(static_cast<int>(PromiseReaction::kSize) ==
static_cast<int>(PromiseReactionJobTask::kSize));
if (type == PromiseReaction::kFulfill) {
Node* handler =
LoadObjectField(current, PromiseReaction::kFulfillHandlerOffset);
ExtractHandlerContext(handler, &var_context);
StoreMapNoWriteBarrier(current,
RootIndex::kPromiseFulfillReactionJobTaskMap);
StoreObjectField(current, PromiseReactionJobTask::kArgumentOffset,
argument);
StoreObjectField(current, PromiseReactionJobTask::kContextOffset,
context);
var_context.value());
STATIC_ASSERT(
static_cast<int>(PromiseReaction::kFulfillHandlerOffset) ==
static_cast<int>(PromiseReactionJobTask::kHandlerOffset));
......@@ -539,12 +611,13 @@ Node* PromiseBuiltinsAssembler::TriggerPromiseReactions(
} else {
Node* handler =
LoadObjectField(current, PromiseReaction::kRejectHandlerOffset);
ExtractHandlerContext(handler, &var_context);
StoreMapNoWriteBarrier(current,
RootIndex::kPromiseRejectReactionJobTaskMap);
StoreObjectField(current, PromiseReactionJobTask::kArgumentOffset,
argument);
StoreObjectField(current, PromiseReactionJobTask::kContextOffset,
context);
var_context.value());
StoreObjectField(current, PromiseReactionJobTask::kHandlerOffset,
handler);
STATIC_ASSERT(
......@@ -552,7 +625,7 @@ Node* PromiseBuiltinsAssembler::TriggerPromiseReactions(
static_cast<int>(
PromiseReactionJobTask::kPromiseOrCapabilityOffset));
}
CallBuiltin(Builtins::kEnqueueMicrotask, context, current);
CallBuiltin(Builtins::kEnqueueMicrotask, var_context.value(), current);
Goto(&loop);
}
BIND(&done_loop);
......
......@@ -154,6 +154,8 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
void PromiseSetStatus(Node* promise, v8::Promise::PromiseState status);
Node* AllocateJSPromise(Node* context);
void ExtractHandlerContext(Node* handler, Variable* var_context);
};
} // namespace internal
......
......@@ -207,6 +207,12 @@ void MicrotaskQueue::FireMicrotasksCompletedCallback(Isolate* isolate) const {
}
}
Microtask MicrotaskQueue::get(intptr_t index) const {
DCHECK_LT(index, size_);
Object microtask(ring_buffer_[(index + start_) % capacity_]);
return Microtask::cast(microtask);
}
void MicrotaskQueue::OnCompleted(Isolate* isolate) {
// TODO(marja): (spec) The discussion about when to clear the KeepDuringJob
// set is still open (whether to clear it after every microtask or once
......
......@@ -84,6 +84,8 @@ class V8_EXPORT_PRIVATE MicrotaskQueue {
intptr_t size() const { return size_; }
intptr_t start() const { return start_; }
Microtask get(intptr_t index) const;
MicrotaskQueue* next() const { return next_; }
MicrotaskQueue* prev() const { return prev_; }
......
......@@ -6030,15 +6030,25 @@ Handle<Object> JSPromise::TriggerPromiseReactions(Isolate* isolate,
Handle<PromiseReaction> reaction = Handle<PromiseReaction>::cast(task);
reactions = handle(reaction->next(), isolate);
Handle<NativeContext> handler_context;
STATIC_ASSERT(static_cast<int>(PromiseReaction::kSize) ==
static_cast<int>(PromiseReactionJobTask::kSize));
if (type == PromiseReaction::kFulfill) {
Handle<HeapObject> handler = handle(reaction->fulfill_handler(), isolate);
if (handler->IsJSReceiver()) {
JSReceiver::GetContextForMicrotask(Handle<JSReceiver>::cast(handler))
.ToHandle(&handler_context);
}
if (handler_context.is_null())
handler_context = isolate->native_context();
task->synchronized_set_map(
ReadOnlyRoots(isolate).promise_fulfill_reaction_job_task_map());
Handle<PromiseFulfillReactionJobTask>::cast(task)->set_argument(
*argument);
Handle<PromiseFulfillReactionJobTask>::cast(task)->set_context(
*isolate->native_context());
*handler_context);
STATIC_ASSERT(
static_cast<int>(PromiseReaction::kFulfillHandlerOffset) ==
static_cast<int>(PromiseFulfillReactionJobTask::kHandlerOffset));
......@@ -6048,20 +6058,26 @@ Handle<Object> JSPromise::TriggerPromiseReactions(Isolate* isolate,
PromiseFulfillReactionJobTask::kPromiseOrCapabilityOffset));
} else {
DisallowHeapAllocation no_gc;
HeapObject handler = reaction->reject_handler();
Handle<HeapObject> handler = handle(reaction->reject_handler(), isolate);
if (handler->IsJSReceiver()) {
JSReceiver::GetContextForMicrotask(Handle<JSReceiver>::cast(handler))
.ToHandle(&handler_context);
}
if (handler_context.is_null())
handler_context = isolate->native_context();
task->synchronized_set_map(
ReadOnlyRoots(isolate).promise_reject_reaction_job_task_map());
Handle<PromiseRejectReactionJobTask>::cast(task)->set_argument(*argument);
Handle<PromiseRejectReactionJobTask>::cast(task)->set_context(
*isolate->native_context());
Handle<PromiseRejectReactionJobTask>::cast(task)->set_handler(handler);
*handler_context);
Handle<PromiseRejectReactionJobTask>::cast(task)->set_handler(*handler);
STATIC_ASSERT(
static_cast<int>(PromiseReaction::kPromiseOrCapabilityOffset) ==
static_cast<int>(
PromiseRejectReactionJobTask::kPromiseOrCapabilityOffset));
}
isolate->native_context()->microtask_queue()->EnqueueMicrotask(
handler_context->microtask_queue()->EnqueueMicrotask(
*Handle<PromiseReactionJobTask>::cast(task));
}
......
......@@ -522,6 +522,27 @@ MaybeHandle<NativeContext> JSReceiver::GetFunctionRealm(
return JSObject::GetFunctionRealm(Handle<JSObject>::cast(receiver));
}
// static
MaybeHandle<NativeContext> JSReceiver::GetContextForMicrotask(
Handle<JSReceiver> receiver) {
Isolate* isolate = receiver->GetIsolate();
while (receiver->IsJSBoundFunction() || receiver->IsJSProxy()) {
if (receiver->IsJSBoundFunction()) {
receiver = handle(
Handle<JSBoundFunction>::cast(receiver)->bound_target_function(),
isolate);
} else {
DCHECK(receiver->IsJSProxy());
Handle<Object> target(Handle<JSProxy>::cast(receiver)->target(), isolate);
if (!target->IsJSReceiver()) return MaybeHandle<NativeContext>();
receiver = Handle<JSReceiver>::cast(target);
}
}
if (!receiver->IsJSFunction()) return MaybeHandle<NativeContext>();
return handle(Handle<JSFunction>::cast(receiver)->native_context(), isolate);
}
Maybe<PropertyAttributes> JSReceiver::GetPropertyAttributes(
LookupIterator* it) {
for (; it->IsFound(); it->Next()) {
......
......@@ -19,6 +19,7 @@ namespace internal {
enum InstanceType : uint16_t;
class JSGlobalObject;
class JSGlobalProxy;
class NativeContext;
// JSReceiver includes types on which properties can be defined, i.e.,
// JSObject and JSProxy.
......@@ -79,6 +80,8 @@ class JSReceiver : public HeapObject {
static MaybeHandle<NativeContext> GetFunctionRealm(
Handle<JSReceiver> receiver);
V8_EXPORT_PRIVATE static MaybeHandle<NativeContext> GetContextForMicrotask(
Handle<JSReceiver> receiver);
// Get the first non-hidden prototype.
static inline MaybeHandle<Object> GetPrototype(Isolate* isolate,
......
......@@ -76,9 +76,10 @@ RUNTIME_FUNCTION(Runtime_EnqueueMicrotask) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0);
Handle<CallableTask> microtask =
isolate->factory()->NewCallableTask(function, isolate->native_context());
isolate->native_context()->microtask_queue()->EnqueueMicrotask(*microtask);
Handle<CallableTask> microtask = isolate->factory()->NewCallableTask(
function, handle(function->native_context(), isolate));
function->native_context()->microtask_queue()->EnqueueMicrotask(*microtask);
return ReadOnlyRoots(isolate).undefined_value();
}
......
......@@ -11,6 +11,7 @@
#include "src/heap/factory.h"
#include "src/objects/foreign.h"
#include "src/objects/promise-inl.h"
#include "src/visitors.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -185,5 +186,99 @@ TEST_F(MicrotaskQueueTest, VisitRoot) {
EXPECT_EQ(expected, actual);
}
TEST_F(MicrotaskQueueTest, PromiseHandlerContext) {
Local<v8::Context> v8_context2 = v8::Context::New(v8_isolate());
Local<v8::Context> v8_context3 = v8::Context::New(v8_isolate());
Local<v8::Context> v8_context4 = v8::Context::New(v8_isolate());
Handle<Context> context2 = Utils::OpenHandle(*v8_context2, isolate());
Handle<Context> context3 = Utils::OpenHandle(*v8_context3, isolate());
Handle<Context> context4 = Utils::OpenHandle(*v8_context3, isolate());
context2->native_context()->set_microtask_queue(microtask_queue());
context3->native_context()->set_microtask_queue(microtask_queue());
context4->native_context()->set_microtask_queue(microtask_queue());
Handle<JSFunction> handler;
Handle<JSProxy> proxy;
Handle<JSProxy> revoked_proxy;
Handle<JSBoundFunction> bound;
// Create a JSFunction on |context2|
{
v8::Context::Scope scope(v8_context2);
handler = RunJS<JSFunction>("()=>{}");
EXPECT_EQ(*context2,
*JSReceiver::GetContextForMicrotask(handler).ToHandleChecked());
}
// Create a JSProxy on |context3|.
{
v8::Context::Scope scope(v8_context3);
ASSERT_TRUE(
v8_context3->Global()
->Set(v8_context3, NewString("handler"), Utils::ToLocal(handler))
.FromJust());
proxy = RunJS<JSProxy>("new Proxy(handler, {})");
revoked_proxy = RunJS<JSProxy>(
"let {proxy, revoke} = Proxy.revocable(handler, {});"
"revoke();"
"proxy");
EXPECT_EQ(*context2,
*JSReceiver::GetContextForMicrotask(proxy).ToHandleChecked());
EXPECT_TRUE(JSReceiver::GetContextForMicrotask(revoked_proxy).is_null());
}
// Create a JSBoundFunction on |context4|.
// Note that its CreationContext and ContextForTaskCancellation is |context2|.
{
v8::Context::Scope scope(v8_context4);
ASSERT_TRUE(
v8_context4->Global()
->Set(v8_context4, NewString("handler"), Utils::ToLocal(handler))
.FromJust());
bound = RunJS<JSBoundFunction>("handler.bind()");
EXPECT_EQ(*context2,
*JSReceiver::GetContextForMicrotask(bound).ToHandleChecked());
}
// Give the objects to the main context.
SetGlobalProperty("handler", Utils::ToLocal(handler));
SetGlobalProperty("proxy", Utils::ToLocal(proxy));
SetGlobalProperty("revoked_proxy", Utils::ToLocal(revoked_proxy));
SetGlobalProperty("bound", Utils::ToLocal(Handle<JSReceiver>::cast(bound)));
RunJS(
"Promise.resolve().then(handler);"
"Promise.reject().catch(proxy);"
"Promise.resolve().then(revoked_proxy);"
"Promise.resolve().then(bound);");
ASSERT_EQ(4, microtask_queue()->size());
Handle<Microtask> microtask1(microtask_queue()->get(0), isolate());
ASSERT_TRUE(microtask1->IsPromiseFulfillReactionJobTask());
EXPECT_EQ(*context2,
Handle<PromiseFulfillReactionJobTask>::cast(microtask1)->context());
Handle<Microtask> microtask2(microtask_queue()->get(1), isolate());
ASSERT_TRUE(microtask2->IsPromiseRejectReactionJobTask());
EXPECT_EQ(*context2,
Handle<PromiseRejectReactionJobTask>::cast(microtask2)->context());
Handle<Microtask> microtask3(microtask_queue()->get(2), isolate());
ASSERT_TRUE(microtask3->IsPromiseFulfillReactionJobTask());
// |microtask3| corresponds to a PromiseReaction for |revoked_proxy|.
// As |revoked_proxy| doesn't have a context, the current context should be
// used as the fallback context.
EXPECT_EQ(*native_context(),
Handle<PromiseFulfillReactionJobTask>::cast(microtask3)->context());
Handle<Microtask> microtask4(microtask_queue()->get(3), isolate());
ASSERT_TRUE(microtask4->IsPromiseFulfillReactionJobTask());
EXPECT_EQ(*context2,
Handle<PromiseFulfillReactionJobTask>::cast(microtask4)->context());
v8_context4->DetachGlobal();
v8_context3->DetachGlobal();
v8_context2->DetachGlobal();
}
} // namespace internal
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment