[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);
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,

View File

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

View File

@ -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) \

View File

@ -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,

View File

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

View File

@ -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<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(); }
Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }

View File

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

View File

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

View File

@ -543,44 +543,45 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
return OpParameter<CompareOperationHint>(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

View File

@ -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<String>& name,
const Handle<ScopeInfo>& scope_info);

View File

@ -195,6 +195,7 @@
V(JSGeneratorRestoreContext) \
V(JSGeneratorRestoreRegister) \
V(JSGeneratorRestoreInputOrDebugPos) \
V(JSPerformPromiseThen) \
V(JSStackCheck) \
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::TypeJSPerformPromiseThen(Node* node) {
// TODO(turbofan): Introduce a Type::Promise here.
return Type::OtherObject();
}
// Simplified operators.
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);
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:

View File

@ -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;

View File

@ -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;

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