test-elements-kind.cc 19.1 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 47
  return Object::Equals(isolate, Handle<Object>::cast(left),
                        Handle<Object>::cast(right))
48 49 50 51
      .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
}

}  // namespace


//
// Tests
//

68 69 70 71 72 73
TEST(SystemPointerElementsKind) {
  CHECK_EQ(ElementsKindToShiftSize(SYSTEM_POINTER_ELEMENTS),
           kSystemPointerSizeLog2);
  CHECK_EQ(ElementsKindToByteSize(SYSTEM_POINTER_ELEMENTS), kSystemPointerSize);
}

74 75 76 77 78 79 80
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());
81
  Handle<PropertyArray> empty_property_array(factory->empty_property_array());
82 83
  Handle<JSFunction> function =
      factory->NewFunctionForTest(factory->empty_string());
84 85 86
  Handle<Object> value(Smi::FromInt(42), isolate);

  Handle<JSObject> object = factory->NewJSObject(function);
87
  Handle<Map> previous_map(object->map(), isolate);
88
  CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
89 90
  CHECK(EQUALS(isolate, object->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
91 92 93 94 95 96 97

  // 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);
98
  CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
99
  CHECK_LE(1, object->property_array()->length());
100
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
101 102 103 104 105 106 107 108 109 110
}


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

  // 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);
133
  CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
134 135
  CHECK(EQUALS(isolate, object->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
136 137 138 139 140 141 142 143

  // 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);
144
  CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
145
  // there must be at least 1 element in the properies store
146
  CHECK_LE(1, object->property_array()->length());
147
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
148 149 150 151 152 153 154 155 156 157 158
}


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());
159
  Handle<PropertyArray> empty_property_array(factory->empty_property_array());
160 161
  Handle<JSFunction> function =
      factory->NewFunctionForTest(factory->empty_string());
162 163 164
  Handle<Object> value(Smi::FromInt(42), isolate);

  Handle<JSObject> object = factory->NewJSObject(function);
165
  Handle<Map> previous_map(object->map(), isolate);
166
  CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
167 168
  CHECK(EQUALS(isolate, object->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, object->elements(), empty_fixed_array));
169 170 171 172 173 174 175

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

  Handle<JSArray> array =
217
      factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
218
  Handle<Map> previous_map(array->map(), isolate);
219
  CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
220 221
  CHECK(EQUALS(isolate, array->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, array->elements(), empty_fixed_array));
jgruber's avatar
jgruber committed
222
  CHECK_EQ(0, Smi::ToInt(array->length()));
223 224 225 226 227 228 229 230

  // 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);
231
  CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
232
  CHECK_LE(1, array->property_array()->length());
233
  CHECK(EQUALS(isolate, array->elements(), empty_fixed_array));
jgruber's avatar
jgruber committed
234
  CHECK_EQ(0, Smi::ToInt(array->length()));
235 236 237 238 239 240 241 242 243 244 245
}


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());
246
  Handle<PropertyArray> empty_property_array(factory->empty_property_array());
247 248 249
  Handle<Object> value(Smi::FromInt(42), isolate);

  Handle<JSArray> array =
250
      factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
251
  Handle<Map> previous_map(array->map(), isolate);
252
  CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
253 254
  CHECK(EQUALS(isolate, array->property_array(), empty_property_array));
  CHECK(EQUALS(isolate, array->elements(), empty_fixed_array));
jgruber's avatar
jgruber committed
255
  CHECK_EQ(0, Smi::ToInt(array->length()));
256 257 258 259 260 261 262

  // 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);
263
  CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
264
  CHECK(EQUALS(isolate, array->property_array(), empty_property_array));
265
  CHECK_LE(1, array->elements()->length());
jgruber's avatar
jgruber committed
266
  CHECK_EQ(1, Smi::ToInt(array->length()));
267 268 269 270 271 272 273 274 275 276

  // 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);
277
  CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
278
  CHECK(EQUALS(isolate, array->property_array(), empty_property_array));
279
  CHECK_LE(non_dict_backing_store_limit, array->elements()->length());
jgruber's avatar
jgruber committed
280
  CHECK_EQ(non_dict_backing_store_limit, Smi::ToInt(array->length()));
281 282 283 284 285 286 287 288 289

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


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 =
310
      factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
311
  Handle<Map> previous_map(array->map(), isolate);
312
  CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
jgruber's avatar
jgruber committed
313
  CHECK_EQ(0, Smi::ToInt(array->length()));
314 315 316 317 318 319 320 321

  // `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);
322
  CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
323
  CHECK_EQ(1, Smi::ToInt(array->length()));
324 325 326

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

  // 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);
343
  CHECK_EQ(HOLEY_SMI_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
344
  CHECK_EQ(2, Smi::ToInt(array->length()));
345 346 347 348 349 350 351

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

  // 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 =
383
      factory->NewJSArray(ElementsKind::PACKED_ELEMENTS, 0, 0);
384
  Handle<Map> previous_map(array->map(), isolate);
385
  CHECK_EQ(PACKED_ELEMENTS, previous_map->elements_kind());
jgruber's avatar
jgruber committed
386
  CHECK_EQ(0, Smi::ToInt(array->length()));
387 388 389 390 391 392 393 394

  // `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);
395
  CHECK_EQ(PACKED_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
396
  CHECK_EQ(1, Smi::ToInt(array->length()));
397 398 399

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

  // 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);
416
  CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
417
  CHECK_EQ(2, Smi::ToInt(array->length()));
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
}


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

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

  // `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);
452
  CHECK_EQ(PACKED_DOUBLE_ELEMENTS, array->map()->elements_kind());
jgruber's avatar
jgruber committed
453
  CHECK_EQ(2, Smi::ToInt(array->length()));
454 455 456

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

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

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

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

490
}  // namespace test_elements_kind
491 492
}  // namespace internal
}  // namespace v8