diff --git a/src/arm/stub-cache-arm.cc b/src/arm/stub-cache-arm.cc index 09ecc798c5..f852786f1d 100644 --- a/src/arm/stub-cache-arm.cc +++ b/src/arm/stub-cache-arm.cc @@ -3274,6 +3274,61 @@ MaybeObject* KeyedStoreStubCompiler::CompileStoreElement(Map* receiver_map) { } +MaybeObject* KeyedStoreStubCompiler::CompileStoreElementWithTransition( + Map* transitioned_map, + Map* untransitioned_map_1, + Map* untransitioned_map_2) { + // ----------- S t a t e ------------- + // -- r0 : value + // -- r1 : key + // -- r2 : receiver + // -- lr : return address + // -- r3 : scratch + // ----------------------------------- + + // The order of map occurrences in the generated code below is important. + // Both IC code and Crankshaft rely on |transitioned_map| being the first + // map in the stub. + + Code* notransition_stub; + ElementsKind elements_kind = transitioned_map->elements_kind(); + bool is_js_array = transitioned_map->instance_type() == JS_ARRAY_TYPE; + MaybeObject* maybe_stub = + KeyedStoreElementStub(is_js_array, elements_kind).TryGetCode(); + if (!maybe_stub->To(¬ransition_stub)) return maybe_stub; + + Label just_store, miss; + __ JumpIfSmi(r2, &miss); + __ ldr(r3, FieldMemOperand(r2, HeapObject::kMapOffset)); + // r3: receiver->map(). + __ mov(ip, Operand(Handle<Map>(transitioned_map))); + __ cmp(r3, ip); + __ b(eq, &just_store); + ASSERT_NE(untransitioned_map_1, NULL); + __ mov(ip, Operand(Handle<Map>(untransitioned_map_1))); + __ cmp(r3, ip); + Code* generic_stub = (strict_mode_ == kStrictMode) + ? isolate()->builtins()->builtin(Builtins::kKeyedStoreIC_Generic_Strict) + : isolate()->builtins()->builtin(Builtins::kKeyedStoreIC_Generic); + __ Jump(Handle<Code>(generic_stub), RelocInfo::CODE_TARGET, eq); + if (untransitioned_map_2 != NULL) { + __ mov(ip, Operand(Handle<Map>(untransitioned_map_2))); + __ cmp(r3, ip); + __ Jump(Handle<Code>(generic_stub), RelocInfo::CODE_TARGET, eq); + } + + __ bind(&miss); + Handle<Code> ic = isolate()->builtins()->KeyedStoreIC_Miss(); + __ Jump(ic, RelocInfo::CODE_TARGET); + + __ bind(&just_store); + __ Jump(Handle<Code>(notransition_stub), RelocInfo::CODE_TARGET); + + // Return the generated code. + return GetCode(NORMAL, NULL, MEGAMORPHIC); +} + + MaybeObject* KeyedStoreStubCompiler::CompileStoreMegamorphic( MapList* receiver_maps, CodeList* handler_ics) { @@ -4313,7 +4368,7 @@ void KeyedStoreStubCompiler::GenerateStoreFastElement( // -- r3 : scratch // -- r4 : scratch (elements) // ----------------------------------- - Label miss_force_generic; + Label miss_force_generic, transition_elements_kind; Register value_reg = r0; Register key_reg = r1; @@ -4347,7 +4402,7 @@ void KeyedStoreStubCompiler::GenerateStoreFastElement( __ b(hs, &miss_force_generic); if (elements_kind == FAST_SMI_ONLY_ELEMENTS) { - __ JumpIfNotSmi(value_reg, &miss_force_generic); + __ JumpIfNotSmi(value_reg, &transition_elements_kind); __ add(scratch, elements_reg, Operand(FixedArray::kHeaderSize - kHeapObjectTag)); @@ -4381,6 +4436,10 @@ void KeyedStoreStubCompiler::GenerateStoreFastElement( Handle<Code> ic = masm->isolate()->builtins()->KeyedStoreIC_MissForceGeneric(); __ Jump(ic, RelocInfo::CODE_TARGET); + + __ bind(&transition_elements_kind); + Handle<Code> ic_miss = masm->isolate()->builtins()->KeyedStoreIC_Miss(); + __ Jump(ic_miss, RelocInfo::CODE_TARGET); } @@ -4396,7 +4455,7 @@ void KeyedStoreStubCompiler::GenerateStoreFastDoubleElement( // -- r4 : scratch // -- r5 : scratch // ----------------------------------- - Label miss_force_generic; + Label miss_force_generic, transition_elements_kind; Register value_reg = r0; Register key_reg = r1; @@ -4434,7 +4493,7 @@ void KeyedStoreStubCompiler::GenerateStoreFastDoubleElement( scratch2, scratch3, scratch4, - &miss_force_generic); + &transition_elements_kind); __ Ret(); // Handle store cache miss, replacing the ic with the generic stub. @@ -4442,6 +4501,10 @@ void KeyedStoreStubCompiler::GenerateStoreFastDoubleElement( Handle<Code> ic = masm->isolate()->builtins()->KeyedStoreIC_MissForceGeneric(); __ Jump(ic, RelocInfo::CODE_TARGET); + + __ bind(&transition_elements_kind); + Handle<Code> ic_miss = masm->isolate()->builtins()->KeyedStoreIC_Miss(); + __ Jump(ic_miss, RelocInfo::CODE_TARGET); } diff --git a/src/ia32/stub-cache-ia32.cc b/src/ia32/stub-cache-ia32.cc index 53f9d5bd03..44649cbffe 100644 --- a/src/ia32/stub-cache-ia32.cc +++ b/src/ia32/stub-cache-ia32.cc @@ -2755,6 +2755,62 @@ MaybeObject* KeyedStoreStubCompiler::CompileStoreElement(Map* receiver_map) { } +MaybeObject* KeyedStoreStubCompiler::CompileStoreElementWithTransition( + Map* transitioned_map, + Map* untransitioned_map_1, + Map* untransitioned_map_2) { + // ----------- S t a t e ------------- + // -- eax : value + // -- ecx : key + // -- edx : receiver + // -- esp[0] : return address + // ----------------------------------- + + // The order of map occurrences in the generated code below is important. + // Both IC code and Crankshaft rely on |transitioned_map| being the first + // map in the stub. + + Code* notransition_stub; + ElementsKind elements_kind = transitioned_map->elements_kind(); + bool is_jsarray = transitioned_map->instance_type() == JS_ARRAY_TYPE; + MaybeObject* maybe_stub = + KeyedStoreElementStub(is_jsarray, elements_kind).TryGetCode(); + if (!maybe_stub->To(¬ransition_stub)) return maybe_stub; + + Label just_store, miss; + __ JumpIfSmi(edx, &miss, Label::kNear); + __ mov(ebx, FieldOperand(edx, HeapObject::kMapOffset)); + // ebx: receiver->map(). + __ cmp(ebx, Handle<Map>(transitioned_map)); + __ j(equal, &just_store); + ASSERT_NE(untransitioned_map_1, NULL); + __ cmp(ebx, Handle<Map>(untransitioned_map_1)); + // TODO(jkummerow): When we have specialized code to do the transition, + // call that code here, then jump to just_store when the call returns. + // <temporary: just use the generic stub> + Code* generic_stub = (strict_mode_ == kStrictMode) + ? isolate()->builtins()->builtin(Builtins::kKeyedStoreIC_Generic_Strict) + : isolate()->builtins()->builtin(Builtins::kKeyedStoreIC_Generic); + __ j(equal, Handle<Code>(generic_stub)); + // </temporary> + if (untransitioned_map_2 != NULL) { + __ cmp(ebx, Handle<Map>(untransitioned_map_2)); + // <temporary: see above, same here> + __ j(equal, Handle<Code>(generic_stub)); + // </temporary> + } + __ bind(&miss); + Handle<Code> ic = isolate()->builtins()->KeyedStoreIC_Miss(); + __ jmp(ic, RelocInfo::CODE_TARGET); + + __ bind(&just_store); + __ jmp(Handle<Code>(notransition_stub), RelocInfo::CODE_TARGET); + + // Return the generated code. + return GetCode(NORMAL, NULL, MEGAMORPHIC); +} + + MaybeObject* KeyedStoreStubCompiler::CompileStoreMegamorphic( MapList* receiver_maps, CodeList* handler_ics) { @@ -3906,7 +3962,7 @@ void KeyedStoreStubCompiler::GenerateStoreFastElement( // -- edx : receiver // -- esp[0] : return address // ----------------------------------- - Label miss_force_generic; + Label miss_force_generic, transition_elements_kind; // This stub is meant to be tail-jumped to, the receiver must already // have been verified by the caller to not be a smi. @@ -3931,7 +3987,7 @@ void KeyedStoreStubCompiler::GenerateStoreFastElement( } if (elements_kind == FAST_SMI_ONLY_ELEMENTS) { - __ JumpIfNotSmi(eax, &miss_force_generic); + __ JumpIfNotSmi(eax, &transition_elements_kind); // ecx is a smi, use times_half_pointer_size instead of // times_pointer_size __ mov(FieldOperand(edi, @@ -3961,6 +4017,11 @@ void KeyedStoreStubCompiler::GenerateStoreFastElement( Handle<Code> ic_force_generic = masm->isolate()->builtins()->KeyedStoreIC_MissForceGeneric(); __ jmp(ic_force_generic, RelocInfo::CODE_TARGET); + + // Handle transition to other elements kinds without using the generic stub. + __ bind(&transition_elements_kind); + Handle<Code> ic_miss = masm->isolate()->builtins()->KeyedStoreIC_Miss(); + __ jmp(ic_miss, RelocInfo::CODE_TARGET); } @@ -3973,7 +4034,7 @@ void KeyedStoreStubCompiler::GenerateStoreFastDoubleElement( // -- edx : receiver // -- esp[0] : return address // ----------------------------------- - Label miss_force_generic; + Label miss_force_generic, transition_elements_kind; // This stub is meant to be tail-jumped to, the receiver must already // have been verified by the caller to not be a smi. @@ -3999,7 +4060,7 @@ void KeyedStoreStubCompiler::GenerateStoreFastDoubleElement( ecx, edx, xmm0, - &miss_force_generic, + &transition_elements_kind, true); __ ret(0); @@ -4008,6 +4069,11 @@ void KeyedStoreStubCompiler::GenerateStoreFastDoubleElement( Handle<Code> ic_force_generic = masm->isolate()->builtins()->KeyedStoreIC_MissForceGeneric(); __ jmp(ic_force_generic, RelocInfo::CODE_TARGET); + + // Handle transition to other elements kinds without using the generic stub. + __ bind(&transition_elements_kind); + Handle<Code> ic_miss = masm->isolate()->builtins()->KeyedStoreIC_Miss(); + __ jmp(ic_miss, RelocInfo::CODE_TARGET); } diff --git a/src/ic.cc b/src/ic.cc index 834dbfd74b..f54f0d79ed 100644 --- a/src/ic.cc +++ b/src/ic.cc @@ -1243,7 +1243,7 @@ MaybeObject* KeyedLoadIC::Load(State state, stub = indexed_interceptor_stub(); } else if (key->IsSmi() && (target() != non_strict_arguments_stub())) { MaybeObject* maybe_stub = ComputeStub(receiver, - false, + LOAD, kNonStrictMode, stub); stub = maybe_stub->IsFailure() ? @@ -1593,14 +1593,15 @@ void KeyedIC::GetReceiverMapsForStub(Code* stub, MapList* result) { MaybeObject* KeyedIC::ComputeStub(JSObject* receiver, - bool is_store, + StubKind stub_kind, StrictModeFlag strict_mode, Code* generic_stub) { State ic_state = target()->ic_state(); - if (ic_state == UNINITIALIZED || ic_state == PREMONOMORPHIC) { + if ((ic_state == UNINITIALIZED || ic_state == PREMONOMORPHIC) && + !IsTransitionStubKind(stub_kind)) { Code* monomorphic_stub; MaybeObject* maybe_stub = ComputeMonomorphicStub(receiver, - is_store, + stub_kind, strict_mode, generic_stub); if (!maybe_stub->To(&monomorphic_stub)) return maybe_stub; @@ -1619,8 +1620,17 @@ MaybeObject* KeyedIC::ComputeStub(JSObject* receiver, // Determine the list of receiver maps that this call site has seen, // adding the map that was just encountered. MapList target_receiver_maps; - GetReceiverMapsForStub(target(), &target_receiver_maps); - if (!AddOneReceiverMapIfMissing(&target_receiver_maps, receiver->map())) { + if (ic_state == UNINITIALIZED || ic_state == PREMONOMORPHIC) { + target_receiver_maps.Add(receiver->map()); + } else { + GetReceiverMapsForStub(target(), &target_receiver_maps); + } + Map* new_map = receiver->map(); + if (IsTransitionStubKind(stub_kind)) { + MaybeObject* maybe_map = ComputeTransitionedMap(receiver, stub_kind); + if (!maybe_map->To(&new_map)) return maybe_map; + } + if (!AddOneReceiverMapIfMissing(&target_receiver_maps, new_map)) { // If the miss wasn't due to an unseen map, a MEGAMORPHIC stub // won't help, use the generic stub. return generic_stub; @@ -1642,21 +1652,14 @@ MaybeObject* KeyedIC::ComputeStub(JSObject* receiver, ASSERT(maybe_cached_stub->IsCode()); return Code::cast(maybe_cached_stub); } - // Collect MONOMORPHIC stubs for all target_receiver_maps. - CodeList handler_ics(target_receiver_maps.length()); - for (int i = 0; i < target_receiver_maps.length(); ++i) { - Map* receiver_map(target_receiver_maps.at(i)); - MaybeObject* maybe_cached_stub = ComputeMonomorphicStubWithoutMapCheck( - receiver_map, strict_mode); - Code* cached_stub; - if (!maybe_cached_stub->To(&cached_stub)) return maybe_cached_stub; - handler_ics.Add(cached_stub); + MaybeObject* maybe_stub = NULL; + if (IsTransitionStubKind(stub_kind)) { + maybe_stub = ComputePolymorphicStubWithTransition( + receiver, &target_receiver_maps, new_map, strict_mode); + } else { + maybe_stub = ComputePolymorphicStub(&target_receiver_maps, strict_mode); } - // Build the MEGAMORPHIC stub. Code* stub; - MaybeObject* maybe_stub = ConstructMegamorphicStub(&target_receiver_maps, - &handler_ics, - strict_mode); if (!maybe_stub->To(&stub)) return maybe_stub; MaybeObject* maybe_update = cache->Update(&target_receiver_maps, flags, stub); if (maybe_update->IsFailure()) return maybe_update; @@ -1684,7 +1687,7 @@ MaybeObject* KeyedIC::ComputeMonomorphicStubWithoutMapCheck( MaybeObject* KeyedIC::ComputeMonomorphicStub(JSObject* receiver, - bool is_store, + StubKind stub_kind, StrictModeFlag strict_mode, Code* generic_stub) { Code* result = NULL; @@ -1695,7 +1698,7 @@ MaybeObject* KeyedIC::ComputeMonomorphicStub(JSObject* receiver, receiver->HasDictionaryElements()) { MaybeObject* maybe_stub = isolate()->stub_cache()->ComputeKeyedLoadOrStoreElement( - receiver, is_store, strict_mode); + receiver, stub_kind, strict_mode); if (!maybe_stub->To(&result)) return maybe_stub; } else { result = generic_stub; @@ -1704,6 +1707,60 @@ MaybeObject* KeyedIC::ComputeMonomorphicStub(JSObject* receiver, } +MaybeObject* KeyedIC::ComputePolymorphicStubWithTransition( + JSObject* receiver, + MapList* receiver_maps, + Map* new_map, + StrictModeFlag strict_mode) { + Map* existing_transitionable_map = NULL; + for (int i = 0; i < receiver_maps->length(); ++i) { + Map* map = receiver_maps->at(i); + if (map != receiver->map() && map != new_map) { + existing_transitionable_map = map; + break; + } + } + KeyedStoreStubCompiler compiler(strict_mode); + return compiler.CompileStoreElementWithTransition( + new_map, + receiver->map(), + existing_transitionable_map); +} + + +MaybeObject* KeyedIC::ComputePolymorphicStub( + MapList* receiver_maps, + StrictModeFlag strict_mode) { + // Collect MONOMORPHIC stubs for all target_receiver_maps. + CodeList handler_ics(receiver_maps->length()); + for (int i = 0; i < receiver_maps->length(); ++i) { + Map* receiver_map(receiver_maps->at(i)); + MaybeObject* maybe_cached_stub = ComputeMonomorphicStubWithoutMapCheck( + receiver_map, strict_mode); + Code* cached_stub; + if (!maybe_cached_stub->To(&cached_stub)) return maybe_cached_stub; + handler_ics.Add(cached_stub); + } + // Build the MEGAMORPHIC stub. + return ConstructMegamorphicStub(receiver_maps, &handler_ics, strict_mode); +} + + +MaybeObject* KeyedIC::ComputeTransitionedMap(JSObject* receiver, + StubKind stub_kind) { + switch (stub_kind) { + case KeyedIC::STORE_TRANSITION_SMI_TO_OBJECT: + case KeyedIC::STORE_TRANSITION_DOUBLE_TO_OBJECT: + return receiver->GetElementsTransitionMap(FAST_ELEMENTS); + case KeyedIC::STORE_TRANSITION_SMI_TO_DOUBLE: + return receiver->GetElementsTransitionMap(FAST_DOUBLE_ELEMENTS); + default: + UNREACHABLE(); + return NULL; + } +} + + MaybeObject* KeyedStoreIC::GetElementStubWithoutMapCheck( bool is_js_array, ElementsKind elements_kind) { @@ -1786,9 +1843,21 @@ MaybeObject* KeyedStoreIC::Store(State state, stub = non_strict_arguments_stub(); } else if (!force_generic) { if (key->IsSmi() && (target() != non_strict_arguments_stub())) { + StubKind stub_kind = STORE_NO_TRANSITION; + if (receiver->GetElementsKind() == FAST_SMI_ONLY_ELEMENTS) { + if (value->IsHeapNumber()) { + stub_kind = STORE_TRANSITION_SMI_TO_DOUBLE; + } else if (value->IsHeapObject()) { + stub_kind = STORE_TRANSITION_SMI_TO_OBJECT; + } + } else if (receiver->GetElementsKind() == FAST_DOUBLE_ELEMENTS) { + if (!value->IsSmi() && !value->IsHeapNumber()) { + stub_kind = STORE_TRANSITION_DOUBLE_TO_OBJECT; + } + } HandleScope scope(isolate()); MaybeObject* maybe_stub = ComputeStub(receiver, - true, + stub_kind, strict_mode, stub); stub = maybe_stub->IsFailure() ? diff --git a/src/ic.h b/src/ic.h index ece5be9f05..04436b54c6 100644 --- a/src/ic.h +++ b/src/ic.h @@ -342,6 +342,13 @@ class LoadIC: public IC { class KeyedIC: public IC { public: + enum StubKind { + LOAD, + STORE_NO_TRANSITION, + STORE_TRANSITION_SMI_TO_OBJECT, + STORE_TRANSITION_SMI_TO_DOUBLE, + STORE_TRANSITION_DOUBLE_TO_OBJECT + }; explicit KeyedIC(Isolate* isolate) : IC(NO_EXTRA_FRAME, isolate) {} virtual ~KeyedIC() {} @@ -357,7 +364,7 @@ class KeyedIC: public IC { virtual Code::Kind kind() const = 0; MaybeObject* ComputeStub(JSObject* receiver, - bool is_store, + StubKind stub_kind, StrictModeFlag strict_mode, Code* default_stub); @@ -374,9 +381,23 @@ class KeyedIC: public IC { StrictModeFlag strict_mode); MaybeObject* ComputeMonomorphicStub(JSObject* receiver, - bool is_store, + StubKind stub_kind, StrictModeFlag strict_mode, Code* default_stub); + + MaybeObject* ComputePolymorphicStubWithTransition(JSObject* receiver, + MapList* receiver_maps, + Map* new_map, + StrictModeFlag strict_mode); + + MaybeObject* ComputePolymorphicStub(MapList* receiver_maps, + StrictModeFlag strict_mode); + + MaybeObject* ComputeTransitionedMap(JSObject* receiver, StubKind stub_kind); + + static bool IsTransitionStubKind(StubKind stub_kind) { + return stub_kind > STORE_NO_TRANSITION; + } }; diff --git a/src/objects.cc b/src/objects.cc index 604460a6d7..baba61a9fb 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -1097,7 +1097,7 @@ void HeapObject::HeapObjectShortPrint(StringStream* accumulator) { } switch (map()->instance_type()) { case MAP_TYPE: - accumulator->Add("<Map>"); + accumulator->Add("<Map(elements=%u)>", Map::cast(this)->elements_kind()); break; case FIXED_ARRAY_TYPE: accumulator->Add("<FixedArray[%u]>", FixedArray::cast(this)->length()); @@ -2163,13 +2163,135 @@ static MaybeObject* AddElementsTransitionMapToDescriptor( } -MaybeObject* JSObject::GetElementsTransitionMap(ElementsKind elements_kind) { +// Returns the contents of |map|'s descriptor array for the given string +// (which might be NULL). |safe_to_add_transition| is set to false and NULL +// is returned if adding transitions is not allowed. +static Object* GetDescriptorContents(Map* map, + String* sentinel_name, + bool* safe_to_add_transition) { + // Get the cached index for the descriptors lookup, or find and cache it. + DescriptorArray* descriptors = map->instance_descriptors(); + DescriptorLookupCache* cache = map->GetIsolate()->descriptor_lookup_cache(); + int index = cache->Lookup(descriptors, sentinel_name); + if (index == DescriptorLookupCache::kAbsent) { + index = descriptors->Search(sentinel_name); + cache->Update(descriptors, sentinel_name, index); + } + // If the transition already exists, return its descriptor. + if (index != DescriptorArray::kNotFound) { + PropertyDetails details(descriptors->GetDetails(index)); + if (details.type() == ELEMENTS_TRANSITION) { + return descriptors->GetValue(index); + } else { + *safe_to_add_transition = false; + } + } + return NULL; +} + + +// Returns the map that |original_map| transitions to if its elements_kind +// is changed to |elements_kind|, or NULL if no such map is cached yet. +// |safe_to_add_transitions| is set to false if adding transitions is not +// allowed. +static Map* LookupElementsTransitionMap(Map* original_map, + ElementsKind elements_kind, + String* sentinel_name, + bool* safe_to_add_transition) { + // Special case: indirect SMI->FAST transition (cf. comment in + // AddElementsTransition()). + if (original_map->elements_kind() == FAST_SMI_ONLY_ELEMENTS && + elements_kind == FAST_ELEMENTS) { + Map* double_map = LookupElementsTransitionMap( + original_map, + FAST_DOUBLE_ELEMENTS, + sentinel_name, + safe_to_add_transition); + if (double_map == NULL) return double_map; + return LookupElementsTransitionMap(double_map, + FAST_ELEMENTS, + sentinel_name, + safe_to_add_transition); + } + Object* descriptor_contents = GetDescriptorContents( + original_map, sentinel_name, safe_to_add_transition); + if (descriptor_contents != NULL) { + Map* maybe_transition_map = + GetElementsTransitionMapFromDescriptor(descriptor_contents, + elements_kind); + ASSERT(maybe_transition_map == NULL || maybe_transition_map->IsMap()); + return maybe_transition_map; + } + return NULL; +} + + +// Adds an entry to |original_map|'s descriptor array for a transition to +// |transitioned_map| when its elements_kind is changed to |elements_kind|, +// using |sentinel_name| as key for the entry. +static MaybeObject* AddElementsTransition(Map* original_map, + ElementsKind elements_kind, + Map* transitioned_map, + String* sentinel_name) { + // The map transition graph should be a tree, therefore the transition + // from SMI to FAST elements is not done directly, but by going through + // DOUBLE elements first. + if (original_map->elements_kind() == FAST_SMI_ONLY_ELEMENTS && + elements_kind == FAST_ELEMENTS) { + bool safe_to_add = true; + Map* double_map = LookupElementsTransitionMap( + original_map, FAST_DOUBLE_ELEMENTS, sentinel_name, &safe_to_add); + // This method is only called when safe_to_add_transition has been found + // to be true earlier. + ASSERT(safe_to_add); + + if (double_map == NULL) { + MaybeObject* maybe_map = original_map->CopyDropTransitions(); + if (!maybe_map->To(&double_map)) return maybe_map; + double_map->set_elements_kind(FAST_DOUBLE_ELEMENTS); + MaybeObject* maybe_double_transition = AddElementsTransition( + original_map, FAST_DOUBLE_ELEMENTS, double_map, sentinel_name); + if (maybe_double_transition->IsFailure()) return maybe_double_transition; + } + return AddElementsTransition( + double_map, FAST_ELEMENTS, transitioned_map, sentinel_name); + } + + DescriptorArray* descriptors = original_map->instance_descriptors(); + bool safe_to_add_transition = true; + Object* descriptor_contents = GetDescriptorContents( + original_map, sentinel_name, &safe_to_add_transition); + // This method is only called when safe_to_add_transition has been found + // to be true earlier. + ASSERT(safe_to_add_transition); + MaybeObject* maybe_new_contents = + AddElementsTransitionMapToDescriptor(descriptor_contents, + transitioned_map); + Object* new_contents; + if (!maybe_new_contents->ToObject(&new_contents)) { + return maybe_new_contents; + } + + ElementsTransitionDescriptor desc(sentinel_name, new_contents); + Object* new_descriptors; + MaybeObject* maybe_new_descriptors = + descriptors->CopyInsert(&desc, KEEP_TRANSITIONS); + if (!maybe_new_descriptors->ToObject(&new_descriptors)) { + return maybe_new_descriptors; + } + descriptors = DescriptorArray::cast(new_descriptors); + original_map->set_instance_descriptors(descriptors); + return original_map; +} + + +MaybeObject* JSObject::GetElementsTransitionMap(ElementsKind to_kind) { Heap* current_heap = GetHeap(); Map* current_map = map(); - DescriptorArray* descriptors = current_map->instance_descriptors(); + ElementsKind from_kind = current_map->elements_kind(); String* elements_transition_sentinel_name = current_heap->empty_symbol(); - if (current_map->elements_kind() == elements_kind) return current_map; + if (from_kind == to_kind) return current_map; // Only objects with FastProperties can have DescriptorArrays and can track // element-related maps. Also don't add descriptors to maps that are shared. @@ -2177,58 +2299,33 @@ MaybeObject* JSObject::GetElementsTransitionMap(ElementsKind elements_kind) { !current_map->IsUndefined() && !current_map->is_shared(); - // Prevent long chains of DICTIONARY -> FAST_ELEMENTS maps cause by objects + // Prevent long chains of DICTIONARY -> FAST_ELEMENTS maps caused by objects // with elements that switch back and forth between dictionary and fast // element mode. - if ((current_map->elements_kind() == DICTIONARY_ELEMENTS && - elements_kind == FAST_ELEMENTS)) { + if (from_kind == DICTIONARY_ELEMENTS && to_kind == FAST_ELEMENTS) { safe_to_add_transition = false; } - Object* descriptor_contents = NULL; if (safe_to_add_transition) { // It's only safe to manipulate the descriptor array if it would be // safe to add a transition. - - // Check if the elements transition already exists. - DescriptorLookupCache* cache = - current_heap->isolate()->descriptor_lookup_cache(); - int index = cache->Lookup(descriptors, elements_transition_sentinel_name); - if (index == DescriptorLookupCache::kAbsent) { - index = descriptors->Search(elements_transition_sentinel_name); - cache->Update(descriptors, - elements_transition_sentinel_name, - index); - } - - // If the transition already exists, check the type. If there is a match, - // return it. - if (index != DescriptorArray::kNotFound) { - PropertyDetails details(PropertyDetails(descriptors->GetDetails(index))); - if (details.type() == ELEMENTS_TRANSITION) { - descriptor_contents = descriptors->GetValue(index); - Map* maybe_transition_map = - GetElementsTransitionMapFromDescriptor(descriptor_contents, - elements_kind); - if (maybe_transition_map != NULL) { - ASSERT(maybe_transition_map->IsMap()); - return maybe_transition_map; - } - } else { - safe_to_add_transition = false; - } + Map* maybe_transition_map = LookupElementsTransitionMap( + current_map, to_kind, elements_transition_sentinel_name, + &safe_to_add_transition); + if (maybe_transition_map != NULL) { + return maybe_transition_map; } } + Map* new_map = NULL; + // No transition to an existing map for the given ElementsKind. Make a new // one. - Object* obj; { MaybeObject* maybe_map = current_map->CopyDropTransitions(); - if (!maybe_map->ToObject(&obj)) return maybe_map; + if (!maybe_map->To(&new_map)) return maybe_map; } - Map* new_map = Map::cast(obj); - new_map->set_elements_kind(elements_kind); + new_map->set_elements_kind(to_kind); // Only remember the map transition if the object's map is NOT equal to the // global object_function's map and there is not an already existing @@ -2237,26 +2334,10 @@ MaybeObject* JSObject::GetElementsTransitionMap(ElementsKind elements_kind) { (GetIsolate()->context()->global_context()->object_function()->map() != map()); if (allow_map_transition) { - MaybeObject* maybe_new_contents = - AddElementsTransitionMapToDescriptor(descriptor_contents, new_map); - Object* new_contents; - if (!maybe_new_contents->ToObject(&new_contents)) { - return maybe_new_contents; - } - - ElementsTransitionDescriptor desc(elements_transition_sentinel_name, - new_contents); - Object* new_descriptors; - MaybeObject* maybe_new_descriptors = descriptors->CopyInsert( - &desc, - KEEP_TRANSITIONS); - if (!maybe_new_descriptors->ToObject(&new_descriptors)) { - return maybe_new_descriptors; - } - descriptors = DescriptorArray::cast(new_descriptors); - current_map->set_instance_descriptors(descriptors); + MaybeObject* maybe_transition = AddElementsTransition( + current_map, to_kind, new_map, elements_transition_sentinel_name); + if (maybe_transition->IsFailure()) return maybe_transition; } - return new_map; } @@ -6709,7 +6790,7 @@ void Map::ClearNonLiveTransitions(Heap* heap, Object* real_prototype) { } else { ASSERT(object->IsFixedArray()); ASSERT(details.type() == ELEMENTS_TRANSITION); - FixedArray* array = reinterpret_cast<FixedArray*>(contents->get(i)); + FixedArray* array = reinterpret_cast<FixedArray*>(object); bool reachable_map_found = false; for (int j = 0; j < array->length(); ++j) { Map* target = reinterpret_cast<Map*>(array->get(j)); @@ -6723,7 +6804,7 @@ void Map::ClearNonLiveTransitions(Heap* heap, Object* real_prototype) { // Getter prototype() is read-only, set_prototype() has side // effects. *RawField(target, Map::kPrototypeOffset) = real_prototype; - } else { + } else if (target->IsMap()) { reachable_map_found = true; } } diff --git a/src/runtime.cc b/src/runtime.cc index 3e04942933..cafe46cfb8 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -13172,6 +13172,14 @@ ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION(ExternalDoubleElements) #undef ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION + +RUNTIME_FUNCTION(MaybeObject*, Runtime_HaveSameMap) { + ASSERT(args.length() == 2); + CONVERT_CHECKED(JSObject, obj1, args[0]); + CONVERT_CHECKED(JSObject, obj2, args[1]); + return isolate->heap()->ToBoolean(obj1->map() == obj2->map()); +} + // ---------------------------------------------------------------------------- // Implementation of Runtime diff --git a/src/runtime.h b/src/runtime.h index c6f77731f1..ed9c2b8891 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -370,6 +370,7 @@ namespace internal { F(HasExternalUnsignedIntElements, 1, 1) \ F(HasExternalFloatElements, 1, 1) \ F(HasExternalDoubleElements, 1, 1) \ + F(HaveSameMap, 2, 1) \ /* profiler */ \ F(ProfilerResume, 0, 1) \ F(ProfilerPause, 0, 1) diff --git a/src/stub-cache.cc b/src/stub-cache.cc index 09ee793947..67451f2b88 100644 --- a/src/stub-cache.cc +++ b/src/stub-cache.cc @@ -497,38 +497,56 @@ MaybeObject* StubCache::ComputeStoreField(String* name, MaybeObject* StubCache::ComputeKeyedLoadOrStoreElement( JSObject* receiver, - bool is_store, + KeyedIC::StubKind stub_kind, StrictModeFlag strict_mode) { Code::Flags flags = Code::ComputeMonomorphicFlags( - is_store ? Code::KEYED_STORE_IC : - Code::KEYED_LOAD_IC, + stub_kind == KeyedIC::LOAD ? Code::KEYED_LOAD_IC + : Code::KEYED_STORE_IC, NORMAL, strict_mode); - String* name = is_store - ? isolate()->heap()->KeyedStoreElementMonomorphic_symbol() - : isolate()->heap()->KeyedLoadElementMonomorphic_symbol(); + String* name = NULL; + switch (stub_kind) { + case KeyedIC::LOAD: + name = isolate()->heap()->KeyedLoadElementMonomorphic_symbol(); + break; + case KeyedIC::STORE_NO_TRANSITION: + name = isolate()->heap()->KeyedStoreElementMonomorphic_symbol(); + break; + default: + UNREACHABLE(); + break; + } Object* maybe_code = receiver->map()->FindInCodeCache(name, flags); if (!maybe_code->IsUndefined()) return Code::cast(maybe_code); - MaybeObject* maybe_new_code = NULL; Map* receiver_map = receiver->map(); - if (is_store) { - KeyedStoreStubCompiler compiler(strict_mode); - maybe_new_code = compiler.CompileStoreElement(receiver_map); - } else { - KeyedLoadStubCompiler compiler; - maybe_new_code = compiler.CompileLoadElement(receiver_map); + MaybeObject* maybe_new_code = NULL; + switch (stub_kind) { + case KeyedIC::LOAD: { + KeyedLoadStubCompiler compiler; + maybe_new_code = compiler.CompileLoadElement(receiver_map); + break; + } + case KeyedIC::STORE_NO_TRANSITION: { + KeyedStoreStubCompiler compiler(strict_mode); + maybe_new_code = compiler.CompileStoreElement(receiver_map); + break; + } + default: + UNREACHABLE(); + break; } - Code* code; + Code* code = NULL; if (!maybe_new_code->To(&code)) return maybe_new_code; - if (is_store) { + + if (stub_kind == KeyedIC::LOAD) { PROFILE(isolate_, - CodeCreateEvent(Logger::KEYED_STORE_IC_TAG, + CodeCreateEvent(Logger::KEYED_LOAD_IC_TAG, Code::cast(code), 0)); } else { PROFILE(isolate_, - CodeCreateEvent(Logger::KEYED_LOAD_IC_TAG, + CodeCreateEvent(Logger::KEYED_STORE_IC_TAG, Code::cast(code), 0)); } ASSERT(code->IsCode()); diff --git a/src/stub-cache.h b/src/stub-cache.h index fcb58e1f08..10d017db73 100644 --- a/src/stub-cache.h +++ b/src/stub-cache.h @@ -30,6 +30,7 @@ #include "allocation.h" #include "arguments.h" +#include "ic-inl.h" #include "macro-assembler.h" #include "objects.h" #include "zone-inl.h" @@ -187,7 +188,7 @@ class StubCache { MUST_USE_RESULT MaybeObject* ComputeKeyedLoadOrStoreElement( JSObject* receiver, - bool is_store, + KeyedIC::StubKind stub_kind, StrictModeFlag strict_mode); // --- @@ -699,6 +700,11 @@ class KeyedStoreStubCompiler: public StubCompiler { MUST_USE_RESULT MaybeObject* CompileStoreElement(Map* receiver_map); + MUST_USE_RESULT MaybeObject* CompileStoreElementWithTransition( + Map* transitioned_map, + Map* untransitioned_map_1, + Map* untransitioned_map_2 = NULL); + MUST_USE_RESULT MaybeObject* CompileStoreMegamorphic( MapList* receiver_maps, CodeList* handler_ics); diff --git a/src/x64/stub-cache-x64.cc b/src/x64/stub-cache-x64.cc index b08ae1d173..c0a13ae6e9 100644 --- a/src/x64/stub-cache-x64.cc +++ b/src/x64/stub-cache-x64.cc @@ -2603,6 +2603,56 @@ MaybeObject* KeyedStoreStubCompiler::CompileStoreElement(Map* receiver_map) { } +MaybeObject* KeyedStoreStubCompiler::CompileStoreElementWithTransition( + Map* transitioned_map, + Map* untransitioned_map_1, + Map* untransitioned_map_2) { + // ----------- S t a t e ------------- + // -- rax : value + // -- rcx : key + // -- rdx : receiver + // -- rsp[0] : return address + // ----------------------------------- + + // The order of map occurrences in the generated code below is important. + // Both IC code and Crankshaft rely on |transitioned_map| being the first + // map in the stub. + + Code* notransition_stub; + ElementsKind elements_kind = transitioned_map->elements_kind(); + bool is_js_array = transitioned_map->instance_type() == JS_ARRAY_TYPE; + MaybeObject* maybe_stub = + KeyedStoreElementStub(is_js_array, elements_kind).TryGetCode(); + if (!maybe_stub->To(¬ransition_stub)) return maybe_stub; + + Label just_store, miss; + __ JumpIfSmi(rdx, &miss, Label::kNear); + __ movq(rbx, FieldOperand(rdx, HeapObject::kMapOffset)); + // rbx: receiver->map(). + __ Cmp(rbx, Handle<Map>(transitioned_map)); + __ j(equal, &just_store); + ASSERT_NE(untransitioned_map_1, NULL); + __ Cmp(rbx, Handle<Map>(untransitioned_map_1)); + Code* generic_stub = (strict_mode_ == kStrictMode) + ? isolate()->builtins()->builtin(Builtins::kKeyedStoreIC_Generic_Strict) + : isolate()->builtins()->builtin(Builtins::kKeyedStoreIC_Generic); + __ j(equal, Handle<Code>(generic_stub), RelocInfo::CODE_TARGET); + if (untransitioned_map_2 != NULL) { + __ Cmp(rbx, Handle<Map>(untransitioned_map_2)); + __ j(equal, Handle<Code>(generic_stub), RelocInfo::CODE_TARGET); + } + __ bind(&miss); + Handle<Code> ic = isolate()->builtins()->KeyedStoreIC_Miss(); + __ jmp(ic, RelocInfo::CODE_TARGET); + + __ bind(&just_store); + __ jmp(Handle<Code>(notransition_stub), RelocInfo::CODE_TARGET); + + // Return the generated code. + return GetCode(NORMAL, NULL, MEGAMORPHIC); +} + + MaybeObject* KeyedStoreStubCompiler::CompileStoreMegamorphic( MapList* receiver_maps, CodeList* handler_ics) { @@ -3694,7 +3744,7 @@ void KeyedStoreStubCompiler::GenerateStoreFastElement( // -- rdx : receiver // -- rsp[0] : return address // ----------------------------------- - Label miss_force_generic; + Label miss_force_generic, transition_elements_kind; // This stub is meant to be tail-jumped to, the receiver must already // have been verified by the caller to not be a smi. @@ -3717,13 +3767,13 @@ void KeyedStoreStubCompiler::GenerateStoreFastElement( __ j(above_equal, &miss_force_generic); } - // Do the store and update the write barrier. if (elements_kind == FAST_SMI_ONLY_ELEMENTS) { - __ JumpIfNotSmi(rax, &miss_force_generic); + __ JumpIfNotSmi(rax, &transition_elements_kind); __ SmiToInteger32(rcx, rcx); __ movq(FieldOperand(rdi, rcx, times_pointer_size, FixedArray::kHeaderSize), rax); } else { + // Do the store and update the write barrier. ASSERT(elements_kind == FAST_ELEMENTS); __ SmiToInteger32(rcx, rcx); __ lea(rcx, @@ -3742,6 +3792,10 @@ void KeyedStoreStubCompiler::GenerateStoreFastElement( Handle<Code> ic_force_generic = masm->isolate()->builtins()->KeyedStoreIC_MissForceGeneric(); __ jmp(ic_force_generic, RelocInfo::CODE_TARGET); + + __ bind(&transition_elements_kind); + Handle<Code> ic_miss = masm->isolate()->builtins()->KeyedStoreIC_Miss(); + __ jmp(ic_miss, RelocInfo::CODE_TARGET); } @@ -3754,7 +3808,7 @@ void KeyedStoreStubCompiler::GenerateStoreFastDoubleElement( // -- rdx : receiver // -- rsp[0] : return address // ----------------------------------- - Label miss_force_generic; + Label miss_force_generic, transition_elements_kind; // This stub is meant to be tail-jumped to, the receiver must already // have been verified by the caller to not be a smi. @@ -3776,7 +3830,8 @@ void KeyedStoreStubCompiler::GenerateStoreFastDoubleElement( // Handle smi values specially __ SmiToInteger32(rcx, rcx); - __ StoreNumberToDoubleElements(rax, rdi, rcx, xmm0, &miss_force_generic); + __ StoreNumberToDoubleElements(rax, rdi, rcx, xmm0, + &transition_elements_kind); __ ret(0); // Handle store cache miss, replacing the ic with the generic stub. @@ -3784,6 +3839,10 @@ void KeyedStoreStubCompiler::GenerateStoreFastDoubleElement( Handle<Code> ic_force_generic = masm->isolate()->builtins()->KeyedStoreIC_MissForceGeneric(); __ jmp(ic_force_generic, RelocInfo::CODE_TARGET); + + __ bind(&transition_elements_kind); + Handle<Code> ic_miss = masm->isolate()->builtins()->KeyedStoreIC_Miss(); + __ jmp(ic_miss, RelocInfo::CODE_TARGET); } diff --git a/test/mjsunit/element-kind.js b/test/mjsunit/element-kind.js index d61e26a173..cc95f46001 100644 --- a/test/mjsunit/element-kind.js +++ b/test/mjsunit/element-kind.js @@ -183,7 +183,6 @@ polymorphic(doubles, support_smi_only_arrays // Crankshaft support for smi-only elements in dynamic array literals. function get(foo) { return foo; } // Used to generate dynamic values. -//function crankshaft_test(expected_kind) { function crankshaft_test() { var a = [get(1), get(2), get(3)]; assertKind(element_kind.fast_smi_only_elements, a); @@ -204,3 +203,55 @@ for (var i = 0; i < 3; i++) { } %OptimizeFunctionOnNextCall(crankshaft_test); crankshaft_test(); + +// Elements_kind transitions for arrays. + +// A map can have three different elements_kind transitions: SMI->DOUBLE, +// DOUBLE->OBJECT, and SMI->OBJECT. No matter in which order these three are +// created, they must always end up with the same FAST map. +// Preparation: create one pair of identical objects for each case. +var a = [1, 2, 3]; +var b = [1, 2, 3]; +assertTrue(%HaveSameMap(a, b)); +assertKind(element_kind.fast_smi_only_elements, a); +var c = [1, 2, 3]; +c["case2"] = true; +var d = [1, 2, 3]; +d["case2"] = true; +assertTrue(%HaveSameMap(c, d)); +assertFalse(%HaveSameMap(a, c)); +assertKind(element_kind.fast_smi_only_elements, c); +var e = [1, 2, 3]; +e["case3"] = true; +var f = [1, 2, 3]; +f["case3"] = true; +assertTrue(%HaveSameMap(e, f)); +assertFalse(%HaveSameMap(a, e)); +assertFalse(%HaveSameMap(c, e)); +assertKind(element_kind.fast_smi_only_elements, e); +// Case 1: SMI->DOUBLE, DOUBLE->OBJECT, SMI->OBJECT. +a[0] = 1.5; +assertKind(element_kind.fast_double_elements, a); +a[0] = "foo"; +assertKind(element_kind.fast_elements, a); +b[0] = "bar"; +assertTrue(%HaveSameMap(a, b)); +// Case 2: SMI->DOUBLE, SMI->OBJECT, DOUBLE->OBJECT. +c[0] = 1.5; +assertKind(element_kind.fast_double_elements, c); +assertFalse(%HaveSameMap(c, d)); +d[0] = "foo"; +assertKind(element_kind.fast_elements, d); +assertFalse(%HaveSameMap(c, d)); +c[0] = "bar"; +assertTrue(%HaveSameMap(c, d)); +// Case 3: SMI->OBJECT, SMI->DOUBLE, DOUBLE->OBJECT. +e[0] = "foo"; +assertKind(element_kind.fast_elements, e); +assertFalse(%HaveSameMap(e, f)); +f[0] = 1.5; +assertKind(element_kind.fast_double_elements, f); +assertFalse(%HaveSameMap(e, f)); +f[0] = "bar"; +assertKind(element_kind.fast_elements, f); +assertTrue(%HaveSameMap(e, f));