Commit 594803c9 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Inline Function#bind in more cases.

So far the inlining of Function#bind into TurboFan optimized code was
limited to cases where TurboFan could infer the constant JSFunction that
was bound. However we can easily extend that to cover JSBoundFunction as
well, and obviously also take the LOAD_IC feedback if we don't have a
known JSFunction or JSBoundFunction.

This adds a new operator JSCreateBoundFunction that contains the logic
for the creation of the bound function object and the arguments.

On the micro-benchmarks we go from

  functionBindParameter0: 1239 ms.
  functionBindConstant0: 478 ms.
  functionBindBoundConstant0: 1256 ms.
  functionBindParameter1: 1278 ms.
  functionBindConstant1: 475 ms.
  functionBindBoundConstant1: 1253 ms.
  functionBindParameter2: 1431 ms.
  functionBindConstant2: 616 ms.
  functionBindBoundConstant2: 1437 ms.

to

  functionBindParameter0: 462 ms.
  functionBindConstant0: 485 ms.
  functionBindBoundConstant0: 474 ms.
  functionBindParameter1: 478 ms.
  functionBindConstant1: 474 ms.
  functionBindBoundConstant1: 474 ms.
  functionBindParameter2: 617 ms.
  functionBindConstant2: 614 ms.
  functionBindBoundConstant2: 616 ms.

which is a ~2.5x improvement. On the jshint benchmark in the
web-tooling-benchmark we observe a 2-3% improvement, which corresponds
to the time we had seen it running in the generic version.

Bug: v8:6936, v8:6946
Change-Id: I940d13220ff35ae602dbaa33349ba4bbe0c9a9d3
Reviewed-on: https://chromium-review.googlesource.com/723080Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48639}
parent 2b9a6d89
......@@ -1661,88 +1661,6 @@ Reduction JSBuiltinReducer::ReduceDateGetTime(Node* node) {
return NoChange();
}
// ES6 section 19.2.3.2 Function.prototype.bind ( thisArg, ...args )
Reduction JSBuiltinReducer::ReduceFunctionBind(Node* node) {
// Value inputs to the {node} are as follows:
//
// - target, which is Function.prototype.bind JSFunction
// - receiver, which is the [[BoundTargetFunction]]
// - bound_this (optional), which is the [[BoundThis]]
// - and all the remaining value inouts are [[BoundArguments]]
Node* receiver = NodeProperties::GetValueInput(node, 1);
Type* receiver_type = NodeProperties::GetType(receiver);
Node* bound_this = (node->op()->ValueInputCount() < 3)
? jsgraph()->UndefinedConstant()
: NodeProperties::GetValueInput(node, 2);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (receiver_type->IsHeapConstant() &&
receiver_type->AsHeapConstant()->Value()->IsJSFunction()) {
Handle<JSFunction> target_function =
Handle<JSFunction>::cast(receiver_type->AsHeapConstant()->Value());
// Check that the "length" property on the {target_function} is the
// default JSFunction accessor.
LookupIterator length_lookup(target_function, factory()->length_string(),
target_function, LookupIterator::OWN);
if (length_lookup.state() != LookupIterator::ACCESSOR ||
!length_lookup.GetAccessors()->IsAccessorInfo()) {
return NoChange();
}
// Check that the "name" property on the {target_function} is the
// default JSFunction accessor.
LookupIterator name_lookup(target_function, factory()->name_string(),
target_function, LookupIterator::OWN);
if (name_lookup.state() != LookupIterator::ACCESSOR ||
!name_lookup.GetAccessors()->IsAccessorInfo()) {
return NoChange();
}
// Determine the prototype of the {target_function}.
Handle<Object> prototype(target_function->map()->prototype(), isolate());
// Setup the map for the JSBoundFunction instance.
Handle<Map> map = target_function->IsConstructor()
? isolate()->bound_function_with_constructor_map()
: isolate()->bound_function_without_constructor_map();
if (map->prototype() != *prototype) {
map = Map::TransitionToPrototype(map, prototype);
}
DCHECK_EQ(target_function->IsConstructor(), map->is_constructor());
// Create the [[BoundArguments]] for the result.
Node* bound_arguments = jsgraph()->EmptyFixedArrayConstant();
if (node->op()->ValueInputCount() > 3) {
int const length = node->op()->ValueInputCount() - 3;
AllocationBuilder a(jsgraph(), effect, control);
a.AllocateArray(length, factory()->fixed_array_map());
for (int i = 0; i < length; ++i) {
a.Store(AccessBuilder::ForFixedArraySlot(i),
NodeProperties::GetValueInput(node, 3 + i));
}
bound_arguments = effect = a.Finish();
}
// Create the JSBoundFunction result.
AllocationBuilder a(jsgraph(), effect, control);
a.Allocate(JSBoundFunction::kSize, NOT_TENURED, Type::BoundFunction());
a.Store(AccessBuilder::ForMap(), map);
a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(),
jsgraph()->EmptyFixedArrayConstant());
a.Store(AccessBuilder::ForJSObjectElements(),
jsgraph()->EmptyFixedArrayConstant());
a.Store(AccessBuilder::ForJSBoundFunctionBoundTargetFunction(), receiver);
a.Store(AccessBuilder::ForJSBoundFunctionBoundThis(), bound_this);
a.Store(AccessBuilder::ForJSBoundFunctionBoundArguments(), bound_arguments);
Node* value = effect = a.Finish();
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
return NoChange();
}
// ES6 section 18.2.2 isFinite ( number )
Reduction JSBuiltinReducer::ReduceGlobalIsFinite(Node* node) {
JSCallReduction r(node);
......@@ -2926,8 +2844,6 @@ Reduction JSBuiltinReducer::Reduce(Node* node) {
return ReduceDateNow(node);
case kDateGetTime:
return ReduceDateGetTime(node);
case kFunctionBind:
return ReduceFunctionBind(node);
case kGlobalIsFinite:
reduction = ReduceGlobalIsFinite(node);
break;
......
......@@ -67,7 +67,6 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final
InstanceType collection_iterator_instance_type_last);
Reduction ReduceDateNow(Node* node);
Reduction ReduceDateGetTime(Node* node);
Reduction ReduceFunctionBind(Node* node);
Reduction ReduceGlobalIsFinite(Node* node);
Reduction ReduceGlobalIsNaN(Node* node);
Reduction ReduceMapHas(Node* node);
......
......@@ -28,6 +28,7 @@ bool CanBePrimitive(Node* node) {
case IrOpcode::kJSCreate:
case IrOpcode::kJSCreateArguments:
case IrOpcode::kJSCreateArray:
case IrOpcode::kJSCreateBoundFunction:
case IrOpcode::kJSCreateClosure:
case IrOpcode::kJSCreateEmptyLiteralArray:
case IrOpcode::kJSCreateEmptyLiteralObject:
......@@ -301,6 +302,106 @@ Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
return reduction.Changed() ? reduction : Changed(node);
}
// ES section #sec-function.prototype.bind
Reduction JSCallReducer::ReduceFunctionPrototypeBind(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
// Value inputs to the {node} are as follows:
//
// - target, which is Function.prototype.bind JSFunction
// - receiver, which is the [[BoundTargetFunction]]
// - bound_this (optional), which is the [[BoundThis]]
// - and all the remaining value inouts are [[BoundArguments]]
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* bound_this = (node->op()->ValueInputCount() < 3)
? jsgraph()->UndefinedConstant()
: NodeProperties::GetValueInput(node, 2);
Node* context = NodeProperties::GetContextInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Ensure that the {receiver} is known to be a JSBoundFunction or
// a JSFunction with the same [[Prototype]], and all maps we've
// seen for the {receiver} so far indicate that {receiver} is
// definitely a constructor or not a constructor.
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());
bool const is_constructor = receiver_maps[0]->is_constructor();
Handle<Object> const prototype(receiver_maps[0]->prototype(), isolate());
for (Handle<Map> const receiver_map : receiver_maps) {
// Check for consistency among the {receiver_maps}.
STATIC_ASSERT(LAST_TYPE == LAST_FUNCTION_TYPE);
if (receiver_map->prototype() != *prototype) return NoChange();
if (receiver_map->is_constructor() != is_constructor) return NoChange();
if (receiver_map->instance_type() < FIRST_FUNCTION_TYPE) return NoChange();
// Disallow binding of slow-mode functions. We need to figure out
// whether the length and name property are in the original state.
if (receiver_map->is_dictionary_map()) return NoChange();
// Check whether the length and name properties are still present
// as AccessorInfo objects. In that case, their values can be
// recomputed even if the actual value of the object changes.
// This mirrors the checks done in builtins-function-gen.cc at
// runtime otherwise.
Handle<DescriptorArray> descriptors(receiver_map->instance_descriptors(),
isolate());
if (descriptors->length() < 2) return NoChange();
if (descriptors->GetKey(JSFunction::kLengthDescriptorIndex) !=
isolate()->heap()->length_string()) {
return NoChange();
}
if (!descriptors->GetValue(JSFunction::kLengthDescriptorIndex)
->IsAccessorInfo()) {
return NoChange();
}
if (descriptors->GetKey(JSFunction::kNameDescriptorIndex) !=
isolate()->heap()->name_string()) {
return NoChange();
}
if (!descriptors->GetValue(JSFunction::kNameDescriptorIndex)
->IsAccessorInfo()) {
return NoChange();
}
}
// Setup the map for the resulting JSBoundFunction with the
// correct instance {prototype}.
Handle<Map> map(
is_constructor
? native_context()->bound_function_with_constructor_map()
: native_context()->bound_function_without_constructor_map(),
isolate());
if (map->prototype() != *prototype) {
map = Map::TransitionToPrototype(map, prototype);
}
// Make sure we can rely on the {receiver_maps}.
if (result == NodeProperties::kUnreliableReceiverMaps) {
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver,
effect, control);
}
// Replace the {node} with a JSCreateBoundFunction.
int const arity = std::max(0, node->op()->ValueInputCount() - 3);
int const input_count = 2 + arity + 3;
Node** inputs = graph()->zone()->NewArray<Node*>(input_count);
inputs[0] = receiver;
inputs[1] = bound_this;
for (int i = 0; i < arity; ++i) {
inputs[2 + i] = NodeProperties::GetValueInput(node, 3 + i);
}
inputs[2 + arity + 0] = context;
inputs[2 + arity + 1] = effect;
inputs[2 + arity + 2] = control;
Node* value = effect = graph()->NewNode(
javascript()->CreateBoundFunction(arity, map), input_count, inputs);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// ES6 section 19.2.3.3 Function.prototype.call (thisArg, ...args)
Reduction JSCallReducer::ReduceFunctionPrototypeCall(Node* node) {
......@@ -1484,6 +1585,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceBooleanConstructor(node);
case Builtins::kFunctionPrototypeApply:
return ReduceFunctionPrototypeApply(node);
case Builtins::kFastFunctionPrototypeBind:
return ReduceFunctionPrototypeBind(node);
case Builtins::kFunctionPrototypeCall:
return ReduceFunctionPrototypeCall(node);
case Builtins::kFunctionPrototypeHasInstance:
......
......@@ -58,6 +58,7 @@ class JSCallReducer final : public AdvancedReducer {
Node* node, Handle<FunctionTemplateInfo> function_template_info);
Reduction ReduceNumberConstructor(Node* node);
Reduction ReduceFunctionPrototypeApply(Node* node);
Reduction ReduceFunctionPrototypeBind(Node* node);
Reduction ReduceFunctionPrototypeCall(Node* node);
Reduction ReduceFunctionPrototypeHasInstance(Node* node);
Reduction ReduceObjectConstructor(Node* node);
......
......@@ -137,6 +137,8 @@ Reduction JSCreateLowering::Reduce(Node* node) {
return ReduceJSCreateArguments(node);
case IrOpcode::kJSCreateArray:
return ReduceJSCreateArray(node);
case IrOpcode::kJSCreateBoundFunction:
return ReduceJSCreateBoundFunction(node);
case IrOpcode::kJSCreateClosure:
return ReduceJSCreateClosure(node);
case IrOpcode::kJSCreateIterResultObject:
......@@ -848,6 +850,46 @@ Reduction JSCreateLowering::ReduceJSCreateArray(Node* node) {
return ReduceNewArrayToStubCall(node, site);
}
Reduction JSCreateLowering::ReduceJSCreateBoundFunction(Node* node) {
DCHECK_EQ(IrOpcode::kJSCreateBoundFunction, node->opcode());
CreateBoundFunctionParameters const& p =
CreateBoundFunctionParametersOf(node->op());
int const arity = static_cast<int>(p.arity());
Handle<Map> const map = p.map();
Node* bound_target_function = NodeProperties::GetValueInput(node, 0);
Node* bound_this = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Create the [[BoundArguments]] for the result.
Node* bound_arguments = jsgraph()->EmptyFixedArrayConstant();
if (arity > 0) {
AllocationBuilder a(jsgraph(), effect, control);
a.AllocateArray(arity, factory()->fixed_array_map());
for (int i = 0; i < arity; ++i) {
a.Store(AccessBuilder::ForFixedArraySlot(i),
NodeProperties::GetValueInput(node, 2 + i));
}
bound_arguments = effect = a.Finish();
}
// Create the JSBoundFunction result.
AllocationBuilder a(jsgraph(), effect, control);
a.Allocate(JSBoundFunction::kSize, NOT_TENURED, Type::BoundFunction());
a.Store(AccessBuilder::ForMap(), map);
a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(),
jsgraph()->EmptyFixedArrayConstant());
a.Store(AccessBuilder::ForJSObjectElements(),
jsgraph()->EmptyFixedArrayConstant());
a.Store(AccessBuilder::ForJSBoundFunctionBoundTargetFunction(),
bound_target_function);
a.Store(AccessBuilder::ForJSBoundFunctionBoundThis(), bound_this);
a.Store(AccessBuilder::ForJSBoundFunctionBoundArguments(), bound_arguments);
RelaxControls(node);
a.FinishAndChange(node);
return Changed(node);
}
Reduction JSCreateLowering::ReduceJSCreateClosure(Node* node) {
DCHECK_EQ(IrOpcode::kJSCreateClosure, node->opcode());
CreateClosureParameters const& p = CreateClosureParametersOf(node->op());
......
......@@ -50,6 +50,7 @@ class V8_EXPORT_PRIVATE JSCreateLowering final
Reduction ReduceJSCreate(Node* node);
Reduction ReduceJSCreateArguments(Node* node);
Reduction ReduceJSCreateArray(Node* node);
Reduction ReduceJSCreateBoundFunction(Node* node);
Reduction ReduceJSCreateClosure(Node* node);
Reduction ReduceJSCreateIterResultObject(Node* node);
Reduction ReduceJSCreateKeyValueArray(Node* node);
......
......@@ -387,6 +387,9 @@ void JSGenericLowering::LowerJSCreateArray(Node* node) {
NodeProperties::ChangeOp(node, common()->Call(desc));
}
void JSGenericLowering::LowerJSCreateBoundFunction(Node* node) {
UNREACHABLE(); // Eliminated in typed lowering.
}
void JSGenericLowering::LowerJSCreateClosure(Node* node) {
CreateClosureParameters const& p = CreateClosureParametersOf(node->op());
......
......@@ -277,6 +277,7 @@ bool NeedsConvertReceiver(Node* receiver, Node* effect) {
case IrOpcode::kJSCreate:
case IrOpcode::kJSCreateArguments:
case IrOpcode::kJSCreateArray:
case IrOpcode::kJSCreateBoundFunction:
case IrOpcode::kJSCreateClosure:
case IrOpcode::kJSCreateIterResultObject:
case IrOpcode::kJSCreateKeyValueArray:
......
......@@ -450,6 +450,33 @@ const CreateArrayParameters& CreateArrayParametersOf(const Operator* op) {
return OpParameter<CreateArrayParameters>(op);
}
bool operator==(CreateBoundFunctionParameters const& lhs,
CreateBoundFunctionParameters const& rhs) {
return lhs.arity() == rhs.arity() &&
lhs.map().location() == rhs.map().location();
}
bool operator!=(CreateBoundFunctionParameters const& lhs,
CreateBoundFunctionParameters const& rhs) {
return !(lhs == rhs);
}
size_t hash_value(CreateBoundFunctionParameters const& p) {
return base::hash_combine(p.arity(), p.map().location());
}
std::ostream& operator<<(std::ostream& os,
CreateBoundFunctionParameters const& p) {
os << p.arity();
if (!p.map().is_null()) os << ", " << Brief(*p.map());
return os;
}
const CreateBoundFunctionParameters& CreateBoundFunctionParametersOf(
const Operator* op) {
DCHECK_EQ(IrOpcode::kJSCreateBoundFunction, op->opcode());
return OpParameter<CreateBoundFunctionParameters>(op);
}
bool operator==(CreateClosureParameters const& lhs,
CreateClosureParameters const& rhs) {
......@@ -1036,6 +1063,18 @@ const Operator* JSOperatorBuilder::CreateArray(size_t arity,
parameters); // parameter
}
const Operator* JSOperatorBuilder::CreateBoundFunction(size_t arity,
Handle<Map> map) {
// bound_target_function, bound_this, arg1, ..., argN
int const value_input_count = static_cast<int>(arity) + 2;
CreateBoundFunctionParameters parameters(arity, map);
return new (zone()) Operator1<CreateBoundFunctionParameters>( // --
IrOpcode::kJSCreateBoundFunction, Operator::kEliminatable, // opcode
"JSCreateBoundFunction", // name
value_input_count, 1, 1, 1, 1, 0, // counts
parameters); // parameter
}
const Operator* JSOperatorBuilder::CreateClosure(
Handle<SharedFunctionInfo> shared_info, VectorSlotPair const& feedback,
PretenureFlag pretenure) {
......
......@@ -527,6 +527,32 @@ std::ostream& operator<<(std::ostream&, CreateArrayParameters const&);
const CreateArrayParameters& CreateArrayParametersOf(const Operator* op);
// Defines shared information for the bound function that should be created.
// This is used as parameter by JSCreateBoundFunction operators.
class CreateBoundFunctionParameters final {
public:
CreateBoundFunctionParameters(size_t arity, Handle<Map> map)
: arity_(arity), map_(map) {}
size_t arity() const { return arity_; }
Handle<Map> map() const { return map_; }
private:
size_t const arity_;
Handle<Map> const map_;
};
bool operator==(CreateBoundFunctionParameters const&,
CreateBoundFunctionParameters const&);
bool operator!=(CreateBoundFunctionParameters const&,
CreateBoundFunctionParameters const&);
size_t hash_value(CreateBoundFunctionParameters const&);
std::ostream& operator<<(std::ostream&, CreateBoundFunctionParameters const&);
const CreateBoundFunctionParameters& CreateBoundFunctionParametersOf(
const Operator* op);
// Defines shared information for the closure that should be created. This is
// used as a parameter by JSCreateClosure operators.
......@@ -644,6 +670,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* Create();
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);
......
......@@ -132,6 +132,7 @@
V(JSCreate) \
V(JSCreateArguments) \
V(JSCreateArray) \
V(JSCreateBoundFunction) \
V(JSCreateClosure) \
V(JSCreateIterResultObject) \
V(JSCreateKeyValueArray) \
......
......@@ -1138,6 +1138,10 @@ Type* Typer::Visitor::TypeJSCreateArguments(Node* node) {
Type* Typer::Visitor::TypeJSCreateArray(Node* node) { return Type::Array(); }
Type* Typer::Visitor::TypeJSCreateBoundFunction(Node* node) {
return Type::BoundFunction();
}
Type* Typer::Visitor::TypeJSCreateGeneratorObject(Node* node) {
return Type::OtherObject();
}
......
......@@ -598,6 +598,10 @@ void Verifier::Visitor::Check(Node* node) {
// Type is Array.
CheckTypeIs(node, Type::Array());
break;
case IrOpcode::kJSCreateBoundFunction:
// Type is BoundFunction.
CheckTypeIs(node, Type::BoundFunction());
break;
case IrOpcode::kJSCreateClosure:
// Type is Function.
CheckTypeIs(node, Type::Function());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment