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

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1216 lines
37 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-inl.h"
#include "src/objects-inl.h"
#include "src/objects.h"
#include "src/v8.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace test_inobject_slack_tracking {
static const int kMaxInobjectProperties = JSObject::kMaxInObjectProperties;
template <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> 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) {
if (obj->IsUnboxedDoubleField(field_index)) {
return obj->RawFastDoublePropertyAt(field_index);
} else {
Object* value = obj->RawFastPropertyAt(field_index);
CHECK(value->IsMutableHeapNumber());
return MutableHeapNumber::cast(value)->value();
}
}
static double GetDoubleFieldValue(JSObject obj, int property_index) {
FieldIndex index = FieldIndex::ForPropertyIndex(obj->map(), property_index);
return GetDoubleFieldValue(obj, index);
}
bool IsObjectShrinkable(JSObject obj) {
Handle<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_VALUE_TYPE, "Boolean", "true");
TestSubclassBuiltin("A2", JS_VALUE_TYPE, "Boolean", "false");
}
TEST(SubclassBooleanBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassBooleanBuiltin();
}
TEST(SubclassErrorBuiltin) {
// Avoid eventual completion of in-object slack tracking.
FLAG_always_opt = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
const int first_field = 2;
TestSubclassBuiltin("A1", JS_ERROR_TYPE, "Error", "'err'", first_field);
TestSubclassBuiltin("A2", JS_ERROR_TYPE, "EvalError", "'err'", first_field);
TestSubclassBuiltin("A3", JS_ERROR_TYPE, "RangeError", "'err'", first_field);
TestSubclassBuiltin("A4", JS_ERROR_TYPE, "ReferenceError", "'err'",
first_field);
TestSubclassBuiltin("A5", JS_ERROR_TYPE, "SyntaxError", "'err'", first_field);
TestSubclassBuiltin("A6", JS_ERROR_TYPE, "TypeError", "'err'", first_field);
TestSubclassBuiltin("A7", JS_ERROR_TYPE, "URIError", "'err'", first_field);
}
TEST(SubclassErrorBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassErrorBuiltin();
}
TEST(SubclassNumberBuiltin) {
// Avoid eventual completion of in-object slack tracking.
FLAG_always_opt = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
TestSubclassBuiltin("A1", JS_VALUE_TYPE, "Number", "42");
TestSubclassBuiltin("A2", JS_VALUE_TYPE, "Number", "4.2");
}
TEST(SubclassNumberBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassNumberBuiltin();
}
TEST(SubclassDateBuiltin) {
// Avoid eventual completion of in-object slack tracking.
FLAG_always_opt = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
TestSubclassBuiltin("A1", JS_DATE_TYPE, "Date", "123456789");
}
TEST(SubclassDateBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassDateBuiltin();
}
TEST(SubclassStringBuiltin) {
// Avoid eventual completion of in-object slack tracking.
FLAG_always_opt = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
TestSubclassBuiltin("A1", JS_VALUE_TYPE, "String", "'some string'");
TestSubclassBuiltin("A2", JS_VALUE_TYPE, "String", "");
}
TEST(SubclassStringBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassStringBuiltin();
}
TEST(SubclassRegExpBuiltin) {
// Avoid eventual completion of in-object slack tracking.
FLAG_always_opt = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
const int first_field = 1;
TestSubclassBuiltin("A1", JS_REGEXP_TYPE, "RegExp", "'o(..)h', 'g'",
first_field);
}
TEST(SubclassRegExpBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassRegExpBuiltin();
}
TEST(SubclassArrayBuiltin) {
// Avoid eventual completion of in-object slack tracking.
FLAG_always_opt = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
TestSubclassBuiltin("A1", JS_ARRAY_TYPE, "Array", "42");
}
TEST(SubclassArrayBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassArrayBuiltin();
}
TEST(SubclassTypedArrayBuiltin) {
// Avoid eventual completion of in-object slack tracking.
FLAG_always_opt = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
#define TYPED_ARRAY_TEST(Type, type, TYPE, elementType) \
TestSubclassBuiltin("A" #Type, JS_TYPED_ARRAY_TYPE, #Type "Array", "42");
TYPED_ARRAYS(TYPED_ARRAY_TEST)
#undef TYPED_ARRAY_TEST
}
TEST(SubclassTypedArrayBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassTypedArrayBuiltin();
}
TEST(SubclassCollectionBuiltin) {
// Avoid eventual completion of in-object slack tracking.
FLAG_always_opt = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
TestSubclassBuiltin("A1", JS_SET_TYPE, "Set", "");
TestSubclassBuiltin("A2", JS_MAP_TYPE, "Map", "");
TestSubclassBuiltin("A3", JS_WEAK_SET_TYPE, "WeakSet", "");
TestSubclassBuiltin("A4", JS_WEAK_MAP_TYPE, "WeakMap", "");
}
TEST(SubclassCollectionBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassCollectionBuiltin();
}
TEST(SubclassArrayBufferBuiltin) {
// Avoid eventual completion of in-object slack tracking.
FLAG_always_opt = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
TestSubclassBuiltin("A1", JS_ARRAY_BUFFER_TYPE, "ArrayBuffer", "42");
TestSubclassBuiltin("A2", JS_DATA_VIEW_TYPE, "DataView",
"new ArrayBuffer(42)");
}
TEST(SubclassArrayBufferBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassArrayBufferBuiltin();
}
TEST(SubclassPromiseBuiltin) {
// Avoid eventual completion of in-object slack tracking.
FLAG_always_opt = false;
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
TestSubclassBuiltin("A1", JS_PROMISE_TYPE, "Promise",
"function(resolve, reject) { resolve('ok'); }");
}
TEST(SubclassPromiseBuiltinNoInlineNew) {
FLAG_inline_new = false;
TestSubclassPromiseBuiltin();
}
} // namespace test_inobject_slack_tracking
} // namespace internal
} // namespace v8