// Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include "include/v8-function.h" #include "src/api/api-inl.h" #include "src/base/strings.h" #include "src/codegen/assembler-inl.h" #include "src/codegen/compilation-cache.h" #include "src/codegen/compiler.h" #include "src/codegen/macro-assembler-inl.h" #include "src/codegen/script-details.h" #include "src/common/globals.h" #include "src/debug/debug.h" #include "src/deoptimizer/deoptimizer.h" #include "src/execution/execution.h" #include "src/flags/flags.h" #include "src/handles/global-handles-inl.h" #include "src/heap/combined-heap.h" #include "src/heap/factory.h" #include "src/heap/gc-tracer.h" #include "src/heap/heap-inl.h" #include "src/heap/heap-verifier.h" #include "src/heap/incremental-marking.h" #include "src/heap/large-spaces.h" #include "src/heap/mark-compact-inl.h" #include "src/heap/mark-compact.h" #include "src/heap/marking-barrier.h" #include "src/heap/marking-state-inl.h" #include "src/heap/memory-chunk.h" #include "src/heap/memory-reducer.h" #include "src/heap/parked-scope.h" #include "src/heap/remembered-set-inl.h" #include "src/heap/safepoint.h" #include "src/ic/ic.h" #include "src/numbers/hash-seed-inl.h" #include "src/objects/call-site-info-inl.h" #include "src/objects/elements.h" #include "src/objects/field-type.h" #include "src/objects/heap-number-inl.h" #include "src/objects/js-array-inl.h" #include "src/objects/js-collection-inl.h" #include "src/objects/managed-inl.h" #include "src/objects/objects-inl.h" #include "src/objects/slots.h" #include "src/objects/transitions.h" #include "src/regexp/regexp.h" #include "src/snapshot/snapshot.h" #include "src/tracing/tracing-category-observer.h" #include "src/utils/ostreams.h" #include "test/cctest/cctest.h" #include "test/cctest/feedback-vector-helper.h" #include "test/cctest/heap/heap-tester.h" #include "test/cctest/heap/heap-utils.h" #include "test/cctest/test-transitions.h" namespace v8 { namespace internal { namespace heap { // We only start allocation-site tracking with the second instantiation. static const int kPretenureCreationCount = AllocationSite::kPretenureMinimumCreated + 1; static void CheckMap(Map map, int type, int instance_size) { CHECK(map.IsHeapObject()); DCHECK(IsValidHeapObject(CcTest::heap(), map)); CHECK_EQ(ReadOnlyRoots(CcTest::heap()).meta_map(), map.map()); CHECK_EQ(type, map.instance_type()); CHECK_EQ(instance_size, map.instance_size()); } TEST(HeapMaps) { CcTest::InitializeVM(); ReadOnlyRoots roots(CcTest::heap()); CheckMap(roots.meta_map(), MAP_TYPE, Map::kSize); CheckMap(roots.heap_number_map(), HEAP_NUMBER_TYPE, HeapNumber::kSize); CheckMap(roots.fixed_array_map(), FIXED_ARRAY_TYPE, kVariableSizeSentinel); CheckMap(roots.hash_table_map(), HASH_TABLE_TYPE, kVariableSizeSentinel); CheckMap(roots.string_map(), STRING_TYPE, kVariableSizeSentinel); } static void VerifyStoredPrototypeMap(Isolate* isolate, int stored_map_context_index, int stored_ctor_context_index) { Handle context = isolate->native_context(); Handle this_map(Map::cast(context->get(stored_map_context_index)), isolate); Handle fun( JSFunction::cast(context->get(stored_ctor_context_index)), isolate); Handle proto(JSObject::cast(fun->initial_map().prototype()), isolate); Handle that_map(proto->map(), isolate); CHECK(proto->HasFastProperties()); CHECK_EQ(*this_map, *that_map); } // Checks that critical maps stored on the context (mostly used for fast-path // checks) are unchanged after initialization. TEST(ContextMaps) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); HandleScope handle_scope(isolate); VerifyStoredPrototypeMap(isolate, Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX, Context::STRING_FUNCTION_INDEX); VerifyStoredPrototypeMap(isolate, Context::REGEXP_PROTOTYPE_MAP_INDEX, Context::REGEXP_FUNCTION_INDEX); } TEST(InitialObjects) { LocalContext env; HandleScope scope(CcTest::i_isolate()); Handle context = v8::Utils::OpenHandle(*env); // Initial ArrayIterator prototype. CHECK_EQ( context->initial_array_iterator_prototype(), *v8::Utils::OpenHandle(*CompileRun("[][Symbol.iterator]().__proto__"))); // Initial Array prototype. CHECK_EQ(context->initial_array_prototype(), *v8::Utils::OpenHandle(*CompileRun("Array.prototype"))); // Initial Generator prototype. CHECK_EQ(context->initial_generator_prototype(), *v8::Utils::OpenHandle( *CompileRun("(function*(){}).__proto__.prototype"))); // Initial Iterator prototype. CHECK_EQ(context->initial_iterator_prototype(), *v8::Utils::OpenHandle( *CompileRun("[][Symbol.iterator]().__proto__.__proto__"))); // Initial Object prototype. CHECK_EQ(context->initial_object_prototype(), *v8::Utils::OpenHandle(*CompileRun("Object.prototype"))); } static void CheckOddball(Isolate* isolate, Object obj, const char* string) { CHECK(obj.IsOddball()); Handle handle(obj, isolate); Object print_string = *Object::ToString(isolate, handle).ToHandleChecked(); CHECK(String::cast(print_string).IsOneByteEqualTo(base::CStrVector(string))); } static void CheckSmi(Isolate* isolate, int value, const char* string) { Handle handle(Smi::FromInt(value), isolate); Object print_string = *Object::ToString(isolate, handle).ToHandleChecked(); CHECK(String::cast(print_string).IsOneByteEqualTo(base::CStrVector(string))); } static void CheckNumber(Isolate* isolate, double value, const char* string) { Handle number = isolate->factory()->NewNumber(value); CHECK(number->IsNumber()); Handle print_string = Object::ToString(isolate, number).ToHandleChecked(); CHECK(String::cast(*print_string).IsOneByteEqualTo(base::CStrVector(string))); } void CheckEmbeddedObjectsAreEqual(Isolate* isolate, Handle lhs, Handle rhs) { int mode_mask = RelocInfo::ModeMask(RelocInfo::FULL_EMBEDDED_OBJECT); PtrComprCageBase cage_base(isolate); RelocIterator lhs_it(*lhs, mode_mask); RelocIterator rhs_it(*rhs, mode_mask); while (!lhs_it.done() && !rhs_it.done()) { CHECK_EQ(lhs_it.rinfo()->target_object(cage_base), rhs_it.rinfo()->target_object(cage_base)); lhs_it.next(); rhs_it.next(); } CHECK(lhs_it.done() == rhs_it.done()); } static void CheckGcSafeFindCodeForInnerPointer(Isolate* isolate) { // Test GcSafeFindCodeForInnerPointer #define __ assm. Assembler assm(AssemblerOptions{}); __ nop(); // supported on all architectures PtrComprCageBase cage_base(isolate); CodeDesc desc; assm.GetCode(isolate, &desc); Handle code( Factory::CodeBuilder(isolate, desc, CodeKind::FOR_TESTING) .Build() ->instruction_stream(), isolate); CHECK(code->IsInstructionStream(cage_base)); HeapObject obj = HeapObject::cast(*code); Address obj_addr = obj.address(); for (int i = 0; i < obj.Size(cage_base); i += kTaggedSize) { Code lookup_result = isolate->heap()->FindCodeForInnerPointer(obj_addr + i); CHECK_EQ(*code, lookup_result.instruction_stream()); } Handle copy( Factory::CodeBuilder(isolate, desc, CodeKind::FOR_TESTING) .Build() ->instruction_stream(), isolate); HeapObject obj_copy = HeapObject::cast(*copy); Code not_right = isolate->heap()->FindCodeForInnerPointer( obj_copy.address() + obj_copy.Size(cage_base) / 2); CHECK_NE(not_right.instruction_stream(), *code); CHECK_EQ(not_right.instruction_stream(), *copy); } TEST(HandleNull) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); HandleScope outer_scope(isolate); LocalContext context; Handle n(Object(0), isolate); CHECK(!n.is_null()); } TEST(HeapObjects) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); Heap* heap = isolate->heap(); HandleScope sc(isolate); Handle value = factory->NewNumber(1.000123); CHECK(value->IsHeapNumber()); CHECK(value->IsNumber()); CHECK_EQ(1.000123, value->Number()); value = factory->NewNumber(1.0); CHECK(value->IsSmi()); CHECK(value->IsNumber()); CHECK_EQ(1.0, value->Number()); value = factory->NewNumberFromInt(1024); CHECK(value->IsSmi()); CHECK(value->IsNumber()); CHECK_EQ(1024.0, value->Number()); value = factory->NewNumberFromInt(Smi::kMinValue); CHECK(value->IsSmi()); CHECK(value->IsNumber()); CHECK_EQ(Smi::kMinValue, Handle::cast(value)->value()); value = factory->NewNumberFromInt(Smi::kMaxValue); CHECK(value->IsSmi()); CHECK(value->IsNumber()); CHECK_EQ(Smi::kMaxValue, Handle::cast(value)->value()); #if !defined(V8_TARGET_ARCH_64_BIT) // TODO(lrn): We need a NumberFromIntptr function in order to test this. value = factory->NewNumberFromInt(Smi::kMinValue - 1); CHECK(value->IsHeapNumber()); CHECK(value->IsNumber()); CHECK_EQ(static_cast(Smi::kMinValue - 1), value->Number()); #endif value = factory->NewNumberFromUint(static_cast(Smi::kMaxValue) + 1); CHECK(value->IsHeapNumber()); CHECK(value->IsNumber()); CHECK_EQ(static_cast(static_cast(Smi::kMaxValue) + 1), value->Number()); value = factory->NewNumberFromUint(static_cast(1) << 31); CHECK(value->IsHeapNumber()); CHECK(value->IsNumber()); CHECK_EQ(static_cast(static_cast(1) << 31), value->Number()); // nan oddball checks CHECK(factory->nan_value()->IsNumber()); CHECK(std::isnan(factory->nan_value()->Number())); Handle s = factory->NewStringFromStaticChars("fisk hest "); CHECK(s->IsString()); CHECK_EQ(10, s->length()); Handle object_string = Handle::cast(factory->Object_string()); Handle global(CcTest::i_isolate()->context().global_object(), isolate); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, global, object_string)); // Check ToString for oddballs ReadOnlyRoots roots(heap); CheckOddball(isolate, roots.true_value(), "true"); CheckOddball(isolate, roots.false_value(), "false"); CheckOddball(isolate, roots.null_value(), "null"); CheckOddball(isolate, roots.undefined_value(), "undefined"); // Check ToString for Smis CheckSmi(isolate, 0, "0"); CheckSmi(isolate, 42, "42"); CheckSmi(isolate, -42, "-42"); // Check ToString for Numbers CheckNumber(isolate, 1.1, "1.1"); CheckGcSafeFindCodeForInnerPointer(isolate); } TEST(Tagging) { CcTest::InitializeVM(); int request = 24; CHECK_EQ(request, static_cast(OBJECT_POINTER_ALIGN(request))); CHECK(Smi::FromInt(42).IsSmi()); CHECK(Smi::FromInt(Smi::kMinValue).IsSmi()); CHECK(Smi::FromInt(Smi::kMaxValue).IsSmi()); } TEST(GarbageCollection) { if (v8_flags.single_generation) return; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); HandleScope sc(isolate); // Check GC. heap::CollectGarbage(CcTest::heap(), NEW_SPACE); Handle global(CcTest::i_isolate()->context().global_object(), isolate); Handle name = factory->InternalizeUtf8String("theFunction"); Handle prop_name = factory->InternalizeUtf8String("theSlot"); Handle prop_namex = factory->InternalizeUtf8String("theSlotx"); Handle obj_name = factory->InternalizeUtf8String("theObject"); Handle twenty_three(Smi::FromInt(23), isolate); Handle twenty_four(Smi::FromInt(24), isolate); { HandleScope inner_scope(isolate); // Allocate a function and keep it in global object's property. Handle function = factory->NewFunctionForTesting(name); Object::SetProperty(isolate, global, name, function).Check(); // Allocate an object. Unrooted after leaving the scope. Handle obj = factory->NewJSObject(function); Object::SetProperty(isolate, obj, prop_name, twenty_three).Check(); Object::SetProperty(isolate, obj, prop_namex, twenty_four).Check(); CHECK_EQ(Smi::FromInt(23), *Object::GetProperty(isolate, obj, prop_name).ToHandleChecked()); CHECK_EQ(Smi::FromInt(24), *Object::GetProperty(isolate, obj, prop_namex).ToHandleChecked()); } heap::CollectGarbage(CcTest::heap(), NEW_SPACE); // Function should be alive. CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, global, name)); // Check function is retained. Handle func_value = Object::GetProperty(isolate, global, name).ToHandleChecked(); CHECK(func_value->IsJSFunction()); Handle function = Handle::cast(func_value); { HandleScope inner_scope(isolate); // Allocate another object, make it reachable from global. Handle obj = factory->NewJSObject(function); Object::SetProperty(isolate, global, obj_name, obj).Check(); Object::SetProperty(isolate, obj, prop_name, twenty_three).Check(); } // After gc, it should survive. heap::CollectGarbage(CcTest::heap(), NEW_SPACE); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, global, obj_name)); Handle obj = Object::GetProperty(isolate, global, obj_name).ToHandleChecked(); CHECK(obj->IsJSObject()); CHECK_EQ(Smi::FromInt(23), *Object::GetProperty(isolate, obj, prop_name).ToHandleChecked()); } static void VerifyStringAllocation(Isolate* isolate, const char* string) { HandleScope scope(isolate); Handle s = isolate->factory() ->NewStringFromUtf8(base::CStrVector(string)) .ToHandleChecked(); CHECK_EQ(strlen(string), s->length()); for (int index = 0; index < s->length(); index++) { CHECK_EQ(static_cast(string[index]), s->Get(index)); } } TEST(String) { CcTest::InitializeVM(); Isolate* isolate = reinterpret_cast(CcTest::isolate()); VerifyStringAllocation(isolate, "a"); VerifyStringAllocation(isolate, "ab"); VerifyStringAllocation(isolate, "abc"); VerifyStringAllocation(isolate, "abcd"); VerifyStringAllocation(isolate, "fiskerdrengen er paa havet"); } TEST(LocalHandles) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope scope(CcTest::isolate()); const char* name = "Kasper the spunky"; Handle string = factory->NewStringFromAsciiChecked(name); CHECK_EQ(strlen(name), string->length()); } TEST(GlobalHandles) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); GlobalHandles* global_handles = isolate->global_handles(); Handle h1; Handle h2; Handle h3; Handle h4; { HandleScope scope(isolate); Handle i = factory->NewStringFromStaticChars("fisk"); Handle u = factory->NewNumber(1.12344); h1 = global_handles->Create(*i); h2 = global_handles->Create(*u); h3 = global_handles->Create(*i); h4 = global_handles->Create(*u); } // after gc, it should survive heap::CollectGarbage(CcTest::heap(), NEW_SPACE); CHECK((*h1).IsString()); CHECK((*h2).IsHeapNumber()); CHECK((*h3).IsString()); CHECK((*h4).IsHeapNumber()); CHECK_EQ(*h3, *h1); GlobalHandles::Destroy(h1.location()); GlobalHandles::Destroy(h3.location()); CHECK_EQ(*h4, *h2); GlobalHandles::Destroy(h2.location()); GlobalHandles::Destroy(h4.location()); } static bool WeakPointerCleared = false; static void TestWeakGlobalHandleCallback( const v8::WeakCallbackInfo& data) { std::pair*, int>* p = reinterpret_cast*, int>*>( data.GetParameter()); if (p->second == 1234) WeakPointerCleared = true; p->first->Reset(); } TEST(WeakGlobalUnmodifiedApiHandlesScavenge) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); LocalContext context; Factory* factory = isolate->factory(); GlobalHandles* global_handles = isolate->global_handles(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( CcTest::heap()); WeakPointerCleared = false; Handle h1; Handle h2; { HandleScope scope(isolate); // Create an Api object that is unmodified. Local function = FunctionTemplate::New(context->GetIsolate()) ->GetFunction(context.local()) .ToLocalChecked(); Local i = function->NewInstance(context.local()).ToLocalChecked(); Handle u = factory->NewNumber(1.12344); h1 = global_handles->Create(*u); h2 = global_handles->Create(*(reinterpret_cast(*i))); } std::pair*, int> handle_and_id(&h2, 1234); GlobalHandles::MakeWeak( h2.location(), reinterpret_cast(&handle_and_id), &TestWeakGlobalHandleCallback, v8::WeakCallbackType::kParameter); heap::CollectGarbage(CcTest::heap(), v8_flags.single_generation ? OLD_SPACE : NEW_SPACE); CHECK((*h1).IsHeapNumber()); CHECK(WeakPointerCleared); GlobalHandles::Destroy(h1.location()); } TEST(WeakGlobalHandlesMark) { ManualGCScope manual_gc_scope; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); GlobalHandles* global_handles = isolate->global_handles(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( CcTest::heap()); WeakPointerCleared = false; Handle h1; Handle h2; { HandleScope scope(isolate); Handle i = factory->NewStringFromStaticChars("fisk"); Handle u = factory->NewNumber(1.12344); h1 = global_handles->Create(*i); h2 = global_handles->Create(*u); } // Make sure the objects are promoted. heap::EmptyNewSpaceUsingGC(isolate->heap()); CHECK(!Heap::InYoungGeneration(*h1) && !Heap::InYoungGeneration(*h2)); std::pair*, int> handle_and_id(&h2, 1234); GlobalHandles::MakeWeak( h2.location(), reinterpret_cast(&handle_and_id), &TestWeakGlobalHandleCallback, v8::WeakCallbackType::kParameter); // Incremental marking potentially marked handles before they turned weak. heap::CollectAllGarbage(CcTest::heap()); CHECK((*h1).IsString()); CHECK(WeakPointerCleared); GlobalHandles::Destroy(h1.location()); } TEST(DeleteWeakGlobalHandle) { v8_flags.stress_compaction = false; v8_flags.stress_incremental_marking = false; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); GlobalHandles* global_handles = isolate->global_handles(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( CcTest::heap()); WeakPointerCleared = false; Handle h; { HandleScope scope(isolate); Handle i = factory->NewStringFromStaticChars("fisk"); h = global_handles->Create(*i); } std::pair*, int> handle_and_id(&h, 1234); GlobalHandles::MakeWeak(h.location(), reinterpret_cast(&handle_and_id), &TestWeakGlobalHandleCallback, v8::WeakCallbackType::kParameter); CHECK(!WeakPointerCleared); heap::CollectGarbage(CcTest::heap(), OLD_SPACE); CHECK(WeakPointerCleared); } TEST(BytecodeArray) { if (!v8_flags.compact) return; static const uint8_t kRawBytes[] = {0xC3, 0x7E, 0xA5, 0x5A}; static const int kRawBytesSize = sizeof(kRawBytes); static const int32_t kFrameSize = 32; static const int32_t kParameterCount = 2; ManualGCScope manual_gc_scope; heap::ManualEvacuationCandidatesSelectionScope manual_evacuation_candidate_selection_scope(manual_gc_scope); CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Heap* heap = isolate->heap(); Factory* factory = isolate->factory(); HandleScope scope(isolate); heap::SimulateFullSpace(heap->old_space()); Handle constant_pool = factory->NewFixedArray(5, AllocationType::kOld); for (int i = 0; i < 5; i++) { Handle number = factory->NewHeapNumber(i); constant_pool->set(i, *number); } // Allocate and initialize BytecodeArray Handle array = factory->NewBytecodeArray( kRawBytesSize, kRawBytes, kFrameSize, kParameterCount, constant_pool); CHECK(array->IsBytecodeArray()); CHECK_EQ(array->length(), (int)sizeof(kRawBytes)); CHECK_EQ(array->frame_size(), kFrameSize); CHECK_EQ(array->parameter_count(), kParameterCount); CHECK_EQ(array->constant_pool(), *constant_pool); CHECK_LE(array->address(), array->GetFirstBytecodeAddress()); CHECK_GE(array->address() + array->BytecodeArraySize(), array->GetFirstBytecodeAddress() + array->length()); for (int i = 0; i < kRawBytesSize; i++) { CHECK_EQ(Memory(array->GetFirstBytecodeAddress() + i), kRawBytes[i]); CHECK_EQ(array->get(i), kRawBytes[i]); } FixedArray old_constant_pool_address = *constant_pool; // Perform a full garbage collection and force the constant pool to be on an // evacuation candidate. Page* evac_page = Page::FromHeapObject(*constant_pool); heap::ForceEvacuationCandidate(evac_page); // We need to invoke GC without stack, otherwise no compaction is performed. DisableConservativeStackScanningScopeForTesting no_stack_scanning(heap); heap::CollectAllGarbage(heap); // BytecodeArray should survive. CHECK_EQ(array->length(), kRawBytesSize); CHECK_EQ(array->frame_size(), kFrameSize); for (int i = 0; i < kRawBytesSize; i++) { CHECK_EQ(array->get(i), kRawBytes[i]); CHECK_EQ(Memory(array->GetFirstBytecodeAddress() + i), kRawBytes[i]); } // Constant pool should have been migrated. CHECK_EQ(array->constant_pool().ptr(), constant_pool->ptr()); CHECK_NE(array->constant_pool().ptr(), old_constant_pool_address.ptr()); } TEST(BytecodeArrayAging) { static const uint8_t kRawBytes[] = {0xC3, 0x7E, 0xA5, 0x5A}; static const int kRawBytesSize = sizeof(kRawBytes); static const int32_t kFrameSize = 32; static const int32_t kParameterCount = 2; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); HandleScope scope(isolate); Handle array = factory->NewBytecodeArray(kRawBytesSize, kRawBytes, kFrameSize, kParameterCount, factory->empty_fixed_array()); CHECK_EQ(0, array->bytecode_age()); array->EnsureOldForTesting(); } static const char* not_so_random_string_table[] = { "abstract", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "var", "void", "volatile", "while", "with", nullptr}; static void CheckInternalizedStrings(const char** strings) { Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); for (const char* string = *strings; *strings != nullptr; string = *strings++) { HandleScope scope(isolate); Handle a = isolate->factory()->InternalizeUtf8String(base::CStrVector(string)); // InternalizeUtf8String may return a failure if a GC is needed. CHECK(a->IsInternalizedString()); Handle b = factory->InternalizeUtf8String(string); CHECK_EQ(*b, *a); CHECK(b->IsOneByteEqualTo(base::CStrVector(string))); b = isolate->factory()->InternalizeUtf8String(base::CStrVector(string)); CHECK_EQ(*b, *a); CHECK(b->IsOneByteEqualTo(base::CStrVector(string))); } } TEST(StringTable) { CcTest::InitializeVM(); v8::HandleScope sc(CcTest::isolate()); CheckInternalizedStrings(not_so_random_string_table); CheckInternalizedStrings(not_so_random_string_table); } TEST(FunctionAllocation) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope sc(CcTest::isolate()); Handle name = factory->InternalizeUtf8String("theFunction"); Handle function = factory->NewFunctionForTesting(name); Handle twenty_three(Smi::FromInt(23), isolate); Handle twenty_four(Smi::FromInt(24), isolate); Handle prop_name = factory->InternalizeUtf8String("theSlot"); Handle obj = factory->NewJSObject(function); Object::SetProperty(isolate, obj, prop_name, twenty_three).Check(); CHECK_EQ(Smi::FromInt(23), *Object::GetProperty(isolate, obj, prop_name).ToHandleChecked()); // Check that we can add properties to function objects. Object::SetProperty(isolate, function, prop_name, twenty_four).Check(); CHECK_EQ( Smi::FromInt(24), *Object::GetProperty(isolate, function, prop_name).ToHandleChecked()); } TEST(ObjectProperties) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope sc(CcTest::isolate()); Handle object_string( String::cast(ReadOnlyRoots(CcTest::heap()).Object_string()), isolate); Handle object = Object::GetProperty(isolate, CcTest::i_isolate()->global_object(), object_string) .ToHandleChecked(); Handle constructor = Handle::cast(object); Handle obj = factory->NewJSObject(constructor); Handle first = factory->InternalizeUtf8String("first"); Handle second = factory->InternalizeUtf8String("second"); Handle one(Smi::FromInt(1), isolate); Handle two(Smi::FromInt(2), isolate); // check for empty CHECK(Just(false) == JSReceiver::HasOwnProperty(isolate, obj, first)); // add first Object::SetProperty(isolate, obj, first, one).Check(); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, obj, first)); // delete first CHECK(Just(true) == JSReceiver::DeleteProperty(obj, first, LanguageMode::kSloppy)); CHECK(Just(false) == JSReceiver::HasOwnProperty(isolate, obj, first)); // add first and then second Object::SetProperty(isolate, obj, first, one).Check(); Object::SetProperty(isolate, obj, second, two).Check(); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, obj, first)); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, obj, second)); // delete first and then second CHECK(Just(true) == JSReceiver::DeleteProperty(obj, first, LanguageMode::kSloppy)); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, obj, second)); CHECK(Just(true) == JSReceiver::DeleteProperty(obj, second, LanguageMode::kSloppy)); CHECK(Just(false) == JSReceiver::HasOwnProperty(isolate, obj, first)); CHECK(Just(false) == JSReceiver::HasOwnProperty(isolate, obj, second)); // add first and then second Object::SetProperty(isolate, obj, first, one).Check(); Object::SetProperty(isolate, obj, second, two).Check(); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, obj, first)); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, obj, second)); // delete second and then first CHECK(Just(true) == JSReceiver::DeleteProperty(obj, second, LanguageMode::kSloppy)); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, obj, first)); CHECK(Just(true) == JSReceiver::DeleteProperty(obj, first, LanguageMode::kSloppy)); CHECK(Just(false) == JSReceiver::HasOwnProperty(isolate, obj, first)); CHECK(Just(false) == JSReceiver::HasOwnProperty(isolate, obj, second)); // check string and internalized string match const char* string1 = "fisk"; Handle s1 = factory->NewStringFromAsciiChecked(string1); Object::SetProperty(isolate, obj, s1, one).Check(); Handle s1_string = factory->InternalizeUtf8String(string1); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, obj, s1_string)); // check internalized string and string match const char* string2 = "fugl"; Handle s2_string = factory->InternalizeUtf8String(string2); Object::SetProperty(isolate, obj, s2_string, one).Check(); Handle s2 = factory->NewStringFromAsciiChecked(string2); CHECK(Just(true) == JSReceiver::HasOwnProperty(isolate, obj, s2)); } TEST(JSObjectMaps) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope sc(CcTest::isolate()); Handle name = factory->InternalizeUtf8String("theFunction"); Handle function = factory->NewFunctionForTesting(name); Handle prop_name = factory->InternalizeUtf8String("theSlot"); Handle obj = factory->NewJSObject(function); Handle initial_map(function->initial_map(), isolate); // Set a propery Handle twenty_three(Smi::FromInt(23), isolate); Object::SetProperty(isolate, obj, prop_name, twenty_three).Check(); CHECK_EQ(Smi::FromInt(23), *Object::GetProperty(isolate, obj, prop_name).ToHandleChecked()); // Check the map has changed CHECK(*initial_map != obj->map()); } TEST(JSArray) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope sc(CcTest::isolate()); Handle name = factory->InternalizeUtf8String("Array"); Handle fun_obj = Object::GetProperty(isolate, CcTest::i_isolate()->global_object(), name) .ToHandleChecked(); Handle function = Handle::cast(fun_obj); // Allocate the object. Handle element; Handle object = factory->NewJSObject(function); Handle array = Handle::cast(object); // We just initialized the VM, no heap allocation failure yet. JSArray::Initialize(array, 0); // Set array length to 0. JSArray::SetLength(array, 0); CHECK_EQ(Smi::zero(), array->length()); // Must be in fast mode. CHECK(array->HasSmiOrObjectElements()); // array[length] = name. Object::SetElement(isolate, array, 0, name, ShouldThrow::kDontThrow).Check(); CHECK_EQ(Smi::FromInt(1), array->length()); element = i::Object::GetElement(isolate, array, 0).ToHandleChecked(); CHECK_EQ(*element, *name); // Set array length with larger than smi value. JSArray::SetLength(array, static_cast(Smi::kMaxValue) + 1); uint32_t int_length = 0; CHECK(array->length().ToArrayIndex(&int_length)); CHECK_EQ(static_cast(Smi::kMaxValue) + 1, int_length); CHECK(array->HasDictionaryElements()); // Must be in slow mode. // array[length] = name. Object::SetElement(isolate, array, int_length, name, ShouldThrow::kDontThrow) .Check(); uint32_t new_int_length = 0; CHECK(array->length().ToArrayIndex(&new_int_length)); CHECK_EQ(static_cast(int_length), new_int_length - 1); element = Object::GetElement(isolate, array, int_length).ToHandleChecked(); CHECK_EQ(*element, *name); element = Object::GetElement(isolate, array, 0).ToHandleChecked(); CHECK_EQ(*element, *name); } TEST(JSObjectCopy) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope sc(CcTest::isolate()); Handle object_string( String::cast(ReadOnlyRoots(CcTest::heap()).Object_string()), isolate); Handle object = Object::GetProperty(isolate, CcTest::i_isolate()->global_object(), object_string) .ToHandleChecked(); Handle constructor = Handle::cast(object); Handle obj = factory->NewJSObject(constructor); Handle first = factory->InternalizeUtf8String("first"); Handle second = factory->InternalizeUtf8String("second"); Handle one(Smi::FromInt(1), isolate); Handle two(Smi::FromInt(2), isolate); Object::SetProperty(isolate, obj, first, one).Check(); Object::SetProperty(isolate, obj, second, two).Check(); Object::SetElement(isolate, obj, 0, first, ShouldThrow::kDontThrow).Check(); Object::SetElement(isolate, obj, 1, second, ShouldThrow::kDontThrow).Check(); // Make the clone. Handle value1, value2; Handle clone = factory->CopyJSObject(obj); CHECK(!clone.is_identical_to(obj)); value1 = Object::GetElement(isolate, obj, 0).ToHandleChecked(); value2 = Object::GetElement(isolate, clone, 0).ToHandleChecked(); CHECK_EQ(*value1, *value2); value1 = Object::GetElement(isolate, obj, 1).ToHandleChecked(); value2 = Object::GetElement(isolate, clone, 1).ToHandleChecked(); CHECK_EQ(*value1, *value2); value1 = Object::GetProperty(isolate, obj, first).ToHandleChecked(); value2 = Object::GetProperty(isolate, clone, first).ToHandleChecked(); CHECK_EQ(*value1, *value2); value1 = Object::GetProperty(isolate, obj, second).ToHandleChecked(); value2 = Object::GetProperty(isolate, clone, second).ToHandleChecked(); CHECK_EQ(*value1, *value2); // Flip the values. Object::SetProperty(isolate, clone, first, two).Check(); Object::SetProperty(isolate, clone, second, one).Check(); Object::SetElement(isolate, clone, 0, second, ShouldThrow::kDontThrow) .Check(); Object::SetElement(isolate, clone, 1, first, ShouldThrow::kDontThrow).Check(); value1 = Object::GetElement(isolate, obj, 1).ToHandleChecked(); value2 = Object::GetElement(isolate, clone, 0).ToHandleChecked(); CHECK_EQ(*value1, *value2); value1 = Object::GetElement(isolate, obj, 0).ToHandleChecked(); value2 = Object::GetElement(isolate, clone, 1).ToHandleChecked(); CHECK_EQ(*value1, *value2); value1 = Object::GetProperty(isolate, obj, second).ToHandleChecked(); value2 = Object::GetProperty(isolate, clone, first).ToHandleChecked(); CHECK_EQ(*value1, *value2); value1 = Object::GetProperty(isolate, obj, first).ToHandleChecked(); value2 = Object::GetProperty(isolate, clone, second).ToHandleChecked(); CHECK_EQ(*value1, *value2); } TEST(StringAllocation) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); const unsigned char chars[] = {0xE5, 0xA4, 0xA7}; for (int length = 0; length < 100; length++) { v8::HandleScope scope(CcTest::isolate()); char* non_one_byte = NewArray(3 * length + 1); char* one_byte = NewArray(length + 1); non_one_byte[3 * length] = 0; one_byte[length] = 0; for (int i = 0; i < length; i++) { one_byte[i] = 'a'; non_one_byte[3 * i] = chars[0]; non_one_byte[3 * i + 1] = chars[1]; non_one_byte[3 * i + 2] = chars[2]; } Handle non_one_byte_sym = factory->InternalizeUtf8String( base::Vector(non_one_byte, 3 * length)); CHECK_EQ(length, non_one_byte_sym->length()); Handle one_byte_sym = factory->InternalizeString(base::OneByteVector(one_byte, length)); CHECK_EQ(length, one_byte_sym->length()); CHECK(one_byte_sym->HasHashCode()); Handle non_one_byte_str = factory ->NewStringFromUtf8( base::Vector(non_one_byte, 3 * length)) .ToHandleChecked(); CHECK_EQ(length, non_one_byte_str->length()); Handle one_byte_str = factory->NewStringFromUtf8(base::Vector(one_byte, length)) .ToHandleChecked(); CHECK_EQ(length, one_byte_str->length()); DeleteArray(non_one_byte); DeleteArray(one_byte); } } static int ObjectsFoundInHeap(Heap* heap, Handle objs[], int size) { // Count the number of objects found in the heap. int found_count = 0; HeapObjectIterator iterator(heap); for (HeapObject obj = iterator.Next(); !obj.is_null(); obj = iterator.Next()) { for (int i = 0; i < size; i++) { // V8_EXTERNAL_CODE_SPACE specific: we might be comparing // InstructionStream object with non-InstructionStream object here and it // might produce false positives because operator== for tagged values // compares only lower 32 bits when pointer compression is enabled. if (objs[i]->ptr() == obj.ptr()) { found_count++; } } } return found_count; } TEST(Iteration) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope scope(CcTest::isolate()); // Array of objects to scan haep for. const int objs_count = 6; Handle objs[objs_count]; int next_objs_index = 0; // Allocate a JS array to OLD_SPACE and NEW_SPACE objs[next_objs_index++] = factory->NewJSArray(10); objs[next_objs_index++] = factory->NewJSArray(10, HOLEY_ELEMENTS, AllocationType::kOld); // Allocate a small string to OLD_DATA_SPACE and NEW_SPACE objs[next_objs_index++] = factory->NewStringFromStaticChars("abcdefghij"); objs[next_objs_index++] = factory->NewStringFromStaticChars("abcdefghij", AllocationType::kOld); // Allocate a large string (for large object space). int large_size = kMaxRegularHeapObjectSize + 1; char* str = new char[large_size]; for (int i = 0; i < large_size - 1; ++i) str[i] = 'a'; str[large_size - 1] = '\0'; objs[next_objs_index++] = factory->NewStringFromAsciiChecked(str, AllocationType::kOld); delete[] str; // Add a Map object to look for. objs[next_objs_index++] = Handle(HeapObject::cast(*objs[0]).map(), isolate); CHECK_EQ(objs_count, next_objs_index); CHECK_EQ(objs_count, ObjectsFoundInHeap(CcTest::heap(), objs, objs_count)); } TEST(TestBytecodeFlushing) { #if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN) v8_flags.turbofan = false; v8_flags.always_turbofan = false; i::v8_flags.optimize_for_size = false; #endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN) #if ENABLE_SPARKPLUG v8_flags.always_sparkplug = false; #endif // ENABLE_SPARKPLUG i::v8_flags.flush_bytecode = true; i::v8_flags.allow_natives_syntax = true; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); Isolate* i_isolate = CcTest::i_isolate(); Factory* factory = i_isolate->factory(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( CcTest::heap()); { v8::HandleScope scope(isolate); v8::Context::New(isolate)->Enter(); const char* source = "function foo() {" " var x = 42;" " var y = 42;" " var z = x + y;" "};" "foo()"; Handle foo_name = factory->InternalizeUtf8String("foo"); // This compile will add the code to the compilation cache. { v8::HandleScope new_scope(isolate); CompileRun(source); } // Check function is compiled. Handle func_value = Object::GetProperty(i_isolate, i_isolate->global_object(), foo_name) .ToHandleChecked(); CHECK(func_value->IsJSFunction()); Handle function = Handle::cast(func_value); CHECK(function->shared().is_compiled()); // The code will survive at least two GCs. heap::CollectAllGarbage(CcTest::heap()); heap::CollectAllGarbage(CcTest::heap()); CHECK(function->shared().is_compiled()); function->shared().GetBytecodeArray(i_isolate).EnsureOldForTesting(); heap::CollectAllGarbage(CcTest::heap()); // foo should no longer be in the compilation cache CHECK(!function->shared().is_compiled()); CHECK(!function->is_compiled()); // Call foo to get it recompiled. CompileRun("foo()"); CHECK(function->shared().is_compiled()); CHECK(function->is_compiled()); } } static void TestMultiReferencedBytecodeFlushing(bool sparkplug_compile) { #if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN) v8_flags.turbofan = false; v8_flags.always_turbofan = false; i::v8_flags.optimize_for_size = false; #endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN) #if ENABLE_SPARKPLUG v8_flags.always_sparkplug = false; v8_flags.flush_baseline_code = true; #else if (sparkplug_compile) return; #endif // ENABLE_SPARKPLUG i::v8_flags.flush_bytecode = true; i::v8_flags.allow_natives_syntax = true; ManualGCScope manual_gc_scope; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); Isolate* i_isolate = CcTest::i_isolate(); Factory* factory = i_isolate->factory(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( CcTest::heap()); { v8::HandleScope scope(isolate); v8::Context::New(isolate)->Enter(); const char* source = "function foo() {" " var x = 42;" " var y = 42;" " var z = x + y;" "};" "foo()"; Handle foo_name = factory->InternalizeUtf8String("foo"); // This compile will add the code to the compilation cache. { v8::HandleScope new_scope(isolate); CompileRun(source); } // Check function is compiled. Handle func_value = Object::GetProperty(i_isolate, i_isolate->global_object(), foo_name) .ToHandleChecked(); CHECK(func_value->IsJSFunction()); Handle function = Handle::cast(func_value); Handle shared = handle(function->shared(), i_isolate); CHECK(shared->is_compiled()); // Make a copy of the SharedFunctionInfo which points to the same bytecode. Handle copy = i_isolate->factory()->CloneSharedFunctionInfo(shared); if (sparkplug_compile) { v8::HandleScope baseline_compilation_scope(isolate); IsCompiledScope is_compiled_scope = copy->is_compiled_scope(i_isolate); Compiler::CompileSharedWithBaseline( i_isolate, copy, Compiler::CLEAR_EXCEPTION, &is_compiled_scope); } shared->GetBytecodeArray(i_isolate).EnsureOldForTesting(); heap::CollectAllGarbage(CcTest::heap()); // foo should no longer be in the compilation cache CHECK(!shared->is_compiled()); CHECK(!copy->is_compiled()); CHECK(!function->is_compiled()); // The feedback metadata for both SharedFunctionInfo instances should have // been reset. CHECK(!shared->HasFeedbackMetadata()); CHECK(!copy->HasFeedbackMetadata()); } } TEST(TestMultiReferencedBytecodeFlushing) { TestMultiReferencedBytecodeFlushing(/*sparkplug_compile=*/false); } TEST(TestMultiReferencedBytecodeFlushingWithSparkplug) { TestMultiReferencedBytecodeFlushing(/*sparkplug_compile=*/true); } HEAP_TEST(Regress10560) { i::v8_flags.flush_bytecode = true; i::v8_flags.allow_natives_syntax = true; // Disable flags that allocate a feedback vector eagerly. #if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN) i::v8_flags.turbofan = false; i::v8_flags.always_turbofan = false; #endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN) #if ENABLE_SPARKPLUG v8_flags.always_sparkplug = false; #endif // ENABLE_SPARKPLUG i::v8_flags.lazy_feedback_allocation = true; ManualGCScope manual_gc_scope; CcTest::InitializeVM(); v8::Isolate* isolate = CcTest::isolate(); Isolate* i_isolate = CcTest::i_isolate(); Factory* factory = i_isolate->factory(); Heap* heap = i_isolate->heap(); { v8::HandleScope scope(isolate); const char* source = "function foo() {" " var x = 42;" " var y = 42;" " var z = x + y;" "};" "foo()"; Handle foo_name = factory->InternalizeUtf8String("foo"); CompileRun(source); // Check function is compiled. Handle func_value = Object::GetProperty(i_isolate, i_isolate->global_object(), foo_name) .ToHandleChecked(); CHECK(func_value->IsJSFunction()); Handle function = Handle::cast(func_value); CHECK(function->shared().is_compiled()); CHECK(!function->has_feedback_vector()); // Pre-age bytecode so it will be flushed on next run. CHECK(function->shared().HasBytecodeArray()); function->shared().GetBytecodeArray(i_isolate).EnsureOldForTesting(); CHECK(function->shared().GetBytecodeArray(i_isolate).IsOld()); heap::SimulateFullSpace(heap->old_space()); // Just check bytecode isn't flushed still CHECK(function->shared().GetBytecodeArray(i_isolate).IsOld()); CHECK(function->shared().is_compiled()); heap->set_force_gc_on_next_allocation(); // Allocate feedback vector. IsCompiledScope is_compiled_scope( function->shared().is_compiled_scope(i_isolate)); JSFunction::EnsureFeedbackVector(i_isolate, function, &is_compiled_scope); CHECK(function->has_feedback_vector()); CHECK(function->shared().is_compiled()); CHECK(function->is_compiled()); } } UNINITIALIZED_TEST(Regress10843) { v8_flags.max_semi_space_size = 2; v8_flags.min_semi_space_size = 2; v8_flags.max_old_space_size = 8; v8_flags.compact_on_every_full_gc = true; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); Isolate* i_isolate = reinterpret_cast(isolate); Factory* factory = i_isolate->factory(); Heap* heap = i_isolate->heap(); bool callback_was_invoked = false; heap->AddNearHeapLimitCallback( [](void* data, size_t current_heap_limit, size_t initial_heap_limit) -> size_t { *reinterpret_cast(data) = true; return current_heap_limit * 2; }, &callback_was_invoked); { HandleScope scope(i_isolate); std::vector> arrays; for (int i = 0; i < 140; i++) { arrays.push_back(factory->NewFixedArray(10000)); } heap::CollectAllGarbage(heap); heap::CollectAllGarbage(heap); for (int i = 0; i < 40; i++) { arrays.push_back(factory->NewFixedArray(10000)); } heap::CollectAllGarbage(heap); for (int i = 0; i < 100; i++) { arrays.push_back(factory->NewFixedArray(10000)); } heap::CollectAllGarbage(heap); CHECK(callback_was_invoked); } isolate->Dispose(); } size_t near_heap_limit_invocation_count = 0; size_t InvokeGCNearHeapLimitCallback(void* data, size_t current_heap_limit, size_t initial_heap_limit) { near_heap_limit_invocation_count++; if (near_heap_limit_invocation_count > 1) { // We are already in a GC triggered in this callback, raise the limit // to avoid an OOM. return current_heap_limit * 5; } DCHECK_EQ(near_heap_limit_invocation_count, 1); // Operations that may cause GC (e.g. taking heap snapshots) in the // near heap limit callback should not hit the AllowGarbageCollection // assertion. static_cast(data)->GetHeapProfiler()->TakeHeapSnapshot(); return current_heap_limit * 5; } UNINITIALIZED_TEST(Regress12777) { v8::Isolate::CreateParams create_params; create_params.constraints.set_max_old_generation_size_in_bytes(10 * i::MB); create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* isolate = v8::Isolate::New(create_params); isolate->AddNearHeapLimitCallback(InvokeGCNearHeapLimitCallback, isolate); { v8::Isolate::Scope isolate_scope(isolate); Isolate* i_isolate = reinterpret_cast(isolate); // Allocate data to trigger the NearHeapLimitCallback. HandleScope scope(i_isolate); int length = 2 * i::MB / i::kTaggedSize; std::vector> arrays; for (int i = 0; i < 5; i++) { arrays.push_back(i_isolate->factory()->NewFixedArray(length)); } heap::CollectAllGarbage(i_isolate->heap()); for (int i = 0; i < 5; i++) { arrays.push_back(i_isolate->factory()->NewFixedArray(length)); } heap::CollectAllGarbage(i_isolate->heap()); for (int i = 0; i < 5; i++) { arrays.push_back(i_isolate->factory()->NewFixedArray(length)); } // Normally, taking a heap snapshot in the near heap limit would result in // a full GC, then the overhead of the promotions would cause another // invocation of the heap limit callback and it can raise the limit in // the second call to avoid an OOM, so we test that the callback can // indeed raise the limit this way in this case. When there is only one // generation, however, there would not be the overhead of promotions so the // callback may not be triggered again during the generation of the heap // snapshot. In that case we only need to check that the callback is called // and it can perform GC-triggering operations jsut fine there. size_t minimum_callback_invocation_count = v8_flags.single_generation ? 1 : 2; CHECK_GE(near_heap_limit_invocation_count, minimum_callback_invocation_count); } isolate->GetHeapProfiler()->DeleteAllHeapSnapshots(); isolate->Dispose(); } #if !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN) TEST(TestOptimizeAfterBytecodeFlushingCandidate) { if (v8_flags.single_generation) return; v8_flags.turbofan = true; v8_flags.always_turbofan = false; #if ENABLE_SPARKPLUG v8_flags.always_sparkplug = false; #endif // ENABLE_SPARKPLUG i::v8_flags.optimize_for_size = false; i::v8_flags.incremental_marking = true; i::v8_flags.flush_bytecode = true; i::v8_flags.allow_natives_syntax = true; ManualGCScope manual_gc_scope; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( CcTest::heap()); v8::HandleScope outer_scope(CcTest::isolate()); const char* source = "function foo() {" " var x = 42;" " var y = 42;" " var z = x + y;" "};" "foo()"; Handle foo_name = factory->InternalizeUtf8String("foo"); // This compile will add the code to the compilation cache. { v8::HandleScope scope(CcTest::isolate()); CompileRun(source); } // Check function is compiled. Handle func_value = Object::GetProperty(isolate, isolate->global_object(), foo_name) .ToHandleChecked(); CHECK(func_value->IsJSFunction()); Handle function = Handle::cast(func_value); CHECK(function->shared().is_compiled()); // The code will survive at least two GCs. heap::CollectAllGarbage(CcTest::heap()); heap::CollectAllGarbage(CcTest::heap()); CHECK(function->shared().is_compiled()); function->shared().GetBytecodeArray(isolate).EnsureOldForTesting(); heap::CollectAllGarbage(CcTest::heap()); CHECK(!function->shared().is_compiled()); CHECK(!function->is_compiled()); // This compile will compile the function again. { v8::HandleScope scope(CcTest::isolate()); CompileRun("foo();"); } function->shared().GetBytecodeArray(isolate).EnsureOldForTesting(); heap::SimulateIncrementalMarking(CcTest::heap()); // Force optimization while incremental marking is active and while // the function is enqueued as a candidate. { v8::HandleScope scope(CcTest::isolate()); CompileRun( "%PrepareFunctionForOptimization(foo);" "%OptimizeFunctionOnNextCall(foo); foo();"); } // Simulate one final GC and make sure the candidate wasn't flushed. heap::CollectAllGarbage(CcTest::heap()); CHECK(function->shared().is_compiled()); CHECK(function->is_compiled()); } #endif // !defined(V8_LITE_MODE) && defined(V8_ENABLE_TURBOFAN) TEST(TestUseOfIncrementalBarrierOnCompileLazy) { if (!v8_flags.incremental_marking) return; // Turn off always_turbofan because it interferes with running the built-in // for the last call to g(). v8_flags.always_turbofan = false; v8_flags.allow_natives_syntax = true; CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); Heap* heap = isolate->heap(); v8::HandleScope scope(CcTest::isolate()); CompileRun( "function make_closure(x) {" " return function() { return x + 3 };" "}" "var f = make_closure(5);" "%PrepareFunctionForOptimization(f); f();" "var g = make_closure(5);"); // Check f is compiled. Handle f_name = factory->InternalizeUtf8String("f"); Handle f_value = Object::GetProperty(isolate, isolate->global_object(), f_name) .ToHandleChecked(); Handle f_function = Handle::cast(f_value); CHECK(f_function->is_compiled()); // Check g is not compiled. Handle g_name = factory->InternalizeUtf8String("g"); Handle g_value = Object::GetProperty(isolate, isolate->global_object(), g_name) .ToHandleChecked(); Handle g_function = Handle::cast(g_value); CHECK(!g_function->is_compiled()); heap::SimulateIncrementalMarking(heap); CompileRun("%OptimizeFunctionOnNextCall(f); f();"); // g should now have available an optimized function, unmarked by gc. The // CompileLazy built-in will discover it and install it in the closure, and // the incremental write barrier should be used. CompileRun("g();"); CHECK(g_function->is_compiled()); } void CompilationCacheCachingBehavior(bool retain_script) { // If we do not have the compilation cache turned off, this test is invalid. if (!v8_flags.compilation_cache) { return; } if (!v8_flags.flush_bytecode || (v8_flags.always_sparkplug && !v8_flags.flush_baseline_code)) { return; } CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); CompilationCache* compilation_cache = isolate->compilation_cache(); LanguageMode language_mode = LanguageMode::kSloppy; DisableConservativeStackScanningScopeForTesting no_stack_scanning( CcTest::heap()); v8::HandleScope outer_scope(CcTest::isolate()); const char* raw_source = retain_script ? "function foo() {" " var x = 42;" " var y = 42;" " var z = x + y;" "};" "foo();" : "(function foo() {" " var x = 42;" " var y = 42;" " var z = x + y;" "})();"; Handle source = factory->InternalizeUtf8String(raw_source); { v8::HandleScope scope(CcTest::isolate()); CompileRun(raw_source); } // The script should be in the cache now. { v8::HandleScope scope(CcTest::isolate()); ScriptDetails script_details(Handle(), v8::ScriptOriginOptions(true, false)); auto lookup_result = compilation_cache->LookupScript(source, script_details, language_mode); CHECK(!lookup_result.toplevel_sfi().is_null()); } // Check that the code cache entry survives at least one GC. { heap::CollectAllGarbage(CcTest::heap()); v8::HandleScope scope(CcTest::isolate()); ScriptDetails script_details(Handle(), v8::ScriptOriginOptions(true, false)); auto lookup_result = compilation_cache->LookupScript(source, script_details, language_mode); CHECK(!lookup_result.toplevel_sfi().is_null()); // Progress code age until it's old and ready for GC. Handle shared = lookup_result.toplevel_sfi().ToHandleChecked(); CHECK(shared->HasBytecodeArray()); shared->GetBytecodeArray(CcTest::i_isolate()).EnsureOldForTesting(); } // The first GC flushes the BytecodeArray from the SFI. heap::CollectAllGarbage(CcTest::heap()); // The second GC removes the SFI from the compilation cache. heap::CollectAllGarbage(CcTest::heap()); { v8::HandleScope scope(CcTest::isolate()); // Ensure code aging cleared the entry from the cache. ScriptDetails script_details(Handle(), v8::ScriptOriginOptions(true, false)); auto lookup_result = compilation_cache->LookupScript(source, script_details, language_mode); CHECK(lookup_result.toplevel_sfi().is_null()); CHECK_EQ(retain_script, !lookup_result.script().is_null()); } } TEST(CompilationCacheCachingBehaviorDiscardScript) { CompilationCacheCachingBehavior(false); } TEST(CompilationCacheCachingBehaviorRetainScript) { CompilationCacheCachingBehavior(true); } namespace { template Handle GetSharedFunctionInfo( v8::Local function_or_script) { Handle i_function = Handle::cast(v8::Utils::OpenHandle(*function_or_script)); return handle(i_function->shared(), CcTest::i_isolate()); } template void AgeBytecode(v8::Local function_or_script) { Handle shared = GetSharedFunctionInfo(function_or_script); CHECK(shared->HasBytecodeArray()); shared->GetBytecodeArray(CcTest::i_isolate()).EnsureOldForTesting(); } void CompilationCacheRegeneration(bool retain_root_sfi, bool flush_root_sfi, bool flush_eager_sfi) { // If the compilation cache is turned off, this test is invalid. if (!v8_flags.compilation_cache) { return; } // Skip test if code flushing was disabled. if (!v8_flags.flush_bytecode || (v8_flags.always_sparkplug && !v8_flags.flush_baseline_code)) { return; } CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); DisableConservativeStackScanningScopeForTesting no_stack_scanning( CcTest::heap()); const char* source = "({" " lazyFunction: function () {" " var x = 42;" " var y = 42;" " var z = x + y;" " }," " eagerFunction: (function () {" " var x = 43;" " var y = 43;" " var z = x + y;" " })" "})"; v8::Global outer_function; v8::Global lazy_function; v8::Global eager_function; { v8::HandleScope scope(CcTest::isolate()); v8::Local context = v8::Isolate::GetCurrent()->GetCurrentContext(); v8::Local script = v8_compile(v8_str(source)); outer_function.Reset(CcTest::isolate(), script); // Even though the script has not executed, it should already be parsed. Handle script_sfi = GetSharedFunctionInfo(script); CHECK(script_sfi->is_compiled()); v8::Local result = script->Run(context).ToLocalChecked(); // Now that the script has run, we can get references to the inner // functions, and verify that the eager parsing heuristics are behaving as // expected. v8::Local result_obj = result->ToObject(context).ToLocalChecked(); v8::Local lazy_function_value = result_obj->GetRealNamedProperty(context, v8_str("lazyFunction")) .ToLocalChecked(); CHECK(lazy_function_value->IsFunction()); CHECK(!GetSharedFunctionInfo(lazy_function_value)->is_compiled()); lazy_function.Reset(CcTest::isolate(), lazy_function_value.As()); v8::Local eager_function_value = result_obj->GetRealNamedProperty(context, v8_str("eagerFunction")) .ToLocalChecked(); CHECK(eager_function_value->IsFunction()); eager_function.Reset(CcTest::isolate(), eager_function_value.As()); CHECK(GetSharedFunctionInfo(eager_function_value)->is_compiled()); } { v8::HandleScope scope(CcTest::isolate()); // Progress code age until it's old and ready for GC. if (flush_root_sfi) { v8::Local outer_function_value = outer_function.Get(CcTest::isolate()); AgeBytecode(outer_function_value); } if (flush_eager_sfi) { v8::Local eager_function_value = eager_function.Get(CcTest::isolate()); AgeBytecode(eager_function_value); } if (!retain_root_sfi) { outer_function.Reset(); } } if (v8_flags.stress_incremental_marking) { // This GC finishes incremental marking if it is already running. If // incremental marking was already running we would not flush the code right // away. heap::CollectAllGarbage(CcTest::heap()); } // The first GC performs code flushing. heap::CollectAllGarbage(CcTest::heap()); // The second GC clears the entry from the compilation cache. heap::CollectAllGarbage(CcTest::heap()); // The root SharedFunctionInfo can be retained either by a Global in this // function or by the compilation cache. bool root_sfi_should_still_exist = retain_root_sfi || !flush_root_sfi; { v8::HandleScope scope(CcTest::isolate()); // The lazy function should still not be compiled. Handle lazy_sfi = GetSharedFunctionInfo(lazy_function.Get(CcTest::isolate())); CHECK(!lazy_sfi->is_compiled()); // The eager function may have had its bytecode flushed. Handle eager_sfi = GetSharedFunctionInfo(eager_function.Get(CcTest::isolate())); CHECK_EQ(!flush_eager_sfi, eager_sfi->is_compiled()); // Check whether the root SharedFunctionInfo is still reachable from the // Script. Handle