- Added a map cache for literal objects. This will
authorbak@chromium.org <bak@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 25 Sep 2008 07:46:07 +0000 (07:46 +0000)
committerbak@chromium.org <bak@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 25 Sep 2008 07:46:07 +0000 (07:46 +0000)
  canonicalize maps for object literals. JSON objects
  with the same set of properties names will then
  share the same map.
  This reduces the amount of generated code associated
  with object literals.
- Added a flag canonicalize_object_literal_maps.
  (default true)
- Changed the format of a function's literal array.
  Only the global context is now stored in the literal prefix.

Review URL: http://codereview.chromium.org/4078

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

src/ast.cc
src/codegen-ia32.cc
src/contexts.h
src/factory.cc
src/factory.h
src/flag-definitions.h
src/objects-inl.h
src/objects.cc
src/objects.h
src/parser.cc
src/runtime.cc

index 6e60078..2f21d92 100644 (file)
@@ -146,7 +146,6 @@ ObjectLiteral::Property::Property(bool is_getter, FunctionLiteral* value) {
 }
 
 
-
 void LabelCollector::AddLabel(Label* label) {
   // Add the label to the collector, but discard duplicates.
   int length = labels_->length();
index ce1bce6..1c6fc5b 100644 (file)
@@ -3811,7 +3811,8 @@ void Ia32CodeGenerator::VisitRegExpLiteral(RegExpLiteral* node) {
 // therefore context dependent.
 class ObjectLiteralDeferred: public DeferredCode {
  public:
-  ObjectLiteralDeferred(CodeGenerator* generator, ObjectLiteral* node)
+  ObjectLiteralDeferred(CodeGenerator* generator,
+                        ObjectLiteral* node)
       : DeferredCode(generator), node_(node) {
     set_comment("[ ObjectLiteralDeferred");
   }
@@ -3838,7 +3839,6 @@ void ObjectLiteralDeferred::Generate() {
 
 void Ia32CodeGenerator::VisitObjectLiteral(ObjectLiteral* node) {
   Comment cmnt(masm_, "[ ObjectLiteral");
-
   ObjectLiteralDeferred* deferred = new ObjectLiteralDeferred(this, node);
 
   // Retrieve the literal array and check the allocated entry.
@@ -3867,6 +3867,7 @@ void Ia32CodeGenerator::VisitObjectLiteral(ObjectLiteral* node) {
   // Push the new cloned literal object as the result.
   __ push(eax);
 
+
   for (int i = 0; i < node->properties()->length(); i++) {
     ObjectLiteral::Property* property  = node->properties()->at(i);
     switch (property->kind()) {
index f0869ac..00ad7b4 100644 (file)
@@ -91,7 +91,8 @@ enum ContextLookupFlags {
   V(EMPTY_SCRIPT_INDEX, Script, empty_script) \
   V(SCRIPT_FUNCTION_INDEX, JSFunction, script_function) \
   V(CONTEXT_EXTENSION_FUNCTION_INDEX, JSFunction, context_extension_function) \
-  V(OUT_OF_MEMORY_INDEX, Object, out_of_memory)
+  V(OUT_OF_MEMORY_INDEX, Object, out_of_memory) \
+  V(MAP_CACHE_INDEX, Object, map_cache)
 
 // JSFunctions are pairs (context, function code), sometimes also called
 // closures. A Context object is used to represent function contexts and
@@ -207,6 +208,7 @@ class Context: public FixedArray {
     SCRIPT_FUNCTION_INDEX,
     CONTEXT_EXTENSION_FUNCTION_INDEX,
     OUT_OF_MEMORY_INDEX,
+    MAP_CACHE_INDEX,
     GLOBAL_CONTEXT_SLOTS
   };
 
index 444e7e2..3167a71 100644 (file)
@@ -211,12 +211,8 @@ Handle<JSFunction> Factory::NewFunctionFromBoilerplate(
     // Store the object, regexp and array functions in the literals
     // array prefix.  These functions will be used when creating
     // object, regexp and array literals in this function.
-    literals->set(JSFunction::kLiteralObjectFunctionIndex,
-                  context->global_context()->object_function());
-    literals->set(JSFunction::kLiteralRegExpFunctionIndex,
-                  context->global_context()->regexp_function());
-    literals->set(JSFunction::kLiteralArrayFunctionIndex,
-                  context->global_context()->array_function());
+    literals->set(JSFunction::kLiteralGlobalContextIndex,
+                  context->global_context());
   }
   result->set_literals(*literals);
   ASSERT(!result->IsBoilerplate());
@@ -558,6 +554,12 @@ Handle<JSObject> Factory::NewJSObject(Handle<JSFunction> constructor,
 }
 
 
+Handle<JSObject> Factory::NewJSObjectFromMap(Handle<Map> map) {
+  CALL_HEAP_FUNCTION(Heap::AllocateJSObjectFromMap(*map, NOT_TENURED),
+                     JSObject);
+}
+
+
 Handle<JSObject> Factory::NewObjectLiteral(int expected_number_of_properties) {
   Handle<Map> map = Handle<Map>(Top::object_function()->initial_map());
   map = Factory::CopyMap(map);
@@ -749,6 +751,47 @@ Handle<JSFunction> Factory::CreateApiFunction(
 }
 
 
+Handle<MapCache> Factory::NewMapCache(int at_least_space_for) {
+  CALL_HEAP_FUNCTION(MapCache::Allocate(at_least_space_for), MapCache);
+}
+
+
+static Object* UpdateMapCacheWith(Context* context,
+                                  FixedArray* keys,
+                                  Map* map) {
+  Object* result = MapCache::cast(context->map_cache())->Put(keys, map);
+  if (!result->IsFailure()) context->set_map_cache(MapCache::cast(result));
+  return result;
+}
+
+
+Handle<MapCache> Factory::AddToMapCache(Handle<Context> context,
+                                        Handle<FixedArray> keys,
+                                        Handle<Map> map) {
+  CALL_HEAP_FUNCTION(UpdateMapCacheWith(*context, *keys, *map), MapCache);
+}
+
+
+Handle<Map> Factory::ObjectLiteralMapFromCache(Handle<Context> context,
+                                               Handle<FixedArray> keys) {
+  if (context->map_cache()->IsUndefined()) {
+    // Allocate the new map cache for the global context.
+    Handle<MapCache> new_cache = NewMapCache(24);
+    context->set_map_cache(*new_cache);
+  }
+  // Check to see whether there is a maching element in the cache.
+  Handle<MapCache> cache =
+      Handle<MapCache>(MapCache::cast(context->map_cache()));
+  Handle<Object> result = Handle<Object>(cache->Lookup(*keys));
+  if (result->IsMap()) return Handle<Map>::cast(result);
+  // Create a new map and add it to the cache.
+  Handle<Map> map =
+      CopyMap(Handle<Map>(context->object_function()->initial_map()));
+  AddToMapCache(context, keys, map);
+  return Handle<Map>(map);
+}
+
+
 void Factory::ConfigureInstance(Handle<FunctionTemplateInfo> desc,
                                 Handle<JSObject> instance,
                                 bool* pending_exception) {
index 9efbcd5..0d854c0 100644 (file)
@@ -167,6 +167,10 @@ class Factory : public AllStatic {
   static Handle<JSObject> NewJSObject(Handle<JSFunction> constructor,
                                       PretenureFlag pretenure = NOT_TENURED);
 
+  // JS objects are pretenured when allocated by the bootstrapper and
+  // runtime.
+  static Handle<JSObject> NewJSObjectFromMap(Handle<Map> map);
+
   // Allocate a JS object representing an object literal.  The object is
   // pretenured (allocated directly in the old generation).
   static Handle<JSObject> NewObjectLiteral(int expected_number_of_properties);
@@ -291,6 +295,12 @@ class Factory : public AllStatic {
 
   static Handle<DebugInfo> NewDebugInfo(Handle<SharedFunctionInfo> shared);
 
+
+  // Return a map using the map cache in the global context.
+  // The key the an ordered set of property names.
+  static Handle<Map> ObjectLiteralMapFromCache(Handle<Context> context,
+                                               Handle<FixedArray> keys);
+
  private:
   static Handle<JSFunction> NewFunctionHelper(Handle<String> name,
                                               Handle<Object> prototype);
@@ -302,6 +312,14 @@ class Factory : public AllStatic {
   static Handle<JSFunction> BaseNewFunctionFromBoilerplate(
       Handle<JSFunction> boilerplate,
       Handle<Map> function_map);
+
+  // Create a new map cache.
+  static Handle<MapCache> NewMapCache(int at_least_space_for);
+
+  // Update the map cache in the global context with (keys, map)
+  static Handle<MapCache> AddToMapCache(Handle<Context> context,
+                                        Handle<FixedArray> keys,
+                                        Handle<Map> map);
 };
 
 
index ac0aeb6..52c2847 100644 (file)
@@ -154,6 +154,9 @@ DEFINE_bool(cleanup_ics_at_gc, true,
 DEFINE_bool(cleanup_caches_in_maps_at_gc, true,
             "Flush code caches in maps during mark compact cycle.")
 
+DEFINE_bool(canonicalize_object_literal_maps, true,
+            "Canonicalize maps for object literals.")
+
 // mksnapshot.cc
 DEFINE_bool(h, false, "print this message")
 
index 61ab68b..2645363 100644 (file)
@@ -325,6 +325,11 @@ bool Object::IsCompilationCacheTable() {
 }
 
 
+bool Object::IsMapCache() {
+  return IsHashTable();
+}
+
+
 bool Object::IsPrimitive() {
   return IsOddball() || IsNumber() || IsString();
 }
@@ -1112,6 +1117,7 @@ CAST_ACCESSOR(DescriptorArray)
 CAST_ACCESSOR(Dictionary)
 CAST_ACCESSOR(SymbolTable)
 CAST_ACCESSOR(CompilationCacheTable)
+CAST_ACCESSOR(MapCache)
 CAST_ACCESSOR(String)
 CAST_ACCESSOR(SeqString)
 CAST_ACCESSOR(AsciiString)
index 8c82045..748179d 100644 (file)
@@ -3923,6 +3923,11 @@ Object* JSFunction::SetInstanceClassName(String* name) {
 }
 
 
+Context* JSFunction::GlobalContextFromLiterals(FixedArray* literals) {
+  return Context::cast(literals->get(JSFunction::kLiteralGlobalContextIndex));
+}
+
+
 void Oddball::OddballIterateBody(ObjectVisitor* v) {
   // Assumes all Object* members are contiguously allocated!
   IteratePointers(v, kToStringOffset, kToNumberOffset + kPointerSize);
@@ -5533,12 +5538,12 @@ Object* HashTable<prefix_size, element_size>::EnsureCapacity(
 
   Object* obj = Allocate(nof * 2);
   if (obj->IsFailure()) return obj;
-  HashTable* dict = HashTable::cast(obj);
-  WriteBarrierMode mode = dict->GetWriteBarrierMode();
+  HashTable* table = HashTable::cast(obj);
+  WriteBarrierMode mode = table->GetWriteBarrierMode();
 
   // Copy prefix to new array.
   for (int i = kPrefixStartIndex; i < kPrefixStartIndex + prefix_size; i++) {
-    dict->set(i, get(i), mode);
+    table->set(i, get(i), mode);
   }
   // Rehash the elements.
   uint32_t (*Hash)(Object* key) = key->GetHashFunction();
@@ -5547,14 +5552,14 @@ Object* HashTable<prefix_size, element_size>::EnsureCapacity(
     Object* key = get(from_index);
     if (IsKey(key)) {
       uint32_t insertion_index =
-          EntryToIndex(dict->FindInsertionEntry(key, Hash(key)));
+          EntryToIndex(table->FindInsertionEntry(key, Hash(key)));
       for (int j = 0; j < element_size; j++) {
-        dict->set(insertion_index + j, get(from_index + j), mode);
+        table->set(insertion_index + j, get(from_index + j), mode);
       }
     }
   }
-  dict->SetNumberOfElements(NumberOfElements());
-  return dict;
+  table->SetNumberOfElements(NumberOfElements());
+  return table;
 }
 
 
@@ -5656,6 +5661,70 @@ Object* CompilationCacheTable::Put(String* src, Object* value) {
 }
 
 
+// SymbolsKey used for HashTable where key is array of symbols.
+class SymbolsKey : public HashTableKey {
+ public:
+  explicit SymbolsKey(FixedArray* symbols) {
+    symbols_ = symbols;
+  }
+
+  bool IsMatch(Object* other) {
+    if (!other->IsFixedArray()) return false;
+    FixedArray* o = FixedArray::cast(other);
+    int len = symbols_->length();
+    if (o->length() != len) return false;
+    for (int i = 0; i < len; i++) {
+      if (o->get(i) != symbols_->get(i)) return false;
+    }
+    return true;
+  }
+
+  uint32_t Hash() { return SymbolsHash(symbols_); }
+
+  HashFunction GetHashFunction() { return SymbolsHash; }
+
+  Object* GetObject() { return symbols_; }
+
+  static uint32_t SymbolsHash(Object* obj) {
+    FixedArray* symbols_ = FixedArray::cast(obj);
+    int len = symbols_->length();
+    uint32_t  hash = 0;
+    for (int i = 0; i < len; i++) {
+      hash ^= String::cast(symbols_->get(i))->Hash();
+    }
+    return hash;
+  }
+
+  bool IsStringKey() { return false; }
+
+  FixedArray* symbols_;
+};
+
+Object* MapCache::Lookup(FixedArray* array) {
+  SymbolsKey key(array);
+  int entry = FindEntry(&key);
+  if (entry != -1) {
+    return get(EntryToIndex(entry) + 1);
+  } else {
+    return Heap::undefined_value();
+  }
+}
+
+
+Object* MapCache::Put(FixedArray* array, Map* value) {
+  SymbolsKey key(array);
+  Object* obj = EnsureCapacity(1, &key);
+  if (obj->IsFailure()) return obj;
+
+  MapCache* cache = reinterpret_cast<MapCache*>(obj);
+  int entry = cache->FindInsertionEntry(array, key.Hash());
+  cache->set(EntryToIndex(entry), array);
+  cache->set(EntryToIndex(entry) + 1, value);
+  cache->ElementAdded();
+  return cache;
+}
+
+
 Object* Dictionary::Allocate(int at_least_space_for) {
   Object* obj = DictionaryBase::Allocate(at_least_space_for);
   // Initialize the next enumeration index.
index e830a15..85a8122 100644 (file)
@@ -619,6 +619,7 @@ class Object BASE_EMBEDDED {
   inline bool IsDictionary();
   inline bool IsSymbolTable();
   inline bool IsCompilationCacheTable();
+  inline bool IsMapCache();
   inline bool IsPrimitive();
   inline bool IsGlobalObject();
   inline bool IsJSGlobalObject();
@@ -1836,6 +1837,22 @@ class CompilationCacheTable: public HashTable<0, 2> {
 };
 
 
+// MapCache.
+//
+// Maps keys that are a fixed array of symbols to a map.
+// Used for canonicalize maps for object literals.
+class MapCache: public HashTable<0, 2> {
+ public:
+  // Find cached value for a string key, otherwise return null.
+  Object* Lookup(FixedArray* key);
+  Object* Put(FixedArray* key, Map* value);
+  static inline MapCache* cast(Object* obj);
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(MapCache);
+};
+
+
 // Dictionary for keeping properties and elements in slow case.
 //
 // One element in the prefix is used for storing non-element
@@ -2671,6 +2688,9 @@ class JSFunction: public JSObject {
   // Returns the number of allocated literals.
   int NumberOfLiterals();
 
+  // Retrieve the global context from a function's literal array.
+  static Context* GlobalContextFromLiterals(FixedArray* literals);
+
   // Layout descriptors.
   static const int kPrototypeOrInitialMapOffset = JSObject::kHeaderSize;
   static const int kSharedFunctionInfoOffset =
@@ -2680,11 +2700,8 @@ class JSFunction: public JSObject {
   static const int kSize = kLiteralsOffset + kPointerSize;
 
   // Layout of the literals array.
-  static const int kLiteralsPrefixSize = 3;
-  static const int kLiteralObjectFunctionIndex = 0;
-  static const int kLiteralRegExpFunctionIndex = 1;
-  static const int kLiteralArrayFunctionIndex = 2;
-
+  static const int kLiteralsPrefixSize = 1;
+  static const int kLiteralGlobalContextIndex = 0;
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(JSFunction);
 };
index 20f075d..c39b4b6 100644 (file)
@@ -2738,8 +2738,7 @@ Expression* Parser::ParseObjectLiteral(bool* ok) {
         NEW(ObjectLiteral::Property(key, value));
 
     // Count CONSTANT or COMPUTED properties to maintain the enumeration order.
-    if (IsBoilerplateProperty(property))
-      number_of_boilerplate_properties++;
+    if (IsBoilerplateProperty(property)) number_of_boilerplate_properties++;
     properties.Add(property);
 
     // TODO(1240767): Consider allowing trailing comma.
index eaa119c..9b79145 100644 (file)
@@ -97,6 +97,37 @@ static Object* Runtime_CloneObjectLiteralBoilerplate(Arguments args) {
 }
 
 
+static Handle<Map> ComputeObjectLiteralMap(
+    Handle<Context> context,
+    Handle<FixedArray> constant_properties,
+    bool &is_result_from_cache) {
+  if (FLAG_canonicalize_object_literal_maps) {
+    // First find prefix of consecutive symbol keys.
+    int number_of_properties = constant_properties->length()/2;
+    int number_of_symbol_keys = 0;
+    while ((number_of_symbol_keys < number_of_properties) &&
+           (constant_properties->get(number_of_symbol_keys*2)->IsSymbol())) {
+      number_of_symbol_keys++;
+    }
+    // Based on the number of prefix symbols key we decide whether
+    // to use the map cache in the global context.
+    const int kMaxKeys = 10;
+    if ((number_of_symbol_keys == number_of_properties)
+        && (number_of_symbol_keys < kMaxKeys)) {
+      // Create the fixed array with the key.
+      Handle<FixedArray> keys = Factory::NewFixedArray(number_of_symbol_keys);
+      for (int i = 0; i < number_of_symbol_keys; i++) {
+        keys->set(i, constant_properties->get(i*2));
+      }
+      is_result_from_cache = true;
+      return Factory::ObjectLiteralMapFromCache(context, keys);
+    }
+  }
+  is_result_from_cache = false;
+  return Handle<Map>(context->object_function()->initial_map());
+}
+
+
 static Object* Runtime_CreateObjectLiteralBoilerplate(Arguments args) {
   HandleScope scope;
   ASSERT(args.length() == 3);
@@ -104,21 +135,24 @@ static Object* Runtime_CreateObjectLiteralBoilerplate(Arguments args) {
   Handle<FixedArray> literals = args.at<FixedArray>(0);
   int literals_index = Smi::cast(args[1])->value();
   Handle<FixedArray> constant_properties = args.at<FixedArray>(2);
+  Handle<Context> context =
+      Handle<Context>(JSFunction::GlobalContextFromLiterals(*literals));
+
+  bool is_result_from_cache;
+  Handle<Map> map = ComputeObjectLiteralMap(context,
+                                            constant_properties,
+                                            is_result_from_cache);
 
   // Get the object function from the literals array.  This is the
   // object function from the context in which the function was
   // created.  We do not use the object function from the current
   // global context because this might be the object function from
   // another context which we should not have access to.
-  const int kObjectFunIndex = JSFunction::kLiteralObjectFunctionIndex;
-  Handle<JSFunction> constructor =
-      Handle<JSFunction>(JSFunction::cast(literals->get(kObjectFunIndex)));
-
-  Handle<JSObject> boilerplate = Factory::NewJSObject(constructor, TENURED);
-
+  Handle<JSObject> boilerplate = Factory::NewJSObjectFromMap(map);
   {  // Add the constant propeties to the boilerplate.
     int length = constant_properties->length();
-    OptimizedObjectForAddingMultipleProperties opt(boilerplate, true);
+    OptimizedObjectForAddingMultipleProperties opt(boilerplate,
+                                                   !is_result_from_cache);
     for (int index = 0; index < length; index +=2) {
       Handle<Object> key(constant_properties->get(index+0));
       Handle<Object> value(constant_properties->get(index+1));
@@ -160,9 +194,8 @@ static Object* Runtime_CreateArrayLiteral(Arguments args) {
   ASSERT(args.length() == 2);
   CONVERT_CHECKED(FixedArray, elements, args[0]);
   CONVERT_CHECKED(FixedArray, literals, args[1]);
-  const int kArrayFunIndex = JSFunction::kLiteralArrayFunctionIndex;
-  JSFunction* constructor = JSFunction::cast(literals->get(kArrayFunIndex));
-
+  JSFunction* constructor =
+      JSFunction::GlobalContextFromLiterals(literals)->array_function();
   // Create the JSArray.
   Object* object = Heap::AllocateJSObject(constructor);
   if (object->IsFailure()) return object;
@@ -699,10 +732,9 @@ static Object* Runtime_MaterializeRegExpLiteral(Arguments args) {
   // created.  We do not use the RegExp function from the current
   // global context because this might be the RegExp function from
   // another context which we should not have access to.
-  const int kRegexpFunIndex = JSFunction::kLiteralRegExpFunctionIndex;
   Handle<JSFunction> constructor =
-      Handle<JSFunction>(JSFunction::cast(literals->get(kRegexpFunIndex)));
-
+      Handle<JSFunction>(
+          JSFunction::GlobalContextFromLiterals(*literals)->regexp_function());
   // Compute the regular expression literal.
   bool has_pending_exception;
   Handle<Object> regexp =
@@ -839,12 +871,8 @@ static Object* Runtime_SetCode(Arguments args) {
       // Insert the object, regexp and array functions in the literals
       // array prefix.  These are the functions that will be used when
       // creating object, regexp and array literals.
-      literals->set(JSFunction::kLiteralObjectFunctionIndex,
-                    context->global_context()->object_function());
-      literals->set(JSFunction::kLiteralRegExpFunctionIndex,
-                    context->global_context()->regexp_function());
-      literals->set(JSFunction::kLiteralArrayFunctionIndex,
-                    context->global_context()->array_function());
+      literals->set(JSFunction::kLiteralGlobalContextIndex,
+                    context->global_context());
     }
     target->set_literals(*literals);
   }