v8/test/cctest/test-inobject-slack-tracking.cc

1473 lines
44 KiB
C++
Raw Normal View History

// 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/api-inl.h"
#include "src/init/v8.h"
#include "src/objects/heap-number-inl.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace test_inobject_slack_tracking {
static const int kMaxInobjectProperties = JSObject::kMaxInObjectProperties;
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(CcTest::isolate()->GetCurrentContext()).ToLocal(&result)) {
return result;
}
return v8::Local<v8::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(), isolate);
ScriptContextTable::LookupResult lookup_result;
if (ScriptContextTable::Lookup(isolate, *script_contexts, *str_name,
&lookup_result)) {
Handle<Context> script_context = ScriptContextTable::GetContext(
isolate, script_contexts, lookup_result.context_index);
Handle<Object> result(script_context->get(lookup_result.slot_index),
isolate);
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> RunI(v8::Local<v8::Script> script) {
return OpenHandle<T>(Run(script));
}
template <typename T>
static inline Handle<T> CompileRunI(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) {
Object value = obj.RawFastPropertyAt(field_index);
if (value.IsHeapNumber()) {
return HeapNumber::cast(value).value();
} else {
return value.Number();
}
}
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().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<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 = RunI<JSObject>(new_A_script);
CHECK(func->has_initial_map());
Handle<Map> 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<JSObject> tmp = RunI<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_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 = CompileRunI<JSObject>("new A(1);");
Handle<JSObject> obj3 = CompileRunI<JSObject>("new A(3);");
Handle<JSObject> obj5 = CompileRunI<JSObject>("new A(5);");
CHECK(func->has_initial_map());
Handle<Map> 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<JSObject>("new A(1);");
obj3 = CompileRunI<JSObject>("new A(3);");
obj5 = CompileRunI<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_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 = RunI<JSObject>(new_A_script);
CHECK(func->has_initial_map());
Handle<Map> 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<JSObject> tmp = RunI<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_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 = RunI<JSObject>(new_B_script);
CHECK(a_func->has_initial_map());
Handle<Map> a_initial_map(a_func->initial_map(), a_func->GetIsolate());
CHECK(b_func->has_initial_map());
Handle<Map> 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<JSObject> tmp = RunI<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_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 = RunI<JSObject>(new_A_script);
Handle<JSObject> b_obj = RunI<JSObject>(new_B_script);
CHECK(a_func->has_initial_map());
Handle<Map> a_initial_map(a_func->initial_map(), a_func->GetIsolate());
CHECK(b_func->has_initial_map());
Handle<Map> 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<JSObject> tmp = RunI<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 = RunI<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 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<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 = RunI<JSObject>(new_script);
CHECK(func->has_initial_map());
Handle<Map> 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<JSObject> tmp = RunI<JSObject>(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<int>& 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<int>(hierarchy_desc.size()));
}
TEST(Subclasses) {
std::vector<int> hierarchy_desc;
hierarchy_desc.push_back(50);
hierarchy_desc.push_back(128);
TestSubclassChain(hierarchy_desc);
}
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_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 = RunI<JSObject>(new_script);
CHECK(func->has_initial_map());
Handle<Map> 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<JSObject> tmp = RunI<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);
}
static void CheckExpectedProperties(int expected, std::ostringstream& os) {
Handle<HeapObject> obj = Handle<HeapObject>::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<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 = RunI<JSObject>(new_script);
CHECK(func->has_initial_map());
Handle<Map> 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<JSObject> tmp = RunI<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());
}
RunI<JSObject>(new_script);
CHECK(func->has_initial_map());
Handle<Map> 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<JSObject> obj = RunI<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 = RunI<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_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_PRIMITIVE_WRAPPER_TYPE, "Boolean", "true");
TestSubclassBuiltin("A2", JS_PRIMITIVE_WRAPPER_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_PRIMITIVE_WRAPPER_TYPE, "Number", "42");
TestSubclassBuiltin("A2", JS_PRIMITIVE_WRAPPER_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_PRIMITIVE_WRAPPER_TYPE, "String",
"'some string'");
TestSubclassBuiltin("A2", JS_PRIMITIVE_WRAPPER_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_REG_EXP_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;
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();
}
[runtime] Better instance pre-sizing with transpiled classes. For instances created via constructors and `new` we try to pre-size the instances such that ideally all the data properties can be allocated as in-object properties (and we don't need to allocate the out-of-object PropertyArray backing store). This is accomplished with the helper of the Parser, which counts the property assignments to `this` in the constructor, and we use that as the starting point for pre-sizing logic (a mechanism called *slack tracking* is used to dynamically shrink the objects based on the real memory usage, and eventually compute the final starting size for instances of the individual constructors). This works well even with class hierarchies, since for a derived class constructor we just include the current constructor plus all the base constructors. I.e. with ```js class A { constructor() { this.x00 = null; this.x01 = null; this.x02 = null; this.x03 = null; this.x04 = null; this.x05 = null; this.x06 = null; this.x07 = null; this.x08 = null; this.x09 = null; this.x10 = null; this.x11 = null; this.x12 = null; this.x13 = null; this.x14 = null; this.x15 = null; this.x16 = null; this.x17 = null; this.x18 = null; this.x19 = null; } } class B extends A { constructor() { super(); } } ``` we will eventually learn that instances of `B` need 20 in-object properties. However this breaks with transpiled code (i.e. as generated via TypeScript or Babel), even when the constructors are properly chained. ```js function A() { this.x00 = null; this.x01 = null; this.x02 = null; this.x03 = null; this.x04 = null; this.x05 = null; this.x06 = null; this.x07 = null; this.x08 = null; this.x09 = null; this.x10 = null; this.x11 = null; this.x12 = null; this.x13 = null; this.x14 = null; this.x15 = null; this.x16 = null; this.x17 = null; this.x18 = null; this.x19 = null; } function B() { A.call(this); } Object.setPrototypeOf(B, A); ``` Here we will always have 10 in-object properties for instances of `B` (due to the generic over-allocation logic), and the other 10 properties have to be allocated in the out-of-object PropertyArray. This is unfortunate and actually not necessary. Instead we could just do the same [[Prototype]] walk on the constructor for regular function constructors that we perform for derived (native) class constructors. This CL changes that, such that we give the same treatment to transpiled class that we have for native classes. R=verwaest@chromium.org Bug: v8:8764, v8:8765 Doc: https://bit.ly/v8-instance-presizing-with-transpiled-classes Change-Id: Iac54391e41c9a39101751a678b3a647269fb009d Reviewed-on: https://chromium-review.googlesource.com/c/1442643 Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Toon Verwaest <verwaest@chromium.org> Cr-Commit-Position: refs/heads/master@{#59214}
2019-01-30 14:36:53 +00:00
TEST(SubclassTranspiledClassHierarchy) {
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
CompileRun(
"Object.setPrototypeOf(B, A);\n"
"function A() {\n"
" this.a0 = 0;\n"
" this.a1 = 1;\n"
" this.a2 = 1;\n"
" this.a3 = 1;\n"
" this.a4 = 1;\n"
" this.a5 = 1;\n"
" this.a6 = 1;\n"
" this.a7 = 1;\n"
" this.a8 = 1;\n"
" this.a9 = 1;\n"
" this.a10 = 1;\n"
" this.a11 = 1;\n"
" this.a12 = 1;\n"
" this.a13 = 1;\n"
" this.a14 = 1;\n"
" this.a15 = 1;\n"
" this.a16 = 1;\n"
" this.a17 = 1;\n"
" this.a18 = 1;\n"
" this.a19 = 1;\n"
"};\n"
"function B() {\n"
" A.call(this);\n"
" this.b = 1;\n"
"};\n");
Handle<JSFunction> func = GetGlobal<JSFunction>("B");
// Zero instances have been created so far.
CHECK(!func->has_initial_map());
v8::Local<v8::Script> new_script = v8_compile("new B()");
RunI<JSObject>(new_script);
CHECK(func->has_initial_map());
Handle<Map> initial_map(func->initial_map(), func->GetIsolate());
CHECK_EQ(JS_OBJECT_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 = RunI<JSObject>(new_script);
// Two instances of a subclass created.
CHECK_EQ(Map::kSlackTrackingCounterStart - 2,
initial_map->construction_counter());
CHECK(initial_map->IsInobjectSlackTrackingInProgress());
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 = RunI<JSObject>(new_script);
CHECK_EQ(initial_map->IsInobjectSlackTrackingInProgress(),
IsObjectShrinkable(*tmp));
}
CHECK(!initial_map->IsInobjectSlackTrackingInProgress());
CHECK(!IsObjectShrinkable(*obj));
// No slack left.
CHECK_EQ(21, obj->map().GetInObjectProperties());
CHECK_EQ(JS_OBJECT_TYPE, obj->map().instance_type());
[runtime] Better instance pre-sizing with transpiled classes. For instances created via constructors and `new` we try to pre-size the instances such that ideally all the data properties can be allocated as in-object properties (and we don't need to allocate the out-of-object PropertyArray backing store). This is accomplished with the helper of the Parser, which counts the property assignments to `this` in the constructor, and we use that as the starting point for pre-sizing logic (a mechanism called *slack tracking* is used to dynamically shrink the objects based on the real memory usage, and eventually compute the final starting size for instances of the individual constructors). This works well even with class hierarchies, since for a derived class constructor we just include the current constructor plus all the base constructors. I.e. with ```js class A { constructor() { this.x00 = null; this.x01 = null; this.x02 = null; this.x03 = null; this.x04 = null; this.x05 = null; this.x06 = null; this.x07 = null; this.x08 = null; this.x09 = null; this.x10 = null; this.x11 = null; this.x12 = null; this.x13 = null; this.x14 = null; this.x15 = null; this.x16 = null; this.x17 = null; this.x18 = null; this.x19 = null; } } class B extends A { constructor() { super(); } } ``` we will eventually learn that instances of `B` need 20 in-object properties. However this breaks with transpiled code (i.e. as generated via TypeScript or Babel), even when the constructors are properly chained. ```js function A() { this.x00 = null; this.x01 = null; this.x02 = null; this.x03 = null; this.x04 = null; this.x05 = null; this.x06 = null; this.x07 = null; this.x08 = null; this.x09 = null; this.x10 = null; this.x11 = null; this.x12 = null; this.x13 = null; this.x14 = null; this.x15 = null; this.x16 = null; this.x17 = null; this.x18 = null; this.x19 = null; } function B() { A.call(this); } Object.setPrototypeOf(B, A); ``` Here we will always have 10 in-object properties for instances of `B` (due to the generic over-allocation logic), and the other 10 properties have to be allocated in the out-of-object PropertyArray. This is unfortunate and actually not necessary. Instead we could just do the same [[Prototype]] walk on the constructor for regular function constructors that we perform for derived (native) class constructors. This CL changes that, such that we give the same treatment to transpiled class that we have for native classes. R=verwaest@chromium.org Bug: v8:8764, v8:8765 Doc: https://bit.ly/v8-instance-presizing-with-transpiled-classes Change-Id: Iac54391e41c9a39101751a678b3a647269fb009d Reviewed-on: https://chromium-review.googlesource.com/c/1442643 Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Toon Verwaest <verwaest@chromium.org> Cr-Commit-Position: refs/heads/master@{#59214}
2019-01-30 14:36:53 +00:00
}
TEST(Regress8853_ClassConstructor) {
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
// For classes without any this.prop assignments in their
// constructors we start out with 10 inobject properties.
Handle<JSObject> obj = CompileRunI<JSObject>("new (class {});\n");
CHECK(obj->map().IsInobjectSlackTrackingInProgress());
CHECK(IsObjectShrinkable(*obj));
CHECK_EQ(10, obj->map().GetInObjectProperties());
// For classes with N explicit this.prop assignments in their
// constructors we start out with N+8 inobject properties.
obj = CompileRunI<JSObject>(
"new (class {\n"
" constructor() {\n"
" this.x = 1;\n"
" this.y = 2;\n"
" this.z = 3;\n"
" }\n"
"});\n");
CHECK(obj->map().IsInobjectSlackTrackingInProgress());
CHECK(IsObjectShrinkable(*obj));
CHECK_EQ(3 + 8, obj->map().GetInObjectProperties());
}
TEST(Regress8853_ClassHierarchy) {
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
// For class hierarchies without any this.prop assignments in their
// constructors we reserve 2 inobject properties per constructor plus
// 8 inobject properties slack on top.
std::string base = "(class {})";
for (int i = 1; i < 10; ++i) {
std::string script = "new " + base + ";\n";
Handle<JSObject> obj = CompileRunI<JSObject>(script.c_str());
CHECK(obj->map().IsInobjectSlackTrackingInProgress());
CHECK(IsObjectShrinkable(*obj));
CHECK_EQ(8 + 2 * i, obj->map().GetInObjectProperties());
base = "(class extends " + base + " {})";
}
}
TEST(Regress8853_FunctionConstructor) {
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
// For constructor functions without any this.prop assignments in
// them we start out with 10 inobject properties.
Handle<JSObject> obj = CompileRunI<JSObject>("new (function() {});\n");
CHECK(obj->map().IsInobjectSlackTrackingInProgress());
CHECK(IsObjectShrinkable(*obj));
CHECK_EQ(10, obj->map().GetInObjectProperties());
// For constructor functions with N explicit this.prop assignments
// in them we start out with N+8 inobject properties.
obj = CompileRunI<JSObject>(
"new (function() {\n"
" this.a = 1;\n"
" this.b = 2;\n"
" this.c = 3;\n"
" this.d = 3;\n"
" this.c = 3;\n"
" this.f = 3;\n"
"});\n");
CHECK(obj->map().IsInobjectSlackTrackingInProgress());
CHECK(IsObjectShrinkable(*obj));
CHECK_EQ(6 + 8, obj->map().GetInObjectProperties());
}
TEST(InstanceFieldsArePropertiesDefaultConstructorLazy) {
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
Handle<JSObject> obj = CompileRunI<JSObject>(
"new (class {\n"
" x00 = null;\n"
" x01 = null;\n"
" x02 = null;\n"
" x03 = null;\n"
" x04 = null;\n"
" x05 = null;\n"
" x06 = null;\n"
" x07 = null;\n"
" x08 = null;\n"
" x09 = null;\n"
" x10 = null;\n"
"});\n");
CHECK_EQ(11 + 8, obj->map().GetInObjectProperties());
}
TEST(InstanceFieldsArePropertiesFieldsAndConstructorLazy) {
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
Handle<JSObject> obj = CompileRunI<JSObject>(
"new (class {\n"
" x00 = null;\n"
" x01 = null;\n"
" x02 = null;\n"
" x03 = null;\n"
" x04 = null;\n"
" x05 = null;\n"
" x06 = null;\n"
" x07 = null;\n"
" x08 = null;\n"
" x09 = null;\n"
" x10 = null;\n"
" constructor() {\n"
" this.x11 = null;\n"
" this.x12 = null;\n"
" this.x12 = null;\n"
" this.x14 = null;\n"
" this.x15 = null;\n"
" this.x16 = null;\n"
" this.x17 = null;\n"
" this.x18 = null;\n"
" this.x19 = null;\n"
" this.x20 = null;\n"
" }\n"
"});\n");
CHECK_EQ(21 + 8, obj->map().GetInObjectProperties());
}
TEST(InstanceFieldsArePropertiesDefaultConstructorEager) {
i::FLAG_lazy = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
Handle<JSObject> obj = CompileRunI<JSObject>(
"new (class {\n"
" x00 = null;\n"
" x01 = null;\n"
" x02 = null;\n"
" x03 = null;\n"
" x04 = null;\n"
" x05 = null;\n"
" x06 = null;\n"
" x07 = null;\n"
" x08 = null;\n"
" x09 = null;\n"
" x10 = null;\n"
"});\n");
CHECK_EQ(11 + 8, obj->map().GetInObjectProperties());
}
TEST(InstanceFieldsArePropertiesFieldsAndConstructorEager) {
i::FLAG_lazy = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
Handle<JSObject> obj = CompileRunI<JSObject>(
"new (class {\n"
" x00 = null;\n"
" x01 = null;\n"
" x02 = null;\n"
" x03 = null;\n"
" x04 = null;\n"
" x05 = null;\n"
" x06 = null;\n"
" x07 = null;\n"
" x08 = null;\n"
" x09 = null;\n"
" x10 = null;\n"
" constructor() {\n"
" this.x11 = null;\n"
" this.x12 = null;\n"
" this.x12 = null;\n"
" this.x14 = null;\n"
" this.x15 = null;\n"
" this.x16 = null;\n"
" this.x17 = null;\n"
" this.x18 = null;\n"
" this.x19 = null;\n"
" this.x20 = null;\n"
" }\n"
"});\n");
CHECK_EQ(21 + 8, obj->map().GetInObjectProperties());
}
} // namespace test_inobject_slack_tracking
} // namespace internal
} // namespace v8