diff options
Diffstat (limited to 'deps/v8/test/cctest/test-inobject-slack-tracking.cc')
-rw-r--r-- | deps/v8/test/cctest/test-inobject-slack-tracking.cc | 1111 |
1 files changed, 1111 insertions, 0 deletions
diff --git a/deps/v8/test/cctest/test-inobject-slack-tracking.cc b/deps/v8/test/cctest/test-inobject-slack-tracking.cc new file mode 100644 index 0000000000..6ce77c9416 --- /dev/null +++ b/deps/v8/test/cctest/test-inobject-slack-tracking.cc @@ -0,0 +1,1111 @@ +// 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 <sstream> +#include <utility> + +#include "src/api.h" +#include "src/objects.h" +#include "src/v8.h" + +#include "test/cctest/cctest.h" + +using namespace v8::base; +using namespace v8::internal; + + +static const int kMaxInobjectProperties = + (JSObject::kMaxInstanceSize - JSObject::kHeaderSize) >> kPointerSizeLog2; + + +template <typename T> +static Handle<T> OpenHandle(v8::Local<v8::Value> value) { + Handle<Object> obj = v8::Utils::OpenHandle(*value); + return Handle<T>::cast(obj); +} + + +static inline v8::Local<v8::Value> Run(v8::Local<v8::Script> script) { + v8::Local<v8::Value> result; + if (script->Run(v8::Isolate::GetCurrent()->GetCurrentContext()) + .ToLocal(&result)) { + return result; + } + return v8::Local<v8::Value>(); +} + + +template <typename T = Object> +Handle<T> GetGlobal(const char* name) { + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + Handle<String> str_name = factory->InternalizeUtf8String(name); + + Handle<Object> value = + Object::GetProperty(isolate->global_object(), str_name).ToHandleChecked(); + return Handle<T>::cast(value); +} + + +template <typename T = Object> +Handle<T> GetLexical(const char* name) { + Isolate* isolate = CcTest::i_isolate(); + Factory* factory = isolate->factory(); + + Handle<String> str_name = factory->InternalizeUtf8String(name); + Handle<ScriptContextTable> script_contexts( + isolate->native_context()->script_context_table()); + + ScriptContextTable::LookupResult lookup_result; + if (ScriptContextTable::Lookup(script_contexts, str_name, &lookup_result)) { + Handle<Object> result = + FixedArray::get(ScriptContextTable::GetContext( + script_contexts, lookup_result.context_index), + lookup_result.slot_index); + return Handle<T>::cast(result); + } + return Handle<T>(); +} + + +template <typename T = Object> +Handle<T> GetLexical(const std::string& name) { + return GetLexical<T>(name.c_str()); +} + + +template <typename T> +static inline Handle<T> Run(v8::Local<v8::Script> script) { + return OpenHandle<T>(Run(script)); +} + + +template <typename T> +static inline Handle<T> CompileRun(const char* script) { + return OpenHandle<T>(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 HeapNumber::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<Map> filler_map = + CcTest::i_isolate()->factory()->one_pointer_filler_map(); + + int inobject_properties = obj->map()->GetInObjectProperties(); + int unused = obj->map()->unused_property_fields(); + 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_inline_construct = false; + 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<JSFunction> func = GetGlobal<JSFunction>("A"); + + // Zero instances were created so far. + CHECK(!func->has_initial_map()); + + v8::Local<v8::Script> new_A_script = v8_compile("new A();"); + + Handle<JSObject> obj = Run<JSObject>(new_A_script); + + CHECK(func->has_initial_map()); + Handle<Map> initial_map(func->initial_map()); + + // 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<JSObject> tmp = Run<JSObject>(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_inline_construct = false; + 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<JSFunction> func = GetGlobal<JSFunction>("A"); + + // Zero instances were created so far. + CHECK(!func->has_initial_map()); + + Handle<JSObject> obj1 = CompileRun<JSObject>("new A(1);"); + Handle<JSObject> obj3 = CompileRun<JSObject>("new A(3);"); + Handle<JSObject> obj5 = CompileRun<JSObject>("new A(5);"); + + CHECK(func->has_initial_map()); + Handle<Map> initial_map(func->initial_map()); + + // 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()->unused_property_fields()); + + CHECK_EQ(5, obj3->map()->GetInObjectProperties()); + CHECK_EQ(2, obj3->map()->unused_property_fields()); + + CHECK_EQ(5, obj5->map()->GetInObjectProperties()); + CHECK_EQ(0, obj5->map()->unused_property_fields()); + + // Since slack tracking is complete, the new objects should not be shrinkable. + obj1 = CompileRun<JSObject>("new A(1);"); + obj3 = CompileRun<JSObject>("new A(3);"); + obj5 = CompileRun<JSObject>("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_inline_construct = false; + 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<JSFunction> func = GetGlobal<JSFunction>("A"); + + // Zero instances were created so far. + CHECK(!func->has_initial_map()); + + v8::Local<v8::Script> new_A_script = v8_compile("CreateGenerator();"); + + Handle<JSObject> obj = Run<JSObject>(new_A_script); + + CHECK(func->has_initial_map()); + Handle<Map> initial_map(func->initial_map()); + + // 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<JSObject> tmp = Run<JSObject>(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_inline_construct = false; + 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<JSFunction> a_func = GetLexical<JSFunction>("A"); + Handle<JSFunction> b_func = GetLexical<JSFunction>("B"); + + // Zero instances were created so far. + CHECK(!a_func->has_initial_map()); + CHECK(!b_func->has_initial_map()); + + v8::Local<v8::Script> new_B_script = v8_compile("new B();"); + + Handle<JSObject> obj = Run<JSObject>(new_B_script); + + CHECK(a_func->has_initial_map()); + Handle<Map> a_initial_map(a_func->initial_map()); + + CHECK(b_func->has_initial_map()); + Handle<Map> b_initial_map(b_func->initial_map()); + + // 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<JSObject> tmp = Run<JSObject>(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_inline_construct = false; + 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<JSFunction> a_func = GetLexical<JSFunction>("A"); + Handle<JSFunction> b_func = GetLexical<JSFunction>("B"); + + // Zero instances were created so far. + CHECK(!a_func->has_initial_map()); + CHECK(!b_func->has_initial_map()); + + v8::Local<v8::Script> new_A_script = v8_compile("new A();"); + v8::Local<v8::Script> new_B_script = v8_compile("new B();"); + + Handle<JSObject> a_obj = Run<JSObject>(new_A_script); + Handle<JSObject> b_obj = Run<JSObject>(new_B_script); + + CHECK(a_func->has_initial_map()); + Handle<Map> a_initial_map(a_func->initial_map()); + + CHECK(b_func->has_initial_map()); + Handle<Map> b_initial_map(b_func->initial_map()); + + // 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<JSObject> tmp = Run<JSObject>(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<JSObject> tmp = Run<JSObject>(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 hierachy 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<int>& hierarchy_desc) { + std::ostringstream os; + os << "'use strict';\n\n"; + + int n = static_cast<int>(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<v8::Script> 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<int>& 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<v8::Script> new_script = GetNewObjectScript(class_name); + + Handle<JSFunction> func = GetLexical<JSFunction>(class_name); + + Handle<JSObject> obj = Run<JSObject>(new_script); + + CHECK(func->has_initial_map()); + Handle<Map> initial_map(func->initial_map()); + + // 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<JSObject> tmp = Run<JSObject>(new_script); + CHECK_EQ(initial_map->IsInobjectSlackTrackingInProgress(), + IsObjectShrinkable(*tmp)); + } + CHECK(!initial_map->IsInobjectSlackTrackingInProgress()); + CHECK(!IsObjectShrinkable(*obj)); + + // No slack left. + CHECK_EQ(fields_count, obj->map()->GetInObjectProperties()); + } +} + + +static void TestSubclassChain(const std::vector<int>& hierarchy_desc) { + // Avoid eventual completion of in-object slack tracking. + FLAG_inline_construct = false; + FLAG_always_opt = false; + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + + CreateClassHierarchy(hierarchy_desc); + TestClassHierarchy(hierarchy_desc, static_cast<int>(hierarchy_desc.size())); +} + + +TEST(LongSubclassChain1) { + std::vector<int> hierarchy_desc; + for (int i = 0; i < 7; i++) { + hierarchy_desc.push_back(i * 10); + } + TestSubclassChain(hierarchy_desc); +} + + +TEST(LongSubclassChain2) { + std::vector<int> 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<int> 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_inline_construct = false; + FLAG_always_opt = false; + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + + std::vector<int> 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<v8::Script> new_script = GetNewObjectScript(class_name); + + Handle<JSFunction> func = GetLexical<JSFunction>(class_name); + + Handle<JSObject> obj = Run<JSObject>(new_script); + + CHECK(func->has_initial_map()); + Handle<Map> initial_map(func->initial_map()); + + // 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<JSObject> tmp = Run<JSObject>(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); +} + + +TEST(SlowModeSubclass) { + // Avoid eventual completion of in-object slack tracking. + FLAG_inline_construct = false; + FLAG_always_opt = false; + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + + std::vector<int> 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<v8::Script> new_script = GetNewObjectScript(class_name); + + Handle<JSFunction> func = GetLexical<JSFunction>(class_name); + + Handle<JSObject> obj = Run<JSObject>(new_script); + + CHECK(func->has_initial_map()); + Handle<Map> initial_map(func->initial_map()); + + // 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<JSObject> tmp = Run<JSObject>(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<JSFunction> func = GetLexical<JSFunction>(subclass_name); + + // Zero instances were created so far. + CHECK(!func->has_initial_map()); + + v8::Local<v8::Script> new_script; + { + std::ostringstream os; + os << "new " << subclass_name << "(" << ctor_arguments << ");"; + new_script = v8_compile(os.str().c_str()); + } + + Run<JSObject>(new_script); + + CHECK(func->has_initial_map()); + Handle<Map> initial_map(func->initial_map()); + + 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<JSObject> obj = Run<JSObject>(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<JSObject> tmp = Run<JSObject>(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_inline_construct = false; + 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_inline_construct = false; + 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_inline_construct = false; + 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_inline_construct = false; + FLAG_always_opt = false; + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + + const int first_field = 2; + TestSubclassBuiltin("A1", JS_OBJECT_TYPE, "Error", "'err'", first_field); + TestSubclassBuiltin("A2", JS_OBJECT_TYPE, "EvalError", "'err'", first_field); + TestSubclassBuiltin("A3", JS_OBJECT_TYPE, "RangeError", "'err'", first_field); + TestSubclassBuiltin("A4", JS_OBJECT_TYPE, "ReferenceError", "'err'", + first_field); + TestSubclassBuiltin("A5", JS_OBJECT_TYPE, "SyntaxError", "'err'", + first_field); + TestSubclassBuiltin("A6", JS_OBJECT_TYPE, "TypeError", "'err'", first_field); + TestSubclassBuiltin("A7", JS_OBJECT_TYPE, "URIError", "'err'", first_field); +} + + +TEST(SubclassErrorBuiltinNoInlineNew) { + FLAG_inline_new = false; + TestSubclassErrorBuiltin(); +} + + +TEST(SubclassNumberBuiltin) { + // Avoid eventual completion of in-object slack tracking. + FLAG_inline_construct = false; + 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_inline_construct = false; + 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_inline_construct = false; + 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_inline_construct = false; + 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_inline_construct = false; + 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_inline_construct = false; + FLAG_always_opt = false; + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + +#define TYPED_ARRAY_TEST(Type, type, TYPE, elementType, size) \ + 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_inline_construct = false; + 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_inline_construct = false; + 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_inline_construct = false; + FLAG_always_opt = false; + CcTest::InitializeVM(); + v8::HandleScope scope(CcTest::isolate()); + + const int first_field = 4; + TestSubclassBuiltin("A1", JS_PROMISE_TYPE, "Promise", + "function(resolve, reject) { resolve('ok'); }", + first_field); +} + + +TEST(SubclassPromiseBuiltinNoInlineNew) { + FLAG_inline_new = false; + TestSubclassPromiseBuiltin(); +} |