[builtins] Also use the Promise#then protector for Promise#finally().
Add a fast-path to Promise#finally, which skips the "then" lookup of the Promise#then lookup chain is intact, similar to what we already do for Promise#catch. Drive-by-fix: Also use the @@species protector to speed up the lookup of the SpeciesConstructor in Promise#finally. Bug: v8:7253 Change-Id: If77e779a0188904effc4528beffc8f0bdd7c2efe Reviewed-on: https://chromium-review.googlesource.com/902283 Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Cr-Commit-Position: refs/heads/master@{#51116}
This commit is contained in:
parent
6703dacdd6
commit
d4f072ced3
@ -612,6 +612,62 @@ void PromiseBuiltinsAssembler::PromiseFulfill(
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... TArgs>
|
||||
Node* PromiseBuiltinsAssembler::InvokeThen(Node* native_context, Node* receiver,
|
||||
TArgs... args) {
|
||||
CSA_ASSERT(this, IsNativeContext(native_context));
|
||||
|
||||
VARIABLE(var_result, MachineRepresentation::kTagged);
|
||||
Label if_fast(this), if_slow(this, Label::kDeferred), done(this, &var_result);
|
||||
GotoIf(TaggedIsSmi(receiver), &if_slow);
|
||||
Node* const receiver_map = LoadMap(receiver);
|
||||
// We can skip the "then" lookup on {receiver} if it's [[Prototype]]
|
||||
// is the (initial) Promise.prototype and the Promise#then protector
|
||||
// is intact, as that guards the lookup path for the "then" property
|
||||
// on JSPromise instances which have the (initial) %PromisePrototype%.
|
||||
BranchIfPromiseThenLookupChainIntact(native_context, receiver_map, &if_fast,
|
||||
&if_slow);
|
||||
|
||||
BIND(&if_fast);
|
||||
{
|
||||
Node* const then =
|
||||
LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
|
||||
Node* const result =
|
||||
CallJS(CodeFactory::CallFunction(
|
||||
isolate(), ConvertReceiverMode::kNotNullOrUndefined),
|
||||
native_context, then, receiver, args...);
|
||||
var_result.Bind(result);
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
BIND(&if_slow);
|
||||
{
|
||||
Node* const then = GetProperty(native_context, receiver,
|
||||
isolate()->factory()->then_string());
|
||||
Node* const result = CallJS(
|
||||
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
|
||||
native_context, then, receiver, args...);
|
||||
var_result.Bind(result);
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
BIND(&done);
|
||||
return var_result.value();
|
||||
}
|
||||
|
||||
void PromiseBuiltinsAssembler::BranchIfPromiseSpeciesLookupChainIntact(
|
||||
Node* native_context, Node* promise_map, Label* if_fast, Label* if_slow) {
|
||||
CSA_ASSERT(this, IsNativeContext(native_context));
|
||||
CSA_ASSERT(this, IsJSPromiseMap(promise_map));
|
||||
|
||||
Node* const promise_prototype =
|
||||
LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
|
||||
GotoIfForceSlowPath(if_slow);
|
||||
GotoIfNot(WordEqual(LoadMapPrototype(promise_map), promise_prototype),
|
||||
if_slow);
|
||||
Branch(IsSpeciesProtectorCellInvalid(), if_slow, if_fast);
|
||||
}
|
||||
|
||||
void PromiseBuiltinsAssembler::BranchIfPromiseThenLookupChainIntact(
|
||||
Node* native_context, Node* receiver_map, Label* if_fast, Label* if_slow) {
|
||||
CSA_ASSERT(this, IsMap(receiver_map));
|
||||
@ -907,13 +963,9 @@ TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) {
|
||||
Node* const native_context = LoadNativeContext(context);
|
||||
Node* const promise_fun =
|
||||
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
|
||||
Node* const promise_prototype =
|
||||
LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
|
||||
Node* const promise_map = LoadMap(promise);
|
||||
GotoIfNot(WordEqual(LoadMapPrototype(promise_map), promise_prototype),
|
||||
&slow_constructor);
|
||||
Branch(IsSpeciesProtectorCellInvalid(), &slow_constructor,
|
||||
&fast_promise_capability);
|
||||
BranchIfPromiseSpeciesLookupChainIntact(
|
||||
native_context, promise_map, &fast_promise_capability, &slow_constructor);
|
||||
|
||||
BIND(&slow_constructor);
|
||||
Node* const constructor =
|
||||
@ -1015,38 +1067,8 @@ TF_BUILTIN(PromisePrototypeCatch, PromiseBuiltinsAssembler) {
|
||||
Node* const context = Parameter(Descriptor::kContext);
|
||||
|
||||
// 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
|
||||
VARIABLE(var_then, MachineRepresentation::kTagged);
|
||||
Label if_fast(this), if_slow(this, Label::kDeferred), done(this);
|
||||
GotoIf(TaggedIsSmi(receiver), &if_slow);
|
||||
Node* const receiver_map = LoadMap(receiver);
|
||||
// We can skip the "then" lookup on {receiver} if it's [[Prototype]]
|
||||
// is the (initial) Promise.prototype and the Promise#then protector
|
||||
// is intact, as that guards the lookup path for the "then" property
|
||||
// on JSPromise instances which have the (initial) %PromisePrototype%.
|
||||
Node* const native_context = LoadNativeContext(context);
|
||||
BranchIfPromiseThenLookupChainIntact(native_context, receiver_map, &if_fast,
|
||||
&if_slow);
|
||||
|
||||
BIND(&if_fast);
|
||||
{
|
||||
var_then.Bind(
|
||||
LoadContextElement(native_context, Context::PROMISE_THEN_INDEX));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
BIND(&if_slow);
|
||||
{
|
||||
var_then.Bind(
|
||||
GetProperty(context, receiver, isolate()->factory()->then_string()));
|
||||
Goto(&done);
|
||||
}
|
||||
|
||||
BIND(&done);
|
||||
Node* const then = var_then.value();
|
||||
Node* const result = CallJS(
|
||||
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
|
||||
context, then, receiver, on_fulfilled, on_rejected);
|
||||
Return(result);
|
||||
Return(InvokeThen(native_context, receiver, on_fulfilled, on_rejected));
|
||||
}
|
||||
|
||||
// ES section #sec-promiseresolvethenablejob
|
||||
@ -1461,16 +1483,11 @@ TF_BUILTIN(PromiseThenFinally, PromiseBuiltinsAssembler) {
|
||||
CallBuiltin(Builtins::kPromiseResolve, context, constructor, result);
|
||||
|
||||
// 7. Let valueThunk be equivalent to a function that returns value.
|
||||
Node* native_context = LoadNativeContext(context);
|
||||
Node* const native_context = LoadNativeContext(context);
|
||||
Node* const value_thunk = CreateValueThunkFunction(value, native_context);
|
||||
|
||||
// 8. Return ? Invoke(promise, "then", « valueThunk »).
|
||||
Node* const promise_then =
|
||||
GetProperty(context, promise, factory()->then_string());
|
||||
Node* const result_promise = CallJS(
|
||||
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
|
||||
context, promise_then, promise, value_thunk);
|
||||
Return(result_promise);
|
||||
Return(InvokeThen(native_context, promise, value_thunk));
|
||||
}
|
||||
|
||||
TF_BUILTIN(PromiseThrowerFinally, PromiseBuiltinsAssembler) {
|
||||
@ -1523,35 +1540,44 @@ TF_BUILTIN(PromiseCatchFinally, PromiseBuiltinsAssembler) {
|
||||
CallBuiltin(Builtins::kPromiseResolve, context, constructor, result);
|
||||
|
||||
// 7. Let thrower be equivalent to a function that throws reason.
|
||||
Node* native_context = LoadNativeContext(context);
|
||||
Node* const native_context = LoadNativeContext(context);
|
||||
Node* const thrower = CreateThrowerFunction(reason, native_context);
|
||||
|
||||
// 8. Return ? Invoke(promise, "then", « thrower »).
|
||||
Node* const promise_then =
|
||||
GetProperty(context, promise, factory()->then_string());
|
||||
Node* const result_promise = CallJS(
|
||||
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
|
||||
context, promise_then, promise, thrower);
|
||||
Return(result_promise);
|
||||
Return(InvokeThen(native_context, promise, thrower));
|
||||
}
|
||||
|
||||
TF_BUILTIN(PromisePrototypeFinally, PromiseBuiltinsAssembler) {
|
||||
CSA_ASSERT_JS_ARGC_EQ(this, 1);
|
||||
|
||||
// 1. Let promise be the this value.
|
||||
Node* const promise = Parameter(Descriptor::kReceiver);
|
||||
Node* const receiver = Parameter(Descriptor::kReceiver);
|
||||
Node* const on_finally = Parameter(Descriptor::kOnFinally);
|
||||
Node* const context = Parameter(Descriptor::kContext);
|
||||
|
||||
// 2. If Type(promise) is not Object, throw a TypeError exception.
|
||||
ThrowIfNotJSReceiver(context, promise, MessageTemplate::kCalledOnNonObject,
|
||||
ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
|
||||
"Promise.prototype.finally");
|
||||
|
||||
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
|
||||
Node* const native_context = LoadNativeContext(context);
|
||||
Node* const promise_fun =
|
||||
LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
|
||||
Node* const constructor = SpeciesConstructor(context, promise, promise_fun);
|
||||
VARIABLE(var_constructor, MachineRepresentation::kTagged, promise_fun);
|
||||
Label slow_constructor(this, Label::kDeferred), done_constructor(this);
|
||||
Node* const receiver_map = LoadMap(receiver);
|
||||
GotoIfNot(IsJSPromiseMap(receiver_map), &slow_constructor);
|
||||
BranchIfPromiseSpeciesLookupChainIntact(native_context, receiver_map,
|
||||
&done_constructor, &slow_constructor);
|
||||
BIND(&slow_constructor);
|
||||
{
|
||||
Node* const constructor =
|
||||
SpeciesConstructor(context, receiver, promise_fun);
|
||||
var_constructor.Bind(constructor);
|
||||
Goto(&done_constructor);
|
||||
}
|
||||
BIND(&done_constructor);
|
||||
Node* const constructor = var_constructor.value();
|
||||
|
||||
// 4. Assert: IsConstructor(C) is true.
|
||||
CSA_ASSERT(this, IsConstructor(constructor));
|
||||
@ -1593,13 +1619,8 @@ TF_BUILTIN(PromisePrototypeFinally, PromiseBuiltinsAssembler) {
|
||||
|
||||
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
|
||||
BIND(&perform_finally);
|
||||
Node* const promise_then =
|
||||
GetProperty(context, promise, factory()->then_string());
|
||||
Node* const result_promise = CallJS(
|
||||
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
|
||||
context, promise_then, promise, var_then_finally.value(),
|
||||
var_catch_finally.value());
|
||||
Return(result_promise);
|
||||
Return(InvokeThen(native_context, receiver, var_then_finally.value(),
|
||||
var_catch_finally.value()));
|
||||
}
|
||||
|
||||
TF_BUILTIN(ResolveNativePromise, PromiseBuiltinsAssembler) {
|
||||
|
@ -131,6 +131,14 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
|
||||
void PromiseFulfill(Node* context, Node* promise, Node* result,
|
||||
v8::Promise::PromiseState status);
|
||||
|
||||
// We can shortcut the SpeciesConstructor on {promise_map} if it's
|
||||
// [[Prototype]] is the (initial) Promise.prototype and the @@species
|
||||
// protector is intact, as that guards the lookup path for the "constructor"
|
||||
// property on JSPromise instances which have the %PromisePrototype%.
|
||||
void BranchIfPromiseSpeciesLookupChainIntact(Node* native_context,
|
||||
Node* promise_map,
|
||||
Label* if_fast, Label* if_slow);
|
||||
|
||||
// We can skip the "then" lookup on {receiver_map} if it's [[Prototype]]
|
||||
// is the (initial) Promise.prototype and the Promise#then() protector
|
||||
// is intact, as that guards the lookup path for the "then" property
|
||||
@ -139,6 +147,9 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
|
||||
Node* receiver_map, Label* if_fast,
|
||||
Label* if_slow);
|
||||
|
||||
template <typename... TArgs>
|
||||
Node* InvokeThen(Node* native_context, Node* receiver, TArgs... args);
|
||||
|
||||
void BranchIfAccessCheckFailed(Node* context, Node* native_context,
|
||||
Node* promise_constructor, Node* executor,
|
||||
Label* if_noaccess);
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "src/compiler/js-call-reducer.h"
|
||||
|
||||
#include "src/api.h"
|
||||
#include "src/builtins/builtins-promise-gen.h"
|
||||
#include "src/builtins/builtins-utils.h"
|
||||
#include "src/code-factory.h"
|
||||
#include "src/code-stubs.h"
|
||||
@ -2968,6 +2969,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
|
||||
return ReduceAsyncFunctionPromiseRelease(node);
|
||||
case Builtins::kPromisePrototypeCatch:
|
||||
return ReducePromisePrototypeCatch(node);
|
||||
case Builtins::kPromisePrototypeFinally:
|
||||
return ReducePromisePrototypeFinally(node);
|
||||
case Builtins::kPromisePrototypeThen:
|
||||
return ReducePromisePrototypeThen(node);
|
||||
default:
|
||||
@ -3932,6 +3935,7 @@ Reduction JSCallReducer::ReduceAsyncFunctionPromiseRelease(Node* node) {
|
||||
return Replace(value);
|
||||
}
|
||||
|
||||
// ES section #sec-promise.prototype.catch
|
||||
Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
||||
CallParameters const& p = CallParametersOf(node->op());
|
||||
@ -3991,7 +3995,150 @@ Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) {
|
||||
node, javascript()->Call(2 + arity, p.frequency(), p.feedback(),
|
||||
ConvertReceiverMode::kNotNullOrUndefined,
|
||||
p.speculation_mode()));
|
||||
return Changed(node);
|
||||
Reduction const reduction = ReducePromisePrototypeThen(node);
|
||||
return reduction.Changed() ? reduction : Changed(node);
|
||||
}
|
||||
|
||||
// ES section #sec-promise.prototype.finally
|
||||
Reduction JSCallReducer::ReducePromisePrototypeFinally(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
||||
CallParameters const& p = CallParametersOf(node->op());
|
||||
int arity = static_cast<int>(p.arity() - 2);
|
||||
Node* receiver = NodeProperties::GetValueInput(node, 1);
|
||||
Node* on_finally = arity >= 1 ? NodeProperties::GetValueInput(node, 2)
|
||||
: jsgraph()->UndefinedConstant();
|
||||
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 that the Promise#then protector is intact. This protector guards
|
||||
// that all JSPromise instances whose [[Prototype]] is the initial
|
||||
// %PromisePrototype% yield the initial %PromisePrototype%.then method
|
||||
// when looking up "then".
|
||||
if (!isolate()->IsPromiseThenLookupChainIntact()) return NoChange();
|
||||
|
||||
// Also check that the @@species protector is intact, which guards the
|
||||
// lookup of "constructor" on JSPromise instances, whoch [[Prototype]] is
|
||||
// the initial %PromisePrototype%, and the Symbol.species lookup on the
|
||||
// %PromisePrototype%.
|
||||
if (!isolate()->IsSpeciesLookupChainIntact()) return NoChange();
|
||||
|
||||
// Check if we know something about {receiver} already.
|
||||
ZoneHandleSet<Map> receiver_maps;
|
||||
NodeProperties::InferReceiverMapsResult result =
|
||||
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
|
||||
if (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()->promise_then_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 (result == NodeProperties::kUnreliableReceiverMaps) {
|
||||
effect =
|
||||
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
|
||||
receiver_maps, p.feedback()),
|
||||
receiver, effect, control);
|
||||
}
|
||||
|
||||
// Check if {on_finally} is callable, and if so wrap it into appropriate
|
||||
// closures that perform the finalization.
|
||||
Node* check = graph()->NewNode(simplified()->ObjectIsCallable(), on_finally);
|
||||
Node* branch =
|
||||
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
|
||||
|
||||
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
|
||||
Node* etrue = effect;
|
||||
Node* catch_true;
|
||||
Node* then_true;
|
||||
{
|
||||
Node* context = jsgraph()->HeapConstant(native_context());
|
||||
Node* constructor = jsgraph()->HeapConstant(
|
||||
handle(native_context()->promise_function(), isolate()));
|
||||
|
||||
// Allocate shared context for the closures below.
|
||||
context = etrue = graph()->NewNode(
|
||||
javascript()->CreateFunctionContext(
|
||||
PromiseBuiltinsAssembler::kPromiseFinallyContextLength,
|
||||
FUNCTION_SCOPE),
|
||||
context, context, etrue, if_true);
|
||||
etrue =
|
||||
graph()->NewNode(simplified()->StoreField(AccessBuilder::ForContextSlot(
|
||||
PromiseBuiltinsAssembler::kOnFinallySlot)),
|
||||
context, on_finally, etrue, if_true);
|
||||
etrue =
|
||||
graph()->NewNode(simplified()->StoreField(AccessBuilder::ForContextSlot(
|
||||
PromiseBuiltinsAssembler::kConstructorSlot)),
|
||||
context, constructor, etrue, if_true);
|
||||
|
||||
// Allocate the closure for the reject case.
|
||||
Handle<SharedFunctionInfo> catch_finally(
|
||||
native_context()->promise_catch_finally_shared_fun(), isolate());
|
||||
catch_true = etrue = graph()->NewNode(
|
||||
javascript()->CreateClosure(catch_finally), context, etrue, if_true);
|
||||
|
||||
// Allocate the closure for the fulfill case.
|
||||
Handle<SharedFunctionInfo> then_finally(
|
||||
native_context()->promise_then_finally_shared_fun(), isolate());
|
||||
then_true = etrue = graph()->NewNode(
|
||||
javascript()->CreateClosure(then_finally), context, etrue, if_true);
|
||||
}
|
||||
|
||||
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
|
||||
Node* efalse = effect;
|
||||
Node* catch_false = on_finally;
|
||||
Node* then_false = on_finally;
|
||||
|
||||
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
|
||||
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
|
||||
Node* catch_finally =
|
||||
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
|
||||
catch_true, catch_false, control);
|
||||
Node* then_finally =
|
||||
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
|
||||
then_true, then_false, control);
|
||||
|
||||
// At this point we definitely know that {receiver} has one of the
|
||||
// {receiver_maps}, so insert a MapGuard as a hint for the lowering
|
||||
// of the call to "then" below.
|
||||
effect = graph()->NewNode(simplified()->MapGuard(receiver_maps), receiver,
|
||||
effect, control);
|
||||
|
||||
// Massage the {node} to call "then" instead by first removing all inputs
|
||||
// following the onFinally parameter, and then replacing the only parameter
|
||||
// input with the {on_finally} value.
|
||||
Node* target = jsgraph()->Constant(handle(native_context()->promise_then()));
|
||||
NodeProperties::ReplaceValueInput(node, target, 0);
|
||||
NodeProperties::ReplaceEffectInput(node, effect);
|
||||
NodeProperties::ReplaceControlInput(node, control);
|
||||
for (; arity > 2; --arity) node->RemoveInput(2);
|
||||
for (; arity < 2; ++arity)
|
||||
node->InsertInput(graph()->zone(), 2, then_finally);
|
||||
node->ReplaceInput(2, then_finally);
|
||||
node->ReplaceInput(3, catch_finally);
|
||||
NodeProperties::ChangeOp(
|
||||
node, javascript()->Call(2 + arity, p.frequency(), p.feedback(),
|
||||
ConvertReceiverMode::kNotNullOrUndefined,
|
||||
p.speculation_mode()));
|
||||
Reduction const reduction = ReducePromisePrototypeThen(node);
|
||||
return reduction.Changed() ? reduction : Changed(node);
|
||||
}
|
||||
|
||||
Reduction JSCallReducer::ReducePromisePrototypeThen(Node* node) {
|
||||
|
@ -102,6 +102,7 @@ class JSCallReducer final : public AdvancedReducer {
|
||||
Reduction ReduceAsyncFunctionPromiseCreate(Node* node);
|
||||
Reduction ReduceAsyncFunctionPromiseRelease(Node* node);
|
||||
Reduction ReducePromisePrototypeCatch(Node* node);
|
||||
Reduction ReducePromisePrototypeFinally(Node* node);
|
||||
Reduction ReducePromisePrototypeThen(Node* node);
|
||||
|
||||
Reduction ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason);
|
||||
|
@ -901,49 +901,59 @@ Reduction JSCreateLowering::ReduceJSCreateClosure(Node* node) {
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
Node* control = NodeProperties::GetControlInput(node);
|
||||
Node* context = NodeProperties::GetContextInput(node);
|
||||
Node* feedback_vector = jsgraph()->UndefinedConstant();
|
||||
|
||||
// Use inline allocation of closures only for instantiation sites that have
|
||||
// seen more than one instantiation, this simplifies the generated code and
|
||||
// also serves as a heuristic of which allocation sites benefit from it.
|
||||
FeedbackSlot slot(FeedbackVector::ToSlot(p.feedback().index()));
|
||||
Handle<Cell> vector_cell(Cell::cast(p.feedback().vector()->Get(slot)));
|
||||
if (vector_cell->map() == isolate()->heap()->many_closures_cell_map()) {
|
||||
Handle<Map> function_map(
|
||||
Map::cast(native_context()->get(shared->function_map_index())));
|
||||
Node* lazy_compile_builtin = jsgraph()->HeapConstant(
|
||||
handle(isolate()->builtins()->builtin(Builtins::kCompileLazy)));
|
||||
DCHECK(!function_map->IsInobjectSlackTrackingInProgress());
|
||||
DCHECK(!function_map->is_dictionary_map());
|
||||
|
||||
// Emit code to allocate the JSFunction instance.
|
||||
STATIC_ASSERT(JSFunction::kSizeWithoutPrototype == 7 * kPointerSize);
|
||||
AllocationBuilder a(jsgraph(), effect, control);
|
||||
a.Allocate(function_map->instance_size());
|
||||
a.Store(AccessBuilder::ForMap(), function_map);
|
||||
a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(),
|
||||
jsgraph()->EmptyFixedArrayConstant());
|
||||
a.Store(AccessBuilder::ForJSObjectElements(),
|
||||
jsgraph()->EmptyFixedArrayConstant());
|
||||
a.Store(AccessBuilder::ForJSFunctionSharedFunctionInfo(), shared);
|
||||
a.Store(AccessBuilder::ForJSFunctionContext(), context);
|
||||
a.Store(AccessBuilder::ForJSFunctionFeedbackVector(), vector_cell);
|
||||
a.Store(AccessBuilder::ForJSFunctionCode(), lazy_compile_builtin);
|
||||
STATIC_ASSERT(JSFunction::kSizeWithoutPrototype == 7 * kPointerSize);
|
||||
if (function_map->has_prototype_slot()) {
|
||||
a.Store(AccessBuilder::ForJSFunctionPrototypeOrInitialMap(),
|
||||
jsgraph()->TheHoleConstant());
|
||||
STATIC_ASSERT(JSFunction::kSizeWithPrototype == 8 * kPointerSize);
|
||||
if (p.feedback().IsValid()) {
|
||||
FeedbackSlot slot(FeedbackVector::ToSlot(p.feedback().index()));
|
||||
Handle<Cell> vector_cell(Cell::cast(p.feedback().vector()->Get(slot)));
|
||||
if (vector_cell->map() != isolate()->heap()->many_closures_cell_map()) {
|
||||
return NoChange();
|
||||
}
|
||||
for (int i = 0; i < function_map->GetInObjectProperties(); i++) {
|
||||
a.Store(AccessBuilder::ForJSObjectInObjectProperty(function_map, i),
|
||||
jsgraph()->UndefinedConstant());
|
||||
}
|
||||
RelaxControls(node);
|
||||
a.FinishAndChange(node);
|
||||
return Changed(node);
|
||||
feedback_vector = jsgraph()->HeapConstant(vector_cell);
|
||||
} else {
|
||||
// CreateClosure without a feedback vector is only allowed for
|
||||
// native (builtin) functions.
|
||||
DCHECK(shared->native());
|
||||
}
|
||||
|
||||
return NoChange();
|
||||
Handle<Map> function_map(
|
||||
Map::cast(native_context()->get(shared->function_map_index())));
|
||||
Node* lazy_compile_builtin = jsgraph()->HeapConstant(handle(
|
||||
shared->native() ? shared->code()
|
||||
: isolate()->builtins()->builtin(Builtins::kCompileLazy),
|
||||
isolate()));
|
||||
DCHECK(!function_map->IsInobjectSlackTrackingInProgress());
|
||||
DCHECK(!function_map->is_dictionary_map());
|
||||
|
||||
// Emit code to allocate the JSFunction instance.
|
||||
STATIC_ASSERT(JSFunction::kSizeWithoutPrototype == 7 * kPointerSize);
|
||||
AllocationBuilder a(jsgraph(), effect, control);
|
||||
a.Allocate(function_map->instance_size(), p.pretenure(), Type::Function());
|
||||
a.Store(AccessBuilder::ForMap(), function_map);
|
||||
a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(),
|
||||
jsgraph()->EmptyFixedArrayConstant());
|
||||
a.Store(AccessBuilder::ForJSObjectElements(),
|
||||
jsgraph()->EmptyFixedArrayConstant());
|
||||
a.Store(AccessBuilder::ForJSFunctionSharedFunctionInfo(), shared);
|
||||
a.Store(AccessBuilder::ForJSFunctionContext(), context);
|
||||
a.Store(AccessBuilder::ForJSFunctionFeedbackVector(), feedback_vector);
|
||||
a.Store(AccessBuilder::ForJSFunctionCode(), lazy_compile_builtin);
|
||||
STATIC_ASSERT(JSFunction::kSizeWithoutPrototype == 7 * kPointerSize);
|
||||
if (function_map->has_prototype_slot()) {
|
||||
a.Store(AccessBuilder::ForJSFunctionPrototypeOrInitialMap(),
|
||||
jsgraph()->TheHoleConstant());
|
||||
STATIC_ASSERT(JSFunction::kSizeWithPrototype == 8 * kPointerSize);
|
||||
}
|
||||
for (int i = 0; i < function_map->GetInObjectProperties(); i++) {
|
||||
a.Store(AccessBuilder::ForJSObjectInObjectProperty(function_map, i),
|
||||
jsgraph()->UndefinedConstant());
|
||||
}
|
||||
RelaxControls(node);
|
||||
a.FinishAndChange(node);
|
||||
return Changed(node);
|
||||
}
|
||||
|
||||
Reduction JSCreateLowering::ReduceJSCreateIterResultObject(Node* node) {
|
||||
|
@ -651,9 +651,10 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
|
||||
const Operator* CreateArguments(CreateArgumentsType type);
|
||||
const Operator* CreateArray(size_t arity, Handle<AllocationSite> site);
|
||||
const Operator* CreateBoundFunction(size_t arity, Handle<Map> map);
|
||||
const Operator* CreateClosure(Handle<SharedFunctionInfo> shared_info,
|
||||
VectorSlotPair const& feedback,
|
||||
PretenureFlag pretenure);
|
||||
const Operator* CreateClosure(
|
||||
Handle<SharedFunctionInfo> shared_info,
|
||||
VectorSlotPair const& feedback = VectorSlotPair(),
|
||||
PretenureFlag pretenure = NOT_TENURED);
|
||||
const Operator* CreateIterResultObject();
|
||||
const Operator* CreateKeyValueArray();
|
||||
const Operator* CreatePromise();
|
||||
|
@ -0,0 +1,19 @@
|
||||
// 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 foo(p) { return p.finally(x => x); }
|
||||
|
||||
const a = Promise.resolve(1);
|
||||
|
||||
foo(a);
|
||||
foo(a);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
foo(a);
|
||||
|
||||
let custom_then_called = false;
|
||||
a.__proto__.then = function() { custom_then_called = true; }
|
||||
foo(a);
|
||||
assertTrue(custom_then_called);
|
@ -0,0 +1,19 @@
|
||||
// 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 foo(p) { return p.finally(x => x); }
|
||||
|
||||
const a = Promise.resolve(1);
|
||||
|
||||
foo(a);
|
||||
foo(a);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
foo(a);
|
||||
|
||||
let custom_then_called = false;
|
||||
a.then = function() { custom_then_called = true; }
|
||||
foo(a);
|
||||
assertTrue(custom_then_called);
|
27
test/mjsunit/compiler/promise-prototype-finally-subclass.js
Normal file
27
test/mjsunit/compiler/promise-prototype-finally-subclass.js
Normal file
@ -0,0 +1,27 @@
|
||||
// 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
|
||||
|
||||
let custom_then_called = false;
|
||||
|
||||
function foo(p) {
|
||||
custom_then_called = false;
|
||||
p.finally(x => x);
|
||||
return custom_then_called;
|
||||
}
|
||||
|
||||
class MyPromise extends Promise {
|
||||
then(onFulfilled, onRejected) {
|
||||
custom_then_called = true;
|
||||
return super.then(onFulfilled, onRejected);
|
||||
}
|
||||
}
|
||||
|
||||
const a = MyPromise.resolve(1);
|
||||
|
||||
assertTrue(foo(a));
|
||||
assertTrue(foo(a));
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
assertTrue(foo(a));
|
41
test/mjsunit/compiler/promise-prototype-finally.js
Normal file
41
test/mjsunit/compiler/promise-prototype-finally.js
Normal file
@ -0,0 +1,41 @@
|
||||
// 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.finally(); }
|
||||
foo(p);
|
||||
foo(p);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
foo(p);
|
||||
})();
|
||||
|
||||
(function() {
|
||||
const p = Promise.resolve(1);
|
||||
function foo(p) { return p.finally(x => x); }
|
||||
foo(p);
|
||||
foo(p);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
foo(p);
|
||||
})();
|
||||
|
||||
(function() {
|
||||
const p = Promise.resolve(1);
|
||||
function foo(p, f) { return p.finally(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.finally(f).finally(f); }
|
||||
foo(p, x => x);
|
||||
foo(p, x => x);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
foo(p, x => x);
|
||||
})();
|
Loading…
Reference in New Issue
Block a user