1// Copyright 2015 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <stdlib.h>
6#include <sstream>
7#include <utility>
8
9#include "src/api.h"
10#include "src/objects.h"
11#include "src/v8.h"
12
13#include "test/cctest/cctest.h"
14
15using namespace v8::base;
16using namespace v8::internal;
17
18
19static const int kMaxInobjectProperties =
20    (JSObject::kMaxInstanceSize - JSObject::kHeaderSize) >> kPointerSizeLog2;
21
22
23template <typename T>
24static Handle<T> OpenHandle(v8::Local<v8::Value> value) {
25  Handle<Object> obj = v8::Utils::OpenHandle(*value);
26  return Handle<T>::cast(obj);
27}
28
29
30static inline v8::Local<v8::Value> Run(v8::Local<v8::Script> script) {
31  v8::Local<v8::Value> result;
32  if (script->Run(v8::Isolate::GetCurrent()->GetCurrentContext())
33          .ToLocal(&result)) {
34    return result;
35  }
36  return v8::Local<v8::Value>();
37}
38
39
40template <typename T = Object>
41Handle<T> GetGlobal(const char* name) {
42  Isolate* isolate = CcTest::i_isolate();
43  Factory* factory = isolate->factory();
44  Handle<String> str_name = factory->InternalizeUtf8String(name);
45
46  Handle<Object> value =
47      Object::GetProperty(isolate->global_object(), str_name).ToHandleChecked();
48  return Handle<T>::cast(value);
49}
50
51
52template <typename T = Object>
53Handle<T> GetLexical(const char* name) {
54  Isolate* isolate = CcTest::i_isolate();
55  Factory* factory = isolate->factory();
56
57  Handle<String> str_name = factory->InternalizeUtf8String(name);
58  Handle<ScriptContextTable> script_contexts(
59      isolate->native_context()->script_context_table());
60
61  ScriptContextTable::LookupResult lookup_result;
62  if (ScriptContextTable::Lookup(script_contexts, str_name, &lookup_result)) {
63    Handle<Object> result =
64        FixedArray::get(*ScriptContextTable::GetContext(
65                            script_contexts, lookup_result.context_index),
66                        lookup_result.slot_index, isolate);
67    return Handle<T>::cast(result);
68  }
69  return Handle<T>();
70}
71
72
73template <typename T = Object>
74Handle<T> GetLexical(const std::string& name) {
75  return GetLexical<T>(name.c_str());
76}
77
78
79template <typename T>
80static inline Handle<T> Run(v8::Local<v8::Script> script) {
81  return OpenHandle<T>(Run(script));
82}
83
84
85template <typename T>
86static inline Handle<T> CompileRun(const char* script) {
87  return OpenHandle<T>(CompileRun(script));
88}
89
90
91static Object* GetFieldValue(JSObject* obj, int property_index) {
92  FieldIndex index = FieldIndex::ForPropertyIndex(obj->map(), property_index);
93  return obj->RawFastPropertyAt(index);
94}
95
96
97static double GetDoubleFieldValue(JSObject* obj, FieldIndex field_index) {
98  if (obj->IsUnboxedDoubleField(field_index)) {
99    return obj->RawFastDoublePropertyAt(field_index);
100  } else {
101    Object* value = obj->RawFastPropertyAt(field_index);
102    CHECK(value->IsMutableHeapNumber());
103    return HeapNumber::cast(value)->value();
104  }
105}
106
107
108static double GetDoubleFieldValue(JSObject* obj, int property_index) {
109  FieldIndex index = FieldIndex::ForPropertyIndex(obj->map(), property_index);
110  return GetDoubleFieldValue(obj, index);
111}
112
113
114bool IsObjectShrinkable(JSObject* obj) {
115  Handle<Map> filler_map =
116      CcTest::i_isolate()->factory()->one_pointer_filler_map();
117
118  int inobject_properties = obj->map()->GetInObjectProperties();
119  int unused = obj->map()->unused_property_fields();
120  if (unused == 0) return false;
121
122  for (int i = inobject_properties - unused; i < inobject_properties; i++) {
123    if (*filler_map != GetFieldValue(obj, i)) {
124      return false;
125    }
126  }
127  return true;
128}
129
130
131TEST(JSObjectBasic) {
132  // Avoid eventual completion of in-object slack tracking.
133  FLAG_inline_construct = false;
134  FLAG_always_opt = false;
135  CcTest::InitializeVM();
136  v8::HandleScope scope(CcTest::isolate());
137  const char* source =
138      "function A() {"
139      "  this.a = 42;"
140      "  this.d = 4.2;"
141      "  this.o = this;"
142      "}";
143  CompileRun(source);
144
145  Handle<JSFunction> func = GetGlobal<JSFunction>("A");
146
147  // Zero instances were created so far.
148  CHECK(!func->has_initial_map());
149
150  v8::Local<v8::Script> new_A_script = v8_compile("new A();");
151
152  Handle<JSObject> obj = Run<JSObject>(new_A_script);
153
154  CHECK(func->has_initial_map());
155  Handle<Map> initial_map(func->initial_map());
156
157  // One instance created.
158  CHECK_EQ(Map::kSlackTrackingCounterStart - 1,
159           initial_map->construction_counter());
160  CHECK(initial_map->IsInobjectSlackTrackingInProgress());
161
162  // There must be at least some slack.
163  CHECK_LT(5, obj->map()->GetInObjectProperties());
164  CHECK_EQ(Smi::FromInt(42), GetFieldValue(*obj, 0));
165  CHECK_EQ(4.2, GetDoubleFieldValue(*obj, 1));
166  CHECK_EQ(*obj, GetFieldValue(*obj, 2));
167  CHECK(IsObjectShrinkable(*obj));
168
169  // Create several objects to complete the tracking.
170  for (int i = 1; i < Map::kGenerousAllocationCount; i++) {
171    CHECK(initial_map->IsInobjectSlackTrackingInProgress());
172    Handle<JSObject> tmp = Run<JSObject>(new_A_script);
173    CHECK_EQ(initial_map->IsInobjectSlackTrackingInProgress(),
174             IsObjectShrinkable(*tmp));
175  }
176  CHECK(!initial_map->IsInobjectSlackTrackingInProgress());
177  CHECK(!IsObjectShrinkable(*obj));
178
179  // No slack left.
180  CHECK_EQ(3, obj->map()->GetInObjectProperties());
181}
182
183
184TEST(JSObjectBasicNoInlineNew) {
185  FLAG_inline_new = false;
186  TestJSObjectBasic();
187}
188
189
190TEST(JSObjectComplex) {
191  // Avoid eventual completion of in-object slack tracking.
192  FLAG_inline_construct = false;
193  FLAG_always_opt = false;
194  CcTest::InitializeVM();
195  v8::HandleScope scope(CcTest::isolate());
196  const char* source =
197      "function A(n) {"
198      "  if (n > 0) this.a = 42;"
199      "  if (n > 1) this.d = 4.2;"
200      "  if (n > 2) this.o1 = this;"
201      "  if (n > 3) this.o2 = this;"
202      "  if (n > 4) this.o3 = this;"
203      "  if (n > 5) this.o4 = this;"
204      "}";
205  CompileRun(source);
206
207  Handle<JSFunction> func = GetGlobal<JSFunction>("A");
208
209  // Zero instances were created so far.
210  CHECK(!func->has_initial_map());
211
212  Handle<JSObject> obj1 = CompileRun<JSObject>("new A(1);");
213  Handle<JSObject> obj3 = CompileRun<JSObject>("new A(3);");
214  Handle<JSObject> obj5 = CompileRun<JSObject>("new A(5);");
215
216  CHECK(func->has_initial_map());
217  Handle<Map> initial_map(func->initial_map());
218
219  // Three instances created.
220  CHECK_EQ(Map::kSlackTrackingCounterStart - 3,
221           initial_map->construction_counter());
222  CHECK(initial_map->IsInobjectSlackTrackingInProgress());
223
224  // There must be at least some slack.
225  CHECK_LT(5, obj3->map()->GetInObjectProperties());
226  CHECK_EQ(Smi::FromInt(42), GetFieldValue(*obj3, 0));
227  CHECK_EQ(4.2, GetDoubleFieldValue(*obj3, 1));
228  CHECK_EQ(*obj3, GetFieldValue(*obj3, 2));
229  CHECK(IsObjectShrinkable(*obj1));
230  CHECK(IsObjectShrinkable(*obj3));
231  CHECK(IsObjectShrinkable(*obj5));
232
233  // Create several objects to complete the tracking.
234  for (int i = 3; i < Map::kGenerousAllocationCount; i++) {
235    CHECK(initial_map->IsInobjectSlackTrackingInProgress());
236    CompileRun("new A(3);");
237  }
238  CHECK(!initial_map->IsInobjectSlackTrackingInProgress());
239
240  // obj1 and obj2 stays shrinkable because we don't clear unused fields.
241  CHECK(IsObjectShrinkable(*obj1));
242  CHECK(IsObjectShrinkable(*obj3));
243  CHECK(!IsObjectShrinkable(*obj5));
244
245  CHECK_EQ(5, obj1->map()->GetInObjectProperties());
246  CHECK_EQ(4, obj1->map()->unused_property_fields());
247
248  CHECK_EQ(5, obj3->map()->GetInObjectProperties());
249  CHECK_EQ(2, obj3->map()->unused_property_fields());
250
251  CHECK_EQ(5, obj5->map()->GetInObjectProperties());
252  CHECK_EQ(0, obj5->map()->unused_property_fields());
253
254  // Since slack tracking is complete, the new objects should not be shrinkable.
255  obj1 = CompileRun<JSObject>("new A(1);");
256  obj3 = CompileRun<JSObject>("new A(3);");
257  obj5 = CompileRun<JSObject>("new A(5);");
258
259  CHECK(!IsObjectShrinkable(*obj1));
260  CHECK(!IsObjectShrinkable(*obj3));
261  CHECK(!IsObjectShrinkable(*obj5));
262}
263
264
265TEST(JSObjectComplexNoInlineNew) {
266  FLAG_inline_new = false;
267  TestJSObjectComplex();
268}
269
270
271TEST(JSGeneratorObjectBasic) {
272  // Avoid eventual completion of in-object slack tracking.
273  FLAG_inline_construct = false;
274  FLAG_always_opt = false;
275  CcTest::InitializeVM();
276  v8::HandleScope scope(CcTest::isolate());
277  const char* source =
278      "function* A() {"
279      "  var i = 0;"
280      "  while(true) {"
281      "    yield i++;"
282      "  }"
283      "};"
284      "function CreateGenerator() {"
285      "  var o = A();"
286      "  o.a = 42;"
287      "  o.d = 4.2;"
288      "  o.o = o;"
289      "  return o;"
290      "}";
291  CompileRun(source);
292
293  Handle<JSFunction> func = GetGlobal<JSFunction>("A");
294
295  // Zero instances were created so far.
296  CHECK(!func->has_initial_map());
297
298  v8::Local<v8::Script> new_A_script = v8_compile("CreateGenerator();");
299
300  Handle<JSObject> obj = Run<JSObject>(new_A_script);
301
302  CHECK(func->has_initial_map());
303  Handle<Map> initial_map(func->initial_map());
304
305  // One instance created.
306  CHECK_EQ(Map::kSlackTrackingCounterStart - 1,
307           initial_map->construction_counter());
308  CHECK(initial_map->IsInobjectSlackTrackingInProgress());
309
310  // There must be at least some slack.
311  CHECK_LT(5, obj->map()->GetInObjectProperties());
312  CHECK_EQ(Smi::FromInt(42), GetFieldValue(*obj, 0));
313  CHECK_EQ(4.2, GetDoubleFieldValue(*obj, 1));
314  CHECK_EQ(*obj, GetFieldValue(*obj, 2));
315  CHECK(IsObjectShrinkable(*obj));
316
317  // Create several objects to complete the tracking.
318  for (int i = 1; i < Map::kGenerousAllocationCount; i++) {
319    CHECK(initial_map->IsInobjectSlackTrackingInProgress());
320    Handle<JSObject> tmp = Run<JSObject>(new_A_script);
321    CHECK_EQ(initial_map->IsInobjectSlackTrackingInProgress(),
322             IsObjectShrinkable(*tmp));
323  }
324  CHECK(!initial_map->IsInobjectSlackTrackingInProgress());
325  CHECK(!IsObjectShrinkable(*obj));
326
327  // No slack left.
328  CHECK_EQ(3, obj->map()->GetInObjectProperties());
329}
330
331
332TEST(JSGeneratorObjectBasicNoInlineNew) {
333  FLAG_inline_new = false;
334  TestJSGeneratorObjectBasic();
335}
336
337
338TEST(SubclassBasicNoBaseClassInstances) {
339  // Avoid eventual completion of in-object slack tracking.
340  FLAG_inline_construct = false;
341  FLAG_always_opt = false;
342  CcTest::InitializeVM();
343  v8::HandleScope scope(CcTest::isolate());
344
345  // Check that base class' and subclass' slack tracking do not interfere with
346  // each other.
347  // In this test we never create base class instances.
348
349  const char* source =
350      "'use strict';"
351      "class A {"
352      "  constructor(...args) {"
353      "    this.aa = 42;"
354      "    this.ad = 4.2;"
355      "    this.ao = this;"
356      "  }"
357      "};"
358      "class B extends A {"
359      "  constructor(...args) {"
360      "    super(...args);"
361      "    this.ba = 142;"
362      "    this.bd = 14.2;"
363      "    this.bo = this;"
364      "  }"
365      "};";
366  CompileRun(source);
367
368  Handle<JSFunction> a_func = GetLexical<JSFunction>("A");
369  Handle<JSFunction> b_func = GetLexical<JSFunction>("B");
370
371  // Zero instances were created so far.
372  CHECK(!a_func->has_initial_map());
373  CHECK(!b_func->has_initial_map());
374
375  v8::Local<v8::Script> new_B_script = v8_compile("new B();");
376
377  Handle<JSObject> obj = Run<JSObject>(new_B_script);
378
379  CHECK(a_func->has_initial_map());
380  Handle<Map> a_initial_map(a_func->initial_map());
381
382  CHECK(b_func->has_initial_map());
383  Handle<Map> b_initial_map(b_func->initial_map());
384
385  // Zero instances of A created.
386  CHECK_EQ(Map::kSlackTrackingCounterStart,
387           a_initial_map->construction_counter());
388  CHECK(a_initial_map->IsInobjectSlackTrackingInProgress());
389
390  // One instance of B created.
391  CHECK_EQ(Map::kSlackTrackingCounterStart - 1,
392           b_initial_map->construction_counter());
393  CHECK(b_initial_map->IsInobjectSlackTrackingInProgress());
394
395  // There must be at least some slack.
396  CHECK_LT(10, obj->map()->GetInObjectProperties());
397  CHECK_EQ(Smi::FromInt(42), GetFieldValue(*obj, 0));
398  CHECK_EQ(4.2, GetDoubleFieldValue(*obj, 1));
399  CHECK_EQ(*obj, GetFieldValue(*obj, 2));
400  CHECK_EQ(Smi::FromInt(142), GetFieldValue(*obj, 3));
401  CHECK_EQ(14.2, GetDoubleFieldValue(*obj, 4));
402  CHECK_EQ(*obj, GetFieldValue(*obj, 5));
403  CHECK(IsObjectShrinkable(*obj));
404
405  // Create several subclass instances to complete the tracking.
406  for (int i = 1; i < Map::kGenerousAllocationCount; i++) {
407    CHECK(b_initial_map->IsInobjectSlackTrackingInProgress());
408    Handle<JSObject> tmp = Run<JSObject>(new_B_script);
409    CHECK_EQ(b_initial_map->IsInobjectSlackTrackingInProgress(),
410             IsObjectShrinkable(*tmp));
411  }
412  CHECK(!b_initial_map->IsInobjectSlackTrackingInProgress());
413  CHECK(!IsObjectShrinkable(*obj));
414
415  // Zero instances of A created.
416  CHECK_EQ(Map::kSlackTrackingCounterStart,
417           a_initial_map->construction_counter());
418  CHECK(a_initial_map->IsInobjectSlackTrackingInProgress());
419
420  // No slack left.
421  CHECK_EQ(6, obj->map()->GetInObjectProperties());
422}
423
424
425TEST(SubclassBasicNoBaseClassInstancesNoInlineNew) {
426  FLAG_inline_new = false;
427  TestSubclassBasicNoBaseClassInstances();
428}
429
430
431TEST(SubclassBasic) {
432  // Avoid eventual completion of in-object slack tracking.
433  FLAG_inline_construct = false;
434  FLAG_always_opt = false;
435  CcTest::InitializeVM();
436  v8::HandleScope scope(CcTest::isolate());
437
438  // Check that base class' and subclass' slack tracking do not interfere with
439  // each other.
440  // In this test we first create enough base class instances to complete
441  // the slack tracking and then proceed creating subclass instances.
442
443  const char* source =
444      "'use strict';"
445      "class A {"
446      "  constructor(...args) {"
447      "    this.aa = 42;"
448      "    this.ad = 4.2;"
449      "    this.ao = this;"
450      "  }"
451      "};"
452      "class B extends A {"
453      "  constructor(...args) {"
454      "    super(...args);"
455      "    this.ba = 142;"
456      "    this.bd = 14.2;"
457      "    this.bo = this;"
458      "  }"
459      "};";
460  CompileRun(source);
461
462  Handle<JSFunction> a_func = GetLexical<JSFunction>("A");
463  Handle<JSFunction> b_func = GetLexical<JSFunction>("B");
464
465  // Zero instances were created so far.
466  CHECK(!a_func->has_initial_map());
467  CHECK(!b_func->has_initial_map());
468
469  v8::Local<v8::Script> new_A_script = v8_compile("new A();");
470  v8::Local<v8::Script> new_B_script = v8_compile("new B();");
471
472  Handle<JSObject> a_obj = Run<JSObject>(new_A_script);
473  Handle<JSObject> b_obj = Run<JSObject>(new_B_script);
474
475  CHECK(a_func->has_initial_map());
476  Handle<Map> a_initial_map(a_func->initial_map());
477
478  CHECK(b_func->has_initial_map());
479  Handle<Map> b_initial_map(b_func->initial_map());
480
481  // One instance of a base class created.
482  CHECK_EQ(Map::kSlackTrackingCounterStart - 1,
483           a_initial_map->construction_counter());
484  CHECK(a_initial_map->IsInobjectSlackTrackingInProgress());
485
486  // One instance of a subclass created.
487  CHECK_EQ(Map::kSlackTrackingCounterStart - 1,
488           b_initial_map->construction_counter());
489  CHECK(b_initial_map->IsInobjectSlackTrackingInProgress());
490
491  // Create several base class instances to complete the tracking.
492  for (int i = 1; i < Map::kGenerousAllocationCount; i++) {
493    CHECK(a_initial_map->IsInobjectSlackTrackingInProgress());
494    Handle<JSObject> tmp = Run<JSObject>(new_A_script);
495    CHECK_EQ(a_initial_map->IsInobjectSlackTrackingInProgress(),
496             IsObjectShrinkable(*tmp));
497  }
498  CHECK(!a_initial_map->IsInobjectSlackTrackingInProgress());
499  CHECK(!IsObjectShrinkable(*a_obj));
500
501  // No slack left.
502  CHECK_EQ(3, a_obj->map()->GetInObjectProperties());
503
504  // There must be at least some slack.
505  CHECK_LT(10, b_obj->map()->GetInObjectProperties());
506  CHECK_EQ(Smi::FromInt(42), GetFieldValue(*b_obj, 0));
507  CHECK_EQ(4.2, GetDoubleFieldValue(*b_obj, 1));
508  CHECK_EQ(*b_obj, GetFieldValue(*b_obj, 2));
509  CHECK_EQ(Smi::FromInt(142), GetFieldValue(*b_obj, 3));
510  CHECK_EQ(14.2, GetDoubleFieldValue(*b_obj, 4));
511  CHECK_EQ(*b_obj, GetFieldValue(*b_obj, 5));
512  CHECK(IsObjectShrinkable(*b_obj));
513
514  // Create several subclass instances to complete the tracking.
515  for (int i = 1; i < Map::kGenerousAllocationCount; i++) {
516    CHECK(b_initial_map->IsInobjectSlackTrackingInProgress());
517    Handle<JSObject> tmp = Run<JSObject>(new_B_script);
518    CHECK_EQ(b_initial_map->IsInobjectSlackTrackingInProgress(),
519             IsObjectShrinkable(*tmp));
520  }
521  CHECK(!b_initial_map->IsInobjectSlackTrackingInProgress());
522  CHECK(!IsObjectShrinkable(*b_obj));
523
524  // No slack left.
525  CHECK_EQ(6, b_obj->map()->GetInObjectProperties());
526}
527
528
529TEST(SubclassBasicNoInlineNew) {
530  FLAG_inline_new = false;
531  TestSubclassBasic();
532}
533
534
535// Creates class hierachy of length matching the |hierarchy_desc| length and
536// with the number of fields at i'th level equal to |hierarchy_desc[i]|.
537static void CreateClassHierarchy(const std::vector<int>& hierarchy_desc) {
538  std::ostringstream os;
539  os << "'use strict';\n\n";
540
541  int n = static_cast<int>(hierarchy_desc.size());
542  for (int cur_class = 0; cur_class < n; cur_class++) {
543    os << "class A" << cur_class;
544    if (cur_class > 0) {
545      os << " extends A" << (cur_class - 1);
546    }
547    os << " {\n"
548          "  constructor(...args) {\n";
549    if (cur_class > 0) {
550      os << "    super(...args);\n";
551    }
552    int fields_count = hierarchy_desc[cur_class];
553    for (int k = 0; k < fields_count; k++) {
554      os << "    this.f" << cur_class << "_" << k << " = " << k << ";\n";
555    }
556    os << "  }\n"
557          "};\n\n";
558  }
559  CompileRun(os.str().c_str());
560}
561
562
563static std::string GetClassName(int class_index) {
564  std::ostringstream os;
565  os << "A" << class_index;
566  return os.str();
567}
568
569
570static v8::Local<v8::Script> GetNewObjectScript(const std::string& class_name) {
571  std::ostringstream os;
572  os << "new " << class_name << "();";
573  return v8_compile(os.str().c_str());
574}
575
576
577// Test that in-object slack tracking works as expected for first |n| classes
578// in the hierarchy.
579// This test works only for if the total property count is less than maximum
580// in-object properties count.
581static void TestClassHierarchy(const std::vector<int>& hierarchy_desc, int n) {
582  int fields_count = 0;
583  for (int cur_class = 0; cur_class < n; cur_class++) {
584    std::string class_name = GetClassName(cur_class);
585    int fields_count_at_current_level = hierarchy_desc[cur_class];
586    fields_count += fields_count_at_current_level;
587
588    // This test is not suitable for in-object properties count overflow case.
589    CHECK_LT(fields_count, kMaxInobjectProperties);
590
591    // Create |class_name| objects and check slack tracking.
592    v8::Local<v8::Script> new_script = GetNewObjectScript(class_name);
593
594    Handle<JSFunction> func = GetLexical<JSFunction>(class_name);
595
596    Handle<JSObject> obj = Run<JSObject>(new_script);
597
598    CHECK(func->has_initial_map());
599    Handle<Map> initial_map(func->initial_map());
600
601    // There must be at least some slack.
602    CHECK_LT(fields_count, obj->map()->GetInObjectProperties());
603
604    // One instance was created.
605    CHECK_EQ(Map::kSlackTrackingCounterStart - 1,
606             initial_map->construction_counter());
607    CHECK(initial_map->IsInobjectSlackTrackingInProgress());
608
609    // Create several instances to complete the tracking.
610    for (int i = 1; i < Map::kGenerousAllocationCount; i++) {
611      CHECK(initial_map->IsInobjectSlackTrackingInProgress());
612      Handle<JSObject> tmp = Run<JSObject>(new_script);
613      CHECK_EQ(initial_map->IsInobjectSlackTrackingInProgress(),
614               IsObjectShrinkable(*tmp));
615      CHECK_EQ(Map::kSlackTrackingCounterStart - i - 1,
616               initial_map->construction_counter());
617    }
618    CHECK(!initial_map->IsInobjectSlackTrackingInProgress());
619    CHECK(!IsObjectShrinkable(*obj));
620
621    // No slack left.
622    CHECK_EQ(fields_count, obj->map()->GetInObjectProperties());
623  }
624}
625
626
627static void TestSubclassChain(const std::vector<int>& hierarchy_desc) {
628  // Avoid eventual completion of in-object slack tracking.
629  FLAG_inline_construct = false;
630  FLAG_always_opt = false;
631  CcTest::InitializeVM();
632  v8::HandleScope scope(CcTest::isolate());
633
634  CreateClassHierarchy(hierarchy_desc);
635  TestClassHierarchy(hierarchy_desc, static_cast<int>(hierarchy_desc.size()));
636}
637
638
639TEST(LongSubclassChain1) {
640  std::vector<int> hierarchy_desc;
641  for (int i = 0; i < 7; i++) {
642    hierarchy_desc.push_back(i * 10);
643  }
644  TestSubclassChain(hierarchy_desc);
645}
646
647
648TEST(LongSubclassChain2) {
649  std::vector<int> hierarchy_desc;
650  hierarchy_desc.push_back(10);
651  for (int i = 0; i < 42; i++) {
652    hierarchy_desc.push_back(0);
653  }
654  hierarchy_desc.push_back(230);
655  TestSubclassChain(hierarchy_desc);
656}
657
658
659TEST(LongSubclassChain3) {
660  std::vector<int> hierarchy_desc;
661  for (int i = 0; i < 42; i++) {
662    hierarchy_desc.push_back(5);
663  }
664  TestSubclassChain(hierarchy_desc);
665}
666
667
668TEST(InobjectPropetiesCountOverflowInSubclass) {
669  // Avoid eventual completion of in-object slack tracking.
670  FLAG_inline_construct = false;
671  FLAG_always_opt = false;
672  CcTest::InitializeVM();
673  v8::HandleScope scope(CcTest::isolate());
674
675  std::vector<int> hierarchy_desc;
676  const int kNoOverflowCount = 5;
677  for (int i = 0; i < kNoOverflowCount; i++) {
678    hierarchy_desc.push_back(50);
679  }
680  // In this class we are going to have properties in the backing store.
681  hierarchy_desc.push_back(100);
682
683  CreateClassHierarchy(hierarchy_desc);
684
685  // For the last class in the hierarchy we need different checks.
686  {
687    int cur_class = kNoOverflowCount;
688    std::string class_name = GetClassName(cur_class);
689
690    // Create |class_name| objects and check slack tracking.
691    v8::Local<v8::Script> new_script = GetNewObjectScript(class_name);
692
693    Handle<JSFunction> func = GetLexical<JSFunction>(class_name);
694
695    Handle<JSObject> obj = Run<JSObject>(new_script);
696
697    CHECK(func->has_initial_map());
698    Handle<Map> initial_map(func->initial_map());
699
700    // There must be no slack left.
701    CHECK_EQ(JSObject::kMaxInstanceSize, obj->map()->instance_size());
702    CHECK_EQ(kMaxInobjectProperties, obj->map()->GetInObjectProperties());
703
704    // One instance was created.
705    CHECK_EQ(Map::kSlackTrackingCounterStart - 1,
706             initial_map->construction_counter());
707    CHECK(initial_map->IsInobjectSlackTrackingInProgress());
708
709    // Create several instances to complete the tracking.
710    for (int i = 1; i < Map::kGenerousAllocationCount; i++) {
711      CHECK(initial_map->IsInobjectSlackTrackingInProgress());
712      Handle<JSObject> tmp = Run<JSObject>(new_script);
713      CHECK(!IsObjectShrinkable(*tmp));
714    }
715    CHECK(!initial_map->IsInobjectSlackTrackingInProgress());
716    CHECK(!IsObjectShrinkable(*obj));
717
718    // No slack left.
719    CHECK_EQ(kMaxInobjectProperties, obj->map()->GetInObjectProperties());
720  }
721
722  // The other classes in the hierarchy are not affected.
723  TestClassHierarchy(hierarchy_desc, kNoOverflowCount);
724}
725
726
727TEST(SlowModeSubclass) {
728  // Avoid eventual completion of in-object slack tracking.
729  FLAG_inline_construct = false;
730  FLAG_always_opt = false;
731  CcTest::InitializeVM();
732  v8::HandleScope scope(CcTest::isolate());
733
734  std::vector<int> hierarchy_desc;
735  const int kNoOverflowCount = 5;
736  for (int i = 0; i < kNoOverflowCount; i++) {
737    hierarchy_desc.push_back(50);
738  }
739  // This class should go dictionary mode.
740  hierarchy_desc.push_back(1000);
741
742  CreateClassHierarchy(hierarchy_desc);
743
744  // For the last class in the hierarchy we need different checks.
745  {
746    int cur_class = kNoOverflowCount;
747    std::string class_name = GetClassName(cur_class);
748
749    // Create |class_name| objects and check slack tracking.
750    v8::Local<v8::Script> new_script = GetNewObjectScript(class_name);
751
752    Handle<JSFunction> func = GetLexical<JSFunction>(class_name);
753
754    Handle<JSObject> obj = Run<JSObject>(new_script);
755
756    CHECK(func->has_initial_map());
757    Handle<Map> initial_map(func->initial_map());
758
759    // Object should go dictionary mode.
760    CHECK_EQ(JSObject::kHeaderSize, obj->map()->instance_size());
761    CHECK(obj->map()->is_dictionary_map());
762
763    // One instance was created.
764    CHECK_EQ(Map::kSlackTrackingCounterStart - 1,
765             initial_map->construction_counter());
766    CHECK(initial_map->IsInobjectSlackTrackingInProgress());
767
768    // Create several instances to complete the tracking.
769    for (int i = 1; i < Map::kGenerousAllocationCount; i++) {
770      CHECK(initial_map->IsInobjectSlackTrackingInProgress());
771      Handle<JSObject> tmp = Run<JSObject>(new_script);
772      CHECK(!IsObjectShrinkable(*tmp));
773    }
774    CHECK(!initial_map->IsInobjectSlackTrackingInProgress());
775    CHECK(!IsObjectShrinkable(*obj));
776
777    // Object should stay in dictionary mode.
778    CHECK_EQ(JSObject::kHeaderSize, obj->map()->instance_size());
779    CHECK(obj->map()->is_dictionary_map());
780  }
781
782  // The other classes in the hierarchy are not affected.
783  TestClassHierarchy(hierarchy_desc, kNoOverflowCount);
784}
785
786
787static void TestSubclassBuiltin(const char* subclass_name,
788                                InstanceType instance_type,
789                                const char* builtin_name,
790                                const char* ctor_arguments = "",
791                                int builtin_properties_count = 0) {
792  {
793    std::ostringstream os;
794    os << "'use strict';\n"
795          "class "
796       << subclass_name << " extends " << builtin_name
797       << " {\n"
798          "  constructor(...args) {\n"
799          "    super(...args);\n"
800          "    this.a = 42;\n"
801          "    this.d = 4.2;\n"
802          "    this.o = this;\n"
803          "  }\n"
804          "};\n";
805    CompileRun(os.str().c_str());
806  }
807
808  Handle<JSFunction> func = GetLexical<JSFunction>(subclass_name);
809
810  // Zero instances were created so far.
811  CHECK(!func->has_initial_map());
812
813  v8::Local<v8::Script> new_script;
814  {
815    std::ostringstream os;
816    os << "new " << subclass_name << "(" << ctor_arguments << ");";
817    new_script = v8_compile(os.str().c_str());
818  }
819
820  Run<JSObject>(new_script);
821
822  CHECK(func->has_initial_map());
823  Handle<Map> initial_map(func->initial_map());
824
825  CHECK_EQ(instance_type, initial_map->instance_type());
826
827  // One instance of a subclass created.
828  CHECK_EQ(Map::kSlackTrackingCounterStart - 1,
829           initial_map->construction_counter());
830  CHECK(initial_map->IsInobjectSlackTrackingInProgress());
831
832  // Create two instances in order to ensure that |obj|.o is a data field
833  // in case of Function subclassing.
834  Handle<JSObject> obj = Run<JSObject>(new_script);
835
836  // Two instances of a subclass created.
837  CHECK_EQ(Map::kSlackTrackingCounterStart - 2,
838           initial_map->construction_counter());
839  CHECK(initial_map->IsInobjectSlackTrackingInProgress());
840
841  // There must be at least some slack.
842  CHECK_LT(builtin_properties_count + 5, obj->map()->GetInObjectProperties());
843  CHECK_EQ(Smi::FromInt(42), GetFieldValue(*obj, builtin_properties_count + 0));
844  CHECK_EQ(4.2, GetDoubleFieldValue(*obj, builtin_properties_count + 1));
845  CHECK_EQ(*obj, GetFieldValue(*obj, builtin_properties_count + 2));
846  CHECK(IsObjectShrinkable(*obj));
847
848  // Create several subclass instances to complete the tracking.
849  for (int i = 2; i < Map::kGenerousAllocationCount; i++) {
850    CHECK(initial_map->IsInobjectSlackTrackingInProgress());
851    Handle<JSObject> tmp = Run<JSObject>(new_script);
852    CHECK_EQ(initial_map->IsInobjectSlackTrackingInProgress(),
853             IsObjectShrinkable(*tmp));
854  }
855  CHECK(!initial_map->IsInobjectSlackTrackingInProgress());
856  CHECK(!IsObjectShrinkable(*obj));
857
858  // No slack left.
859  CHECK_EQ(builtin_properties_count + 3, obj->map()->GetInObjectProperties());
860
861  CHECK_EQ(instance_type, obj->map()->instance_type());
862}
863
864
865TEST(SubclassObjectBuiltin) {
866  // Avoid eventual completion of in-object slack tracking.
867  FLAG_inline_construct = false;
868  FLAG_always_opt = false;
869  CcTest::InitializeVM();
870  v8::HandleScope scope(CcTest::isolate());
871
872  TestSubclassBuiltin("A1", JS_OBJECT_TYPE, "Object", "true");
873  TestSubclassBuiltin("A2", JS_OBJECT_TYPE, "Object", "42");
874  TestSubclassBuiltin("A3", JS_OBJECT_TYPE, "Object", "'some string'");
875}
876
877
878TEST(SubclassObjectBuiltinNoInlineNew) {
879  FLAG_inline_new = false;
880  TestSubclassObjectBuiltin();
881}
882
883
884TEST(SubclassFunctionBuiltin) {
885  // Avoid eventual completion of in-object slack tracking.
886  FLAG_inline_construct = false;
887  FLAG_always_opt = false;
888  CcTest::InitializeVM();
889  v8::HandleScope scope(CcTest::isolate());
890
891  TestSubclassBuiltin("A1", JS_FUNCTION_TYPE, "Function", "'return 153;'");
892  TestSubclassBuiltin("A2", JS_FUNCTION_TYPE, "Function", "'this.a = 44;'");
893}
894
895
896TEST(SubclassFunctionBuiltinNoInlineNew) {
897  FLAG_inline_new = false;
898  TestSubclassFunctionBuiltin();
899}
900
901
902TEST(SubclassBooleanBuiltin) {
903  // Avoid eventual completion of in-object slack tracking.
904  FLAG_inline_construct = false;
905  FLAG_always_opt = false;
906  CcTest::InitializeVM();
907  v8::HandleScope scope(CcTest::isolate());
908
909  TestSubclassBuiltin("A1", JS_VALUE_TYPE, "Boolean", "true");
910  TestSubclassBuiltin("A2", JS_VALUE_TYPE, "Boolean", "false");
911}
912
913
914TEST(SubclassBooleanBuiltinNoInlineNew) {
915  FLAG_inline_new = false;
916  TestSubclassBooleanBuiltin();
917}
918
919
920TEST(SubclassErrorBuiltin) {
921  // Avoid eventual completion of in-object slack tracking.
922  FLAG_inline_construct = false;
923  FLAG_always_opt = false;
924  CcTest::InitializeVM();
925  v8::HandleScope scope(CcTest::isolate());
926
927  const int first_field = 2;
928  TestSubclassBuiltin("A1", JS_ERROR_TYPE, "Error", "'err'", first_field);
929  TestSubclassBuiltin("A2", JS_ERROR_TYPE, "EvalError", "'err'", first_field);
930  TestSubclassBuiltin("A3", JS_ERROR_TYPE, "RangeError", "'err'", first_field);
931  TestSubclassBuiltin("A4", JS_ERROR_TYPE, "ReferenceError", "'err'",
932                      first_field);
933  TestSubclassBuiltin("A5", JS_ERROR_TYPE, "SyntaxError", "'err'", first_field);
934  TestSubclassBuiltin("A6", JS_ERROR_TYPE, "TypeError", "'err'", first_field);
935  TestSubclassBuiltin("A7", JS_ERROR_TYPE, "URIError", "'err'", first_field);
936}
937
938
939TEST(SubclassErrorBuiltinNoInlineNew) {
940  FLAG_inline_new = false;
941  TestSubclassErrorBuiltin();
942}
943
944
945TEST(SubclassNumberBuiltin) {
946  // Avoid eventual completion of in-object slack tracking.
947  FLAG_inline_construct = false;
948  FLAG_always_opt = false;
949  CcTest::InitializeVM();
950  v8::HandleScope scope(CcTest::isolate());
951
952  TestSubclassBuiltin("A1", JS_VALUE_TYPE, "Number", "42");
953  TestSubclassBuiltin("A2", JS_VALUE_TYPE, "Number", "4.2");
954}
955
956
957TEST(SubclassNumberBuiltinNoInlineNew) {
958  FLAG_inline_new = false;
959  TestSubclassNumberBuiltin();
960}
961
962
963TEST(SubclassDateBuiltin) {
964  // Avoid eventual completion of in-object slack tracking.
965  FLAG_inline_construct = false;
966  FLAG_always_opt = false;
967  CcTest::InitializeVM();
968  v8::HandleScope scope(CcTest::isolate());
969
970  TestSubclassBuiltin("A1", JS_DATE_TYPE, "Date", "123456789");
971}
972
973
974TEST(SubclassDateBuiltinNoInlineNew) {
975  FLAG_inline_new = false;
976  TestSubclassDateBuiltin();
977}
978
979
980TEST(SubclassStringBuiltin) {
981  // Avoid eventual completion of in-object slack tracking.
982  FLAG_inline_construct = false;
983  FLAG_always_opt = false;
984  CcTest::InitializeVM();
985  v8::HandleScope scope(CcTest::isolate());
986
987  TestSubclassBuiltin("A1", JS_VALUE_TYPE, "String", "'some string'");
988  TestSubclassBuiltin("A2", JS_VALUE_TYPE, "String", "");
989}
990
991
992TEST(SubclassStringBuiltinNoInlineNew) {
993  FLAG_inline_new = false;
994  TestSubclassStringBuiltin();
995}
996
997
998TEST(SubclassRegExpBuiltin) {
999  // Avoid eventual completion of in-object slack tracking.
1000  FLAG_inline_construct = false;
1001  FLAG_always_opt = false;
1002  CcTest::InitializeVM();
1003  v8::HandleScope scope(CcTest::isolate());
1004
1005  const int first_field = 1;
1006  TestSubclassBuiltin("A1", JS_REGEXP_TYPE, "RegExp", "'o(..)h', 'g'",
1007                      first_field);
1008}
1009
1010
1011TEST(SubclassRegExpBuiltinNoInlineNew) {
1012  FLAG_inline_new = false;
1013  TestSubclassRegExpBuiltin();
1014}
1015
1016
1017TEST(SubclassArrayBuiltin) {
1018  // Avoid eventual completion of in-object slack tracking.
1019  FLAG_inline_construct = false;
1020  FLAG_always_opt = false;
1021  CcTest::InitializeVM();
1022  v8::HandleScope scope(CcTest::isolate());
1023
1024  TestSubclassBuiltin("A1", JS_ARRAY_TYPE, "Array", "42");
1025}
1026
1027
1028TEST(SubclassArrayBuiltinNoInlineNew) {
1029  FLAG_inline_new = false;
1030  TestSubclassArrayBuiltin();
1031}
1032
1033
1034TEST(SubclassTypedArrayBuiltin) {
1035  // Avoid eventual completion of in-object slack tracking.
1036  FLAG_inline_construct = false;
1037  FLAG_always_opt = false;
1038  CcTest::InitializeVM();
1039  v8::HandleScope scope(CcTest::isolate());
1040
1041#define TYPED_ARRAY_TEST(Type, type, TYPE, elementType, size) \
1042  TestSubclassBuiltin("A" #Type, JS_TYPED_ARRAY_TYPE, #Type "Array", "42");
1043
1044  TYPED_ARRAYS(TYPED_ARRAY_TEST)
1045
1046#undef TYPED_ARRAY_TEST
1047}
1048
1049
1050TEST(SubclassTypedArrayBuiltinNoInlineNew) {
1051  FLAG_inline_new = false;
1052  TestSubclassTypedArrayBuiltin();
1053}
1054
1055
1056TEST(SubclassCollectionBuiltin) {
1057  // Avoid eventual completion of in-object slack tracking.
1058  FLAG_inline_construct = false;
1059  FLAG_always_opt = false;
1060  CcTest::InitializeVM();
1061  v8::HandleScope scope(CcTest::isolate());
1062
1063  TestSubclassBuiltin("A1", JS_SET_TYPE, "Set", "");
1064  TestSubclassBuiltin("A2", JS_MAP_TYPE, "Map", "");
1065  TestSubclassBuiltin("A3", JS_WEAK_SET_TYPE, "WeakSet", "");
1066  TestSubclassBuiltin("A4", JS_WEAK_MAP_TYPE, "WeakMap", "");
1067}
1068
1069
1070TEST(SubclassCollectionBuiltinNoInlineNew) {
1071  FLAG_inline_new = false;
1072  TestSubclassCollectionBuiltin();
1073}
1074
1075
1076TEST(SubclassArrayBufferBuiltin) {
1077  // Avoid eventual completion of in-object slack tracking.
1078  FLAG_inline_construct = false;
1079  FLAG_always_opt = false;
1080  CcTest::InitializeVM();
1081  v8::HandleScope scope(CcTest::isolate());
1082
1083  TestSubclassBuiltin("A1", JS_ARRAY_BUFFER_TYPE, "ArrayBuffer", "42");
1084  TestSubclassBuiltin("A2", JS_DATA_VIEW_TYPE, "DataView",
1085                      "new ArrayBuffer(42)");
1086}
1087
1088
1089TEST(SubclassArrayBufferBuiltinNoInlineNew) {
1090  FLAG_inline_new = false;
1091  TestSubclassArrayBufferBuiltin();
1092}
1093
1094
1095TEST(SubclassPromiseBuiltin) {
1096  // Avoid eventual completion of in-object slack tracking.
1097  FLAG_inline_construct = false;
1098  FLAG_always_opt = false;
1099  CcTest::InitializeVM();
1100  v8::HandleScope scope(CcTest::isolate());
1101
1102  const int first_field = 5;
1103  TestSubclassBuiltin("A1", JS_PROMISE_TYPE, "Promise",
1104                      "function(resolve, reject) { resolve('ok'); }",
1105                      first_field);
1106}
1107
1108
1109TEST(SubclassPromiseBuiltinNoInlineNew) {
1110  FLAG_inline_new = false;
1111  TestSubclassPromiseBuiltin();
1112}
1113