Commit fcff2437 authored by Mythri A's avatar Mythri A Committed by Commit Bot

Reland "[compiler] Cache OSR optimized code"

This is a reland of cfb10028
with a fix for failures in lite mode.

Original change's description:
> [compiler] Cache OSR optimized code
>
> With lazy feedback allocation, for functions that get OSRed we may
> not have feedback for the initial part of the functions since feedback
> vectors might be allocated after the function started executing. Hence
> we would not be able to optimize the function on the next call. This
> means we may have to OSR twice before we actually optimize function.
> This cl introduces OSR cache, so we could reuse the optimized code. One
> side effect of this cl is that the OSRed code won't be function context
> specialized anymore.
>
> Bug: chromium:987523
> Change-Id: Ic1e2abca85ccfa0a66a0fa83f7247392cc1e7cb2
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1796329
> Commit-Queue: Mythri Alle <mythria@chromium.org>
> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
> Reviewed-by: Georg Neis <neis@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#64014}

Bug: chromium:987523
Change-Id: I9c782242b07b24d15247533ab4ee044334b429ff
TBR: rmcilroy@chromium.org
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1826898
Commit-Queue: Mythri Alle <mythria@chromium.org>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64023}
parent 1e4bb087
......@@ -2648,6 +2648,9 @@ v8_source_set("v8_base_without_compiler") {
"src/objects/ordered-hash-table-inl.h",
"src/objects/ordered-hash-table.cc",
"src/objects/ordered-hash-table.h",
"src/objects/osr-optimized-code-cache-inl.h",
"src/objects/osr-optimized-code-cache.cc",
"src/objects/osr-optimized-code-cache.h",
"src/objects/promise-inl.h",
"src/objects/promise.h",
"src/objects/property-array-inl.h",
......
......@@ -666,21 +666,25 @@ V8_WARN_UNUSED_RESULT MaybeHandle<Code> GetCodeFromOptimizedCodeCache(
function->GetIsolate(),
RuntimeCallCounterId::kCompileGetFromOptimizedCodeMap);
Handle<SharedFunctionInfo> shared(function->shared(), function->GetIsolate());
Isolate* isolate = function->GetIsolate();
DisallowHeapAllocation no_gc;
if (osr_offset.IsNone()) {
if (function->has_feedback_vector()) {
Code code;
if (osr_offset.IsNone() && function->has_feedback_vector()) {
FeedbackVector feedback_vector = function->feedback_vector();
feedback_vector.EvictOptimizedCodeMarkedForDeoptimization(
function->shared(), "GetCodeFromOptimizedCodeCache");
Code code = feedback_vector.optimized_code();
code = feedback_vector.optimized_code();
} else if (!osr_offset.IsNone()) {
code = function->context()
.native_context()
.GetOSROptimizedCodeCache()
.GetOptimizedCode(shared, osr_offset, isolate);
}
if (!code.is_null()) {
// Caching of optimized code enabled and optimized code found.
DCHECK(!code.marked_for_deoptimization());
DCHECK(function->shared().is_compiled());
return Handle<Code>(code, feedback_vector.GetIsolate());
}
}
return Handle<Code>(code, isolate);
}
return MaybeHandle<Code>();
}
......@@ -711,12 +715,15 @@ void InsertCodeIntoOptimizedCodeCache(
// Cache optimized context-specific code.
Handle<JSFunction> function = compilation_info->closure();
Handle<SharedFunctionInfo> shared(function->shared(), function->GetIsolate());
Handle<Context> native_context(function->context().native_context(),
Handle<NativeContext> native_context(function->context().native_context(),
function->GetIsolate());
if (compilation_info->osr_offset().IsNone()) {
Handle<FeedbackVector> vector =
handle(function->feedback_vector(), function->GetIsolate());
FeedbackVector::SetOptimizedCode(vector, code);
} else {
OSROptimizedCodeCache::AddOptimizedCode(native_context, shared, code,
compilation_info->osr_offset());
}
}
......
......@@ -1028,8 +1028,14 @@ PipelineCompilationJob::Status PipelineCompilationJob::PrepareJobImpl(
compilation_info()->MarkAsAllocationFoldingEnabled();
}
// Determine whether to specialize the code for the function's context.
// We can't do this in the case of OSR, because we want to cache the
// generated code on the native context keyed on SharedFunctionInfo.
// TODO(mythria): Check if it is better to key the OSR cache on JSFunction and
// allow context specialization for OSR code.
if (compilation_info()->closure()->raw_feedback_cell().map() ==
ReadOnlyRoots(isolate).one_closure_cell_map()) {
ReadOnlyRoots(isolate).one_closure_cell_map() &&
!compilation_info()->is_osr()) {
compilation_info()->MarkAsFunctionContextSpecializing();
data_.ChooseSpecializationContext();
}
......
......@@ -357,6 +357,9 @@ void Deoptimizer::DeoptimizeMarkedCodeForContext(NativeContext native_context) {
for (Code code : codes) {
isolate->heap()->InvalidateCodeDeoptimizationData(code);
}
native_context.GetOSROptimizedCodeCache().EvictMarkedCode(
native_context.GetIsolate());
}
void Deoptimizer::DeoptimizeAll(Isolate* isolate) {
......@@ -375,6 +378,7 @@ void Deoptimizer::DeoptimizeAll(Isolate* isolate) {
while (!context.IsUndefined(isolate)) {
NativeContext native_context = NativeContext::cast(context);
MarkAllCodeForContext(native_context);
OSROptimizedCodeCache::Clear(native_context);
DeoptimizeMarkedCodeForContext(native_context);
context = native_context.next_context_link();
}
......@@ -432,6 +436,13 @@ void Deoptimizer::DeoptimizeFunction(JSFunction function, Code code) {
code.set_deopt_already_counted(true);
}
DeoptimizeMarkedCodeForContext(function.context().native_context());
// TODO(mythria): Ideally EvictMarkCode should compact the cache without
// having to explicitly call this. We don't do this currently because
// compacting causes GC and DeoptimizeMarkedCodeForContext uses raw
// pointers. Update DeoptimizeMarkedCodeForContext to use handles and remove
// this call from here.
OSROptimizedCodeCache::Compact(
Handle<NativeContext>(function.context().native_context(), isolate));
}
}
......
......@@ -1445,6 +1445,7 @@ Handle<NativeContext> Factory::NewNativeContext() {
context->set_math_random_index(Smi::zero());
context->set_serialized_objects(*empty_fixed_array());
context->set_microtask_queue(nullptr);
context->set_osr_code_cache(*empty_weak_fixed_array());
return context;
}
......
......@@ -13,6 +13,7 @@
#include "src/objects/js-objects-inl.h"
#include "src/objects/map-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/osr-optimized-code-cache-inl.h"
#include "src/objects/regexp-match-info.h"
#include "src/objects/scope-info.h"
#include "src/objects/shared-function-info.h"
......@@ -280,6 +281,10 @@ void NativeContext::set_microtask_queue(MicrotaskQueue* microtask_queue) {
reinterpret_cast<Address>(microtask_queue));
}
OSROptimizedCodeCache NativeContext::GetOSROptimizedCodeCache() {
return OSROptimizedCodeCache::cast(osr_code_cache());
}
OBJECT_CONSTRUCTORS_IMPL(NativeContext, Context)
} // namespace internal
......
......@@ -7,6 +7,7 @@
#include "src/objects/fixed-array.h"
#include "src/objects/function-kind.h"
#include "src/objects/osr-optimized-code-cache.h"
#include "torque-generated/field-offsets-tq.h"
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
......@@ -355,6 +356,7 @@ enum ContextLookupFlags {
V(WEAKMAP_SET_INDEX, JSFunction, weakmap_set) \
V(WEAKMAP_GET_INDEX, JSFunction, weakmap_get) \
V(WEAKSET_ADD_INDEX, JSFunction, weakset_add) \
V(OSR_CODE_CACHE_INDEX, WeakFixedArray, osr_code_cache) \
NATIVE_CONTEXT_INTRINSIC_FUNCTIONS(V)
// A table of all script contexts. Every loaded top-level script with top-level
......@@ -718,6 +720,8 @@ class NativeContext : public Context {
void SetDeoptimizedCodeListHead(Object head);
Object DeoptimizedCodeListHead();
inline OSROptimizedCodeCache GetOSROptimizedCodeCache();
void ResetErrorsThrown();
void IncrementErrorsThrown();
int GetErrorsThrown();
......
......@@ -41,6 +41,7 @@ class HeapNumber;
class ObjectHashTable;
class ObjectTemplateInfo;
class ObjectVisitor;
class OSROptimizedCodeCache;
class PreparseData;
class PropertyArray;
class PropertyCell;
......@@ -186,6 +187,7 @@ class ZoneForwardList;
V(OrderedHashMap) \
V(OrderedHashSet) \
V(OrderedNameDictionary) \
V(OSROptimizedCodeCache) \
V(PreparseData) \
V(PromiseReactionJobTask) \
V(PropertyArray) \
......
......@@ -350,6 +350,13 @@ DEF_GETTER(HeapObject, IsDependentCode, bool) {
return true;
}
DEF_GETTER(HeapObject, IsOSROptimizedCodeCache, bool) {
if (!IsWeakFixedArray(isolate)) return false;
// There's actually no way to see the difference between a weak fixed array
// and a osr optimized code cache.
return true;
}
DEF_GETTER(HeapObject, IsAbstractCode, bool) {
return IsBytecodeArray(isolate) || IsCode(isolate);
}
......
// Copyright 2019 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.
#ifndef V8_OBJECTS_OSR_OPTIMIZED_CODE_CACHE_INL_H_
#define V8_OBJECTS_OSR_OPTIMIZED_CODE_CACHE_INL_H_
#include "src/objects/osr-optimized-code-cache.h"
#include "src/objects/fixed-array-inl.h"
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
namespace v8 {
namespace internal {
OBJECT_CONSTRUCTORS_IMPL(OSROptimizedCodeCache, WeakFixedArray)
CAST_ACCESSOR(OSROptimizedCodeCache)
} // namespace internal
} // namespace v8
#include "src/objects/object-macros-undef.h"
#endif // V8_OBJECTS_OSR_OPTIMIZED_CODE_CACHE_INL_H_
// Copyright 2019 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/execution/isolate-inl.h"
#include "src/objects/code.h"
#include "src/objects/maybe-object.h"
#include "src/objects/shared-function-info.h"
#include "src/objects/osr-optimized-code-cache.h"
namespace v8 {
namespace internal {
const int OSROptimizedCodeCache::kInitialLength;
const int OSROptimizedCodeCache::kMaxLength;
void OSROptimizedCodeCache::AddOptimizedCode(
Handle<NativeContext> native_context, Handle<SharedFunctionInfo> shared,
Handle<Code> code, BailoutId osr_offset) {
DCHECK(!osr_offset.IsNone());
DCHECK_EQ(code->kind(), Code::OPTIMIZED_FUNCTION);
STATIC_ASSERT(kEntryLength == 3);
Isolate* isolate = native_context->GetIsolate();
DCHECK(!isolate->serializer_enabled());
Handle<OSROptimizedCodeCache> osr_cache(
native_context->GetOSROptimizedCodeCache(), isolate);
DCHECK_EQ(osr_cache->FindEntry(shared, osr_offset), -1);
int entry = -1;
for (int index = 0; index < osr_cache->length(); index += kEntryLength) {
if (osr_cache->Get(index + kSharedOffset)->IsCleared() ||
osr_cache->Get(index + kCachedCodeOffset)->IsCleared()) {
entry = index;
break;
}
}
if (entry == -1 && osr_cache->length() + kEntryLength <= kMaxLength) {
entry = GrowOSRCache(native_context, &osr_cache);
} else if (entry == -1) {
// We reached max capacity and cannot grow further. Reuse an existing entry.
// TODO(mythria): We could use better mechanisms (like lru) to replace
// existing entries. Though we don't expect this to be a common case, so
// for now choosing to replace the first entry.
entry = 0;
}
osr_cache->InitializeEntry(entry, *shared, *code, osr_offset);
}
void OSROptimizedCodeCache::Clear(NativeContext native_context) {
native_context.set_osr_code_cache(
*native_context.GetIsolate()->factory()->empty_weak_fixed_array());
}
void OSROptimizedCodeCache::Compact(Handle<NativeContext> native_context) {
Handle<OSROptimizedCodeCache> osr_cache(
native_context->GetOSROptimizedCodeCache(), native_context->GetIsolate());
Isolate* isolate = native_context->GetIsolate();
// Re-adjust the cache so all the valid entries are on one side. This will
// enable us to compress the cache if needed.
int curr_valid_index = 0;
for (int curr_index = 0; curr_index < osr_cache->length();
curr_index += kEntryLength) {
if (osr_cache->Get(curr_index + kSharedOffset)->IsCleared() ||
osr_cache->Get(curr_index + kCachedCodeOffset)->IsCleared()) {
continue;
}
if (curr_valid_index != curr_index) {
osr_cache->MoveEntry(curr_index, curr_valid_index, isolate);
}
curr_valid_index += kEntryLength;
}
if (!NeedsTrimming(curr_valid_index, osr_cache->length())) return;
Handle<OSROptimizedCodeCache> new_osr_cache =
Handle<OSROptimizedCodeCache>::cast(isolate->factory()->NewWeakFixedArray(
CapacityForLength(curr_valid_index), AllocationType::kOld));
DCHECK_LT(new_osr_cache->length(), osr_cache->length());
{
DisallowHeapAllocation no_gc;
new_osr_cache->CopyElements(native_context->GetIsolate(), 0, *osr_cache, 0,
new_osr_cache->length(),
new_osr_cache->GetWriteBarrierMode(no_gc));
}
native_context->set_osr_code_cache(*new_osr_cache);
}
Code OSROptimizedCodeCache::GetOptimizedCode(Handle<SharedFunctionInfo> shared,
BailoutId osr_offset,
Isolate* isolate) {
DisallowHeapAllocation no_gc;
int index = FindEntry(shared, osr_offset);
if (index == -1) return Code();
Code code = GetCodeFromEntry(index);
if (code.is_null()) {
ClearEntry(index, isolate);
return code;
}
DCHECK(code.is_optimized_code() && !code.marked_for_deoptimization());
return code;
}
void OSROptimizedCodeCache::EvictMarkedCode(Isolate* isolate) {
// This is called from DeoptimizeMarkedCodeForContext that uses raw pointers
// and hence the DisallowHeapAllocation scope here.
DisallowHeapAllocation no_gc;
for (int index = 0; index < length(); index += kEntryLength) {
MaybeObject code_entry = Get(index + kCachedCodeOffset);
HeapObject heap_object;
if (!code_entry->GetHeapObject(&heap_object)) continue;
DCHECK(heap_object.IsCode());
DCHECK(Code::cast(heap_object).is_optimized_code());
if (!Code::cast(heap_object).marked_for_deoptimization()) continue;
ClearEntry(index, isolate);
}
}
int OSROptimizedCodeCache::GrowOSRCache(
Handle<NativeContext> native_context,
Handle<OSROptimizedCodeCache>* osr_cache) {
Isolate* isolate = native_context->GetIsolate();
int old_length = (*osr_cache)->length();
int grow_by = CapacityForLength(old_length) - old_length;
DCHECK_GT(grow_by, kEntryLength);
*osr_cache = Handle<OSROptimizedCodeCache>::cast(
isolate->factory()->CopyWeakFixedArrayAndGrow(*osr_cache, grow_by));
for (int i = old_length; i < (*osr_cache)->length(); i++) {
(*osr_cache)->Set(i, HeapObjectReference::ClearedValue(isolate));
}
native_context->set_osr_code_cache(**osr_cache);
return old_length;
}
Code OSROptimizedCodeCache::GetCodeFromEntry(int index) {
DCHECK_LE(index + OSRCodeCacheConstants::kEntryLength, length());
DCHECK_EQ(index % kEntryLength, 0);
HeapObject code_entry;
Get(index + OSRCodeCacheConstants::kCachedCodeOffset)
->GetHeapObject(&code_entry);
return code_entry.is_null() ? Code() : Code::cast(code_entry);
}
SharedFunctionInfo OSROptimizedCodeCache::GetSFIFromEntry(int index) {
DCHECK_LE(index + OSRCodeCacheConstants::kEntryLength, length());
DCHECK_EQ(index % kEntryLength, 0);
HeapObject sfi_entry;
Get(index + OSRCodeCacheConstants::kSharedOffset)->GetHeapObject(&sfi_entry);
return sfi_entry.is_null() ? SharedFunctionInfo()
: SharedFunctionInfo::cast(sfi_entry);
}
BailoutId OSROptimizedCodeCache::GetBailoutIdFromEntry(int index) {
DCHECK_LE(index + OSRCodeCacheConstants::kEntryLength, length());
DCHECK_EQ(index % kEntryLength, 0);
Smi osr_offset_entry;
Get(index + kOsrIdOffset)->ToSmi(&osr_offset_entry);
return BailoutId(osr_offset_entry.value());
}
int OSROptimizedCodeCache::FindEntry(Handle<SharedFunctionInfo> shared,
BailoutId osr_offset) {
DisallowHeapAllocation no_gc;
DCHECK(!osr_offset.IsNone());
for (int index = 0; index < length(); index += kEntryLength) {
if (GetSFIFromEntry(index) != *shared) continue;
if (GetBailoutIdFromEntry(index) != osr_offset) continue;
return index;
}
return -1;
}
void OSROptimizedCodeCache::ClearEntry(int index, Isolate* isolate) {
Set(index + OSRCodeCacheConstants::kSharedOffset,
HeapObjectReference::ClearedValue(isolate));
Set(index + OSRCodeCacheConstants::kCachedCodeOffset,
HeapObjectReference::ClearedValue(isolate));
Set(index + OSRCodeCacheConstants::kOsrIdOffset,
HeapObjectReference::ClearedValue(isolate));
}
void OSROptimizedCodeCache::InitializeEntry(int entry,
SharedFunctionInfo shared,
Code code, BailoutId osr_offset) {
Set(entry + OSRCodeCacheConstants::kSharedOffset,
HeapObjectReference::Weak(shared));
Set(entry + OSRCodeCacheConstants::kCachedCodeOffset,
HeapObjectReference::Weak(code));
Set(entry + OSRCodeCacheConstants::kOsrIdOffset,
MaybeObject::FromSmi(Smi::FromInt(osr_offset.ToInt())));
}
void OSROptimizedCodeCache::MoveEntry(int src, int dst, Isolate* isolate) {
Set(dst + OSRCodeCacheConstants::kSharedOffset,
Get(src + OSRCodeCacheConstants::kSharedOffset));
Set(dst + OSRCodeCacheConstants::kCachedCodeOffset,
Get(src + OSRCodeCacheConstants::kCachedCodeOffset));
Set(dst + OSRCodeCacheConstants::kOsrIdOffset, Get(src + kOsrIdOffset));
ClearEntry(src, isolate);
}
int OSROptimizedCodeCache::CapacityForLength(int curr_length) {
// TODO(mythria): This is a randomly chosen heuristic and is not based on any
// data. We may have to tune this later.
if (curr_length == 0) return kInitialLength;
if (curr_length * 2 > kMaxLength) return kMaxLength;
return curr_length * 2;
}
bool OSROptimizedCodeCache::NeedsTrimming(int num_valid_entries,
int curr_length) {
return curr_length > kInitialLength && curr_length > num_valid_entries * 3;
}
} // namespace internal
} // namespace v8
// Copyright 2019 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.
#ifndef V8_OBJECTS_OSR_OPTIMIZED_CODE_CACHE_H_
#define V8_OBJECTS_OSR_OPTIMIZED_CODE_CACHE_H_
#include "src/objects/fixed-array.h"
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
namespace v8 {
namespace internal {
class V8_EXPORT OSROptimizedCodeCache : public WeakFixedArray {
public:
DECL_CAST(OSROptimizedCodeCache)
enum OSRCodeCacheConstants {
kSharedOffset,
kCachedCodeOffset,
kOsrIdOffset,
kEntryLength
};
static const int kInitialLength = OSRCodeCacheConstants::kEntryLength * 4;
static const int kMaxLength = OSRCodeCacheConstants::kEntryLength * 1024;
// Caches the optimized code |code| corresponding to the shared function
// |shared| and bailout id |osr_offset| in the OSROptimized code cache.
// If the OSR code cache wasn't created before it creates a code cache with
// kOSRCodeCacheInitialLength entries.
static void AddOptimizedCode(Handle<NativeContext> context,
Handle<SharedFunctionInfo> shared,
Handle<Code> code, BailoutId osr_offset);
// Reduces the size of the OSR code cache if the number of valid entries are
// less than the current capacity of the cache.
static void Compact(Handle<NativeContext> context);
// Sets the OSR optimized code cache to an empty array.
static void Clear(NativeContext context);
// Returns the code corresponding to the shared function |shared| and
// BailoutId |offset| if an entry exists in the cache. Returns an empty
// object otherwise.
Code GetOptimizedCode(Handle<SharedFunctionInfo> shared, BailoutId osr_offset,
Isolate* isolate);
// Remove all code objects marked for deoptimization from OSR code cache.
void EvictMarkedCode(Isolate* isolate);
private:
// Functions that implement heuristics on when to grow / shrink the cache.
static int CapacityForLength(int curr_capacity);
static bool NeedsTrimming(int num_valid_entries, int curr_capacity);
static int GrowOSRCache(Handle<NativeContext> native_context,
Handle<OSROptimizedCodeCache>* osr_cache);
// Helper functions to get individual items from an entry in the cache.
Code GetCodeFromEntry(int index);
SharedFunctionInfo GetSFIFromEntry(int index);
BailoutId GetBailoutIdFromEntry(int index);
inline int FindEntry(Handle<SharedFunctionInfo> shared, BailoutId osr_offset);
inline void ClearEntry(int src, Isolate* isolate);
inline void InitializeEntry(int entry, SharedFunctionInfo shared, Code code,
BailoutId osr_offset);
inline void MoveEntry(int src, int dst, Isolate* isolate);
OBJECT_CONSTRUCTORS(OSROptimizedCodeCache, WeakFixedArray);
};
} // namespace internal
} // namespace v8
#include "src/objects/object-macros-undef.h"
#endif // V8_OBJECTS_OSR_OPTIMIZED_CODE_CACHE_H_
......@@ -157,6 +157,9 @@ RUNTIME_FUNCTION(Runtime_NotifyDeoptimized) {
TimerEventScope<TimerEventDeoptimizeCode> timer(isolate);
TRACE_EVENT0("v8", "V8.DeoptimizeCode");
Handle<JSFunction> function = deoptimizer->function();
// For OSR the optimized code isn't installed on the function, so get the
// code object from deoptimizer.
Handle<Code> optimized_code = deoptimizer->compiled_code();
DeoptimizeKind type = deoptimizer->deopt_kind();
// TODO(turbofan): We currently need the native context to materialize
......@@ -174,7 +177,7 @@ RUNTIME_FUNCTION(Runtime_NotifyDeoptimized) {
// Invalidate the underlying optimized code on non-lazy deopts.
if (type != DeoptimizeKind::kLazy) {
Deoptimizer::DeoptimizeFunction(*function);
Deoptimizer::DeoptimizeFunction(*function, *optimized_code);
}
return ReadOnlyRoots(isolate).undefined_value();
......
......@@ -190,6 +190,7 @@ v8_source_set("unittests_sources") {
"numbers/conversions-unittest.cc",
"objects/backing-store-unittest.cc",
"objects/object-unittest.cc",
"objects/osr-optimized-code-cache-unittest.cc",
"objects/value-serializer-unittest.cc",
"parser/ast-value-unittest.cc",
"parser/preparser-unittest.cc",
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment