// Copyright 2014 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/ic/handler-compiler.h" #include "src/ic/call-optimization.h" #include "src/ic/ic.h" #include "src/ic/ic-inl.h" #include "src/isolate-inl.h" #include "src/profiler/cpu-profiler.h" namespace v8 { namespace internal { Handle PropertyHandlerCompiler::Find(Handle name, Handle stub_holder, Code::Kind kind, CacheHolderFlag cache_holder, Code::StubType type) { Code::Flags flags = Code::ComputeHandlerFlags(kind, type, cache_holder); Object* probe = stub_holder->FindInCodeCache(*name, flags); if (probe->IsCode()) return handle(Code::cast(probe)); return Handle::null(); } Handle NamedLoadHandlerCompiler::ComputeLoadNonexistent( Handle name, Handle receiver_map) { Isolate* isolate = name->GetIsolate(); if (receiver_map->prototype()->IsNull()) { // TODO(jkummerow/verwaest): If there is no prototype and the property // is nonexistent, introduce a builtin to handle this (fast properties // -> return undefined, dictionary properties -> do negative lookup). return Handle(); } CacheHolderFlag flag; Handle stub_holder_map = IC::GetHandlerCacheHolder(receiver_map, false, isolate, &flag); // If no dictionary mode objects are present in the prototype chain, the load // nonexistent IC stub can be shared for all names for a given map and we use // the empty string for the map cache in that case. If there are dictionary // mode objects involved, we need to do negative lookups in the stub and // therefore the stub will be specific to the name. Handle cache_name = receiver_map->is_dictionary_map() ? name : Handle::cast(isolate->factory()->nonexistent_symbol()); Handle current_map = stub_holder_map; Handle last(JSObject::cast(receiver_map->prototype())); while (true) { if (current_map->is_dictionary_map()) cache_name = name; if (current_map->prototype()->IsNull()) break; if (name->IsPrivate()) { // TODO(verwaest): Use nonexistent_private_symbol. cache_name = name; JSReceiver* prototype = JSReceiver::cast(current_map->prototype()); if (!prototype->map()->is_hidden_prototype() && !prototype->map()->IsJSGlobalObjectMap()) { break; } } last = handle(JSObject::cast(current_map->prototype())); current_map = handle(last->map()); } // Compile the stub that is either shared for all names or // name specific if there are global objects involved. Handle handler = PropertyHandlerCompiler::Find( cache_name, stub_holder_map, Code::LOAD_IC, flag, Code::FAST); if (!handler.is_null()) return handler; NamedLoadHandlerCompiler compiler(isolate, receiver_map, last, flag); handler = compiler.CompileLoadNonexistent(cache_name); Map::UpdateCodeCache(stub_holder_map, cache_name, handler); return handler; } Handle PropertyHandlerCompiler::GetCode(Code::Kind kind, Code::StubType type, Handle name) { Code::Flags flags = Code::ComputeHandlerFlags(kind, type, cache_holder()); Handle code = GetCodeWithFlags(flags, name); PROFILE(isolate(), CodeCreateEvent(Logger::HANDLER_TAG, *code, *name)); #ifdef DEBUG code->VerifyEmbeddedObjects(); #endif return code; } #define __ ACCESS_MASM(masm()) Register NamedLoadHandlerCompiler::FrontendHeader(Register object_reg, Handle name, Label* miss, ReturnHolder return_what) { PrototypeCheckType check_type = SKIP_RECEIVER; int function_index = map()->IsPrimitiveMap() ? map()->GetConstructorFunctionIndex() : Map::kNoConstructorFunctionIndex; if (function_index != Map::kNoConstructorFunctionIndex) { GenerateDirectLoadGlobalFunctionPrototype(masm(), function_index, scratch1(), miss); Object* function = isolate()->native_context()->get(function_index); Object* prototype = JSFunction::cast(function)->instance_prototype(); Handle map(JSObject::cast(prototype)->map()); set_map(map); object_reg = scratch1(); check_type = CHECK_ALL_MAPS; } // Check that the maps starting from the prototype haven't changed. return CheckPrototypes(object_reg, scratch1(), scratch2(), scratch3(), name, miss, check_type, return_what); } // Frontend for store uses the name register. It has to be restored before a // miss. Register NamedStoreHandlerCompiler::FrontendHeader(Register object_reg, Handle name, Label* miss, ReturnHolder return_what) { return CheckPrototypes(object_reg, this->name(), scratch1(), scratch2(), name, miss, SKIP_RECEIVER, return_what); } Register PropertyHandlerCompiler::Frontend(Handle name) { Label miss; if (IC::ICUseVector(kind())) { PushVectorAndSlot(); } Register reg = FrontendHeader(receiver(), name, &miss, RETURN_HOLDER); FrontendFooter(name, &miss); // The footer consumes the vector and slot from the stack if miss occurs. if (IC::ICUseVector(kind())) { DiscardVectorAndSlot(); } return reg; } void PropertyHandlerCompiler::NonexistentFrontendHeader(Handle name, Label* miss, Register scratch1, Register scratch2) { Register holder_reg; Handle last_map; if (holder().is_null()) { holder_reg = receiver(); last_map = map(); // If |type| has null as its prototype, |holder()| is // Handle::null(). DCHECK(last_map->prototype() == isolate()->heap()->null_value()); } else { last_map = handle(holder()->map()); // This condition matches the branches below. bool need_holder = last_map->is_dictionary_map() && !last_map->IsJSGlobalObjectMap(); holder_reg = FrontendHeader(receiver(), name, miss, need_holder ? RETURN_HOLDER : DONT_RETURN_ANYTHING); } if (last_map->is_dictionary_map()) { if (last_map->IsJSGlobalObjectMap()) { Handle global = holder().is_null() ? Handle::cast(isolate()->global_object()) : Handle::cast(holder()); GenerateCheckPropertyCell(masm(), global, name, scratch1, miss); } else { if (!name->IsUniqueName()) { DCHECK(name->IsString()); name = factory()->InternalizeString(Handle::cast(name)); } DCHECK(holder().is_null() || holder()->property_dictionary()->FindEntry(name) == NameDictionary::kNotFound); GenerateDictionaryNegativeLookup(masm(), miss, holder_reg, name, scratch1, scratch2); } } } Handle NamedLoadHandlerCompiler::CompileLoadField(Handle name, FieldIndex field) { Register reg = Frontend(name); __ Move(receiver(), reg); LoadFieldStub stub(isolate(), field); GenerateTailCall(masm(), stub.GetCode()); return GetCode(kind(), Code::FAST, name); } Handle NamedLoadHandlerCompiler::CompileLoadConstant(Handle name, int constant_index) { Register reg = Frontend(name); __ Move(receiver(), reg); LoadConstantStub stub(isolate(), constant_index); GenerateTailCall(masm(), stub.GetCode()); return GetCode(kind(), Code::FAST, name); } Handle NamedLoadHandlerCompiler::CompileLoadNonexistent( Handle name) { Label miss; if (IC::ICUseVector(kind())) { DCHECK(kind() == Code::LOAD_IC); PushVectorAndSlot(); } NonexistentFrontendHeader(name, &miss, scratch2(), scratch3()); if (IC::ICUseVector(kind())) { DiscardVectorAndSlot(); } GenerateLoadConstant(isolate()->factory()->undefined_value()); FrontendFooter(name, &miss); return GetCode(kind(), Code::FAST, name); } Handle NamedLoadHandlerCompiler::CompileLoadCallback( Handle name, Handle callback) { Register reg = Frontend(name); GenerateLoadCallback(reg, callback); return GetCode(kind(), Code::FAST, name); } Handle NamedLoadHandlerCompiler::CompileLoadCallback( Handle name, const CallOptimization& call_optimization, int accessor_index) { DCHECK(call_optimization.is_simple_api_call()); Register holder = Frontend(name); GenerateApiAccessorCall(masm(), call_optimization, map(), receiver(), scratch2(), false, no_reg, holder, accessor_index); return GetCode(kind(), Code::FAST, name); } void NamedLoadHandlerCompiler::InterceptorVectorSlotPush(Register holder_reg) { if (IC::ICUseVector(kind())) { if (holder_reg.is(receiver())) { PushVectorAndSlot(); } else { DCHECK(holder_reg.is(scratch1())); PushVectorAndSlot(scratch2(), scratch3()); } } } void NamedLoadHandlerCompiler::InterceptorVectorSlotPop(Register holder_reg, PopMode mode) { if (IC::ICUseVector(kind())) { if (mode == DISCARD) { DiscardVectorAndSlot(); } else { if (holder_reg.is(receiver())) { PopVectorAndSlot(); } else { DCHECK(holder_reg.is(scratch1())); PopVectorAndSlot(scratch2(), scratch3()); } } } } Handle NamedLoadHandlerCompiler::CompileLoadInterceptor( LookupIterator* it) { // So far the most popular follow ups for interceptor loads are DATA and // ExecutableAccessorInfo, so inline only them. Other cases may be added // later. bool inline_followup = false; switch (it->state()) { case LookupIterator::TRANSITION: UNREACHABLE(); case LookupIterator::ACCESS_CHECK: case LookupIterator::INTERCEPTOR: case LookupIterator::JSPROXY: case LookupIterator::NOT_FOUND: case LookupIterator::INTEGER_INDEXED_EXOTIC: break; case LookupIterator::DATA: inline_followup = it->property_details().type() == DATA && !it->is_dictionary_holder(); break; case LookupIterator::ACCESSOR: { Handle accessors = it->GetAccessors(); if (accessors->IsExecutableAccessorInfo()) { Handle info = Handle::cast(accessors); inline_followup = info->getter() != NULL && ExecutableAccessorInfo::IsCompatibleReceiverMap( isolate(), info, map()); } else if (accessors->IsAccessorPair()) { Handle property_holder(it->GetHolder()); Handle getter(Handle::cast(accessors)->getter(), isolate()); if (!getter->IsJSFunction()) break; if (!property_holder->HasFastProperties()) break; auto function = Handle::cast(getter); CallOptimization call_optimization(function); Handle receiver_map = map(); inline_followup = call_optimization.is_simple_api_call() && call_optimization.IsCompatibleReceiverMap( receiver_map, property_holder); } } } Label miss; InterceptorVectorSlotPush(receiver()); bool lost_holder_register = false; auto holder_orig = holder(); // non masking interceptors must check the entire chain, so temporarily reset // the holder to be that last element for the FrontendHeader call. if (holder()->GetNamedInterceptor()->non_masking()) { DCHECK(!inline_followup); JSObject* last = *holder(); PrototypeIterator iter(isolate(), last); while (!iter.IsAtEnd()) { lost_holder_register = true; // Casting to JSObject is fine here. The LookupIterator makes sure to // look behind non-masking interceptors during the original lookup, and // we wouldn't try to compile a handler if there was a Proxy anywhere. last = iter.GetCurrent(); iter.Advance(); } auto last_handle = handle(last); set_holder(last_handle); } Register reg = FrontendHeader(receiver(), it->name(), &miss, RETURN_HOLDER); // Reset the holder so further calculations are correct. set_holder(holder_orig); if (lost_holder_register) { if (*it->GetReceiver() == *holder()) { reg = receiver(); } else { // Reload lost holder register. auto cell = isolate()->factory()->NewWeakCell(holder()); __ LoadWeakValue(reg, cell, &miss); } } FrontendFooter(it->name(), &miss); InterceptorVectorSlotPop(reg); if (inline_followup) { // TODO(368): Compile in the whole chain: all the interceptors in // prototypes and ultimate answer. GenerateLoadInterceptorWithFollowup(it, reg); } else { GenerateLoadInterceptor(reg); } return GetCode(kind(), Code::FAST, it->name()); } void NamedLoadHandlerCompiler::GenerateLoadPostInterceptor( LookupIterator* it, Register interceptor_reg) { Handle real_named_property_holder(it->GetHolder()); Handle holder_map(holder()->map()); set_map(holder_map); set_holder(real_named_property_holder); Label miss; InterceptorVectorSlotPush(interceptor_reg); Register reg = FrontendHeader(interceptor_reg, it->name(), &miss, RETURN_HOLDER); FrontendFooter(it->name(), &miss); // We discard the vector and slot now because we don't miss below this point. InterceptorVectorSlotPop(reg, DISCARD); switch (it->state()) { case LookupIterator::ACCESS_CHECK: case LookupIterator::INTERCEPTOR: case LookupIterator::JSPROXY: case LookupIterator::NOT_FOUND: case LookupIterator::INTEGER_INDEXED_EXOTIC: case LookupIterator::TRANSITION: UNREACHABLE(); case LookupIterator::DATA: { DCHECK_EQ(DATA, it->property_details().type()); __ Move(receiver(), reg); LoadFieldStub stub(isolate(), it->GetFieldIndex()); GenerateTailCall(masm(), stub.GetCode()); break; } case LookupIterator::ACCESSOR: if (it->GetAccessors()->IsExecutableAccessorInfo()) { Handle info = Handle::cast(it->GetAccessors()); DCHECK_NOT_NULL(info->getter()); GenerateLoadCallback(reg, info); } else { auto function = handle(JSFunction::cast( AccessorPair::cast(*it->GetAccessors())->getter())); CallOptimization call_optimization(function); GenerateApiAccessorCall(masm(), call_optimization, holder_map, receiver(), scratch2(), false, no_reg, reg, it->GetAccessorIndex()); } } } Handle NamedLoadHandlerCompiler::CompileLoadViaGetter( Handle name, int accessor_index, int expected_arguments) { Register holder = Frontend(name); GenerateLoadViaGetter(masm(), map(), receiver(), holder, accessor_index, expected_arguments, scratch2()); return GetCode(kind(), Code::FAST, name); } // TODO(verwaest): Cleanup. holder() is actually the receiver. Handle NamedStoreHandlerCompiler::CompileStoreTransition( Handle transition, Handle name) { Label miss; PushVectorAndSlot(); // Check that we are allowed to write this. bool is_nonexistent = holder()->map() == transition->GetBackPointer(); if (is_nonexistent) { // Find the top object. Handle last; PrototypeIterator::WhereToEnd end = name->IsPrivate() ? PrototypeIterator::END_AT_NON_HIDDEN : PrototypeIterator::END_AT_NULL; PrototypeIterator iter(isolate(), holder()); while (!iter.IsAtEnd(end)) { last = PrototypeIterator::GetCurrent(iter); iter.Advance(); } if (!last.is_null()) set_holder(last); NonexistentFrontendHeader(name, &miss, scratch1(), scratch2()); } else { FrontendHeader(receiver(), name, &miss, DONT_RETURN_ANYTHING); DCHECK(holder()->HasFastProperties()); } int descriptor = transition->LastAdded(); Handle descriptors(transition->instance_descriptors()); PropertyDetails details = descriptors->GetDetails(descriptor); Representation representation = details.representation(); DCHECK(!representation.IsNone()); // Stub is never generated for objects that require access checks. DCHECK(!transition->is_access_check_needed()); // Call to respective StoreTransitionStub. bool virtual_args = StoreTransitionHelper::HasVirtualSlotArg(); Register map_reg = StoreTransitionHelper::MapRegister(); if (details.type() == DATA_CONSTANT) { DCHECK(descriptors->GetValue(descriptor)->IsJSFunction()); Register tmp = virtual_args ? VectorStoreICDescriptor::VectorRegister() : map_reg; GenerateRestoreMap(transition, tmp, scratch2(), &miss); GenerateConstantCheck(tmp, descriptor, value(), scratch2(), &miss); if (virtual_args) { // This will move the map from tmp into map_reg. RearrangeVectorAndSlot(tmp, map_reg); } else { PopVectorAndSlot(); } GenerateRestoreName(name); StoreTransitionStub stub(isolate()); GenerateTailCall(masm(), stub.GetCode()); } else { if (representation.IsHeapObject()) { GenerateFieldTypeChecks(descriptors->GetFieldType(descriptor), value(), &miss); } StoreTransitionStub::StoreMode store_mode = Map::cast(transition->GetBackPointer())->unused_property_fields() == 0 ? StoreTransitionStub::ExtendStorageAndStoreMapAndValue : StoreTransitionStub::StoreMapAndValue; Register tmp = virtual_args ? VectorStoreICDescriptor::VectorRegister() : map_reg; GenerateRestoreMap(transition, tmp, scratch2(), &miss); if (virtual_args) { RearrangeVectorAndSlot(tmp, map_reg); } else { PopVectorAndSlot(); } GenerateRestoreName(name); StoreTransitionStub stub(isolate(), FieldIndex::ForDescriptor(*transition, descriptor), representation, store_mode); GenerateTailCall(masm(), stub.GetCode()); } GenerateRestoreName(&miss, name); PopVectorAndSlot(); TailCallBuiltin(masm(), MissBuiltin(kind())); return GetCode(kind(), Code::FAST, name); } bool NamedStoreHandlerCompiler::RequiresFieldTypeChecks( HeapType* field_type) const { return !field_type->Classes().Done(); } Handle NamedStoreHandlerCompiler::CompileStoreField(LookupIterator* it) { Label miss; DCHECK(it->representation().IsHeapObject()); HeapType* field_type = *it->GetFieldType(); bool need_save_restore = false; if (RequiresFieldTypeChecks(field_type)) { need_save_restore = IC::ICUseVector(kind()); if (need_save_restore) PushVectorAndSlot(); GenerateFieldTypeChecks(field_type, value(), &miss); if (need_save_restore) PopVectorAndSlot(); } StoreFieldStub stub(isolate(), it->GetFieldIndex(), it->representation()); GenerateTailCall(masm(), stub.GetCode()); __ bind(&miss); if (need_save_restore) PopVectorAndSlot(); TailCallBuiltin(masm(), MissBuiltin(kind())); return GetCode(kind(), Code::FAST, it->name()); } Handle NamedStoreHandlerCompiler::CompileStoreViaSetter( Handle object, Handle name, int accessor_index, int expected_arguments) { Register holder = Frontend(name); GenerateStoreViaSetter(masm(), map(), receiver(), holder, accessor_index, expected_arguments, scratch2()); return GetCode(kind(), Code::FAST, name); } Handle NamedStoreHandlerCompiler::CompileStoreCallback( Handle object, Handle name, const CallOptimization& call_optimization, int accessor_index) { Register holder = Frontend(name); GenerateApiAccessorCall(masm(), call_optimization, handle(object->map()), receiver(), scratch2(), true, value(), holder, accessor_index); return GetCode(kind(), Code::FAST, name); } #undef __ void ElementHandlerCompiler::CompileElementHandlers( MapHandleList* receiver_maps, CodeHandleList* handlers, LanguageMode language_mode) { for (int i = 0; i < receiver_maps->length(); ++i) { Handle receiver_map = receiver_maps->at(i); Handle cached_stub; if (receiver_map->IsStringMap()) { cached_stub = LoadIndexedStringStub(isolate()).GetCode(); } else if (receiver_map->instance_type() < FIRST_JS_RECEIVER_TYPE) { cached_stub = is_strong(language_mode) ? isolate()->builtins()->KeyedLoadIC_Slow_Strong() : isolate()->builtins()->KeyedLoadIC_Slow(); } else { bool is_js_array = receiver_map->instance_type() == JS_ARRAY_TYPE; ElementsKind elements_kind = receiver_map->elements_kind(); // No need to check for an elements-free prototype chain here, the // generated stub code needs to check that dynamically anyway. bool convert_hole_to_undefined = (is_js_array && elements_kind == FAST_HOLEY_ELEMENTS && *receiver_map == isolate()->get_initial_js_array_map(elements_kind)) && !is_strong(language_mode); if (receiver_map->has_indexed_interceptor()) { cached_stub = LoadIndexedInterceptorStub(isolate()).GetCode(); } else if (IsSloppyArgumentsElements(elements_kind)) { cached_stub = KeyedLoadSloppyArgumentsStub(isolate()).GetCode(); } else if (IsFastElementsKind(elements_kind) || IsFixedTypedArrayElementsKind(elements_kind)) { cached_stub = LoadFastElementStub(isolate(), is_js_array, elements_kind, convert_hole_to_undefined).GetCode(); } else { DCHECK(elements_kind == DICTIONARY_ELEMENTS); LoadICState state = LoadICState(is_strong(language_mode) ? LoadICState::kStrongModeState : kNoExtraICState); cached_stub = LoadDictionaryElementStub(isolate(), state).GetCode(); } } handlers->Add(cached_stub); } } } // namespace internal } // namespace v8