test-elements-kind.cc 18.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// 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/global-handles.h"
15
#include "src/heap/factory.h"
16
#include "src/ic/stub-cache.h"
17
#include "src/objects-inl.h"
18
#include "src/objects/js-array-inl.h"
19

20 21
namespace v8 {
namespace internal {
22
namespace test_elements_kind {
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

//
// 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>
44
bool EQUALS(Isolate* isolate, Handle<T> left, Handle<M> right) {
45
  if (*left == *right) return true;
46
  return JSObject::Equals(isolate, Handle<Object>::cast(left),
47 48 49 50 51
                          Handle<Object>::cast(right))
      .FromJust();
}

template <typename T, typename M>
52 53
bool EQUALS(Isolate* isolate, Handle<T> left, M right) {
  return EQUALS(isolate, left, handle(right, isolate));
54 55 56
}

template <typename T, typename M>
57 58
bool EQUALS(Isolate* isolate, T left, Handle<M> right) {
  return EQUALS(isolate, handle(left, isolate), right);
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
}

}  // 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());
75
  Handle<PropertyArray> empty_property_array(factory->empty_property_array());
76 77
  Handle<JSFunction> function =
      factory->NewFunctionForTest(factory->empty_string());
78 79 80
  Handle<Object> value(Smi::FromInt(42), isolate);

  Handle<JSObject> object = factory->NewJSObject(function);
81
  Handle<Map> previous_map(object->map(), isolate);
82
  CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
83 84
  CHECK(EQUALS(isolate, object->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
85 86 87 88 89 90 91

  // 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);
92
  CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
93
  CHECK_LE(1, object->property_array()->length());
94
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
95 96 97 98 99 100 101 102 103 104
}


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());
105
  Handle<PropertyArray> empty_property_array(factory->empty_property_array());
106 107
  Handle<JSFunction> function =
      factory->NewFunctionForTest(factory->empty_string());
108 109 110 111 112 113
  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);
114
  Handle<Map> previous_map(object->map(), isolate);
115
  CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
116 117
  CHECK(EQUALS(isolate, object->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
118 119 120 121 122 123 124 125 126

  // 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);
127
  CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
128 129
  CHECK(EQUALS(isolate, object->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
130 131 132 133 134 135 136 137

  // 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);
138
  CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
139
  // there must be at least 1 element in the properies store
140
  CHECK_LE(1, object->property_array()->length());
141
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
142 143 144 145 146 147 148 149 150 151 152
}


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());
153
  Handle<PropertyArray> empty_property_array(factory->empty_property_array());
154 155
  Handle<JSFunction> function =
      factory->NewFunctionForTest(factory->empty_string());
156 157 158
  Handle<Object> value(Smi::FromInt(42), isolate);

  Handle<JSObject> object = factory->NewJSObject(function);
159
  Handle<Map> previous_map(object->map(), isolate);
160
  CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
161 162
  CHECK(EQUALS(isolate, object->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
163 164 165 166 167 168 169

  // 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);
170
  CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
171
  CHECK(EQUALS(isolate, object->property_array(), empty_property_array));
172 173 174 175 176 177 178 179 180 181 182
  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);
183
  CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
184
  CHECK(EQUALS(isolate, object->property_array(), empty_property_array));
185 186 187 188 189 190 191 192 193
  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);
194
  CHECK_EQ(DICTIONARY_ELEMENTS, object->map()->elements_kind());
195
  CHECK(EQUALS(isolate, object->property_array(), empty_property_array));
196 197 198 199 200 201 202 203 204 205 206
  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());
207
  Handle<PropertyArray> empty_property_array(factory->empty_property_array());
208 209 210
  Handle<Object> value(Smi::FromInt(42), isolate);

  Handle<JSArray> array =
211
      factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
212
  Handle<Map> previous_map(array->map(), isolate);
213
  CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
214 215
  CHECK(EQUALS(isolate, array->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, array->elements(), empty_fixed_array));
jgruber's avatar
jgruber committed
216
  CHECK_EQ(0, Smi::ToInt(array->length()));
217 218 219 220 221 222 223 224

  // 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);
225
  CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
226
  CHECK_LE(1, array->property_array()->length());
227
  CHECK(EQUALS(isolate, array->elements(), empty_fixed_array));
jgruber's avatar
jgruber committed
228
  CHECK_EQ(0, Smi::ToInt(array->length()));
229 230 231 232 233 234 235 236 237 238 239
}


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());
240
  Handle<PropertyArray> empty_property_array(factory->empty_property_array());
241 242 243
  Handle<Object> value(Smi::FromInt(42), isolate);

  Handle<JSArray> array =
244
      factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
245
  Handle<Map> previous_map(array->map(), isolate);
246
  CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
247 248
  CHECK(EQUALS(isolate, array->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, array->elements(), empty_fixed_array));
jgruber's avatar
jgruber committed
249
  CHECK_EQ(0, Smi::ToInt(array->length()));
250 251 252 253 254 255 256

  // 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);
257
  CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
258
  CHECK(EQUALS(isolate, array->property_array(), empty_property_array));
259
  CHECK_LE(1, array->elements()->length());
jgruber's avatar
jgruber committed
260
  CHECK_EQ(1, Smi::ToInt(array->length()));
261 262 263 264 265 266 267 268 269 270

  // 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);
271
  CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
272
  CHECK(EQUALS(isolate, array->property_array(), empty_property_array));
273
  CHECK_LE(non_dict_backing_store_limit, array->elements()->length());
jgruber's avatar
jgruber committed
274
  CHECK_EQ(non_dict_backing_store_limit, Smi::ToInt(array->length()));
275 276 277 278 279 280 281 282 283

  // 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);
284
  CHECK_EQ(DICTIONARY_ELEMENTS, array->map()->elements_kind());
285
  CHECK(EQUALS(isolate, array->property_array(), empty_property_array));
286 287
  CHECK_LE(non_dict_backing_store_limit, array->elements()->length());
  CHECK_LE(array->elements()->length(), index);
jgruber's avatar
jgruber committed
288
  CHECK_EQ(index + 1, Smi::ToInt(array->length()));
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
}


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 =
304
      factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
305
  Handle<Map> previous_map(array->map(), isolate);
306
  CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
jgruber's avatar
jgruber committed
307
  CHECK_EQ(0, Smi::ToInt(array->length()));
308 309 310 311 312 313 314 315

  // `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);
316
  CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
317
  CHECK_EQ(1, Smi::ToInt(array->length()));
318 319 320

  // `delete array[0]` does not alter length, but changes the elments_kind
  name = MakeString("0");
neis's avatar
neis committed
321
  CHECK(JSReceiver::DeletePropertyOrElement(array, name).FromMaybe(false));
322
  CHECK_NE(array->map(), *previous_map);
323
  CHECK_EQ(HOLEY_SMI_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
324
  CHECK_EQ(1, Smi::ToInt(array->length()));
325
  previous_map = handle(array->map(), isolate);
326 327 328 329 330 331 332 333 334 335 336

  // 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);
337
  CHECK_EQ(HOLEY_SMI_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
338
  CHECK_EQ(2, Smi::ToInt(array->length()));
339 340 341 342 343 344 345

  // 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);
346
  CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
347
  CHECK_EQ(2, Smi::ToInt(array->length()));
348
  previous_map = handle(array->map(), isolate);
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376

  // 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 =
377
      factory->NewJSArray(ElementsKind::PACKED_ELEMENTS, 0, 0);
378
  Handle<Map> previous_map(array->map(), isolate);
379
  CHECK_EQ(PACKED_ELEMENTS, previous_map->elements_kind());
jgruber's avatar
jgruber committed
380
  CHECK_EQ(0, Smi::ToInt(array->length()));
381 382 383 384 385 386 387 388

  // `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);
389
  CHECK_EQ(PACKED_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
390
  CHECK_EQ(1, Smi::ToInt(array->length()));
391 392 393

  // `delete array[0]` does not alter length, but changes the elments_kind
  name = MakeString("0");
neis's avatar
neis committed
394
  CHECK(JSReceiver::DeletePropertyOrElement(array, name).FromMaybe(false));
395
  CHECK_NE(array->map(), *previous_map);
396
  CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
397
  CHECK_EQ(1, Smi::ToInt(array->length()));
398
  previous_map = handle(array->map(), isolate);
399 400 401 402 403 404 405 406 407 408 409

  // 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);
410
  CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
411
  CHECK_EQ(2, Smi::ToInt(array->length()));
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
}


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 =
427
      factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
428
  Handle<Map> previous_map(array->map(), isolate);
429

430
  // `array[0] = value_double` changes |elements_kind| to PACKED_DOUBLE_ELEMENTS
431 432 433 434 435
  name = MakeString("0");
  JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double,
                                                    NONE)
      .Check();
  CHECK_NE(array->map(), *previous_map);
436
  CHECK_EQ(PACKED_DOUBLE_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
437
  CHECK_EQ(1, Smi::ToInt(array->length()));
438
  previous_map = handle(array->map(), isolate);
439 440 441 442 443 444 445

  // `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);
446
  CHECK_EQ(PACKED_DOUBLE_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
447
  CHECK_EQ(2, Smi::ToInt(array->length()));
448 449 450

  // `delete array[0]` does not alter length, but changes the elments_kind
  name = MakeString("0");
neis's avatar
neis committed
451
  CHECK(JSReceiver::DeletePropertyOrElement(array, name).FromMaybe(false));
452
  CHECK_NE(array->map(), *previous_map);
453
  CHECK_EQ(HOLEY_DOUBLE_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
454
  CHECK_EQ(2, Smi::ToInt(array->length()));
455
  previous_map = handle(array->map(), isolate);
456 457 458 459 460 461 462

  // 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);
463
  CHECK_EQ(HOLEY_DOUBLE_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
464
  CHECK_EQ(2, Smi::ToInt(array->length()));
465

466
  // Adding a string to the array changes to elements_kind PACKED_ELEMENTS
467 468 469 470 471
  name = MakeString("1");
  JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string,
                                                    NONE)
      .Check();
  CHECK_NE(array->map(), *previous_map);
472
  CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
473
  CHECK_EQ(2, Smi::ToInt(array->length()));
474
  previous_map = handle(array->map(), isolate);
475 476 477 478 479 480 481 482

  // 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);
}
483

484
}  // namespace test_elements_kind
485 486
}  // namespace internal
}  // namespace v8