// Copyright 2014 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 "src/v8.h" #include "test/cctest/cctest.h" #include "src/api.h" #include "src/debug/debug.h" #include "src/execution.h" #include "src/factory.h" #include "src/global-handles.h" #include "src/macro-assembler.h" #include "src/objects-inl.h" #include "test/cctest/test-feedback-vector.h" using namespace v8::internal; namespace { #define CHECK_SLOT_KIND(helper, index, expected_kind) \ CHECK_EQ(expected_kind, helper.vector()->GetKind(helper.slot(index))); static Handle GetFunction(const char* name) { v8::MaybeLocal v8_f = CcTest::global()->Get( v8::Isolate::GetCurrent()->GetCurrentContext(), v8_str(name)); Handle f = Handle::cast(v8::Utils::OpenHandle(*v8_f.ToLocalChecked())); return f; } TEST(VectorStructure) { LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); Zone zone(isolate->allocator(), ZONE_NAME); Handle vector; { FeedbackVectorSpec one_slot(&zone); one_slot.AddGeneralSlot(); vector = NewFeedbackVector(isolate, &one_slot); FeedbackVectorHelper helper(vector); CHECK_EQ(1, helper.slot_count()); } { FeedbackVectorSpec one_icslot(&zone); one_icslot.AddCallICSlot(); vector = NewFeedbackVector(isolate, &one_icslot); FeedbackVectorHelper helper(vector); CHECK_EQ(1, helper.slot_count()); } { FeedbackVectorSpec spec(&zone); for (int i = 0; i < 3; i++) { spec.AddGeneralSlot(); } for (int i = 0; i < 5; i++) { spec.AddCallICSlot(); } vector = NewFeedbackVector(isolate, &spec); FeedbackVectorHelper helper(vector); CHECK_EQ(8, helper.slot_count()); int index = vector->GetIndex(helper.slot(0)); CHECK_EQ(helper.slot(0), vector->ToSlot(index)); index = vector->GetIndex(helper.slot(3)); CHECK_EQ(helper.slot(3), vector->ToSlot(index)); index = vector->GetIndex(helper.slot(7)); CHECK_EQ(3 + 4 * FeedbackMetadata::GetSlotSize(FeedbackSlotKind::kCall), index); CHECK_EQ(helper.slot(7), vector->ToSlot(index)); CHECK_EQ(3 + 5 * FeedbackMetadata::GetSlotSize(FeedbackSlotKind::kCall), vector->length()); } { FeedbackVectorSpec spec(&zone); spec.AddGeneralSlot(); spec.AddCreateClosureSlot(); spec.AddGeneralSlot(); vector = NewFeedbackVector(isolate, &spec); FeedbackVectorHelper helper(vector); CHECK_EQ(1, FeedbackMetadata::GetSlotSize(FeedbackSlotKind::kCreateClosure)); FeedbackSlot slot = helper.slot(1); Cell* cell = Cell::cast(vector->Get(slot)); CHECK_EQ(cell->value(), *factory->undefined_value()); } } // IC slots need an encoding to recognize what is in there. TEST(VectorICMetadata) { LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); Zone zone(isolate->allocator(), ZONE_NAME); FeedbackVectorSpec spec(&zone); // Set metadata. for (int i = 0; i < 40; i++) { switch (i % 4) { case 0: spec.AddGeneralSlot(); break; case 1: spec.AddCallICSlot(); break; case 2: spec.AddLoadICSlot(); break; case 3: spec.AddKeyedLoadICSlot(); break; } } Handle vector = NewFeedbackVector(isolate, &spec); FeedbackVectorHelper helper(vector); CHECK_EQ(40, helper.slot_count()); // Meanwhile set some feedback values and type feedback values to // verify the data structure remains intact. vector->Set(FeedbackSlot(0), *vector); // Verify the metadata is correctly set up from the spec. for (int i = 0; i < 40; i++) { FeedbackSlotKind kind = vector->GetKind(helper.slot(i)); switch (i % 4) { case 0: CHECK_EQ(FeedbackSlotKind::kGeneral, kind); break; case 1: CHECK_EQ(FeedbackSlotKind::kCall, kind); break; case 2: CHECK_EQ(FeedbackSlotKind::kLoadProperty, kind); break; case 3: CHECK_EQ(FeedbackSlotKind::kLoadKeyed, kind); break; } } } TEST(VectorSlotClearing) { LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); Zone zone(isolate->allocator(), ZONE_NAME); CompileRun("function f() {};"); Handle f = GetFunction("f"); // We only test clearing of a FeedbackSlotKind::kGeneral slots because all // the other slot kinds require a host function for clearing. FeedbackVectorSpec spec(&zone); for (int i = 0; i < 5; i++) { spec.AddGeneralSlot(); } Handle vector = NewFeedbackVector(isolate, &spec); FeedbackVectorHelper helper(vector); // Fill with information vector->Set(helper.slot(0), Smi::FromInt(1)); Handle cell = factory->NewWeakCell(factory->fixed_array_map()); vector->Set(helper.slot(1), *cell); Handle site = factory->NewAllocationSite(); vector->Set(helper.slot(2), *site); vector->ClearSlots(*f); // The feedback vector slots are cleared. AllocationSites are still granted // an exemption from clearing, as are smis. CHECK_EQ(Smi::FromInt(1), vector->Get(helper.slot(0))); CHECK_EQ(*FeedbackVector::UninitializedSentinel(isolate), vector->Get(helper.slot(1))); CHECK(vector->Get(helper.slot(2))->IsAllocationSite()); } TEST(VectorCallICStates) { if (i::FLAG_always_opt) return; CcTest::InitializeVM(); LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); // Make sure function f has a call that uses a type feedback slot. CompileRun( "function foo() { return 17; }" "function f(a) { a(); } f(foo);"); Handle f = GetFunction("f"); // There should be one IC. Handle feedback_vector = Handle(f->feedback_vector(), isolate); FeedbackSlot slot(0); CallICNexus nexus(feedback_vector, slot); CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); // CallIC doesn't return map feedback. CHECK(!nexus.FindFirstMap()); CompileRun("f(function() { return 16; })"); CHECK_EQ(GENERIC, nexus.StateFromFeedback()); // After a collection, state should remain GENERIC. CcTest::CollectAllGarbage(); CHECK_EQ(GENERIC, nexus.StateFromFeedback()); } TEST(VectorCallFeedbackForArray) { if (i::FLAG_always_opt) return; CcTest::InitializeVM(); LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); // Make sure function f has a call that uses a type feedback slot. CompileRun( "function foo() { return 17; }" "function f(a) { a(); } f(Array);"); Handle f = GetFunction("f"); // There should be one IC. Handle feedback_vector = Handle(f->feedback_vector(), isolate); FeedbackSlot slot(0); CallICNexus nexus(feedback_vector, slot); // A call to Array is special, it contains an AllocationSite as feedback. CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); CHECK(nexus.GetFeedback()->IsAllocationSite()); CcTest::CollectAllGarbage(); // It should stay monomorphic even after a GC. CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); } TEST(VectorCallCounts) { if (i::FLAG_always_opt) return; CcTest::InitializeVM(); LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); // Make sure function f has a call that uses a type feedback slot. CompileRun( "function foo() { return 17; }" "function f(a) { a(); } f(foo);"); Handle f = GetFunction("f"); // There should be one IC. Handle feedback_vector = Handle(f->feedback_vector(), isolate); FeedbackSlot slot(0); CallICNexus nexus(feedback_vector, slot); CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); CompileRun("f(foo); f(foo);"); CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); CHECK_EQ(3, nexus.ExtractCallCount()); // Send the IC megamorphic, but we should still have incrementing counts. CompileRun("f(function() { return 12; });"); CHECK_EQ(GENERIC, nexus.StateFromFeedback()); CHECK_EQ(4, nexus.ExtractCallCount()); } TEST(VectorConstructCounts) { if (i::FLAG_always_opt) return; CcTest::InitializeVM(); LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); // Make sure function f has a call that uses a type feedback slot. CompileRun( "function Foo() {}" "function f(a) { new a(); } f(Foo);"); Handle f = GetFunction("f"); Handle feedback_vector = Handle(f->feedback_vector(), isolate); FeedbackSlot slot(0); CallICNexus nexus(feedback_vector, slot); CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); CHECK(feedback_vector->Get(slot)->IsWeakCell()); CompileRun("f(Foo); f(Foo);"); CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); CHECK_EQ(3, nexus.ExtractCallCount()); // Send the IC megamorphic, but we should still have incrementing counts. CompileRun("f(function() {});"); CHECK_EQ(GENERIC, nexus.StateFromFeedback()); CHECK_EQ(4, nexus.ExtractCallCount()); } TEST(VectorLoadICStates) { if (i::FLAG_always_opt) return; CcTest::InitializeVM(); LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); // Make sure function f has a call that uses a type feedback slot. CompileRun( "var o = { foo: 3 };" "function f(a) { return a.foo; } f(o);"); Handle f = GetFunction("f"); // There should be one IC. Handle feedback_vector = Handle(f->feedback_vector(), isolate); FeedbackSlot slot(0); LoadICNexus nexus(feedback_vector, slot); CHECK_EQ(PREMONOMORPHIC, nexus.StateFromFeedback()); CompileRun("f(o)"); CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); // Verify that the monomorphic map is the one we expect. v8::MaybeLocal v8_o = CcTest::global()->Get(context.local(), v8_str("o")); Handle o = Handle::cast(v8::Utils::OpenHandle(*v8_o.ToLocalChecked())); CHECK_EQ(o->map(), nexus.FindFirstMap()); // Now go polymorphic. CompileRun("f({ blarg: 3, foo: 2 })"); CHECK_EQ(POLYMORPHIC, nexus.StateFromFeedback()); CompileRun( "delete o.foo;" "f(o)"); CHECK_EQ(POLYMORPHIC, nexus.StateFromFeedback()); CompileRun("f({ blarg: 3, torino: 10, foo: 2 })"); CHECK_EQ(POLYMORPHIC, nexus.StateFromFeedback()); MapHandles maps; nexus.ExtractMaps(&maps); CHECK_EQ(4, maps.size()); // Finally driven megamorphic. CompileRun("f({ blarg: 3, gran: 3, torino: 10, foo: 2 })"); CHECK_EQ(MEGAMORPHIC, nexus.StateFromFeedback()); CHECK(!nexus.FindFirstMap()); // After a collection, state should not be reset to PREMONOMORPHIC. CcTest::CollectAllGarbage(); CHECK_EQ(MEGAMORPHIC, nexus.StateFromFeedback()); } TEST(VectorLoadGlobalICSlotSharing) { if (i::FLAG_always_opt) return; CcTest::InitializeVM(); LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); // Function f has 5 LoadGlobalICs: 3 for {o} references outside of "typeof" // operator and 2 for {o} references inside "typeof" operator. CompileRun( "o = 10;" "function f() {" " var x = o || 10;" " var y = typeof o;" " return o , typeof o, x , y, o;" "}" "f();"); Handle f = GetFunction("f"); // There should be two IC slots for {o} references outside and inside // typeof operator respectively. Handle feedback_vector = Handle(f->feedback_vector(), isolate); FeedbackVectorHelper helper(feedback_vector); CHECK_EQ(2, helper.slot_count()); CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLoadGlobalNotInsideTypeof); CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kLoadGlobalInsideTypeof); FeedbackSlot slot1 = helper.slot(0); FeedbackSlot slot2 = helper.slot(1); CHECK_EQ(MONOMORPHIC, LoadGlobalICNexus(feedback_vector, slot1).StateFromFeedback()); CHECK_EQ(MONOMORPHIC, LoadGlobalICNexus(feedback_vector, slot2).StateFromFeedback()); } TEST(VectorLoadICOnSmi) { if (i::FLAG_always_opt) return; CcTest::InitializeVM(); LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); Heap* heap = isolate->heap(); // Make sure function f has a call that uses a type feedback slot. CompileRun( "var o = { foo: 3 };" "function f(a) { return a.foo; } f(o);"); Handle f = GetFunction("f"); // There should be one IC. Handle feedback_vector = Handle(f->feedback_vector(), isolate); FeedbackSlot slot(0); LoadICNexus nexus(feedback_vector, slot); CHECK_EQ(PREMONOMORPHIC, nexus.StateFromFeedback()); CompileRun("f(34)"); CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); // Verify that the monomorphic map is the one we expect. Map* number_map = heap->heap_number_map(); CHECK_EQ(number_map, nexus.FindFirstMap()); // Now go polymorphic on o. CompileRun("f(o)"); CHECK_EQ(POLYMORPHIC, nexus.StateFromFeedback()); MapHandles maps; nexus.ExtractMaps(&maps); CHECK_EQ(2, maps.size()); // One of the maps should be the o map. v8::MaybeLocal v8_o = CcTest::global()->Get(context.local(), v8_str("o")); Handle o = Handle::cast(v8::Utils::OpenHandle(*v8_o.ToLocalChecked())); bool number_map_found = false; bool o_map_found = false; for (Handle current : maps) { if (*current == number_map) number_map_found = true; else if (*current == o->map()) o_map_found = true; } CHECK(number_map_found && o_map_found); // The degree of polymorphism doesn't change. CompileRun("f(100)"); CHECK_EQ(POLYMORPHIC, nexus.StateFromFeedback()); MapHandles maps2; nexus.ExtractMaps(&maps2); CHECK_EQ(2, maps2.size()); } TEST(ReferenceContextAllocatesNoSlots) { if (i::FLAG_always_opt) return; CcTest::InitializeVM(); LocalContext context; v8::HandleScope scope(context->GetIsolate()); Isolate* isolate = CcTest::i_isolate(); { CompileRun( "function testvar(x) {" " y = x;" " y = a;" " return y;" "}" "a = 3;" "testvar({});"); Handle f = GetFunction("testvar"); // There should be two LOAD_ICs, one for a and one for y at the end. Handle feedback_vector = handle(f->feedback_vector(), isolate); FeedbackVectorHelper helper(feedback_vector); CHECK_EQ(4, helper.slot_count()); CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kStoreGlobalSloppy); CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kLoadGlobalNotInsideTypeof); CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kStoreGlobalSloppy); CHECK_SLOT_KIND(helper, 3, FeedbackSlotKind::kLoadGlobalNotInsideTypeof); } { CompileRun( "function testprop(x) {" " 'use strict';" " x.blue = a;" "}" "testprop({ blue: 3 });"); Handle f = GetFunction("testprop"); // There should be one LOAD_IC, for the load of a. Handle feedback_vector(f->feedback_vector()); FeedbackVectorHelper helper(feedback_vector); CHECK_EQ(2, helper.slot_count()); CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLoadGlobalNotInsideTypeof); CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kStoreNamedStrict); } { CompileRun( "function testpropfunc(x) {" " x().blue = a;" " return x().blue;" "}" "function makeresult() { return { blue: 3 }; }" "testpropfunc(makeresult);"); Handle f = GetFunction("testpropfunc"); // There should be 1 LOAD_GLOBAL_IC to load x (in both cases), 2 CALL_ICs // to call x and a LOAD_IC to load blue. Handle feedback_vector(f->feedback_vector()); FeedbackVectorHelper helper(feedback_vector); CHECK_EQ(5, helper.slot_count()); CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kCall); CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kLoadGlobalNotInsideTypeof); CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kStoreNamedSloppy); CHECK_SLOT_KIND(helper, 3, FeedbackSlotKind::kCall); CHECK_SLOT_KIND(helper, 4, FeedbackSlotKind::kLoadProperty); } { CompileRun( "function testkeyedprop(x) {" " x[0] = a;" " return x[0];" "}" "testkeyedprop([0, 1, 2]);"); Handle f = GetFunction("testkeyedprop"); // There should be 1 LOAD_GLOBAL_ICs for the load of a, and one // KEYED_LOAD_IC for the load of x[0] in the return statement. Handle feedback_vector(f->feedback_vector()); FeedbackVectorHelper helper(feedback_vector); CHECK_EQ(3, helper.slot_count()); CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLoadGlobalNotInsideTypeof); CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kStoreKeyedSloppy); CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kLoadKeyed); } { CompileRun( "function testkeyedprop(x) {" " 'use strict';" " x[0] = a;" " return x[0];" "}" "testkeyedprop([0, 1, 2]);"); Handle f = GetFunction("testkeyedprop"); // There should be 1 LOAD_GLOBAL_ICs for the load of a, and one // KEYED_LOAD_IC for the load of x[0] in the return statement. Handle feedback_vector(f->feedback_vector()); FeedbackVectorHelper helper(feedback_vector); CHECK_EQ(3, helper.slot_count()); CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLoadGlobalNotInsideTypeof); CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kStoreKeyedStrict); CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kLoadKeyed); } { CompileRun( "function testcompound(x) {" " 'use strict';" " x.old = x.young = x.in_between = a;" " return x.old + x.young;" "}" "testcompound({ old: 3, young: 3, in_between: 3 });"); Handle f = GetFunction("testcompound"); // There should be 1 LOAD_GLOBAL_IC for load of a and 2 LOAD_ICs, for load // of x.old and x.young. Handle feedback_vector(f->feedback_vector()); FeedbackVectorHelper helper(feedback_vector); CHECK_EQ(7, helper.slot_count()); CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLoadGlobalNotInsideTypeof); CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kStoreNamedStrict); CHECK_SLOT_KIND(helper, 2, FeedbackSlotKind::kStoreNamedStrict); CHECK_SLOT_KIND(helper, 3, FeedbackSlotKind::kStoreNamedStrict); CHECK_SLOT_KIND(helper, 4, FeedbackSlotKind::kLoadProperty); CHECK_SLOT_KIND(helper, 5, FeedbackSlotKind::kLoadProperty); CHECK_SLOT_KIND(helper, 6, FeedbackSlotKind::kBinaryOp); } } TEST(VectorStoreICBasic) { if (i::FLAG_always_opt) return; CcTest::InitializeVM(); LocalContext context; v8::HandleScope scope(context->GetIsolate()); CompileRun( "function f(a) {" " a.foo = 5;" "}" "var a = { foo: 3 };" "f(a);" "f(a);" "f(a);"); Handle f = GetFunction("f"); // There should be one IC slot. Handle feedback_vector(f->feedback_vector()); FeedbackVectorHelper helper(feedback_vector); CHECK_EQ(1, helper.slot_count()); FeedbackSlot slot(0); StoreICNexus nexus(feedback_vector, slot); CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); } TEST(StoreOwnIC) { if (i::FLAG_always_opt) return; CcTest::InitializeVM(); LocalContext context; v8::HandleScope scope(context->GetIsolate()); CompileRun( "function f(v) {" " return {a: 0, b: v, c: 0};" "}" "f(1);" "f(2);" "f(3);"); Handle f = GetFunction("f"); // There should be one IC slot. Handle feedback_vector(f->feedback_vector()); FeedbackVectorHelper helper(feedback_vector); CHECK_EQ(2, helper.slot_count()); CHECK_SLOT_KIND(helper, 0, FeedbackSlotKind::kLiteral); CHECK_SLOT_KIND(helper, 1, FeedbackSlotKind::kStoreOwnNamed); StoreOwnICNexus nexus(feedback_vector, helper.slot(1)); CHECK_EQ(MONOMORPHIC, nexus.StateFromFeedback()); } } // namespace