Have one, long-lived map for bound functions.
authorjkummerow@chromium.org <jkummerow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 13 Jun 2014 12:19:04 +0000 (12:19 +0000)
committerjkummerow@chromium.org <jkummerow@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 13 Jun 2014 12:19:04 +0000 (12:19 +0000)
This avoids creating a new map for every bound function. Bonus: some cleanup in Runtime_FunctionBindArguments.

R=verwaest@chromium.org

Review URL: https://codereview.chromium.org/335653002

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

include/v8.h
src/bootstrapper.cc
src/contexts.h
src/runtime.cc
src/v8natives.js
test/mjsunit/runtime-gen/functionbindarguments.js [new file with mode: 0644]
tools/generate-runtime-tests.py

index 2312cd9..1db1473 100644 (file)
@@ -5525,7 +5525,7 @@ class Internals {
   static const int kJSObjectHeaderSize = 3 * kApiPointerSize;
   static const int kFixedArrayHeaderSize = 2 * kApiPointerSize;
   static const int kContextHeaderSize = 2 * kApiPointerSize;
-  static const int kContextEmbedderDataIndex = 75;
+  static const int kContextEmbedderDataIndex = 76;
   static const int kFullStringRepresentationMask = 0x07;
   static const int kStringEncodingMask = 0x4;
   static const int kExternalTwoByteRepresentationTag = 0x02;
index 87e1d20..4d7ce52 100644 (file)
@@ -262,24 +262,32 @@ class Genesis BASE_EMBEDDED {
   void TransferNamedProperties(Handle<JSObject> from, Handle<JSObject> to);
   void TransferIndexedProperties(Handle<JSObject> from, Handle<JSObject> to);
 
-  enum PrototypePropertyMode {
-    DONT_ADD_PROTOTYPE,
-    ADD_READONLY_PROTOTYPE,
-    ADD_WRITEABLE_PROTOTYPE
+  enum FunctionMode {
+    // With prototype.
+    FUNCTION_WITH_WRITEABLE_PROTOTYPE,
+    FUNCTION_WITH_READONLY_PROTOTYPE,
+    // Without prototype.
+    FUNCTION_WITHOUT_PROTOTYPE,
+    BOUND_FUNCTION
   };
 
-  Handle<Map> CreateFunctionMap(PrototypePropertyMode prototype_mode);
+  static bool IsFunctionModeWithPrototype(FunctionMode function_mode) {
+    return (function_mode == FUNCTION_WITH_WRITEABLE_PROTOTYPE ||
+            function_mode == FUNCTION_WITH_READONLY_PROTOTYPE);
+  }
+
+  Handle<Map> CreateFunctionMap(FunctionMode function_mode);
 
   void SetFunctionInstanceDescriptor(Handle<Map> map,
-                                     PrototypePropertyMode prototypeMode);
+                                     FunctionMode function_mode);
   void MakeFunctionInstancePrototypeWritable();
 
   Handle<Map> CreateStrictFunctionMap(
-      PrototypePropertyMode prototype_mode,
+      FunctionMode function_mode,
       Handle<JSFunction> empty_function);
 
   void SetStrictFunctionInstanceDescriptor(Handle<Map> map,
-                                           PrototypePropertyMode propertyMode);
+                                           FunctionMode function_mode);
 
   static bool CompileBuiltin(Isolate* isolate, int index);
   static bool CompileExperimentalBuiltin(Isolate* isolate, int index);
@@ -382,8 +390,8 @@ static Handle<JSFunction> InstallFunction(Handle<JSObject> target,
 
 
 void Genesis::SetFunctionInstanceDescriptor(
-    Handle<Map> map, PrototypePropertyMode prototypeMode) {
-  int size = (prototypeMode == DONT_ADD_PROTOTYPE) ? 4 : 5;
+    Handle<Map> map, FunctionMode function_mode) {
+  int size = IsFunctionModeWithPrototype(function_mode) ? 5 : 4;
   Map::EnsureDescriptorSlack(map, size);
 
   PropertyAttributes attribs = static_cast<PropertyAttributes>(
@@ -417,8 +425,8 @@ void Genesis::SetFunctionInstanceDescriptor(
                           caller, attribs);
     map->AppendDescriptor(&d);
   }
-  if (prototypeMode != DONT_ADD_PROTOTYPE) {
-    if (prototypeMode == ADD_WRITEABLE_PROTOTYPE) {
+  if (IsFunctionModeWithPrototype(function_mode)) {
+    if (function_mode == FUNCTION_WITH_WRITEABLE_PROTOTYPE) {
       attribs = static_cast<PropertyAttributes>(attribs & ~READ_ONLY);
     }
     Handle<AccessorInfo> prototype =
@@ -430,10 +438,10 @@ void Genesis::SetFunctionInstanceDescriptor(
 }
 
 
-Handle<Map> Genesis::CreateFunctionMap(PrototypePropertyMode prototype_mode) {
+Handle<Map> Genesis::CreateFunctionMap(FunctionMode function_mode) {
   Handle<Map> map = factory()->NewMap(JS_FUNCTION_TYPE, JSFunction::kSize);
-  SetFunctionInstanceDescriptor(map, prototype_mode);
-  map->set_function_with_prototype(prototype_mode != DONT_ADD_PROTOTYPE);
+  SetFunctionInstanceDescriptor(map, function_mode);
+  map->set_function_with_prototype(IsFunctionModeWithPrototype(function_mode));
   return map;
 }
 
@@ -445,14 +453,15 @@ Handle<JSFunction> Genesis::CreateEmptyFunction(Isolate* isolate) {
   // Functions with this map will not have a 'prototype' property, and
   // can not be used as constructors.
   Handle<Map> function_without_prototype_map =
-      CreateFunctionMap(DONT_ADD_PROTOTYPE);
+      CreateFunctionMap(FUNCTION_WITHOUT_PROTOTYPE);
   native_context()->set_sloppy_function_without_prototype_map(
       *function_without_prototype_map);
 
   // Allocate the function map. This map is temporary, used only for processing
   // of builtins.
   // Later the map is replaced with writable prototype map, allocated below.
-  Handle<Map> function_map = CreateFunctionMap(ADD_READONLY_PROTOTYPE);
+  Handle<Map> function_map =
+      CreateFunctionMap(FUNCTION_WITH_READONLY_PROTOTYPE);
   native_context()->set_sloppy_function_map(*function_map);
   native_context()->set_sloppy_function_with_readonly_prototype_map(
       *function_map);
@@ -460,7 +469,7 @@ Handle<JSFunction> Genesis::CreateEmptyFunction(Isolate* isolate) {
   // The final map for functions. Writeable prototype.
   // This map is installed in MakeFunctionInstancePrototypeWritable.
   sloppy_function_map_writable_prototype_ =
-      CreateFunctionMap(ADD_WRITEABLE_PROTOTYPE);
+      CreateFunctionMap(FUNCTION_WITH_WRITEABLE_PROTOTYPE);
 
   Factory* factory = isolate->factory();
 
@@ -514,7 +523,8 @@ Handle<JSFunction> Genesis::CreateEmptyFunction(Isolate* isolate) {
   sloppy_function_map_writable_prototype_->set_prototype(*empty_function);
 
   // Allocate the function map first and then patch the prototype later
-  Handle<Map> empty_function_map = CreateFunctionMap(DONT_ADD_PROTOTYPE);
+  Handle<Map> empty_function_map =
+      CreateFunctionMap(FUNCTION_WITHOUT_PROTOTYPE);
   empty_function_map->set_prototype(
       native_context()->object_function()->prototype());
   empty_function->set_map(*empty_function_map);
@@ -523,8 +533,8 @@ Handle<JSFunction> Genesis::CreateEmptyFunction(Isolate* isolate) {
 
 
 void Genesis::SetStrictFunctionInstanceDescriptor(
-    Handle<Map> map, PrototypePropertyMode prototypeMode) {
-  int size = (prototypeMode == DONT_ADD_PROTOTYPE) ? 4 : 5;
+    Handle<Map> map, FunctionMode function_mode) {
+  int size = IsFunctionModeWithPrototype(function_mode) ? 5 : 4;
   Map::EnsureDescriptorSlack(map, size);
 
   Handle<AccessorPair> arguments(factory()->NewAccessorPair());
@@ -534,9 +544,17 @@ void Genesis::SetStrictFunctionInstanceDescriptor(
   PropertyAttributes ro_attribs =
       static_cast<PropertyAttributes>(DONT_ENUM | DONT_DELETE | READ_ONLY);
 
-  Handle<AccessorInfo> length =
-      Accessors::FunctionLengthInfo(isolate(), ro_attribs);
-  {  // Add length.
+  // Add length.
+  if (function_mode == BOUND_FUNCTION) {
+    Handle<String> length_string = isolate()->factory()->length_string();
+    FieldDescriptor d(length_string, 0, ro_attribs, Representation::Tagged());
+    map->AppendDescriptor(&d);
+  } else {
+    ASSERT(function_mode == FUNCTION_WITH_WRITEABLE_PROTOTYPE ||
+           function_mode == FUNCTION_WITH_READONLY_PROTOTYPE ||
+           function_mode == FUNCTION_WITHOUT_PROTOTYPE);
+    Handle<AccessorInfo> length =
+        Accessors::FunctionLengthInfo(isolate(), ro_attribs);
     CallbacksDescriptor d(Handle<Name>(Name::cast(length->name())),
                           length, ro_attribs);
     map->AppendDescriptor(&d);
@@ -557,10 +575,11 @@ void Genesis::SetStrictFunctionInstanceDescriptor(
     CallbacksDescriptor d(factory()->caller_string(), caller, rw_attribs);
     map->AppendDescriptor(&d);
   }
-  if (prototypeMode != DONT_ADD_PROTOTYPE) {
+  if (IsFunctionModeWithPrototype(function_mode)) {
     // Add prototype.
     PropertyAttributes attribs =
-        prototypeMode == ADD_WRITEABLE_PROTOTYPE ? rw_attribs : ro_attribs;
+        function_mode == FUNCTION_WITH_WRITEABLE_PROTOTYPE ? rw_attribs
+                                                           : ro_attribs;
     Handle<AccessorInfo> prototype =
         Accessors::FunctionPrototypeInfo(isolate(), attribs);
     CallbacksDescriptor d(Handle<Name>(Name::cast(prototype->name())),
@@ -605,11 +624,11 @@ Handle<JSFunction> Genesis::GetGeneratorPoisonFunction() {
 
 
 Handle<Map> Genesis::CreateStrictFunctionMap(
-    PrototypePropertyMode prototype_mode,
+    FunctionMode function_mode,
     Handle<JSFunction> empty_function) {
   Handle<Map> map = factory()->NewMap(JS_FUNCTION_TYPE, JSFunction::kSize);
-  SetStrictFunctionInstanceDescriptor(map, prototype_mode);
-  map->set_function_with_prototype(prototype_mode != DONT_ADD_PROTOTYPE);
+  SetStrictFunctionInstanceDescriptor(map, function_mode);
+  map->set_function_with_prototype(IsFunctionModeWithPrototype(function_mode));
   map->set_prototype(*empty_function);
   return map;
 }
@@ -618,7 +637,7 @@ Handle<Map> Genesis::CreateStrictFunctionMap(
 void Genesis::CreateStrictModeFunctionMaps(Handle<JSFunction> empty) {
   // Allocate map for the prototype-less strict mode instances.
   Handle<Map> strict_function_without_prototype_map =
-      CreateStrictFunctionMap(DONT_ADD_PROTOTYPE, empty);
+      CreateStrictFunctionMap(FUNCTION_WITHOUT_PROTOTYPE, empty);
   native_context()->set_strict_function_without_prototype_map(
       *strict_function_without_prototype_map);
 
@@ -626,18 +645,23 @@ void Genesis::CreateStrictModeFunctionMaps(Handle<JSFunction> empty) {
   // only for processing of builtins.
   // Later the map is replaced with writable prototype map, allocated below.
   Handle<Map> strict_function_map =
-      CreateStrictFunctionMap(ADD_READONLY_PROTOTYPE, empty);
+      CreateStrictFunctionMap(FUNCTION_WITH_READONLY_PROTOTYPE, empty);
   native_context()->set_strict_function_map(*strict_function_map);
 
   // The final map for the strict mode functions. Writeable prototype.
   // This map is installed in MakeFunctionInstancePrototypeWritable.
   strict_function_map_writable_prototype_ =
-      CreateStrictFunctionMap(ADD_WRITEABLE_PROTOTYPE, empty);
+      CreateStrictFunctionMap(FUNCTION_WITH_WRITEABLE_PROTOTYPE, empty);
+  // Special map for bound functions.
+  Handle<Map> bound_function_map =
+      CreateStrictFunctionMap(BOUND_FUNCTION, empty);
+  native_context()->set_bound_function_map(*bound_function_map);
 
   // Complete the callbacks.
   PoisonArgumentsAndCaller(strict_function_without_prototype_map);
   PoisonArgumentsAndCaller(strict_function_map);
   PoisonArgumentsAndCaller(strict_function_map_writable_prototype_);
+  PoisonArgumentsAndCaller(bound_function_map);
 }
 
 
index 2792c2e..1ee5a6f 100644 (file)
@@ -130,6 +130,7 @@ enum BindingFlags {
     sloppy_function_without_prototype_map) \
   V(STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX, Map, \
     strict_function_without_prototype_map) \
+  V(BOUND_FUNCTION_MAP_INDEX, Map, bound_function_map) \
   V(REGEXP_RESULT_MAP_INDEX, Map, regexp_result_map)\
   V(SLOPPY_ARGUMENTS_BOILERPLATE_INDEX, JSObject, \
     sloppy_arguments_boilerplate) \
@@ -271,6 +272,7 @@ class Context: public FixedArray {
     STRICT_FUNCTION_MAP_INDEX,
     SLOPPY_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX,
     STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX,
+    BOUND_FUNCTION_MAP_INDEX,
     INITIAL_OBJECT_PROTOTYPE_INDEX,
     INITIAL_ARRAY_PROTOTYPE_INDEX,
     BOOLEAN_FUNCTION_INDEX,
index 83fbcb7..ff9a367 100644 (file)
@@ -8192,8 +8192,9 @@ RUNTIME_FUNCTION(Runtime_FunctionBindArguments) {
   HandleScope scope(isolate);
   ASSERT(args.length() == 4);
   CONVERT_ARG_HANDLE_CHECKED(JSFunction, bound_function, 0);
-  RUNTIME_ASSERT(args[3]->IsNumber());
-  Handle<Object> bindee = args.at<Object>(1);
+  CONVERT_ARG_HANDLE_CHECKED(Object, bindee, 1);
+  CONVERT_ARG_HANDLE_CHECKED(Object, this_object, 2);
+  CONVERT_NUMBER_ARG_HANDLE_CHECKED(new_length, 3);
 
   // TODO(lrn): Create bound function in C++ code from premade shared info.
   bound_function->shared()->set_bound(true);
@@ -8203,10 +8204,10 @@ RUNTIME_FUNCTION(Runtime_FunctionBindArguments) {
       GetCallerArguments(isolate, 0, &argc);
   // Don't count the this-arg.
   if (argc > 0) {
-    RUNTIME_ASSERT(*arguments[0] == args[2]);
+    RUNTIME_ASSERT(arguments[0].is_identical_to(this_object));
     argc--;
   } else {
-    RUNTIME_ASSERT(args[2]->IsUndefined());
+    RUNTIME_ASSERT(this_object->IsUndefined());
   }
   // Initialize array of bindings (function, this, and any existing arguments
   // if the function was already bound).
@@ -8228,7 +8229,7 @@ RUNTIME_FUNCTION(Runtime_FunctionBindArguments) {
     int array_size = JSFunction::kBoundArgumentsStartIndex + argc;
     new_bindings = isolate->factory()->NewFixedArray(array_size);
     new_bindings->set(JSFunction::kBoundFunctionIndex, *bindee);
-    new_bindings->set(JSFunction::kBoundThisIndex, args[2]);
+    new_bindings->set(JSFunction::kBoundThisIndex, *this_object);
     i = 2;
   }
   // Copy arguments, skipping the first which is "this_arg".
@@ -8239,13 +8240,17 @@ RUNTIME_FUNCTION(Runtime_FunctionBindArguments) {
       isolate->heap()->fixed_cow_array_map());
   bound_function->set_function_bindings(*new_bindings);
 
-  // Update length.
+  // Update length. Have to remove the prototype first so that map migration
+  // is happy about the number of fields.
+  RUNTIME_ASSERT(bound_function->RemovePrototype());
+  Handle<Map> bound_function_map(
+      isolate->native_context()->bound_function_map());
+  JSObject::MigrateToMap(bound_function, bound_function_map);
   Handle<String> length_string = isolate->factory()->length_string();
-  Handle<Object> new_length(args.at<Object>(3));
   PropertyAttributes attr =
       static_cast<PropertyAttributes>(DONT_DELETE | DONT_ENUM | READ_ONLY);
-  Runtime::ForceSetObjectProperty(
-      bound_function, length_string, new_length, attr).Assert();
+  JSObject::SetOwnPropertyIgnoreAttributes(bound_function, length_string,
+                                           new_length, attr);
   return *bound_function;
 }
 
index 68ca900..1d05338 100644 (file)
@@ -1781,19 +1781,15 @@ function FunctionBind(this_arg) { // Length is 1.
     return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc);
   };
 
-  %FunctionRemovePrototype(boundFunction);
   var new_length = 0;
-  if (%_ClassOf(this) == "Function") {
-    // Function or FunctionProxy.
-    var old_length = this.length;
-    // FunctionProxies might provide a non-UInt32 value. If so, ignore it.
-    if ((typeof old_length === "number") &&
-        ((old_length >>> 0) === old_length)) {
-      var argc = %_ArgumentsLength();
-      if (argc > 0) argc--;  // Don't count the thisArg as parameter.
-      new_length = old_length - argc;
-      if (new_length < 0) new_length = 0;
-    }
+  var old_length = this.length;
+  // FunctionProxies might provide a non-UInt32 value. If so, ignore it.
+  if ((typeof old_length === "number") &&
+      ((old_length >>> 0) === old_length)) {
+    var argc = %_ArgumentsLength();
+    if (argc > 0) argc--;  // Don't count the thisArg as parameter.
+    new_length = old_length - argc;
+    if (new_length < 0) new_length = 0;
   }
   // This runtime function finds any remaining arguments on the stack,
   // so we don't pass the arguments object.
diff --git a/test/mjsunit/runtime-gen/functionbindarguments.js b/test/mjsunit/runtime-gen/functionbindarguments.js
new file mode 100644 (file)
index 0000000..cc842fa
--- /dev/null
@@ -0,0 +1,8 @@
+// Copyright 2014 the V8 project authors. All rights reserved.
+// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY
+// Flags: --allow-natives-syntax --harmony
+var _bound_function = function() {};
+var _bindee = new Object();
+var arg2 = undefined;
+var _new_length = 1.5;
+%FunctionBindArguments(_bound_function, _bindee, arg2, _new_length);
index a4414be..04c9043 100755 (executable)
@@ -48,9 +48,9 @@ EXPAND_MACROS = [
 # remove or change runtime functions, but make sure we don't lose our ability
 # to parse them!
 EXPECTED_FUNCTION_COUNT = 358
-EXPECTED_FUZZABLE_COUNT = 325
+EXPECTED_FUZZABLE_COUNT = 326
 EXPECTED_CCTEST_COUNT = 6
-EXPECTED_UNKNOWN_COUNT = 5
+EXPECTED_UNKNOWN_COUNT = 4
 EXPECTED_BUILTINS_COUNT = 798
 
 
@@ -143,6 +143,7 @@ CUSTOM_KNOWN_GOOD_INPUT = {
   "DateParseString": [None, "new Array(8)", None],
   "DefineOrRedefineAccessorProperty": [None, None, "function() {}",
                                        "function() {}", 2, None],
+  "FunctionBindArguments": [None, None, "undefined", None, None],
   "GetBreakLocations": [None, 0, None],
   "GetDefaultReceiver": ["function() {}", None],
   "GetImplFromInitializedIntlObject": ["new Intl.NumberFormat('en-US')", None],