Commit f9512482 authored by Georg Neis's avatar Georg Neis Committed by Commit Bot

[turbofan] First part of brokerization/serialization for instanceof

- Move SerializePrototype out of DependOnStablePrototypes into
  ComputePropertyAccessInfo.
- Brokerize JSNativeContextSpecialization::InferHasInPrototypeChain.
- Brokerize JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance
  (modulo the call to ReduceJSInstanceOf).
- Brokerize JSNativeContextSpecialization::ReduceJSHasInPrototypeChain.
- Serialize for JSCallReducer::ReduceObjectPrototypeIsPrototypeOf.
- Serialize for JSNativeContextSpecialization::ReduceJSInstanceOf. This
  is still incomplete.

Bug: v8:7790
Change-Id: Ic56eab5ddd8d725a13d2980e5b55db53ae82e822
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1709408
Commit-Queue: Georg Neis <neis@chromium.org>
Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62920}
parent f42b1a5d
......@@ -537,6 +537,7 @@ PropertyAccessInfo AccessInfoFactory::ComputePropertyAccessInfo(
}
// Walk up the prototype chain.
MapRef(broker(), map).SerializePrototype();
if (!map->prototype().IsJSObject()) {
// Perform the implicit ToObject for primitives here.
// Implemented according to ES6 section 7.3.2 GetV (V, P).
......
......@@ -550,13 +550,7 @@ namespace {
// This function expects to never see a JSProxy.
void DependOnStablePrototypeChain(CompilationDependencies* deps, MapRef map,
base::Optional<JSObjectRef> last_prototype) {
// TODO(neis): Remove heap access (SerializePrototype call).
AllowCodeDependencyChange dependency_change_;
AllowHandleAllocation handle_allocation_;
AllowHandleDereference handle_dereference_;
AllowHeapAllocation heap_allocation_;
while (true) {
map.SerializePrototype();
HeapObjectRef proto = map.prototype();
if (!proto.IsJSObject()) {
CHECK_EQ(proto.map().oddball_type(), OddballType::kNull);
......
......@@ -70,6 +70,8 @@ enum class OddballType : uint8_t {
V(InternalizedString) \
V(String) \
V(Symbol) \
/* Subtypes of JSReceiver */ \
V(JSObject) \
/* Subtypes of HeapObject */ \
V(AccessorInfo) \
V(AllocationSite) \
......@@ -83,7 +85,7 @@ enum class OddballType : uint8_t {
V(FixedArrayBase) \
V(FunctionTemplateInfo) \
V(HeapNumber) \
V(JSObject) \
V(JSReceiver) \
V(Map) \
V(MutableHeapNumber) \
V(Name) \
......@@ -223,9 +225,15 @@ class PropertyCellRef : public HeapObjectRef {
ObjectRef value() const;
};
class JSObjectRef : public HeapObjectRef {
class JSReceiverRef : public HeapObjectRef {
public:
using HeapObjectRef::HeapObjectRef;
Handle<JSReceiver> object() const;
};
class JSObjectRef : public JSReceiverRef {
public:
using JSReceiverRef::JSReceiverRef;
Handle<JSObject> object() const;
uint64_t RawFastDoublePropertyAsBitsAt(FieldIndex index) const;
......@@ -261,9 +269,10 @@ class JSBoundFunctionRef : public JSObjectRef {
Handle<JSBoundFunction> object() const;
void Serialize();
bool serialized() const;
// The following are available only after calling Serialize().
ObjectRef bound_target_function() const;
JSReceiverRef bound_target_function() const;
ObjectRef bound_this() const;
FixedArrayRef bound_arguments() const;
};
......
......@@ -847,6 +847,8 @@ Reduction JSCallReducer::ReduceObjectPrototypeHasOwnProperty(Node* node) {
// ES #sec-object.prototype.isprototypeof
Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) {
DisallowHeapAccessIf no_heap_access(FLAG_concurrent_inlining);
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* value = node->op()->ValueInputCount() > 2
......
......@@ -256,7 +256,14 @@ class JSObjectField {
uint64_t number_bits_ = 0;
};
class JSObjectData : public HeapObjectData {
class JSReceiverData : public HeapObjectData {
public:
JSReceiverData(JSHeapBroker* broker, ObjectData** storage,
Handle<JSReceiver> object)
: HeapObjectData(broker, storage, object) {}
};
class JSObjectData : public JSReceiverData {
public:
JSObjectData(JSHeapBroker* broker, ObjectData** storage,
Handle<JSObject> object);
......@@ -462,6 +469,7 @@ class JSBoundFunctionData : public JSObjectData {
Handle<JSBoundFunction> object);
void Serialize(JSHeapBroker* broker);
bool serialized() const { return serialized_; }
ObjectData* bound_target_function() const { return bound_target_function_; }
ObjectData* bound_this() const { return bound_this_; }
......@@ -1309,21 +1317,26 @@ void JSBoundFunctionData::Serialize(JSHeapBroker* broker) {
Handle<JSBoundFunction> function = Handle<JSBoundFunction>::cast(object());
DCHECK_NULL(bound_target_function_);
DCHECK_NULL(bound_this_);
DCHECK_NULL(bound_arguments_);
bound_target_function_ =
broker->GetOrCreateData(function->bound_target_function());
bound_this_ = broker->GetOrCreateData(function->bound_this());
if (bound_target_function_->IsJSBoundFunction()) {
bound_target_function_->AsJSBoundFunction()->Serialize(broker);
} else if (bound_target_function_->IsJSFunction()) {
bound_target_function_->AsJSFunction()->Serialize(broker);
}
DCHECK_NULL(bound_arguments_);
bound_arguments_ =
broker->GetOrCreateData(function->bound_arguments())->AsFixedArray();
bound_arguments_->SerializeContents(broker);
DCHECK_NULL(bound_this_);
bound_this_ = broker->GetOrCreateData(function->bound_this());
}
JSObjectData::JSObjectData(JSHeapBroker* broker, ObjectData** storage,
Handle<JSObject> object)
: HeapObjectData(broker, storage, object),
: JSReceiverData(broker, storage, object),
inobject_fields_(broker->zone()),
own_constant_elements_(broker->zone()),
own_properties_(broker->zone()) {}
......@@ -2979,7 +2992,7 @@ BIMODAL_ACCESSOR(HeapObject, Map, map)
BIMODAL_ACCESSOR(JSArray, Object, length)
BIMODAL_ACCESSOR(JSBoundFunction, Object, bound_target_function)
BIMODAL_ACCESSOR(JSBoundFunction, JSReceiver, bound_target_function)
BIMODAL_ACCESSOR(JSBoundFunction, Object, bound_this)
BIMODAL_ACCESSOR(JSBoundFunction, FixedArray, bound_arguments)
......@@ -3792,6 +3805,11 @@ void JSBoundFunctionRef::Serialize() {
data()->AsJSBoundFunction()->Serialize(broker());
}
bool JSBoundFunctionRef::serialized() const {
CHECK_NE(broker()->mode(), JSHeapBroker::kDisabled);
return data()->AsJSBoundFunction()->serialized();
}
void PropertyCellRef::Serialize() {
if (broker()->mode() == JSHeapBroker::kDisabled) return;
CHECK_EQ(broker()->mode(), JSHeapBroker::kSerializing);
......
......@@ -381,6 +381,10 @@ Reduction JSNativeContextSpecialization::ReduceJSGetSuperConstructor(
}
Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
// TODO(neis): Eliminate heap accesses.
AllowHandleDereference allow_handle_dereference;
AllowHandleAllocation allow_handle_allocation;
DCHECK_EQ(IrOpcode::kJSInstanceOf, node->opcode());
FeedbackParameter const& p = FeedbackParameterOf(node->op());
Node* object = NodeProperties::GetValueInput(node, 0);
......@@ -502,7 +506,7 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
JSNativeContextSpecialization::InferHasInPrototypeChainResult
JSNativeContextSpecialization::InferHasInPrototypeChain(
Node* receiver, Node* effect, Handle<HeapObject> prototype) {
Node* receiver, Node* effect, HeapObjectRef const& prototype) {
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMapsUnsafe(broker(), receiver, effect,
......@@ -515,28 +519,31 @@ JSNativeContextSpecialization::InferHasInPrototypeChain(
bool all = true;
bool none = true;
for (size_t i = 0; i < receiver_maps.size(); ++i) {
Handle<Map> receiver_map = receiver_maps[i];
if (receiver_map->instance_type() <= LAST_SPECIAL_RECEIVER_TYPE) {
MapRef map(broker(), receiver_maps[i]);
while (true) {
if (IsSpecialReceiverInstanceType(map.instance_type())) {
return kMayBeInPrototypeChain;
}
if (result == NodeProperties::kUnreliableReceiverMaps &&
!receiver_map->is_stable()) {
!map.is_stable()) {
return kMayBeInPrototypeChain;
}
for (PrototypeIterator it(isolate(), receiver_map);; it.Advance()) {
if (it.IsAtEnd()) {
if (!map.IsJSObjectMap()) {
all = false;
break;
}
Handle<HeapObject> current =
PrototypeIterator::GetCurrent<HeapObject>(it);
if (current.is_identical_to(prototype)) {
if (FLAG_concurrent_inlining && !map.serialized_prototype()) {
TRACE_BROKER_MISSING(broker(), "prototype data for map " << map);
return kMayBeInPrototypeChain;
}
if (map.prototype().equals(prototype)) {
none = false;
break;
}
if (!current->map().is_stable() ||
current->map().instance_type() <= LAST_SPECIAL_RECEIVER_TYPE) {
return kMayBeInPrototypeChain;
map = map.prototype().map();
if (map.oddball_type() == OddballType::kNull) {
all = false;
break;
}
}
}
......@@ -552,8 +559,8 @@ JSNativeContextSpecialization::InferHasInPrototypeChain(
// might be a different object each time, so it's much simpler to include
// {prototype}. That does, however, mean that we must check {prototype}'s
// map stability.
if (!prototype->map().is_stable()) return kMayBeInPrototypeChain;
last_prototype.emplace(broker(), Handle<JSObject>::cast(prototype));
if (!prototype.map().is_stable()) return kMayBeInPrototypeChain;
last_prototype = prototype.AsJSObject();
}
WhereToStart start = result == NodeProperties::kUnreliableReceiverMaps
? kStartAtReceiver
......@@ -568,6 +575,8 @@ JSNativeContextSpecialization::InferHasInPrototypeChain(
Reduction JSNativeContextSpecialization::ReduceJSHasInPrototypeChain(
Node* node) {
DisallowHeapAccessIf no_heap_access(FLAG_concurrent_inlining);
DCHECK_EQ(IrOpcode::kJSHasInPrototypeChain, node->opcode());
Node* value = NodeProperties::GetValueInput(node, 0);
Node* prototype = NodeProperties::GetValueInput(node, 1);
......@@ -578,7 +587,7 @@ Reduction JSNativeContextSpecialization::ReduceJSHasInPrototypeChain(
HeapObjectMatcher m(prototype);
if (m.HasValue()) {
InferHasInPrototypeChainResult result =
InferHasInPrototypeChain(value, effect, m.Value());
InferHasInPrototypeChain(value, effect, m.Ref(broker()));
if (result != kMayBeInPrototypeChain) {
Node* value = jsgraph()->BooleanConstant(result == kIsInPrototypeChain);
ReplaceWithValue(node, value);
......@@ -591,6 +600,8 @@ Reduction JSNativeContextSpecialization::ReduceJSHasInPrototypeChain(
Reduction JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance(
Node* node) {
DisallowHeapAccessIf no_heap_access(FLAG_concurrent_inlining);
DCHECK_EQ(IrOpcode::kJSOrdinaryHasInstance, node->opcode());
Node* constructor = NodeProperties::GetValueInput(node, 0);
Node* object = NodeProperties::GetValueInput(node, 1);
......@@ -599,34 +610,41 @@ Reduction JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance(
HeapObjectMatcher m(constructor);
if (!m.HasValue()) return NoChange();
// Check if the {constructor} is a JSBoundFunction.
if (m.Value()->IsJSBoundFunction()) {
// OrdinaryHasInstance on bound functions turns into a recursive
// invocation of the instanceof operator again.
// ES6 section 7.3.19 OrdinaryHasInstance (C, O) step 2.
Handle<JSBoundFunction> function = Handle<JSBoundFunction>::cast(m.Value());
Handle<JSReceiver> bound_target_function(function->bound_target_function(),
isolate());
if (m.Ref(broker()).IsJSBoundFunction()) {
// OrdinaryHasInstance on bound functions turns into a recursive invocation
// of the instanceof operator again.
JSBoundFunctionRef function = m.Ref(broker()).AsJSBoundFunction();
if (FLAG_concurrent_inlining && !function.serialized()) {
TRACE_BROKER_MISSING(broker(), "data for function " << function);
return NoChange();
}
JSReceiverRef bound_target_function = function.bound_target_function();
NodeProperties::ReplaceValueInput(node, object, 0);
NodeProperties::ReplaceValueInput(
node, jsgraph()->HeapConstant(bound_target_function), 1);
node, jsgraph()->Constant(bound_target_function), 1);
NodeProperties::ChangeOp(node, javascript()->InstanceOf(VectorSlotPair()));
Reduction const reduction = ReduceJSInstanceOf(node);
return reduction.Changed() ? reduction : Changed(node);
}
if (m.Ref(broker()).IsJSFunction()) {
// Optimize if we currently know the "prototype" property.
if (m.Value()->IsJSFunction()) {
JSFunctionRef function = m.Ref(broker()).AsJSFunction();
// TODO(neis): This is a temporary hack needed because the copy reducer
// runs only after this pass.
function.Serialize();
if (FLAG_concurrent_inlining && !function.serialized()) {
TRACE_BROKER_MISSING(broker(), "data for function " << function);
return NoChange();
}
// TODO(neis): Remove the has_prototype_slot condition once the broker is
// always enabled.
if (!function.map().has_prototype_slot() || !function.has_prototype() ||
function.PrototypeRequiresRuntimeLookup()) {
return NoChange();
}
ObjectRef prototype = dependencies()->DependOnPrototypeProperty(function);
Node* prototype_constant = jsgraph()->Constant(prototype);
......
......@@ -234,7 +234,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
kMayBeInPrototypeChain
};
InferHasInPrototypeChainResult InferHasInPrototypeChain(
Node* receiver, Node* effect, Handle<HeapObject> prototype);
Node* receiver, Node* effect, HeapObjectRef const& prototype);
Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; }
......
......@@ -92,7 +92,6 @@ namespace compiler {
V(TestEqualStrict) \
V(TestGreaterThan) \
V(TestGreaterThanOrEqual) \
V(TestInstanceOf) \
V(TestLessThan) \
V(TestLessThanOrEqual) \
V(TestNull) \
......@@ -208,6 +207,7 @@ namespace compiler {
V(SwitchOnGeneratorState) \
V(SwitchOnSmiNoFeedback) \
V(TestIn) \
V(TestInstanceOf) \
CLEAR_ACCUMULATOR_LIST(V) \
CLEAR_ENVIRONMENT_LIST(V) \
CONDITIONAL_JUMPS_LIST(V) \
......@@ -374,6 +374,7 @@ class SerializerForBackgroundCompilation {
FeedbackSlot slot, AccessMode mode);
void ProcessMapHintsForPromises(Hints const& receiver_hints);
void ProcessHintsForPromiseResolve(Hints const& resolution_hints);
void ProcessHintsForObjectIsPrototypeOf(Hints const& value_hints);
void ProcessHintsForRegExpTest(Hints const& regexp_hints);
PropertyAccessInfo ProcessMapForRegExpTest(MapRef map);
void ProcessHintsForFunctionCall(Hints const& target_hints);
......@@ -1659,7 +1660,7 @@ void SerializerForBackgroundCompilation::ProcessBuiltinCall(
ProcessHintsForPromiseResolve(resolution_hints);
}
break;
case Builtins::kRegExpPrototypeTest: {
case Builtins::kRegExpPrototypeTest:
// For JSCallReducer::ReduceRegExpPrototypeTest.
if (arguments.size() >= 1 &&
speculation_mode != SpeculationMode::kDisallowSpeculation) {
......@@ -1667,7 +1668,6 @@ void SerializerForBackgroundCompilation::ProcessBuiltinCall(
ProcessHintsForRegExpTest(regexp_hints);
}
break;
}
case Builtins::kFunctionPrototypeCall:
if (arguments.size() >= 1 &&
speculation_mode != SpeculationMode::kDisallowSpeculation) {
......@@ -1675,6 +1675,11 @@ void SerializerForBackgroundCompilation::ProcessBuiltinCall(
ProcessHintsForFunctionCall(target_hints);
}
break;
case Builtins::kObjectPrototypeIsPrototypeOf:
if (arguments.size() >= 2) {
ProcessHintsForObjectIsPrototypeOf(arguments[1]);
}
break;
case Builtins::kFastFunctionPrototypeBind:
if (arguments.size() >= 2 &&
speculation_mode != SpeculationMode::kDisallowSpeculation) {
......@@ -1687,6 +1692,26 @@ void SerializerForBackgroundCompilation::ProcessBuiltinCall(
}
}
void SerializerForBackgroundCompilation::ProcessHintsForObjectIsPrototypeOf(
Hints const& value_hints) {
auto processMap = [&](Handle<Map> map_handle) {
MapRef map(broker(), map_handle);
while (map.IsJSObjectMap()) {
map.SerializePrototype();
map = map.prototype().map();
}
};
for (auto hint : value_hints.constants()) {
if (!hint->IsHeapObject()) continue;
Handle<HeapObject> object(Handle<HeapObject>::cast(hint));
processMap(handle(object->map(), broker()->isolate()));
}
for (auto map_hint : value_hints.maps()) {
processMap(map_hint);
}
}
void SerializerForBackgroundCompilation::ProcessHintsForPromiseResolve(
Hints const& resolution_hints) {
auto processMap = [&](Handle<Map> map) {
......@@ -2213,6 +2238,38 @@ void SerializerForBackgroundCompilation::VisitTestIn(
ProcessKeyedPropertyAccess(receiver, key, slot, AccessMode::kHas);
}
void SerializerForBackgroundCompilation::VisitTestInstanceOf(
BytecodeArrayIterator* iterator) {
Hints const& lhs =
environment()->register_hints(iterator->GetRegisterOperand(0));
Hints const& rhs = environment()->accumulator_hints();
FeedbackSlot slot = iterator->GetSlotOperand(1);
// Inspect feedback (about the rhs of the operator).
Handle<FeedbackVector> feedback_vector =
environment()->function().feedback_vector();
FeedbackNexus nexus(feedback_vector, slot);
VectorSlotPair rhs_feedback(feedback_vector, slot, nexus.ic_state());
Handle<JSObject> constructor;
if (rhs_feedback.IsValid() &&
nexus.GetConstructorFeedback().ToHandle(&constructor)) {
if (constructor->IsJSBoundFunction()) {
JSBoundFunctionRef(broker(), constructor).Serialize();
} else if (constructor->IsJSFunction()) {
JSFunctionRef(broker(), constructor).Serialize();
} else {
UNREACHABLE();
}
}
// TODO(neis): Support full instanceof semantics.
ProcessHintsForObjectIsPrototypeOf(lhs);
// TODO(neis): Process rhs hints.
USE(rhs);
}
void SerializerForBackgroundCompilation::VisitStaKeyedProperty(
BytecodeArrayIterator* iterator) {
Hints const& receiver =
......
......@@ -160,3 +160,16 @@
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo());
})();
(function() {
function A() {}
A.prototype = {};
var a = {__proto__: new A, gaga: 42};
function foo() { a.gaga; return A.prototype.isPrototypeOf(a); }
%PrepareFunctionForOptimization(foo);
assertTrue(foo());
assertTrue(foo());
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo());
})();
......@@ -5,19 +5,77 @@
// Flags: --allow-natives-syntax
(function() {
class A {}
class A {};
function foo(a, fn) {
function foo(a, fn) {
const C = a.constructor;
fn(a);
return a instanceof C;
};
%PrepareFunctionForOptimization(foo);
assertTrue(foo(new A(), a => {}));
assertTrue(foo(new A(), a => {}));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(new A(), a => {}));
assertFalse(foo(new A(), a => {
a.__proto__ = {};
}));
};
%PrepareFunctionForOptimization(foo);
assertTrue(foo(new A(), a => {}));
assertTrue(foo(new A(), a => {}));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo(new A(), a => {}));
assertFalse(foo(new A(), a => { a.__proto__ = {}; }));
})();
(function() {
class A {};
A.__proto__ = {};
A.prototype = {};
function foo() {
var x = Object.create(Object.create(Object.create(A.prototype)));
return x instanceof A;
};
%PrepareFunctionForOptimization(foo);
assertTrue(foo());
assertTrue(foo());
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo());
})();
(function() {
class A {};
A.prototype = {};
A.__proto__ = {};
var a = {__proto__: new A, gaga: 42};
function foo() {
A.bla; // Make A.__proto__ fast again.
a.gaga;
return a instanceof A;
};
%PrepareFunctionForOptimization(foo);
assertTrue(foo());
assertTrue(foo());
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo());
})();
(function() {
class A {};
A.prototype = {};
A.__proto__ = {};
const boundA = Function.prototype.bind.call(A, {});
boundA.prototype = {};
boundA.__proto__ = {};
var a = {__proto__: new boundA, gaga: 42};
function foo() {
A.bla; // Make A.__proto__ fast again.
boundA.bla; // Make boundA.__proto__ fast again.
a.gaga;
return a instanceof boundA;
};
%PrepareFunctionForOptimization(foo);
assertTrue(foo());
assertTrue(foo());
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo());
})();
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