Index: test/cctest/test-elements-kind.cc |
diff --git a/test/cctest/test-elements-kind.cc b/test/cctest/test-elements-kind.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f5630ab54e6321ae90457d5a919844872e990898 |
--- /dev/null |
+++ b/test/cctest/test-elements-kind.cc |
@@ -0,0 +1,475 @@ |
+// Copyright 2015 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 <stdlib.h> |
+#include <utility> |
+ |
+#include "test/cctest/test-api.h" |
+ |
+#include "src/v8.h" |
+ |
+#include "src/compilation-cache.h" |
+#include "src/execution.h" |
+#include "src/factory.h" |
+#include "src/global-handles.h" |
+#include "src/ic/stub-cache.h" |
+#include "src/objects.h" |
+ |
+using namespace v8::internal; |
+ |
+ |
+// |
+// Helper functions. |
+// |
+ |
+namespace { |
+ |
+Handle<String> MakeString(const char* str) { |
+ Isolate* isolate = CcTest::i_isolate(); |
+ Factory* factory = isolate->factory(); |
+ return factory->InternalizeUtf8String(str); |
+} |
+ |
+ |
+Handle<String> MakeName(const char* str, int suffix) { |
+ EmbeddedVector<char, 128> buffer; |
+ SNPrintF(buffer, "%s%d", str, suffix); |
+ return MakeString(buffer.start()); |
+} |
+ |
+ |
+template <typename T, typename M> |
+bool EQUALS(Handle<T> left, Handle<M> right) { |
+ if (*left == *right) return true; |
+ return JSObject::Equals(Handle<Object>::cast(left), |
+ Handle<Object>::cast(right)) |
+ .FromJust(); |
+} |
+ |
+ |
+template <typename T, typename M> |
+bool EQUALS(Handle<T> left, M right) { |
+ return EQUALS(left, handle(right)); |
+} |
+ |
+ |
+template <typename T, typename M> |
+bool EQUALS(T left, Handle<M> right) { |
+ return EQUALS(handle(left), right); |
+} |
+ |
+} // namespace |
+ |
+ |
+// |
+// Tests |
+// |
+ |
+TEST(JSObjectAddingProperties) { |
+ CcTest::InitializeVM(); |
+ Isolate* isolate = CcTest::i_isolate(); |
+ Factory* factory = isolate->factory(); |
+ v8::HandleScope scope(CcTest::isolate()); |
+ |
+ Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array()); |
+ Handle<JSFunction> function = factory->NewFunction(factory->empty_string()); |
+ Handle<Object> value(Smi::FromInt(42), isolate); |
+ |
+ Handle<JSObject> object = factory->NewJSObject(function); |
+ Handle<Map> previous_map(object->map()); |
+ CHECK_EQ(previous_map->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK(EQUALS(object->properties(), empty_fixed_array)); |
+ CHECK(EQUALS(object->elements(), empty_fixed_array)); |
+ |
+ // for the default constructor function no in-object properties are reserved |
+ // hence adding a single property will initialize the property-array |
+ Handle<String> name = MakeName("property", 0); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) |
+ .Check(); |
+ CHECK_NE(object->map(), *previous_map); |
+ CHECK_EQ(object->map()->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK_LE(1, object->properties()->length()); |
+ CHECK(EQUALS(object->elements(), empty_fixed_array)); |
+} |
+ |
+ |
+TEST(JSObjectInObjectAddingProperties) { |
+ CcTest::InitializeVM(); |
+ Isolate* isolate = CcTest::i_isolate(); |
+ Factory* factory = isolate->factory(); |
+ v8::HandleScope scope(CcTest::isolate()); |
+ |
+ Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array()); |
+ Handle<JSFunction> function = factory->NewFunction(factory->empty_string()); |
+ int nof_inobject_properties = 10; |
+ // force in object properties by changing the expected_nof_properties |
+ function->shared()->set_expected_nof_properties(nof_inobject_properties); |
+ Handle<Object> value(Smi::FromInt(42), isolate); |
+ |
+ Handle<JSObject> object = factory->NewJSObject(function); |
+ Handle<Map> previous_map(object->map()); |
+ CHECK_EQ(previous_map->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK(EQUALS(object->properties(), empty_fixed_array)); |
+ CHECK(EQUALS(object->elements(), empty_fixed_array)); |
+ |
+ // we have reserved space for in-object properties, hence adding up to |
+ // |nof_inobject_properties| will not create a property store |
+ for (int i = 0; i < nof_inobject_properties; i++) { |
+ Handle<String> name = MakeName("property", i); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) |
+ .Check(); |
+ } |
+ CHECK_NE(object->map(), *previous_map); |
+ CHECK_EQ(object->map()->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK(EQUALS(object->properties(), empty_fixed_array)); |
+ CHECK(EQUALS(object->elements(), empty_fixed_array)); |
+ |
+ // adding one more property will not fit in the in-object properties, thus |
+ // creating a property store |
+ int index = nof_inobject_properties + 1; |
+ Handle<String> name = MakeName("property", index); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) |
+ .Check(); |
+ CHECK_NE(object->map(), *previous_map); |
+ CHECK_EQ(object->map()->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ // there must be at least 1 element in the properies store |
+ CHECK_LE(1, object->properties()->length()); |
+ CHECK(EQUALS(object->elements(), empty_fixed_array)); |
+} |
+ |
+ |
+TEST(JSObjectAddingElements) { |
+ CcTest::InitializeVM(); |
+ Isolate* isolate = CcTest::i_isolate(); |
+ Factory* factory = isolate->factory(); |
+ v8::HandleScope scope(CcTest::isolate()); |
+ |
+ Handle<String> name; |
+ Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array()); |
+ Handle<JSFunction> function = factory->NewFunction(factory->empty_string()); |
+ Handle<Object> value(Smi::FromInt(42), isolate); |
+ |
+ Handle<JSObject> object = factory->NewJSObject(function); |
+ Handle<Map> previous_map(object->map()); |
+ CHECK_EQ(previous_map->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK(EQUALS(object->properties(), empty_fixed_array)); |
+ CHECK(EQUALS(object->elements(), empty_fixed_array)); |
+ |
+ // Adding an indexed element initializes the elements array |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) |
+ .Check(); |
+ // no change in elements_kind => no map transition |
+ CHECK_EQ(object->map(), *previous_map); |
+ CHECK_EQ(object->map()->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK(EQUALS(object->properties(), empty_fixed_array)); |
+ CHECK_LE(1, object->elements()->length()); |
+ |
+ // Adding more consecutive elements without a change in the backing store |
+ int non_dict_backing_store_limit = 100; |
+ for (int i = 1; i < non_dict_backing_store_limit; i++) { |
+ name = MakeName("", i); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) |
+ .Check(); |
+ } |
+ // no change in elements_kind => no map transition |
+ CHECK_EQ(object->map(), *previous_map); |
+ CHECK_EQ(object->map()->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK(EQUALS(object->properties(), empty_fixed_array)); |
+ CHECK_LE(non_dict_backing_store_limit, object->elements()->length()); |
+ |
+ // Adding an element at an very large index causes a change to |
+ // DICTIONARY_ELEMENTS |
+ name = MakeString("100000000"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) |
+ .Check(); |
+ // change in elements_kind => map transition |
+ CHECK_NE(object->map(), *previous_map); |
+ CHECK_EQ(object->map()->elements_kind(), DICTIONARY_ELEMENTS); |
+ CHECK(EQUALS(object->properties(), empty_fixed_array)); |
+ CHECK_LE(non_dict_backing_store_limit, object->elements()->length()); |
+} |
+ |
+ |
+TEST(JSArrayAddingProperties) { |
+ CcTest::InitializeVM(); |
+ Isolate* isolate = CcTest::i_isolate(); |
+ Factory* factory = isolate->factory(); |
+ v8::HandleScope scope(CcTest::isolate()); |
+ |
+ Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array()); |
+ Handle<Object> value(Smi::FromInt(42), isolate); |
+ |
+ Handle<JSArray> array = |
+ factory->NewJSArray(ElementsKind::FAST_SMI_ELEMENTS, 0, 0); |
+ Handle<Map> previous_map(array->map()); |
+ CHECK_EQ(previous_map->elements_kind(), FAST_SMI_ELEMENTS); |
+ CHECK(EQUALS(array->properties(), empty_fixed_array)); |
+ CHECK(EQUALS(array->elements(), empty_fixed_array)); |
+ CHECK_EQ(Smi::cast(array->length())->value(), 0); |
+ |
+ // for the default constructor function no in-object properties are reserved |
+ // hence adding a single property will initialize the property-array |
+ Handle<String> name = MakeName("property", 0); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE) |
+ .Check(); |
+ // No change in elements_kind but added property => new map |
+ CHECK_NE(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_SMI_ELEMENTS); |
+ CHECK_LE(1, array->properties()->length()); |
+ CHECK(EQUALS(array->elements(), empty_fixed_array)); |
+ CHECK_EQ(Smi::cast(array->length())->value(), 0); |
+} |
+ |
+ |
+TEST(JSArrayAddingElements) { |
+ CcTest::InitializeVM(); |
+ Isolate* isolate = CcTest::i_isolate(); |
+ Factory* factory = isolate->factory(); |
+ v8::HandleScope scope(CcTest::isolate()); |
+ |
+ Handle<String> name; |
+ Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array()); |
+ Handle<Object> value(Smi::FromInt(42), isolate); |
+ |
+ Handle<JSArray> array = |
+ factory->NewJSArray(ElementsKind::FAST_SMI_ELEMENTS, 0, 0); |
+ Handle<Map> previous_map(array->map()); |
+ CHECK_EQ(previous_map->elements_kind(), FAST_SMI_ELEMENTS); |
+ CHECK(EQUALS(array->properties(), empty_fixed_array)); |
+ CHECK(EQUALS(array->elements(), empty_fixed_array)); |
+ CHECK_EQ(Smi::cast(array->length())->value(), 0); |
+ |
+ // Adding an indexed element initializes the elements array |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE) |
+ .Check(); |
+ // no change in elements_kind => no map transition |
+ CHECK_EQ(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_SMI_ELEMENTS); |
+ CHECK(EQUALS(array->properties(), empty_fixed_array)); |
+ CHECK_LE(1, array->elements()->length()); |
+ CHECK_EQ(1, Smi::cast(array->length())->value()); |
+ |
+ // Adding more consecutive elements without a change in the backing store |
+ int non_dict_backing_store_limit = 100; |
+ for (int i = 1; i < non_dict_backing_store_limit; i++) { |
+ name = MakeName("", i); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE) |
+ .Check(); |
+ } |
+ // no change in elements_kind => no map transition |
+ CHECK_EQ(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_SMI_ELEMENTS); |
+ CHECK(EQUALS(array->properties(), empty_fixed_array)); |
+ CHECK_LE(non_dict_backing_store_limit, array->elements()->length()); |
+ CHECK_EQ(non_dict_backing_store_limit, Smi::cast(array->length())->value()); |
+ |
+ // Adding an element at an very large index causes a change to |
+ // DICTIONARY_ELEMENTS |
+ int index = 100000000; |
+ name = MakeName("", index); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE) |
+ .Check(); |
+ // change in elements_kind => map transition |
+ CHECK_NE(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), DICTIONARY_ELEMENTS); |
+ CHECK(EQUALS(array->properties(), empty_fixed_array)); |
+ CHECK_LE(non_dict_backing_store_limit, array->elements()->length()); |
+ CHECK_LE(array->elements()->length(), index); |
+ CHECK_EQ(index + 1, Smi::cast(array->length())->value()); |
+} |
+ |
+ |
+TEST(JSArrayAddingElementsGeneralizingiFastSmiElements) { |
+ CcTest::InitializeVM(); |
+ Isolate* isolate = CcTest::i_isolate(); |
+ Factory* factory = isolate->factory(); |
+ v8::HandleScope scope(CcTest::isolate()); |
+ |
+ Handle<String> name; |
+ Handle<Object> value_smi(Smi::FromInt(42), isolate); |
+ Handle<Object> value_string(MakeString("value")); |
+ Handle<Object> value_double = factory->NewNumber(3.1415); |
+ |
+ Handle<JSArray> array = |
+ factory->NewJSArray(ElementsKind::FAST_SMI_ELEMENTS, 0, 0); |
+ Handle<Map> previous_map(array->map()); |
+ CHECK_EQ(previous_map->elements_kind(), FAST_SMI_ELEMENTS); |
+ CHECK_EQ(Smi::cast(array->length())->value(), 0); |
+ |
+ // `array[0] = smi_value` doesn't change the elements_kind |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, |
+ NONE) |
+ .Check(); |
+ // no change in elements_kind => no map transition |
+ CHECK_EQ(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_SMI_ELEMENTS); |
+ CHECK_EQ(1, Smi::cast(array->length())->value()); |
+ |
+ // `delete array[0]` does not alter length, but changes the elments_kind |
+ name = MakeString("0"); |
+ JSReceiver::DeletePropertyOrElement(array, name).Check(); |
+ CHECK_NE(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_SMI_ELEMENTS); |
+ CHECK_EQ(1, Smi::cast(array->length())->value()); |
+ previous_map = handle(array->map()); |
+ |
+ // add a couple of elements again |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, |
+ NONE) |
+ .Check(); |
+ name = MakeString("1"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, |
+ NONE) |
+ .Check(); |
+ CHECK_EQ(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_SMI_ELEMENTS); |
+ CHECK_EQ(2, Smi::cast(array->length())->value()); |
+ |
+ // Adding a string to the array changes from FAST_HOLEY_SMI to FAST_HOLEY |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string, |
+ NONE) |
+ .Check(); |
+ CHECK_NE(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK_EQ(2, Smi::cast(array->length())->value()); |
+ previous_map = handle(array->map()); |
+ |
+ // We don't transition back to FAST_SMI even if we remove the string |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, |
+ NONE) |
+ .Check(); |
+ CHECK_EQ(array->map(), *previous_map); |
+ |
+ // Adding a double doesn't change the map either |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double, |
+ NONE) |
+ .Check(); |
+ CHECK_EQ(array->map(), *previous_map); |
+} |
+ |
+ |
+TEST(JSArrayAddingElementsGeneralizingFastElements) { |
+ CcTest::InitializeVM(); |
+ Isolate* isolate = CcTest::i_isolate(); |
+ Factory* factory = isolate->factory(); |
+ v8::HandleScope scope(CcTest::isolate()); |
+ |
+ Handle<String> name; |
+ Handle<Object> value_smi(Smi::FromInt(42), isolate); |
+ Handle<Object> value_string(MakeString("value")); |
+ |
+ Handle<JSArray> array = |
+ factory->NewJSArray(ElementsKind::FAST_ELEMENTS, 0, 0); |
+ Handle<Map> previous_map(array->map()); |
+ CHECK_EQ(previous_map->elements_kind(), FAST_ELEMENTS); |
+ CHECK_EQ(Smi::cast(array->length())->value(), 0); |
+ |
+ // `array[0] = smi_value` doesn't change the elements_kind |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, |
+ NONE) |
+ .Check(); |
+ // no change in elements_kind => no map transition |
+ CHECK_EQ(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_ELEMENTS); |
+ CHECK_EQ(1, Smi::cast(array->length())->value()); |
+ |
+ // `delete array[0]` does not alter length, but changes the elments_kind |
+ name = MakeString("0"); |
+ JSReceiver::DeletePropertyOrElement(array, name).Check(); |
+ CHECK_NE(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK_EQ(1, Smi::cast(array->length())->value()); |
+ previous_map = handle(array->map()); |
+ |
+ // add a couple of elements, elements_kind stays HOLEY |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string, |
+ NONE) |
+ .Check(); |
+ name = MakeString("1"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, |
+ NONE) |
+ .Check(); |
+ CHECK_EQ(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK_EQ(2, Smi::cast(array->length())->value()); |
+} |
+ |
+ |
+TEST(JSArrayAddingElementsGeneralizingiFastDoubleElements) { |
+ CcTest::InitializeVM(); |
+ Isolate* isolate = CcTest::i_isolate(); |
+ Factory* factory = isolate->factory(); |
+ v8::HandleScope scope(CcTest::isolate()); |
+ |
+ Handle<String> name; |
+ Handle<Object> value_smi(Smi::FromInt(42), isolate); |
+ Handle<Object> value_string(MakeString("value")); |
+ Handle<Object> value_double = factory->NewNumber(3.1415); |
+ |
+ Handle<JSArray> array = |
+ factory->NewJSArray(ElementsKind::FAST_SMI_ELEMENTS, 0, 0); |
+ Handle<Map> previous_map(array->map()); |
+ |
+ // `array[0] = value_double` changes |elements_kind| to FAST_DOUBLE_ELEMENTS |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double, |
+ NONE) |
+ .Check(); |
+ CHECK_NE(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_DOUBLE_ELEMENTS); |
+ CHECK_EQ(1, Smi::cast(array->length())->value()); |
+ previous_map = handle(array->map()); |
+ |
+ // `array[1] = value_smi` doesn't alter the |elements_kind| |
+ name = MakeString("1"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, |
+ NONE) |
+ .Check(); |
+ CHECK_EQ(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_DOUBLE_ELEMENTS); |
+ CHECK_EQ(2, Smi::cast(array->length())->value()); |
+ |
+ // `delete array[0]` does not alter length, but changes the elments_kind |
+ name = MakeString("0"); |
+ JSReceiver::DeletePropertyOrElement(array, name).Check(); |
+ CHECK_NE(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_DOUBLE_ELEMENTS); |
+ CHECK_EQ(2, Smi::cast(array->length())->value()); |
+ previous_map = handle(array->map()); |
+ |
+ // filling the hole `array[0] = value_smi` again doesn't transition back |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double, |
+ NONE) |
+ .Check(); |
+ CHECK_EQ(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_DOUBLE_ELEMENTS); |
+ CHECK_EQ(2, Smi::cast(array->length())->value()); |
+ |
+ // Adding a string to the array changes to elements_kind FAST_ELEMENTS |
+ name = MakeString("1"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string, |
+ NONE) |
+ .Check(); |
+ CHECK_NE(array->map(), *previous_map); |
+ CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_ELEMENTS); |
+ CHECK_EQ(2, Smi::cast(array->length())->value()); |
+ previous_map = handle(array->map()); |
+ |
+ // Adding a double doesn't change the map |
+ name = MakeString("0"); |
+ JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double, |
+ NONE) |
+ .Check(); |
+ CHECK_EQ(array->map(), *previous_map); |
+} |