[turbofan] Generalize and optimize API calls a bit.

When calling API functions (i.e. Blink C++ DOM methods and accessors)
directly from TurboFan we currently only optimize that to a fast call
via the CallApiCallback builtin when TurboFan is able to find reliable
map information about the receiver in the graph. This is usually only
the case when the call is preceeded by a monomorphic or polymorphic
property access on the receiver, i.e. something like

```js
element.hasAttribute("bar");
```

will work, since the call to the `hasAttribute` method is immediately
preceeded by a `CheckMaps(element)` in the monomorphic/polymorphic case.
However this no longer works when the responsible LOAD_IC was
megamorphic or the method is called via `Function#call()` for example:

```js
const hasAttribute = Element.prototype.hasAttribute;
// ...
hasAttribute.call(element, "bar");
```

This change allows for more optimizations in two cases:

1. When the method accepts any receiver (i.e. no signature type and no
   access checks needed), and
2. when we find information about the receiver in the graph, but that
   information is not reliable.

For the first case, when the API method accepts any receiver and doesn't
limit it to specific types of receivers (aka no compatible receiver
check is required), we just insert a ConvertReceiver receiver and pass
the result as both the receiver and the holder to the API callback.

For the second case, we lift the current restriction of only supporting
reliable, stable receiver map information, because we only need to know
two things:

a. The Map::constructor field on the root map satisfies the compatible
   receiver check.
b. If the receiver is a JSObject, then the "access check needed" bit
   is not set (or the API method accepts any receiver).

Both of these cannot change with map transitions. So if at some point in
the past we knew that this held for a given receiver (by looking into
the TurboFan graph), we definitely know that it's still going to hold at
any later point.

Bug: v8:8820
Change-Id: I2316e8a4e2b3b7560e5c5d2b7d1569ebe1d3a1c8
Cq-Include-Trybots: luci.chromium.try:linux-blink-rel
Reviewed-on: https://chromium-review.googlesource.com/c/1466562
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59526}
This commit is contained in:
Benedikt Meurer 2019-02-12 12:56:12 +01:00 committed by Commit Bot
parent 63851f8577
commit cd1063cac1

View File

@ -2870,18 +2870,53 @@ Reduction JSCallReducer::ReduceCallApiFunction(
CallParameters const& p = CallParametersOf(node->op());
int const argc = static_cast<int>(p.arity()) - 2;
Node* target = NodeProperties::GetValueInput(node, 0);
Node* receiver =
(p.convert_mode() == ConvertReceiverMode::kNullOrUndefined)
? jsgraph()->Constant(native_context().global_proxy_object())
Node* global_proxy =
jsgraph()->Constant(native_context().global_proxy_object());
Node* receiver = (p.convert_mode() == ConvertReceiverMode::kNullOrUndefined)
? global_proxy
: NodeProperties::GetValueInput(node, 1);
Node* holder;
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// See if we can optimize this API call to {target}.
Handle<FunctionTemplateInfo> function_template_info(
FunctionTemplateInfo::cast(shared.object()->function_data()), isolate());
CallOptimization call_optimization(isolate(), function_template_info);
if (!call_optimization.is_simple_api_call()) return NoChange();
// If the {target} accepts any kind of {receiver}, we only need to
// ensure that the {receiver} is actually a JSReceiver at this point,
// and also pass that as the {holder}. There are two independent bits
// here:
//
// a. When the "accept any receiver" bit is set, it means we don't
// need to perform access checks, even if the {receiver}'s map
// has the "needs access check" bit set.
// b. When the {function_template_info} has no signature, we don't
// need to do the compatible receiver check, since all receivers
// are considered compatible at that point, and the {receiver}
// will be pass as the {holder}.
//
if (function_template_info->accept_any_receiver() &&
function_template_info->signature()->IsUndefined(isolate())) {
receiver = holder = effect =
graph()->NewNode(simplified()->ConvertReceiver(p.convert_mode()),
receiver, global_proxy, effect, control);
} else {
// Infer the {receiver} maps, and check if we can inline the API function
// callback based on those.
// callback based on those. Note that we don't need to know the concrete
// {receiver} maps at this point and we also don't need to install any
// stability dependencies, since the only relevant information regarding
// the {receiver} is the {Map::constructor} field on the root map (which
// is different from the JavaScript exposed "constructor" property) and
// that field cannot change. So if we know that {receiver} had a certain
// constructor at some point in the past (i.e. it had a certain map),
// then this constructor is going to be the same later, since this
// information cannot change with map transitions. The same is true for
// the instance type, e.g. we still know that the instance type is JSObject
// even if that information is unreliable, and the "access check needed"
// bit, which also cannot change later.
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(broker(), receiver, effect,
@ -2889,39 +2924,30 @@ Reduction JSCallReducer::ReduceCallApiFunction(
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
for (Handle<Map> map : receiver_maps) {
MapRef receiver_map(broker(), map);
if (!receiver_map.IsJSObjectMap() ||
(!function_template_info->accept_any_receiver() &&
receiver_map.is_access_check_needed())) {
if (!receiver_map.IsJSReceiverMap() ||
(receiver_map.is_access_check_needed() &&
!function_template_info->accept_any_receiver())) {
return NoChange();
}
// In case of unreliable {receiver} information, the {receiver_maps}
// must all be stable in order to consume the information.
if (result == NodeProperties::kUnreliableReceiverMaps) {
if (!receiver_map.is_stable()) return NoChange();
}
}
// See if we can constant-fold the compatible receiver checks.
CallOptimization call_optimization(isolate(), function_template_info);
if (!call_optimization.is_simple_api_call()) return NoChange();
CallOptimization::HolderLookup lookup;
Handle<JSObject> api_holder =
call_optimization.LookupHolderOfExpectedType(receiver_maps[0], &lookup);
if (lookup == CallOptimization::kHolderNotFound) return NoChange();
for (size_t i = 1; i < receiver_maps.size(); ++i) {
CallOptimization::HolderLookup lookupi;
Handle<JSObject> holder = call_optimization.LookupHolderOfExpectedType(
Handle<JSObject> holderi = call_optimization.LookupHolderOfExpectedType(
receiver_maps[i], &lookupi);
if (lookup != lookupi) return NoChange();
if (!api_holder.is_identical_to(holder)) return NoChange();
if (!api_holder.is_identical_to(holderi)) return NoChange();
}
// Install stability dependencies for unreliable {receiver_maps}.
if (result == NodeProperties::kUnreliableReceiverMaps) {
for (Handle<Map> map : receiver_maps) {
MapRef receiver_map(broker(), map);
dependencies()->DependOnStableMap(receiver_map);
}
// Determine the appropriate holder for the {lookup}.
holder = lookup == CallOptimization::kHolderFound
? jsgraph()->HeapConstant(api_holder)
: receiver;
}
// Load the {target}s context.
@ -2944,9 +2970,6 @@ Reduction JSCallReducer::ReduceCallApiFunction(
cid.GetStackParameterCount() + argc + 1 /* implicit receiver */,
CallDescriptor::kNeedsFrameState);
ApiFunction api_function(v8::ToCData<Address>(call_handler_info->callback()));
Node* holder = lookup == CallOptimization::kHolderFound
? jsgraph()->HeapConstant(api_holder)
: receiver;
ExternalReference function_reference = ExternalReference::Create(
&api_function, ExternalReference::DIRECT_API_CALL);
node->InsertInput(graph()->zone(), 0,