From: bmeurer@chromium.org Date: Mon, 28 Apr 2014 11:02:12 +0000 (+0000) Subject: Revert "Fix and cleanup Map::GeneralizeRepresentation()." X-Git-Tag: upstream/4.7.83~9390 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=5396d4e994830f6a8ddc0913b53a17c17aad5609;p=platform%2Fupstream%2Fv8.git Revert "Fix and cleanup Map::GeneralizeRepresentation()." This reverts commit r21006 for performance regressions. TBR=svenpanne@chromium.org Review URL: https://codereview.chromium.org/259003002 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@21009 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- diff --git a/src/objects.cc b/src/objects.cc index 21ed836..98e0436 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -2425,6 +2425,43 @@ Map* Map::FindRootMap() { } +// Returns NULL if the updated map is incompatible. +Map* Map::FindUpdatedMap(int verbatim, + int length, + DescriptorArray* descriptors) { + DisallowHeapAllocation no_allocation; + + // This can only be called on roots of transition trees. + ASSERT(GetBackPointer()->IsUndefined()); + + Map* current = this; + + for (int i = verbatim; i < length; i++) { + if (!current->HasTransitionArray()) break; + Name* name = descriptors->GetKey(i); + TransitionArray* transitions = current->transitions(); + int transition = transitions->Search(name); + if (transition == TransitionArray::kNotFound) break; + current = transitions->GetTarget(transition); + PropertyDetails details = descriptors->GetDetails(i); + PropertyDetails target_details = + current->instance_descriptors()->GetDetails(i); + if (details.attributes() != target_details.attributes()) return NULL; + if (details.type() == CALLBACKS) { + if (target_details.type() != CALLBACKS) return NULL; + if (descriptors->GetValue(i) != + current->instance_descriptors()->GetValue(i)) { + return NULL; + } + } else if (target_details.type() == CALLBACKS) { + return NULL; + } + } + + return current; +} + + Map* Map::FindLastMatchMap(int verbatim, int length, DescriptorArray* descriptors) { @@ -2515,10 +2552,13 @@ void Map::GeneralizeFieldType(Handle map, int modify_index, Handle new_field_type) { Isolate* isolate = map->GetIsolate(); + Handle field_owner(map->FindFieldOwner(modify_index), isolate); + Handle descriptors( + field_owner->instance_descriptors(), isolate); // Check if we actually need to generalize the field type at all. Handle old_field_type( - map->instance_descriptors()->GetFieldType(modify_index), isolate); + descriptors->GetFieldType(modify_index), isolate); if (new_field_type->NowIs(old_field_type)) { ASSERT(Map::GeneralizeFieldType(old_field_type, new_field_type, @@ -2526,12 +2566,6 @@ void Map::GeneralizeFieldType(Handle map, return; } - // Determine the field owner. - Handle field_owner(map->FindFieldOwner(modify_index), isolate); - Handle descriptors( - field_owner->instance_descriptors(), isolate); - ASSERT_EQ(*old_field_type, descriptors->GetFieldType(modify_index)); - // Determine the generalized new field type. new_field_type = Map::GeneralizeFieldType( old_field_type, new_field_type, isolate); @@ -2564,28 +2598,23 @@ void Map::GeneralizeFieldType(Handle map, // (partial) version of the type in the transition tree. // To do this, on each rewrite: // - Search the root of the transition tree using FindRootMap. -// - Find |target_map|, the newest matching version of this map using the keys -// in the |old_map|'s descriptor array to walk the transition tree. -// - Merge/generalize the descriptor array of the |old_map| and |target_map|. -// - Generalize the |modify_index| descriptor using |new_representation| and -// |new_field_type|. -// - Walk the tree again starting from the root towards |target_map|. Stop at +// - Find |updated|, the newest matching version of this map using +// FindUpdatedMap. This uses the keys in the own map's descriptor array to +// walk the transition tree. +// - Merge/generalize the descriptor array of the current map and |updated|. +// - Generalize the |modify_index| descriptor using |new_representation|. +// - Walk the tree again starting from the root towards |updated|. Stop at // |split_map|, the first map who's descriptor array does not match the merged // descriptor array. -// - If |target_map| == |split_map|, |target_map| is in the expected state. -// Return it. -// - Otherwise, invalidate the outdated transition target from |target_map|, and +// - If |updated| == |split_map|, |updated| is in the expected state. Return it. +// - Otherwise, invalidate the outdated transition target from |updated|, and // replace its transition tree with a new branch for the updated descriptors. Handle Map::GeneralizeRepresentation(Handle old_map, int modify_index, Representation new_representation, Handle new_field_type, StoreMode store_mode) { - Isolate* isolate = old_map->GetIsolate(); - - Handle old_descriptors( - old_map->instance_descriptors(), isolate); - int old_nof = old_map->NumberOfOwnDescriptors(); + Handle old_descriptors(old_map->instance_descriptors()); PropertyDetails old_details = old_descriptors->GetDetails(modify_index); Representation old_representation = old_details.representation(); @@ -2612,239 +2641,84 @@ Handle Map::GeneralizeRepresentation(Handle old_map, return old_map; } + if (new_representation.Equals(old_representation) && + old_details.type() == FIELD) { + Map::GeneralizeFieldType(old_map, modify_index, new_field_type); + return old_map; + } + + Handle root_map(old_map->FindRootMap()); + // Check the state of the root map. - Handle root_map(old_map->FindRootMap(), isolate); if (!old_map->EquivalentToForTransition(*root_map)) { return CopyGeneralizeAllRepresentations(old_map, modify_index, store_mode, old_details.attributes(), "not equivalent"); } - int root_nof = root_map->NumberOfOwnDescriptors(); - if (modify_index < root_nof) { - PropertyDetails old_details = old_descriptors->GetDetails(modify_index); - if ((old_details.type() != FIELD && store_mode == FORCE_FIELD) || - (old_details.type() == FIELD && - (!new_field_type->NowIs(old_descriptors->GetFieldType(modify_index)) || - !new_representation.fits_into(old_details.representation())))) { - return CopyGeneralizeAllRepresentations(old_map, modify_index, store_mode, - old_details.attributes(), "root modification"); - } - } - Handle target_map = root_map; - for (int i = root_nof; i < old_nof; ++i) { - int j = target_map->SearchTransition(old_descriptors->GetKey(i)); - if (j == TransitionArray::kNotFound) break; - Handle tmp_map(target_map->GetTransition(j), isolate); - Handle tmp_descriptors = handle( - tmp_map->instance_descriptors(), isolate); + int verbatim = root_map->NumberOfOwnDescriptors(); - // Check if target map is incompatible. - PropertyDetails old_details = old_descriptors->GetDetails(i); - PropertyDetails tmp_details = tmp_descriptors->GetDetails(i); - PropertyType old_type = old_details.type(); - PropertyType tmp_type = tmp_details.type(); - if (tmp_details.attributes() != old_details.attributes() || - ((tmp_type == CALLBACKS || old_type == CALLBACKS) && - (tmp_type != old_type || - tmp_descriptors->GetValue(i) != old_descriptors->GetValue(i)))) { - return CopyGeneralizeAllRepresentations( - old_map, modify_index, store_mode, - old_details.attributes(), "incompatible"); - } - Representation old_representation = old_details.representation(); - Representation tmp_representation = tmp_details.representation(); - if (!old_representation.fits_into(tmp_representation) || - (!new_representation.fits_into(tmp_representation) && - modify_index == i)) { - break; - } - if (tmp_type == FIELD) { - // Generalize the field type as necessary. - Handle old_field_type = (old_type == FIELD) - ? handle(old_descriptors->GetFieldType(i), isolate) - : old_descriptors->GetValue(i)->OptimalType( - isolate, tmp_representation); - if (modify_index == i) { - old_field_type = GeneralizeFieldType( - new_field_type, old_field_type, isolate); - } - GeneralizeFieldType(tmp_map, i, old_field_type); - } else if (tmp_type == CONSTANT) { - if (old_type != CONSTANT || - old_descriptors->GetConstant(i) != tmp_descriptors->GetConstant(i)) { - break; - } - } else { - ASSERT_EQ(tmp_type, old_type); - ASSERT_EQ(tmp_descriptors->GetValue(i), old_descriptors->GetValue(i)); - } - target_map = tmp_map; - } - - // Directly change the map if the target map is more general. - Handle target_descriptors( - target_map->instance_descriptors(), isolate); - int target_nof = target_map->NumberOfOwnDescriptors(); - if (target_nof == old_nof && - (store_mode != FORCE_FIELD || - target_descriptors->GetDetails(modify_index).type() == FIELD)) { - ASSERT(modify_index < target_nof); - ASSERT(new_representation.fits_into( - target_descriptors->GetDetails(modify_index).representation())); - ASSERT(target_descriptors->GetDetails(modify_index).type() != FIELD || - new_field_type->NowIs( - target_descriptors->GetFieldType(modify_index))); - return target_map; - } - - // Find the last compatible target map in the transition tree. - for (int i = target_nof; i < old_nof; ++i) { - int j = target_map->SearchTransition(old_descriptors->GetKey(i)); - if (j == TransitionArray::kNotFound) break; - Handle tmp_map(target_map->GetTransition(j), isolate); - Handle tmp_descriptors( - tmp_map->instance_descriptors(), isolate); - - // Check if target map is compatible. - PropertyDetails old_details = old_descriptors->GetDetails(i); - PropertyDetails tmp_details = tmp_descriptors->GetDetails(i); - if (tmp_details.attributes() != old_details.attributes() || - ((tmp_details.type() == CALLBACKS || old_details.type() == CALLBACKS) && - (tmp_details.type() != old_details.type() || - tmp_descriptors->GetValue(i) != old_descriptors->GetValue(i)))) { - return CopyGeneralizeAllRepresentations( - old_map, modify_index, store_mode, - old_details.attributes(), "incompatible"); - } - target_map = tmp_map; + if (store_mode != ALLOW_AS_CONSTANT && modify_index < verbatim) { + return CopyGeneralizeAllRepresentations(old_map, modify_index, store_mode, + old_details.attributes(), "root modification"); } - target_nof = target_map->NumberOfOwnDescriptors(); - target_descriptors = handle(target_map->instance_descriptors(), isolate); - // Allocate a new descriptor array large enough to hold the required - // descriptors, with minimally the exact same size as the old descriptor - // array. - int new_slack = Max( - old_nof, old_descriptors->number_of_descriptors()) - old_nof; - Handle new_descriptors = DescriptorArray::Allocate( - isolate, old_nof, new_slack); - ASSERT(new_descriptors->length() > target_descriptors->length() || - new_descriptors->NumberOfSlackDescriptors() > 0 || - new_descriptors->number_of_descriptors() == - old_descriptors->number_of_descriptors()); - ASSERT(new_descriptors->number_of_descriptors() == old_nof); - - // 0 -> |root_nof| - int current_offset = 0; - for (int i = 0; i < root_nof; ++i) { - PropertyDetails old_details = old_descriptors->GetDetails(i); - if (old_details.type() == FIELD) current_offset++; - Descriptor d(handle(old_descriptors->GetKey(i), isolate), - handle(old_descriptors->GetValue(i), isolate), - old_details); - new_descriptors->Set(i, &d); + int descriptors = old_map->NumberOfOwnDescriptors(); + Map* raw_updated = root_map->FindUpdatedMap( + verbatim, descriptors, *old_descriptors); + if (raw_updated == NULL) { + return CopyGeneralizeAllRepresentations(old_map, modify_index, store_mode, + old_details.attributes(), "incompatible"); } - // |root_nof| -> |target_nof| - for (int i = root_nof; i < target_nof; ++i) { - Handle target_key(target_descriptors->GetKey(i), isolate); - PropertyDetails old_details = old_descriptors->GetDetails(i); - PropertyDetails target_details = target_descriptors->GetDetails(i); - target_details = target_details.CopyWithRepresentation( - old_details.representation().generalize( - target_details.representation())); - if (modify_index == i) { - target_details = target_details.CopyWithRepresentation( - new_representation.generalize(target_details.representation())); - } - if (old_details.type() == FIELD || - target_details.type() == FIELD || - (modify_index == i && store_mode == FORCE_FIELD) || - (target_descriptors->GetValue(i) != old_descriptors->GetValue(i))) { - Handle old_field_type = (old_details.type() == FIELD) - ? handle(old_descriptors->GetFieldType(i), isolate) - : old_descriptors->GetValue(i)->OptimalType( - isolate, target_details.representation()); - Handle target_field_type = (target_details.type() == FIELD) - ? handle(target_descriptors->GetFieldType(i), isolate) - : target_descriptors->GetValue(i)->OptimalType( - isolate, target_details.representation()); - target_field_type = GeneralizeFieldType( - target_field_type, old_field_type, isolate); - if (modify_index == i) { - target_field_type = GeneralizeFieldType( - target_field_type, new_field_type, isolate); - } - FieldDescriptor d(target_key, - current_offset++, - target_field_type, - target_details.attributes(), - target_details.representation()); - new_descriptors->Set(i, &d); - } else { - ASSERT_NE(FIELD, target_details.type()); - Descriptor d(target_key, - handle(target_descriptors->GetValue(i), isolate), - target_details); - new_descriptors->Set(i, &d); - } - } + Handle updated(raw_updated); + Handle updated_descriptors(updated->instance_descriptors()); - // |target_nof| -> |old_nof| - for (int i = target_nof; i < old_nof; ++i) { - PropertyDetails old_details = old_descriptors->GetDetails(i); - Handle old_key(old_descriptors->GetKey(i), isolate); - if (modify_index == i) { - old_details = old_details.CopyWithRepresentation( - new_representation.generalize(old_details.representation())); - } - if (old_details.type() == FIELD) { - Handle old_field_type( - old_descriptors->GetFieldType(i), isolate); - if (modify_index == i) { - old_field_type = GeneralizeFieldType( - old_field_type, new_field_type, isolate); - } - FieldDescriptor d(old_key, - current_offset++, - old_field_type, - old_details.attributes(), - old_details.representation()); - new_descriptors->Set(i, &d); - } else { - ASSERT(old_details.type() == CONSTANT || old_details.type() == CALLBACKS); - if (modify_index == i && store_mode == FORCE_FIELD) { - FieldDescriptor d(old_key, - current_offset++, - GeneralizeFieldType( - old_descriptors->GetValue(i)->OptimalType( - isolate, old_details.representation()), - new_field_type, isolate), - old_details.attributes(), - old_details.representation()); - new_descriptors->Set(i, &d); - } else { - ASSERT_NE(FIELD, old_details.type()); - Descriptor d(old_key, - handle(old_descriptors->GetValue(i), isolate), - old_details); - new_descriptors->Set(i, &d); - } - } - } + int valid = updated->NumberOfOwnDescriptors(); - new_descriptors->Sort(); + // Directly change the map if the target map is more general. Ensure that the + // target type of the modify_index is a FIELD, unless we are migrating. + if (updated_descriptors->IsMoreGeneralThan( + verbatim, valid, descriptors, *old_descriptors) && + (store_mode == ALLOW_AS_CONSTANT || + updated_descriptors->GetDetails(modify_index).type() == FIELD)) { + Representation updated_representation = + updated_descriptors->GetDetails(modify_index).representation(); + if (new_representation.fits_into(updated_representation)) return updated; + } - ASSERT(store_mode != FORCE_FIELD || + Handle new_descriptors = DescriptorArray::Merge( + updated, verbatim, valid, descriptors, modify_index, + store_mode, old_map); + ASSERT(store_mode == ALLOW_AS_CONSTANT || new_descriptors->GetDetails(modify_index).type() == FIELD); + Isolate* isolate = new_descriptors->GetIsolate(); + old_representation = + new_descriptors->GetDetails(modify_index).representation(); + Representation updated_representation = + new_representation.generalize(old_representation); + if (!updated_representation.Equals(old_representation)) { + new_descriptors->SetRepresentation(modify_index, updated_representation); + } + if (new_descriptors->GetDetails(modify_index).type() == FIELD) { + Handle field_type( + new_descriptors->GetFieldType(modify_index), isolate); + new_field_type = Map::GeneralizeFieldType( + field_type, new_field_type, isolate); + new_descriptors->SetValue(modify_index, *new_field_type); + } + Handle split_map(root_map->FindLastMatchMap( - root_nof, old_nof, *new_descriptors), isolate); - int split_nof = split_map->NumberOfOwnDescriptors(); - ASSERT_NE(old_nof, split_nof); + verbatim, descriptors, *new_descriptors)); + int split_descriptors = split_map->NumberOfOwnDescriptors(); + // This is shadowed by |updated_descriptors| being more general than + // |old_descriptors|. + ASSERT(descriptors != split_descriptors); + + int descriptor = split_descriptors; split_map->DeprecateTarget( - old_descriptors->GetKey(split_nof), *new_descriptors); + old_descriptors->GetKey(descriptor), *new_descriptors); if (FLAG_trace_generalization) { PropertyDetails old_details = old_descriptors->GetDetails(modify_index); @@ -2858,7 +2732,7 @@ Handle Map::GeneralizeRepresentation(Handle old_map, : HeapType::Constant(handle(new_descriptors->GetValue(modify_index), isolate), isolate); old_map->PrintGeneralization( - stdout, "", modify_index, split_nof, old_nof, + stdout, "", modify_index, descriptor, descriptors, old_details.type() == CONSTANT && store_mode == FORCE_FIELD, old_details.representation(), new_details.representation(), *old_field_type, *new_field_type); @@ -2866,9 +2740,10 @@ Handle Map::GeneralizeRepresentation(Handle old_map, // Add missing transitions. Handle new_map = split_map; - for (int i = split_nof; i < old_nof; ++i) { - new_map = CopyInstallDescriptors(new_map, i, new_descriptors); + for (; descriptor < descriptors; descriptor++) { + new_map = CopyInstallDescriptors(new_map, descriptor, new_descriptors); } + new_map->set_owns_descriptors(true); return new_map; } @@ -8522,6 +8397,150 @@ void DescriptorArray::CopyFrom(int index, } +// Creates a new descriptor array by merging the descriptor array of |right_map| +// into the (at least partly) updated descriptor array of |left_map|. +// The method merges two descriptor array in three parts. Both descriptor arrays +// are identical up to |verbatim|. They also overlap in keys up to |valid|. +// Between |verbatim| and |valid|, the resulting descriptor type as well as the +// representation are generalized from both |left_map| and |right_map|. Beyond +// |valid|, the descriptors are copied verbatim from |right_map| up to +// |new_size|. +// In case of incompatible types, the type and representation of |right_map| is +// used. +Handle DescriptorArray::Merge(Handle left_map, + int verbatim, + int valid, + int new_size, + int modify_index, + StoreMode store_mode, + Handle right_map) { + ASSERT(verbatim <= valid); + ASSERT(valid <= new_size); + + // Allocate a new descriptor array large enough to hold the required + // descriptors, with minimally the exact same size as this descriptor array. + Isolate* isolate = left_map->GetIsolate(); + Handle left(left_map->instance_descriptors()); + Handle right(right_map->instance_descriptors()); + Handle result = DescriptorArray::Allocate( + isolate, + new_size, + Max(new_size, right->number_of_descriptors()) - new_size); + ASSERT(result->length() > left->length() || + result->NumberOfSlackDescriptors() > 0 || + result->number_of_descriptors() == right->number_of_descriptors()); + ASSERT(result->number_of_descriptors() == new_size); + + int descriptor; + + // 0 -> |verbatim| + int current_offset = 0; + for (descriptor = 0; descriptor < verbatim; descriptor++) { + if (left->GetDetails(descriptor).type() == FIELD) current_offset++; + Descriptor d(handle(right->GetKey(descriptor)), + handle(right->GetValue(descriptor), right->GetIsolate()), + right->GetDetails(descriptor)); + result->Set(descriptor, &d); + } + + // |verbatim| -> |valid| + for (; descriptor < valid; descriptor++) { + PropertyDetails left_details = left->GetDetails(descriptor); + PropertyDetails right_details = right->GetDetails(descriptor); + if (left_details.type() == FIELD || right_details.type() == FIELD || + (store_mode == FORCE_FIELD && descriptor == modify_index) || + (left_details.type() == CONSTANT && + right_details.type() == CONSTANT && + left->GetValue(descriptor) != right->GetValue(descriptor))) { + ASSERT(left_details.type() == CONSTANT || left_details.type() == FIELD); + ASSERT(right_details.type() == CONSTANT || right_details.type() == FIELD); + Representation representation = left_details.representation().generalize( + right_details.representation()); + Handle left_type = (left_details.type() == FIELD) + ? handle(left->GetFieldType(descriptor), isolate) + : left->GetValue(descriptor)->OptimalType(isolate, representation); + Handle right_type = (right_details.type() == FIELD) + ? handle(right->GetFieldType(descriptor), isolate) + : right->GetValue(descriptor)->OptimalType(isolate, representation); + Handle field_type = Map::GeneralizeFieldType( + left_type, right_type, isolate); + FieldDescriptor d(handle(left->GetKey(descriptor), isolate), + current_offset++, + field_type, + right_details.attributes(), + representation); + result->Set(descriptor, &d); + } else { + Descriptor d(handle(right->GetKey(descriptor), isolate), + handle(right->GetValue(descriptor), isolate), + right_details); + result->Set(descriptor, &d); + } + } + + // |valid| -> |new_size| + for (; descriptor < new_size; descriptor++) { + PropertyDetails right_details = right->GetDetails(descriptor); + if (right_details.type() == FIELD) { + FieldDescriptor d(handle(right->GetKey(descriptor), isolate), + current_offset++, + handle(right->GetFieldType(descriptor), isolate), + right_details.attributes(), + right_details.representation()); + result->Set(descriptor, &d); + } else if (store_mode == FORCE_FIELD && descriptor == modify_index) { + ASSERT_EQ(CONSTANT, right_details.type()); + Representation field_representation = right_details.representation(); + Handle field_type = right->GetValue(descriptor)->OptimalType( + isolate, field_representation); + FieldDescriptor d(handle(right->GetKey(descriptor), isolate), + current_offset++, + field_type, + right_details.attributes(), + field_representation); + result->Set(descriptor, &d); + } else { + Descriptor d(handle(right->GetKey(descriptor), isolate), + handle(right->GetValue(descriptor), isolate), + right_details); + result->Set(descriptor, &d); + } + } + + result->Sort(); + return result; +} + + +// Checks whether a merge of |other| into |this| would return a copy of |this|. +bool DescriptorArray::IsMoreGeneralThan(int verbatim, + int valid, + int new_size, + DescriptorArray* other) { + ASSERT(verbatim <= valid); + ASSERT(valid <= new_size); + if (valid != new_size) return false; + + for (int descriptor = verbatim; descriptor < valid; descriptor++) { + PropertyDetails details = GetDetails(descriptor); + PropertyDetails other_details = other->GetDetails(descriptor); + if (!other_details.representation().fits_into(details.representation())) { + return false; + } + if (details.type() == CONSTANT) { + if (other_details.type() != CONSTANT) return false; + if (GetValue(descriptor) != other->GetValue(descriptor)) return false; + } else if (details.type() == FIELD && other_details.type() == FIELD) { + if (!other->GetFieldType(descriptor)->NowIs(GetFieldType(descriptor))) { + return false; + } + } + } + + return true; +} + + // We need the whiteness witness since sort will reshuffle the entries in the // descriptor array. If the descriptor array were to be black, the shuffling // would move a slot that was already recorded as pointing into an evacuation diff --git a/src/objects.h b/src/objects.h index 5afb690..5d96c2f 100644 --- a/src/objects.h +++ b/src/objects.h @@ -3441,6 +3441,20 @@ class DescriptorArray: public FixedArray { // array. inline void Append(Descriptor* desc); + static Handle Merge(Handle left_map, + int verbatim, + int valid, + int new_size, + int modify_index, + StoreMode store_mode, + Handle right_map) + V8_WARN_UNUSED_RESULT; + + bool IsMoreGeneralThan(int verbatim, + int valid, + int new_size, + DescriptorArray* other); + static Handle CopyUpTo(Handle desc, int enumeration_index, int slack = 0); @@ -6719,6 +6733,7 @@ class Map: public HeapObject { void DeprecateTransitionTree(); void DeprecateTarget(Name* key, DescriptorArray* new_descriptors); + Map* FindUpdatedMap(int verbatim, int length, DescriptorArray* descriptors); Map* FindLastMatchMap(int verbatim, int length, DescriptorArray* descriptors); void UpdateDescriptor(int descriptor_number, Descriptor* desc); diff --git a/src/property.h b/src/property.h index 48435cc..c82fb82 100644 --- a/src/property.h +++ b/src/property.h @@ -66,7 +66,6 @@ class Descriptor BASE_EMBEDDED { details_(attributes, type, representation, field_index) { } friend class DescriptorArray; - friend class Map; };