// 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, Handle feedback_vector, Handle 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(ast_id.ToInt()); } Handle 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(cell->value(), isolate()); } else { return Handle(value, isolate()); } } return Handle::cast(isolate()->factory()->undefined_value()); } Handle TypeFeedbackOracle::GetInfo(FeedbackSlot slot) { DCHECK(slot.ToInt() >= 0 && slot.ToInt() < feedback_vector_->length()); Handle undefined = Handle::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(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 value = GetInfo(slot); return value->IsUndefined(isolate()) || value.is_identical_to( FeedbackVector::UninitializedSentinel(isolate())); } bool TypeFeedbackOracle::CallIsMonomorphic(FeedbackSlot slot) { Handle value = GetInfo(slot); return value->IsAllocationSite() || value->IsJSFunction(); } bool TypeFeedbackOracle::CallNewIsMonomorphic(FeedbackSlot slot) { Handle info = GetInfo(slot); return info->IsAllocationSite() || info->IsJSFunction(); } byte TypeFeedbackOracle::ForInType(FeedbackSlot feedback_vector_slot) { Handle 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 TypeFeedbackOracle::GetCallTarget(FeedbackSlot slot) { Handle info = GetInfo(slot); if (info->IsAllocationSite()) { return Handle(isolate()->native_context()->array_function()); } return Handle::cast(info); } Handle TypeFeedbackOracle::GetCallNewTarget(FeedbackSlot slot) { Handle info = GetInfo(slot); if (info->IsJSFunction()) { return Handle::cast(info); } DCHECK(info->IsAllocationSite()); return Handle(isolate()->native_context()->array_function()); } Handle TypeFeedbackOracle::GetCallAllocationSite( FeedbackSlot slot) { Handle info = GetInfo(slot); if (info->IsAllocationSite()) { return Handle::cast(info); } return Handle::null(); } Handle TypeFeedbackOracle::GetCallNewAllocationSite( FeedbackSlot slot) { Handle info = GetInfo(slot); if (info->IsAllocationSite()) { return Handle::cast(info); } return Handle::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 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 = Handle::cast(info); Handle 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* fixed_right_arg, Handle* allocation_site, Token::Value op) { Handle 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(); *allocation_site = Handle::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(); *allocation_site = Handle::null(); if (!object->IsCode()) return; // Merge the feedback from full-codegen if available. Handle code = Handle::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::null(); } } AstType* TypeFeedbackOracle::CountType(TypeFeedbackId id, FeedbackSlot slot) { Handle 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 = Handle::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, 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, 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, SmallMapList* types) { StoreICNexus nexus(feedback_vector_, slot); CollectReceiverTypes(stub_cache, &nexus, name, types); } void TypeFeedbackOracle::CollectReceiverTypes(StubCache* stub_cache, FeedbackNexus* nexus, Handle 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(maps.at(i)); if (IsRelevantFeedback(*map, *native_context_)) { types->AddMapIfMissing(maps.at(i), zone()); } } } uint16_t TypeFeedbackOracle::ToBooleanTypes(TypeFeedbackId id) { Handle object = GetInfo(id); return object->IsCode() ? Handle::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) { DisallowHeapAllocation no_allocation; ZoneList 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, ZoneList* 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, ZoneList* infos) { AllowHeapAllocation allocation_allowed; Code* old_code = *code; dictionary_ = UnseededNumberDictionary::New(isolate(), infos->length()); RelocateRelocInfos(infos, old_code, *code); } void TypeFeedbackOracle::RelocateRelocInfos(ZoneList* 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* 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((*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