Track elements_kind transitions in KeyedStoreICs.
authorjkummerow@chromium.org <jkummerow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 11 Oct 2011 09:33:00 +0000 (09:33 +0000)
committerjkummerow@chromium.org <jkummerow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 11 Oct 2011 09:33:00 +0000 (09:33 +0000)
Review URL: http://codereview.chromium.org/8166017

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@9577 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/arm/stub-cache-arm.cc
src/ia32/stub-cache-ia32.cc
src/ic.cc
src/ic.h
src/objects.cc
src/runtime.cc
src/runtime.h
src/stub-cache.cc
src/stub-cache.h
src/x64/stub-cache-x64.cc
test/mjsunit/element-kind.js

index 09ecc79..f852786 100644 (file)
@@ -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(&notransition_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);
 }
 
 
index 53f9d5b..44649cb 100644 (file)
@@ -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(&notransition_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);
 }
 
 
index 834dbfd..f54f0d7 100644 (file)
--- 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() ?
index ece5be9..04436b5 100644 (file)
--- 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;
+  }
 };
 
 
index 604460a..baba61a 100644 (file)
@@ -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;
           }
         }
index 3e04942..cafe46c 100644 (file)
@@ -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
 
index c6f7773..ed9c2b8 100644 (file)
@@ -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)
index 09ee793..67451f2 100644 (file)
@@ -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());
index fcb58e1..10d017d 100644 (file)
@@ -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);
index b08ae1d..c0a13ae 100644 (file)
@@ -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(&notransition_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);
 }
 
 
index d61e26a..cc95f46 100644 (file)
@@ -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));