Skip to content

Commit

Permalink
[turbofan] Inline Function#bind in more cases.
Browse files Browse the repository at this point in the history
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/723080
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48639}
  • Loading branch information
bmeurer authored and Commit Bot committed Oct 17, 2017
1 parent 2b9a6d8 commit 594803c
Show file tree
Hide file tree
Showing 13 changed files with 226 additions and 85 deletions.
84 changes: 0 additions & 84 deletions src/compiler/js-builtin-reducer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
1 change: 0 additions & 1 deletion src/compiler/js-builtin-reducer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
103 changes: 103 additions & 0 deletions src/compiler/js-call-reducer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/compiler/js-call-reducer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
42 changes: 42 additions & 0 deletions src/compiler/js-create-lowering.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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());
Expand Down
1 change: 1 addition & 0 deletions src/compiler/js-create-lowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/js-generic-lowering.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
1 change: 1 addition & 0 deletions src/compiler/js-inlining.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
39 changes: 39 additions & 0 deletions src/compiler/js-operator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 594803c

Please sign in to comment.