| // 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/assembler-inl.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<FeedbackVector> 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_ = FeedbackVector::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(FeedbackSlot 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()) { |
| return Handle<Object>(obj, isolate()); |
| } |
| |
| return undefined; |
| } |
| |
| InlineCacheState TypeFeedbackOracle::LoadInlineCacheState(FeedbackSlot slot) { |
| if (!slot.IsInvalid()) { |
| FeedbackSlotKind kind = feedback_vector_->GetKind(slot); |
| if (IsLoadICKind(kind)) { |
| LoadICNexus nexus(feedback_vector_, slot); |
| return nexus.StateFromFeedback(); |
| } else if (IsKeyedLoadICKind(kind)) { |
| 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(FeedbackSlot slot) { |
| if (!slot.IsInvalid()) { |
| FeedbackSlotKind kind = feedback_vector_->GetKind(slot); |
| if (IsStoreICKind(kind)) { |
| StoreICNexus nexus(feedback_vector_, slot); |
| return nexus.StateFromFeedback() == UNINITIALIZED; |
| } else if (IsKeyedStoreICKind(kind)) { |
| KeyedStoreICNexus nexus(feedback_vector_, slot); |
| return nexus.StateFromFeedback() == UNINITIALIZED; |
| } |
| } |
| return true; |
| } |
| |
| bool TypeFeedbackOracle::CallIsUninitialized(FeedbackSlot slot) { |
| Handle<Object> value = GetInfo(slot); |
| return value->IsUndefined(isolate()) || |
| value.is_identical_to( |
| FeedbackVector::UninitializedSentinel(isolate())); |
| } |
| |
| bool TypeFeedbackOracle::CallIsMonomorphic(FeedbackSlot slot) { |
| Handle<Object> value = GetInfo(slot); |
| return value->IsAllocationSite() || value->IsJSFunction(); |
| } |
| |
| bool TypeFeedbackOracle::CallNewIsMonomorphic(FeedbackSlot slot) { |
| Handle<Object> info = GetInfo(slot); |
| return info->IsAllocationSite() || info->IsJSFunction(); |
| } |
| |
| byte TypeFeedbackOracle::ForInType(FeedbackSlot feedback_vector_slot) { |
| Handle<Object> value = GetInfo(feedback_vector_slot); |
| return value.is_identical_to(FeedbackVector::UninitializedSentinel(isolate())) |
| ? ForInStatement::FAST_FOR_IN |
| : ForInStatement::SLOW_FOR_IN; |
| } |
| |
| void TypeFeedbackOracle::GetStoreModeAndKeyType( |
| FeedbackSlot slot, KeyedAccessStoreMode* store_mode, |
| IcCheckType* key_type) { |
| if (!slot.IsInvalid() && feedback_vector_->IsKeyedStoreIC(slot)) { |
| 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(FeedbackSlot 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(FeedbackSlot 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( |
| FeedbackSlot slot) { |
| Handle<Object> info = GetInfo(slot); |
| if (info->IsAllocationSite()) { |
| return Handle<AllocationSite>::cast(info); |
| } |
| return Handle<AllocationSite>::null(); |
| } |
| |
| Handle<AllocationSite> TypeFeedbackOracle::GetCallNewAllocationSite( |
| FeedbackSlot 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::kInternalizedString: |
| return AstType::InternalizedString(); |
| case CompareOperationHint::kString: |
| return AstType::String(); |
| case CompareOperationHint::kReceiver: |
| return AstType::Receiver(); |
| 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, FeedbackSlot 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, FeedbackSlot 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, FeedbackSlot 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(FeedbackSlot 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( |
| FeedbackSlot 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(FeedbackSlot slot, |
| Handle<Name> name, |
| SmallMapList* receiver_types) { |
| receiver_types->Clear(); |
| CollectReceiverTypes(isolate()->store_stub_cache(), slot, name, |
| receiver_types); |
| } |
| |
| void TypeFeedbackOracle::KeyedAssignmentReceiverTypes( |
| FeedbackSlot 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(FeedbackSlot slot, |
| SmallMapList* receiver_types) { |
| receiver_types->Clear(); |
| if (!slot.IsInvalid()) CollectReceiverTypes(slot, receiver_types); |
| } |
| |
| void TypeFeedbackOracle::CollectReceiverTypes(StubCache* stub_cache, |
| FeedbackSlot 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(FeedbackSlot slot, |
| SmallMapList* types) { |
| FeedbackSlotKind kind = feedback_vector_->GetKind(slot); |
| if (IsStoreICKind(kind) || IsStoreOwnICKind(kind)) { |
| StoreICNexus nexus(feedback_vector_, slot); |
| CollectReceiverTypes(&nexus, types); |
| } else { |
| DCHECK(IsKeyedStoreICKind(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 |