v8/src/type-info.cc
rmcilroy 5369874042 [Interpreter] Collect String feedback on CompareOps.
Collect string feedback for compare operations. Without this,
functions which have a lot of string compare operations end up with
a high generic type percentage, and don't get optimized until very
late.

Currently TurboFan doesn't use this String feedback for compare
operations, but this could be done in future work if it is useful.

BUG=chromium:660947

Review-Url: https://codereview.chromium.org/2506013005
Cr-Commit-Position: refs/heads/master@{#41078}
2016-11-17 14:45:31 +00:00

598 lines
21 KiB
C++

// Copyright 2012 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/type-info.h"
#include "src/ast/ast.h"
#include "src/code-stubs.h"
#include "src/ic/ic.h"
#include "src/ic/stub-cache.h"
#include "src/objects-inl.h"
namespace v8 {
namespace internal {
TypeFeedbackOracle::TypeFeedbackOracle(
Isolate* isolate, Zone* zone, Handle<Code> code,
Handle<TypeFeedbackVector> feedback_vector, Handle<Context> native_context)
: native_context_(native_context), isolate_(isolate), zone_(zone) {
BuildDictionary(code);
DCHECK(dictionary_->IsUnseededNumberDictionary());
// We make a copy of the feedback vector because a GC could clear
// the type feedback info contained therein.
// TODO(mvstanton): revisit the decision to copy when we weakly
// traverse the feedback vector at GC time.
feedback_vector_ = TypeFeedbackVector::Copy(isolate, feedback_vector);
}
static uint32_t IdToKey(TypeFeedbackId ast_id) {
return static_cast<uint32_t>(ast_id.ToInt());
}
Handle<Object> TypeFeedbackOracle::GetInfo(TypeFeedbackId ast_id) {
int entry = dictionary_->FindEntry(IdToKey(ast_id));
if (entry != UnseededNumberDictionary::kNotFound) {
Object* value = dictionary_->ValueAt(entry);
if (value->IsCell()) {
Cell* cell = Cell::cast(value);
return Handle<Object>(cell->value(), isolate());
} else {
return Handle<Object>(value, isolate());
}
}
return Handle<Object>::cast(isolate()->factory()->undefined_value());
}
Handle<Object> TypeFeedbackOracle::GetInfo(FeedbackVectorSlot slot) {
DCHECK(slot.ToInt() >= 0 && slot.ToInt() < feedback_vector_->length());
Handle<Object> undefined =
Handle<Object>::cast(isolate()->factory()->undefined_value());
Object* obj = feedback_vector_->Get(slot);
// Slots do not embed direct pointers to maps, functions. Instead
// a WeakCell is always used.
if (obj->IsWeakCell()) {
WeakCell* cell = WeakCell::cast(obj);
if (cell->cleared()) return undefined;
obj = cell->value();
}
if (obj->IsJSFunction() || obj->IsAllocationSite() || obj->IsSymbol() ||
obj->IsSimd128Value()) {
return Handle<Object>(obj, isolate());
}
return undefined;
}
InlineCacheState TypeFeedbackOracle::LoadInlineCacheState(
FeedbackVectorSlot slot) {
if (!slot.IsInvalid()) {
FeedbackVectorSlotKind kind = feedback_vector_->GetKind(slot);
if (kind == FeedbackVectorSlotKind::LOAD_IC) {
LoadICNexus nexus(feedback_vector_, slot);
return nexus.StateFromFeedback();
} else if (kind == FeedbackVectorSlotKind::KEYED_LOAD_IC) {
KeyedLoadICNexus nexus(feedback_vector_, slot);
return nexus.StateFromFeedback();
}
}
// If we can't find an IC, assume we've seen *something*, but we don't know
// what. PREMONOMORPHIC roughly encodes this meaning.
return PREMONOMORPHIC;
}
bool TypeFeedbackOracle::StoreIsUninitialized(FeedbackVectorSlot slot) {
if (!slot.IsInvalid()) {
FeedbackVectorSlotKind kind = feedback_vector_->GetKind(slot);
if (kind == FeedbackVectorSlotKind::STORE_IC) {
StoreICNexus nexus(feedback_vector_, slot);
return nexus.StateFromFeedback() == UNINITIALIZED;
} else if (kind == FeedbackVectorSlotKind::KEYED_STORE_IC) {
KeyedStoreICNexus nexus(feedback_vector_, slot);
return nexus.StateFromFeedback() == UNINITIALIZED;
}
}
return true;
}
bool TypeFeedbackOracle::CallIsUninitialized(FeedbackVectorSlot slot) {
Handle<Object> value = GetInfo(slot);
return value->IsUndefined(isolate()) ||
value.is_identical_to(
TypeFeedbackVector::UninitializedSentinel(isolate()));
}
bool TypeFeedbackOracle::CallIsMonomorphic(FeedbackVectorSlot slot) {
Handle<Object> value = GetInfo(slot);
return value->IsAllocationSite() || value->IsJSFunction();
}
bool TypeFeedbackOracle::CallNewIsMonomorphic(FeedbackVectorSlot slot) {
Handle<Object> info = GetInfo(slot);
return info->IsAllocationSite() || info->IsJSFunction();
}
byte TypeFeedbackOracle::ForInType(FeedbackVectorSlot feedback_vector_slot) {
Handle<Object> value = GetInfo(feedback_vector_slot);
return value.is_identical_to(
TypeFeedbackVector::UninitializedSentinel(isolate()))
? ForInStatement::FAST_FOR_IN
: ForInStatement::SLOW_FOR_IN;
}
void TypeFeedbackOracle::GetStoreModeAndKeyType(
FeedbackVectorSlot slot, KeyedAccessStoreMode* store_mode,
IcCheckType* key_type) {
if (!slot.IsInvalid() &&
feedback_vector_->GetKind(slot) ==
FeedbackVectorSlotKind::KEYED_STORE_IC) {
KeyedStoreICNexus nexus(feedback_vector_, slot);
*store_mode = nexus.GetKeyedAccessStoreMode();
*key_type = nexus.GetKeyType();
} else {
*store_mode = STANDARD_STORE;
*key_type = ELEMENT;
}
}
Handle<JSFunction> TypeFeedbackOracle::GetCallTarget(FeedbackVectorSlot slot) {
Handle<Object> info = GetInfo(slot);
if (info->IsAllocationSite()) {
return Handle<JSFunction>(isolate()->native_context()->array_function());
}
return Handle<JSFunction>::cast(info);
}
Handle<JSFunction> TypeFeedbackOracle::GetCallNewTarget(
FeedbackVectorSlot slot) {
Handle<Object> info = GetInfo(slot);
if (info->IsJSFunction()) {
return Handle<JSFunction>::cast(info);
}
DCHECK(info->IsAllocationSite());
return Handle<JSFunction>(isolate()->native_context()->array_function());
}
Handle<AllocationSite> TypeFeedbackOracle::GetCallAllocationSite(
FeedbackVectorSlot slot) {
Handle<Object> info = GetInfo(slot);
if (info->IsAllocationSite()) {
return Handle<AllocationSite>::cast(info);
}
return Handle<AllocationSite>::null();
}
Handle<AllocationSite> TypeFeedbackOracle::GetCallNewAllocationSite(
FeedbackVectorSlot slot) {
Handle<Object> info = GetInfo(slot);
if (info->IsAllocationSite()) {
return Handle<AllocationSite>::cast(info);
}
return Handle<AllocationSite>::null();
}
namespace {
AstType* CompareOpHintToType(CompareOperationHint hint) {
switch (hint) {
case CompareOperationHint::kNone:
return AstType::None();
case CompareOperationHint::kSignedSmall:
return AstType::SignedSmall();
case CompareOperationHint::kNumber:
return AstType::Number();
case CompareOperationHint::kNumberOrOddball:
return AstType::NumberOrOddball();
case CompareOperationHint::kString:
return AstType::String();
case CompareOperationHint::kAny:
return AstType::Any();
}
UNREACHABLE();
return AstType::None();
}
AstType* BinaryOpFeedbackToType(int hint) {
switch (hint) {
case BinaryOperationFeedback::kNone:
return AstType::None();
case BinaryOperationFeedback::kSignedSmall:
return AstType::SignedSmall();
case BinaryOperationFeedback::kNumber:
return AstType::Number();
case BinaryOperationFeedback::kString:
return AstType::String();
case BinaryOperationFeedback::kNumberOrOddball:
return AstType::NumberOrOddball();
case BinaryOperationFeedback::kAny:
default:
return AstType::Any();
}
UNREACHABLE();
return AstType::None();
}
} // end anonymous namespace
void TypeFeedbackOracle::CompareType(TypeFeedbackId id, FeedbackVectorSlot slot,
AstType** left_type, AstType** right_type,
AstType** combined_type) {
Handle<Object> info = GetInfo(id);
// A check for a valid slot is not sufficient here. InstanceOf collects
// type feedback in a General slot.
if (!info->IsCode()) {
// For some comparisons we don't have type feedback, e.g.
// LiteralCompareTypeof.
*left_type = *right_type = *combined_type = AstType::None();
return;
}
// Feedback from Ignition. The feedback slot will be allocated and initialized
// to AstType::None() even when ignition is not enabled. So it is safe to get
// feedback from the type feedback vector.
DCHECK(!slot.IsInvalid());
CompareICNexus nexus(feedback_vector_, slot);
*left_type = *right_type = *combined_type =
CompareOpHintToType(nexus.GetCompareOperationFeedback());
// Merge the feedback from full-codegen if available.
Handle<Code> code = Handle<Code>::cast(info);
Handle<Map> map;
Map* raw_map = code->FindFirstMap();
if (raw_map != NULL) Map::TryUpdate(handle(raw_map)).ToHandle(&map);
if (code->is_compare_ic_stub()) {
CompareICStub stub(code->stub_key(), isolate());
AstType* left_type_from_ic =
CompareICState::StateToType(zone(), stub.left());
AstType* right_type_from_ic =
CompareICState::StateToType(zone(), stub.right());
AstType* combined_type_from_ic =
CompareICState::StateToType(zone(), stub.state(), map);
// Full-codegen collects lhs and rhs feedback seperately and Crankshaft
// could use this information to optimize better. So if combining the
// feedback has made the feedback less precise, we should use the feedback
// only from Full-codegen. If the union of the feedback from Full-codegen
// is same as that of Ignition, there is no need to combine feedback from
// from Ignition.
AstType* combined_type_from_fcg = AstType::Union(
left_type_from_ic,
AstType::Union(right_type_from_ic, combined_type_from_ic, zone()),
zone());
if (combined_type_from_fcg == *left_type) {
// Full-codegen collects information about lhs, rhs and result types
// seperately. So just retain that information.
*left_type = left_type_from_ic;
*right_type = right_type_from_ic;
*combined_type = combined_type_from_ic;
} else {
// Combine Ignition and Full-codegen feedbacks.
*left_type = AstType::Union(*left_type, left_type_from_ic, zone());
*right_type = AstType::Union(*right_type, right_type_from_ic, zone());
*combined_type =
AstType::Union(*combined_type, combined_type_from_ic, zone());
}
}
}
void TypeFeedbackOracle::BinaryType(TypeFeedbackId id, FeedbackVectorSlot slot,
AstType** left, AstType** right,
AstType** result,
Maybe<int>* fixed_right_arg,
Handle<AllocationSite>* allocation_site,
Token::Value op) {
Handle<Object> object = GetInfo(id);
if (slot.IsInvalid()) {
// For some binary ops we don't have ICs or feedback slots,
// e.g. Token::COMMA, but for the operations covered by the BinaryOpIC we
// should always have them.
DCHECK(!object->IsCode());
DCHECK(op < BinaryOpICState::FIRST_TOKEN ||
op > BinaryOpICState::LAST_TOKEN);
*left = *right = *result = AstType::None();
*fixed_right_arg = Nothing<int>();
*allocation_site = Handle<AllocationSite>::null();
return;
}
// Feedback from Ignition. The feedback slot will be allocated and initialized
// to AstType::None() even when ignition is not enabled. So it is safe to get
// feedback from the type feedback vector.
DCHECK(!slot.IsInvalid());
BinaryOpICNexus nexus(feedback_vector_, slot);
*left = *right = *result =
BinaryOpFeedbackToType(Smi::cast(nexus.GetFeedback())->value());
*fixed_right_arg = Nothing<int>();
*allocation_site = Handle<AllocationSite>::null();
if (!object->IsCode()) return;
// Merge the feedback from full-codegen if available.
Handle<Code> code = Handle<Code>::cast(object);
DCHECK_EQ(Code::BINARY_OP_IC, code->kind());
BinaryOpICState state(isolate(), code->extra_ic_state());
DCHECK_EQ(op, state.op());
// Full-codegen collects lhs and rhs feedback seperately and Crankshaft
// could use this information to optimize better. So if combining the
// feedback has made the feedback less precise, we should use the feedback
// only from Full-codegen. If the union of the feedback from Full-codegen
// is same as that of Ignition, there is no need to combine feedback from
// from Ignition.
AstType* combined_type_from_fcg = AstType::Union(
state.GetLeftType(),
AstType::Union(state.GetRightType(), state.GetResultType(), zone()),
zone());
if (combined_type_from_fcg == *left) {
// Full-codegen collects information about lhs, rhs and result types
// seperately. So just retain that information.
*left = state.GetLeftType();
*right = state.GetRightType();
*result = state.GetResultType();
} else {
// Combine Ignition and Full-codegen feedback.
*left = AstType::Union(*left, state.GetLeftType(), zone());
*right = AstType::Union(*right, state.GetRightType(), zone());
*result = AstType::Union(*result, state.GetResultType(), zone());
}
// Ignition does not collect this feedback.
*fixed_right_arg = state.fixed_right_arg();
AllocationSite* first_allocation_site = code->FindFirstAllocationSite();
if (first_allocation_site != NULL) {
*allocation_site = handle(first_allocation_site);
} else {
*allocation_site = Handle<AllocationSite>::null();
}
}
AstType* TypeFeedbackOracle::CountType(TypeFeedbackId id,
FeedbackVectorSlot slot) {
Handle<Object> object = GetInfo(id);
if (slot.IsInvalid()) {
DCHECK(!object->IsCode());
return AstType::None();
}
DCHECK(!slot.IsInvalid());
BinaryOpICNexus nexus(feedback_vector_, slot);
AstType* type =
BinaryOpFeedbackToType(Smi::cast(nexus.GetFeedback())->value());
if (!object->IsCode()) return type;
Handle<Code> code = Handle<Code>::cast(object);
DCHECK_EQ(Code::BINARY_OP_IC, code->kind());
BinaryOpICState state(isolate(), code->extra_ic_state());
return AstType::Union(type, state.GetLeftType(), zone());
}
bool TypeFeedbackOracle::HasOnlyStringMaps(SmallMapList* receiver_types) {
bool all_strings = receiver_types->length() > 0;
for (int i = 0; i < receiver_types->length(); i++) {
all_strings &= receiver_types->at(i)->IsStringMap();
}
return all_strings;
}
void TypeFeedbackOracle::PropertyReceiverTypes(FeedbackVectorSlot slot,
Handle<Name> name,
SmallMapList* receiver_types) {
receiver_types->Clear();
if (!slot.IsInvalid()) {
LoadICNexus nexus(feedback_vector_, slot);
CollectReceiverTypes(isolate()->load_stub_cache(), &nexus, name,
receiver_types);
}
}
void TypeFeedbackOracle::KeyedPropertyReceiverTypes(
FeedbackVectorSlot slot, SmallMapList* receiver_types, bool* is_string,
IcCheckType* key_type) {
receiver_types->Clear();
if (slot.IsInvalid()) {
*is_string = false;
*key_type = ELEMENT;
} else {
KeyedLoadICNexus nexus(feedback_vector_, slot);
CollectReceiverTypes(&nexus, receiver_types);
*is_string = HasOnlyStringMaps(receiver_types);
*key_type = nexus.GetKeyType();
}
}
void TypeFeedbackOracle::AssignmentReceiverTypes(FeedbackVectorSlot slot,
Handle<Name> name,
SmallMapList* receiver_types) {
receiver_types->Clear();
CollectReceiverTypes(isolate()->store_stub_cache(), slot, name,
receiver_types);
}
void TypeFeedbackOracle::KeyedAssignmentReceiverTypes(
FeedbackVectorSlot slot, SmallMapList* receiver_types,
KeyedAccessStoreMode* store_mode, IcCheckType* key_type) {
receiver_types->Clear();
CollectReceiverTypes(slot, receiver_types);
GetStoreModeAndKeyType(slot, store_mode, key_type);
}
void TypeFeedbackOracle::CountReceiverTypes(FeedbackVectorSlot slot,
SmallMapList* receiver_types) {
receiver_types->Clear();
if (!slot.IsInvalid()) CollectReceiverTypes(slot, receiver_types);
}
void TypeFeedbackOracle::CollectReceiverTypes(StubCache* stub_cache,
FeedbackVectorSlot slot,
Handle<Name> name,
SmallMapList* types) {
StoreICNexus nexus(feedback_vector_, slot);
CollectReceiverTypes(stub_cache, &nexus, name, types);
}
void TypeFeedbackOracle::CollectReceiverTypes(StubCache* stub_cache,
FeedbackNexus* nexus,
Handle<Name> name,
SmallMapList* types) {
if (FLAG_collect_megamorphic_maps_from_stub_cache &&
nexus->ic_state() == MEGAMORPHIC) {
types->Reserve(4, zone());
stub_cache->CollectMatchingMaps(types, name, native_context_, zone());
} else {
CollectReceiverTypes(nexus, types);
}
}
void TypeFeedbackOracle::CollectReceiverTypes(FeedbackVectorSlot slot,
SmallMapList* types) {
FeedbackVectorSlotKind kind = feedback_vector_->GetKind(slot);
if (kind == FeedbackVectorSlotKind::STORE_IC) {
StoreICNexus nexus(feedback_vector_, slot);
CollectReceiverTypes(&nexus, types);
} else {
DCHECK_EQ(FeedbackVectorSlotKind::KEYED_STORE_IC, kind);
KeyedStoreICNexus nexus(feedback_vector_, slot);
CollectReceiverTypes(&nexus, types);
}
}
void TypeFeedbackOracle::CollectReceiverTypes(FeedbackNexus* nexus,
SmallMapList* types) {
MapHandleList maps;
if (nexus->ic_state() == MONOMORPHIC) {
Map* map = nexus->FindFirstMap();
if (map != NULL) maps.Add(handle(map));
} else if (nexus->ic_state() == POLYMORPHIC) {
nexus->FindAllMaps(&maps);
} else {
return;
}
types->Reserve(maps.length(), zone());
for (int i = 0; i < maps.length(); i++) {
Handle<Map> map(maps.at(i));
if (IsRelevantFeedback(*map, *native_context_)) {
types->AddMapIfMissing(maps.at(i), zone());
}
}
}
uint16_t TypeFeedbackOracle::ToBooleanTypes(TypeFeedbackId id) {
Handle<Object> object = GetInfo(id);
return object->IsCode() ? Handle<Code>::cast(object)->to_boolean_state() : 0;
}
// Things are a bit tricky here: The iterator for the RelocInfos and the infos
// themselves are not GC-safe, so we first get all infos, then we create the
// dictionary (possibly triggering GC), and finally we relocate the collected
// infos before we process them.
void TypeFeedbackOracle::BuildDictionary(Handle<Code> code) {
DisallowHeapAllocation no_allocation;
ZoneList<RelocInfo> infos(16, zone());
HandleScope scope(isolate());
GetRelocInfos(code, &infos);
CreateDictionary(code, &infos);
ProcessRelocInfos(&infos);
// Allocate handle in the parent scope.
dictionary_ = scope.CloseAndEscape(dictionary_);
}
void TypeFeedbackOracle::GetRelocInfos(Handle<Code> code,
ZoneList<RelocInfo>* infos) {
int mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET_WITH_ID);
for (RelocIterator it(*code, mask); !it.done(); it.next()) {
infos->Add(*it.rinfo(), zone());
}
}
void TypeFeedbackOracle::CreateDictionary(Handle<Code> code,
ZoneList<RelocInfo>* infos) {
AllowHeapAllocation allocation_allowed;
Code* old_code = *code;
dictionary_ = UnseededNumberDictionary::New(isolate(), infos->length());
RelocateRelocInfos(infos, old_code, *code);
}
void TypeFeedbackOracle::RelocateRelocInfos(ZoneList<RelocInfo>* infos,
Code* old_code,
Code* new_code) {
for (int i = 0; i < infos->length(); i++) {
RelocInfo* info = &(*infos)[i];
info->set_host(new_code);
info->set_pc(new_code->instruction_start() +
(info->pc() - old_code->instruction_start()));
}
}
void TypeFeedbackOracle::ProcessRelocInfos(ZoneList<RelocInfo>* infos) {
for (int i = 0; i < infos->length(); i++) {
RelocInfo reloc_entry = (*infos)[i];
Address target_address = reloc_entry.target_address();
TypeFeedbackId ast_id =
TypeFeedbackId(static_cast<unsigned>((*infos)[i].data()));
Code* target = Code::GetCodeFromTargetAddress(target_address);
switch (target->kind()) {
case Code::LOAD_IC:
case Code::STORE_IC:
case Code::KEYED_LOAD_IC:
case Code::KEYED_STORE_IC:
case Code::BINARY_OP_IC:
case Code::COMPARE_IC:
case Code::TO_BOOLEAN_IC:
SetInfo(ast_id, target);
break;
default:
break;
}
}
}
void TypeFeedbackOracle::SetInfo(TypeFeedbackId ast_id, Object* target) {
DCHECK(dictionary_->FindEntry(IdToKey(ast_id)) ==
UnseededNumberDictionary::kNotFound);
// Dictionary has been allocated with sufficient size for all elements.
DisallowHeapAllocation no_need_to_resize_dictionary;
HandleScope scope(isolate());
USE(UnseededNumberDictionary::AtNumberPut(
dictionary_, IdToKey(ast_id), handle(target, isolate())));
}
} // namespace internal
} // namespace v8