Allocate as many object-literal properties as possible inobject.

This can lead to large objects which wastes a lot of space if we normalize properties.  We therfore clear the inobject properties when normalizing properties.  This is done by adjusting the instance size in the new map and overwriting the inobject properties with a filler.
Review URL: http://codereview.chromium.org/17308

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1051 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
ager@chromium.org 2009-01-12 10:59:58 +00:00
parent 4a16e4928a
commit 12854e6c67
10 changed files with 208 additions and 59 deletions

View File

@ -195,6 +195,29 @@ Handle<Map> Factory::CopyMap(Handle<Map> src) {
}
Handle<Map> Factory::CopyMap(Handle<Map> src,
int extra_inobject_properties) {
Handle<Map> copy = CopyMap(src);
// Check that we do not overflow the instance size when adding the
// extra inobject properties.
int instance_size_delta = extra_inobject_properties * kPointerSize;
int max_instance_size_delta =
JSObject::kMaxInstanceSize - copy->instance_size();
if (instance_size_delta > max_instance_size_delta) {
// If the instance size overflows, we allocate as many properties
// as we can as inobject properties.
instance_size_delta = max_instance_size_delta;
extra_inobject_properties = max_instance_size_delta >> kPointerSizeLog2;
}
// Adjust the map with the extra inobject properties.
int inobject_properties =
copy->inobject_properties() + extra_inobject_properties;
copy->set_inobject_properties(inobject_properties);
copy->set_unused_property_fields(inobject_properties);
copy->set_instance_size(copy->instance_size() + instance_size_delta);
return copy;
}
Handle<Map> Factory::CopyMapDropTransitions(Handle<Map> src) {
CALL_HEAP_FUNCTION(src->CopyDropTransitions(), Map);
}
@ -577,16 +600,6 @@ Handle<JSObject> Factory::NewJSObjectFromMap(Handle<Map> map) {
}
Handle<JSObject> Factory::NewObjectLiteral(int expected_number_of_properties) {
Handle<Map> map = Handle<Map>(Top::object_function()->initial_map());
map = Factory::CopyMap(map);
map->set_instance_descriptors(Heap::empty_descriptor_array());
map->set_unused_property_fields(expected_number_of_properties);
CALL_HEAP_FUNCTION(Heap::AllocateJSObjectFromMap(*map, TENURED),
JSObject);
}
Handle<JSArray> Factory::NewArrayLiteral(int length) {
return NewJSArrayWithElements(NewFixedArray(length), TENURED);
}
@ -816,7 +829,8 @@ Handle<Map> Factory::ObjectLiteralMapFromCache(Handle<Context> context,
if (result->IsMap()) return Handle<Map>::cast(result);
// Create a new map and add it to the cache.
Handle<Map> map =
CopyMap(Handle<Map>(context->object_function()->initial_map()));
CopyMap(Handle<Map>(context->object_function()->initial_map()),
keys->length());
AddToMapCache(context, keys, map);
return Handle<Map>(map);
}

View File

@ -157,6 +157,10 @@ class Factory : public AllStatic {
static Handle<Map> CopyMap(Handle<Map> map);
// Copy the map adding more inobject properties if possible without
// overflowing the instance size.
static Handle<Map> CopyMap(Handle<Map> map, int extra_inobject_props);
static Handle<Map> CopyMapDropTransitions(Handle<Map> map);
static Handle<FixedArray> CopyFixedArray(Handle<FixedArray> array);
@ -182,10 +186,6 @@ class Factory : public AllStatic {
// runtime.
static Handle<JSObject> NewJSObjectFromMap(Handle<Map> map);
// Allocate a JS object representing an object literal. The object is
// pretenured (allocated directly in the old generation).
static Handle<JSObject> NewObjectLiteral(int expected_number_of_properties);
// Allocate a JS array representing an array literal. The array is
// pretenured (allocated directly in the old generation).
static Handle<JSArray> NewArrayLiteral(int length);

View File

@ -99,8 +99,9 @@ void SetExpectedNofPropertiesFromEstimate(Handle<JSFunction> func,
}
void NormalizeProperties(Handle<JSObject> object) {
CALL_HEAP_FUNCTION_VOID(object->NormalizeProperties());
void NormalizeProperties(Handle<JSObject> object,
PropertyNormalizationMode mode) {
CALL_HEAP_FUNCTION_VOID(object->NormalizeProperties(mode));
}
@ -454,7 +455,7 @@ OptimizedObjectForAddingMultipleProperties(Handle<JSObject> object,
// Normalize the properties of object to avoid n^2 behavior
// when extending the object multiple properties.
unused_property_fields_ = object->map()->unused_property_fields();
NormalizeProperties(object_);
NormalizeProperties(object_, KEEP_INOBJECT_PROPERTIES);
has_been_transformed_ = true;
} else {

View File

@ -96,7 +96,8 @@ class Handle {
// an object of expected type, or the handle is an error if running out
// of space or encounting an internal error.
void NormalizeProperties(Handle<JSObject> object);
void NormalizeProperties(Handle<JSObject> object,
PropertyNormalizationMode mode);
void NormalizeElements(Handle<JSObject> object);
void TransformToFastProperties(Handle<JSObject> object,
int unused_property_fields);

View File

@ -2293,6 +2293,7 @@ Object* Heap::CopyFixedArray(FixedArray* src) {
Object* Heap::AllocateFixedArray(int length) {
if (length == 0) return empty_fixed_array();
Object* result = AllocateRawFixedArray(length);
if (!result->IsFailure()) {
// Initialize header.

View File

@ -43,6 +43,22 @@
namespace v8 { namespace internal {
#define FIELD_ADDR(p, offset) \
(reinterpret_cast<byte*>(p) + offset - kHeapObjectTag)
#define WRITE_FIELD(p, offset, value) \
(*reinterpret_cast<Object**>(FIELD_ADDR(p, offset)) = value)
#define WRITE_INT_FIELD(p, offset, value) \
(*reinterpret_cast<int*>(FIELD_ADDR(p, offset)) = value)
#define WRITE_BARRIER(object, offset) \
Heap::RecordWrite(object->address(), offset);
// Getters and setters are stored in a fixed array property. These are
// constants for their indices.
const int kGetterIndex = 0;
@ -1040,7 +1056,7 @@ Object* JSObject::AddFastProperty(String* name,
// Normalize the object if the name is not a real identifier.
StringInputBuffer buffer(name);
if (!Scanner::IsIdentifier(&buffer)) {
Object* obj = NormalizeProperties();
Object* obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES);
if (obj->IsFailure()) return obj;
return AddSlowProperty(name, value, attributes);
}
@ -1078,7 +1094,7 @@ Object* JSObject::AddFastProperty(String* name,
if (map()->unused_property_fields() == 0) {
if (properties()->length() > kMaxFastProperties) {
Object* obj = NormalizeProperties();
Object* obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES);
if (obj->IsFailure()) return obj;
return AddSlowProperty(name, value, attributes);
}
@ -1180,7 +1196,7 @@ Object* JSObject::AddProperty(String* name,
} else {
// Normalize the object to prevent very large instance descriptors.
// This eliminates unwanted N^2 allocation and lookup behavior.
Object* obj = NormalizeProperties();
Object* obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES);
if (obj->IsFailure()) return obj;
}
}
@ -1253,7 +1269,7 @@ Object* JSObject::ConvertDescriptorToField(String* name,
PropertyAttributes attributes) {
if (map()->unused_property_fields() == 0 &&
properties()->length() > kMaxFastProperties) {
Object* obj = NormalizeProperties();
Object* obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES);
if (obj->IsFailure()) return obj;
return ReplaceSlowProperty(name, new_value, attributes);
}
@ -1848,7 +1864,7 @@ PropertyAttributes JSObject::GetLocalPropertyAttribute(String* name) {
}
Object* JSObject::NormalizeProperties() {
Object* JSObject::NormalizeProperties(PropertyNormalizationMode mode) {
if (!HasFastProperties()) return this;
// Allocate new content
@ -1908,13 +1924,33 @@ Object* JSObject::NormalizeProperties() {
// Allocate new map.
obj = map()->Copy();
if (obj->IsFailure()) return obj;
Map* new_map = Map::cast(obj);
// Clear inobject properties if needed by adjusting the instance
// size and putting in a filler or byte array instead of the
// inobject properties.
if (mode == CLEAR_INOBJECT_PROPERTIES && map()->inobject_properties() > 0) {
int instance_size_delta = map()->inobject_properties() * kPointerSize;
int new_instance_size = map()->instance_size() - instance_size_delta;
new_map->set_inobject_properties(0);
new_map->set_instance_size(new_instance_size);
if (instance_size_delta == kPointerSize) {
WRITE_FIELD(this, new_instance_size, Heap::one_word_filler_map());
} else {
int byte_array_length = ByteArray::LengthFor(instance_size_delta);
int byte_array_length_offset = new_instance_size + kPointerSize;
WRITE_FIELD(this, new_instance_size, Heap::byte_array_map());
WRITE_INT_FIELD(this, byte_array_length_offset, byte_array_length);
}
WRITE_BARRIER(this, new_instance_size);
}
new_map->set_unused_property_fields(0);
// We have now sucessfully allocated all the necessary objects.
// Changes can now be made with the guarantee that all of them take effect.
set_map(Map::cast(obj));
set_map(new_map);
map()->set_instance_descriptors(Heap::empty_descriptor_array());
map()->set_unused_property_fields(0);
set_properties(dictionary);
Counters::props_to_dictionary.Increment();
@ -1982,7 +2018,7 @@ Object* JSObject::DeletePropertyPostInterceptor(String* name) {
if (!result.IsValid()) return Heap::true_value();
// Normalize object if needed.
Object* obj = NormalizeProperties();
Object* obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES);
if (obj->IsFailure()) return obj;
ASSERT(!HasFastProperties());
@ -2145,7 +2181,7 @@ Object* JSObject::DeleteProperty(String* name) {
return JSObject::cast(this)->DeleteLazyProperty(&result, name);
}
// Normalize object if needed.
Object* obj = NormalizeProperties();
Object* obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES);
if (obj->IsFailure()) return obj;
// Make sure the properties are normalized before removing the entry.
Dictionary* dictionary = property_dictionary();
@ -2411,7 +2447,7 @@ Object* JSObject::DefineGetterSetter(String* name,
}
// Normalize object to make this operation simple.
Object* ok = NormalizeProperties();
Object* ok = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES);
if (ok->IsFailure()) return ok;
// Allocate the fixed array to hold getter and setter.
@ -6664,7 +6700,9 @@ Object* Dictionary::TransformPropertiesToFastFor(JSObject* obj,
if (descriptors_unchecked->IsFailure()) return descriptors_unchecked;
DescriptorArray* descriptors = DescriptorArray::cast(descriptors_unchecked);
int number_of_allocated_fields = number_of_fields + unused_property_fields;
int inobject_props = obj->map()->inobject_properties();
int number_of_allocated_fields =
number_of_fields + unused_property_fields - inobject_props;
// Allocate the fixed array for the fields.
Object* fields = Heap::AllocateFixedArray(number_of_allocated_fields);
@ -6682,6 +6720,7 @@ Object* Dictionary::TransformPropertiesToFastFor(JSObject* obj,
if (key->IsFailure()) return key;
PropertyDetails details = DetailsAt(i);
PropertyType type = details.type();
if (value->IsJSFunction()) {
ConstantFunctionDescriptor d(String::cast(key),
JSFunction::cast(value),
@ -6689,7 +6728,14 @@ Object* Dictionary::TransformPropertiesToFastFor(JSObject* obj,
details.index());
w.Write(&d);
} else if (type == NORMAL) {
FixedArray::cast(fields)->set(current_offset, value);
if (current_offset < inobject_props) {
obj->InObjectPropertyAtPut(current_offset,
value,
UPDATE_WRITE_BARRIER);
} else {
int offset = current_offset - inobject_props;
FixedArray::cast(fields)->set(offset, value);
}
FieldDescriptor d(String::cast(key),
current_offset++,
details.attributes(),
@ -6722,8 +6768,9 @@ Object* Dictionary::TransformPropertiesToFastFor(JSObject* obj,
ASSERT(obj->IsJSObject());
descriptors->SetNextEnumerationIndex(NextEnumerationIndex());
// Check it really works.
// Check that it really works.
ASSERT(obj->HasFastProperties());
return obj;
}

View File

@ -166,9 +166,20 @@ class PropertyDetails BASE_EMBEDDED {
uint32_t value_;
};
// Setter that skips the write barrier if mode is SKIP_WRITE_BARRIER.
enum WriteBarrierMode { SKIP_WRITE_BARRIER, UPDATE_WRITE_BARRIER };
// PropertyNormalizationMode is used to specify wheter or not to
// keep inobject properties when normalizing properties of a
// JSObject.
enum PropertyNormalizationMode {
CLEAR_INOBJECT_PROPERTIES,
KEEP_INOBJECT_PROPERTIES
};
// All Maps have a field instance_type containing a InstanceType.
// It describes the type of the instances.
//
@ -560,9 +571,9 @@ enum CompareResult {
inline void set_##name(bool value); \
#define DECL_ACCESSORS(name, type) \
inline type* name(); \
inline void set_##name(type* value, \
#define DECL_ACCESSORS(name, type) \
inline type* name(); \
inline void set_##name(type* value, \
WriteBarrierMode mode = UPDATE_WRITE_BARRIER); \
@ -1357,7 +1368,7 @@ class JSObject: public HeapObject {
// Convert the object to use the canonical dictionary
// representation.
Object* NormalizeProperties();
Object* NormalizeProperties(PropertyNormalizationMode mode);
Object* NormalizeElements();
// Transform slow named properties to fast variants.
@ -2293,7 +2304,7 @@ class Code: public HeapObject {
// - How to iterate over an object (for garbage collection)
class Map: public HeapObject {
public:
// instance size.
// Instance size.
inline int instance_size();
inline void set_instance_size(int value);
@ -2301,16 +2312,16 @@ class Map: public HeapObject {
inline int inobject_properties();
inline void set_inobject_properties(int value);
// instance type.
// Instance type.
inline InstanceType instance_type();
inline void set_instance_type(InstanceType value);
// tells how many unused property fields are available in the instance.
// (only used for JSObject in fast mode).
// Tells how many unused property fields are available in the
// instance (only used for JSObject in fast mode).
inline int unused_property_fields();
inline void set_unused_property_fields(int value);
// bit field.
// Bit field.
inline byte bit_field();
inline void set_bit_field(byte value);
@ -2463,7 +2474,6 @@ class Map: public HeapObject {
static const int kInObjectPropertiesOffset = kInstanceSizesOffset + 1;
// The bytes at positions 2 and 3 are not in use at the moment.
// Byte offsets within kInstanceAttributesOffset attributes.
static const int kInstanceTypeOffset = kInstanceAttributesOffset + 0;
static const int kUnusedPropertyFieldsOffset = kInstanceAttributesOffset + 1;

View File

@ -102,9 +102,9 @@ static Handle<Map> ComputeObjectLiteralMap(
Handle<Context> context,
Handle<FixedArray> constant_properties,
bool* is_result_from_cache) {
int number_of_properties = constant_properties->length() / 2;
if (FLAG_canonicalize_object_literal_maps) {
// First find prefix of consecutive symbol keys.
int number_of_properties = constant_properties->length()/2;
int number_of_symbol_keys = 0;
while ((number_of_symbol_keys < number_of_properties) &&
(constant_properties->get(number_of_symbol_keys*2)->IsSymbol())) {
@ -125,7 +125,9 @@ static Handle<Map> ComputeObjectLiteralMap(
}
}
*is_result_from_cache = false;
return Handle<Map>(context->object_function()->initial_map());
return Factory::CopyMap(
Handle<Map>(context->object_function()->initial_map()),
number_of_properties);
}

View File

@ -25,25 +25,32 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Make sure that we can create large object literals.
var nofProperties = 150;
// Test that we can create object literals of various sizes.
function testLiteral(size) {
// Build large object literal string.
var literal = "var o = { ";
// Build object-literal string.
var literal = "var o = { ";
for (var i = 0; i < nofProperties; i++) {
if (i > 0) literal += ",";
literal += ("a" + i + ":" + i);
}
literal += "}";
for (var i = 0; i < size; i++) {
if (i > 0) literal += ",";
literal += ("a" + i + ":" + i);
}
literal += "}";
// Create the object literal.
eval(literal);
// Create the large object literal
eval(literal);
// Check that the properties have the expected values.
for (var i = 0; i < nofProperties; i++) {
assertEquals(o["a"+i], i);
// Check that the properties have the expected values.
for (var i = 0; i < size; i++) {
assertEquals(i, o["a"+i]);
}
}
// The sizes to test.
var sizes = [0, 1, 2, 100, 200, 400, 1000];
// Run the test.
for (var i = 0; i < sizes.length; i++) {
testLiteral(sizes[i]);
}

View File

@ -0,0 +1,66 @@
// Copyright 2009 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: --expose-gc
// Test that the clearing of object literal when normalizing objects
// works. In particular, test that the garbage collector handles the
// normalized object literals correctly.
function testLiteral(size) {
// Build object-literal string.
var literal = "var o = { ";
for (var i = 0; i < size; i++) {
if (i > 0) literal += ",";
literal += ("a" + i + ":" + i);
}
literal += "}";
// Create the object literal.
eval(literal);
// Force normalization of the properties.
delete o["a" + (size - 1)];
// Perform GC.
gc();
// Check that the properties have the expected values.
for (var i = 0; i < size - 1; i++) {
assertEquals(i, o["a"+i]);
}
}
// The sizes to test.
var sizes = [0, 1, 2, 100, 200, 400, 1000];
// Run the test.
for (var i = 0; i < sizes.length; i++) {
testLiteral(sizes[i]);
}