[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 <jgruber@chromium.org>
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51071}
This commit is contained in:
Benedikt Meurer 2018-02-02 14:00:12 +01:00 committed by Commit Bot
parent ac5ad35283
commit a582199c5e
16 changed files with 281 additions and 112 deletions

View File

@ -143,11 +143,8 @@ Node* AsyncBuiltinsAssembler::Await(
Goto(&do_perform_promise_then); Goto(&do_perform_promise_then);
BIND(&do_perform_promise_then); BIND(&do_perform_promise_then);
return CallBuiltin(Builtins::kPerformPromiseThen, context, wrapped_value,
CallBuiltin(Builtins::kPerformNativePromiseThen, context, wrapped_value, on_resolve, on_reject, throwaway);
on_resolve, on_reject, throwaway);
return wrapped_value;
} }
void AsyncBuiltinsAssembler::InitializeNativeClosure(Node* context, void AsyncBuiltinsAssembler::InitializeNativeClosure(Node* context,

View File

@ -128,7 +128,7 @@ void AsyncFromSyncBuiltinsAssembler::Generate_AsyncFromSyncIteratorMethod(
// Perform ! PerformPromiseThen(valueWrapperCapability.[[Promise]], // Perform ! PerformPromiseThen(valueWrapperCapability.[[Promise]],
// onFulfilled, undefined, promiseCapability). // onFulfilled, undefined, promiseCapability).
Return(CallBuiltin(Builtins::kPerformNativePromiseThen, context, wrapper, Return(CallBuiltin(Builtins::kPerformPromiseThen, context, wrapper,
on_fulfilled, UndefinedConstant(), promise)); on_fulfilled, UndefinedConstant(), promise));
BIND(&reject_promise); BIND(&reject_promise);

View File

@ -219,8 +219,6 @@ namespace internal {
/* Promise helpers */ \ /* Promise helpers */ \
TFS(ResolveNativePromise, kPromise, kValue) \ TFS(ResolveNativePromise, kPromise, kValue) \
TFS(RejectNativePromise, kPromise, kValue, kDebugEvent) \ TFS(RejectNativePromise, kPromise, kValue, kDebugEvent) \
TFS(PerformNativePromiseThen, kPromise, kResolveReaction, kRejectReaction, \
kResultPromise) \
TFS(EnqueueMicrotask, kMicrotask) \ TFS(EnqueueMicrotask, kMicrotask) \
TFC(RunMicrotasks, RunMicrotasks, 1) \ TFC(RunMicrotasks, RunMicrotasks, 1) \
\ \
@ -811,6 +809,8 @@ namespace internal {
TFJ(PromiseAllResolveElementClosure, 1, kValue) \ TFJ(PromiseAllResolveElementClosure, 1, kValue) \
/* ES #sec-promise.prototype.then */ \ /* ES #sec-promise.prototype.then */ \
TFJ(PromisePrototypeThen, 2, kOnFulfilled, kOnRejected) \ TFJ(PromisePrototypeThen, 2, kOnFulfilled, kOnRejected) \
/* ES #sec-performpromisethen */ \
TFS(PerformPromiseThen, kPromise, kOnFulfilled, kOnRejected, kResultPromise) \
/* ES #sec-promise.prototype.catch */ \ /* ES #sec-promise.prototype.catch */ \
TFJ(PromisePrototypeCatch, 1, kOnRejected) \ TFJ(PromisePrototypeCatch, 1, kOnRejected) \
/* ES #sec-fulfillpromise */ \ /* ES #sec-fulfillpromise */ \
@ -1246,7 +1246,6 @@ namespace internal {
V(AsyncGeneratorResolve) \ V(AsyncGeneratorResolve) \
V(AsyncGeneratorAwaitCaught) \ V(AsyncGeneratorAwaitCaught) \
V(AsyncGeneratorAwaitUncaught) \ V(AsyncGeneratorAwaitUncaught) \
V(PerformNativePromiseThen) \
V(PromiseAll) \ V(PromiseAll) \
V(PromiseConstructor) \ V(PromiseConstructor) \
V(PromiseFulfillReactionJob) \ V(PromiseFulfillReactionJob) \

View File

@ -256,36 +256,18 @@ void PromiseBuiltinsAssembler::PromiseSetHandledHint(Node* promise) {
StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags); StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags);
} }
void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context, // ES section #sec-performpromisethen
Node* promise, void PromiseBuiltinsAssembler::PerformPromiseThen(
Node* on_fulfilled, Node* context, Node* promise, Node* on_fulfilled, Node* on_rejected,
Node* on_rejected, Node* result_promise_or_capability) {
Node* result) { CSA_ASSERT(this, TaggedIsNotSmi(promise));
CSA_ASSERT(this, TaggedIsNotSmi(result)); CSA_ASSERT(this, IsJSPromise(promise));
CSA_ASSERT(this, Word32Or(IsJSPromise(result), IsPromiseCapability(result))); CSA_ASSERT(this,
Word32Or(IsCallable(on_fulfilled), IsUndefined(on_fulfilled)));
// 3. If IsCallable(onFulfilled) is false, then CSA_ASSERT(this, Word32Or(IsCallable(on_rejected), IsUndefined(on_rejected)));
// a. Set onFulfilled to undefined. CSA_ASSERT(this, TaggedIsNotSmi(result_promise_or_capability));
VARIABLE(var_on_fulfilled, MachineRepresentation::kTagged, on_fulfilled); CSA_ASSERT(this, Word32Or(IsJSPromise(result_promise_or_capability),
Label if_fulfilled_done(this), if_fulfilled_notcallable(this); IsPromiseCapability(result_promise_or_capability)));
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);
Label if_pending(this), if_notpending(this), done(this); Label if_pending(this), if_notpending(this), done(this);
Node* const status = PromiseStatus(promise); Node* const status = PromiseStatus(promise);
@ -300,9 +282,9 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
// push onto the microtask queue. // push onto the microtask queue.
Node* const promise_reactions = Node* const promise_reactions =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset); LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
Node* const reaction = AllocatePromiseReaction(promise_reactions, result, Node* const reaction =
var_on_fulfilled.value(), AllocatePromiseReaction(promise_reactions, result_promise_or_capability,
var_on_rejected.value()); on_fulfilled, on_rejected);
StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reaction); StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reaction);
Goto(&done); Goto(&done);
} }
@ -319,7 +301,7 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
BIND(&if_fulfilled); BIND(&if_fulfilled);
{ {
var_map.Bind(LoadRoot(Heap::kPromiseFulfillReactionJobTaskMapRootIndex)); var_map.Bind(LoadRoot(Heap::kPromiseFulfillReactionJobTaskMapRootIndex));
var_handler.Bind(var_on_fulfilled.value()); var_handler.Bind(on_fulfilled);
Goto(&enqueue); Goto(&enqueue);
} }
@ -327,7 +309,7 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
{ {
CSA_ASSERT(this, IsPromiseStatus(status, v8::Promise::kRejected)); CSA_ASSERT(this, IsPromiseStatus(status, v8::Promise::kRejected));
var_map.Bind(LoadRoot(Heap::kPromiseRejectReactionJobTaskMapRootIndex)); var_map.Bind(LoadRoot(Heap::kPromiseRejectReactionJobTaskMapRootIndex));
var_handler.Bind(var_on_rejected.value()); var_handler.Bind(on_rejected);
GotoIf(PromiseHasHandler(promise), &enqueue); GotoIf(PromiseHasHandler(promise), &enqueue);
CallRuntime(Runtime::kPromiseRevokeReject, context, promise); CallRuntime(Runtime::kPromiseRevokeReject, context, promise);
Goto(&enqueue); Goto(&enqueue);
@ -337,7 +319,8 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
Node* argument = Node* argument =
LoadObjectField(promise, JSPromise::kReactionsOrResultOffset); 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_promise_or_capability);
CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), microtask); CallBuiltin(Builtins::kEnqueueMicrotask, NoContextConstant(), microtask);
Goto(&done); Goto(&done);
} }
@ -346,6 +329,22 @@ void PromiseBuiltinsAssembler::InternalPerformPromiseThen(Node* context,
PromiseSetHasHandler(promise); 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. // Promise fast path implementations rely on unmodified JSPromise instances.
// We use a fairly coarse granularity for this and simply check whether both // 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 // 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); BIND(&fast_promise_capability);
{ {
Node* const result_promise = AllocateAndInitJSPromise(context, promise); Node* const result_promise = AllocateAndInitJSPromise(context, promise);
var_result_promise.Bind(result_promise);
var_result_promise_or_capability.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); Goto(&perform_promise_then);
} }
@ -971,21 +969,47 @@ TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) {
var_result_promise.Bind( var_result_promise.Bind(
LoadObjectField(capability, PromiseCapability::kPromiseOffset)); LoadObjectField(capability, PromiseCapability::kPromiseOffset));
var_result_promise_or_capability.Bind(capability); var_result_promise_or_capability.Bind(capability);
CSA_ASSERT(this,
IsPromiseCapability(var_result_promise_or_capability.value()));
Goto(&perform_promise_then); Goto(&perform_promise_then);
} }
// 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
// resultCapability). // resultCapability).
BIND(&perform_promise_then); BIND(&perform_promise_then);
CSA_ASSERT( {
this, // We do some work of the PerformPromiseThen operation here, in that
Word32Or(IsJSPromise(var_result_promise_or_capability.value()), // we check the handlers and turn non-callable handlers into undefined.
IsPromiseCapability(var_result_promise_or_capability.value()))); // This is because this is the one and only callsite of PerformPromiseThen
InternalPerformPromiseThen(context, promise, on_fulfilled, on_rejected, // that has to do this.
var_result_promise_or_capability.value());
Return(var_result_promise.value()); // 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 // ES#sec-promise-resolve-functions
@ -1621,20 +1645,6 @@ TF_BUILTIN(RejectNativePromise, PromiseBuiltinsAssembler) {
Return(UndefinedConstant()); 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* PromiseBuiltinsAssembler::PerformPromiseAll(
Node* context, Node* constructor, Node* capability, Node* context, Node* constructor, Node* capability,
const IteratorRecord& iterator, Label* if_exception, const IteratorRecord& iterator, Label* if_exception,

View File

@ -121,9 +121,9 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
void PromiseSetHasHandler(Node* promise); void PromiseSetHasHandler(Node* promise);
void PromiseSetHandledHint(Node* promise); void PromiseSetHandledHint(Node* promise);
void InternalPerformPromiseThen(Node* context, Node* promise, void PerformPromiseThen(Node* context, Node* promise, Node* on_fulfilled,
Node* on_fulfill, Node* on_reject, Node* on_rejected,
Node* result); Node* result_promise_or_capability);
void InternalResolvePromise(Node* context, Node* promise, Node* result); void InternalResolvePromise(Node* context, Node* promise, Node* result);
@ -177,7 +177,6 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* promise_or_capability, Node* promise_or_capability,
PromiseReaction::Type type); PromiseReaction::Type type);
private:
Node* IsPromiseStatus(Node* actual, v8::Promise::PromiseState expected); Node* IsPromiseStatus(Node* actual, v8::Promise::PromiseState expected);
void PromiseSetStatus(Node* promise, v8::Promise::PromiseState status); void PromiseSetStatus(Node* promise, v8::Promise::PromiseState status);

View File

@ -2969,6 +2969,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceAsyncFunctionPromiseRelease(node); return ReduceAsyncFunctionPromiseRelease(node);
case Builtins::kPromisePrototypeCatch: case Builtins::kPromisePrototypeCatch:
return ReducePromisePrototypeCatch(node); return ReducePromisePrototypeCatch(node);
case Builtins::kPromisePrototypeThen:
return ReducePromisePrototypeThen(node);
default: default:
break; break;
} }
@ -3989,6 +3991,87 @@ Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) {
return Changed(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<Map> 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<Map> 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(); } Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); } Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }

View File

@ -102,6 +102,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceAsyncFunctionPromiseCreate(Node* node); Reduction ReduceAsyncFunctionPromiseCreate(Node* node);
Reduction ReduceAsyncFunctionPromiseRelease(Node* node); Reduction ReduceAsyncFunctionPromiseRelease(Node* node);
Reduction ReducePromisePrototypeCatch(Node* node); Reduction ReducePromisePrototypeCatch(Node* node);
Reduction ReducePromisePrototypeThen(Node* node);
Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason); Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason);

View File

@ -766,6 +766,13 @@ void JSGenericLowering::LowerJSDebugger(Node* node) {
ReplaceWithStubCall(node, callable, flags); 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(); } Zone* JSGenericLowering::zone() const { return graph()->zone(); }

View File

@ -543,44 +543,45 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
return OpParameter<CompareOperationHint>(op); return OpParameter<CompareOperationHint>(op);
} }
#define CACHED_OP_LIST(V) \ #define CACHED_OP_LIST(V) \
V(BitwiseOr, Operator::kNoProperties, 2, 1) \ V(BitwiseOr, Operator::kNoProperties, 2, 1) \
V(BitwiseXor, Operator::kNoProperties, 2, 1) \ V(BitwiseXor, Operator::kNoProperties, 2, 1) \
V(BitwiseAnd, Operator::kNoProperties, 2, 1) \ V(BitwiseAnd, Operator::kNoProperties, 2, 1) \
V(ShiftLeft, Operator::kNoProperties, 2, 1) \ V(ShiftLeft, Operator::kNoProperties, 2, 1) \
V(ShiftRight, Operator::kNoProperties, 2, 1) \ V(ShiftRight, Operator::kNoProperties, 2, 1) \
V(ShiftRightLogical, Operator::kNoProperties, 2, 1) \ V(ShiftRightLogical, Operator::kNoProperties, 2, 1) \
V(Subtract, Operator::kNoProperties, 2, 1) \ V(Subtract, Operator::kNoProperties, 2, 1) \
V(Multiply, Operator::kNoProperties, 2, 1) \ V(Multiply, Operator::kNoProperties, 2, 1) \
V(Divide, Operator::kNoProperties, 2, 1) \ V(Divide, Operator::kNoProperties, 2, 1) \
V(Modulus, Operator::kNoProperties, 2, 1) \ V(Modulus, Operator::kNoProperties, 2, 1) \
V(Exponentiate, Operator::kNoProperties, 2, 1) \ V(Exponentiate, Operator::kNoProperties, 2, 1) \
V(BitwiseNot, Operator::kNoProperties, 1, 1) \ V(BitwiseNot, Operator::kNoProperties, 1, 1) \
V(Decrement, Operator::kNoProperties, 1, 1) \ V(Decrement, Operator::kNoProperties, 1, 1) \
V(Increment, Operator::kNoProperties, 1, 1) \ V(Increment, Operator::kNoProperties, 1, 1) \
V(Negate, Operator::kNoProperties, 1, 1) \ V(Negate, Operator::kNoProperties, 1, 1) \
V(ToInteger, Operator::kNoProperties, 1, 1) \ V(ToInteger, Operator::kNoProperties, 1, 1) \
V(ToLength, Operator::kNoProperties, 1, 1) \ V(ToLength, Operator::kNoProperties, 1, 1) \
V(ToName, Operator::kNoProperties, 1, 1) \ V(ToName, Operator::kNoProperties, 1, 1) \
V(ToNumber, Operator::kNoProperties, 1, 1) \ V(ToNumber, Operator::kNoProperties, 1, 1) \
V(ToNumeric, Operator::kNoProperties, 1, 1) \ V(ToNumeric, Operator::kNoProperties, 1, 1) \
V(ToObject, Operator::kFoldable, 1, 1) \ V(ToObject, Operator::kFoldable, 1, 1) \
V(ToString, Operator::kNoProperties, 1, 1) \ V(ToString, Operator::kNoProperties, 1, 1) \
V(Create, Operator::kNoProperties, 2, 1) \ V(Create, Operator::kNoProperties, 2, 1) \
V(CreateIterResultObject, Operator::kEliminatable, 2, 1) \ V(CreateIterResultObject, Operator::kEliminatable, 2, 1) \
V(CreateKeyValueArray, Operator::kEliminatable, 2, 1) \ V(CreateKeyValueArray, Operator::kEliminatable, 2, 1) \
V(CreatePromise, Operator::kEliminatable, 0, 1) \ V(CreatePromise, Operator::kEliminatable, 0, 1) \
V(HasProperty, Operator::kNoProperties, 2, 1) \ V(HasProperty, Operator::kNoProperties, 2, 1) \
V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \ V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \
V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \ V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \
V(ForInEnumerate, Operator::kNoProperties, 1, 1) \ V(ForInEnumerate, Operator::kNoProperties, 1, 1) \
V(LoadMessage, Operator::kNoThrow | Operator::kNoWrite, 0, 1) \ V(LoadMessage, Operator::kNoThrow | Operator::kNoWrite, 0, 1) \
V(StoreMessage, Operator::kNoRead | Operator::kNoThrow, 1, 0) \ V(StoreMessage, Operator::kNoRead | Operator::kNoThrow, 1, 0) \
V(GeneratorRestoreContinuation, Operator::kNoThrow, 1, 1) \ V(GeneratorRestoreContinuation, Operator::kNoThrow, 1, 1) \
V(GeneratorRestoreContext, Operator::kNoThrow, 1, 1) \ V(GeneratorRestoreContext, Operator::kNoThrow, 1, 1) \
V(GeneratorRestoreInputOrDebugPos, Operator::kNoThrow, 1, 1) \ V(GeneratorRestoreInputOrDebugPos, Operator::kNoThrow, 1, 1) \
V(StackCheck, Operator::kNoWrite, 0, 0) \ V(StackCheck, Operator::kNoWrite, 0, 0) \
V(Debugger, Operator::kNoProperties, 0, 0) \ V(Debugger, Operator::kNoProperties, 0, 0) \
V(PerformPromiseThen, Operator::kNoDeopt | Operator::kNoThrow, 4, 1) \
V(GetSuperConstructor, Operator::kNoWrite, 1, 1) V(GetSuperConstructor, Operator::kNoWrite, 1, 1)
#define BINARY_OP_LIST(V) V(Add) #define BINARY_OP_LIST(V) V(Add)
@ -1157,6 +1158,10 @@ const Operator* JSOperatorBuilder::CreateBlockContext(
scope_info); // parameter scope_info); // parameter
} }
#undef BINARY_OP_LIST
#undef CACHED_OP_LIST
#undef COMPARE_OP_LIST
} // namespace compiler } // namespace compiler
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8

View File

@ -754,6 +754,8 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* StackCheck(); const Operator* StackCheck();
const Operator* Debugger(); const Operator* Debugger();
const Operator* PerformPromiseThen();
const Operator* CreateFunctionContext(int slot_count, ScopeType scope_type); const Operator* CreateFunctionContext(int slot_count, ScopeType scope_type);
const Operator* CreateCatchContext(const Handle<String>& name, const Operator* CreateCatchContext(const Handle<String>& name,
const Handle<ScopeInfo>& scope_info); const Handle<ScopeInfo>& scope_info);

View File

@ -195,6 +195,7 @@
V(JSGeneratorRestoreContext) \ V(JSGeneratorRestoreContext) \
V(JSGeneratorRestoreRegister) \ V(JSGeneratorRestoreRegister) \
V(JSGeneratorRestoreInputOrDebugPos) \ V(JSGeneratorRestoreInputOrDebugPos) \
V(JSPerformPromiseThen) \
V(JSStackCheck) \ V(JSStackCheck) \
V(JSDebugger) V(JSDebugger)

View File

@ -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::TypeJSDebugger(Node* node) { return Type::Any(); }
Type* Typer::Visitor::TypeJSPerformPromiseThen(Node* node) {
// TODO(turbofan): Introduce a Type::Promise here.
return Type::OtherObject();
}
// Simplified operators. // Simplified operators.
Type* Typer::Visitor::TypeBooleanNot(Node* node) { return Type::Boolean(); } Type* Typer::Visitor::TypeBooleanNot(Node* node) { return Type::Boolean(); }

View File

@ -853,6 +853,14 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckNotTyped(node); CheckNotTyped(node);
break; 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::kComment:
case IrOpcode::kDebugAbort: case IrOpcode::kDebugAbort:
case IrOpcode::kDebugBreak: case IrOpcode::kDebugBreak:

View File

@ -503,6 +503,7 @@ bool Map::IsJSObjectMap() const {
STATIC_ASSERT(LAST_JS_OBJECT_TYPE == LAST_TYPE); STATIC_ASSERT(LAST_JS_OBJECT_TYPE == LAST_TYPE);
return instance_type() >= FIRST_JS_OBJECT_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::IsJSArrayMap() const { return instance_type() == JS_ARRAY_TYPE; }
bool Map::IsJSFunctionMap() const { bool Map::IsJSFunctionMap() const {
return instance_type() == JS_FUNCTION_TYPE; return instance_type() == JS_FUNCTION_TYPE;

View File

@ -713,6 +713,7 @@ class Map : public HeapObject {
inline bool IsPrimitiveMap() const; inline bool IsPrimitiveMap() const;
inline bool IsJSReceiverMap() const; inline bool IsJSReceiverMap() const;
inline bool IsJSObjectMap() const; inline bool IsJSObjectMap() const;
inline bool IsJSPromiseMap() const;
inline bool IsJSArrayMap() const; inline bool IsJSArrayMap() const;
inline bool IsJSFunctionMap() const; inline bool IsJSFunctionMap() const;
inline bool IsStringMap() const; inline bool IsStringMap() const;

View File

@ -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);
})();