// 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 #include #include #include "src/api-inl.h" #include "src/objects-inl.h" #include "src/objects.h" #include "src/v8.h" #include "test/cctest/cctest.h" namespace v8 { namespace internal { namespace test_inobject_slack_tracking { static const int kMaxInobjectProperties = JSObject::kMaxInObjectProperties; template static Handle OpenHandle(v8::Local value) { Handle obj = v8::Utils::OpenHandle(*value); return Handle::cast(obj); } static inline v8::Local Run(v8::Local script) { v8::Local result; if (script->Run(v8::Isolate::GetCurrent()->GetCurrentContext()) .ToLocal(&result)) { return result; } return v8::Local(); } template Handle GetLexical(const char* name) { Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); Handle str_name = factory->InternalizeUtf8String(name); Handle script_contexts( isolate->native_context()->script_context_table(), isolate); ScriptContextTable::LookupResult lookup_result; if (ScriptContextTable::Lookup(isolate, script_contexts, str_name, &lookup_result)) { Handle result = FixedArray::get( *ScriptContextTable::GetContext(isolate, script_contexts, lookup_result.context_index), lookup_result.slot_index, isolate); return Handle::cast(result); } return Handle(); } template Handle GetLexical(const std::string& name) { return GetLexical(name.c_str()); } template static inline Handle RunI(v8::Local script) { return OpenHandle(Run(script)); } template static inline Handle CompileRunI(const char* script) { return OpenHandle(CompileRun(script)); } static Object* GetFieldValue(JSObject* obj, int property_index) { FieldIndex index = FieldIndex::ForPropertyIndex(obj->map(), property_index); return obj->RawFastPropertyAt(index); } static double GetDoubleFieldValue(JSObject* obj, FieldIndex field_index) { if (obj->IsUnboxedDoubleField(field_index)) { return obj->RawFastDoublePropertyAt(field_index); } else { Object* value = obj->RawFastPropertyAt(field_index); CHECK(value->IsMutableHeapNumber()); return MutableHeapNumber::cast(value)->value(); } } static double GetDoubleFieldValue(JSObject* obj, int property_index) { FieldIndex index = FieldIndex::ForPropertyIndex(obj->map(), property_index); return GetDoubleFieldValue(obj, index); } bool IsObjectShrinkable(JSObject* obj) { Handle filler_map = CcTest::i_isolate()->factory()->one_pointer_filler_map(); int inobject_properties = obj->map()->GetInObjectProperties(); int unused = obj->map()->UnusedPropertyFields(); if (unused == 0) return false; for (int i = inobject_properties - unused; i < inobject_properties; i++) { if (*filler_map != GetFieldValue(obj, i)) { return false; } } return true; } TEST(JSObjectBasic) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); const char* source = "function A() {" " this.a = 42;" " this.d = 4.2;" " this.o = this;" "}"; CompileRun(source); Handle func = GetGlobal("A"); // Zero instances were created so far. CHECK(!func->has_initial_map()); v8::Local new_A_script = v8_compile("new A();"); Handle obj = RunI(new_A_script); CHECK(func->has_initial_map()); Handle initial_map(func->initial_map(), func->GetIsolate()); // One instance created. CHECK_EQ(Map::kSlackTrackingCounterStart - 1, initial_map->construction_counter()); CHECK(initial_map->IsInobjectSlackTrackingInProgress()); // There must be at least some slack. CHECK_LT(5, obj->map()->GetInObjectProperties()); CHECK_EQ(Smi::FromInt(42), GetFieldValue(*obj, 0)); CHECK_EQ(4.2, GetDoubleFieldValue(*obj, 1)); CHECK_EQ(*obj, GetFieldValue(*obj, 2)); CHECK(IsObjectShrinkable(*obj)); // Create several objects to complete the tracking. for (int i = 1; i < Map::kGenerousAllocationCount; i++) { CHECK(initial_map->IsInobjectSlackTrackingInProgress()); Handle tmp = RunI(new_A_script); CHECK_EQ(initial_map->IsInobjectSlackTrackingInProgress(), IsObjectShrinkable(*tmp)); } CHECK(!initial_map->IsInobjectSlackTrackingInProgress()); CHECK(!IsObjectShrinkable(*obj)); // No slack left. CHECK_EQ(3, obj->map()->GetInObjectProperties()); } TEST(JSObjectBasicNoInlineNew) { FLAG_inline_new = false; TestJSObjectBasic(); } TEST(JSObjectComplex) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); const char* source = "function A(n) {" " if (n > 0) this.a = 42;" " if (n > 1) this.d = 4.2;" " if (n > 2) this.o1 = this;" " if (n > 3) this.o2 = this;" " if (n > 4) this.o3 = this;" " if (n > 5) this.o4 = this;" "}"; CompileRun(source); Handle func = GetGlobal("A"); // Zero instances were created so far. CHECK(!func->has_initial_map()); Handle obj1 = CompileRunI("new A(1);"); Handle obj3 = CompileRunI("new A(3);"); Handle obj5 = CompileRunI("new A(5);"); CHECK(func->has_initial_map()); Handle initial_map(func->initial_map(), func->GetIsolate()); // Three instances created. CHECK_EQ(Map::kSlackTrackingCounterStart - 3, initial_map->construction_counter()); CHECK(initial_map->IsInobjectSlackTrackingInProgress()); // There must be at least some slack. CHECK_LT(5, obj3->map()->GetInObjectProperties()); CHECK_EQ(Smi::FromInt(42), GetFieldValue(*obj3, 0)); CHECK_EQ(4.2, GetDoubleFieldValue(*obj3, 1)); CHECK_EQ(*obj3, GetFieldValue(*obj3, 2)); CHECK(IsObjectShrinkable(*obj1)); CHECK(IsObjectShrinkable(*obj3)); CHECK(IsObjectShrinkable(*obj5)); // Create several objects to complete the tracking. for (int i = 3; i < Map::kGenerousAllocationCount; i++) { CHECK(initial_map->IsInobjectSlackTrackingInProgress()); CompileRun("new A(3);"); } CHECK(!initial_map->IsInobjectSlackTrackingInProgress()); // obj1 and obj2 stays shrinkable because we don't clear unused fields. CHECK(IsObjectShrinkable(*obj1)); CHECK(IsObjectShrinkable(*obj3)); CHECK(!IsObjectShrinkable(*obj5)); CHECK_EQ(5, obj1->map()->GetInObjectProperties()); CHECK_EQ(4, obj1->map()->UnusedPropertyFields()); CHECK_EQ(5, obj3->map()->GetInObjectProperties()); CHECK_EQ(2, obj3->map()->UnusedPropertyFields()); CHECK_EQ(5, obj5->map()->GetInObjectProperties()); CHECK_EQ(0, obj5->map()->UnusedPropertyFields()); // Since slack tracking is complete, the new objects should not be shrinkable. obj1 = CompileRunI("new A(1);"); obj3 = CompileRunI("new A(3);"); obj5 = CompileRunI("new A(5);"); CHECK(!IsObjectShrinkable(*obj1)); CHECK(!IsObjectShrinkable(*obj3)); CHECK(!IsObjectShrinkable(*obj5)); } TEST(JSObjectComplexNoInlineNew) { FLAG_inline_new = false; TestJSObjectComplex(); } TEST(JSGeneratorObjectBasic) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); const char* source = "function* A() {" " var i = 0;" " while(true) {" " yield i++;" " }" "};" "function CreateGenerator() {" " var o = A();" " o.a = 42;" " o.d = 4.2;" " o.o = o;" " return o;" "}"; CompileRun(source); Handle func = GetGlobal("A"); // Zero instances were created so far. CHECK(!func->has_initial_map()); v8::Local new_A_script = v8_compile("CreateGenerator();"); Handle obj = RunI(new_A_script); CHECK(func->has_initial_map()); Handle initial_map(func->initial_map(), func->GetIsolate()); // One instance created. CHECK_EQ(Map::kSlackTrackingCounterStart - 1, initial_map->construction_counter()); CHECK(initial_map->IsInobjectSlackTrackingInProgress()); // There must be at least some slack. CHECK_LT(5, obj->map()->GetInObjectProperties()); CHECK_EQ(Smi::FromInt(42), GetFieldValue(*obj, 0)); CHECK_EQ(4.2, GetDoubleFieldValue(*obj, 1)); CHECK_EQ(*obj, GetFieldValue(*obj, 2)); CHECK(IsObjectShrinkable(*obj)); // Create several objects to complete the tracking. for (int i = 1; i < Map::kGenerousAllocationCount; i++) { CHECK(initial_map->IsInobjectSlackTrackingInProgress()); Handle tmp = RunI(new_A_script); CHECK_EQ(initial_map->IsInobjectSlackTrackingInProgress(), IsObjectShrinkable(*tmp)); } CHECK(!initial_map->IsInobjectSlackTrackingInProgress()); CHECK(!IsObjectShrinkable(*obj)); // No slack left. CHECK_EQ(3, obj->map()->GetInObjectProperties()); } TEST(JSGeneratorObjectBasicNoInlineNew) { FLAG_inline_new = false; TestJSGeneratorObjectBasic(); } TEST(SubclassBasicNoBaseClassInstances) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); // Check that base class' and subclass' slack tracking do not interfere with // each other. // In this test we never create base class instances. const char* source = "'use strict';" "class A {" " constructor(...args) {" " this.aa = 42;" " this.ad = 4.2;" " this.ao = this;" " }" "};" "class B extends A {" " constructor(...args) {" " super(...args);" " this.ba = 142;" " this.bd = 14.2;" " this.bo = this;" " }" "};"; CompileRun(source); Handle a_func = GetLexical("A"); Handle b_func = GetLexical("B"); // Zero instances were created so far. CHECK(!a_func->has_initial_map()); CHECK(!b_func->has_initial_map()); v8::Local new_B_script = v8_compile("new B();"); Handle obj = RunI(new_B_script); CHECK(a_func->has_initial_map()); Handle a_initial_map(a_func->initial_map(), a_func->GetIsolate()); CHECK(b_func->has_initial_map()); Handle b_initial_map(b_func->initial_map(), a_func->GetIsolate()); // Zero instances of A created. CHECK_EQ(Map::kSlackTrackingCounterStart, a_initial_map->construction_counter()); CHECK(a_initial_map->IsInobjectSlackTrackingInProgress()); // One instance of B created. CHECK_EQ(Map::kSlackTrackingCounterStart - 1, b_initial_map->construction_counter()); CHECK(b_initial_map->IsInobjectSlackTrackingInProgress()); // There must be at least some slack. CHECK_LT(10, obj->map()->GetInObjectProperties()); CHECK_EQ(Smi::FromInt(42), GetFieldValue(*obj, 0)); CHECK_EQ(4.2, GetDoubleFieldValue(*obj, 1)); CHECK_EQ(*obj, GetFieldValue(*obj, 2)); CHECK_EQ(Smi::FromInt(142), GetFieldValue(*obj, 3)); CHECK_EQ(14.2, GetDoubleFieldValue(*obj, 4)); CHECK_EQ(*obj, GetFieldValue(*obj, 5)); CHECK(IsObjectShrinkable(*obj)); // Create several subclass instances to complete the tracking. for (int i = 1; i < Map::kGenerousAllocationCount; i++) { CHECK(b_initial_map->IsInobjectSlackTrackingInProgress()); Handle tmp = RunI(new_B_script); CHECK_EQ(b_initial_map->IsInobjectSlackTrackingInProgress(), IsObjectShrinkable(*tmp)); } CHECK(!b_initial_map->IsInobjectSlackTrackingInProgress()); CHECK(!IsObjectShrinkable(*obj)); // Zero instances of A created. CHECK_EQ(Map::kSlackTrackingCounterStart, a_initial_map->construction_counter()); CHECK(a_initial_map->IsInobjectSlackTrackingInProgress()); // No slack left. CHECK_EQ(6, obj->map()->GetInObjectProperties()); } TEST(SubclassBasicNoBaseClassInstancesNoInlineNew) { FLAG_inline_new = false; TestSubclassBasicNoBaseClassInstances(); } TEST(SubclassBasic) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); // Check that base class' and subclass' slack tracking do not interfere with // each other. // In this test we first create enough base class instances to complete // the slack tracking and then proceed creating subclass instances. const char* source = "'use strict';" "class A {" " constructor(...args) {" " this.aa = 42;" " this.ad = 4.2;" " this.ao = this;" " }" "};" "class B extends A {" " constructor(...args) {" " super(...args);" " this.ba = 142;" " this.bd = 14.2;" " this.bo = this;" " }" "};"; CompileRun(source); Handle a_func = GetLexical("A"); Handle b_func = GetLexical("B"); // Zero instances were created so far. CHECK(!a_func->has_initial_map()); CHECK(!b_func->has_initial_map()); v8::Local new_A_script = v8_compile("new A();"); v8::Local new_B_script = v8_compile("new B();"); Handle a_obj = RunI(new_A_script); Handle b_obj = RunI(new_B_script); CHECK(a_func->has_initial_map()); Handle a_initial_map(a_func->initial_map(), a_func->GetIsolate()); CHECK(b_func->has_initial_map()); Handle b_initial_map(b_func->initial_map(), a_func->GetIsolate()); // One instance of a base class created. CHECK_EQ(Map::kSlackTrackingCounterStart - 1, a_initial_map->construction_counter()); CHECK(a_initial_map->IsInobjectSlackTrackingInProgress()); // One instance of a subclass created. CHECK_EQ(Map::kSlackTrackingCounterStart - 1, b_initial_map->construction_counter()); CHECK(b_initial_map->IsInobjectSlackTrackingInProgress()); // Create several base class instances to complete the tracking. for (int i = 1; i < Map::kGenerousAllocationCount; i++) { CHECK(a_initial_map->IsInobjectSlackTrackingInProgress()); Handle tmp = RunI(new_A_script); CHECK_EQ(a_initial_map->IsInobjectSlackTrackingInProgress(), IsObjectShrinkable(*tmp)); } CHECK(!a_initial_map->IsInobjectSlackTrackingInProgress()); CHECK(!IsObjectShrinkable(*a_obj)); // No slack left. CHECK_EQ(3, a_obj->map()->GetInObjectProperties()); // There must be at least some slack. CHECK_LT(10, b_obj->map()->GetInObjectProperties()); CHECK_EQ(Smi::FromInt(42), GetFieldValue(*b_obj, 0)); CHECK_EQ(4.2, GetDoubleFieldValue(*b_obj, 1)); CHECK_EQ(*b_obj, GetFieldValue(*b_obj, 2)); CHECK_EQ(Smi::FromInt(142), GetFieldValue(*b_obj, 3)); CHECK_EQ(14.2, GetDoubleFieldValue(*b_obj, 4)); CHECK_EQ(*b_obj, GetFieldValue(*b_obj, 5)); CHECK(IsObjectShrinkable(*b_obj)); // Create several subclass instances to complete the tracking. for (int i = 1; i < Map::kGenerousAllocationCount; i++) { CHECK(b_initial_map->IsInobjectSlackTrackingInProgress()); Handle tmp = RunI(new_B_script); CHECK_EQ(b_initial_map->IsInobjectSlackTrackingInProgress(), IsObjectShrinkable(*tmp)); } CHECK(!b_initial_map->IsInobjectSlackTrackingInProgress()); CHECK(!IsObjectShrinkable(*b_obj)); // No slack left. CHECK_EQ(6, b_obj->map()->GetInObjectProperties()); } TEST(SubclassBasicNoInlineNew) { FLAG_inline_new = false; TestSubclassBasic(); } // Creates class hierarchy of length matching the |hierarchy_desc| length and // with the number of fields at i'th level equal to |hierarchy_desc[i]|. static void CreateClassHierarchy(const std::vector& hierarchy_desc) { std::ostringstream os; os << "'use strict';\n\n"; int n = static_cast(hierarchy_desc.size()); for (int cur_class = 0; cur_class < n; cur_class++) { os << "class A" << cur_class; if (cur_class > 0) { os << " extends A" << (cur_class - 1); } os << " {\n" " constructor(...args) {\n"; if (cur_class > 0) { os << " super(...args);\n"; } int fields_count = hierarchy_desc[cur_class]; for (int k = 0; k < fields_count; k++) { os << " this.f" << cur_class << "_" << k << " = " << k << ";\n"; } os << " }\n" "};\n\n"; } CompileRun(os.str().c_str()); } static std::string GetClassName(int class_index) { std::ostringstream os; os << "A" << class_index; return os.str(); } static v8::Local GetNewObjectScript(const std::string& class_name) { std::ostringstream os; os << "new " << class_name << "();"; return v8_compile(os.str().c_str()); } // Test that in-object slack tracking works as expected for first |n| classes // in the hierarchy. // This test works only for if the total property count is less than maximum // in-object properties count. static void TestClassHierarchy(const std::vector& hierarchy_desc, int n) { int fields_count = 0; for (int cur_class = 0; cur_class < n; cur_class++) { std::string class_name = GetClassName(cur_class); int fields_count_at_current_level = hierarchy_desc[cur_class]; fields_count += fields_count_at_current_level; // This test is not suitable for in-object properties count overflow case. CHECK_LT(fields_count, kMaxInobjectProperties); // Create |class_name| objects and check slack tracking. v8::Local new_script = GetNewObjectScript(class_name); Handle func = GetLexical(class_name); Handle obj = RunI(new_script); CHECK(func->has_initial_map()); Handle initial_map(func->initial_map(), func->GetIsolate()); // If the object is slow-mode already, bail out. if (obj->map()->is_dictionary_map()) continue; // There must be at least some slack. CHECK_LT(fields_count, obj->map()->GetInObjectProperties()); // One instance was created. CHECK_EQ(Map::kSlackTrackingCounterStart - 1, initial_map->construction_counter()); CHECK(initial_map->IsInobjectSlackTrackingInProgress()); // Create several instances to complete the tracking. for (int i = 1; i < Map::kGenerousAllocationCount; i++) { CHECK(initial_map->IsInobjectSlackTrackingInProgress()); Handle tmp = RunI(new_script); CHECK_EQ(initial_map->IsInobjectSlackTrackingInProgress(), IsObjectShrinkable(*tmp)); if (!initial_map->IsInobjectSlackTrackingInProgress()) { // Turbofan can force completion of in-object slack tracking. break; } CHECK_EQ(Map::kSlackTrackingCounterStart - i - 1, initial_map->construction_counter()); } CHECK(!initial_map->IsInobjectSlackTrackingInProgress()); CHECK(!IsObjectShrinkable(*obj)); // No slack left. CHECK_EQ(fields_count, obj->map()->GetInObjectProperties()); } } static void TestSubclassChain(const std::vector& hierarchy_desc) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); CreateClassHierarchy(hierarchy_desc); TestClassHierarchy(hierarchy_desc, static_cast(hierarchy_desc.size())); } TEST(Subclasses) { std::vector hierarchy_desc; hierarchy_desc.push_back(50); hierarchy_desc.push_back(128); TestSubclassChain(hierarchy_desc); } TEST(LongSubclassChain1) { std::vector hierarchy_desc; for (int i = 0; i < 7; i++) { hierarchy_desc.push_back(i * 10); } TestSubclassChain(hierarchy_desc); } TEST(LongSubclassChain2) { std::vector hierarchy_desc; hierarchy_desc.push_back(10); for (int i = 0; i < 42; i++) { hierarchy_desc.push_back(0); } hierarchy_desc.push_back(230); TestSubclassChain(hierarchy_desc); } TEST(LongSubclassChain3) { std::vector hierarchy_desc; for (int i = 0; i < 42; i++) { hierarchy_desc.push_back(5); } TestSubclassChain(hierarchy_desc); } TEST(InobjectPropetiesCountOverflowInSubclass) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); std::vector hierarchy_desc; const int kNoOverflowCount = 5; for (int i = 0; i < kNoOverflowCount; i++) { hierarchy_desc.push_back(50); } // In this class we are going to have properties in the backing store. hierarchy_desc.push_back(100); CreateClassHierarchy(hierarchy_desc); // For the last class in the hierarchy we need different checks. { int cur_class = kNoOverflowCount; std::string class_name = GetClassName(cur_class); // Create |class_name| objects and check slack tracking. v8::Local new_script = GetNewObjectScript(class_name); Handle func = GetLexical(class_name); Handle obj = RunI(new_script); CHECK(func->has_initial_map()); Handle initial_map(func->initial_map(), func->GetIsolate()); // There must be no slack left. CHECK_EQ(JSObject::kMaxInstanceSize, obj->map()->instance_size()); CHECK_EQ(kMaxInobjectProperties, obj->map()->GetInObjectProperties()); // One instance was created. CHECK_EQ(Map::kSlackTrackingCounterStart - 1, initial_map->construction_counter()); CHECK(initial_map->IsInobjectSlackTrackingInProgress()); // Create several instances to complete the tracking. for (int i = 1; i < Map::kGenerousAllocationCount; i++) { CHECK(initial_map->IsInobjectSlackTrackingInProgress()); Handle tmp = RunI(new_script); CHECK(!IsObjectShrinkable(*tmp)); } CHECK(!initial_map->IsInobjectSlackTrackingInProgress()); CHECK(!IsObjectShrinkable(*obj)); // No slack left. CHECK_EQ(kMaxInobjectProperties, obj->map()->GetInObjectProperties()); } // The other classes in the hierarchy are not affected. TestClassHierarchy(hierarchy_desc, kNoOverflowCount); } static void CheckExpectedProperties(int expected, std::ostringstream& os) { Handle obj = Handle::cast( v8::Utils::OpenHandle(*CompileRun(os.str().c_str()))); CHECK_EQ(expected, obj->map()->GetInObjectProperties()); } TEST(ObjectLiteralPropertyBackingStoreSize) { v8::HandleScope scope(CcTest::isolate()); LocalContext env; std::ostringstream os; // An index key does not require space in the property backing store. os << "(function() {\n" " function f() {\n" " var o = {\n" " '-1': 42,\n" // Allocate for non-index key. " 1: 42,\n" // Do not allocate for index key. " '2': 42\n" // Do not allocate for index key. " };\n" " return o;\n" " }\n" "\n" " return f();\n" "} )();"; CheckExpectedProperties(1, os); // Avoid over-/under-allocation for computed property names. os << "(function() {\n" " 'use strict';\n" " function f(x) {\n" " var o = {\n" " 1: 42,\n" // Do not allocate for index key. " '2': 42,\n" // Do not allocate for index key. " [x]: 42,\n" // Allocate for property with computed name. " 3: 42,\n" // Do not allocate for index key. " '4': 42\n" // Do not allocate for index key. " };\n" " return o;\n" " }\n" "\n" " var x = 'hello'\n" "\n" " return f(x);\n" "} )();"; CheckExpectedProperties(1, os); // Conversion to index key. os << "(function() {\n" " function f(x) {\n" " var o = {\n" " 1: 42,\n" // Do not allocate for index key. " '2': 42,\n" // Do not allocate for index key. " [x]: 42,\n" // Allocate for property with computed name. " 3: 42,\n" // Do not allocate for index key. " get 12() {}\n" // Do not allocate for index key. " };\n" " return o;\n" " }\n" "\n" " var x = 'hello'\n" "\n" " return f(x);\n" "} )();"; CheckExpectedProperties(1, os); os << "(function() {\n" " function f() {\n" " var o = {};\n" " return o;\n" " }\n" "\n" " return f();\n" "} )();"; // Empty objects have slack for 4 properties. CheckExpectedProperties(4, os); os << "(function() {\n" " function f(x) {\n" " var o = {\n" " a: 42,\n" // Allocate for constant property. " [x]: 42,\n" // Allocate for property with computed name. " b: 42\n" // Allocate for constant property. " };\n" " return o;\n" " }\n" "\n" " var x = 'hello'\n" "\n" " return f(x);\n" "} )();"; CheckExpectedProperties(3, os); os << "(function() {\n" " function f(x) {\n" " var o = {\n" " a: 42,\n" // Allocate for constant property. " __proto__: 42,\n" // Do not allocate for __proto__. " [x]: 42\n" // Allocate for property with computed name. " };\n" " return o;\n" " }\n" "\n" " var x = 'hello'\n" "\n" " return f(x);\n" "} )();"; // __proto__ is not allocated in the backing store. CheckExpectedProperties(2, os); os << "(function() {\n" " function f(x) {\n" " var o = {\n" " a: 42,\n" // Allocate for constant property. " [x]: 42,\n" // Allocate for property with computed name. " __proto__: 42\n" // Do not allocate for __proto__. " };\n" " return o;\n" " }\n" "\n" " var x = 'hello'\n" "\n" " return f(x);\n" "} )();"; CheckExpectedProperties(2, os); } TEST(SlowModeSubclass) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); std::vector hierarchy_desc; const int kNoOverflowCount = 5; for (int i = 0; i < kNoOverflowCount; i++) { hierarchy_desc.push_back(50); } // This class should go dictionary mode. hierarchy_desc.push_back(1000); CreateClassHierarchy(hierarchy_desc); // For the last class in the hierarchy we need different checks. { int cur_class = kNoOverflowCount; std::string class_name = GetClassName(cur_class); // Create |class_name| objects and check slack tracking. v8::Local new_script = GetNewObjectScript(class_name); Handle func = GetLexical(class_name); Handle obj = RunI(new_script); CHECK(func->has_initial_map()); Handle initial_map(func->initial_map(), func->GetIsolate()); // Object should go dictionary mode. CHECK_EQ(JSObject::kHeaderSize, obj->map()->instance_size()); CHECK(obj->map()->is_dictionary_map()); // One instance was created. CHECK_EQ(Map::kSlackTrackingCounterStart - 1, initial_map->construction_counter()); CHECK(initial_map->IsInobjectSlackTrackingInProgress()); // Create several instances to complete the tracking. for (int i = 1; i < Map::kGenerousAllocationCount; i++) { CHECK(initial_map->IsInobjectSlackTrackingInProgress()); Handle tmp = RunI(new_script); CHECK(!IsObjectShrinkable(*tmp)); } CHECK(!initial_map->IsInobjectSlackTrackingInProgress()); CHECK(!IsObjectShrinkable(*obj)); // Object should stay in dictionary mode. CHECK_EQ(JSObject::kHeaderSize, obj->map()->instance_size()); CHECK(obj->map()->is_dictionary_map()); } // The other classes in the hierarchy are not affected. TestClassHierarchy(hierarchy_desc, kNoOverflowCount); } static void TestSubclassBuiltin(const char* subclass_name, InstanceType instance_type, const char* builtin_name, const char* ctor_arguments = "", int builtin_properties_count = 0) { { std::ostringstream os; os << "'use strict';\n" "class " << subclass_name << " extends " << builtin_name << " {\n" " constructor(...args) {\n" " super(...args);\n" " this.a = 42;\n" " this.d = 4.2;\n" " this.o = this;\n" " }\n" "};\n"; CompileRun(os.str().c_str()); } Handle func = GetLexical(subclass_name); // Zero instances were created so far. CHECK(!func->has_initial_map()); v8::Local new_script; { std::ostringstream os; os << "new " << subclass_name << "(" << ctor_arguments << ");"; new_script = v8_compile(os.str().c_str()); } RunI(new_script); CHECK(func->has_initial_map()); Handle initial_map(func->initial_map(), func->GetIsolate()); CHECK_EQ(instance_type, initial_map->instance_type()); // One instance of a subclass created. CHECK_EQ(Map::kSlackTrackingCounterStart - 1, initial_map->construction_counter()); CHECK(initial_map->IsInobjectSlackTrackingInProgress()); // Create two instances in order to ensure that |obj|.o is a data field // in case of Function subclassing. Handle obj = RunI(new_script); // Two instances of a subclass created. CHECK_EQ(Map::kSlackTrackingCounterStart - 2, initial_map->construction_counter()); CHECK(initial_map->IsInobjectSlackTrackingInProgress()); // There must be at least some slack. CHECK_LT(builtin_properties_count + 5, obj->map()->GetInObjectProperties()); CHECK_EQ(Smi::FromInt(42), GetFieldValue(*obj, builtin_properties_count + 0)); CHECK_EQ(4.2, GetDoubleFieldValue(*obj, builtin_properties_count + 1)); CHECK_EQ(*obj, GetFieldValue(*obj, builtin_properties_count + 2)); CHECK(IsObjectShrinkable(*obj)); // Create several subclass instances to complete the tracking. for (int i = 2; i < Map::kGenerousAllocationCount; i++) { CHECK(initial_map->IsInobjectSlackTrackingInProgress()); Handle tmp = RunI(new_script); CHECK_EQ(initial_map->IsInobjectSlackTrackingInProgress(), IsObjectShrinkable(*tmp)); } CHECK(!initial_map->IsInobjectSlackTrackingInProgress()); CHECK(!IsObjectShrinkable(*obj)); // No slack left. CHECK_EQ(builtin_properties_count + 3, obj->map()->GetInObjectProperties()); CHECK_EQ(instance_type, obj->map()->instance_type()); } TEST(SubclassObjectBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); TestSubclassBuiltin("A1", JS_OBJECT_TYPE, "Object", "true"); TestSubclassBuiltin("A2", JS_OBJECT_TYPE, "Object", "42"); TestSubclassBuiltin("A3", JS_OBJECT_TYPE, "Object", "'some string'"); } TEST(SubclassObjectBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassObjectBuiltin(); } TEST(SubclassFunctionBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); TestSubclassBuiltin("A1", JS_FUNCTION_TYPE, "Function", "'return 153;'"); TestSubclassBuiltin("A2", JS_FUNCTION_TYPE, "Function", "'this.a = 44;'"); } TEST(SubclassFunctionBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassFunctionBuiltin(); } TEST(SubclassBooleanBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); TestSubclassBuiltin("A1", JS_VALUE_TYPE, "Boolean", "true"); TestSubclassBuiltin("A2", JS_VALUE_TYPE, "Boolean", "false"); } TEST(SubclassBooleanBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassBooleanBuiltin(); } TEST(SubclassErrorBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); const int first_field = 2; TestSubclassBuiltin("A1", JS_ERROR_TYPE, "Error", "'err'", first_field); TestSubclassBuiltin("A2", JS_ERROR_TYPE, "EvalError", "'err'", first_field); TestSubclassBuiltin("A3", JS_ERROR_TYPE, "RangeError", "'err'", first_field); TestSubclassBuiltin("A4", JS_ERROR_TYPE, "ReferenceError", "'err'", first_field); TestSubclassBuiltin("A5", JS_ERROR_TYPE, "SyntaxError", "'err'", first_field); TestSubclassBuiltin("A6", JS_ERROR_TYPE, "TypeError", "'err'", first_field); TestSubclassBuiltin("A7", JS_ERROR_TYPE, "URIError", "'err'", first_field); } TEST(SubclassErrorBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassErrorBuiltin(); } TEST(SubclassNumberBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); TestSubclassBuiltin("A1", JS_VALUE_TYPE, "Number", "42"); TestSubclassBuiltin("A2", JS_VALUE_TYPE, "Number", "4.2"); } TEST(SubclassNumberBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassNumberBuiltin(); } TEST(SubclassDateBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); TestSubclassBuiltin("A1", JS_DATE_TYPE, "Date", "123456789"); } TEST(SubclassDateBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassDateBuiltin(); } TEST(SubclassStringBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); TestSubclassBuiltin("A1", JS_VALUE_TYPE, "String", "'some string'"); TestSubclassBuiltin("A2", JS_VALUE_TYPE, "String", ""); } TEST(SubclassStringBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassStringBuiltin(); } TEST(SubclassRegExpBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); const int first_field = 1; TestSubclassBuiltin("A1", JS_REGEXP_TYPE, "RegExp", "'o(..)h', 'g'", first_field); } TEST(SubclassRegExpBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassRegExpBuiltin(); } TEST(SubclassArrayBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); TestSubclassBuiltin("A1", JS_ARRAY_TYPE, "Array", "42"); } TEST(SubclassArrayBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassArrayBuiltin(); } TEST(SubclassTypedArrayBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; // Make BigInt64Array/BigUint64Array available for testing. FLAG_harmony_bigint = true; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); #define TYPED_ARRAY_TEST(Type, type, TYPE, elementType) \ TestSubclassBuiltin("A" #Type, JS_TYPED_ARRAY_TYPE, #Type "Array", "42"); TYPED_ARRAYS(TYPED_ARRAY_TEST) #undef TYPED_ARRAY_TEST } TEST(SubclassTypedArrayBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassTypedArrayBuiltin(); } TEST(SubclassCollectionBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); TestSubclassBuiltin("A1", JS_SET_TYPE, "Set", ""); TestSubclassBuiltin("A2", JS_MAP_TYPE, "Map", ""); TestSubclassBuiltin("A3", JS_WEAK_SET_TYPE, "WeakSet", ""); TestSubclassBuiltin("A4", JS_WEAK_MAP_TYPE, "WeakMap", ""); } TEST(SubclassCollectionBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassCollectionBuiltin(); } TEST(SubclassArrayBufferBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); TestSubclassBuiltin("A1", JS_ARRAY_BUFFER_TYPE, "ArrayBuffer", "42"); TestSubclassBuiltin("A2", JS_DATA_VIEW_TYPE, "DataView", "new ArrayBuffer(42)"); } TEST(SubclassArrayBufferBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassArrayBufferBuiltin(); } TEST(SubclassPromiseBuiltin) { // Avoid eventual completion of in-object slack tracking. FLAG_always_opt = false; CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); TestSubclassBuiltin("A1", JS_PROMISE_TYPE, "Promise", "function(resolve, reject) { resolve('ok'); }"); } TEST(SubclassPromiseBuiltinNoInlineNew) { FLAG_inline_new = false; TestSubclassPromiseBuiltin(); } } // namespace test_inobject_slack_tracking } // namespace internal } // namespace v8