// 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/v8.h" #include "src/ic/call-optimization.h" #include "src/ic/handler-compiler.h" #include "src/ic/ic.h" #include "src/ic/ic-inl.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 type) { Isolate* isolate = name->GetIsolate(); Handle receiver_map = IC::TypeToMap(*type, isolate); 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(*type, 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; 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, type, 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::STUB_TAG, *code, *name)); return code; } void PropertyHandlerCompiler::set_type_for_object(Handle object) { type_ = IC::CurrentTypeOf(object, isolate()); } #define __ ACCESS_MASM(masm()) Register NamedLoadHandlerCompiler::FrontendHeader(Register object_reg, Handle name, Label* miss) { PrototypeCheckType check_type = CHECK_ALL_MAPS; int function_index = -1; if (type()->Is(HeapType::String())) { function_index = Context::STRING_FUNCTION_INDEX; } else if (type()->Is(HeapType::Symbol())) { function_index = Context::SYMBOL_FUNCTION_INDEX; } else if (type()->Is(HeapType::Number())) { function_index = Context::NUMBER_FUNCTION_INDEX; } else if (type()->Is(HeapType::Boolean())) { function_index = Context::BOOLEAN_FUNCTION_INDEX; } else { check_type = SKIP_RECEIVER; } if (check_type == CHECK_ALL_MAPS) { GenerateDirectLoadGlobalFunctionPrototype(masm(), function_index, scratch1(), miss); Object* function = isolate()->native_context()->get(function_index); Object* prototype = JSFunction::cast(function)->instance_prototype(); set_type_for_object(handle(prototype, isolate())); object_reg = scratch1(); } // Check that the maps starting from the prototype haven't changed. return CheckPrototypes(object_reg, scratch1(), scratch2(), scratch3(), name, miss, check_type); } // 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) { return CheckPrototypes(object_reg, this->name(), scratch1(), scratch2(), name, miss, SKIP_RECEIVER); } Register PropertyHandlerCompiler::Frontend(Handle name) { Label miss; if (IC::ICUseVector(kind())) { PushVectorAndSlot(); } Register reg = FrontendHeader(receiver(), name, &miss); 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 = IC::TypeToMap(*type(), isolate()); // If |type| has null as its prototype, |holder()| is // Handle::null(). DCHECK(last_map->prototype() == isolate()->heap()->null_value()); } else { holder_reg = FrontendHeader(receiver(), name, miss); last_map = handle(holder()->map()); } if (last_map->is_dictionary_map()) { if (last_map->IsJSGlobalObjectMap()) { Handle global = holder().is_null() ? Handle::cast(type()->AsConstant()->Value()) : 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) { DCHECK(call_optimization.is_simple_api_call()); Frontend(name); Handle receiver_map = IC::TypeToMap(*type(), isolate()); GenerateFastApiCall(masm(), call_optimization, receiver_map, receiver(), scratch1(), false, 0, NULL); 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 FIELD 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: break; case LookupIterator::DATA: inline_followup = it->property_details().type() == FIELD && !it->is_dictionary_holder(); break; case LookupIterator::ACCESSOR: { Handle accessors = it->GetAccessors(); inline_followup = accessors->IsExecutableAccessorInfo(); if (!inline_followup) break; Handle info = Handle::cast(accessors); inline_followup = info->getter() != NULL && ExecutableAccessorInfo::IsCompatibleReceiverType( isolate(), info, type()); } } Label miss; InterceptorVectorSlotPush(receiver()); Register reg = FrontendHeader(receiver(), it->name(), &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()); set_type_for_object(holder()); set_holder(real_named_property_holder); Label miss; InterceptorVectorSlotPush(interceptor_reg); Register reg = FrontendHeader(interceptor_reg, it->name(), &miss); 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::TRANSITION: UNREACHABLE(); case LookupIterator::DATA: { DCHECK_EQ(FIELD, it->property_details().type()); __ Move(receiver(), reg); LoadFieldStub stub(isolate(), it->GetFieldIndex()); GenerateTailCall(masm(), stub.GetCode()); break; } case LookupIterator::ACCESSOR: Handle info = Handle::cast(it->GetAccessors()); DCHECK_NE(NULL, info->getter()); GenerateLoadCallback(reg, info); } } Handle NamedLoadHandlerCompiler::CompileLoadViaGetter( Handle name, Handle getter) { Frontend(name); GenerateLoadViaGetter(masm(), type(), receiver(), getter); return GetCode(kind(), Code::FAST, name); } // TODO(verwaest): Cleanup. holder() is actually the receiver. Handle NamedStoreHandlerCompiler::CompileStoreTransition( Handle transition, Handle name) { Label miss; // 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 iter(isolate(), holder()); while (!iter.IsAtEnd()) { last = Handle::cast(PrototypeIterator::GetCurrent(iter)); iter.Advance(); } if (!last.is_null()) set_holder(last); NonexistentFrontendHeader(name, &miss, scratch1(), scratch2()); } else { FrontendHeader(receiver(), name, &miss); 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. if (details.type() == CONSTANT) { GenerateRestoreMap(transition, scratch2(), &miss); DCHECK(descriptors->GetValue(descriptor)->IsJSFunction()); Register map_reg = StoreTransitionDescriptor::MapRegister(); GenerateConstantCheck(map_reg, descriptor, value(), scratch2(), &miss); 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; GenerateRestoreMap(transition, scratch2(), &miss); GenerateRestoreName(name); StoreTransitionStub stub(isolate(), FieldIndex::ForDescriptor(*transition, descriptor), representation, store_mode); GenerateTailCall(masm(), stub.GetCode()); } GenerateRestoreName(&miss, name); TailCallBuiltin(masm(), MissBuiltin(kind())); return GetCode(kind(), Code::FAST, name); } Handle NamedStoreHandlerCompiler::CompileStoreField(LookupIterator* it) { Label miss; DCHECK(it->representation().IsHeapObject()); GenerateFieldTypeChecks(*it->GetFieldType(), value(), &miss); StoreFieldStub stub(isolate(), it->GetFieldIndex(), it->representation()); GenerateTailCall(masm(), stub.GetCode()); __ bind(&miss); TailCallBuiltin(masm(), MissBuiltin(kind())); return GetCode(kind(), Code::FAST, it->name()); } Handle NamedStoreHandlerCompiler::CompileStoreViaSetter( Handle object, Handle name, Handle setter) { Frontend(name); GenerateStoreViaSetter(masm(), type(), receiver(), setter); return GetCode(kind(), Code::FAST, name); } Handle NamedStoreHandlerCompiler::CompileStoreCallback( Handle object, Handle name, const CallOptimization& call_optimization) { Frontend(name); Register values[] = {value()}; GenerateFastApiCall(masm(), call_optimization, handle(object->map()), receiver(), scratch1(), true, 1, values); return GetCode(kind(), Code::FAST, name); } #undef __ void ElementHandlerCompiler::CompileElementHandlers( MapHandleList* receiver_maps, CodeHandleList* handlers) { 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 = isolate()->builtins()->KeyedLoadIC_Slow(); } else { bool is_js_array = receiver_map->instance_type() == JS_ARRAY_TYPE; ElementsKind elements_kind = receiver_map->elements_kind(); 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) || IsExternalArrayElementsKind(elements_kind) || IsFixedTypedArrayElementsKind(elements_kind)) { cached_stub = LoadFastElementStub(isolate(), is_js_array, elements_kind) .GetCode(); } else { DCHECK(elements_kind == DICTIONARY_ELEMENTS); cached_stub = LoadDictionaryElementStub(isolate()).GetCode(); } } handlers->Add(cached_stub); } } } } // namespace v8::internal