Index: test/cctest/test-inobject-slack-tracking.cc |
diff --git a/test/cctest/test-inobject-slack-tracking.cc b/test/cctest/test-inobject-slack-tracking.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..79cb8335adbf6e1836541c8da3cc12c4ca506b6e |
--- /dev/null |
+++ b/test/cctest/test-inobject-slack-tracking.cc |
@@ -0,0 +1,1098 @@ |
+// 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); |
+ DCHECK(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->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->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->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->counter()); |
+ CHECK(a_initial_map->IsInobjectSlackTrackingInProgress()); |
+ |
+ // One instance of B created. |
+ CHECK_EQ(Map::kSlackTrackingCounterStart - 1, b_initial_map->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->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->counter()); |
+ CHECK(a_initial_map->IsInobjectSlackTrackingInProgress()); |
+ |
+ // One instance of a subclass created. |
+ CHECK_EQ(Map::kSlackTrackingCounterStart - 1, b_initial_map->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. |
+ DCHECK_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->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->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->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->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->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(); |
+} |