Remember and reuse derived map for external arrays

Ensure that all objects that had the same map before attaching an external array have the same map once the external array is attached.

BUG=75639
TEST=fast/canvas/webgl/uninitialized-test.html

Review URL: http://codereview.chromium.org/6685073

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@7318 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
danno@chromium.org 2011-03-23 09:57:12 +00:00
parent a12e07999f
commit 88854cd712
15 changed files with 289 additions and 70 deletions

View File

@ -2981,14 +2981,16 @@ void PrepareExternalArrayElements(i::Handle<i::JSObject> object,
// map if the element type is now changing, because assumptions about
// generated code based on the receiver's map will be invalid.
i::Handle<i::HeapObject> elements(object->elements());
bool force_unique_map =
bool cant_reuse_map =
elements->map()->IsUndefined() ||
!elements->map()->has_external_array_elements() ||
elements->map() != HEAP->MapForExternalArrayType(array_type);
if (force_unique_map) {
if (cant_reuse_map) {
i::Handle<i::Map> external_array_map =
FACTORY->NewExternalArrayElementsMap(
i::Handle<i::Map>(object->map()));
FACTORY->GetExternalArrayElementsMap(
i::Handle<i::Map>(object->map()),
array_type,
object->HasFastProperties());
object->set_map(*external_array_map);
}
object->set_elements(*array);

View File

@ -1936,6 +1936,7 @@ void Genesis::TransferNamedProperties(Handle<JSObject> from,
break;
}
case MAP_TRANSITION:
case EXTERNAL_ARRAY_TRANSITION:
case CONSTANT_TRANSITION:
case NULL_DESCRIPTOR:
// Ignore non-properties.

View File

@ -392,8 +392,14 @@ Handle<Map> Factory::GetSlowElementsMap(Handle<Map> src) {
}
Handle<Map> Factory::NewExternalArrayElementsMap(Handle<Map> src) {
CALL_HEAP_FUNCTION(isolate(), src->NewExternalArrayElementsMap(), Map);
Handle<Map> Factory::GetExternalArrayElementsMap(
Handle<Map> src,
ExternalArrayType array_type,
bool safe_to_add_transition) {
CALL_HEAP_FUNCTION(isolate(),
src->GetExternalArrayElementsMap(array_type,
safe_to_add_transition),
Map);
}

View File

@ -192,7 +192,9 @@ class Factory {
Handle<Map> GetSlowElementsMap(Handle<Map> map);
Handle<Map> NewExternalArrayElementsMap(Handle<Map> map);
Handle<Map> GetExternalArrayElementsMap(Handle<Map> map,
ExternalArrayType array_type,
bool safe_to_add_transition);
Handle<FixedArray> CopyFixedArray(Handle<FixedArray> array);

View File

@ -1101,11 +1101,11 @@ void MarkCompactCollector::MarkDescriptorArray(
ASSERT(contents->IsFixedArray());
ASSERT(contents->length() >= 2);
SetMark(contents);
// Contents contains (value, details) pairs. If the details say that
// the type of descriptor is MAP_TRANSITION, CONSTANT_TRANSITION, or
// NULL_DESCRIPTOR, we don't mark the value as live. Only for
// MAP_TRANSITION and CONSTANT_TRANSITION is the value an Object* (a
// Map*).
// Contents contains (value, details) pairs. If the details say that the type
// of descriptor is MAP_TRANSITION, CONSTANT_TRANSITION,
// EXTERNAL_ARRAY_TRANSITION or NULL_DESCRIPTOR, we don't mark the value as
// live. Only for MAP_TRANSITION, EXTERNAL_ARRAY_TRANSITION and
// CONSTANT_TRANSITION is the value an Object* (a Map*).
for (int i = 0; i < contents->length(); i += 2) {
// If the pair (value, details) at index i, i+1 is not
// a transition or null descriptor, mark the value.

View File

@ -170,14 +170,15 @@ PropertyKind.Indexed = 2;
// A copy of the PropertyType enum from global.h
PropertyType = {};
PropertyType.Normal = 0;
PropertyType.Field = 1;
PropertyType.ConstantFunction = 2;
PropertyType.Callbacks = 3;
PropertyType.Interceptor = 4;
PropertyType.MapTransition = 5;
PropertyType.ConstantTransition = 6;
PropertyType.NullDescriptor = 7;
PropertyType.Normal = 0;
PropertyType.Field = 1;
PropertyType.ConstantFunction = 2;
PropertyType.Callbacks = 3;
PropertyType.Interceptor = 4;
PropertyType.MapTransition = 5;
PropertyType.ExternalArrayTransition = 6;
PropertyType.ConstantTransition = 7;
PropertyType.NullDescriptor = 8;
// Different attributes for a property.

View File

@ -1722,7 +1722,8 @@ bool DescriptorArray::IsProperty(int descriptor_number) {
bool DescriptorArray::IsTransition(int descriptor_number) {
PropertyType t = GetType(descriptor_number);
return t == MAP_TRANSITION || t == CONSTANT_TRANSITION;
return t == MAP_TRANSITION || t == CONSTANT_TRANSITION ||
t == EXTERNAL_ARRAY_TRANSITION;
}
@ -2871,21 +2872,6 @@ MaybeObject* Map::GetSlowElementsMap() {
}
MaybeObject* Map::NewExternalArrayElementsMap() {
// TODO(danno): Special case empty object map (or most common case)
// to return a pre-canned pixel array map.
Object* obj;
{ MaybeObject* maybe_obj = CopyDropTransitions();
if (!maybe_obj->ToObject(&obj)) return maybe_obj;
}
Map* new_map = Map::cast(obj);
new_map->set_has_fast_elements(false);
new_map->set_has_external_array_elements(true);
isolate()->counters()->map_to_external_array_elements()->Increment();
return new_map;
}
ACCESSORS(Map, instance_descriptors, DescriptorArray,
kInstanceDescriptorsOffset)
ACCESSORS(Map, code_cache, Object, kCodeCacheOffset)

View File

@ -1282,12 +1282,21 @@ MaybeObject* JSObject::AddFastProperty(String* name,
}
}
// Only allow map transition if the object's map is NOT equal to the
// global object_function's map and there is not a transition for name.
// Only allow map transition if the object isn't the global object and there
// is not a transition for the name, or there's a transition for the name but
// it's unrelated to properties.
int descriptor_index = old_descriptors->Search(name);
// External array transitions are stored in the descriptor for property "",
// which is not a identifier and should have forced a switch to slow
// properties above.
ASSERT(descriptor_index == DescriptorArray::kNotFound ||
old_descriptors->GetType(descriptor_index) != EXTERNAL_ARRAY_TRANSITION);
bool can_insert_transition = descriptor_index == DescriptorArray::kNotFound ||
old_descriptors->GetType(descriptor_index) == EXTERNAL_ARRAY_TRANSITION;
bool allow_map_transition =
!old_descriptors->Contains(name) &&
(isolate->context()->global_context()->object_function()->
map() != map());
can_insert_transition &&
(isolate->context()->global_context()->object_function()->map() != map());
ASSERT(index < map()->inobject_properties() ||
(index - map()->inobject_properties()) < properties()->length() ||
@ -1819,6 +1828,77 @@ void Map::LookupInDescriptors(JSObject* holder,
}
MaybeObject* Map::GetExternalArrayElementsMap(ExternalArrayType array_type,
bool safe_to_add_transition) {
DescriptorArray* descriptors = instance_descriptors();
String* external_array_sentinel_name = GetIsolate()->heap()->empty_symbol();
if (safe_to_add_transition) {
// It's only safe to manipulate the descriptor array if it would be
// safe to add a transition.
ASSERT(!is_shared()); // no transitions can be added to shared maps.
// Check if the external array transition already exists.
DescriptorLookupCache* cache = heap()->isolate()->descriptor_lookup_cache();
int index = cache->Lookup(descriptors, external_array_sentinel_name);
if (index == DescriptorLookupCache::kAbsent) {
index = descriptors->Search(external_array_sentinel_name);
cache->Update(descriptors,
external_array_sentinel_name,
index);
}
// If the transition already exists, check the type. If there is a match,
// return it.
if (index != DescriptorArray::kNotFound) {
PropertyDetails details(PropertyDetails(descriptors->GetDetails(index)));
if (details.type() == EXTERNAL_ARRAY_TRANSITION &&
details.array_type() == array_type) {
return descriptors->GetValue(index);
} else {
safe_to_add_transition = false;
}
}
}
// No transition to an existing external array map. Make a new one.
Object* obj;
{ MaybeObject* maybe_map = CopyDropTransitions();
if (!maybe_map->ToObject(&obj)) return maybe_map;
}
Map* new_map = Map::cast(obj);
new_map->set_has_fast_elements(false);
new_map->set_has_external_array_elements(true);
GetIsolate()->counters()->map_to_external_array_elements()->Increment();
// Only remember the map transition if the object's map is NOT equal to the
// global object_function's map and there is not an already existing
// non-matching external array transition.
bool allow_map_transition =
safe_to_add_transition &&
(GetIsolate()->context()->global_context()->object_function()->map() !=
map());
if (allow_map_transition) {
// Allocate new instance descriptors for the old map with map transition.
ExternalArrayTransitionDescriptor desc(external_array_sentinel_name,
Map::cast(new_map),
array_type);
Object* new_descriptors;
MaybeObject* maybe_new_descriptors = descriptors->CopyInsert(
&desc,
KEEP_TRANSITIONS);
if (!maybe_new_descriptors->ToObject(&new_descriptors)) {
return maybe_new_descriptors;
}
descriptors = DescriptorArray::cast(new_descriptors);
set_instance_descriptors(descriptors);
}
return new_map;
}
void JSObject::LocalLookupRealNamedProperty(String* name,
LookupResult* result) {
if (IsJSGlobalProxy()) {
@ -2051,6 +2131,7 @@ MaybeObject* JSObject::SetProperty(LookupResult* result,
return ConvertDescriptorToFieldAndMapTransition(name, value, attributes);
}
case NULL_DESCRIPTOR:
case EXTERNAL_ARRAY_TRANSITION:
return ConvertDescriptorToFieldAndMapTransition(name, value, attributes);
default:
UNREACHABLE();
@ -2130,6 +2211,7 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
// if the value is a function.
return ConvertDescriptorToFieldAndMapTransition(name, value, attributes);
case NULL_DESCRIPTOR:
case EXTERNAL_ARRAY_TRANSITION:
return ConvertDescriptorToFieldAndMapTransition(name, value, attributes);
default:
UNREACHABLE();
@ -5484,6 +5566,7 @@ void Map::CreateBackPointers() {
DescriptorArray* descriptors = instance_descriptors();
for (int i = 0; i < descriptors->number_of_descriptors(); i++) {
if (descriptors->GetType(i) == MAP_TRANSITION ||
descriptors->GetType(i) == EXTERNAL_ARRAY_TRANSITION ||
descriptors->GetType(i) == CONSTANT_TRANSITION) {
// Get target.
Map* target = Map::cast(descriptors->GetValue(i));
@ -5526,6 +5609,7 @@ void Map::ClearNonLiveTransitions(Heap* heap, Object* real_prototype) {
// non-live object.
PropertyDetails details(Smi::cast(contents->get(i + 1)));
if (details.type() == MAP_TRANSITION ||
details.type() == EXTERNAL_ARRAY_TRANSITION ||
details.type() == CONSTANT_TRANSITION) {
Map* target = reinterpret_cast<Map*>(contents->get(i));
ASSERT(target->IsHeapObject());
@ -6399,6 +6483,7 @@ const char* Code::PropertyType2String(PropertyType type) {
case CALLBACKS: return "CALLBACKS";
case INTERCEPTOR: return "INTERCEPTOR";
case MAP_TRANSITION: return "MAP_TRANSITION";
case EXTERNAL_ARRAY_TRANSITION: return "EXTERNAL_ARRAY_TRANSITION";
case CONSTANT_TRANSITION: return "CONSTANT_TRANSITION";
case NULL_DESCRIPTOR: return "NULL_DESCRIPTOR";
}

View File

@ -135,19 +135,37 @@ class PropertyDetails BASE_EMBEDDED {
PropertyDetails(PropertyAttributes attributes,
PropertyType type,
int index = 0) {
ASSERT(type != EXTERNAL_ARRAY_TRANSITION);
ASSERT(TypeField::is_valid(type));
ASSERT(AttributesField::is_valid(attributes));
ASSERT(IndexField::is_valid(index));
ASSERT(StorageField::is_valid(index));
value_ = TypeField::encode(type)
| AttributesField::encode(attributes)
| IndexField::encode(index);
| StorageField::encode(index);
ASSERT(type == this->type());
ASSERT(attributes == this->attributes());
ASSERT(index == this->index());
}
PropertyDetails(PropertyAttributes attributes,
PropertyType type,
ExternalArrayType array_type) {
ASSERT(type == EXTERNAL_ARRAY_TRANSITION);
ASSERT(TypeField::is_valid(type));
ASSERT(AttributesField::is_valid(attributes));
ASSERT(StorageField::is_valid(static_cast<int>(array_type)));
value_ = TypeField::encode(type)
| AttributesField::encode(attributes)
| StorageField::encode(static_cast<int>(array_type));
ASSERT(type == this->type());
ASSERT(attributes == this->attributes());
ASSERT(array_type == this->array_type());
}
// Conversion for storing details as Object*.
inline PropertyDetails(Smi* smi);
inline Smi* AsSmi();
@ -157,7 +175,8 @@ class PropertyDetails BASE_EMBEDDED {
bool IsTransition() {
PropertyType t = type();
ASSERT(t != INTERCEPTOR);
return t == MAP_TRANSITION || t == CONSTANT_TRANSITION;
return t == MAP_TRANSITION || t == CONSTANT_TRANSITION ||
t == EXTERNAL_ARRAY_TRANSITION;
}
bool IsProperty() {
@ -166,11 +185,18 @@ class PropertyDetails BASE_EMBEDDED {
PropertyAttributes attributes() { return AttributesField::decode(value_); }
int index() { return IndexField::decode(value_); }
int index() { return StorageField::decode(value_); }
ExternalArrayType array_type() {
ASSERT(type() == EXTERNAL_ARRAY_TRANSITION);
return static_cast<ExternalArrayType>(StorageField::decode(value_));
}
inline PropertyDetails AsDeleted();
static bool IsValidIndex(int index) { return IndexField::is_valid(index); }
static bool IsValidIndex(int index) {
return StorageField::is_valid(index);
}
bool IsReadOnly() { return (attributes() & READ_ONLY) != 0; }
bool IsDontDelete() { return (attributes() & DONT_DELETE) != 0; }
@ -179,10 +205,10 @@ class PropertyDetails BASE_EMBEDDED {
// Bit fields in value_ (type, shift, size). Must be public so the
// constants can be embedded in generated code.
class TypeField: public BitField<PropertyType, 0, 3> {};
class AttributesField: public BitField<PropertyAttributes, 3, 3> {};
class DeletedField: public BitField<uint32_t, 6, 1> {};
class IndexField: public BitField<uint32_t, 7, 32-7> {};
class TypeField: public BitField<PropertyType, 0, 4> {};
class AttributesField: public BitField<PropertyAttributes, 4, 3> {};
class DeletedField: public BitField<uint32_t, 7, 1> {};
class StorageField: public BitField<uint32_t, 8, 32-8> {};
static const int kInitialIndex = 1;
private:
@ -3502,18 +3528,18 @@ class Code: public HeapObject {
static const int kFlagsICStateShift = 0;
static const int kFlagsICInLoopShift = 3;
static const int kFlagsTypeShift = 4;
static const int kFlagsKindShift = 7;
static const int kFlagsICHolderShift = 11;
static const int kFlagsExtraICStateShift = 12;
static const int kFlagsArgumentsCountShift = 14;
static const int kFlagsKindShift = 8;
static const int kFlagsICHolderShift = 12;
static const int kFlagsExtraICStateShift = 13;
static const int kFlagsArgumentsCountShift = 15;
static const int kFlagsICStateMask = 0x00000007; // 00000000111
static const int kFlagsICInLoopMask = 0x00000008; // 00000001000
static const int kFlagsTypeMask = 0x00000070; // 00001110000
static const int kFlagsKindMask = 0x00000780; // 11110000000
static const int kFlagsCacheInPrototypeMapMask = 0x00000800;
static const int kFlagsExtraICStateMask = 0x00003000;
static const int kFlagsArgumentsCountMask = 0xFFFFC000;
static const int kFlagsTypeMask = 0x000000F0; // 00001110000
static const int kFlagsKindMask = 0x00000F00; // 11110000000
static const int kFlagsCacheInPrototypeMapMask = 0x00001000;
static const int kFlagsExtraICStateMask = 0x00006000;
static const int kFlagsArgumentsCountMask = 0xFFFF8000;
static const int kFlagsNotUsedInLookup =
(kFlagsICInLoopMask | kFlagsTypeMask | kFlagsCacheInPrototypeMapMask);
@ -3715,7 +3741,9 @@ class Map: public HeapObject {
// Returns a new map with all transitions dropped from the descriptors and the
// external array elements bit set.
MUST_USE_RESULT inline MaybeObject* NewExternalArrayElementsMap();
MUST_USE_RESULT MaybeObject* GetExternalArrayElementsMap(
ExternalArrayType array_type,
bool safe_to_add_transition);
// Returns the property index for name (only valid for FAST MODE).
int PropertyIndexFor(String* name);

View File

@ -52,6 +52,12 @@ void LookupResult::Print(FILE* out) {
GetTransitionMap()->Print(out);
PrintF(out, "\n");
break;
case EXTERNAL_ARRAY_TRANSITION:
PrintF(out, " -type = external array transition\n");
PrintF(out, " -map:\n");
GetTransitionMap()->Print(out);
PrintF(out, "\n");
break;
case CONSTANT_FUNCTION:
PrintF(out, " -type = constant function\n");
PrintF(out, " -function:\n");

View File

@ -110,6 +110,16 @@ class MapTransitionDescriptor: public Descriptor {
: Descriptor(key, map, attributes, MAP_TRANSITION) { }
};
class ExternalArrayTransitionDescriptor: public Descriptor {
public:
ExternalArrayTransitionDescriptor(String* key,
Map* map,
ExternalArrayType array_type)
: Descriptor(key, map, PropertyDetails(NONE,
EXTERNAL_ARRAY_TRANSITION,
array_type)) { }
};
// Marks a field name in a map so that adding the field is guaranteed
// to create a FIELD descriptor in the new map. Used after adding
// a constant function the first time, creating a CONSTANT_FUNCTION
@ -262,7 +272,8 @@ class LookupResult BASE_EMBEDDED {
Map* GetTransitionMap() {
ASSERT(lookup_type_ == DESCRIPTOR_TYPE);
ASSERT(type() == MAP_TRANSITION || type() == CONSTANT_TRANSITION);
ASSERT(type() == MAP_TRANSITION || type() == CONSTANT_TRANSITION ||
type() == EXTERNAL_ARRAY_TRANSITION);
return Map::cast(GetValue());
}

View File

@ -9308,6 +9308,7 @@ static MaybeObject* DebugLookupResultValue(Heap* heap,
}
case INTERCEPTOR:
case MAP_TRANSITION:
case EXTERNAL_ARRAY_TRANSITION:
case CONSTANT_TRANSITION:
case NULL_DESCRIPTOR:
return heap->undefined_value();

View File

@ -649,7 +649,7 @@ MaybeObject* StubCache::ComputeKeyedLoadOrStoreExternalArray(
}
Object* result;
{ MaybeObject* maybe_result =
receiver->map()->UpdateCodeCache(name, Code::cast(code));
receiver->UpdateMapCodeCache(name, Code::cast(code));
if (!maybe_result->ToObject(&result)) return maybe_result;
}
}

View File

@ -318,14 +318,15 @@ enum InlineCacheHolderFlag {
// Must fit in the BitField PropertyDetails::TypeField.
// A copy of this is in mirror-debugger.js.
enum PropertyType {
NORMAL = 0, // only in slow mode
FIELD = 1, // only in fast mode
CONSTANT_FUNCTION = 2, // only in fast mode
CALLBACKS = 3,
INTERCEPTOR = 4, // only in lookup results, not in descriptors.
MAP_TRANSITION = 5, // only in fast mode
CONSTANT_TRANSITION = 6, // only in fast mode
NULL_DESCRIPTOR = 7, // only in fast mode
NORMAL = 0, // only in slow mode
FIELD = 1, // only in fast mode
CONSTANT_FUNCTION = 2, // only in fast mode
CALLBACKS = 3,
INTERCEPTOR = 4, // only in lookup results, not in descriptors.
MAP_TRANSITION = 5, // only in fast mode
EXTERNAL_ARRAY_TRANSITION = 6,
CONSTANT_TRANSITION = 7, // only in fast mode
NULL_DESCRIPTOR = 8, // only in fast mode
// All properties before MAP_TRANSITION are real.
FIRST_PHANTOM_PROPERTY_TYPE = MAP_TRANSITION,
// There are no IC stubs for NULL_DESCRIPTORS. Therefore,

View File

@ -11598,6 +11598,95 @@ static void ExternalArrayTestHelper(v8::ExternalArrayType array_type,
free(large_array_data);
}
// The "" property descriptor is overloaded to store information about
// the external array. Ensure that setting and accessing the "" property
// works (it should overwrite the information cached about the external
// array in the DescriptorArray) in various situations.
result = CompileRun("ext_array[''] = 23; ext_array['']");
CHECK_EQ(23, result->Int32Value());
// Property "" set after the external array is associated with the object.
{
v8::Handle<v8::Object> obj2 = v8::Object::New();
obj2->Set(v8_str("ee_test_field"), v8::Int32::New(256));
obj2->Set(v8_str(""), v8::Int32::New(1503));
// Set the elements to be the external array.
obj2->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
context->Global()->Set(v8_str("ext_array"), obj2);
result = CompileRun("ext_array['']");
CHECK_EQ(1503, result->Int32Value());
}
// Property "" set after the external array is associated with the object.
{
v8::Handle<v8::Object> obj2 = v8::Object::New();
obj2->Set(v8_str("ee_test_field_2"), v8::Int32::New(256));
// Set the elements to be the external array.
obj2->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
obj2->Set(v8_str(""), v8::Int32::New(1503));
context->Global()->Set(v8_str("ext_array"), obj2);
result = CompileRun("ext_array['']");
CHECK_EQ(1503, result->Int32Value());
}
// Should reuse the map from previous test.
{
v8::Handle<v8::Object> obj2 = v8::Object::New();
obj2->Set(v8_str("ee_test_field_2"), v8::Int32::New(256));
// Set the elements to be the external array. Should re-use the map
// from previous test.
obj2->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
context->Global()->Set(v8_str("ext_array"), obj2);
result = CompileRun("ext_array['']");
}
// Property "" is a constant function that shouldn't not be interfered with
// when an external array is set.
{
v8::Handle<v8::Object> obj2 = v8::Object::New();
// Start
obj2->Set(v8_str("ee_test_field3"), v8::Int32::New(256));
// Add a constant function to an object.
context->Global()->Set(v8_str("ext_array"), obj2);
result = CompileRun("ext_array[''] = function() {return 1503;};"
"ext_array['']();");
// Add an external array transition to the same map that
// has the constant transition.
v8::Handle<v8::Object> obj3 = v8::Object::New();
obj3->Set(v8_str("ee_test_field3"), v8::Int32::New(256));
obj3->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
context->Global()->Set(v8_str("ext_array"), obj3);
}
// If a external array transition is in the map, it should get clobbered
// by a constant function.
{
// Add an external array transition.
v8::Handle<v8::Object> obj3 = v8::Object::New();
obj3->Set(v8_str("ee_test_field4"), v8::Int32::New(256));
obj3->SetIndexedPropertiesToExternalArrayData(array_data,
array_type,
kElementCount);
// Add a constant function to the same map that just got an external array
// transition.
v8::Handle<v8::Object> obj2 = v8::Object::New();
obj2->Set(v8_str("ee_test_field4"), v8::Int32::New(256));
context->Global()->Set(v8_str("ext_array"), obj2);
result = CompileRun("ext_array[''] = function() {return 1503;};"
"ext_array['']();");
}
free(array_data);
}