[nci] Share smi feedback and enable related optimizations

This CL enables a first batch of feedback-based optimizations in NCI
code. Specifically, optimizations based on unary, binary, compare,
for-in-next, and for-in-prepare feedback are now enabled.

This has two main implications:

1. NCI code can now deopt. Deoptimized code is currently thrown away
permanently and cannot be reused. Now that shared/cached NCI code can
deopt, this leads to an interesting question of what should happen
with deoptimized NCI code. The answer in this CL is to remove the
cache entry (it may later be re-added).

2. Tiering up from NCI to TF still requires feedback; since NCI code,
starting with this CL, no longer collects full feedback, feedback must
be created in some other way. This is solved by sharing a
context-independent encoding of feedback across native contexts.

Feedback is shared through a new SerializedFeedback object type,
essentially a byte array of serialized feedback. Currently, only
smi-based feedback is shared, but map-based feedback will be added in
the future.

SerializedFeedback is kept in the NCI cache alongside NCI Code
objects.  It is created on NCI cache insertion, and deserialized upon
NCI cache hits.

Bug: v8:8888
Change-Id: Ic0d5fbea3aa4d3b0a165624dab9d0283b07dcee7
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2531775
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Mythri Alle <mythria@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71224}
This commit is contained in:
Jakob Gruber 2020-11-17 07:25:40 +01:00 committed by Commit Bot
parent 35c0d461fb
commit 3599cce1f5
20 changed files with 497 additions and 79 deletions

View File

@ -3065,6 +3065,9 @@ v8_source_set("v8_base_without_compiler") {
"src/objects/scope-info.h",
"src/objects/script-inl.h",
"src/objects/script.h",
"src/objects/serialized-feedback-inl.h",
"src/objects/serialized-feedback.cc",
"src/objects/serialized-feedback.h",
"src/objects/shared-function-info-inl.h",
"src/objects/shared-function-info.cc",
"src/objects/shared-function-info.h",

View File

@ -17,11 +17,18 @@
namespace v8 {
namespace internal {
namespace {
// The number of generations for each sub cache.
static const int kRegExpGenerations = 2;
constexpr int kRegExpGenerations = 2;
// Initial size of each compilation cache table allocated.
static const int kInitialCacheSize = 64;
constexpr int kInitialCacheSize = 64;
// The index of the youngest generation table.
constexpr int kYoungestTableIndex = 0;
} // namespace
CompilationCache::CompilationCache(Isolate* isolate)
: isolate_(isolate),
@ -41,17 +48,22 @@ CompilationCache::CompilationCache(Isolate* isolate)
Handle<CompilationCacheTable> CompilationSubCache::GetTable(int generation) {
DCHECK_LT(generation, generations());
Handle<CompilationCacheTable> result;
if (tables_[generation].IsUndefined(isolate())) {
result = CompilationCacheTable::New(isolate(), kInitialCacheSize);
tables_[generation] = *result;
} else {
if (has_table(generation)) {
CompilationCacheTable table =
CompilationCacheTable::cast(tables_[generation]);
result = Handle<CompilationCacheTable>(table, isolate());
} else {
result = CompilationCacheTable::New(isolate(), kInitialCacheSize);
tables_[generation] = *result;
}
return result;
}
bool CompilationSubCache::has_table(int generation) const {
DCHECK_LT(generation, generations());
return !tables_[generation].IsUndefined(isolate());
}
// static
void CompilationSubCache::AgeByGeneration(CompilationSubCache* c) {
DCHECK_GT(c->generations(), 1);
@ -62,14 +74,15 @@ void CompilationSubCache::AgeByGeneration(CompilationSubCache* c) {
}
// Set the first generation as unborn.
c->tables_[0] = ReadOnlyRoots(c->isolate()).undefined_value();
c->tables_[kYoungestTableIndex] =
ReadOnlyRoots(c->isolate()).undefined_value();
}
// static
void CompilationSubCache::AgeCustom(CompilationSubCache* c) {
DCHECK_EQ(c->generations(), 1);
if (c->tables_[0].IsUndefined(c->isolate())) return;
CompilationCacheTable::cast(c->tables_[0]).Age();
if (!c->has_table(kYoungestTableIndex)) return;
CompilationCacheTable::cast(c->tables_[kYoungestTableIndex]).Age();
}
void CompilationCacheScript::Age() {
@ -270,43 +283,77 @@ void CompilationCacheRegExp::Put(Handle<String> source, JSRegExp::Flags flags,
CompilationCacheTable::PutRegExp(isolate(), table, source, flags, data));
}
MaybeHandle<Code> CompilationCacheCode::Lookup(Handle<SharedFunctionInfo> key) {
bool CompilationCacheCode::Lookup(
Handle<SharedFunctionInfo> key, MaybeHandle<Code>* code_out,
MaybeHandle<SerializedFeedback>* feedback_out) {
Code raw_code;
SerializedFeedback raw_feedback;
int generation = 0;
{
// Make sure not to leak the table into the surrounding handle
// scope. Otherwise, we risk keeping old tables around even after
// having cleared the cache.
HandleScope scope(isolate());
MaybeHandle<Code> maybe_value;
int generation = 0;
MaybeHandle<Code> maybe_code;
MaybeHandle<SerializedFeedback> maybe_feedback;
bool found = false;
for (; generation < generations(); generation++) {
Handle<CompilationCacheTable> table = GetTable(generation);
maybe_value = table->LookupCode(key);
if (!maybe_value.is_null()) break;
found = table->LookupCode(key, &maybe_code, &maybe_feedback);
if (found) break;
}
if (maybe_value.is_null()) {
if (!found) {
isolate()->counters()->compilation_cache_misses()->Increment();
return MaybeHandle<Code>();
return false;
}
Handle<Code> value = maybe_value.ToHandleChecked();
if (generation != 0) Put(key, value); // Add to the first generation.
raw_code = *maybe_code.ToHandleChecked();
raw_feedback = *maybe_feedback.ToHandleChecked();
}
// The Code object shouldn't be marked for deoptimization, otherwise it'd
// immediately be thrown out on the next call.
DCHECK(!raw_code.marked_for_deoptimization());
*code_out = handle(raw_code, isolate());
*feedback_out = handle(raw_feedback, isolate());
if (generation != kYoungestTableIndex) {
// Copy to youngest generation.
Put(key, code_out->ToHandleChecked(), feedback_out->ToHandleChecked());
}
isolate()->counters()->compilation_cache_hits()->Increment();
return scope.CloseAndEscape(value);
return true;
}
void CompilationCacheCode::Put(Handle<SharedFunctionInfo> key,
Handle<Code> value) {
Handle<Code> value_code,
Handle<SerializedFeedback> value_feedback) {
HandleScope scope(isolate());
Handle<CompilationCacheTable> table = GetFirstTable();
SetFirstTable(CompilationCacheTable::PutCode(isolate(), table, key, value));
SetFirstTable(CompilationCacheTable::PutCode(isolate(), table, key,
value_code, value_feedback));
}
// static
void CompilationCacheCode::TraceAgeing() {
DCHECK(FLAG_trace_turbo_nci);
StdoutStream os;
os << "NCI cache ageing: Removing oldest generation" << std::endl;
}
// static
void CompilationCacheCode::TraceRemovalForDeoptimization(SharedFunctionInfo key,
Code value) {
DCHECK(FLAG_trace_turbo_nci);
StdoutStream os;
os << "NCI cache removal for deoptimization: " << Brief(key) << ", "
<< Brief(value) << std::endl;
}
// static
void CompilationCacheCode::TraceInsertion(Handle<SharedFunctionInfo> key,
Handle<Code> value) {
DCHECK(FLAG_trace_turbo_nci);
@ -315,6 +362,7 @@ void CompilationCacheCode::TraceInsertion(Handle<SharedFunctionInfo> key,
<< std::endl;
}
// static
void CompilationCacheCode::TraceHit(Handle<SharedFunctionInfo> key,
Handle<Code> value) {
DCHECK(FLAG_trace_turbo_nci);
@ -375,8 +423,10 @@ MaybeHandle<FixedArray> CompilationCache::LookupRegExp(Handle<String> source,
return reg_exp_.Lookup(source, flags);
}
MaybeHandle<Code> CompilationCache::LookupCode(Handle<SharedFunctionInfo> sfi) {
return code_.Lookup(sfi);
bool CompilationCache::LookupCode(
Handle<SharedFunctionInfo> sfi, MaybeHandle<Code>* code_out,
MaybeHandle<SerializedFeedback>* feedback_out) {
return code_.Lookup(sfi, code_out, feedback_out);
}
void CompilationCache::PutScript(Handle<String> source,
@ -419,8 +469,18 @@ void CompilationCache::PutRegExp(Handle<String> source, JSRegExp::Flags flags,
}
void CompilationCache::PutCode(Handle<SharedFunctionInfo> shared,
Handle<Code> code) {
code_.Put(shared, code);
Handle<Code> code,
Handle<SerializedFeedback> feedback) {
code_.Put(shared, code, feedback);
}
void CompilationCache::ClearDeoptimizedCode() { code_.ClearDeoptimizedCode(); }
void CompilationCacheCode::ClearDeoptimizedCode() {
for (int i = 0; i < generations(); i++) {
if (!has_table(i)) continue;
GetTable(i)->ClearDeoptimizedCode();
}
}
void CompilationCache::Clear() {

View File

@ -34,6 +34,7 @@ class CompilationSubCache {
// Get the compilation cache tables for a specific generation.
Handle<CompilationCacheTable> GetTable(int generation);
bool has_table(int generation) const;
// Accessors for first generation.
Handle<CompilationCacheTable> GetFirstTable() {
@ -159,8 +160,11 @@ class CompilationCacheCode : public CompilationSubCache {
explicit CompilationCacheCode(Isolate* isolate)
: CompilationSubCache(isolate, kGenerations) {}
MaybeHandle<Code> Lookup(Handle<SharedFunctionInfo> key);
void Put(Handle<SharedFunctionInfo> key, Handle<Code> value);
bool Lookup(Handle<SharedFunctionInfo> key, MaybeHandle<Code>* code_out,
MaybeHandle<SerializedFeedback>* feedback_out);
void Put(Handle<SharedFunctionInfo> key, Handle<Code> value_code,
Handle<SerializedFeedback> value_feedback);
void ClearDeoptimizedCode();
void Age() override;
@ -170,6 +174,7 @@ class CompilationCacheCode : public CompilationSubCache {
static constexpr int kGenerations = 2;
static void TraceAgeing();
static void TraceRemovalForDeoptimization(SharedFunctionInfo key, Code value);
static void TraceInsertion(Handle<SharedFunctionInfo> key,
Handle<Code> value);
static void TraceHit(Handle<SharedFunctionInfo> key, Handle<Code> value);
@ -205,7 +210,8 @@ class V8_EXPORT_PRIVATE CompilationCache {
MaybeHandle<FixedArray> LookupRegExp(Handle<String> source,
JSRegExp::Flags flags);
MaybeHandle<Code> LookupCode(Handle<SharedFunctionInfo> sfi);
bool LookupCode(Handle<SharedFunctionInfo> sfi, MaybeHandle<Code>* code_out,
MaybeHandle<SerializedFeedback>* feedback_out);
// Associate the (source, kind) pair to the shared function
// info. This may overwrite an existing mapping.
@ -225,7 +231,9 @@ class V8_EXPORT_PRIVATE CompilationCache {
void PutRegExp(Handle<String> source, JSRegExp::Flags flags,
Handle<FixedArray> data);
void PutCode(Handle<SharedFunctionInfo> shared, Handle<Code> code);
void PutCode(Handle<SharedFunctionInfo> shared, Handle<Code> code,
Handle<SerializedFeedback> feedback);
void ClearDeoptimizedCode();
// Clear the cache - also used to initialize the cache at startup.
void Clear();

View File

@ -44,6 +44,7 @@
#include "src/objects/js-function-inl.h"
#include "src/objects/map.h"
#include "src/objects/object-list-macros.h"
#include "src/objects/serialized-feedback.h"
#include "src/objects/shared-function-info.h"
#include "src/objects/string.h"
#include "src/parsing/parse-info.h"
@ -917,10 +918,19 @@ void InsertCodeIntoCompilationCache(Isolate* isolate,
Handle<Code> code = info->code();
DCHECK(!info->function_context_specializing());
Handle<SerializedFeedback> serialized_feedback =
SerializedFeedback::Serialize(
isolate, handle(info->closure()->feedback_vector(), isolate));
Handle<SharedFunctionInfo> sfi = info->shared_info();
CompilationCache* cache = isolate->compilation_cache();
cache->PutCode(sfi, code);
DCHECK(!cache->LookupCode(sfi).is_null());
cache->PutCode(sfi, code, serialized_feedback);
#ifdef DEBUG
MaybeHandle<Code> maybe_code;
MaybeHandle<SerializedFeedback> maybe_feedback;
DCHECK(cache->LookupCode(sfi, &maybe_code, &maybe_feedback));
#endif // DEBUG
sfi->set_may_have_cached_code(true);

View File

@ -4128,7 +4128,6 @@ JSTypeHintLowering::LoweringResult
BytecodeGraphBuilder::TryBuildSimplifiedUnaryOp(const Operator* op,
Node* operand,
FeedbackSlot slot) {
if (!CanApplyTypeHintLowering(op)) return NoChange();
Node* effect = environment()->GetEffectDependency();
Node* control = environment()->GetControlDependency();
JSTypeHintLowering::LoweringResult result =
@ -4142,7 +4141,6 @@ JSTypeHintLowering::LoweringResult
BytecodeGraphBuilder::TryBuildSimplifiedBinaryOp(const Operator* op, Node* left,
Node* right,
FeedbackSlot slot) {
if (!CanApplyTypeHintLowering(op)) return NoChange();
Node* effect = environment()->GetEffectDependency();
Node* control = environment()->GetControlDependency();
JSTypeHintLowering::LoweringResult result =
@ -4157,7 +4155,6 @@ BytecodeGraphBuilder::TryBuildSimplifiedForInNext(Node* receiver,
Node* cache_array,
Node* cache_type, Node* index,
FeedbackSlot slot) {
if (!CanApplyTypeHintLowering(IrOpcode::kJSForInNext)) return NoChange();
Node* effect = environment()->GetEffectDependency();
Node* control = environment()->GetControlDependency();
JSTypeHintLowering::LoweringResult result =
@ -4170,7 +4167,6 @@ BytecodeGraphBuilder::TryBuildSimplifiedForInNext(Node* receiver,
JSTypeHintLowering::LoweringResult
BytecodeGraphBuilder::TryBuildSimplifiedForInPrepare(Node* enumerator,
FeedbackSlot slot) {
if (!CanApplyTypeHintLowering(IrOpcode::kJSForInPrepare)) return NoChange();
Node* effect = environment()->GetEffectDependency();
Node* control = environment()->GetControlDependency();
JSTypeHintLowering::LoweringResult result =

View File

@ -4704,8 +4704,8 @@ bool HasMigrationTargets(const MapHandles& maps) {
} // namespace
bool JSHeapBroker::CanUseFeedback(const FeedbackNexus& nexus) const {
// TODO(jgruber,v8:8888): Currently, nci code does not use any
// feedback. This restriction will be relaxed in the future.
// TODO(jgruber,v8:8888): Currently, nci code does not use all feedback
// kinds. This restriction will be relaxed in the future.
return !is_native_context_independent() && !nexus.IsUninitialized();
}
@ -4818,7 +4818,7 @@ ProcessedFeedback const& JSHeapBroker::ReadFeedbackForGlobalAccess(
ProcessedFeedback const& JSHeapBroker::ReadFeedbackForBinaryOperation(
FeedbackSource const& source) const {
FeedbackNexus nexus(source.vector, source.slot, feedback_nexus_config());
if (!CanUseFeedback(nexus)) return NewInsufficientFeedback(nexus.kind());
if (nexus.IsUninitialized()) return NewInsufficientFeedback(nexus.kind());
BinaryOperationHint hint = nexus.GetBinaryOperationFeedback();
DCHECK_NE(hint, BinaryOperationHint::kNone); // Not uninitialized.
return *zone()->New<BinaryOperationFeedback>(hint, nexus.kind());
@ -4827,7 +4827,7 @@ ProcessedFeedback const& JSHeapBroker::ReadFeedbackForBinaryOperation(
ProcessedFeedback const& JSHeapBroker::ReadFeedbackForCompareOperation(
FeedbackSource const& source) const {
FeedbackNexus nexus(source.vector, source.slot, feedback_nexus_config());
if (!CanUseFeedback(nexus)) return NewInsufficientFeedback(nexus.kind());
if (nexus.IsUninitialized()) return NewInsufficientFeedback(nexus.kind());
CompareOperationHint hint = nexus.GetCompareOperationFeedback();
DCHECK_NE(hint, CompareOperationHint::kNone); // Not uninitialized.
return *zone()->New<CompareOperationFeedback>(hint, nexus.kind());
@ -4836,7 +4836,7 @@ ProcessedFeedback const& JSHeapBroker::ReadFeedbackForCompareOperation(
ProcessedFeedback const& JSHeapBroker::ReadFeedbackForForIn(
FeedbackSource const& source) const {
FeedbackNexus nexus(source.vector, source.slot, feedback_nexus_config());
if (!CanUseFeedback(nexus)) return NewInsufficientFeedback(nexus.kind());
if (nexus.IsUninitialized()) return NewInsufficientFeedback(nexus.kind());
ForInHint hint = nexus.GetForInFeedback();
DCHECK_NE(hint, ForInHint::kNone); // Not uninitialized.
return *zone()->New<ForInFeedback>(hint, nexus.kind());

View File

@ -2371,7 +2371,7 @@ Reduction JSTypedLowering::Reduce(Node* node) {
const IrOpcode::Value opcode = node->opcode();
if (broker()->generate_full_feedback_collection() &&
IrOpcode::IsFeedbackCollectingOpcode(opcode)) {
IrOpcode::OpcodeMustCollectFeedbackForNCI(opcode)) {
// In NCI code, it is not valid to reduce feedback-collecting JS opcodes
// into non-feedback-collecting lower-level opcodes; missed feedback would
// result in soft deopts.

View File

@ -1089,12 +1089,48 @@ class V8_EXPORT_PRIVATE IrOpcode {
return kJSCreateFunctionContext <= value && value <= kJSCreateBlockContext;
}
// These opcodes *must* collect full feedback in NCI code in order to avoid
// deopts after tier-up to Turbofan.
// TODO(jgruber,v8:8888): The goal is for this to be the empty set at some
// point in the future.
static bool OpcodeMustCollectFeedbackForNCI(Value value) {
switch (value) {
case kJSCall:
case kJSCallWithArrayLike:
case kJSCallWithSpread:
case kJSCloneObject:
case kJSConstruct:
case kJSConstructWithArrayLike:
case kJSConstructWithSpread:
case kJSCreateEmptyLiteralArray:
case kJSCreateLiteralArray:
case kJSCreateLiteralObject:
case kJSCreateLiteralRegExp:
case kJSGetIterator:
case kJSGetTemplateObject:
case kJSHasProperty:
case kJSInstanceOf:
case kJSLoadGlobal:
case kJSLoadNamed:
case kJSLoadNamedFromSuper:
case kJSLoadProperty:
case kJSStoreDataPropertyInLiteral:
case kJSStoreGlobal:
case kJSStoreInArrayLiteral:
case kJSStoreNamed:
case kJSStoreNamedOwn:
case kJSStoreProperty:
return true;
default:
return false;
}
UNREACHABLE();
}
// These opcode take the feedback vector as an input, and implement
// feedback-collecting logic in generic lowering.
static bool IsFeedbackCollectingOpcode(Value value) {
#define CASE(Name, ...) \
case k##Name: \
return true;
#define CASE(Name, ...) case k##Name:
switch (value) {
JS_ARITH_BINOP_LIST(CASE)
JS_ARITH_UNOP_LIST(CASE)

View File

@ -10,6 +10,7 @@
#include "src/builtins/accessors.h"
#include "src/codegen/assembler-inl.h"
#include "src/codegen/callable.h"
#include "src/codegen/compilation-cache.h"
#include "src/codegen/macro-assembler.h"
#include "src/codegen/register-configuration.h"
#include "src/common/assert-scope.h"
@ -392,6 +393,7 @@ void Deoptimizer::DeoptimizeAll(Isolate* isolate) {
MarkAllCodeForContext(native_context);
OSROptimizedCodeCache::Clear(native_context);
DeoptimizeMarkedCodeForContext(native_context);
isolate->compilation_cache()->ClearDeoptimizedCode();
context = native_context.next_context_link();
}
}
@ -452,6 +454,16 @@ void Deoptimizer::DeoptimizeFunction(JSFunction function, Code code) {
// this call from here.
OSROptimizedCodeCache::Compact(
Handle<NativeContext>(function.context().native_context(), isolate));
// Remove deoptimized Code from the NCI cache - such objects currently
// cannot be reused.
// TODO(jgruber): Instead of removal (and possibly later re-insertion), we
// should learn from deopts. Potentially this already happens now;
// re-insertion will also insert updated (generalized) feedback. We should
// still take a closer look at this in the future though.
if (code.kind() == CodeKind::NATIVE_CONTEXT_INDEPENDENT) {
isolate->compilation_cache()->ClearDeoptimizedCode();
}
}
}

View File

@ -3484,18 +3484,27 @@ Handle<JSFunction> Factory::JSFunctionBuilder::Build() {
PrepareMap();
PrepareFeedbackCell();
// Determine the associated Code object.
Handle<Code> code;
const bool have_cached_code =
sfi_->TryGetCachedCode(isolate_).ToHandle(&code);
if (!have_cached_code) code = handle(sfi_->GetCode(), isolate_);
// Determine the associated Code object. If we hit the NCI cache, take that;
// otherwise, ask the SharedFunctionInfo for the appropriate Code object.
MaybeHandle<Code> maybe_code;
MaybeHandle<SerializedFeedback> maybe_feedback;
const bool have_nci_cache = sfi_->TryGetCachedCodeAndSerializedFeedback(
isolate_, &maybe_code, &maybe_feedback);
Handle<Code> code = have_nci_cache ? maybe_code.ToHandleChecked()
: handle(sfi_->GetCode(), isolate_);
Handle<JSFunction> result = BuildRaw(code);
if (have_cached_code) {
if (have_nci_cache) {
if (FLAG_trace_turbo_nci) CompilationCacheCode::TraceHit(sfi_, code);
if (!result->has_feedback_vector()) {
IsCompiledScope is_compiled_scope(sfi_->is_compiled_scope(isolate_));
JSFunction::EnsureFeedbackVector(result, &is_compiled_scope);
if (FLAG_trace_turbo_nci) CompilationCacheCode::TraceHit(sfi_, code);
// TODO(jgruber,v8:8888): Consider combining shared feedback with
// existing feedback here.
maybe_feedback.ToHandleChecked()->DeserializeInto(
result->feedback_vector());
}
}
Compiler::PostInstantiation(result);

View File

@ -458,7 +458,6 @@ int Code::stack_slots() const {
}
bool Code::marked_for_deoptimization() const {
DCHECK(CodeKindCanDeoptimize(kind()));
int32_t flags = code_data_container(kAcquireLoad).kind_specific_flags();
return MarkedForDeoptimizationField::decode(flags);
}

View File

@ -4,7 +4,9 @@
#include "src/objects/compilation-cache-table.h"
#include "src/codegen/compilation-cache.h"
#include "src/objects/compilation-cache-table-inl.h"
#include "src/objects/serialized-feedback-inl.h"
namespace v8 {
namespace internal {
@ -282,14 +284,32 @@ Handle<Object> CompilationCacheTable::LookupRegExp(Handle<String> src,
return Handle<Object>(get(EntryToIndex(entry) + 1), isolate);
}
MaybeHandle<Code> CompilationCacheTable::LookupCode(
Handle<SharedFunctionInfo> key) {
namespace {
constexpr int CodeCacheSFIIndex(InternalIndex entry) {
return CompilationCacheTable::EntryToIndex(entry) + 0;
}
constexpr int CodeCacheCodeIndex(InternalIndex entry) {
return CompilationCacheTable::EntryToIndex(entry) + 1;
}
constexpr int CodeCacheFeedbackIndex(InternalIndex entry) {
return CompilationCacheTable::EntryToIndex(entry) + 2;
}
} // namespace
bool CompilationCacheTable::LookupCode(
Handle<SharedFunctionInfo> key, MaybeHandle<Code>* code_out,
MaybeHandle<SerializedFeedback>* feedback_out) {
Isolate* isolate = GetIsolate();
DisallowHeapAllocation no_allocation;
CodeKey k(key);
InternalIndex entry = FindEntry(isolate, &k);
if (entry.is_not_found()) return {};
return Handle<Code>(Code::cast(get(EntryToIndex(entry) + 1)), isolate);
if (entry.is_not_found()) return false;
*code_out = handle(Code::cast(get(CodeCacheCodeIndex(entry))), isolate);
*feedback_out = handle(
SerializedFeedback::cast(get(CodeCacheFeedbackIndex(entry))), isolate);
return true;
}
Handle<CompilationCacheTable> CompilationCacheTable::PutScript(
@ -369,15 +389,18 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutRegExp(
Handle<CompilationCacheTable> CompilationCacheTable::PutCode(
Isolate* isolate, Handle<CompilationCacheTable> cache,
Handle<SharedFunctionInfo> key, Handle<Code> value) {
Handle<SharedFunctionInfo> key, Handle<Code> value_code,
Handle<SerializedFeedback> value_feedback) {
STATIC_ASSERT(CompilationCacheShape::kEntrySize >= 3);
CodeKey k(key);
{
InternalIndex entry = cache->FindEntry(isolate, &k);
if (entry.is_found()) {
// Update.
cache->set(EntryToIndex(entry), *key);
cache->set(EntryToIndex(entry) + 1, *value);
cache->set(CodeCacheSFIIndex(entry), *key);
cache->set(CodeCacheCodeIndex(entry), *value_code);
cache->set(CodeCacheFeedbackIndex(entry), *value_feedback);
return cache;
}
}
@ -385,8 +408,9 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutCode(
// Insert.
cache = EnsureCapacity(isolate, cache);
InternalIndex entry = cache->FindInsertionEntry(isolate, k.Hash());
cache->set(EntryToIndex(entry), *key);
cache->set(EntryToIndex(entry) + 1, *value);
cache->set(CodeCacheSFIIndex(entry), *key);
cache->set(CodeCacheCodeIndex(entry), *value_code);
cache->set(CodeCacheFeedbackIndex(entry), *value_feedback);
cache->ElementAdded();
return cache;
}
@ -424,6 +448,35 @@ void CompilationCacheTable::Age() {
}
}
void CompilationCacheTable::ClearDeoptimizedCode() {
DisallowHeapAllocation no_allocation;
Object the_hole_value = GetReadOnlyRoots().the_hole_value();
Object undefined_value = GetReadOnlyRoots().undefined_value();
for (InternalIndex entry : IterateEntries()) {
Object maybe_code = get(CodeCacheCodeIndex(entry));
if (maybe_code == the_hole_value) continue;
// TODO(jgruber): There should only be one not-present value. Undefined is
// used as the initial filler on allocation. The hole is inserted by
// deletions.
if (maybe_code == undefined_value) continue;
SharedFunctionInfo sfi =
SharedFunctionInfo::cast(get(CodeCacheSFIIndex(entry)));
Code code = Code::cast(maybe_code);
if (code.marked_for_deoptimization()) {
// TODO(jgruber): Due to design of the compilation cache, there's a
// strange layering violation here in which we call the higher-level
// tracing function from the lower-level implementation. At some point,
// the entire compilation cache design should be reconsidered.
if (FLAG_trace_turbo_nci) {
CompilationCacheCode::TraceRemovalForDeoptimization(sfi, code);
}
sfi.set_may_have_cached_code(false);
RemoveEntry(EntryToIndex(entry));
}
}
}
void CompilationCacheTable::Remove(Object value) {
DisallowHeapAllocation no_allocation;
for (InternalIndex entry : IterateEntries()) {

View File

@ -8,6 +8,7 @@
#include "src/objects/feedback-cell.h"
#include "src/objects/hash-table.h"
#include "src/objects/js-regexp.h"
#include "src/objects/serialized-feedback.h"
#include "src/objects/shared-function-info.h"
#include "src/roots/roots.h"
@ -122,10 +123,14 @@ class CompilationCacheTable
// The Code cache shares native-context-independent (NCI) code between
// contexts.
MaybeHandle<Code> LookupCode(Handle<SharedFunctionInfo> key);
bool LookupCode(Handle<SharedFunctionInfo> key, MaybeHandle<Code>* code_out,
MaybeHandle<SerializedFeedback>* feedback_out);
static Handle<CompilationCacheTable> PutCode(
Isolate* isolate, Handle<CompilationCacheTable> cache,
Handle<SharedFunctionInfo> key, Handle<Code> value);
Handle<SharedFunctionInfo> key, Handle<Code> value_code,
Handle<SerializedFeedback> value_feedback);
// Removes Code objects that are marked_for_deoptimization from the cache.
void ClearDeoptimizedCode();
void Remove(Object value);
void Age();

View File

@ -0,0 +1,26 @@
// Copyright 2020 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_SERIALIZED_FEEDBACK_INL_H_
#define V8_OBJECTS_SERIALIZED_FEEDBACK_INL_H_
#include "src/objects/fixed-array-inl.h"
#include "src/objects/serialized-feedback.h"
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
namespace v8 {
namespace internal {
SerializedFeedback::SerializedFeedback(Address ptr) : ByteArray(ptr) {}
CAST_ACCESSOR(SerializedFeedback)
} // namespace internal
} // namespace v8
#include "src/objects/object-macros-undef.h"
#endif // V8_OBJECTS_SERIALIZED_FEEDBACK_INL_H_

View File

@ -0,0 +1,120 @@
// Copyright 2020 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/objects/serialized-feedback.h"
#include "src/objects/fixed-array-inl.h"
#include "src/objects/serialized-feedback-inl.h"
namespace v8 {
namespace internal {
namespace {
// Currently, only smi-based feedback is serialized.
bool IsSerialized(FeedbackSlotKind kind) {
switch (kind) {
case FeedbackSlotKind::kBinaryOp:
case FeedbackSlotKind::kCompareOp:
case FeedbackSlotKind::kForIn:
return true;
case FeedbackSlotKind::kStoreGlobalSloppy:
case FeedbackSlotKind::kStoreNamedSloppy:
case FeedbackSlotKind::kStoreKeyedSloppy:
case FeedbackSlotKind::kCall:
case FeedbackSlotKind::kLoadProperty:
case FeedbackSlotKind::kLoadGlobalNotInsideTypeof:
case FeedbackSlotKind::kLoadGlobalInsideTypeof:
case FeedbackSlotKind::kLoadKeyed:
case FeedbackSlotKind::kHasKeyed:
case FeedbackSlotKind::kStoreGlobalStrict:
case FeedbackSlotKind::kStoreNamedStrict:
case FeedbackSlotKind::kStoreOwnNamed:
case FeedbackSlotKind::kStoreKeyedStrict:
case FeedbackSlotKind::kStoreInArrayLiteral:
case FeedbackSlotKind::kStoreDataPropertyInLiteral:
case FeedbackSlotKind::kTypeProfile:
case FeedbackSlotKind::kLiteral:
case FeedbackSlotKind::kInstanceOf:
case FeedbackSlotKind::kCloneObject:
return false;
case FeedbackSlotKind::kInvalid:
case FeedbackSlotKind::kKindsNumber:
UNREACHABLE();
}
UNREACHABLE();
}
constexpr int SlotCountToByteLength(int slot_count) {
return slot_count * kUInt32Size;
}
constexpr int ByteLengthToSlotCount(int byte_length) {
CONSTEXPR_DCHECK((byte_length % kUInt32Size) == 0);
return byte_length / kUInt32Size;
}
} // namespace
// static
Handle<SerializedFeedback> SerializedFeedback::Serialize(
Isolate* isolate, Handle<FeedbackVector> vector) {
Handle<FeedbackMetadata> metadata(vector->metadata(), isolate);
const int slot_count = metadata->slot_count();
const int byte_length = SlotCountToByteLength(slot_count);
// Allocating in old space since these objects are inserted into long-lived
// caches.
auto sf = Handle<SerializedFeedback>::cast(
isolate->factory()->NewByteArray(byte_length, AllocationType::kOld));
// Initialize all relevant slots.
for (int i = 0; i < slot_count;) {
const FeedbackSlot slot{i};
const FeedbackSlotKind slot_kind = metadata->GetKind(slot);
const int slot_size = FeedbackMetadata::GetSlotSize(slot_kind);
if (IsSerialized(slot_kind)) {
// All handled slot kinds currently use smi-based feedback; these are
// simply serialized as the value.
DCHECK_EQ(slot_size, 1);
const uint32_t value = vector->Get(slot)->ToSmi().value();
sf->set_uint32(i, value);
} else {
// Unhandled slot kinds are zeroed.
sf->set_uint32(i, 0);
}
i += slot_size;
}
return sf;
}
void SerializedFeedback::DeserializeInto(FeedbackVector vector) const {
DisallowHeapAllocation no_gc;
FeedbackMetadata metadata = vector.metadata();
const int slot_count = metadata.slot_count();
CHECK_EQ(slot_count, ByteLengthToSlotCount(length()));
for (int i = 0; i < slot_count;) {
const FeedbackSlot slot{i};
const FeedbackSlotKind slot_kind = metadata.GetKind(slot);
const int slot_size = FeedbackMetadata::GetSlotSize(slot_kind);
const uint32_t serialized_value = get_uint32(i);
if (IsSerialized(slot_kind)) {
DCHECK_EQ(slot_size, 1);
DCHECK_EQ(vector.Get(slot)->ToSmi().value(), 0); // Uninitialized.
vector.SynchronizedSet(slot, Smi::FromInt(serialized_value),
SKIP_WRITE_BARRIER);
DCHECK_EQ(vector.Get(slot)->ToSmi().value(), serialized_value);
} else {
DCHECK_EQ(serialized_value, 0);
}
i += slot_size;
}
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,41 @@
// Copyright 2020 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_SERIALIZED_FEEDBACK_H_
#define V8_OBJECTS_SERIALIZED_FEEDBACK_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 FeedbackVector;
// A serialized representation of FeedbackVector, used to share collected
// feedback between native contexts.
//
// Note: The encoding is not final and thus not documented here yet. Currently,
// only smi-based feedback is shared/serialized.
class SerializedFeedback : public ByteArray {
public:
// Serialize current feedback vector values into a SerializedFeedback object.
static Handle<SerializedFeedback> Serialize(Isolate* isolate,
Handle<FeedbackVector> vector);
// Deserialize into the given vector.
void DeserializeInto(FeedbackVector vector) const;
DECL_CAST(SerializedFeedback)
OBJECT_CONSTRUCTORS(SerializedFeedback, ByteArray);
};
} // namespace internal
} // namespace v8
#include "src/objects/object-macros-undef.h"
#endif // V8_OBJECTS_SERIALIZED_FEEDBACK_H_

View File

@ -422,10 +422,21 @@ std::ostream& operator<<(std::ostream& os, const SourceCodeOf& v) {
}
}
bool SharedFunctionInfo::TryGetCachedCodeAndSerializedFeedback(
Isolate* isolate, MaybeHandle<Code>* code_out,
MaybeHandle<SerializedFeedback>* feedback_out) {
if (!may_have_cached_code()) return {};
Handle<SharedFunctionInfo> sfi(*this, isolate);
return isolate->compilation_cache()->LookupCode(sfi, code_out, feedback_out);
}
MaybeHandle<Code> SharedFunctionInfo::TryGetCachedCode(Isolate* isolate) {
if (!may_have_cached_code()) return {};
Handle<SharedFunctionInfo> zis(*this, isolate);
return isolate->compilation_cache()->LookupCode(zis);
Handle<SharedFunctionInfo> sfi(*this, isolate);
MaybeHandle<Code> maybe_code;
MaybeHandle<SerializedFeedback> maybe_feedback;
isolate->compilation_cache()->LookupCode(sfi, &maybe_code, &maybe_feedback);
return maybe_code;
}
void SharedFunctionInfo::DisableOptimization(BailoutReason reason) {

View File

@ -34,6 +34,7 @@ class BytecodeArray;
class CoverageInfo;
class DebugInfo;
class IsCompiledScope;
class SerializedFeedback;
class WasmCapiFunctionData;
class WasmExportedFunctionData;
class WasmJSFunctionData;
@ -401,8 +402,12 @@ class SharedFunctionInfo : public HeapObject {
// hence the 'may'.
DECL_BOOLEAN_ACCESSORS(may_have_cached_code)
// Returns the cached Code object for this SFI if it exists, an empty handle
// otherwise.
// Fetches cached NCI artifacts, if they exist. Some callsites only care
// about the cached Code object; to distinguish them clearly, there's a
// dedicated helper that only returns the Code object.
bool TryGetCachedCodeAndSerializedFeedback(
Isolate* isolate, MaybeHandle<Code>* code_out,
MaybeHandle<SerializedFeedback>* feedback_out);
MaybeHandle<Code> TryGetCachedCode(Isolate* isolate);
// Is this function a top-level function (scripts, evals).

View File

@ -46,11 +46,22 @@ RUNTIME_FUNCTION(Runtime_CompileLazy) {
return ReadOnlyRoots(isolate).exception();
}
if (sfi->may_have_cached_code()) {
Handle<Code> code;
if (sfi->TryGetCachedCode(isolate).ToHandle(&code)) {
function->set_code(*code);
JSFunction::EnsureFeedbackVector(function, &is_compiled_scope);
MaybeHandle<Code> maybe_code;
MaybeHandle<SerializedFeedback> maybe_feedback;
if (sfi->TryGetCachedCodeAndSerializedFeedback(isolate, &maybe_code,
&maybe_feedback)) {
Handle<Code> code = maybe_code.ToHandleChecked();
if (FLAG_trace_turbo_nci) CompilationCacheCode::TraceHit(sfi, code);
function->set_code(*code);
if (!function->has_feedback_vector()) {
JSFunction::EnsureFeedbackVector(function, &is_compiled_scope);
// TODO(jgruber,v8:8888): Consider combining shared feedback with
// existing feedback here.
maybe_feedback.ToHandleChecked()->DeserializeInto(
function->feedback_vector());
}
return *code;
}
}

View File

@ -1444,6 +1444,19 @@
'compiler/serializer-transition-propagation': [SKIP],
# crbug.com/v8/11110
'es6/super-ic-opt*': [SKIP],
# Rely on optimizations not yet enabled for NCI. For example, preserving
# kIdentifyZero for kNumberFloor (and thus avoiding later deopts) only works
# if the JSCallReducer reduces the Math.floor builtin call to the
# kNumberFloor operator.
'compiler/number-floor': [SKIP],
'compiler/number-ceil': [SKIP],
'compiler/number-min': [SKIP],
'compiler/number-modulus': [SKIP],
'compiler/number-toboolean': [SKIP],
'compiler/number-abs': [SKIP],
'compiler/number-round': [SKIP],
'compiler/number-max': [SKIP],
'compiler/number-trunc': [SKIP],
}], # variant == nci or variant == nci_as_midtier
['((arch == mipsel or arch == mips64el or arch == mips or arch == mips64) and not simd_mips) or (arch in [ppc64, s390x])', {