Commit 8a677a28 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[builtins] Squeeze JSPromise::result and JSPromise::reactions into a single field.

A given JSPromise can either be in pending state, and accumulates
reactions, or in settled state, where all reactions are scheduled
as microtasks, and it carries a result. So we can use a single field
on the JSPromise instance to hold both the result and the reactions
and that field is interpreted differently depending on the status of
the JSPromise.

Bug: v8:7253
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_chromium_rel_ng
Change-Id: I19a7d499c88f452f0d35979ab95deb110021cde9
Reviewed-on: https://chromium-review.googlesource.com/895528Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51004}
parent 96dd41a3
...@@ -22,21 +22,26 @@ Node* PromiseBuiltinsAssembler::AllocateJSPromise(Node* context) { ...@@ -22,21 +22,26 @@ Node* PromiseBuiltinsAssembler::AllocateJSPromise(Node* context) {
Node* const promise_fun = Node* const promise_fun =
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX); LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
CSA_ASSERT(this, IsFunctionWithPrototypeSlotMap(LoadMap(promise_fun))); CSA_ASSERT(this, IsFunctionWithPrototypeSlotMap(LoadMap(promise_fun)));
Node* const initial_map = Node* const promise_map =
LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset); LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
Node* const instance = AllocateJSObjectFromMap(initial_map); Node* const promise = Allocate(JSPromise::kSizeWithEmbedderFields);
return instance; StoreMapNoWriteBarrier(promise, promise_map);
StoreObjectFieldRoot(promise, JSPromise::kPropertiesOrHashOffset,
Heap::kEmptyFixedArrayRootIndex);
StoreObjectFieldRoot(promise, JSPromise::kElementsOffset,
Heap::kEmptyFixedArrayRootIndex);
return promise;
} }
void PromiseBuiltinsAssembler::PromiseInit(Node* promise) { void PromiseBuiltinsAssembler::PromiseInit(Node* promise) {
STATIC_ASSERT(v8::Promise::kPending == 0); STATIC_ASSERT(v8::Promise::kPending == 0);
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kReactionsOffset, StoreObjectFieldNoWriteBarrier(promise, JSPromise::kReactionsOrResultOffset,
SmiConstant(Smi::kZero)); SmiConstant(Smi::kZero));
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset,
SmiConstant(Smi::kZero)); SmiConstant(Smi::kZero));
for (int i = 0; i < v8::Promise::kEmbedderFieldCount; i++) { for (int i = 0; i < v8::Promise::kEmbedderFieldCount; i++) {
int offset = JSPromise::kSize + i * kPointerSize; int offset = JSPromise::kSize + i * kPointerSize;
StoreObjectFieldNoWriteBarrier(promise, offset, SmiConstant(0)); StoreObjectFieldNoWriteBarrier(promise, offset, SmiConstant(Smi::kZero));
} }
} }
...@@ -60,12 +65,12 @@ Node* PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Node* context, ...@@ -60,12 +65,12 @@ Node* PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Node* context,
Node* PromiseBuiltinsAssembler::AllocateAndSetJSPromise( Node* PromiseBuiltinsAssembler::AllocateAndSetJSPromise(
Node* context, v8::Promise::PromiseState status, Node* result) { Node* context, v8::Promise::PromiseState status, Node* result) {
Node* const instance = AllocateJSPromise(context); DCHECK_NE(Promise::kPending, status);
StoreObjectFieldNoWriteBarrier(instance, JSPromise::kResultOffset, result); Node* const instance = AllocateJSPromise(context);
StoreObjectFieldNoWriteBarrier(instance, JSPromise::kReactionsOrResultOffset,
result);
STATIC_ASSERT(JSPromise::kStatusShift == 0); STATIC_ASSERT(JSPromise::kStatusShift == 0);
StoreObjectFieldNoWriteBarrier(instance, JSPromise::kReactionsOffset,
SmiConstant(Smi::kZero));
StoreObjectFieldNoWriteBarrier(instance, JSPromise::kFlagsOffset, StoreObjectFieldNoWriteBarrier(instance, JSPromise::kFlagsOffset,
SmiConstant(status)); SmiConstant(status));
for (int i = 0; i < v8::Promise::kEmbedderFieldCount; i++) { for (int i = 0; i < v8::Promise::kEmbedderFieldCount; i++) {
...@@ -393,11 +398,11 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context, ...@@ -393,11 +398,11 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
// Once the {promise} is resolved we decide on the concrete handler to // Once the {promise} is resolved we decide on the concrete handler to
// push onto the microtask queue. // push onto the microtask queue.
Node* const promise_reactions = Node* const promise_reactions =
LoadObjectField(promise, JSPromise::kReactionsOffset); LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
Node* const reaction = AllocatePromiseReaction(promise_reactions, result, Node* const reaction = AllocatePromiseReaction(promise_reactions, result,
var_on_fulfilled.value(), var_on_fulfilled.value(),
var_on_rejected.value()); var_on_rejected.value());
StoreObjectField(promise, JSPromise::kReactionsOffset, reaction); StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reaction);
Goto(&done); Goto(&done);
} }
...@@ -428,7 +433,8 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context, ...@@ -428,7 +433,8 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
} }
BIND(&enqueue); BIND(&enqueue);
Node* argument = LoadObjectField(promise, JSPromise::kResultOffset); Node* argument =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
Node* microtask = AllocatePromiseReactionJobTask( Node* microtask = AllocatePromiseReactionJobTask(
var_map.value(), context, argument, var_handler.value(), result); var_map.value(), context, argument, var_handler.value(), result);
CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), microtask); CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), microtask);
...@@ -583,7 +589,7 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context, ...@@ -583,7 +589,7 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
{ {
Node* const thenable_status = PromiseStatus(result); Node* const thenable_status = PromiseStatus(result);
Node* const thenable_value = Node* const thenable_value =
LoadObjectField(result, JSPromise::kResultOffset); LoadObjectField(result, JSPromise::kReactionsOrResultOffset);
Label if_isnotpending(this); Label if_isnotpending(this);
GotoIfNot(IsPromiseStatus(thenable_status, v8::Promise::kPending), GotoIfNot(IsPromiseStatus(thenable_status, v8::Promise::kPending),
...@@ -714,7 +720,7 @@ void PromiseBuiltinsAssembler::PromiseFulfill( ...@@ -714,7 +720,7 @@ void PromiseBuiltinsAssembler::PromiseFulfill(
Node* reactions; Node* reactions;
{ {
VARIABLE(var_current, MachineRepresentation::kTagged, VARIABLE(var_current, MachineRepresentation::kTagged,
LoadObjectField(promise, JSPromise::kReactionsOffset)); LoadObjectField(promise, JSPromise::kReactionsOrResultOffset));
VARIABLE(var_reversed, MachineRepresentation::kTagged, VARIABLE(var_reversed, MachineRepresentation::kTagged,
SmiConstant(Smi::kZero)); SmiConstant(Smi::kZero));
...@@ -735,9 +741,7 @@ void PromiseBuiltinsAssembler::PromiseFulfill( ...@@ -735,9 +741,7 @@ void PromiseBuiltinsAssembler::PromiseFulfill(
} }
// Reset the {promise}. // Reset the {promise}.
StoreObjectField(promise, JSPromise::kResultOffset, result); StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, result);
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kReactionsOffset,
SmiConstant(Smi::kZero));
PromiseSetStatus(promise, status); PromiseSetStatus(promise, status);
// Morph the {reactions} into PromiseReactionJobTasks and push them // Morph the {reactions} into PromiseReactionJobTasks and push them
......
...@@ -1013,14 +1013,12 @@ Reduction JSCreateLowering::ReduceJSCreatePromise(Node* node) { ...@@ -1013,14 +1013,12 @@ Reduction JSCreateLowering::ReduceJSCreatePromise(Node* node) {
jsgraph()->EmptyFixedArrayConstant()); jsgraph()->EmptyFixedArrayConstant());
a.Store(AccessBuilder::ForJSObjectElements(), a.Store(AccessBuilder::ForJSObjectElements(),
jsgraph()->EmptyFixedArrayConstant()); jsgraph()->EmptyFixedArrayConstant());
a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kResultOffset), a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kReactionsOrResultOffset),
jsgraph()->UndefinedConstant());
a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kReactionsOffset),
jsgraph()->ZeroConstant()); jsgraph()->ZeroConstant());
STATIC_ASSERT(v8::Promise::kPending == 0); STATIC_ASSERT(v8::Promise::kPending == 0);
a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kFlagsOffset), a.Store(AccessBuilder::ForJSObjectOffset(JSPromise::kFlagsOffset),
jsgraph()->ZeroConstant()); jsgraph()->ZeroConstant());
STATIC_ASSERT(JSPromise::kSize == 6 * kPointerSize); STATIC_ASSERT(JSPromise::kSize == 5 * kPointerSize);
for (int i = 0; i < v8::Promise::kEmbedderFieldCount; ++i) { for (int i = 0; i < v8::Promise::kEmbedderFieldCount; ++i) {
a.Store( a.Store(
AccessBuilder::ForJSObjectOffset(JSPromise::kSize + i * kPointerSize), AccessBuilder::ForJSObjectOffset(JSPromise::kSize + i * kPointerSize),
......
...@@ -1125,10 +1125,11 @@ void PromiseReaction::PromiseReactionVerify() { ...@@ -1125,10 +1125,11 @@ void PromiseReaction::PromiseReactionVerify() {
void JSPromise::JSPromiseVerify() { void JSPromise::JSPromiseVerify() {
CHECK(IsJSPromise()); CHECK(IsJSPromise());
JSObjectVerify(); JSObjectVerify();
VerifyPointer(result()); VerifyPointer(reactions_or_result());
VerifyPointer(reactions());
CHECK(reactions()->IsSmi() || reactions()->IsPromiseReaction());
VerifySmiField(kFlagsOffset); VerifySmiField(kFlagsOffset);
if (status() == Promise::kPending) {
CHECK(reactions()->IsSmi() || reactions()->IsPromiseReaction());
}
} }
template <typename Derived> template <typename Derived>
......
...@@ -2835,12 +2835,20 @@ ACCESSORS(PromiseCapability, promise, HeapObject, kPromiseOffset) ...@@ -2835,12 +2835,20 @@ ACCESSORS(PromiseCapability, promise, HeapObject, kPromiseOffset)
ACCESSORS(PromiseCapability, resolve, Object, kResolveOffset) ACCESSORS(PromiseCapability, resolve, Object, kResolveOffset)
ACCESSORS(PromiseCapability, reject, Object, kRejectOffset) ACCESSORS(PromiseCapability, reject, Object, kRejectOffset)
ACCESSORS(JSPromise, result, Object, kResultOffset) ACCESSORS(JSPromise, reactions_or_result, Object, kReactionsOrResultOffset)
ACCESSORS(JSPromise, reactions, Object, kReactionsOffset)
SMI_ACCESSORS(JSPromise, flags, kFlagsOffset) SMI_ACCESSORS(JSPromise, flags, kFlagsOffset)
BOOL_ACCESSORS(JSPromise, flags, has_handler, kHasHandlerBit) BOOL_ACCESSORS(JSPromise, flags, has_handler, kHasHandlerBit)
BOOL_ACCESSORS(JSPromise, flags, handled_hint, kHandledHintBit) BOOL_ACCESSORS(JSPromise, flags, handled_hint, kHandledHintBit)
Object* JSPromise::result() const {
DCHECK_NE(Promise::kPending, status());
return reactions_or_result();
}
Object* JSPromise::reactions() const {
DCHECK_EQ(Promise::kPending, status());
return reactions_or_result();
}
ElementsKind JSObject::GetElementsKind() { ElementsKind JSObject::GetElementsKind() {
ElementsKind kind = map()->elements_kind(); ElementsKind kind = map()->elements_kind();
......
...@@ -552,8 +552,11 @@ void JSArray::JSArrayPrint(std::ostream& os) { // NOLINT ...@@ -552,8 +552,11 @@ void JSArray::JSArrayPrint(std::ostream& os) { // NOLINT
void JSPromise::JSPromisePrint(std::ostream& os) { // NOLINT void JSPromise::JSPromisePrint(std::ostream& os) { // NOLINT
JSObjectPrintHeader(os, this, "JSPromise"); JSObjectPrintHeader(os, this, "JSPromise");
os << "\n - status = " << JSPromise::Status(status()); os << "\n - status = " << JSPromise::Status(status());
if (status() == Promise::kPending) {
os << "\n - reactions = " << Brief(reactions());
} else {
os << "\n - result = " << Brief(result()); os << "\n - result = " << Brief(result());
os << "\n - reactions: " << Brief(reactions()); }
os << "\n - has_handler = " << has_handler(); os << "\n - has_handler = " << has_handler();
os << "\n "; os << "\n ";
} }
......
...@@ -3998,13 +3998,27 @@ class JSMessageObject: public JSObject { ...@@ -3998,13 +3998,27 @@ class JSMessageObject: public JSObject {
typedef BodyDescriptor BodyDescriptorWeak; typedef BodyDescriptor BodyDescriptorWeak;
}; };
// Representation of promise objects in the specification. Our layout of
// JSPromise differs a bit from the layout in the specification, for example
// there's only a single list of PromiseReaction objects, instead of separate
// lists for fulfill and reject reactions. The PromiseReaction carries both
// callbacks from the start, and is eventually morphed into the proper kind of
// PromiseReactionJobTask when the JSPromise is settled.
//
// We also overlay the result and reactions fields on the JSPromise, since
// the reactions are only necessary for pending promises, whereas the result
// is only meaningful for settled promises.
class JSPromise : public JSObject { class JSPromise : public JSObject {
public: public:
// TODO(bmeurer): Overlay {result} and {reactions}! // [reactions_or_result]: Smi 0 terminated list of PromiseReaction objects
DECL_ACCESSORS(result, Object) // in case the JSPromise was not settled yet, otherwise the result.
DECL_ACCESSORS(reactions_or_result, Object)
// [result]: Checks that the promise is settled and returns the result.
inline Object* result() const;
// [reactions]: Smi 0 terminated list of PromiseReaction objects. // [reactions]: Checks that the promise is pending and returns the reactions.
DECL_ACCESSORS(reactions, Object) inline Object* reactions() const;
DECL_INT_ACCESSORS(flags) DECL_INT_ACCESSORS(flags)
...@@ -4031,9 +4045,8 @@ class JSPromise : public JSObject { ...@@ -4031,9 +4045,8 @@ class JSPromise : public JSObject {
DECL_VERIFIER(JSPromise) DECL_VERIFIER(JSPromise)
// Layout description. // Layout description.
static const int kResultOffset = JSObject::kHeaderSize; static const int kReactionsOrResultOffset = JSObject::kHeaderSize;
static const int kReactionsOffset = kResultOffset + kPointerSize; static const int kFlagsOffset = kReactionsOrResultOffset + kPointerSize;
static const int kFlagsOffset = kReactionsOffset + kPointerSize;
static const int kSize = kFlagsOffset + kPointerSize; static const int kSize = kFlagsOffset + kPointerSize;
static const int kSizeWithEmbedderFields = static const int kSizeWithEmbedderFields =
kSize + v8::Promise::kEmbedderFieldCount * kPointerSize; kSize + v8::Promise::kEmbedderFieldCount * kPointerSize;
......
...@@ -1312,10 +1312,9 @@ void V8HeapExplorer::ExtractJSArrayBufferReferences( ...@@ -1312,10 +1312,9 @@ void V8HeapExplorer::ExtractJSArrayBufferReferences(
} }
void V8HeapExplorer::ExtractJSPromiseReferences(int entry, JSPromise* promise) { void V8HeapExplorer::ExtractJSPromiseReferences(int entry, JSPromise* promise) {
SetInternalReference(promise, entry, "result", promise->result(), SetInternalReference(promise, entry, "reactions_or_result",
JSPromise::kResultOffset); promise->reactions_or_result(),
SetInternalReference(promise, entry, "reactions", promise->reactions(), JSPromise::kReactionsOrResultOffset);
JSPromise::kReactionsOffset);
} }
void V8HeapExplorer::ExtractFixedArrayReferences(int entry, FixedArray* array) { void V8HeapExplorer::ExtractFixedArrayReferences(int entry, FixedArray* array) {
......
...@@ -265,7 +265,10 @@ MaybeHandle<JSArray> Runtime::GetInternalProperties(Isolate* isolate, ...@@ -265,7 +265,10 @@ MaybeHandle<JSArray> Runtime::GetInternalProperties(Isolate* isolate,
Handle<String> status_str = factory->NewStringFromAsciiChecked(status); Handle<String> status_str = factory->NewStringFromAsciiChecked(status);
result->set(1, *status_str); result->set(1, *status_str);
Handle<Object> value_obj(promise->result(), isolate); Handle<Object> value_obj(promise->status() == Promise::kPending
? isolate->heap()->undefined_value()
: promise->result(),
isolate);
Handle<String> promise_value = Handle<String> promise_value =
factory->NewStringFromAsciiChecked("[[PromiseValue]]"); factory->NewStringFromAsciiChecked("[[PromiseValue]]");
result->set(2, *promise_value); result->set(2, *promise_value);
......
...@@ -2769,17 +2769,17 @@ TEST(JSPromise) { ...@@ -2769,17 +2769,17 @@ TEST(JSPromise) {
const v8::HeapGraphNode* resolved = GetProperty( const v8::HeapGraphNode* resolved = GetProperty(
env->GetIsolate(), global, v8::HeapGraphEdge::kProperty, "resolved"); env->GetIsolate(), global, v8::HeapGraphEdge::kProperty, "resolved");
CHECK(GetProperty(env->GetIsolate(), resolved, v8::HeapGraphEdge::kInternal, CHECK(GetProperty(env->GetIsolate(), resolved, v8::HeapGraphEdge::kInternal,
"result")); "reactions_or_result"));
const v8::HeapGraphNode* rejected = GetProperty( const v8::HeapGraphNode* rejected = GetProperty(
env->GetIsolate(), global, v8::HeapGraphEdge::kProperty, "rejected"); env->GetIsolate(), global, v8::HeapGraphEdge::kProperty, "rejected");
CHECK(GetProperty(env->GetIsolate(), rejected, v8::HeapGraphEdge::kInternal, CHECK(GetProperty(env->GetIsolate(), rejected, v8::HeapGraphEdge::kInternal,
"result")); "reactions_or_result"));
const v8::HeapGraphNode* pending = GetProperty( const v8::HeapGraphNode* pending = GetProperty(
env->GetIsolate(), global, v8::HeapGraphEdge::kProperty, "pending"); env->GetIsolate(), global, v8::HeapGraphEdge::kProperty, "pending");
CHECK(GetProperty(env->GetIsolate(), pending, v8::HeapGraphEdge::kInternal, CHECK(GetProperty(env->GetIsolate(), pending, v8::HeapGraphEdge::kInternal,
"reactions")); "reactions_or_result"));
const char* objectNames[] = {"resolved", "rejected", "pending", "chained"}; const char* objectNames[] = {"resolved", "rejected", "pending", "chained"};
for (auto objectName : objectNames) { for (auto objectName : objectNames) {
......
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