|
@@ -0,0 +1,819 @@
|
|
|
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
|
+From: "[email protected]" <[email protected]>
|
|
|
+Date: Tue, 27 Oct 2020 13:13:08 +0100
|
|
|
+Subject: Merged: [runtime] Fix sorted order of DescriptorArray entries
|
|
|
+
|
|
|
+Revision: 518d67ad652fc24b7eb03e48bb342f952d4ccf74
|
|
|
+
|
|
|
+BUG=chromium:1133527
|
|
|
+NOTRY=true
|
|
|
+NOPRESUBMIT=true
|
|
|
+NOTREECHECKS=true
|
|
|
[email protected]
|
|
|
+
|
|
|
+Change-Id: I10831b27c5c10b9a967e47a5fd08f806ef5d306d
|
|
|
+Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2502328
|
|
|
+Reviewed-by: Toon Verwaest <[email protected]>
|
|
|
+Cr-Commit-Position: refs/branch-heads/8.6@{#34}
|
|
|
+Cr-Branched-From: a64aed2333abf49e494d2a5ce24bbd14fff19f60-refs/heads/8.6.395@{#1}
|
|
|
+Cr-Branched-From: a626bc036236c9bf92ac7b87dc40c9e538b087e3-refs/heads/master@{#69472}
|
|
|
+
|
|
|
+diff --git a/src/codegen/code-stub-assembler.cc b/src/codegen/code-stub-assembler.cc
|
|
|
+index 6e9b817759dbccd314fec13a911fee40d0b92b80..da0cb08023d1cade3fcb6ef0fb0f5ecaacd125ba 100644
|
|
|
+--- a/src/codegen/code-stub-assembler.cc
|
|
|
++++ b/src/codegen/code-stub-assembler.cc
|
|
|
+@@ -1772,12 +1772,13 @@ TNode<IntPtrT> CodeStubAssembler::LoadJSReceiverIdentityHash(
|
|
|
+ return var_hash.value();
|
|
|
+ }
|
|
|
+
|
|
|
+-TNode<Uint32T> CodeStubAssembler::LoadNameHashField(SloppyTNode<Name> name) {
|
|
|
+- CSA_ASSERT(this, IsName(name));
|
|
|
+- return LoadObjectField<Uint32T>(name, Name::kHashFieldOffset);
|
|
|
++TNode<Uint32T> CodeStubAssembler::LoadNameHashAssumeComputed(TNode<Name> name) {
|
|
|
++ TNode<Uint32T> hash_field = LoadNameHashField(name);
|
|
|
++ CSA_ASSERT(this, IsClearWord32(hash_field, Name::kHashNotComputedMask));
|
|
|
++ return Unsigned(Word32Shr(hash_field, Int32Constant(Name::kHashShift)));
|
|
|
+ }
|
|
|
+
|
|
|
+-TNode<Uint32T> CodeStubAssembler::LoadNameHash(SloppyTNode<Name> name,
|
|
|
++TNode<Uint32T> CodeStubAssembler::LoadNameHash(TNode<Name> name,
|
|
|
+ Label* if_hash_not_computed) {
|
|
|
+ TNode<Uint32T> hash_field = LoadNameHashField(name);
|
|
|
+ if (if_hash_not_computed != nullptr) {
|
|
|
+@@ -8026,7 +8027,7 @@ void CodeStubAssembler::LookupBinary(TNode<Name> unique_name,
|
|
|
+ TNode<Uint32T> limit =
|
|
|
+ Unsigned(Int32Sub(NumberOfEntries<Array>(array), Int32Constant(1)));
|
|
|
+ TVARIABLE(Uint32T, var_high, limit);
|
|
|
+- TNode<Uint32T> hash = LoadNameHashField(unique_name);
|
|
|
++ TNode<Uint32T> hash = LoadNameHashAssumeComputed(unique_name);
|
|
|
+ CSA_ASSERT(this, Word32NotEqual(hash, Int32Constant(0)));
|
|
|
+
|
|
|
+ // Assume non-empty array.
|
|
|
+@@ -8044,7 +8045,7 @@ void CodeStubAssembler::LookupBinary(TNode<Name> unique_name,
|
|
|
+ TNode<Uint32T> sorted_key_index = GetSortedKeyIndex<Array>(array, mid);
|
|
|
+ TNode<Name> mid_name = GetKey<Array>(array, sorted_key_index);
|
|
|
+
|
|
|
+- TNode<Uint32T> mid_hash = LoadNameHashField(mid_name);
|
|
|
++ TNode<Uint32T> mid_hash = LoadNameHashAssumeComputed(mid_name);
|
|
|
+
|
|
|
+ Label mid_greater(this), mid_less(this), merge(this);
|
|
|
+ Branch(Uint32GreaterThanOrEqual(mid_hash, hash), &mid_greater, &mid_less);
|
|
|
+@@ -8071,7 +8072,7 @@ void CodeStubAssembler::LookupBinary(TNode<Name> unique_name,
|
|
|
+ TNode<Uint32T> sort_index =
|
|
|
+ GetSortedKeyIndex<Array>(array, var_low.value());
|
|
|
+ TNode<Name> current_name = GetKey<Array>(array, sort_index);
|
|
|
+- TNode<Uint32T> current_hash = LoadNameHashField(current_name);
|
|
|
++ TNode<Uint32T> current_hash = LoadNameHashAssumeComputed(current_name);
|
|
|
+ GotoIf(Word32NotEqual(current_hash, hash), if_not_found);
|
|
|
+ Label next(this);
|
|
|
+ GotoIf(TaggedNotEqual(current_name, unique_name), &next);
|
|
|
+diff --git a/src/codegen/code-stub-assembler.h b/src/codegen/code-stub-assembler.h
|
|
|
+index a13699939942dd3260e98dff678566be2c8b4346..99a30c9ad95d4e36e25004f93472cf73130e9cdc 100644
|
|
|
+--- a/src/codegen/code-stub-assembler.h
|
|
|
++++ b/src/codegen/code-stub-assembler.h
|
|
|
+@@ -1322,13 +1322,12 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
|
|
|
+ // Check if the map is set for slow properties.
|
|
|
+ TNode<BoolT> IsDictionaryMap(SloppyTNode<Map> map);
|
|
|
+
|
|
|
+- // Load the hash field of a name as an uint32 value.
|
|
|
+- TNode<Uint32T> LoadNameHashField(SloppyTNode<Name> name);
|
|
|
+- // Load the hash value of a name as an uint32 value.
|
|
|
++ // Load the Name::hash() value of a name as an uint32 value.
|
|
|
+ // If {if_hash_not_computed} label is specified then it also checks if
|
|
|
+ // hash is actually computed.
|
|
|
+- TNode<Uint32T> LoadNameHash(SloppyTNode<Name> name,
|
|
|
++ TNode<Uint32T> LoadNameHash(TNode<Name> name,
|
|
|
+ Label* if_hash_not_computed = nullptr);
|
|
|
++ TNode<Uint32T> LoadNameHashAssumeComputed(TNode<Name> name);
|
|
|
+
|
|
|
+ // Load length field of a String object as Smi value.
|
|
|
+ TNode<Smi> LoadStringLengthAsSmi(TNode<String> string);
|
|
|
+diff --git a/src/diagnostics/objects-debug.cc b/src/diagnostics/objects-debug.cc
|
|
|
+index f94dd8a3c6a148ee53d49eae668ca7e3b85e41ff..185cf31d8cfda1207d0963904fb719f19c1e95c8 100644
|
|
|
+--- a/src/diagnostics/objects-debug.cc
|
|
|
++++ b/src/diagnostics/objects-debug.cc
|
|
|
+@@ -1662,12 +1662,13 @@ bool DescriptorArray::IsSortedNoDuplicates() {
|
|
|
+ uint32_t current = 0;
|
|
|
+ for (int i = 0; i < number_of_descriptors(); i++) {
|
|
|
+ Name key = GetSortedKey(i);
|
|
|
++ CHECK(key.HasHashCode());
|
|
|
+ if (key == current_key) {
|
|
|
+ Print();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ current_key = key;
|
|
|
+- uint32_t hash = GetSortedKey(i).Hash();
|
|
|
++ uint32_t hash = key.hash();
|
|
|
+ if (hash < current) {
|
|
|
+ Print();
|
|
|
+ return false;
|
|
|
+@@ -1685,7 +1686,8 @@ bool TransitionArray::IsSortedNoDuplicates() {
|
|
|
+
|
|
|
+ for (int i = 0; i < number_of_transitions(); i++) {
|
|
|
+ Name key = GetSortedKey(i);
|
|
|
+- uint32_t hash = key.Hash();
|
|
|
++ CHECK(key.HasHashCode());
|
|
|
++ uint32_t hash = key.hash();
|
|
|
+ PropertyKind kind = kData;
|
|
|
+ PropertyAttributes attributes = NONE;
|
|
|
+ if (!TransitionsAccessor::IsSpecialTransition(key.GetReadOnlyRoots(),
|
|
|
+diff --git a/src/objects/descriptor-array-inl.h b/src/objects/descriptor-array-inl.h
|
|
|
+index d9e3408dd96aed4f798176a38ae4f0eade1c6c89..a7c6443a05fa7debc1841fb259ff1bc98ba7e184 100644
|
|
|
+--- a/src/objects/descriptor-array-inl.h
|
|
|
++++ b/src/objects/descriptor-array-inl.h
|
|
|
+@@ -228,7 +228,7 @@ void DescriptorArray::Append(Descriptor* desc) {
|
|
|
+
|
|
|
+ for (insertion = descriptor_number; insertion > 0; --insertion) {
|
|
|
+ Name key = GetSortedKey(insertion - 1);
|
|
|
+- if (key.Hash() <= hash) break;
|
|
|
++ if (key.hash() <= hash) break;
|
|
|
+ SetSortedKey(insertion, GetSortedKeyIndex(insertion - 1));
|
|
|
+ }
|
|
|
+
|
|
|
+diff --git a/src/objects/descriptor-array.h b/src/objects/descriptor-array.h
|
|
|
+index f68948192900d901743548943e1df82567ade2ba..890863d5a01268756d5aa94037925120652cb1bd 100644
|
|
|
+--- a/src/objects/descriptor-array.h
|
|
|
++++ b/src/objects/descriptor-array.h
|
|
|
+@@ -113,7 +113,7 @@ class DescriptorArray
|
|
|
+ int slack = 0);
|
|
|
+
|
|
|
+ // Sort the instance descriptors by the hash codes of their keys.
|
|
|
+- void Sort();
|
|
|
++ V8_EXPORT_PRIVATE void Sort();
|
|
|
+
|
|
|
+ // Search the instance descriptors for given name. {concurrent_search} signals
|
|
|
+ // if we are doing the search on a background thread. If so, we will sacrifice
|
|
|
+diff --git a/src/objects/fixed-array-inl.h b/src/objects/fixed-array-inl.h
|
|
|
+index a49483ebc6490dafdf13301dd991ffc301037476..adde6c7c1f7958643c46aedf8be33300d36f6306 100644
|
|
|
+--- a/src/objects/fixed-array-inl.h
|
|
|
++++ b/src/objects/fixed-array-inl.h
|
|
|
+@@ -217,7 +217,7 @@ int BinarySearch(T* array, Name name, int valid_entries,
|
|
|
+ // index). After doing the binary search and getting the correct internal
|
|
|
+ // index we check to have the index lower than valid_entries, if needed.
|
|
|
+ int high = array->number_of_entries() - 1;
|
|
|
+- uint32_t hash = name.hash_field();
|
|
|
++ uint32_t hash = name.hash();
|
|
|
+ int limit = high;
|
|
|
+
|
|
|
+ DCHECK(low <= high);
|
|
|
+@@ -225,7 +225,7 @@ int BinarySearch(T* array, Name name, int valid_entries,
|
|
|
+ while (low != high) {
|
|
|
+ int mid = low + (high - low) / 2;
|
|
|
+ Name mid_name = array->GetSortedKey(mid);
|
|
|
+- uint32_t mid_hash = mid_name.hash_field();
|
|
|
++ uint32_t mid_hash = mid_name.hash();
|
|
|
+
|
|
|
+ if (mid_hash >= hash) {
|
|
|
+ high = mid;
|
|
|
+@@ -237,7 +237,7 @@ int BinarySearch(T* array, Name name, int valid_entries,
|
|
|
+ for (; low <= limit; ++low) {
|
|
|
+ int sort_index = array->GetSortedKeyIndex(low);
|
|
|
+ Name entry = array->GetKey(InternalIndex(sort_index));
|
|
|
+- uint32_t current_hash = entry.hash_field();
|
|
|
++ uint32_t current_hash = entry.hash();
|
|
|
+ if (current_hash != hash) {
|
|
|
+ // 'search_mode == ALL_ENTRIES' here and below is not needed since
|
|
|
+ // 'out_insertion_index != nullptr' implies 'search_mode == ALL_ENTRIES'.
|
|
|
+@@ -269,12 +269,12 @@ template <SearchMode search_mode, typename T>
|
|
|
+ int LinearSearch(T* array, Name name, int valid_entries,
|
|
|
+ int* out_insertion_index) {
|
|
|
+ if (search_mode == ALL_ENTRIES && out_insertion_index != nullptr) {
|
|
|
+- uint32_t hash = name.hash_field();
|
|
|
++ uint32_t hash = name.hash();
|
|
|
+ int len = array->number_of_entries();
|
|
|
+ for (int number = 0; number < len; number++) {
|
|
|
+ int sorted_index = array->GetSortedKeyIndex(number);
|
|
|
+ Name entry = array->GetKey(InternalIndex(sorted_index));
|
|
|
+- uint32_t current_hash = entry.hash_field();
|
|
|
++ uint32_t current_hash = entry.hash();
|
|
|
+ if (current_hash > hash) {
|
|
|
+ *out_insertion_index = sorted_index;
|
|
|
+ return T::kNotFound;
|
|
|
+diff --git a/src/objects/name-inl.h b/src/objects/name-inl.h
|
|
|
+index 0735b4e506fbcb453d4e1a5a832225ca83ca1b96..ffcd287fd37454e3449b5064b4aa37196dadfdbd 100644
|
|
|
+--- a/src/objects/name-inl.h
|
|
|
++++ b/src/objects/name-inl.h
|
|
|
+@@ -94,6 +94,12 @@ uint32_t Name::Hash() {
|
|
|
+ return String::cast(*this).ComputeAndSetHash();
|
|
|
+ }
|
|
|
+
|
|
|
++uint32_t Name::hash() const {
|
|
|
++ uint32_t field = hash_field();
|
|
|
++ DCHECK(IsHashFieldComputed(field));
|
|
|
++ return field >> kHashShift;
|
|
|
++}
|
|
|
++
|
|
|
+ DEF_GETTER(Name, IsInterestingSymbol, bool) {
|
|
|
+ return IsSymbol(isolate) && Symbol::cast(*this).is_interesting_symbol();
|
|
|
+ }
|
|
|
+diff --git a/src/objects/name.h b/src/objects/name.h
|
|
|
+index 533d8b000ebfa0ef714b61a021de7fc3d0d456f8..6309de9d4ca4502d30c482455103dd16645e6401 100644
|
|
|
+--- a/src/objects/name.h
|
|
|
++++ b/src/objects/name.h
|
|
|
+@@ -23,9 +23,15 @@ class Name : public TorqueGeneratedName<Name, PrimitiveHeapObject> {
|
|
|
+ // Tells whether the hash code has been computed.
|
|
|
+ inline bool HasHashCode();
|
|
|
+
|
|
|
+- // Returns a hash value used for the property table
|
|
|
++ // Returns a hash value used for the property table. Ensures that the hash
|
|
|
++ // value is computed.
|
|
|
++ // TODO(ishell): rename to EnsureHash().
|
|
|
+ inline uint32_t Hash();
|
|
|
+
|
|
|
++ // Returns a hash value used for the property table (same as Hash()), assumes
|
|
|
++ // the hash is already computed.
|
|
|
++ inline uint32_t hash() const;
|
|
|
++
|
|
|
+ // Equality operations.
|
|
|
+ inline bool Equals(Name other);
|
|
|
+ inline static bool Equals(Isolate* isolate, Handle<Name> one,
|
|
|
+diff --git a/src/objects/objects.cc b/src/objects/objects.cc
|
|
|
+index 5e0c831f73b6ccbe40c556497f5ba6fbc9f42b48..54b19d6cdc5ff1f7bd73605a09681c199892b9eb 100644
|
|
|
+--- a/src/objects/objects.cc
|
|
|
++++ b/src/objects/objects.cc
|
|
|
+@@ -4357,16 +4357,16 @@ void DescriptorArray::Sort() {
|
|
|
+ // Reset sorting since the descriptor array might contain invalid pointers.
|
|
|
+ for (int i = 0; i < len; ++i) SetSortedKey(i, i);
|
|
|
+ // Bottom-up max-heap construction.
|
|
|
+- // Index of the last node with children
|
|
|
++ // Index of the last node with children.
|
|
|
+ const int max_parent_index = (len / 2) - 1;
|
|
|
+ for (int i = max_parent_index; i >= 0; --i) {
|
|
|
+ int parent_index = i;
|
|
|
+- const uint32_t parent_hash = GetSortedKey(i).Hash();
|
|
|
++ const uint32_t parent_hash = GetSortedKey(i).hash();
|
|
|
+ while (parent_index <= max_parent_index) {
|
|
|
+ int child_index = 2 * parent_index + 1;
|
|
|
+- uint32_t child_hash = GetSortedKey(child_index).Hash();
|
|
|
++ uint32_t child_hash = GetSortedKey(child_index).hash();
|
|
|
+ if (child_index + 1 < len) {
|
|
|
+- uint32_t right_child_hash = GetSortedKey(child_index + 1).Hash();
|
|
|
++ uint32_t right_child_hash = GetSortedKey(child_index + 1).hash();
|
|
|
+ if (right_child_hash > child_hash) {
|
|
|
+ child_index++;
|
|
|
+ child_hash = right_child_hash;
|
|
|
+@@ -4385,13 +4385,13 @@ void DescriptorArray::Sort() {
|
|
|
+ SwapSortedKeys(0, i);
|
|
|
+ // Shift down the new top element.
|
|
|
+ int parent_index = 0;
|
|
|
+- const uint32_t parent_hash = GetSortedKey(parent_index).Hash();
|
|
|
++ const uint32_t parent_hash = GetSortedKey(parent_index).hash();
|
|
|
+ const int max_parent_index = (i / 2) - 1;
|
|
|
+ while (parent_index <= max_parent_index) {
|
|
|
+ int child_index = parent_index * 2 + 1;
|
|
|
+- uint32_t child_hash = GetSortedKey(child_index).Hash();
|
|
|
++ uint32_t child_hash = GetSortedKey(child_index).hash();
|
|
|
+ if (child_index + 1 < i) {
|
|
|
+- uint32_t right_child_hash = GetSortedKey(child_index + 1).Hash();
|
|
|
++ uint32_t right_child_hash = GetSortedKey(child_index + 1).hash();
|
|
|
+ if (right_child_hash > child_hash) {
|
|
|
+ child_index++;
|
|
|
+ child_hash = right_child_hash;
|
|
|
+diff --git a/src/objects/transitions-inl.h b/src/objects/transitions-inl.h
|
|
|
+index 5694d66d948325bb139b67a3a34c22759224d139..09157b7f5d0051b80e60de51f358d4fd5bde0b99 100644
|
|
|
+--- a/src/objects/transitions-inl.h
|
|
|
++++ b/src/objects/transitions-inl.h
|
|
|
+@@ -169,12 +169,20 @@ int TransitionArray::SearchNameForTesting(Name name, int* out_insertion_index) {
|
|
|
+ return SearchName(name, out_insertion_index);
|
|
|
+ }
|
|
|
+
|
|
|
++Map TransitionArray::SearchAndGetTargetForTesting(
|
|
|
++ PropertyKind kind, Name name, PropertyAttributes attributes) {
|
|
|
++ return SearchAndGetTarget(kind, name, attributes);
|
|
|
++}
|
|
|
++
|
|
|
+ int TransitionArray::SearchSpecial(Symbol symbol, int* out_insertion_index) {
|
|
|
+ return SearchName(symbol, out_insertion_index);
|
|
|
+ }
|
|
|
+
|
|
|
+ int TransitionArray::SearchName(Name name, int* out_insertion_index) {
|
|
|
+ DCHECK(name.IsUniqueName());
|
|
|
++ // The name is taken from DescriptorArray, so it must already has a computed
|
|
|
++ // hash.
|
|
|
++ DCHECK(name.HasHashCode());
|
|
|
+ return internal::Search<ALL_ENTRIES>(this, name, number_of_entries(),
|
|
|
+ out_insertion_index);
|
|
|
+ }
|
|
|
+diff --git a/src/objects/transitions.cc b/src/objects/transitions.cc
|
|
|
+index 1309ca82be544dced9d636f52519d49e70fbc1a3..93d43f42d97ebc53adb6f5927ff92bbb82ba57aa 100644
|
|
|
+--- a/src/objects/transitions.cc
|
|
|
++++ b/src/objects/transitions.cc
|
|
|
+@@ -604,8 +604,8 @@ void TransitionArray::Sort() {
|
|
|
+ temp_kind = details.kind();
|
|
|
+ temp_attributes = details.attributes();
|
|
|
+ }
|
|
|
+- int cmp = CompareKeys(temp_key, temp_key.Hash(), temp_kind,
|
|
|
+- temp_attributes, key, key.Hash(), kind, attributes);
|
|
|
++ int cmp = CompareKeys(temp_key, temp_key.hash(), temp_kind,
|
|
|
++ temp_attributes, key, key.hash(), kind, attributes);
|
|
|
+ if (cmp > 0) {
|
|
|
+ SetKey(j + 1, temp_key);
|
|
|
+ SetRawTarget(j + 1, temp_target);
|
|
|
+diff --git a/src/objects/transitions.h b/src/objects/transitions.h
|
|
|
+index 7bc4d70a35da1e20120cc2f55710ecb9155fd72c..26d29f4cf5ac5c333d95503ab484a9c3b4b1a2fa 100644
|
|
|
+--- a/src/objects/transitions.h
|
|
|
++++ b/src/objects/transitions.h
|
|
|
+@@ -143,6 +143,9 @@ class V8_EXPORT_PRIVATE TransitionsAccessor {
|
|
|
+ return encoding_;
|
|
|
+ }
|
|
|
+
|
|
|
++
|
|
|
++ inline TransitionArray transitions();
|
|
|
++
|
|
|
+ private:
|
|
|
+ friend class MarkCompactCollector; // For HasSimpleTransitionTo.
|
|
|
+ friend class TransitionArray;
|
|
|
+@@ -175,8 +178,6 @@ class V8_EXPORT_PRIVATE TransitionsAccessor {
|
|
|
+ void TraverseTransitionTreeInternal(TraverseCallback callback, void* data,
|
|
|
+ DisallowHeapAllocation* no_gc);
|
|
|
+
|
|
|
+- inline TransitionArray transitions();
|
|
|
+-
|
|
|
+ Isolate* isolate_;
|
|
|
+ Handle<Map> map_handle_;
|
|
|
+ Map map_;
|
|
|
+@@ -231,7 +232,7 @@ class TransitionArray : public WeakFixedArray {
|
|
|
+ V8_EXPORT_PRIVATE bool IsSortedNoDuplicates();
|
|
|
+ #endif
|
|
|
+
|
|
|
+- void Sort();
|
|
|
++ V8_EXPORT_PRIVATE void Sort();
|
|
|
+
|
|
|
+ void PrintInternal(std::ostream& os);
|
|
|
+
|
|
|
+@@ -260,6 +261,9 @@ class TransitionArray : public WeakFixedArray {
|
|
|
+ inline int SearchNameForTesting(Name name,
|
|
|
+ int* out_insertion_index = nullptr);
|
|
|
+
|
|
|
++ inline Map SearchAndGetTargetForTesting(PropertyKind kind, Name name,
|
|
|
++ PropertyAttributes attributes);
|
|
|
++
|
|
|
+ private:
|
|
|
+ friend class Factory;
|
|
|
+ friend class MarkCompactCollector;
|
|
|
+@@ -296,8 +300,8 @@ class TransitionArray : public WeakFixedArray {
|
|
|
+ int Search(PropertyKind kind, Name name, PropertyAttributes attributes,
|
|
|
+ int* out_insertion_index = nullptr);
|
|
|
+
|
|
|
+- Map SearchAndGetTarget(PropertyKind kind, Name name,
|
|
|
+- PropertyAttributes attributes);
|
|
|
++ V8_EXPORT_PRIVATE Map SearchAndGetTarget(PropertyKind kind, Name name,
|
|
|
++ PropertyAttributes attributes);
|
|
|
+
|
|
|
+ // Search a non-property transition (like elements kind, observe or frozen
|
|
|
+ // transitions).
|
|
|
+diff --git a/test/cctest/BUILD.gn b/test/cctest/BUILD.gn
|
|
|
+index a55451c6c9c1d87b9dd670e14c16fcdac1d9753d..c4d0d98466d23be18bb2884a22c02531c9a93878 100644
|
|
|
+--- a/test/cctest/BUILD.gn
|
|
|
++++ b/test/cctest/BUILD.gn
|
|
|
+@@ -205,6 +205,7 @@ v8_source_set("cctest_sources") {
|
|
|
+ "test-debug.cc",
|
|
|
+ "test-decls.cc",
|
|
|
+ "test-deoptimization.cc",
|
|
|
++ "test-descriptor-array.cc",
|
|
|
+ "test-dictionary.cc",
|
|
|
+ "test-diy-fp.cc",
|
|
|
+ "test-double.cc",
|
|
|
+diff --git a/test/cctest/test-descriptor-array.cc b/test/cctest/test-descriptor-array.cc
|
|
|
+new file mode 100644
|
|
|
+index 0000000000000000000000000000000000000000..7abd36ec6c84959c3da59b8e78d9e4a0ee291632
|
|
|
+--- /dev/null
|
|
|
++++ b/test/cctest/test-descriptor-array.cc
|
|
|
+@@ -0,0 +1,424 @@
|
|
|
++// Copyright 2020 the V8 project authors. All rights reserved.
|
|
|
++// Use of this source code is governed by a BSD-style license that can be
|
|
|
++// found in the LICENSE file.
|
|
|
++
|
|
|
++#include "src/base/logging.h"
|
|
|
++#include "src/codegen/code-stub-assembler.h"
|
|
|
++#include "src/common/globals.h"
|
|
|
++#include "src/objects/descriptor-array.h"
|
|
|
++#include "src/objects/property-details.h"
|
|
|
++#include "src/objects/string-inl.h"
|
|
|
++#include "src/objects/transitions-inl.h"
|
|
|
++#include "test/cctest/cctest.h"
|
|
|
++#include "test/cctest/compiler/code-assembler-tester.h"
|
|
|
++#include "test/cctest/compiler/function-tester.h"
|
|
|
++#include "test/cctest/test-transitions.h"
|
|
|
++
|
|
|
++namespace v8 {
|
|
|
++namespace internal {
|
|
|
++
|
|
|
++namespace {
|
|
|
++
|
|
|
++using Label = compiler::CodeAssemblerLabel;
|
|
|
++template <class T>
|
|
|
++using TVariable = compiler::TypedCodeAssemblerVariable<T>;
|
|
|
++
|
|
|
++Handle<Name> NewNameWithHash(Isolate* isolate, const char* str, uint32_t hash,
|
|
|
++ bool is_integer) {
|
|
|
++ uint32_t hash_field = hash << Name::kHashShift;
|
|
|
++
|
|
|
++ static_assert(Name::kNofHashBitFields == 2, "This test needs updating");
|
|
|
++ static_assert(Name::kHashNotComputedMask == 1, "This test needs updating");
|
|
|
++ static_assert(Name::kIsNotIntegerIndexMask == 2, "This test needs updating");
|
|
|
++
|
|
|
++ if (!is_integer) {
|
|
|
++ hash_field |= Name::kIsNotIntegerIndexMask;
|
|
|
++ }
|
|
|
++ Handle<Name> name = isolate->factory()->NewOneByteInternalizedString(
|
|
|
++ OneByteVector(str), hash_field);
|
|
|
++ name->set_hash_field(hash_field);
|
|
|
++ CHECK(name->IsUniqueName());
|
|
|
++ return name;
|
|
|
++}
|
|
|
++
|
|
|
++template <typename... Args>
|
|
|
++MaybeHandle<Object> Call(Isolate* isolate, Handle<JSFunction> function,
|
|
|
++ Args... args) {
|
|
|
++ const int nof_args = sizeof...(Args);
|
|
|
++ Handle<Object> call_args[] = {args...};
|
|
|
++ Handle<Object> receiver = isolate->factory()->undefined_value();
|
|
|
++ return Execution::Call(isolate, function, receiver, nof_args, call_args);
|
|
|
++}
|
|
|
++
|
|
|
++void CheckDescriptorArrayLookups(Isolate* isolate, Handle<Map> map,
|
|
|
++ std::vector<Handle<Name>>& names,
|
|
|
++ Handle<JSFunction> csa_lookup) {
|
|
|
++ // Test C++ implementation.
|
|
|
++ {
|
|
|
++ DisallowHeapAllocation no_gc;
|
|
|
++ DescriptorArray descriptors = map->instance_descriptors();
|
|
|
++ DCHECK(descriptors.IsSortedNoDuplicates());
|
|
|
++ int nof_descriptors = descriptors.number_of_descriptors();
|
|
|
++
|
|
|
++ for (size_t i = 0; i < names.size(); ++i) {
|
|
|
++ Name name = *names[i];
|
|
|
++ InternalIndex index = descriptors.Search(name, nof_descriptors, false);
|
|
|
++ CHECK(index.is_found());
|
|
|
++ CHECK_EQ(i, index.as_uint32());
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ // Test CSA implementation.
|
|
|
++ if (!FLAG_jitless) {
|
|
|
++ for (size_t i = 0; i < names.size(); ++i) {
|
|
|
++ Handle<Object> name_index =
|
|
|
++ Call(isolate, csa_lookup, map, names[i]).ToHandleChecked();
|
|
|
++ CHECK(name_index->IsSmi());
|
|
|
++ CHECK_EQ(DescriptorArray::ToKeyIndex(static_cast<int>(i)),
|
|
|
++ Smi::ToInt(*name_index));
|
|
|
++ }
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++void CheckTransitionArrayLookups(Isolate* isolate,
|
|
|
++ Handle<TransitionArray> transitions,
|
|
|
++ std::vector<Handle<Map>>& maps,
|
|
|
++ Handle<JSFunction> csa_lookup) {
|
|
|
++ // Test C++ implementation.
|
|
|
++ {
|
|
|
++ DisallowHeapAllocation no_gc;
|
|
|
++ DCHECK(transitions->IsSortedNoDuplicates());
|
|
|
++
|
|
|
++ for (size_t i = 0; i < maps.size(); ++i) {
|
|
|
++ Map expected_map = *maps[i];
|
|
|
++ Name name =
|
|
|
++ expected_map.instance_descriptors().GetKey(expected_map.LastAdded());
|
|
|
++
|
|
|
++ Map map = transitions->SearchAndGetTargetForTesting(PropertyKind::kData,
|
|
|
++ name, NONE);
|
|
|
++ CHECK(!map.is_null());
|
|
|
++ CHECK_EQ(expected_map, map);
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ // Test CSA implementation.
|
|
|
++ if (!FLAG_jitless) {
|
|
|
++ for (size_t i = 0; i < maps.size(); ++i) {
|
|
|
++ Handle<Map> expected_map = maps[i];
|
|
|
++ Handle<Name> name(expected_map->instance_descriptors().GetKey(
|
|
|
++ expected_map->LastAdded()),
|
|
|
++ isolate);
|
|
|
++
|
|
|
++ Handle<Object> transition_map =
|
|
|
++ Call(isolate, csa_lookup, transitions, name).ToHandleChecked();
|
|
|
++ CHECK(transition_map->IsMap());
|
|
|
++ CHECK_EQ(*expected_map, *transition_map);
|
|
|
++ }
|
|
|
++ }
|
|
|
++}
|
|
|
++
|
|
|
++// Creates function with (Map, Name) arguments. Returns Smi with the index of
|
|
|
++// the name value of the found descriptor (DescriptorArray::ToKeyIndex())
|
|
|
++// or null otherwise.
|
|
|
++Handle<JSFunction> CreateCsaDescriptorArrayLookup(Isolate* isolate) {
|
|
|
++ // We are not allowed to generate code in jitless mode.
|
|
|
++ if (FLAG_jitless) return Handle<JSFunction>();
|
|
|
++
|
|
|
++ // Preallocate handle for the result in the current handle scope.
|
|
|
++ Handle<JSFunction> result_function(JSFunction{}, isolate);
|
|
|
++
|
|
|
++ const int kNumParams = 2;
|
|
|
++
|
|
|
++ compiler::CodeAssemblerTester asm_tester(
|
|
|
++ isolate, kNumParams + 1, // +1 to include receiver.
|
|
|
++ CodeKind::STUB);
|
|
|
++ {
|
|
|
++ CodeStubAssembler m(asm_tester.state());
|
|
|
++
|
|
|
++ TNode<Map> map = m.CAST(m.Parameter(1));
|
|
|
++ TNode<Name> unique_name = m.CAST(m.Parameter(2));
|
|
|
++
|
|
|
++ Label passed(&m), failed(&m);
|
|
|
++ Label if_found(&m), if_not_found(&m);
|
|
|
++ TVariable<IntPtrT> var_name_index(&m);
|
|
|
++
|
|
|
++ TNode<Uint32T> bit_field3 = m.LoadMapBitField3(map);
|
|
|
++ TNode<DescriptorArray> descriptors = m.LoadMapDescriptors(map);
|
|
|
++
|
|
|
++ m.DescriptorLookup(unique_name, descriptors, bit_field3, &if_found,
|
|
|
++ &var_name_index, &if_not_found);
|
|
|
++
|
|
|
++ m.BIND(&if_found);
|
|
|
++ m.Return(m.SmiTag(var_name_index.value()));
|
|
|
++
|
|
|
++ m.BIND(&if_not_found);
|
|
|
++ m.Return(m.NullConstant());
|
|
|
++ }
|
|
|
++
|
|
|
++ {
|
|
|
++ compiler::FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
|
|
|
++ // Copy function value to a handle created in the outer handle scope.
|
|
|
++ *(result_function.location()) = ft.function->ptr();
|
|
|
++ }
|
|
|
++
|
|
|
++ return result_function;
|
|
|
++}
|
|
|
++
|
|
|
++// Creates function with (TransitionArray, Name) arguments. Returns transition
|
|
|
++// map if transition is found or null otherwise.
|
|
|
++Handle<JSFunction> CreateCsaTransitionArrayLookup(Isolate* isolate) {
|
|
|
++ // We are not allowed to generate code in jitless mode.
|
|
|
++ if (FLAG_jitless) return Handle<JSFunction>();
|
|
|
++
|
|
|
++ // Preallocate handle for the result in the current handle scope.
|
|
|
++ Handle<JSFunction> result_function(JSFunction{}, isolate);
|
|
|
++
|
|
|
++ const int kNumParams = 2;
|
|
|
++ compiler::CodeAssemblerTester asm_tester(
|
|
|
++ isolate, kNumParams + 1, // +1 to include receiver.
|
|
|
++ CodeKind::STUB);
|
|
|
++ {
|
|
|
++ CodeStubAssembler m(asm_tester.state());
|
|
|
++
|
|
|
++ TNode<TransitionArray> transitions = m.CAST(m.Parameter(1));
|
|
|
++ TNode<Name> unique_name = m.CAST(m.Parameter(2));
|
|
|
++
|
|
|
++ Label passed(&m), failed(&m);
|
|
|
++ Label if_found(&m), if_not_found(&m);
|
|
|
++ TVariable<IntPtrT> var_name_index(&m);
|
|
|
++
|
|
|
++ m.TransitionLookup(unique_name, transitions, &if_found, &var_name_index,
|
|
|
++ &if_not_found);
|
|
|
++
|
|
|
++ m.BIND(&if_found);
|
|
|
++ {
|
|
|
++ STATIC_ASSERT(kData == 0);
|
|
|
++ STATIC_ASSERT(NONE == 0);
|
|
|
++ const int kKeyToTargetOffset = (TransitionArray::kEntryTargetIndex -
|
|
|
++ TransitionArray::kEntryKeyIndex) *
|
|
|
++ kTaggedSize;
|
|
|
++ TNode<Map> transition_map = m.CAST(m.GetHeapObjectAssumeWeak(
|
|
|
++ m.LoadArrayElement(transitions, WeakFixedArray::kHeaderSize,
|
|
|
++ var_name_index.value(), kKeyToTargetOffset)));
|
|
|
++ m.Return(transition_map);
|
|
|
++ }
|
|
|
++
|
|
|
++ m.BIND(&if_not_found);
|
|
|
++ m.Return(m.NullConstant());
|
|
|
++ }
|
|
|
++
|
|
|
++ {
|
|
|
++ compiler::FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
|
|
|
++ // Copy function value to a handle created in the outer handle scope.
|
|
|
++ *(result_function.location()) = ft.function->ptr();
|
|
|
++ }
|
|
|
++
|
|
|
++ return result_function;
|
|
|
++}
|
|
|
++
|
|
|
++} // namespace
|
|
|
++
|
|
|
++TEST(DescriptorArrayHashCollisionMassive) {
|
|
|
++ CcTest::InitializeVM();
|
|
|
++ Isolate* isolate = CcTest::i_isolate();
|
|
|
++ HandleScope handle_scope(isolate);
|
|
|
++
|
|
|
++ static_assert(Name::kNofHashBitFields == 2, "This test needs updating");
|
|
|
++
|
|
|
++ std::vector<Handle<Name>> names;
|
|
|
++
|
|
|
++ // Use the same hash value for all names.
|
|
|
++ uint32_t hash =
|
|
|
++ static_cast<uint32_t>(isolate->GenerateIdentityHash(Name::kHashBitMask));
|
|
|
++
|
|
|
++ for (int i = 0; i < kMaxNumberOfDescriptors / 2; ++i) {
|
|
|
++ // Add pairs of names having the same base hash value but having different
|
|
|
++ // values of is_integer bit.
|
|
|
++ bool first_is_integer = (i & 1) != 0;
|
|
|
++ bool second_is_integer = (i & 2) != 0;
|
|
|
++
|
|
|
++ names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer));
|
|
|
++ names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer));
|
|
|
++ }
|
|
|
++
|
|
|
++ // Create descriptor array with the created names by appending fields to some
|
|
|
++ // map. DescriptorArray marking relies on the fact that it's attached to an
|
|
|
++ // owning map.
|
|
|
++ Handle<Map> map = Map::Create(isolate, 0);
|
|
|
++
|
|
|
++ Handle<FieldType> any_type = FieldType::Any(isolate);
|
|
|
++
|
|
|
++ for (size_t i = 0; i < names.size(); ++i) {
|
|
|
++ map = Map::CopyWithField(isolate, map, names[i], any_type, NONE,
|
|
|
++ PropertyConstness::kMutable,
|
|
|
++ Representation::Tagged(), OMIT_TRANSITION)
|
|
|
++ .ToHandleChecked();
|
|
|
++ }
|
|
|
++
|
|
|
++ Handle<JSFunction> csa_lookup = CreateCsaDescriptorArrayLookup(isolate);
|
|
|
++
|
|
|
++ CheckDescriptorArrayLookups(isolate, map, names, csa_lookup);
|
|
|
++
|
|
|
++ // Sort descriptor array and check it again.
|
|
|
++ map->instance_descriptors().Sort();
|
|
|
++ CheckDescriptorArrayLookups(isolate, map, names, csa_lookup);
|
|
|
++}
|
|
|
++
|
|
|
++TEST(DescriptorArrayHashCollision) {
|
|
|
++ CcTest::InitializeVM();
|
|
|
++ Isolate* isolate = CcTest::i_isolate();
|
|
|
++ HandleScope handle_scope(isolate);
|
|
|
++
|
|
|
++ static_assert(Name::kNofHashBitFields == 2, "This test needs updating");
|
|
|
++
|
|
|
++ std::vector<Handle<Name>> names;
|
|
|
++ uint32_t hash = 0;
|
|
|
++
|
|
|
++ for (int i = 0; i < kMaxNumberOfDescriptors / 2; ++i) {
|
|
|
++ if (i % 2 == 0) {
|
|
|
++ // Change hash value for every pair of names.
|
|
|
++ hash = static_cast<uint32_t>(
|
|
|
++ isolate->GenerateIdentityHash(Name::kHashBitMask));
|
|
|
++ }
|
|
|
++
|
|
|
++ // Add pairs of names having the same base hash value but having different
|
|
|
++ // values of is_integer bit.
|
|
|
++ bool first_is_integer = (i & 1) != 0;
|
|
|
++ bool second_is_integer = (i & 2) != 0;
|
|
|
++
|
|
|
++ names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer));
|
|
|
++ names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer));
|
|
|
++ }
|
|
|
++
|
|
|
++ // Create descriptor array with the created names by appending fields to some
|
|
|
++ // map. DescriptorArray marking relies on the fact that it's attached to an
|
|
|
++ // owning map.
|
|
|
++ Handle<Map> map = Map::Create(isolate, 0);
|
|
|
++
|
|
|
++ Handle<FieldType> any_type = FieldType::Any(isolate);
|
|
|
++
|
|
|
++ for (size_t i = 0; i < names.size(); ++i) {
|
|
|
++ map = Map::CopyWithField(isolate, map, names[i], any_type, NONE,
|
|
|
++ PropertyConstness::kMutable,
|
|
|
++ Representation::Tagged(), OMIT_TRANSITION)
|
|
|
++ .ToHandleChecked();
|
|
|
++ }
|
|
|
++
|
|
|
++ Handle<JSFunction> csa_lookup = CreateCsaDescriptorArrayLookup(isolate);
|
|
|
++
|
|
|
++ CheckDescriptorArrayLookups(isolate, map, names, csa_lookup);
|
|
|
++
|
|
|
++ // Sort descriptor array and check it again.
|
|
|
++ map->instance_descriptors().Sort();
|
|
|
++ CheckDescriptorArrayLookups(isolate, map, names, csa_lookup);
|
|
|
++}
|
|
|
++
|
|
|
++TEST(TransitionArrayHashCollisionMassive) {
|
|
|
++ CcTest::InitializeVM();
|
|
|
++ Isolate* isolate = CcTest::i_isolate();
|
|
|
++ HandleScope handle_scope(isolate);
|
|
|
++
|
|
|
++ static_assert(Name::kNofHashBitFields == 2, "This test needs updating");
|
|
|
++
|
|
|
++ std::vector<Handle<Name>> names;
|
|
|
++
|
|
|
++ // Use the same hash value for all names.
|
|
|
++ uint32_t hash =
|
|
|
++ static_cast<uint32_t>(isolate->GenerateIdentityHash(Name::kHashBitMask));
|
|
|
++
|
|
|
++ for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions / 2; ++i) {
|
|
|
++ // Add pairs of names having the same base hash value but having different
|
|
|
++ // values of is_integer bit.
|
|
|
++ bool first_is_integer = (i & 1) != 0;
|
|
|
++ bool second_is_integer = (i & 2) != 0;
|
|
|
++
|
|
|
++ names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer));
|
|
|
++ names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer));
|
|
|
++ }
|
|
|
++
|
|
|
++ // Create transitions for each name.
|
|
|
++ Handle<Map> root_map = Map::Create(isolate, 0);
|
|
|
++
|
|
|
++ std::vector<Handle<Map>> maps;
|
|
|
++
|
|
|
++ Handle<FieldType> any_type = FieldType::Any(isolate);
|
|
|
++
|
|
|
++ for (size_t i = 0; i < names.size(); ++i) {
|
|
|
++ Handle<Map> map =
|
|
|
++ Map::CopyWithField(isolate, root_map, names[i], any_type, NONE,
|
|
|
++ PropertyConstness::kMutable,
|
|
|
++ Representation::Tagged(), INSERT_TRANSITION)
|
|
|
++ .ToHandleChecked();
|
|
|
++ maps.push_back(map);
|
|
|
++ }
|
|
|
++
|
|
|
++ Handle<JSFunction> csa_lookup = CreateCsaTransitionArrayLookup(isolate);
|
|
|
++
|
|
|
++ Handle<TransitionArray> transition_array(
|
|
|
++ TestTransitionsAccessor(isolate, root_map).transitions(), isolate);
|
|
|
++
|
|
|
++ CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup);
|
|
|
++
|
|
|
++ // Sort transition array and check it again.
|
|
|
++ transition_array->Sort();
|
|
|
++ CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup);
|
|
|
++}
|
|
|
++
|
|
|
++TEST(TransitionArrayHashCollision) {
|
|
|
++ CcTest::InitializeVM();
|
|
|
++ Isolate* isolate = CcTest::i_isolate();
|
|
|
++ HandleScope handle_scope(isolate);
|
|
|
++
|
|
|
++ static_assert(Name::kNofHashBitFields == 2, "This test needs updating");
|
|
|
++
|
|
|
++ std::vector<Handle<Name>> names;
|
|
|
++
|
|
|
++ // Use the same hash value for all names.
|
|
|
++ uint32_t hash =
|
|
|
++ static_cast<uint32_t>(isolate->GenerateIdentityHash(Name::kHashBitMask));
|
|
|
++
|
|
|
++ for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions / 2; ++i) {
|
|
|
++ if (i % 2 == 0) {
|
|
|
++ // Change hash value for every pair of names.
|
|
|
++ hash = static_cast<uint32_t>(
|
|
|
++ isolate->GenerateIdentityHash(Name::kHashBitMask));
|
|
|
++ }
|
|
|
++ // Add pairs of names having the same base hash value but having different
|
|
|
++ // values of is_integer bit.
|
|
|
++ bool first_is_integer = (i & 1) != 0;
|
|
|
++ bool second_is_integer = (i & 2) != 0;
|
|
|
++
|
|
|
++ names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer));
|
|
|
++ names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer));
|
|
|
++ }
|
|
|
++
|
|
|
++ // Create transitions for each name.
|
|
|
++ Handle<Map> root_map = Map::Create(isolate, 0);
|
|
|
++
|
|
|
++ std::vector<Handle<Map>> maps;
|
|
|
++
|
|
|
++ Handle<FieldType> any_type = FieldType::Any(isolate);
|
|
|
++
|
|
|
++ for (size_t i = 0; i < names.size(); ++i) {
|
|
|
++ Handle<Map> map =
|
|
|
++ Map::CopyWithField(isolate, root_map, names[i], any_type, NONE,
|
|
|
++ PropertyConstness::kMutable,
|
|
|
++ Representation::Tagged(), INSERT_TRANSITION)
|
|
|
++ .ToHandleChecked();
|
|
|
++ maps.push_back(map);
|
|
|
++ }
|
|
|
++
|
|
|
++ Handle<JSFunction> csa_lookup = CreateCsaTransitionArrayLookup(isolate);
|
|
|
++
|
|
|
++ Handle<TransitionArray> transition_array(
|
|
|
++ TestTransitionsAccessor(isolate, root_map).transitions(), isolate);
|
|
|
++
|
|
|
++ CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup);
|
|
|
++
|
|
|
++ // Sort transition array and check it again.
|
|
|
++ transition_array->Sort();
|
|
|
++ CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup);
|
|
|
++}
|
|
|
++
|
|
|
++} // namespace internal
|
|
|
++} // namespace v8
|
|
|
+diff --git a/test/cctest/test-transitions.h b/test/cctest/test-transitions.h
|
|
|
+index 724eb3d3c544b5e9535e7a1b14d95eccec34f4cc..66bbbfa76dd7c2aa4c84363a69705d2564fee8a6 100644
|
|
|
+--- a/test/cctest/test-transitions.h
|
|
|
++++ b/test/cctest/test-transitions.h
|
|
|
+@@ -24,6 +24,8 @@ class TestTransitionsAccessor : public TransitionsAccessor {
|
|
|
+ bool IsFullTransitionArrayEncoding() {
|
|
|
+ return encoding() == kFullTransitionArray;
|
|
|
+ }
|
|
|
++
|
|
|
++ TransitionArray transitions() { return TransitionsAccessor::transitions(); }
|
|
|
+ };
|
|
|
+
|
|
|
+ } // namespace internal
|