// Copyright 2016 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 #include #include #include "src/deoptimizer/deoptimizer.h" #include "src/objects/objects-inl.h" #include "src/objects/objects.h" #include "src/objects/osr-optimized-code-cache.h" #include "test/unittests/test-utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace v8 { namespace internal { namespace { const char* code_template_string = "function f%d() { return 0; };" "%%PrepareFunctionForOptimization(f%d);" "f%d(); f%d();" "%%OptimizeFunctionOnNextCall(f%d);" "f%d(); f%d;"; void GetSource(i::ScopedVector* source, int index) { i::SNPrintF(*source, code_template_string, index, index, index, index, index, index, index); } const int kInitialLength = OSROptimizedCodeCache::kInitialLength; const int kInitialEntries = kInitialLength / OSROptimizedCodeCache::kEntryLength; const int kMaxLength = OSROptimizedCodeCache::kMaxLength; const int kMaxEntries = kMaxLength / OSROptimizedCodeCache::kEntryLength; } // namespace TEST_F(TestWithNativeContext, AddCodeToEmptyCache) { if (!i::FLAG_opt) return; i::FLAG_allow_natives_syntax = true; i::ScopedVector source(1024); GetSource(&source, 0); Handle function = RunJS(source.begin()); Isolate* isolate = function->GetIsolate(); Handle native_context(function->native_context(), isolate); Handle shared(function->shared(), isolate); Handle code(function->code(), isolate); BailoutId bailout_id(1); OSROptimizedCodeCache::AddOptimizedCode(native_context, shared, code, bailout_id); Handle osr_cache( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), kInitialLength); HeapObject sfi_entry; osr_cache->Get(OSROptimizedCodeCache::kSharedOffset) ->GetHeapObject(&sfi_entry); EXPECT_EQ(sfi_entry, *shared); HeapObject code_entry; osr_cache->Get(OSROptimizedCodeCache::kCachedCodeOffset) ->GetHeapObject(&code_entry); EXPECT_EQ(code_entry, *code); Smi osr_offset_entry; osr_cache->Get(OSROptimizedCodeCache::kOsrIdOffset)->ToSmi(&osr_offset_entry); EXPECT_EQ(osr_offset_entry.value(), bailout_id.ToInt()); } TEST_F(TestWithNativeContext, GrowCodeCache) { if (!i::FLAG_opt) return; i::FLAG_allow_natives_syntax = true; i::ScopedVector source(1024); GetSource(&source, 0); Handle function = RunJS(source.begin()); Isolate* isolate = function->GetIsolate(); Handle native_context(function->native_context(), isolate); Handle shared(function->shared(), isolate); Handle code(function->code(), isolate); int bailout_id = 0; for (bailout_id = 0; bailout_id < kInitialEntries; bailout_id++) { OSROptimizedCodeCache::AddOptimizedCode(native_context, shared, code, BailoutId(bailout_id)); } Handle osr_cache( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), kInitialLength); OSROptimizedCodeCache::AddOptimizedCode(native_context, shared, code, BailoutId(bailout_id)); osr_cache = Handle( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), kInitialLength * 2); int index = kInitialLength; HeapObject sfi_entry; osr_cache->Get(index + OSROptimizedCodeCache::kSharedOffset) ->GetHeapObject(&sfi_entry); EXPECT_EQ(sfi_entry, *shared); HeapObject code_entry; osr_cache->Get(index + OSROptimizedCodeCache::kCachedCodeOffset) ->GetHeapObject(&code_entry); EXPECT_EQ(code_entry, *code); Smi osr_offset_entry; osr_cache->Get(index + OSROptimizedCodeCache::kOsrIdOffset) ->ToSmi(&osr_offset_entry); EXPECT_EQ(osr_offset_entry.value(), bailout_id); } TEST_F(TestWithNativeContext, FindCachedEntry) { if (!i::FLAG_opt) return; i::FLAG_allow_natives_syntax = true; i::ScopedVector source(1024); GetSource(&source, 0); Handle function = RunJS(source.begin()); Isolate* isolate = function->GetIsolate(); Handle native_context(function->native_context(), isolate); Handle shared(function->shared(), isolate); Handle code(function->code(), isolate); int bailout_id = 0; for (bailout_id = 0; bailout_id < kInitialEntries; bailout_id++) { OSROptimizedCodeCache::AddOptimizedCode(native_context, shared, code, BailoutId(bailout_id)); } i::ScopedVector source1(1024); GetSource(&source1, 1); Handle function1 = RunJS(source1.begin()); Handle shared1(function1->shared(), isolate); Handle code1(function1->code(), isolate); OSROptimizedCodeCache::AddOptimizedCode(native_context, shared1, code1, BailoutId(bailout_id)); Handle osr_cache( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->GetOptimizedCode(shared, BailoutId(0), isolate), *code); EXPECT_EQ( osr_cache->GetOptimizedCode(shared1, BailoutId(bailout_id), isolate), *code1); RunJS("%DeoptimizeFunction(f1)"); EXPECT_TRUE( osr_cache->GetOptimizedCode(shared1, BailoutId(bailout_id), isolate) .is_null()); osr_cache->Set(OSROptimizedCodeCache::kCachedCodeOffset, HeapObjectReference::ClearedValue(isolate)); EXPECT_TRUE( osr_cache->GetOptimizedCode(shared, BailoutId(0), isolate).is_null()); } TEST_F(TestWithNativeContext, MaxCapacityCache) { if (!i::FLAG_opt) return; i::FLAG_allow_natives_syntax = true; i::ScopedVector source(1024); GetSource(&source, 0); Handle function = RunJS(source.begin()); Isolate* isolate = function->GetIsolate(); Handle native_context(function->native_context(), isolate); Handle shared(function->shared(), isolate); Handle code(function->code(), isolate); int bailout_id = 0; // Add max_capacity - 1 entries. for (bailout_id = 0; bailout_id < kMaxEntries - 1; bailout_id++) { OSROptimizedCodeCache::AddOptimizedCode(native_context, shared, code, BailoutId(bailout_id)); } Handle osr_cache( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), kMaxLength); // Add an entry to reach max capacity. i::ScopedVector source1(1024); GetSource(&source1, 1); Handle function1 = RunJS(source1.begin()); Handle shared1(function1->shared(), isolate); Handle code1(function1->code(), isolate); OSROptimizedCodeCache::AddOptimizedCode(native_context, shared1, code1, BailoutId(bailout_id)); osr_cache = Handle( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), kMaxLength); int index = (kMaxEntries - 1) * OSROptimizedCodeCache::kEntryLength; HeapObject object; Smi smi; osr_cache->Get(index + OSROptimizedCodeCache::kSharedOffset) ->GetHeapObject(&object); EXPECT_EQ(object, *shared1); osr_cache->Get(index + OSROptimizedCodeCache::kCachedCodeOffset) ->GetHeapObject(&object); EXPECT_EQ(object, *code1); osr_cache->Get(index + OSROptimizedCodeCache::kOsrIdOffset)->ToSmi(&smi); EXPECT_EQ(smi.value(), bailout_id); // Add an entry beyond max capacity. i::ScopedVector source2(1024); GetSource(&source2, 2); Handle function2 = RunJS(source2.begin()); Handle shared2(function2->shared(), isolate); Handle code2(function2->code(), isolate); bailout_id++; OSROptimizedCodeCache::AddOptimizedCode(native_context, shared2, code2, BailoutId(bailout_id)); osr_cache = Handle( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), kMaxLength); index = 0; osr_cache->Get(index + OSROptimizedCodeCache::kSharedOffset) ->GetHeapObject(&object); EXPECT_EQ(object, *shared2); osr_cache->Get(index + OSROptimizedCodeCache::kCachedCodeOffset) ->GetHeapObject(&object); EXPECT_EQ(object, *code2); osr_cache->Get(index + OSROptimizedCodeCache::kOsrIdOffset)->ToSmi(&smi); EXPECT_EQ(smi.value(), bailout_id); } TEST_F(TestWithNativeContext, ReuseClearedEntry) { if (!i::FLAG_opt) return; i::FLAG_allow_natives_syntax = true; i::ScopedVector source(1024); GetSource(&source, 0); Handle function = RunJS(source.begin()); Isolate* isolate = function->GetIsolate(); Handle native_context(function->native_context(), isolate); Handle shared(function->shared(), isolate); Handle code(function->code(), isolate); int num_entries = kInitialEntries * 2; int expected_length = kInitialLength * 2; int bailout_id = 0; for (bailout_id = 0; bailout_id < num_entries; bailout_id++) { OSROptimizedCodeCache::AddOptimizedCode(native_context, shared, code, BailoutId(bailout_id)); } Handle osr_cache( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), expected_length); int clear_index1 = 0; int clear_index2 = (num_entries - 1) * OSROptimizedCodeCache::kEntryLength; osr_cache->Set(clear_index1 + OSROptimizedCodeCache::kSharedOffset, HeapObjectReference::ClearedValue(isolate)); osr_cache->Set(clear_index2 + OSROptimizedCodeCache::kCachedCodeOffset, HeapObjectReference::ClearedValue(isolate)); i::ScopedVector source1(1024); GetSource(&source1, 1); Handle function1 = RunJS(source1.begin()); Handle shared1(function1->shared(), isolate); Handle code1(function1->code(), isolate); OSROptimizedCodeCache::AddOptimizedCode(native_context, shared1, code1, BailoutId(bailout_id)); osr_cache = Handle( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), expected_length); int index = clear_index1; HeapObject object; Smi smi; osr_cache->Get(index + OSROptimizedCodeCache::kSharedOffset) ->GetHeapObject(&object); EXPECT_EQ(object, *shared1); osr_cache->Get(index + OSROptimizedCodeCache::kCachedCodeOffset) ->GetHeapObject(&object); EXPECT_EQ(object, *code1); osr_cache->Get(index + OSROptimizedCodeCache::kOsrIdOffset)->ToSmi(&smi); EXPECT_EQ(smi.value(), bailout_id); i::ScopedVector source2(1024); GetSource(&source2, 2); Handle function2 = RunJS(source2.begin()); Handle shared2(function2->shared(), isolate); Handle code2(function2->code(), isolate); bailout_id++; OSROptimizedCodeCache::AddOptimizedCode(native_context, shared2, code2, BailoutId(bailout_id)); osr_cache = Handle( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), expected_length); index = clear_index2; osr_cache->Get(index + OSROptimizedCodeCache::kSharedOffset) ->GetHeapObject(&object); EXPECT_EQ(object, *shared2); osr_cache->Get(index + OSROptimizedCodeCache::kCachedCodeOffset) ->GetHeapObject(&object); EXPECT_EQ(object, *code2); osr_cache->Get(index + OSROptimizedCodeCache::kOsrIdOffset)->ToSmi(&smi); EXPECT_EQ(smi.value(), bailout_id); } TEST_F(TestWithNativeContext, EvictDeoptedEntriesNoCompact) { if (!i::FLAG_opt) return; i::FLAG_allow_natives_syntax = true; i::ScopedVector source(1024); GetSource(&source, 0); Handle function = RunJS(source.begin()); Isolate* isolate = function->GetIsolate(); Handle native_context(function->native_context(), isolate); Handle shared(function->shared(), isolate); Handle code(function->code(), isolate); i::ScopedVector source1(1024); GetSource(&source1, 1); Handle deopt_function = RunJS(source1.begin()); Handle deopt_shared(deopt_function->shared(), isolate); Handle deopt_code(deopt_function->code(), isolate); int num_entries = kInitialEntries * 2; int expected_length = kInitialLength * 2; int deopt_id1 = num_entries - 2; int deopt_id2 = 0; int bailout_id = 0; for (bailout_id = 0; bailout_id < num_entries; bailout_id++) { if (bailout_id == deopt_id1 || bailout_id == deopt_id2) { OSROptimizedCodeCache::AddOptimizedCode( native_context, deopt_shared, deopt_code, BailoutId(bailout_id)); } else { OSROptimizedCodeCache::AddOptimizedCode(native_context, shared, code, BailoutId(bailout_id)); } } Handle osr_cache( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), expected_length); RunJS("%DeoptimizeFunction(f1)"); osr_cache = Handle( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), expected_length); int index = (num_entries - 2) * OSROptimizedCodeCache::kEntryLength; EXPECT_TRUE(osr_cache->Get(index + OSROptimizedCodeCache::kSharedOffset) ->IsCleared()); EXPECT_TRUE(osr_cache->Get(index + OSROptimizedCodeCache::kCachedCodeOffset) ->IsCleared()); EXPECT_TRUE( osr_cache->Get(index + OSROptimizedCodeCache::kOsrIdOffset)->IsCleared()); index = (num_entries - 1) * OSROptimizedCodeCache::kEntryLength; EXPECT_TRUE(osr_cache->Get(index + OSROptimizedCodeCache::kSharedOffset) ->IsCleared()); EXPECT_TRUE(osr_cache->Get(index + OSROptimizedCodeCache::kCachedCodeOffset) ->IsCleared()); EXPECT_TRUE( osr_cache->Get(index + OSROptimizedCodeCache::kOsrIdOffset)->IsCleared()); } TEST_F(TestWithNativeContext, EvictDeoptedEntriesCompact) { if (!i::FLAG_opt) return; i::FLAG_allow_natives_syntax = true; i::ScopedVector source(1024); GetSource(&source, 0); Handle function = RunJS(source.begin()); Isolate* isolate = function->GetIsolate(); Handle native_context(function->native_context(), isolate); Handle shared(function->shared(), isolate); Handle code(function->code(), isolate); i::ScopedVector source1(1024); GetSource(&source1, 1); Handle deopt_function = RunJS(source1.begin()); Handle deopt_shared(deopt_function->shared(), isolate); Handle deopt_code(deopt_function->code(), isolate); int num_entries = kInitialEntries + 1; int expected_length = kInitialLength * 2; int bailout_id = 0; for (bailout_id = 0; bailout_id < num_entries; bailout_id++) { if (bailout_id % 2 == 0) { OSROptimizedCodeCache::AddOptimizedCode( native_context, deopt_shared, deopt_code, BailoutId(bailout_id)); } else { OSROptimizedCodeCache::AddOptimizedCode(native_context, shared, code, BailoutId(bailout_id)); } } Handle osr_cache( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), expected_length); RunJS("%DeoptimizeFunction(f1)"); osr_cache = Handle( native_context->GetOSROptimizedCodeCache(), isolate); EXPECT_EQ(osr_cache->length(), kInitialLength); } } // namespace internal } // namespace v8