| 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);
|
| +}
|
|
|