diff --git a/src/factory.cc b/src/factory.cc index 9f6025b22e..e24a941b8a 100644 --- a/src/factory.cc +++ b/src/factory.cc @@ -195,6 +195,29 @@ Handle Factory::CopyMap(Handle src) { } +Handle Factory::CopyMap(Handle src, + int extra_inobject_properties) { + Handle 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 Factory::CopyMapDropTransitions(Handle src) { CALL_HEAP_FUNCTION(src->CopyDropTransitions(), Map); } @@ -577,16 +600,6 @@ Handle Factory::NewJSObjectFromMap(Handle map) { } -Handle Factory::NewObjectLiteral(int expected_number_of_properties) { - Handle map = Handle(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 Factory::NewArrayLiteral(int length) { return NewJSArrayWithElements(NewFixedArray(length), TENURED); } @@ -816,7 +829,8 @@ Handle Factory::ObjectLiteralMapFromCache(Handle context, if (result->IsMap()) return Handle::cast(result); // Create a new map and add it to the cache. Handle map = - CopyMap(Handle(context->object_function()->initial_map())); + CopyMap(Handle(context->object_function()->initial_map()), + keys->length()); AddToMapCache(context, keys, map); return Handle(map); } diff --git a/src/factory.h b/src/factory.h index 3cbefd8ada..4ada152a39 100644 --- a/src/factory.h +++ b/src/factory.h @@ -157,6 +157,10 @@ class Factory : public AllStatic { static Handle CopyMap(Handle map); + // Copy the map adding more inobject properties if possible without + // overflowing the instance size. + static Handle CopyMap(Handle map, int extra_inobject_props); + static Handle CopyMapDropTransitions(Handle map); static Handle CopyFixedArray(Handle array); @@ -182,10 +186,6 @@ class Factory : public AllStatic { // runtime. static Handle NewJSObjectFromMap(Handle map); - // Allocate a JS object representing an object literal. The object is - // pretenured (allocated directly in the old generation). - static Handle 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 NewArrayLiteral(int length); diff --git a/src/handles.cc b/src/handles.cc index a33cbfc6cb..58ba4728eb 100644 --- a/src/handles.cc +++ b/src/handles.cc @@ -99,8 +99,9 @@ void SetExpectedNofPropertiesFromEstimate(Handle func, } -void NormalizeProperties(Handle object) { - CALL_HEAP_FUNCTION_VOID(object->NormalizeProperties()); +void NormalizeProperties(Handle object, + PropertyNormalizationMode mode) { + CALL_HEAP_FUNCTION_VOID(object->NormalizeProperties(mode)); } @@ -454,7 +455,7 @@ OptimizedObjectForAddingMultipleProperties(Handle 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 { diff --git a/src/handles.h b/src/handles.h index 6d75c77a17..23709bbbd7 100644 --- a/src/handles.h +++ b/src/handles.h @@ -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 object); +void NormalizeProperties(Handle object, + PropertyNormalizationMode mode); void NormalizeElements(Handle object); void TransformToFastProperties(Handle object, int unused_property_fields); diff --git a/src/heap.cc b/src/heap.cc index c4a860bedc..ba157fe253 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -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. diff --git a/src/objects.cc b/src/objects.cc index edc5a0d03c..9981e4cf5e 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -43,6 +43,22 @@ namespace v8 { namespace internal { +#define FIELD_ADDR(p, offset) \ + (reinterpret_cast(p) + offset - kHeapObjectTag) + + +#define WRITE_FIELD(p, offset, value) \ + (*reinterpret_cast(FIELD_ADDR(p, offset)) = value) + + +#define WRITE_INT_FIELD(p, offset, value) \ + (*reinterpret_cast(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; } diff --git a/src/objects.h b/src/objects.h index c79838aad7..328ec45d41 100644 --- a/src/objects.h +++ b/src/objects.h @@ -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; diff --git a/src/runtime.cc b/src/runtime.cc index 931c2a03e5..d2b62a4226 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -102,9 +102,9 @@ static Handle ComputeObjectLiteralMap( Handle context, Handle 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 ComputeObjectLiteralMap( } } *is_result_from_cache = false; - return Handle(context->object_function()->initial_map()); + return Factory::CopyMap( + Handle(context->object_function()->initial_map()), + number_of_properties); } diff --git a/test/mjsunit/large-object-literal.js b/test/mjsunit/large-object-literal.js index 8118afe4c9..70a27696ba 100644 --- a/test/mjsunit/large-object-literal.js +++ b/test/mjsunit/large-object-literal.js @@ -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]); +} diff --git a/test/mjsunit/object-literal-gc.js b/test/mjsunit/object-literal-gc.js new file mode 100644 index 0000000000..b9d6285cfe --- /dev/null +++ b/test/mjsunit/object-literal-gc.js @@ -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]); +} +