[ic] Load IC data handlers now support prototype chain checks with global and dictionary objects.

BUG=v8:5561

Review-Url: https://codereview.chromium.org/2449463002
Cr-Commit-Position: refs/heads/master@{#40626}
This commit is contained in:
ishell 2016-10-27 08:32:30 -07:00 committed by Commit bot
parent 3a5056a26c
commit 6ea4061b8d
17 changed files with 290 additions and 73 deletions

View File

@ -5086,7 +5086,7 @@ void CodeStubAssembler::TryProbeStubCacheTable(
DCHECK_EQ(kPointerSize, stub_cache->value_reference(table).address() -
stub_cache->key_reference(table).address());
Node* handler = Load(MachineType::Pointer(), key_base,
Node* handler = Load(MachineType::TaggedPointer(), key_base,
IntPtrAdd(entry_offset, IntPtrConstant(kPointerSize)));
// We found the handler.
@ -5530,29 +5530,106 @@ void CodeStubAssembler::HandleLoadICProtoHandler(
DCHECK_EQ(MachineRepresentation::kTagged, var_holder->rep());
DCHECK_EQ(MachineRepresentation::kTagged, var_smi_handler->rep());
Node* validity_cell = LoadObjectField(handler, Tuple3::kValue1Offset);
// IC dispatchers rely on these assumptions to be held.
STATIC_ASSERT(FixedArray::kLengthOffset == LoadHandler::kHolderCellOffset);
DCHECK_EQ(FixedArray::OffsetOfElementAt(LoadHandler::kSmiHandlerIndex),
LoadHandler::kSmiHandlerOffset);
DCHECK_EQ(FixedArray::OffsetOfElementAt(LoadHandler::kValidityCellIndex),
LoadHandler::kValidityCellOffset);
// Both FixedArray and Tuple3 handlers have validity cell at the same offset.
Node* validity_cell =
LoadObjectField(handler, LoadHandler::kValidityCellOffset);
Node* cell_value = LoadObjectField(validity_cell, Cell::kValueOffset);
GotoIf(WordNotEqual(cell_value,
SmiConstant(Smi::FromInt(Map::kPrototypeChainValid))),
miss);
Node* holder =
LoadWeakCellValue(LoadObjectField(handler, Tuple3::kValue2Offset));
// The |holder| is guaranteed to be alive at this point since we passed
// both the receiver map check and the validity cell check.
CSA_ASSERT(WordNotEqual(holder, IntPtrConstant(0)));
Node* smi_handler = LoadObjectField(handler, Tuple3::kValue3Offset);
Node* smi_handler = LoadObjectField(handler, LoadHandler::kSmiHandlerOffset);
CSA_ASSERT(TaggedIsSmi(smi_handler));
var_holder->Bind(holder);
var_smi_handler->Bind(smi_handler);
Label check_prototypes(this);
GotoUnless(IsSetWord<LoadHandler::DoNegativeLookupOnReceiverBits>(
SmiUntag(smi_handler)),
if_smi_handler);
&check_prototypes);
{
// We have a dictionary receiver, do a negative lookup check.
NameDictionaryNegativeLookup(p->receiver, p->name, miss);
Goto(&check_prototypes);
}
NameDictionaryNegativeLookup(p->receiver, p->name, miss);
Goto(if_smi_handler);
Bind(&check_prototypes);
Node* maybe_holder_cell =
LoadObjectField(handler, LoadHandler::kHolderCellOffset);
Label array_handler(this), tuple_handler(this);
Branch(TaggedIsSmi(maybe_holder_cell), &array_handler, &tuple_handler);
Bind(&tuple_handler);
{
Node* holder = LoadWeakCellValue(maybe_holder_cell);
// The |holder| is guaranteed to be alive at this point since we passed
// both the receiver map check and the validity cell check.
CSA_ASSERT(WordNotEqual(holder, IntPtrConstant(0)));
var_holder->Bind(holder);
var_smi_handler->Bind(smi_handler);
Goto(if_smi_handler);
}
Bind(&array_handler);
{
Node* length = SmiUntag(maybe_holder_cell);
BuildFastLoop(MachineType::PointerRepresentation(),
IntPtrConstant(LoadHandler::kFirstPrototypeIndex), length,
[this, p, handler, miss](CodeStubAssembler*, Node* current) {
Node* prototype_cell = LoadFixedArrayElement(
handler, current, 0, INTPTR_PARAMETERS);
CheckPrototype(prototype_cell, p->name, miss);
},
1, IndexAdvanceMode::kPost);
Node* holder_cell = LoadFixedArrayElement(
handler, IntPtrConstant(LoadHandler::kHolderCellIndex), 0,
INTPTR_PARAMETERS);
Node* holder = LoadWeakCellValue(holder_cell);
// The |holder| is guaranteed to be alive at this point since we passed
// the receiver map check, the validity cell check and the prototype chain
// check.
CSA_ASSERT(WordNotEqual(holder, IntPtrConstant(0)));
var_holder->Bind(holder);
var_smi_handler->Bind(smi_handler);
Goto(if_smi_handler);
}
}
void CodeStubAssembler::CheckPrototype(Node* prototype_cell, Node* name,
Label* miss) {
Node* maybe_prototype = LoadWeakCellValue(prototype_cell, miss);
Label done(this);
Label if_property_cell(this), if_dictionary_object(this);
// |maybe_prototype| is either a PropertyCell or a slow-mode prototype.
Branch(WordEqual(LoadMap(maybe_prototype),
LoadRoot(Heap::kGlobalPropertyCellMapRootIndex)),
&if_property_cell, &if_dictionary_object);
Bind(&if_dictionary_object);
{
NameDictionaryNegativeLookup(maybe_prototype, name, miss);
Goto(&done);
}
Bind(&if_property_cell);
{
// Ensure the property cell still contains the hole.
Node* value = LoadObjectField(maybe_prototype, PropertyCell::kValueOffset);
GotoIf(WordNotEqual(value, LoadRoot(Heap::kTheHoleValueRootIndex)), miss);
Goto(&done);
}
Bind(&done);
}
void CodeStubAssembler::NameDictionaryNegativeLookup(Node* object, Node* name,

View File

@ -1107,6 +1107,9 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Variable* var_smi_handler,
Label* if_smi_handler, Label* miss);
void CheckPrototype(compiler::Node* prototype_cell, compiler::Node* name,
Label* miss);
void NameDictionaryNegativeLookup(compiler::Node* object,
compiler::Node* name, Label* miss);

View File

@ -478,10 +478,7 @@ Register PropertyHandlerCompiler::CheckPrototypes(
name, scratch2, miss);
} else if (current_map->is_dictionary_map()) {
DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast.
if (!name->IsUniqueName()) {
DCHECK(name->IsString());
name = factory()->InternalizeString(Handle<String>::cast(name));
}
DCHECK(name->IsUniqueName());
DCHECK(current.is_null() ||
current->property_dictionary()->FindEntry(name) ==
NameDictionary::kNotFound);

View File

@ -508,10 +508,7 @@ Register PropertyHandlerCompiler::CheckPrototypes(
name, scratch2, miss);
} else if (current_map->is_dictionary_map()) {
DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast.
if (!name->IsUniqueName()) {
DCHECK(name->IsString());
name = factory()->InternalizeString(Handle<String>::cast(name));
}
DCHECK(name->IsUniqueName());
DCHECK(current.is_null() || (current->property_dictionary()->FindEntry(
name) == NameDictionary::kNotFound));

View File

@ -59,6 +59,21 @@ class LoadHandler {
// Make sure we don't overflow the smi.
STATIC_ASSERT(ElementsKindBits::kNext <= kSmiValueSize);
// The layout of an Tuple3 handler representing a load of a field from
// prototype when prototype chain checks do not include non-existing lookups
// or access checks.
static const int kHolderCellOffset = Tuple3::kValue1Offset;
static const int kSmiHandlerOffset = Tuple3::kValue2Offset;
static const int kValidityCellOffset = Tuple3::kValue3Offset;
// The layout of an array handler representing a load of a field from
// prototype when prototype chain checks include non-existing lookups and
// access checks.
static const int kSmiHandlerIndex = 0;
static const int kValidityCellIndex = 1;
static const int kHolderCellIndex = 2;
static const int kFirstPrototypeIndex = 3;
// Creates a Smi-handler for loading a field from fast object.
static inline Handle<Object> LoadField(Isolate* isolate,
FieldIndex field_index);

View File

@ -482,10 +482,7 @@ Register PropertyHandlerCompiler::CheckPrototypes(
name, scratch2, miss);
} else if (current_map->is_dictionary_map()) {
DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast.
if (!name->IsUniqueName()) {
DCHECK(name->IsString());
name = factory()->InternalizeString(Handle<String>::cast(name));
}
DCHECK(name->IsUniqueName());
DCHECK(current.is_null() ||
current->property_dictionary()->FindEntry(name) ==
NameDictionary::kNotFound);

View File

@ -93,7 +93,8 @@ Code* IC::target() const {
}
bool IC::IsHandler(Object* object) {
return (object->IsSmi() && (object != nullptr)) || (object->IsTuple3()) ||
return (object->IsSmi() && (object != nullptr)) || object->IsTuple3() ||
object->IsFixedArray() ||
(object->IsCode() && Code::cast(object)->is_handler());
}

View File

@ -853,36 +853,77 @@ Handle<Object> LoadIC::SimpleFieldLoad(FieldIndex index) {
return stub.GetCode();
}
bool LoadIC::IsPrototypeValidityCellCheckEnough(Handle<Map> receiver_map,
Handle<JSObject> holder) {
namespace {
template <bool fill_array>
int InitPrototypeChecks(Isolate* isolate, Handle<Map> receiver_map,
Handle<JSObject> holder, Handle<FixedArray> array,
Handle<Name> name) {
DCHECK(holder->HasFastProperties());
// The following kinds of receiver maps require custom handler compilation.
if (receiver_map->IsPrimitiveMap() || receiver_map->IsJSGlobalProxyMap() ||
receiver_map->IsJSGlobalObjectMap()) {
return false;
return -1;
}
HandleScope scope(isolate);
int checks_count = 0;
// Switch to custom compiled handler if the prototype chain contains global
// or dictionary objects.
for (PrototypeIterator iter(*receiver_map); !iter.IsAtEnd(); iter.Advance()) {
JSObject* current = iter.GetCurrent<JSObject>();
if (current == *holder) break;
Map* current_map = current->map();
for (PrototypeIterator iter(receiver_map); !iter.IsAtEnd(); iter.Advance()) {
Handle<JSObject> current = PrototypeIterator::GetCurrent<JSObject>(iter);
if (*current == *holder) break;
Handle<Map> current_map(current->map(), isolate);
// Only global objects and objects that do not require access
// checks are allowed in stubs.
DCHECK(current_map->IsJSGlobalProxyMap() ||
!current_map->is_access_check_needed());
if (current_map->IsJSGlobalObjectMap()) {
return false;
if (fill_array) {
Handle<JSGlobalObject> global = Handle<JSGlobalObject>::cast(current);
Handle<PropertyCell> cell = JSGlobalObject::EnsureEmptyPropertyCell(
global, name, PropertyCellType::kInvalidated);
DCHECK(cell->value()->IsTheHole(isolate));
Handle<WeakCell> weak_cell = isolate->factory()->NewWeakCell(cell);
array->set(LoadHandler::kFirstPrototypeIndex + checks_count,
*weak_cell);
}
checks_count++;
} else if (current_map->is_dictionary_map()) {
DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast.
return false;
if (fill_array) {
DCHECK_EQ(NameDictionary::kNotFound,
current->property_dictionary()->FindEntry(name));
Handle<WeakCell> weak_cell =
Map::GetOrCreatePrototypeWeakCell(current, isolate);
array->set(LoadHandler::kFirstPrototypeIndex + checks_count,
*weak_cell);
}
checks_count++;
}
}
return true;
return checks_count;
}
} // namespace
int LoadIC::GetPrototypeCheckCount(Handle<Map> receiver_map,
Handle<JSObject> holder) {
return InitPrototypeChecks<false>(isolate(), receiver_map, holder,
Handle<FixedArray>(), Handle<Name>());
}
Handle<Object> LoadIC::SimpleLoadFromPrototype(Handle<Map> receiver_map,
Handle<JSObject> holder,
Handle<Name> name,
Handle<Object> smi_handler) {
DCHECK(IsPrototypeValidityCellCheckEnough(receiver_map, holder));
int checks_count = GetPrototypeCheckCount(receiver_map, holder);
DCHECK_LE(0, checks_count);
if (receiver_map->IsJSGlobalProxyMap() ||
receiver_map->IsJSGlobalObjectMap()) {
@ -898,8 +939,19 @@ Handle<Object> LoadIC::SimpleLoadFromPrototype(Handle<Map> receiver_map,
Handle<WeakCell> holder_cell =
Map::GetOrCreatePrototypeWeakCell(holder, isolate());
return isolate()->factory()->NewTuple3(validity_cell, holder_cell,
smi_handler);
if (checks_count == 0) {
return isolate()->factory()->NewTuple3(holder_cell, smi_handler,
validity_cell);
}
Handle<FixedArray> handler_array(isolate()->factory()->NewFixedArray(
LoadHandler::kFirstPrototypeIndex + checks_count, TENURED));
handler_array->set(LoadHandler::kSmiHandlerIndex, *smi_handler);
handler_array->set(LoadHandler::kValidityCellIndex, *validity_cell);
handler_array->set(LoadHandler::kHolderCellIndex, *holder_cell);
InitPrototypeChecks<true>(isolate(), receiver_map, holder, handler_array,
name);
return handler_array;
}
bool IsCompatibleReceiver(LookupIterator* lookup, Handle<Map> receiver_map) {
@ -1237,10 +1289,10 @@ Handle<Object> LoadIC::GetMapIndependentHandler(LookupIterator* lookup) {
if (receiver_is_holder) {
return smi_handler;
}
if (FLAG_tf_load_ic_stub &&
IsPrototypeValidityCellCheckEnough(map, holder)) {
if (FLAG_tf_load_ic_stub && GetPrototypeCheckCount(map, holder) >= 0) {
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadFieldFromPrototypeDH);
return SimpleLoadFromPrototype(map, holder, smi_handler);
return SimpleLoadFromPrototype(map, holder, lookup->name(),
smi_handler);
}
break; // Custom-compiled handler.
}
@ -1254,9 +1306,10 @@ Handle<Object> LoadIC::GetMapIndependentHandler(LookupIterator* lookup) {
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadConstantDH);
return smi_handler;
}
if (IsPrototypeValidityCellCheckEnough(map, holder)) {
if (GetPrototypeCheckCount(map, holder) >= 0) {
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadConstantFromPrototypeDH);
return SimpleLoadFromPrototype(map, holder, smi_handler);
return SimpleLoadFromPrototype(map, holder, lookup->name(),
smi_handler);
}
} else {
if (receiver_is_holder) {

View File

@ -311,16 +311,18 @@ class LoadIC : public IC {
private:
Handle<Object> SimpleFieldLoad(FieldIndex index);
// Returns true if the validity cell check is enough to ensure that the
// Returns 0 if the validity cell check is enough to ensure that the
// prototype chain from |receiver_map| till |holder| did not change.
bool IsPrototypeValidityCellCheckEnough(Handle<Map> receiver_map,
Handle<JSObject> holder);
// Returns -1 if the handler has to be compiled or the number of prototype
// checks otherwise.
int GetPrototypeCheckCount(Handle<Map> receiver_map, Handle<JSObject> holder);
// Creates a data handler that represents a prototype chain check followed
// by given Smi-handler that encoded a load from the holder.
// Can be used only if IsPrototypeValidityCellCheckEnough() predicate is true.
Handle<Object> SimpleLoadFromPrototype(Handle<Map> receiver_map,
Handle<JSObject> holder,
Handle<Name> name,
Handle<Object> smi_handler);
friend class IC;

View File

@ -464,10 +464,7 @@ Register PropertyHandlerCompiler::CheckPrototypes(
name, scratch2, miss);
} else if (current_map->is_dictionary_map()) {
DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast.
if (!name->IsUniqueName()) {
DCHECK(name->IsString());
name = factory()->InternalizeString(Handle<String>::cast(name));
}
DCHECK(name->IsUniqueName());
DCHECK(current.is_null() ||
current->property_dictionary()->FindEntry(name) ==
NameDictionary::kNotFound);

View File

@ -464,10 +464,7 @@ Register PropertyHandlerCompiler::CheckPrototypes(
name, scratch2, miss);
} else if (current_map->is_dictionary_map()) {
DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast.
if (!name->IsUniqueName()) {
DCHECK(name->IsString());
name = factory()->InternalizeString(Handle<String>::cast(name));
}
DCHECK(name->IsUniqueName());
DCHECK(current.is_null() ||
current->property_dictionary()->FindEntry(name) ==
NameDictionary::kNotFound);

View File

@ -472,10 +472,7 @@ Register PropertyHandlerCompiler::CheckPrototypes(
name, scratch2, miss);
} else if (current_map->is_dictionary_map()) {
DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast.
if (!name->IsUniqueName()) {
DCHECK(name->IsString());
name = factory()->InternalizeString(Handle<String>::cast(name));
}
DCHECK(name->IsUniqueName());
DCHECK(current.is_null() ||
current->property_dictionary()->FindEntry(name) ==
NameDictionary::kNotFound);

View File

@ -452,10 +452,7 @@ Register PropertyHandlerCompiler::CheckPrototypes(
name, scratch2, miss);
} else if (current_map->is_dictionary_map()) {
DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast.
if (!name->IsUniqueName()) {
DCHECK(name->IsString());
name = factory()->InternalizeString(Handle<String>::cast(name));
}
DCHECK(name->IsUniqueName());
DCHECK(current.is_null() ||
current->property_dictionary()->FindEntry(name) ==
NameDictionary::kNotFound);

View File

@ -30,9 +30,10 @@ namespace {
bool CommonStubCacheChecks(StubCache* stub_cache, Name* name, Map* map,
Object* handler) {
// Validate that the name does not move on scavenge, and that we
// Validate that the name and handler do not move on scavenge, and that we
// can use identity checks instead of structural equality checks.
DCHECK(!name->GetHeap()->InNewSpace(name));
DCHECK(!name->GetHeap()->InNewSpace(handler));
DCHECK(name->IsUniqueName());
DCHECK(name->HasHashCode());
if (handler) {

View File

@ -475,10 +475,7 @@ Register PropertyHandlerCompiler::CheckPrototypes(
name, scratch2, miss);
} else if (current_map->is_dictionary_map()) {
DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast.
if (!name->IsUniqueName()) {
DCHECK(name->IsString());
name = factory()->InternalizeString(Handle<String>::cast(name));
}
DCHECK(name->IsUniqueName());
DCHECK(current.is_null() ||
current->property_dictionary()->FindEntry(name) ==
NameDictionary::kNotFound);

View File

@ -482,10 +482,7 @@ Register PropertyHandlerCompiler::CheckPrototypes(
name, scratch2, miss);
} else if (current_map->is_dictionary_map()) {
DCHECK(!current_map->IsJSGlobalProxyMap()); // Proxy maps are fast.
if (!name->IsUniqueName()) {
DCHECK(name->IsString());
name = factory()->InternalizeString(Handle<String>::cast(name));
}
DCHECK(name->IsUniqueName());
DCHECK(current.is_null() ||
current->property_dictionary()->FindEntry(name) ==
NameDictionary::kNotFound);

92
test/mjsunit/prototype-non-existing.js vendored Normal file
View File

@ -0,0 +1,92 @@
// Copyright 2016 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
// Dictionary object in the prototype chain.
(function() {
function A() {
this.z = "a";
}
var a = new A();
function B() {
this.b = "b";
}
B.prototype = a;
var b = new B();
// Ensure b stays slow.
for (var i = 0; i < 1200; i++) {
b["b"+i] = 0;
}
assertFalse(%HasFastProperties(b));
function C() {
this.c = "c";
}
C.prototype = b;
var c = new C();
function f(expected) {
assertFalse(%HasFastProperties(b));
var result = c.z;
assertEquals(expected, result);
}
f("a");
f("a");
f("a");
%OptimizeFunctionOnNextCall(f);
f("a");
a.z = "z";
f("z");
f("z");
f("z");
b.z = "bz";
f("bz");
f("bz");
f("bz");
})();
// Global object in the prototype chain.
(function() {
var global = this;
function A() {
this.z = "a";
}
A.prototype = global.__proto__;
var a = new A();
global.__proto__ = a;
function C() {
this.c = "c";
}
C.prototype = global;
var c = new C();
function f(expected) {
var result = c.z;
assertEquals(expected, result);
}
f("a");
f("a");
f("a");
%OptimizeFunctionOnNextCall(f);
f("a");
a.z = "z";
f("z");
f("z");
f("z");
global.z = "bz";
f("bz");
f("bz");
f("bz");
})();