Keep track of which maps are associated with prototype objects
so we can tune the fast-case vs. hash map heuristics accordingly. Review URL: https://chromiumcodereview.appspot.com/10448011 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@11681 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
40cc4062d2
commit
8cf2af4392
@ -1611,13 +1611,21 @@ bool JSObject::HasFastProperties() {
|
||||
}
|
||||
|
||||
|
||||
int JSObject::MaxFastProperties() {
|
||||
bool JSObject::TooManyFastProperties(int properties) {
|
||||
// Allow extra fast properties if the object has more than
|
||||
// kMaxFastProperties in-object properties. When this is the case,
|
||||
// kFastPropertiesSoftLimit in-object properties. When this is the case,
|
||||
// it is very unlikely that the object is being used as a dictionary
|
||||
// and there is a good chance that allowing more map transitions
|
||||
// will be worth it.
|
||||
return Max(map()->inobject_properties(), kMaxFastProperties);
|
||||
int inobject = map()->inobject_properties();
|
||||
|
||||
int limit;
|
||||
if (map()->used_for_prototype()) {
|
||||
limit = Max(inobject, kMaxFastProperties);
|
||||
} else {
|
||||
limit = Max(inobject, kFastPropertiesSoftLimit);
|
||||
}
|
||||
return properties >= limit;
|
||||
}
|
||||
|
||||
|
||||
@ -2924,6 +2932,20 @@ bool Map::is_shared() {
|
||||
}
|
||||
|
||||
|
||||
void Map::set_used_for_prototype(bool value) {
|
||||
if (value) {
|
||||
set_bit_field3(bit_field3() | (1 << kUsedForPrototype));
|
||||
} else {
|
||||
set_bit_field3(bit_field3() & ~(1 << kUsedForPrototype));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Map::used_for_prototype() {
|
||||
return ((1 << kUsedForPrototype) & bit_field3()) != 0;
|
||||
}
|
||||
|
||||
|
||||
JSFunction* Map::unchecked_constructor() {
|
||||
return reinterpret_cast<JSFunction*>(READ_FIELD(this, kConstructorOffset));
|
||||
}
|
||||
@ -4074,6 +4096,7 @@ Object* JSFunction::prototype() {
|
||||
return instance_prototype();
|
||||
}
|
||||
|
||||
|
||||
bool JSFunction::should_have_prototype() {
|
||||
return map()->function_with_prototype();
|
||||
}
|
||||
|
@ -1580,7 +1580,7 @@ MaybeObject* JSObject::AddFastProperty(String* name,
|
||||
}
|
||||
|
||||
if (map()->unused_property_fields() == 0) {
|
||||
if (properties()->length() > MaxFastProperties()) {
|
||||
if (TooManyFastProperties(properties()->length())) {
|
||||
Object* obj;
|
||||
{ MaybeObject* maybe_obj =
|
||||
NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0);
|
||||
@ -1833,7 +1833,7 @@ MaybeObject* JSObject::ConvertDescriptorToField(String* name,
|
||||
Object* new_value,
|
||||
PropertyAttributes attributes) {
|
||||
if (map()->unused_property_fields() == 0 &&
|
||||
properties()->length() > MaxFastProperties()) {
|
||||
TooManyFastProperties(properties()->length())) {
|
||||
Object* obj;
|
||||
{ MaybeObject* maybe_obj =
|
||||
NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0);
|
||||
@ -5039,6 +5039,7 @@ MaybeObject* Map::CopyDropTransitions() {
|
||||
return new_map;
|
||||
}
|
||||
|
||||
|
||||
void Map::UpdateCodeCache(Handle<Map> map,
|
||||
Handle<String> name,
|
||||
Handle<Code> code) {
|
||||
@ -7582,9 +7583,57 @@ bool JSFunction::IsInlineable() {
|
||||
}
|
||||
|
||||
|
||||
MaybeObject* JSObject::OptimizeAsPrototype() {
|
||||
if (IsGlobalObject()) return this;
|
||||
|
||||
// Make sure prototypes are fast objects and their maps have the bit set
|
||||
// so they remain fast.
|
||||
Map* proto_map = map();
|
||||
if (!proto_map->used_for_prototype()) {
|
||||
if (!HasFastProperties()) {
|
||||
MaybeObject* new_proto = TransformToFastProperties(0);
|
||||
if (new_proto->IsFailure()) return new_proto;
|
||||
ASSERT(new_proto == this);
|
||||
proto_map = map();
|
||||
if (!proto_map->is_shared()) {
|
||||
proto_map->set_used_for_prototype(true);
|
||||
}
|
||||
} else {
|
||||
Heap* heap = GetHeap();
|
||||
// We use the hole value as a singleton key in the prototype transition
|
||||
// map so that we don't multiply the number of maps unnecessarily.
|
||||
Map* new_map =
|
||||
proto_map->GetPrototypeTransition(heap->the_hole_value());
|
||||
if (new_map == NULL) {
|
||||
MaybeObject* maybe_new_map = proto_map->CopyDropTransitions();
|
||||
if (!maybe_new_map->To<Map>(&new_map)) return maybe_new_map;
|
||||
new_map->set_used_for_prototype(true);
|
||||
MaybeObject* ok =
|
||||
proto_map->PutPrototypeTransition(heap->the_hole_value(),
|
||||
new_map);
|
||||
if (ok->IsFailure()) return ok;
|
||||
}
|
||||
ASSERT(!proto_map->is_shared() && !new_map->is_shared());
|
||||
set_map(new_map);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
MaybeObject* JSFunction::SetInstancePrototype(Object* value) {
|
||||
ASSERT(value->IsJSReceiver());
|
||||
Heap* heap = GetHeap();
|
||||
|
||||
// First some logic for the map of the prototype to make sure the
|
||||
// used_for_prototype flag is set.
|
||||
if (value->IsJSObject()) {
|
||||
MaybeObject* ok = JSObject::cast(value)->OptimizeAsPrototype();
|
||||
if (ok->IsFailure()) return ok;
|
||||
}
|
||||
|
||||
// Now some logic for the maps of the objects that are created by using this
|
||||
// function as a constructor.
|
||||
if (has_initial_map()) {
|
||||
// If the function has allocated the initial map
|
||||
// replace it with a copy containing the new prototype.
|
||||
@ -8756,7 +8805,7 @@ MaybeObject* JSArray::SetElementsLength(Object* len) {
|
||||
}
|
||||
|
||||
|
||||
Object* Map::GetPrototypeTransition(Object* prototype) {
|
||||
Map* Map::GetPrototypeTransition(Object* prototype) {
|
||||
FixedArray* cache = prototype_transitions();
|
||||
int number_of_transitions = NumberOfProtoTransitions();
|
||||
const int proto_offset =
|
||||
@ -8766,8 +8815,7 @@ Object* Map::GetPrototypeTransition(Object* prototype) {
|
||||
for (int i = 0; i < number_of_transitions; i++) {
|
||||
if (cache->get(proto_offset + i * step) == prototype) {
|
||||
Object* map = cache->get(map_offset + i * step);
|
||||
ASSERT(map->IsMap());
|
||||
return map;
|
||||
return Map::cast(map);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
@ -8875,21 +8923,26 @@ MaybeObject* JSReceiver::SetPrototype(Object* value,
|
||||
// Nothing to do if prototype is already set.
|
||||
if (map->prototype() == value) return value;
|
||||
|
||||
Object* new_map = map->GetPrototypeTransition(value);
|
||||
if (value->IsJSObject()) {
|
||||
MaybeObject* ok = JSObject::cast(value)->OptimizeAsPrototype();
|
||||
if (ok->IsFailure()) return ok;
|
||||
}
|
||||
|
||||
Map* new_map = map->GetPrototypeTransition(value);
|
||||
if (new_map == NULL) {
|
||||
{ MaybeObject* maybe_new_map = map->CopyDropTransitions();
|
||||
if (!maybe_new_map->ToObject(&new_map)) return maybe_new_map;
|
||||
if (!maybe_new_map->To<Map>(&new_map)) return maybe_new_map;
|
||||
}
|
||||
|
||||
{ MaybeObject* maybe_new_cache =
|
||||
map->PutPrototypeTransition(value, Map::cast(new_map));
|
||||
map->PutPrototypeTransition(value, new_map);
|
||||
if (maybe_new_cache->IsFailure()) return maybe_new_cache;
|
||||
}
|
||||
|
||||
Map::cast(new_map)->set_prototype(value);
|
||||
new_map->set_prototype(value);
|
||||
}
|
||||
ASSERT(Map::cast(new_map)->prototype() == value);
|
||||
real_receiver->set_map(Map::cast(new_map));
|
||||
ASSERT(new_map->prototype() == value);
|
||||
real_receiver->set_map(new_map);
|
||||
|
||||
heap->ClearInstanceofCache();
|
||||
ASSERT(size == Size());
|
||||
|
@ -1583,6 +1583,8 @@ class JSObject: public JSReceiver {
|
||||
MUST_USE_RESULT MaybeObject* DeleteNormalizedProperty(String* name,
|
||||
DeleteMode mode);
|
||||
|
||||
MUST_USE_RESULT MaybeObject* OptimizeAsPrototype();
|
||||
|
||||
// Retrieve interceptors.
|
||||
InterceptorInfo* GetNamedInterceptor();
|
||||
InterceptorInfo* GetIndexedInterceptor();
|
||||
@ -2053,7 +2055,7 @@ class JSObject: public JSReceiver {
|
||||
// Maximal number of fast properties for the JSObject. Used to
|
||||
// restrict the number of map transitions to avoid an explosion in
|
||||
// the number of maps for objects used as dictionaries.
|
||||
inline int MaxFastProperties();
|
||||
inline bool TooManyFastProperties(int properties);
|
||||
|
||||
// Maximal number of elements (numbered 0 .. kMaxElementCount - 1).
|
||||
// Also maximal value of JSArray's length property.
|
||||
@ -2075,7 +2077,8 @@ class JSObject: public JSReceiver {
|
||||
static const int kMaxUncheckedOldFastElementsLength = 500;
|
||||
|
||||
static const int kInitialMaxFastElementArray = 100000;
|
||||
static const int kMaxFastProperties = 12;
|
||||
static const int kFastPropertiesSoftLimit = 12;
|
||||
static const int kMaxFastProperties = 32;
|
||||
static const int kMaxInstanceSize = 255 * kPointerSize;
|
||||
// When extending the backing storage for property values, we increase
|
||||
// its size by more than the 1 entry necessary, so sequentially adding fields
|
||||
@ -4663,9 +4666,16 @@ class Map: public HeapObject {
|
||||
// behavior. If true, the map should never be modified, instead a clone
|
||||
// should be created and modified.
|
||||
inline void set_is_shared(bool value);
|
||||
|
||||
inline bool is_shared();
|
||||
|
||||
// Tells whether the map is used for an object that is a prototype for another
|
||||
// object or is the prototype on a function. Such maps are made faster by
|
||||
// tweaking the heuristics that distinguish between regular object-oriented
|
||||
// objects and the objects that are being used as hash maps. This flag is
|
||||
// for optimization, not correctness.
|
||||
inline void set_used_for_prototype(bool value);
|
||||
inline bool used_for_prototype();
|
||||
|
||||
// Tells whether the instance needs security checks when accessing its
|
||||
// properties.
|
||||
inline void set_is_access_check_needed(bool access_check_needed);
|
||||
@ -4854,9 +4864,18 @@ class Map: public HeapObject {
|
||||
|
||||
void TraverseTransitionTree(TraverseCallback callback, void* data);
|
||||
|
||||
// When you set the prototype of an object using the __proto__ accessor you
|
||||
// need a new map for the object (the prototype is stored in the map). In
|
||||
// order not to multiply maps unnecessarily we store these as transitions in
|
||||
// the original map. That way we can transition to the same map if the same
|
||||
// prototype is set, rather than creating a new map every time. The
|
||||
// transitions are in the form of a map where the keys are prototype objects
|
||||
// and the values are the maps the are transitioned to. The special key
|
||||
// the_hole denotes the map we should transition to when the
|
||||
// used_for_prototype flag is set.
|
||||
static const int kMaxCachedPrototypeTransitions = 256;
|
||||
|
||||
Object* GetPrototypeTransition(Object* prototype);
|
||||
Map* GetPrototypeTransition(Object* prototype);
|
||||
|
||||
MUST_USE_RESULT MaybeObject* PutPrototypeTransition(Object* prototype,
|
||||
Map* map);
|
||||
@ -4949,6 +4968,7 @@ class Map: public HeapObject {
|
||||
// Bit positions for bit field 3
|
||||
static const int kIsShared = 0;
|
||||
static const int kFunctionWithPrototype = 1;
|
||||
static const int kUsedForPrototype = 2;
|
||||
|
||||
// Layout of the default cache. It holds alternating name and code objects.
|
||||
static const int kCodeCacheEntrySize = 2;
|
||||
|
@ -13455,6 +13455,8 @@ ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(ExternalIntElements)
|
||||
ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(ExternalUnsignedIntElements)
|
||||
ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(ExternalFloatElements)
|
||||
ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(ExternalDoubleElements)
|
||||
// Properties test sitting with elements tests - not fooling anyone.
|
||||
ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(FastProperties)
|
||||
|
||||
#undef ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION
|
||||
|
||||
|
@ -380,6 +380,7 @@ namespace internal {
|
||||
F(HasExternalUnsignedIntElements, 1, 1) \
|
||||
F(HasExternalFloatElements, 1, 1) \
|
||||
F(HasExternalDoubleElements, 1, 1) \
|
||||
F(HasFastProperties, 1, 1) \
|
||||
F(TransitionElementsSmiToDouble, 1, 1) \
|
||||
F(TransitionElementsDoubleToObject, 1, 1) \
|
||||
F(HaveSameMap, 2, 1) \
|
||||
|
@ -1579,18 +1579,23 @@ TEST(PrototypeTransitionClearing) {
|
||||
*v8::Handle<v8::Object>::Cast(
|
||||
v8::Context::GetCurrent()->Global()->Get(v8_str("base"))));
|
||||
|
||||
// Verify that only dead prototype transitions are cleared.
|
||||
CHECK_EQ(10, baseObject->map()->NumberOfProtoTransitions());
|
||||
// Verify that only dead prototype transitions are cleared. There is an
|
||||
// extra, 11th, prototype transition on the Object map, which is the
|
||||
// transition to a map with the used_for_prototype flag set (the key is
|
||||
// the_hole).
|
||||
CHECK_EQ(11, baseObject->map()->NumberOfProtoTransitions());
|
||||
HEAP->CollectAllGarbage(Heap::kNoGCFlags);
|
||||
CHECK_EQ(10 - 3, baseObject->map()->NumberOfProtoTransitions());
|
||||
const int transitions = 11 - 3;
|
||||
CHECK_EQ(transitions, baseObject->map()->NumberOfProtoTransitions());
|
||||
|
||||
// Verify that prototype transitions array was compacted.
|
||||
FixedArray* trans = baseObject->map()->prototype_transitions();
|
||||
for (int i = 0; i < 10 - 3; i++) {
|
||||
for (int i = 0; i < transitions; i++) {
|
||||
int j = Map::kProtoTransitionHeaderSize +
|
||||
i * Map::kProtoTransitionElementsPerEntry;
|
||||
CHECK(trans->get(j + Map::kProtoTransitionMapOffset)->IsMap());
|
||||
CHECK(trans->get(j + Map::kProtoTransitionPrototypeOffset)->IsJSObject());
|
||||
Object* proto = trans->get(j + Map::kProtoTransitionPrototypeOffset);
|
||||
CHECK(proto->IsTheHole() || proto->IsJSObject());
|
||||
}
|
||||
|
||||
// Make sure next prototype is placed on an old-space evacuation candidate.
|
||||
|
92
test/mjsunit/fast-prototype.js
Normal file
92
test/mjsunit/fast-prototype.js
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2012 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Flags: --allow-natives-syntax
|
||||
|
||||
// Check that objects that are used for prototypes are in the fast mode.
|
||||
|
||||
function Super() {
|
||||
}
|
||||
|
||||
|
||||
function Sub() {
|
||||
}
|
||||
|
||||
|
||||
function AddProps(obj) {
|
||||
for (var i = 0; i < 26; i++) {
|
||||
obj["x" + i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function DoProtoMagic(proto, set__proto__) {
|
||||
if (set__proto__) {
|
||||
(new Sub()).__proto__ = proto;
|
||||
} else {
|
||||
Sub.prototype = proto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function test(use_new, add_first, set__proto__, same_map_as) {
|
||||
var proto = use_new ? new Super() : {};
|
||||
|
||||
// New object is fast.
|
||||
assertTrue(%HasFastProperties(proto));
|
||||
|
||||
if (add_first) {
|
||||
AddProps(proto);
|
||||
// Adding this many properties makes it slow.
|
||||
assertFalse(%HasFastProperties(proto));
|
||||
DoProtoMagic(proto, set__proto__);
|
||||
// Making it a prototype makes it fast again.
|
||||
assertTrue(%HasFastProperties(proto));
|
||||
} else {
|
||||
DoProtoMagic(proto, set__proto__);
|
||||
// Still fast
|
||||
assertTrue(%HasFastProperties(proto));
|
||||
AddProps(proto);
|
||||
// Setting the bit means it is still fast with all these properties.
|
||||
assertTrue(%HasFastProperties(proto));
|
||||
}
|
||||
if (same_map_as && !add_first) {
|
||||
assertTrue(%HaveSameMap(same_map_as, proto));
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var set__proto__ = ((i & 1) != 0);
|
||||
var use_new = ((i & 2) != 0);
|
||||
|
||||
test(use_new, true, set__proto__);
|
||||
|
||||
var last = test(use_new, false, set__proto__);
|
||||
test(use_new, false, set__proto__, last);
|
||||
}
|
Loading…
Reference in New Issue
Block a user