| // 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/v8.h" |
| |
| #include "src/ast.h" |
| #include "src/code-stubs.h" |
| #include "src/compiler.h" |
| #include "src/ic.h" |
| #include "src/macro-assembler.h" |
| #include "src/stub-cache.h" |
| #include "src/type-info.h" |
| |
| #include "src/ic-inl.h" |
| #include "src/objects-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| |
| TypeFeedbackOracle::TypeFeedbackOracle(Handle<Code> code, |
| Handle<FixedArray> feedback_vector, |
| Handle<Context> native_context, |
| Zone* zone) |
| : native_context_(native_context), |
| zone_(zone) { |
| BuildDictionary(code); |
| ASSERT(dictionary_->IsDictionary()); |
| // 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_ = isolate()->factory()->CopyFixedArray(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(int slot) { |
| ASSERT(slot >= 0 && slot < feedback_vector_->length()); |
| Object* obj = feedback_vector_->get(slot); |
| if (!obj->IsJSFunction() || |
| !CanRetainOtherContext(JSFunction::cast(obj), *native_context_)) { |
| return Handle<Object>(obj, isolate()); |
| } |
| return Handle<Object>::cast(isolate()->factory()->undefined_value()); |
| } |
| |
| |
| bool TypeFeedbackOracle::LoadIsUninitialized(TypeFeedbackId id) { |
| Handle<Object> maybe_code = GetInfo(id); |
| if (maybe_code->IsCode()) { |
| Handle<Code> code = Handle<Code>::cast(maybe_code); |
| return code->is_inline_cache_stub() && code->ic_state() == UNINITIALIZED; |
| } |
| return false; |
| } |
| |
| |
| bool TypeFeedbackOracle::StoreIsUninitialized(TypeFeedbackId ast_id) { |
| Handle<Object> maybe_code = GetInfo(ast_id); |
| if (!maybe_code->IsCode()) return false; |
| Handle<Code> code = Handle<Code>::cast(maybe_code); |
| return code->ic_state() == UNINITIALIZED; |
| } |
| |
| |
| bool TypeFeedbackOracle::StoreIsKeyedPolymorphic(TypeFeedbackId ast_id) { |
| Handle<Object> maybe_code = GetInfo(ast_id); |
| if (maybe_code->IsCode()) { |
| Handle<Code> code = Handle<Code>::cast(maybe_code); |
| return code->is_keyed_store_stub() && |
| code->ic_state() == POLYMORPHIC; |
| } |
| return false; |
| } |
| |
| |
| bool TypeFeedbackOracle::CallIsMonomorphic(int slot) { |
| Handle<Object> value = GetInfo(slot); |
| return value->IsAllocationSite() || value->IsJSFunction(); |
| } |
| |
| |
| bool TypeFeedbackOracle::CallNewIsMonomorphic(int slot) { |
| Handle<Object> info = GetInfo(slot); |
| return FLAG_pretenuring_call_new |
| ? info->IsJSFunction() |
| : info->IsAllocationSite() || info->IsJSFunction(); |
| } |
| |
| |
| byte TypeFeedbackOracle::ForInType(int feedback_vector_slot) { |
| Handle<Object> value = GetInfo(feedback_vector_slot); |
| return value.is_identical_to( |
| TypeFeedbackInfo::UninitializedSentinel(isolate())) |
| ? ForInStatement::FAST_FOR_IN : ForInStatement::SLOW_FOR_IN; |
| } |
| |
| |
| KeyedAccessStoreMode TypeFeedbackOracle::GetStoreMode( |
| TypeFeedbackId ast_id) { |
| Handle<Object> maybe_code = GetInfo(ast_id); |
| if (maybe_code->IsCode()) { |
| Handle<Code> code = Handle<Code>::cast(maybe_code); |
| if (code->kind() == Code::KEYED_STORE_IC) { |
| return KeyedStoreIC::GetKeyedAccessStoreMode(code->extra_ic_state()); |
| } |
| } |
| return STANDARD_STORE; |
| } |
| |
| |
| Handle<JSFunction> TypeFeedbackOracle::GetCallTarget(int 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(int slot) { |
| Handle<Object> info = GetInfo(slot); |
| if (FLAG_pretenuring_call_new || info->IsJSFunction()) { |
| return Handle<JSFunction>::cast(info); |
| } |
| |
| ASSERT(info->IsAllocationSite()); |
| return Handle<JSFunction>(isolate()->native_context()->array_function()); |
| } |
| |
| |
| Handle<AllocationSite> TypeFeedbackOracle::GetCallAllocationSite(int slot) { |
| Handle<Object> info = GetInfo(slot); |
| if (info->IsAllocationSite()) { |
| return Handle<AllocationSite>::cast(info); |
| } |
| return Handle<AllocationSite>::null(); |
| } |
| |
| |
| Handle<AllocationSite> TypeFeedbackOracle::GetCallNewAllocationSite(int slot) { |
| Handle<Object> info = GetInfo(slot); |
| if (FLAG_pretenuring_call_new || info->IsAllocationSite()) { |
| return Handle<AllocationSite>::cast(info); |
| } |
| return Handle<AllocationSite>::null(); |
| } |
| |
| |
| bool TypeFeedbackOracle::LoadIsBuiltin( |
| TypeFeedbackId id, Builtins::Name builtin) { |
| return *GetInfo(id) == isolate()->builtins()->builtin(builtin); |
| } |
| |
| |
| bool TypeFeedbackOracle::LoadIsStub(TypeFeedbackId id, ICStub* stub) { |
| Handle<Object> object = GetInfo(id); |
| if (!object->IsCode()) return false; |
| Handle<Code> code = Handle<Code>::cast(object); |
| if (!code->is_load_stub()) return false; |
| if (code->ic_state() != MONOMORPHIC) return false; |
| return stub->Describes(*code); |
| } |
| |
| |
| void TypeFeedbackOracle::CompareType(TypeFeedbackId id, |
| Type** left_type, |
| Type** right_type, |
| Type** combined_type) { |
| Handle<Object> info = GetInfo(id); |
| if (!info->IsCode()) { |
| // For some comparisons we don't have ICs, e.g. LiteralCompareTypeof. |
| *left_type = *right_type = *combined_type = Type::None(zone()); |
| return; |
| } |
| Handle<Code> code = Handle<Code>::cast(info); |
| |
| Handle<Map> map; |
| Map* raw_map = code->FindFirstMap(); |
| if (raw_map != NULL) { |
| if (Map::CurrentMapForDeprecated(handle(raw_map)).ToHandle(&map) && |
| CanRetainOtherContext(*map, *native_context_)) { |
| map = Handle<Map>::null(); |
| } |
| } |
| |
| if (code->is_compare_ic_stub()) { |
| int stub_minor_key = code->stub_info(); |
| CompareIC::StubInfoToType( |
| stub_minor_key, left_type, right_type, combined_type, map, zone()); |
| } else if (code->is_compare_nil_ic_stub()) { |
| CompareNilICStub stub(isolate(), code->extra_ic_state()); |
| *combined_type = stub.GetType(zone(), map); |
| *left_type = *right_type = stub.GetInputType(zone(), map); |
| } |
| } |
| |
| |
| void TypeFeedbackOracle::BinaryType(TypeFeedbackId id, |
| Type** left, |
| Type** right, |
| Type** result, |
| Maybe<int>* fixed_right_arg, |
| Handle<AllocationSite>* allocation_site, |
| Token::Value op) { |
| Handle<Object> object = GetInfo(id); |
| if (!object->IsCode()) { |
| // For some binary ops we don't have ICs, e.g. Token::COMMA, but for the |
| // operations covered by the BinaryOpIC we should always have them. |
| ASSERT(op < BinaryOpIC::State::FIRST_TOKEN || |
| op > BinaryOpIC::State::LAST_TOKEN); |
| *left = *right = *result = Type::None(zone()); |
| *fixed_right_arg = Maybe<int>(); |
| *allocation_site = Handle<AllocationSite>::null(); |
| return; |
| } |
| Handle<Code> code = Handle<Code>::cast(object); |
| ASSERT_EQ(Code::BINARY_OP_IC, code->kind()); |
| BinaryOpIC::State state(isolate(), code->extra_ic_state()); |
| ASSERT_EQ(op, state.op()); |
| |
| *left = state.GetLeftType(zone()); |
| *right = state.GetRightType(zone()); |
| *result = state.GetResultType(zone()); |
| *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(); |
| } |
| } |
| |
| |
| Type* TypeFeedbackOracle::CountType(TypeFeedbackId id) { |
| Handle<Object> object = GetInfo(id); |
| if (!object->IsCode()) return Type::None(zone()); |
| Handle<Code> code = Handle<Code>::cast(object); |
| ASSERT_EQ(Code::BINARY_OP_IC, code->kind()); |
| BinaryOpIC::State state(isolate(), code->extra_ic_state()); |
| return state.GetLeftType(zone()); |
| } |
| |
| |
| void TypeFeedbackOracle::PropertyReceiverTypes( |
| TypeFeedbackId id, Handle<String> name, |
| SmallMapList* receiver_types, bool* is_prototype) { |
| receiver_types->Clear(); |
| FunctionPrototypeStub proto_stub(isolate(), Code::LOAD_IC); |
| *is_prototype = LoadIsStub(id, &proto_stub); |
| if (!*is_prototype) { |
| Code::Flags flags = Code::ComputeHandlerFlags(Code::LOAD_IC); |
| CollectReceiverTypes(id, name, flags, receiver_types); |
| } |
| } |
| |
| |
| void TypeFeedbackOracle::KeyedPropertyReceiverTypes( |
| TypeFeedbackId id, SmallMapList* receiver_types, bool* is_string) { |
| receiver_types->Clear(); |
| *is_string = false; |
| if (LoadIsBuiltin(id, Builtins::kKeyedLoadIC_String)) { |
| *is_string = true; |
| } else { |
| CollectReceiverTypes(id, receiver_types); |
| } |
| } |
| |
| |
| void TypeFeedbackOracle::AssignmentReceiverTypes( |
| TypeFeedbackId id, Handle<String> name, SmallMapList* receiver_types) { |
| receiver_types->Clear(); |
| Code::Flags flags = Code::ComputeHandlerFlags(Code::STORE_IC); |
| CollectReceiverTypes(id, name, flags, receiver_types); |
| } |
| |
| |
| void TypeFeedbackOracle::KeyedAssignmentReceiverTypes( |
| TypeFeedbackId id, SmallMapList* receiver_types, |
| KeyedAccessStoreMode* store_mode) { |
| receiver_types->Clear(); |
| CollectReceiverTypes(id, receiver_types); |
| *store_mode = GetStoreMode(id); |
| } |
| |
| |
| void TypeFeedbackOracle::CountReceiverTypes(TypeFeedbackId id, |
| SmallMapList* receiver_types) { |
| receiver_types->Clear(); |
| CollectReceiverTypes(id, receiver_types); |
| } |
| |
| |
| void TypeFeedbackOracle::CollectReceiverTypes(TypeFeedbackId ast_id, |
| Handle<String> name, |
| Code::Flags flags, |
| SmallMapList* types) { |
| Handle<Object> object = GetInfo(ast_id); |
| if (object->IsUndefined() || object->IsSmi()) return; |
| |
| ASSERT(object->IsCode()); |
| Handle<Code> code(Handle<Code>::cast(object)); |
| |
| if (FLAG_collect_megamorphic_maps_from_stub_cache && |
| code->ic_state() == MEGAMORPHIC) { |
| types->Reserve(4, zone()); |
| isolate()->stub_cache()->CollectMatchingMaps( |
| types, name, flags, native_context_, zone()); |
| } else { |
| CollectReceiverTypes(ast_id, types); |
| } |
| } |
| |
| |
| // Check if a map originates from a given native context. We use this |
| // information to filter out maps from different context to avoid |
| // retaining objects from different tabs in Chrome via optimized code. |
| bool TypeFeedbackOracle::CanRetainOtherContext(Map* map, |
| Context* native_context) { |
| Object* constructor = NULL; |
| while (!map->prototype()->IsNull()) { |
| constructor = map->constructor(); |
| if (!constructor->IsNull()) { |
| // If the constructor is not null or a JSFunction, we have to |
| // conservatively assume that it may retain a native context. |
| if (!constructor->IsJSFunction()) return true; |
| // Check if the constructor directly references a foreign context. |
| if (CanRetainOtherContext(JSFunction::cast(constructor), |
| native_context)) { |
| return true; |
| } |
| } |
| map = HeapObject::cast(map->prototype())->map(); |
| } |
| constructor = map->constructor(); |
| if (constructor->IsNull()) return false; |
| JSFunction* function = JSFunction::cast(constructor); |
| return CanRetainOtherContext(function, native_context); |
| } |
| |
| |
| bool TypeFeedbackOracle::CanRetainOtherContext(JSFunction* function, |
| Context* native_context) { |
| return function->context()->global_object() != native_context->global_object() |
| && function->context()->global_object() != native_context->builtins(); |
| } |
| |
| |
| void TypeFeedbackOracle::CollectReceiverTypes(TypeFeedbackId ast_id, |
| SmallMapList* types) { |
| Handle<Object> object = GetInfo(ast_id); |
| if (!object->IsCode()) return; |
| Handle<Code> code = Handle<Code>::cast(object); |
| MapHandleList maps; |
| if (code->ic_state() == MONOMORPHIC) { |
| Map* map = code->FindFirstMap(); |
| if (map != NULL) maps.Add(handle(map)); |
| } else if (code->ic_state() == POLYMORPHIC) { |
| code->FindAllMaps(&maps); |
| } else { |
| return; |
| } |
| types->Reserve(maps.length(), zone()); |
| for (int i = 0; i < maps.length(); i++) { |
| Handle<Map> map(maps.at(i)); |
| if (!CanRetainOtherContext(*map, *native_context_)) { |
| types->AddMapIfMissing(map, zone()); |
| } |
| } |
| } |
| |
| |
| byte 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: |
| case Code::COMPARE_NIL_IC: |
| SetInfo(ast_id, target); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| |
| |
| void TypeFeedbackOracle::SetInfo(TypeFeedbackId ast_id, Object* target) { |
| ASSERT(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 v8::internal |