Commit be0494ba authored by jkummerow's avatar jkummerow Committed by Commit bot

Keep prototype maps in dictionary mode until ICs see them

Adding properties to prototypes is faster when we don't force their
maps into fast mode yet. Once a prototype shows up in the IC system,
its setup phase is likely over, and it makes sense to transition it
to fast properties.
This patch speeds up the microbenchmark in the bug by 20x.
Octane-Typescript sees a 3% improvement.

BUG=chromium:607010

Review-Url: https://codereview.chromium.org/2036493006
Cr-Commit-Position: refs/heads/master@{#36828}
parent 49013198
......@@ -3814,7 +3814,7 @@ Local<Object> v8::Object::FindInstanceInPrototypeChain(
v8::Local<FunctionTemplate> tmpl) {
auto isolate = Utils::OpenHandle(this)->GetIsolate();
i::PrototypeIterator iter(isolate, *Utils::OpenHandle(this),
i::PrototypeIterator::START_AT_RECEIVER);
i::kStartAtReceiver);
auto tmpl_info = *Utils::OpenHandle(*tmpl);
while (!tmpl_info->IsTemplateFor(iter.GetCurrent())) {
iter.Advance();
......
......@@ -272,8 +272,7 @@ inline bool HasOnlySimpleReceiverElements(Isolate* isolate,
inline bool HasOnlySimpleElements(Isolate* isolate, JSReceiver* receiver) {
DisallowHeapAllocation no_gc;
PrototypeIterator iter(isolate, receiver,
PrototypeIterator::START_AT_RECEIVER);
PrototypeIterator iter(isolate, receiver, kStartAtReceiver);
for (; !iter.IsAtEnd(); iter.Advance()) {
if (iter.GetCurrent()->IsJSProxy()) return false;
JSObject* current = iter.GetCurrent<JSObject>();
......
......@@ -8097,8 +8097,7 @@ HInstruction* HGraphBuilder::BuildConstantMapCheck(Handle<JSObject> constant) {
HInstruction* HGraphBuilder::BuildCheckPrototypeMaps(Handle<JSObject> prototype,
Handle<JSObject> holder) {
PrototypeIterator iter(isolate(), prototype,
PrototypeIterator::START_AT_RECEIVER);
PrototypeIterator iter(isolate(), prototype, kStartAtReceiver);
while (holder.is_null() ||
!PrototypeIterator::GetCurrent(iter).is_identical_to(holder)) {
BuildConstantMapCheck(PrototypeIterator::GetCurrent<JSObject>(iter));
......
......@@ -96,6 +96,7 @@ Handle<PrototypeInfo> Factory::NewPrototypeInfo() {
result->set_prototype_users(WeakFixedArray::Empty());
result->set_registry_slot(PrototypeInfo::UNREGISTERED);
result->set_validity_cell(Smi::FromInt(0));
result->set_bit_field(0);
return result;
}
......
......@@ -595,6 +595,7 @@ enum CacheHolderFlag {
kCacheOnReceiver
};
enum WhereToStart { kStartAtReceiver, kStartAtPrototype };
// The Store Buffer (GC).
typedef enum {
......
......@@ -444,8 +444,7 @@ Handle<Code> NamedStoreHandlerCompiler::CompileStoreTransition(
PrototypeIterator::WhereToEnd end =
name->IsPrivate() ? PrototypeIterator::END_AT_NON_HIDDEN
: PrototypeIterator::END_AT_NULL;
PrototypeIterator iter(isolate(), holder(),
PrototypeIterator::START_AT_PROTOTYPE, end);
PrototypeIterator iter(isolate(), holder(), kStartAtPrototype, end);
while (!iter.IsAtEnd()) {
last = PrototypeIterator::GetCurrent<JSObject>(iter);
iter.Advance();
......
......@@ -227,7 +227,6 @@ bool IC::AddressIsOptimizedCode() const {
return host->kind() == Code::OPTIMIZED_FUNCTION;
}
static void LookupForRead(LookupIterator* it) {
for (; it->IsFound(); it->Next()) {
switch (it->state()) {
......@@ -641,6 +640,9 @@ MaybeHandle<Object> LoadIC::Load(Handle<Object> object, Handle<Name> name) {
}
}
if (state() != UNINITIALIZED) {
JSObject::MakePrototypesFast(object, kStartAtReceiver, isolate());
}
// Named lookup in the object.
LookupIterator it(object, name);
LookupForRead(&it);
......@@ -1530,6 +1532,9 @@ MaybeHandle<Object> StoreIC::Store(Handle<Object> object, Handle<Name> name,
return TypeError(MessageTemplate::kNonObjectPropertyStore, object, name);
}
if (state() != UNINITIALIZED) {
JSObject::MakePrototypesFast(object, kStartAtPrototype, isolate());
}
LookupIterator it(object, name);
if (FLAG_use_ic) UpdateCaches(&it, value, store_mode);
......
......@@ -157,8 +157,7 @@ Maybe<bool> KeyAccumulator::CollectKeys(Handle<JSReceiver> receiver,
PrototypeIterator::WhereToEnd end = mode_ == KeyCollectionMode::kOwnOnly
? PrototypeIterator::END_AT_NON_HIDDEN
: PrototypeIterator::END_AT_NULL;
for (PrototypeIterator iter(isolate_, object,
PrototypeIterator::START_AT_RECEIVER, end);
for (PrototypeIterator iter(isolate_, object, kStartAtReceiver, end);
!iter.IsAtEnd();) {
Handle<JSReceiver> current =
PrototypeIterator::GetCurrent<JSReceiver>(iter);
......
......@@ -496,8 +496,7 @@ bool LookupIterator::HolderIsReceiverOrHiddenPrototype() const {
if (!current->map()->has_hidden_prototype()) return false;
// JSProxy do not occur as hidden prototypes.
if (object->IsJSProxy()) return false;
PrototypeIterator iter(isolate(), current,
PrototypeIterator::START_AT_PROTOTYPE,
PrototypeIterator iter(isolate(), current, kStartAtPrototype,
PrototypeIterator::END_AT_NON_HIDDEN);
while (!iter.IsAtEnd()) {
if (iter.GetCurrent<JSReceiver>() == object) return true;
......
......@@ -283,9 +283,8 @@ Handle<Object> CallSite::GetMethodName() {
HandleScope outer_scope(isolate_);
Handle<Object> result;
for (PrototypeIterator iter(isolate_, obj,
PrototypeIterator::START_AT_RECEIVER);
!iter.IsAtEnd(); iter.Advance()) {
for (PrototypeIterator iter(isolate_, obj, kStartAtReceiver); !iter.IsAtEnd();
iter.Advance()) {
Handle<Object> current = PrototypeIterator::GetCurrent(iter);
if (!current->IsJSObject()) break;
Handle<JSObject> current_obj = Handle<JSObject>::cast(current);
......
......@@ -1112,8 +1112,7 @@ MaybeHandle<Object> JSReceiver::GetPrototype(Isolate* isolate,
Handle<JSReceiver> receiver) {
// We don't expect access checks to be needed on JSProxy objects.
DCHECK(!receiver->IsAccessCheckNeeded() || receiver->IsJSObject());
PrototypeIterator iter(isolate, receiver,
PrototypeIterator::START_AT_RECEIVER,
PrototypeIterator iter(isolate, receiver, kStartAtReceiver,
PrototypeIterator::END_AT_NON_HIDDEN);
do {
if (!iter.AdvanceFollowingProxies()) return MaybeHandle<Object>();
......@@ -4576,6 +4575,10 @@ bool Map::is_prototype_map() const {
return IsPrototypeMapBits::decode(bit_field2());
}
bool Map::should_be_fast_prototype_map() const {
if (!prototype_info()->IsPrototypeInfo()) return false;
return PrototypeInfo::cast(prototype_info())->should_be_fast_map();
}
void Map::set_elements_kind(ElementsKind elements_kind) {
DCHECK(static_cast<int>(elements_kind) < kElementsKindCount);
......@@ -5511,6 +5514,8 @@ ACCESSORS(Box, value, Object, kValueOffset)
ACCESSORS(PrototypeInfo, prototype_users, Object, kPrototypeUsersOffset)
SMI_ACCESSORS(PrototypeInfo, registry_slot, kRegistrySlotOffset)
ACCESSORS(PrototypeInfo, validity_cell, Object, kValidityCellOffset)
SMI_ACCESSORS(PrototypeInfo, bit_field, kBitFieldOffset)
BOOL_ACCESSORS(PrototypeInfo, bit_field, should_be_fast_map, kShouldBeFastBit)
ACCESSORS(SloppyBlockWithEvalContextExtension, scope_info, ScopeInfo,
kScopeInfoOffset)
......
......@@ -1026,7 +1026,7 @@ Object* FunctionTemplateInfo::GetCompatibleReceiver(Isolate* isolate,
FunctionTemplateInfo* signature = FunctionTemplateInfo::cast(recv_type);
// Check the receiver.
for (PrototypeIterator iter(isolate, JSObject::cast(receiver),
PrototypeIterator::START_AT_RECEIVER,
kStartAtReceiver,
PrototypeIterator::END_AT_NON_HIDDEN);
!iter.IsAtEnd(); iter.Advance()) {
if (signature->IsTemplateFor(iter.GetCurrent())) return iter.GetCurrent();
......@@ -1484,7 +1484,7 @@ void JSObject::SetNormalizedProperty(Handle<JSObject> object,
Maybe<bool> JSReceiver::HasInPrototypeChain(Isolate* isolate,
Handle<JSReceiver> object,
Handle<Object> proto) {
PrototypeIterator iter(isolate, object, PrototypeIterator::START_AT_RECEIVER);
PrototypeIterator iter(isolate, object, kStartAtReceiver);
while (true) {
if (!iter.AdvanceFollowingProxies()) return Nothing<bool>();
if (iter.IsAtEnd()) return Just(false);
......@@ -11862,6 +11862,26 @@ static bool PrototypeBenefitsFromNormalization(Handle<JSObject> object) {
return false;
}
// static
void JSObject::MakePrototypesFast(Handle<Object> receiver,
WhereToStart where_to_start,
Isolate* isolate) {
if (!receiver->IsJSReceiver()) return;
for (PrototypeIterator iter(isolate, Handle<JSReceiver>::cast(receiver),
where_to_start);
!iter.IsAtEnd(); iter.Advance()) {
Handle<Object> current = PrototypeIterator::GetCurrent(iter);
if (!current->IsJSObject()) return;
Handle<JSObject> current_obj = Handle<JSObject>::cast(current);
Map* current_map = current_obj->map();
if (current_map->is_prototype_map() &&
!current_map->should_be_fast_prototype_map()) {
Handle<Map> map(current_map);
Map::SetShouldBeFastPrototypeMap(map, true, isolate);
JSObject::OptimizeAsPrototype(current_obj, FAST_PROTOTYPE);
}
}
}
// static
void JSObject::OptimizeAsPrototype(Handle<JSObject> object,
......@@ -11873,10 +11893,12 @@ void JSObject::OptimizeAsPrototype(Handle<JSObject> object,
"NormalizeAsPrototype");
}
Handle<Map> previous_map(object->map());
if (!object->HasFastProperties()) {
JSObject::MigrateSlowToFast(object, 0, "OptimizeAsPrototype");
}
if (!object->map()->is_prototype_map()) {
if (object->map()->is_prototype_map()) {
if (object->map()->should_be_fast_prototype_map() &&
!object->HasFastProperties()) {
JSObject::MigrateSlowToFast(object, 0, "OptimizeAsPrototype");
}
} else {
if (object->map() == *previous_map) {
Handle<Map> new_map = Map::Copy(handle(object->map()), "CopyAsPrototype");
JSObject::MigrateToMap(object, new_map);
......@@ -11904,6 +11926,7 @@ void JSObject::OptimizeAsPrototype(Handle<JSObject> object,
// static
void JSObject::ReoptimizeIfPrototype(Handle<JSObject> object) {
if (!object->map()->is_prototype_map()) return;
if (!object->map()->should_be_fast_prototype_map()) return;
OptimizeAsPrototype(object, FAST_PROTOTYPE);
}
......@@ -12045,6 +12068,15 @@ Handle<PrototypeInfo> Map::GetOrCreatePrototypeInfo(Handle<Map> prototype_map,
return proto_info;
}
// static
void Map::SetShouldBeFastPrototypeMap(Handle<Map> map, bool value,
Isolate* isolate) {
if (value == false && !map->prototype_info()->IsPrototypeInfo()) {
// "False" is the implicit default value, so there's nothing to do.
return;
}
GetOrCreatePrototypeInfo(map, isolate)->set_should_be_fast_map(value);
}
// static
Handle<Cell> Map::GetOrCreatePrototypeChainValidityCell(Handle<Map> map,
......@@ -13041,9 +13073,8 @@ void JSFunction::CalculateInstanceSizeForDerivedClass(
int* instance_size, int* in_object_properties) {
Isolate* isolate = GetIsolate();
int expected_nof_properties = 0;
for (PrototypeIterator iter(isolate, this,
PrototypeIterator::START_AT_RECEIVER);
!iter.IsAtEnd(); iter.Advance()) {
for (PrototypeIterator iter(isolate, this, kStartAtReceiver); !iter.IsAtEnd();
iter.Advance()) {
JSReceiver* current = iter.GetCurrent<JSReceiver>();
if (!current->IsJSFunction()) break;
JSFunction* func = JSFunction::cast(current);
......@@ -14867,8 +14898,7 @@ Maybe<bool> JSObject::SetPrototype(Handle<JSObject> object,
if (from_javascript) {
// Find the first object in the chain whose prototype object is not
// hidden.
PrototypeIterator iter(isolate, real_receiver,
PrototypeIterator::START_AT_PROTOTYPE,
PrototypeIterator iter(isolate, real_receiver, kStartAtPrototype,
PrototypeIterator::END_AT_NON_HIDDEN);
while (!iter.IsAtEnd()) {
// Casting to JSObject is fine because hidden prototypes are never
......@@ -14901,7 +14931,7 @@ Maybe<bool> JSObject::SetPrototype(Handle<JSObject> object,
// new prototype chain.
if (value->IsJSReceiver()) {
for (PrototypeIterator iter(isolate, JSReceiver::cast(*value),
PrototypeIterator::START_AT_RECEIVER);
kStartAtReceiver);
!iter.IsAtEnd(); iter.Advance()) {
if (iter.GetCurrent<JSReceiver>() == *object) {
// Cycle detected.
......
......@@ -2170,6 +2170,8 @@ class JSObject: public JSReceiver {
static void OptimizeAsPrototype(Handle<JSObject> object,
PrototypeOptimizationMode mode);
static void ReoptimizeIfPrototype(Handle<JSObject> object);
static void MakePrototypesFast(Handle<Object> receiver,
WhereToStart where_to_start, Isolate* isolate);
static void LazyRegisterPrototypeUser(Handle<Map> user, Isolate* isolate);
static void UpdatePrototypeUserRegistration(Handle<Map> old_map,
Handle<Map> new_map,
......@@ -5747,6 +5749,9 @@ class Map: public HeapObject {
Handle<JSObject> prototype, Isolate* isolate);
static Handle<PrototypeInfo> GetOrCreatePrototypeInfo(
Handle<Map> prototype_map, Isolate* isolate);
inline bool should_be_fast_prototype_map() const;
static void SetShouldBeFastPrototypeMap(Handle<Map> map, bool value,
Isolate* isolate);
// [prototype chain validity cell]: Associated with a prototype object,
// stored in that object's map's PrototypeInfo, indicates that prototype
......@@ -6314,6 +6319,11 @@ class PrototypeInfo : public Struct {
// given receiver embed the currently valid cell for that receiver's prototype
// during their compilation and check it on execution.
DECL_ACCESSORS(validity_cell, Object)
// [bit_field]
inline int bit_field() const;
inline void set_bit_field(int bit_field);
DECL_BOOLEAN_ACCESSORS(should_be_fast_map)
DECLARE_CAST(PrototypeInfo)
......@@ -6324,8 +6334,11 @@ class PrototypeInfo : public Struct {
static const int kPrototypeUsersOffset = HeapObject::kHeaderSize;
static const int kRegistrySlotOffset = kPrototypeUsersOffset + kPointerSize;
static const int kValidityCellOffset = kRegistrySlotOffset + kPointerSize;
static const int kConstructorNameOffset = kValidityCellOffset + kPointerSize;
static const int kSize = kConstructorNameOffset + kPointerSize;
static const int kBitFieldOffset = kValidityCellOffset + kPointerSize;
static const int kSize = kBitFieldOffset + kPointerSize;
// Bit field usage.
static const int kShouldBeFastBit = 0;
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(PrototypeInfo);
......
......@@ -25,14 +25,12 @@ namespace internal {
class PrototypeIterator {
public:
enum WhereToStart { START_AT_RECEIVER, START_AT_PROTOTYPE };
enum WhereToEnd { END_AT_NULL, END_AT_NON_HIDDEN };
const int kProxyPrototypeLimit = 100 * 1000;
PrototypeIterator(Isolate* isolate, Handle<JSReceiver> receiver,
WhereToStart where_to_start = START_AT_PROTOTYPE,
WhereToStart where_to_start = kStartAtPrototype,
WhereToEnd where_to_end = END_AT_NULL)
: object_(NULL),
handle_(receiver),
......@@ -41,32 +39,34 @@ class PrototypeIterator {
is_at_end_(false),
seen_proxies_(0) {
CHECK(!handle_.is_null());
if (where_to_start == START_AT_PROTOTYPE) Advance();
if (where_to_start == kStartAtPrototype) Advance();
}
PrototypeIterator(Isolate* isolate, JSReceiver* receiver,
WhereToStart where_to_start = START_AT_PROTOTYPE,
WhereToStart where_to_start = kStartAtPrototype,
WhereToEnd where_to_end = END_AT_NULL)
: object_(receiver),
isolate_(isolate),
where_to_end_(where_to_end),
is_at_end_(false),
seen_proxies_(0) {
if (where_to_start == START_AT_PROTOTYPE) Advance();
if (where_to_start == kStartAtPrototype) Advance();
}
explicit PrototypeIterator(Map* receiver_map)
: object_(receiver_map->prototype()),
isolate_(receiver_map->GetIsolate()),
where_to_end_(END_AT_NULL),
is_at_end_(object_->IsNull()) {}
is_at_end_(object_->IsNull()),
seen_proxies_(0) {}
explicit PrototypeIterator(Handle<Map> receiver_map)
: object_(NULL),
handle_(handle(receiver_map->prototype(), receiver_map->GetIsolate())),
isolate_(receiver_map->GetIsolate()),
where_to_end_(END_AT_NULL),
is_at_end_(handle_->IsNull()) {}
is_at_end_(handle_->IsNull()),
seen_proxies_(0) {}
~PrototypeIterator() {}
......
......@@ -203,8 +203,7 @@ RUNTIME_FUNCTION(Runtime_GetArrayKeys) {
KeyAccumulator accumulator(isolate, KeyCollectionMode::kOwnOnly,
ALL_PROPERTIES);
// No need to separate prototype levels since we only get element keys.
for (PrototypeIterator iter(isolate, array,
PrototypeIterator::START_AT_RECEIVER);
for (PrototypeIterator iter(isolate, array, kStartAtReceiver);
!iter.IsAtEnd(); iter.Advance()) {
if (PrototypeIterator::GetCurrent(iter)->IsJSProxy() ||
PrototypeIterator::GetCurrent<JSObject>(iter)
......@@ -451,8 +450,7 @@ RUNTIME_FUNCTION(Runtime_HasComplexElements) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0);
for (PrototypeIterator iter(isolate, array,
PrototypeIterator::START_AT_RECEIVER);
for (PrototypeIterator iter(isolate, array, kStartAtReceiver);
!iter.IsAtEnd(); iter.Advance()) {
if (PrototypeIterator::GetCurrent(iter)->IsJSProxy()) {
return isolate->heap()->true_value();
......
......@@ -1196,7 +1196,7 @@ RUNTIME_FUNCTION(Runtime_DebugGetLoadedScripts) {
static bool HasInPrototypeChainIgnoringProxies(Isolate* isolate,
JSObject* object,
Object* proto) {
PrototypeIterator iter(isolate, object, PrototypeIterator::START_AT_RECEIVER);
PrototypeIterator iter(isolate, object, kStartAtReceiver);
while (true) {
iter.AdvanceIgnoringProxies();
if (iter.IsAtEnd()) return false;
......
......@@ -22,6 +22,7 @@ namespace {
// deletions during a for-in.
MaybeHandle<HeapObject> Enumerate(Handle<JSReceiver> receiver) {
Isolate* const isolate = receiver->GetIsolate();
JSObject::MakePrototypesFast(receiver, kStartAtReceiver, isolate);
FastKeyAccumulator accumulator(isolate, receiver,
KeyCollectionMode::kIncludePrototypes,
ENUMERABLE_STRINGS);
......
......@@ -536,7 +536,7 @@ void StringStream::PrintPrototype(JSFunction* fun, Object* receiver) {
}
for (PrototypeIterator iter(isolate, JSObject::cast(receiver),
PrototypeIterator::START_AT_RECEIVER);
kStartAtReceiver);
!iter.IsAtEnd(); iter.Advance()) {
if (iter.GetCurrent()->IsJSProxy()) break;
Object* key = iter.GetCurrent<JSObject>()->SlowReverseLookup(fun);
......
......@@ -39,7 +39,13 @@ function SlowPrototype() {
SlowPrototype.prototype.bar = 2;
SlowPrototype.prototype.baz = 3;
delete SlowPrototype.prototype.baz;
new SlowPrototype;
assertFalse(%HasFastProperties(SlowPrototype.prototype));
var slow_proto = new SlowPrototype;
// ICs make prototypes fast.
function ic() { return slow_proto.bar; }
ic();
ic();
assertTrue(%HasFastProperties(slow_proto.__proto__));
// Prototypes stay fast even after deleting properties.
assertTrue(%HasFastProperties(SlowPrototype.prototype));
......
......@@ -46,14 +46,20 @@ function AddProps(obj) {
function DoProtoMagic(proto, set__proto__) {
var receiver;
if (set__proto__) {
(new Sub()).__proto__ = proto;
receiver = new Sub();
receiver.__proto__ = proto;
} else {
Sub.prototype = proto;
// Need to instantiate Sub to mark .prototype as prototype. Make sure the
// instantiated object is used so that the allocation is not optimized away.
%DebugPrint(new Sub());
receiver = new Sub();
}
// Prototypes are made fast when ICs encounter them.
function ic() { return typeof receiver.foo; }
ic();
ic();
}
......
......@@ -30,7 +30,7 @@ function __f_1(__v_4, add_first, __v_6, same_map_as) {
__f_4(__v_1);
assertFalse(%HasFastProperties(__v_1));
__f_0(__v_1, __v_6);
assertTrue(%HasFastProperties(__v_1));
assertFalse(%HasFastProperties(__v_1));
} else {
__f_0(__v_1, __v_6);
assertTrue(%HasFastProperties(__v_1));
......
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