From a582199c5e56c9c84312dfa6d6fa6de724e1a806 Mon Sep 17 00:00:00 2001 From: Benedikt Meurer Date: Fri, 2 Feb 2018 14:00:12 +0100 Subject: [PATCH] [builtins] Unify PerformPromiseThen and optimize it with TurboFan. This creates a uniform PerformPromiseThen builtin, which performs the operation with the same name from the spec, except that it expects the handlers to be either undefined or callable already, since this is only relevant for a single callsite (namely Promise.prototype.then). Introduce a matching operator JSPerformPromiseThen into TurboFan, which represents this operation and removes the additional checks in case of Promise.prototype.then based on the information we can derived from the receiver maps. This yields a nice 20-25% improvement on Promise.prototype.then, as illustrated by the following micro-benchmark ```js const N = 1e7; function inc(x) { return x + 1; } function chain(promise) { return promise.then(inc).then(value => { if (value < N) chain(Promise.resolve(value)); }); } console.time('total'); chain(Promise.resolve(0)); setTimeout(console.timeEnd.bind(console, 'total')); ``` which goes from around 1230ms to 930ms with this patch. Bug: v8:7253 Change-Id: I5712a863acdbe7da3bb8e621887c7b952148c51a Reviewed-on: https://chromium-review.googlesource.com/899064 Reviewed-by: Jakob Gruber Reviewed-by: Benedikt Meurer Commit-Queue: Benedikt Meurer Cr-Commit-Position: refs/heads/master@{#51071} --- src/builtins/builtins-async-gen.cc | 7 +- src/builtins/builtins-async-iterator-gen.cc | 2 +- src/builtins/builtins-definitions.h | 5 +- src/builtins/builtins-promise-gen.cc | 132 ++++++++++-------- src/builtins/builtins-promise-gen.h | 7 +- src/compiler/js-call-reducer.cc | 83 +++++++++++ src/compiler/js-call-reducer.h | 1 + src/compiler/js-generic-lowering.cc | 7 + src/compiler/js-operator.cc | 81 ++++++----- src/compiler/js-operator.h | 2 + src/compiler/opcodes.h | 1 + src/compiler/typer.cc | 5 + src/compiler/verifier.cc | 8 ++ src/objects/map-inl.h | 1 + src/objects/map.h | 1 + .../compiler/promise-prototype-then.js | 50 +++++++ 16 files changed, 281 insertions(+), 112 deletions(-) create mode 100644 test/mjsunit/compiler/promise-prototype-then.js diff --git a/src/builtins/builtins-async-gen.cc b/src/builtins/builtins-async-gen.cc index 0cdcb57a3f..3d4572a757 100644 --- a/src/builtins/builtins-async-gen.cc +++ b/src/builtins/builtins-async-gen.cc @@ -143,11 +143,8 @@ Node* AsyncBuiltinsAssembler::Await( Goto(&do_perform_promise_then); BIND(&do_perform_promise_then); - - CallBuiltin(Builtins::kPerformNativePromiseThen, context, wrapped_value, - on_resolve, on_reject, throwaway); - - return wrapped_value; + return CallBuiltin(Builtins::kPerformPromiseThen, context, wrapped_value, + on_resolve, on_reject, throwaway); } void AsyncBuiltinsAssembler::InitializeNativeClosure(Node* context, diff --git a/src/builtins/builtins-async-iterator-gen.cc b/src/builtins/builtins-async-iterator-gen.cc index f232b32700..3155fb6276 100644 --- a/src/builtins/builtins-async-iterator-gen.cc +++ b/src/builtins/builtins-async-iterator-gen.cc @@ -128,7 +128,7 @@ void AsyncFromSyncBuiltinsAssembler::Generate_AsyncFromSyncIteratorMethod( // Perform ! PerformPromiseThen(valueWrapperCapability.[[Promise]], // onFulfilled, undefined, promiseCapability). - Return(CallBuiltin(Builtins::kPerformNativePromiseThen, context, wrapper, + Return(CallBuiltin(Builtins::kPerformPromiseThen, context, wrapper, on_fulfilled, UndefinedConstant(), promise)); BIND(&reject_promise); diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index a94a1dad71..07bec62b45 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -219,8 +219,6 @@ namespace internal { /* Promise helpers */ \ TFS(ResolveNativePromise, kPromise, kValue) \ TFS(RejectNativePromise, kPromise, kValue, kDebugEvent) \ - TFS(PerformNativePromiseThen, kPromise, kResolveReaction, kRejectReaction, \ - kResultPromise) \ TFS(EnqueueMicrotask, kMicrotask) \ TFC(RunMicrotasks, RunMicrotasks, 1) \ \ @@ -811,6 +809,8 @@ namespace internal { TFJ(PromiseAllResolveElementClosure, 1, kValue) \ /* ES #sec-promise.prototype.then */ \ TFJ(PromisePrototypeThen, 2, kOnFulfilled, kOnRejected) \ + /* ES #sec-performpromisethen */ \ + TFS(PerformPromiseThen, kPromise, kOnFulfilled, kOnRejected, kResultPromise) \ /* ES #sec-promise.prototype.catch */ \ TFJ(PromisePrototypeCatch, 1, kOnRejected) \ /* ES #sec-fulfillpromise */ \ @@ -1246,7 +1246,6 @@ namespace internal { V(AsyncGeneratorResolve) \ V(AsyncGeneratorAwaitCaught) \ V(AsyncGeneratorAwaitUncaught) \ - V(PerformNativePromiseThen) \ V(PromiseAll) \ V(PromiseConstructor) \ V(PromiseFulfillReactionJob) \ diff --git a/src/builtins/builtins-promise-gen.cc b/src/builtins/builtins-promise-gen.cc index 2b5c18e28e..78d2eaa8b3 100644 --- a/src/builtins/builtins-promise-gen.cc +++ b/src/builtins/builtins-promise-gen.cc @@ -256,36 +256,18 @@ void PromiseBuiltinsAssembler::PromiseSetHandledHint(Node* promise) { StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags); } -void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context, - Node* promise, - Node* on_fulfilled, - Node* on_rejected, - Node* result) { - CSA_ASSERT(this, TaggedIsNotSmi(result)); - CSA_ASSERT(this, Word32Or(IsJSPromise(result), IsPromiseCapability(result))); - - // 3. If IsCallable(onFulfilled) is false, then - // a. Set onFulfilled to undefined. - VARIABLE(var_on_fulfilled, MachineRepresentation::kTagged, on_fulfilled); - Label if_fulfilled_done(this), if_fulfilled_notcallable(this); - GotoIf(TaggedIsSmi(on_fulfilled), &if_fulfilled_notcallable); - Branch(IsCallable(on_fulfilled), &if_fulfilled_done, - &if_fulfilled_notcallable); - BIND(&if_fulfilled_notcallable); - var_on_fulfilled.Bind(UndefinedConstant()); - Goto(&if_fulfilled_done); - BIND(&if_fulfilled_done); - - // 4. If IsCallable(onRejected) is false, then - // a. Set onRejected to undefined. - VARIABLE(var_on_rejected, MachineRepresentation::kTagged, on_rejected); - Label if_rejected_done(this), if_rejected_notcallable(this); - GotoIf(TaggedIsSmi(on_rejected), &if_rejected_notcallable); - Branch(IsCallable(on_rejected), &if_rejected_done, &if_rejected_notcallable); - BIND(&if_rejected_notcallable); - var_on_rejected.Bind(UndefinedConstant()); - Goto(&if_rejected_done); - BIND(&if_rejected_done); +// ES section #sec-performpromisethen +void PromiseBuiltinsAssembler::PerformPromiseThen( + Node* context, Node* promise, Node* on_fulfilled, Node* on_rejected, + Node* result_promise_or_capability) { + CSA_ASSERT(this, TaggedIsNotSmi(promise)); + CSA_ASSERT(this, IsJSPromise(promise)); + CSA_ASSERT(this, + Word32Or(IsCallable(on_fulfilled), IsUndefined(on_fulfilled))); + CSA_ASSERT(this, Word32Or(IsCallable(on_rejected), IsUndefined(on_rejected))); + CSA_ASSERT(this, TaggedIsNotSmi(result_promise_or_capability)); + CSA_ASSERT(this, Word32Or(IsJSPromise(result_promise_or_capability), + IsPromiseCapability(result_promise_or_capability))); Label if_pending(this), if_notpending(this), done(this); Node* const status = PromiseStatus(promise); @@ -300,9 +282,9 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context, // push onto the microtask queue. Node* const promise_reactions = LoadObjectField(promise, JSPromise::kReactionsOrResultOffset); - Node* const reaction = AllocatePromiseReaction(promise_reactions, result, - var_on_fulfilled.value(), - var_on_rejected.value()); + Node* const reaction = + AllocatePromiseReaction(promise_reactions, result_promise_or_capability, + on_fulfilled, on_rejected); StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reaction); Goto(&done); } @@ -319,7 +301,7 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context, BIND(&if_fulfilled); { var_map.Bind(LoadRoot(Heap::kPromiseFulfillReactionJobTaskMapRootIndex)); - var_handler.Bind(var_on_fulfilled.value()); + var_handler.Bind(on_fulfilled); Goto(&enqueue); } @@ -327,7 +309,7 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context, { CSA_ASSERT(this, IsPromiseStatus(status, v8::Promise::kRejected)); var_map.Bind(LoadRoot(Heap::kPromiseRejectReactionJobTaskMapRootIndex)); - var_handler.Bind(var_on_rejected.value()); + var_handler.Bind(on_rejected); GotoIf(PromiseHasHandler(promise), &enqueue); CallRuntime(Runtime::kPromiseRevokeReject, context, promise); Goto(&enqueue); @@ -337,7 +319,8 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context, Node* argument = LoadObjectField(promise, JSPromise::kReactionsOrResultOffset); Node* microtask = AllocatePromiseReactionJobTask( - var_map.value(), context, argument, var_handler.value(), result); + var_map.value(), context, argument, var_handler.value(), + result_promise_or_capability); CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), microtask); Goto(&done); } @@ -346,6 +329,22 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context, PromiseSetHasHandler(promise); } +// ES section #sec-performpromisethen +TF_BUILTIN(PerformPromiseThen, PromiseBuiltinsAssembler) { + Node* const context = Parameter(Descriptor::kContext); + Node* const promise = Parameter(Descriptor::kPromise); + Node* const on_fulfilled = Parameter(Descriptor::kOnFulfilled); + Node* const on_rejected = Parameter(Descriptor::kOnRejected); + Node* const result_promise = Parameter(Descriptor::kResultPromise); + + CSA_ASSERT(this, TaggedIsNotSmi(result_promise)); + CSA_ASSERT(this, IsJSPromise(result_promise)); + + PerformPromiseThen(context, promise, on_fulfilled, on_rejected, + result_promise); + Return(result_promise); +} + // Promise fast path implementations rely on unmodified JSPromise instances. // We use a fairly coarse granularity for this and simply check whether both // the promise itself is unmodified (i.e. its map has not changed) and its @@ -957,9 +956,8 @@ TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) { BIND(&fast_promise_capability); { Node* const result_promise = AllocateAndInitJSPromise(context, promise); - var_result_promise.Bind(result_promise); var_result_promise_or_capability.Bind(result_promise); - CSA_ASSERT(this, IsJSPromise(var_result_promise_or_capability.value())); + var_result_promise.Bind(result_promise); Goto(&perform_promise_then); } @@ -971,21 +969,47 @@ TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) { var_result_promise.Bind( LoadObjectField(capability, PromiseCapability::kPromiseOffset)); var_result_promise_or_capability.Bind(capability); - CSA_ASSERT(this, - IsPromiseCapability(var_result_promise_or_capability.value())); Goto(&perform_promise_then); } // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, // resultCapability). BIND(&perform_promise_then); - CSA_ASSERT( - this, - Word32Or(IsJSPromise(var_result_promise_or_capability.value()), - IsPromiseCapability(var_result_promise_or_capability.value()))); - InternalPerformPromiseThen(context, promise, on_fulfilled, on_rejected, - var_result_promise_or_capability.value()); - Return(var_result_promise.value()); + { + // We do some work of the PerformPromiseThen operation here, in that + // we check the handlers and turn non-callable handlers into undefined. + // This is because this is the one and only callsite of PerformPromiseThen + // that has to do this. + + // 3. If IsCallable(onFulfilled) is false, then + // a. Set onFulfilled to undefined. + VARIABLE(var_on_fulfilled, MachineRepresentation::kTagged, on_fulfilled); + Label if_fulfilled_done(this), if_fulfilled_notcallable(this); + GotoIf(TaggedIsSmi(on_fulfilled), &if_fulfilled_notcallable); + Branch(IsCallable(on_fulfilled), &if_fulfilled_done, + &if_fulfilled_notcallable); + BIND(&if_fulfilled_notcallable); + var_on_fulfilled.Bind(UndefinedConstant()); + Goto(&if_fulfilled_done); + BIND(&if_fulfilled_done); + + // 4. If IsCallable(onRejected) is false, then + // a. Set onRejected to undefined. + VARIABLE(var_on_rejected, MachineRepresentation::kTagged, on_rejected); + Label if_rejected_done(this), if_rejected_notcallable(this); + GotoIf(TaggedIsSmi(on_rejected), &if_rejected_notcallable); + Branch(IsCallable(on_rejected), &if_rejected_done, + &if_rejected_notcallable); + BIND(&if_rejected_notcallable); + var_on_rejected.Bind(UndefinedConstant()); + Goto(&if_rejected_done); + BIND(&if_rejected_done); + + PerformPromiseThen(context, promise, var_on_fulfilled.value(), + var_on_rejected.value(), + var_result_promise_or_capability.value()); + Return(var_result_promise.value()); + } } // ES#sec-promise-resolve-functions @@ -1621,20 +1645,6 @@ TF_BUILTIN(RejectNativePromise, PromiseBuiltinsAssembler) { Return(UndefinedConstant()); } -TF_BUILTIN(PerformNativePromiseThen, PromiseBuiltinsAssembler) { - Node* const promise = Parameter(Descriptor::kPromise); - Node* const resolve_reaction = Parameter(Descriptor::kResolveReaction); - Node* const reject_reaction = Parameter(Descriptor::kRejectReaction); - Node* const result_promise = Parameter(Descriptor::kResultPromise); - Node* const context = Parameter(Descriptor::kContext); - - CSA_ASSERT(this, IsJSPromise(result_promise)); - - InternalPerformPromiseThen(context, promise, resolve_reaction, - reject_reaction, result_promise); - Return(result_promise); -} - Node* PromiseBuiltinsAssembler::PerformPromiseAll( Node* context, Node* constructor, Node* capability, const IteratorRecord& iterator, Label* if_exception, diff --git a/src/builtins/builtins-promise-gen.h b/src/builtins/builtins-promise-gen.h index 6fd3baa7da..ebf0d047b4 100644 --- a/src/builtins/builtins-promise-gen.h +++ b/src/builtins/builtins-promise-gen.h @@ -121,9 +121,9 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { void PromiseSetHasHandler(Node* promise); void PromiseSetHandledHint(Node* promise); - void InternalPerformPromiseThen(Node* context, Node* promise, - Node* on_fulfill, Node* on_reject, - Node* result); + void PerformPromiseThen(Node* context, Node* promise, Node* on_fulfilled, + Node* on_rejected, + Node* result_promise_or_capability); void InternalResolvePromise(Node* context, Node* promise, Node* result); @@ -177,7 +177,6 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { Node* promise_or_capability, PromiseReaction::Type type); - private: Node* IsPromiseStatus(Node* actual, v8::Promise::PromiseState expected); void PromiseSetStatus(Node* promise, v8::Promise::PromiseState status); diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc index 798568f305..fc961c4fac 100644 --- a/src/compiler/js-call-reducer.cc +++ b/src/compiler/js-call-reducer.cc @@ -2969,6 +2969,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) { return ReduceAsyncFunctionPromiseRelease(node); case Builtins::kPromisePrototypeCatch: return ReducePromisePrototypeCatch(node); + case Builtins::kPromisePrototypeThen: + return ReducePromisePrototypeThen(node); default: break; } @@ -3989,6 +3991,87 @@ Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) { return Changed(node); } +Reduction JSCallReducer::ReducePromisePrototypeThen(Node* node) { + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + CallParameters const& p = CallParametersOf(node->op()); + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* on_fulfilled = node->op()->ValueInputCount() > 2 + ? NodeProperties::GetValueInput(node, 2) + : jsgraph()->UndefinedConstant(); + Node* on_rejected = node->op()->ValueInputCount() > 3 + ? NodeProperties::GetValueInput(node, 3) + : jsgraph()->UndefinedConstant(); + Node* context = NodeProperties::GetContextInput(node); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { + return NoChange(); + } + + // Check that promises aren't being observed through (debug) hooks. + if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange(); + + // Check if the @@species protector is intact. The @@species protector + // guards the "constructor" lookup on all JSPromise instances and the + // initial Promise.prototype, as well as the Symbol.species lookup on + // the Promise constructor. + if (!isolate()->IsSpeciesLookupChainIntact()) return NoChange(); + + // Check if we know something about {receiver} already. + ZoneHandleSet receiver_maps; + NodeProperties::InferReceiverMapsResult infer_receiver_maps_result = + NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); + if (infer_receiver_maps_result == NodeProperties::kNoReceiverMaps) { + return NoChange(); + } + DCHECK_NE(0, receiver_maps.size()); + + // Check whether all {receiver_maps} are JSPromise maps and + // have the initial Promise.prototype as their [[Prototype]]. + for (Handle receiver_map : receiver_maps) { + if (!receiver_map->IsJSPromiseMap()) return NoChange(); + if (receiver_map->prototype() != native_context()->promise_prototype()) { + return NoChange(); + } + } + + // Add a code dependency on the necessary protectors. + dependencies()->AssumePropertyCell(factory()->promise_hook_protector()); + dependencies()->AssumePropertyCell(factory()->species_protector()); + + // If the {receiver_maps} aren't reliable, we need to repeat the + // map check here, guarded by the CALL_IC. + if (infer_receiver_maps_result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } + + // Check that {on_fulfilled} is callable. + on_fulfilled = graph()->NewNode( + common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue), + graph()->NewNode(simplified()->ObjectIsCallable(), on_fulfilled), + on_fulfilled, jsgraph()->UndefinedConstant()); + + // Check that {on_rejected} is callable. + on_rejected = graph()->NewNode( + common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue), + graph()->NewNode(simplified()->ObjectIsCallable(), on_rejected), + on_rejected, jsgraph()->UndefinedConstant()); + + // Create the resulting JSPromise. + Node* result = effect = + graph()->NewNode(javascript()->CreatePromise(), context, effect); + + // Chain {result} onto {receiver}. + result = effect = graph()->NewNode(javascript()->PerformPromiseThen(), + receiver, on_fulfilled, on_rejected, + result, context, effect, control); + ReplaceWithValue(node, result, effect, control); + return Replace(result); +} + Graph* JSCallReducer::graph() const { return jsgraph()->graph(); } Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); } diff --git a/src/compiler/js-call-reducer.h b/src/compiler/js-call-reducer.h index 69d19c57d6..bd284e8745 100644 --- a/src/compiler/js-call-reducer.h +++ b/src/compiler/js-call-reducer.h @@ -102,6 +102,7 @@ class JSCallReducer final : public AdvancedReducer { Reduction ReduceAsyncFunctionPromiseCreate(Node* node); Reduction ReduceAsyncFunctionPromiseRelease(Node* node); Reduction ReducePromisePrototypeCatch(Node* node); + Reduction ReducePromisePrototypeThen(Node* node); Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason); diff --git a/src/compiler/js-generic-lowering.cc b/src/compiler/js-generic-lowering.cc index db93e644d3..ed1d3b4174 100644 --- a/src/compiler/js-generic-lowering.cc +++ b/src/compiler/js-generic-lowering.cc @@ -766,6 +766,13 @@ void JSGenericLowering::LowerJSDebugger(Node* node) { ReplaceWithStubCall(node, callable, flags); } +void JSGenericLowering::LowerJSPerformPromiseThen(Node* node) { + CallDescriptor::Flags flags = FrameStateFlagForCall(node); + Callable callable = + Builtins::CallableFor(isolate(), Builtins::kPerformPromiseThen); + ReplaceWithStubCall(node, callable, flags); +} + Zone* JSGenericLowering::zone() const { return graph()->zone(); } diff --git a/src/compiler/js-operator.cc b/src/compiler/js-operator.cc index bfa5fc6800..0fd9625e14 100644 --- a/src/compiler/js-operator.cc +++ b/src/compiler/js-operator.cc @@ -543,44 +543,45 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) { return OpParameter(op); } -#define CACHED_OP_LIST(V) \ - V(BitwiseOr, Operator::kNoProperties, 2, 1) \ - V(BitwiseXor, Operator::kNoProperties, 2, 1) \ - V(BitwiseAnd, Operator::kNoProperties, 2, 1) \ - V(ShiftLeft, Operator::kNoProperties, 2, 1) \ - V(ShiftRight, Operator::kNoProperties, 2, 1) \ - V(ShiftRightLogical, Operator::kNoProperties, 2, 1) \ - V(Subtract, Operator::kNoProperties, 2, 1) \ - V(Multiply, Operator::kNoProperties, 2, 1) \ - V(Divide, Operator::kNoProperties, 2, 1) \ - V(Modulus, Operator::kNoProperties, 2, 1) \ - V(Exponentiate, Operator::kNoProperties, 2, 1) \ - V(BitwiseNot, Operator::kNoProperties, 1, 1) \ - V(Decrement, Operator::kNoProperties, 1, 1) \ - V(Increment, Operator::kNoProperties, 1, 1) \ - V(Negate, Operator::kNoProperties, 1, 1) \ - V(ToInteger, Operator::kNoProperties, 1, 1) \ - V(ToLength, Operator::kNoProperties, 1, 1) \ - V(ToName, Operator::kNoProperties, 1, 1) \ - V(ToNumber, Operator::kNoProperties, 1, 1) \ - V(ToNumeric, Operator::kNoProperties, 1, 1) \ - V(ToObject, Operator::kFoldable, 1, 1) \ - V(ToString, Operator::kNoProperties, 1, 1) \ - V(Create, Operator::kNoProperties, 2, 1) \ - V(CreateIterResultObject, Operator::kEliminatable, 2, 1) \ - V(CreateKeyValueArray, Operator::kEliminatable, 2, 1) \ - V(CreatePromise, Operator::kEliminatable, 0, 1) \ - V(HasProperty, Operator::kNoProperties, 2, 1) \ - V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \ - V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \ - V(ForInEnumerate, Operator::kNoProperties, 1, 1) \ - V(LoadMessage, Operator::kNoThrow | Operator::kNoWrite, 0, 1) \ - V(StoreMessage, Operator::kNoRead | Operator::kNoThrow, 1, 0) \ - V(GeneratorRestoreContinuation, Operator::kNoThrow, 1, 1) \ - V(GeneratorRestoreContext, Operator::kNoThrow, 1, 1) \ - V(GeneratorRestoreInputOrDebugPos, Operator::kNoThrow, 1, 1) \ - V(StackCheck, Operator::kNoWrite, 0, 0) \ - V(Debugger, Operator::kNoProperties, 0, 0) \ +#define CACHED_OP_LIST(V) \ + V(BitwiseOr, Operator::kNoProperties, 2, 1) \ + V(BitwiseXor, Operator::kNoProperties, 2, 1) \ + V(BitwiseAnd, Operator::kNoProperties, 2, 1) \ + V(ShiftLeft, Operator::kNoProperties, 2, 1) \ + V(ShiftRight, Operator::kNoProperties, 2, 1) \ + V(ShiftRightLogical, Operator::kNoProperties, 2, 1) \ + V(Subtract, Operator::kNoProperties, 2, 1) \ + V(Multiply, Operator::kNoProperties, 2, 1) \ + V(Divide, Operator::kNoProperties, 2, 1) \ + V(Modulus, Operator::kNoProperties, 2, 1) \ + V(Exponentiate, Operator::kNoProperties, 2, 1) \ + V(BitwiseNot, Operator::kNoProperties, 1, 1) \ + V(Decrement, Operator::kNoProperties, 1, 1) \ + V(Increment, Operator::kNoProperties, 1, 1) \ + V(Negate, Operator::kNoProperties, 1, 1) \ + V(ToInteger, Operator::kNoProperties, 1, 1) \ + V(ToLength, Operator::kNoProperties, 1, 1) \ + V(ToName, Operator::kNoProperties, 1, 1) \ + V(ToNumber, Operator::kNoProperties, 1, 1) \ + V(ToNumeric, Operator::kNoProperties, 1, 1) \ + V(ToObject, Operator::kFoldable, 1, 1) \ + V(ToString, Operator::kNoProperties, 1, 1) \ + V(Create, Operator::kNoProperties, 2, 1) \ + V(CreateIterResultObject, Operator::kEliminatable, 2, 1) \ + V(CreateKeyValueArray, Operator::kEliminatable, 2, 1) \ + V(CreatePromise, Operator::kEliminatable, 0, 1) \ + V(HasProperty, Operator::kNoProperties, 2, 1) \ + V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \ + V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \ + V(ForInEnumerate, Operator::kNoProperties, 1, 1) \ + V(LoadMessage, Operator::kNoThrow | Operator::kNoWrite, 0, 1) \ + V(StoreMessage, Operator::kNoRead | Operator::kNoThrow, 1, 0) \ + V(GeneratorRestoreContinuation, Operator::kNoThrow, 1, 1) \ + V(GeneratorRestoreContext, Operator::kNoThrow, 1, 1) \ + V(GeneratorRestoreInputOrDebugPos, Operator::kNoThrow, 1, 1) \ + V(StackCheck, Operator::kNoWrite, 0, 0) \ + V(Debugger, Operator::kNoProperties, 0, 0) \ + V(PerformPromiseThen, Operator::kNoDeopt | Operator::kNoThrow, 4, 1) \ V(GetSuperConstructor, Operator::kNoWrite, 1, 1) #define BINARY_OP_LIST(V) V(Add) @@ -1157,6 +1158,10 @@ const Operator* JSOperatorBuilder::CreateBlockContext( scope_info); // parameter } +#undef BINARY_OP_LIST +#undef CACHED_OP_LIST +#undef COMPARE_OP_LIST + } // namespace compiler } // namespace internal } // namespace v8 diff --git a/src/compiler/js-operator.h b/src/compiler/js-operator.h index f72093b055..0e223036e6 100644 --- a/src/compiler/js-operator.h +++ b/src/compiler/js-operator.h @@ -754,6 +754,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final const Operator* StackCheck(); const Operator* Debugger(); + const Operator* PerformPromiseThen(); + const Operator* CreateFunctionContext(int slot_count, ScopeType scope_type); const Operator* CreateCatchContext(const Handle& name, const Handle& scope_info); diff --git a/src/compiler/opcodes.h b/src/compiler/opcodes.h index 2b6d65e196..efec7c221a 100644 --- a/src/compiler/opcodes.h +++ b/src/compiler/opcodes.h @@ -195,6 +195,7 @@ V(JSGeneratorRestoreContext) \ V(JSGeneratorRestoreRegister) \ V(JSGeneratorRestoreInputOrDebugPos) \ + V(JSPerformPromiseThen) \ V(JSStackCheck) \ V(JSDebugger) diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index e7dbbcdb25..f5ef2e4570 100644 --- a/src/compiler/typer.cc +++ b/src/compiler/typer.cc @@ -1842,6 +1842,11 @@ Type* Typer::Visitor::TypeJSStackCheck(Node* node) { return Type::Any(); } Type* Typer::Visitor::TypeJSDebugger(Node* node) { return Type::Any(); } +Type* Typer::Visitor::TypeJSPerformPromiseThen(Node* node) { + // TODO(turbofan): Introduce a Type::Promise here. + return Type::OtherObject(); +} + // Simplified operators. Type* Typer::Visitor::TypeBooleanNot(Node* node) { return Type::Boolean(); } diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc index 14126167ef..7060476729 100644 --- a/src/compiler/verifier.cc +++ b/src/compiler/verifier.cc @@ -853,6 +853,14 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) { CheckNotTyped(node); break; + case IrOpcode::kJSPerformPromiseThen: + CheckValueInputIs(node, 0, Type::Any()); + CheckValueInputIs(node, 1, Type::Any()); + CheckValueInputIs(node, 2, Type::Any()); + CheckValueInputIs(node, 3, Type::Any()); + CheckTypeIs(node, Type::OtherObject()); + break; + case IrOpcode::kComment: case IrOpcode::kDebugAbort: case IrOpcode::kDebugBreak: diff --git a/src/objects/map-inl.h b/src/objects/map-inl.h index c78f947b3a..250a998f61 100644 --- a/src/objects/map-inl.h +++ b/src/objects/map-inl.h @@ -503,6 +503,7 @@ bool Map::IsJSObjectMap() const { STATIC_ASSERT(LAST_JS_OBJECT_TYPE == LAST_TYPE); return instance_type() >= FIRST_JS_OBJECT_TYPE; } +bool Map::IsJSPromiseMap() const { return instance_type() == JS_PROMISE_TYPE; } bool Map::IsJSArrayMap() const { return instance_type() == JS_ARRAY_TYPE; } bool Map::IsJSFunctionMap() const { return instance_type() == JS_FUNCTION_TYPE; diff --git a/src/objects/map.h b/src/objects/map.h index bf0d843884..a21ba5a963 100644 --- a/src/objects/map.h +++ b/src/objects/map.h @@ -713,6 +713,7 @@ class Map : public HeapObject { inline bool IsPrimitiveMap() const; inline bool IsJSReceiverMap() const; inline bool IsJSObjectMap() const; + inline bool IsJSPromiseMap() const; inline bool IsJSArrayMap() const; inline bool IsJSFunctionMap() const; inline bool IsStringMap() const; diff --git a/test/mjsunit/compiler/promise-prototype-then.js b/test/mjsunit/compiler/promise-prototype-then.js new file mode 100644 index 0000000000..caf77708b6 --- /dev/null +++ b/test/mjsunit/compiler/promise-prototype-then.js @@ -0,0 +1,50 @@ +// 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. + +// Flags: --allow-natives-syntax + +(function() { + const p = Promise.resolve(1); + function foo(p) { return p.then(); } + foo(p); + foo(p); + %OptimizeFunctionOnNextCall(foo); + foo(p); +})(); + +(function() { + const p = Promise.resolve(1); + function foo(p) { return p.then(x => x); } + foo(p); + foo(p); + %OptimizeFunctionOnNextCall(foo); + foo(p); +})(); + +(function() { + const p = Promise.resolve(1); + function foo(p) { return p.then(x => x, y => y); } + foo(p); + foo(p); + %OptimizeFunctionOnNextCall(foo); + foo(p); +})(); + +(function() { + const p = Promise.resolve(1); + function foo(p, f) { return p.then(f, f); } + foo(p, x => x); + foo(p, x => x); + %OptimizeFunctionOnNextCall(foo); + foo(p, x => x); +})(); + +(function() { + const p = Promise.resolve(1); + function foo(p, f) { return p.then(f, f).then(f, f); } + foo(p, x => x); + foo(p, x => x); + %OptimizeFunctionOnNextCall(foo); + foo(p, x => x); +})();