Optimize Function.prototype.call
authorverwaest@chromium.org <verwaest@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 13 Jun 2014 12:52:23 +0000 (12:52 +0000)
committerverwaest@chromium.org <verwaest@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 13 Jun 2014 12:52:23 +0000 (12:52 +0000)
- May inline the function, or call it directly, instead of going through call
- Supports arguments object escaping when it escapes to builtins (preparation for slice.call(arguments, ...) optimization)
- Both .call and .apply now support inlining when calling builtins indirectly

BUG=
R=verwaest@chromium.org, rossberg@chromium.org

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

Patch from Petka Antonov <p.antonov@partner.samsung.com>.

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

src/hydrogen.cc
src/hydrogen.h
src/objects.h
test/mjsunit/compiler/inlined-call.js [new file with mode: 0644]

index 3162eb6..658cc5c 100644 (file)
@@ -7923,9 +7923,9 @@ bool HOptimizedGraphBuilder::TryInlineSetter(Handle<JSFunction> setter,
 }
 
 
-bool HOptimizedGraphBuilder::TryInlineApply(Handle<JSFunction> function,
-                                            Call* expr,
-                                            int arguments_count) {
+bool HOptimizedGraphBuilder::TryInlineIndirectCall(Handle<JSFunction> function,
+                                                   Call* expr,
+                                                   int arguments_count) {
   return TryInline(function,
                    arguments_count,
                    NULL,
@@ -7977,12 +7977,22 @@ bool HOptimizedGraphBuilder::TryInlineBuiltinFunctionCall(Call* expr) {
 
 bool HOptimizedGraphBuilder::TryInlineBuiltinMethodCall(
     Call* expr,
-    HValue* receiver,
-    Handle<Map> receiver_map) {
+    Handle<JSFunction> function,
+    Handle<Map> receiver_map,
+    int args_count_no_receiver) {
+  if (!function->shared()->HasBuiltinFunctionId()) return false;
+  BuiltinFunctionId id = function->shared()->builtin_function_id();
+  int argument_count = args_count_no_receiver + 1;  // Plus receiver.
+
+  if (receiver_map.is_null()) {
+    HValue* receiver = environment()->ExpressionStackAt(args_count_no_receiver);
+    if (receiver->IsConstant() &&
+        HConstant::cast(receiver)->handle(isolate())->IsHeapObject()) {
+      receiver_map = handle(Handle<HeapObject>::cast(
+          HConstant::cast(receiver)->handle(isolate()))->map());
+    }
+  }
   // Try to inline calls like Math.* as operations in the calling function.
-  if (!expr->target()->shared()->HasBuiltinFunctionId()) return false;
-  BuiltinFunctionId id = expr->target()->shared()->builtin_function_id();
-  int argument_count = expr->arguments()->length() + 1;  // Plus receiver.
   switch (id) {
     case kStringCharCodeAt:
     case kStringCharAt:
@@ -8090,7 +8100,7 @@ bool HOptimizedGraphBuilder::TryInlineBuiltinMethodCall(
       if (receiver_map->is_observed()) return false;
       ASSERT(receiver_map->is_extensible());
 
-      Drop(expr->arguments()->length());
+      Drop(args_count_no_receiver);
       HValue* result;
       HValue* reduced_length;
       HValue* receiver = Pop();
@@ -8166,7 +8176,7 @@ bool HOptimizedGraphBuilder::TryInlineBuiltinMethodCall(
       Handle<JSObject> prototype(JSObject::cast(receiver_map->prototype()));
       BuildCheckPrototypeMaps(prototype, Handle<JSObject>());
 
-      const int argc = expr->arguments()->length();
+      const int argc = args_count_no_receiver;
       if (argc != 1) return false;
 
       HValue* value_to_push = Pop();
@@ -8223,7 +8233,7 @@ bool HOptimizedGraphBuilder::TryInlineBuiltinMethodCall(
       // Threshold for fast inlined Array.shift().
       HConstant* inline_threshold = Add<HConstant>(static_cast<int32_t>(16));
 
-      Drop(expr->arguments()->length());
+      Drop(args_count_no_receiver);
       HValue* receiver = Pop();
       HValue* function = Pop();
       HValue* result;
@@ -8534,31 +8544,83 @@ bool HOptimizedGraphBuilder::TryInlineApiCall(Handle<JSFunction> function,
 }
 
 
-bool HOptimizedGraphBuilder::TryCallApply(Call* expr) {
+void HOptimizedGraphBuilder::HandleIndirectCall(Call* expr,
+                                                HValue* function,
+                                                int arguments_count) {
+  Handle<JSFunction> known_function;
+  int args_count_no_receiver = arguments_count - 1;
+  if (function->IsConstant() &&
+      HConstant::cast(function)->handle(isolate())->IsJSFunction()) {
+    known_function = Handle<JSFunction>::cast(
+        HConstant::cast(function)->handle(isolate()));
+    if (TryInlineIndirectCall(known_function, expr, args_count_no_receiver)) {
+      return;
+    }
+
+    Handle<Map> map;
+    if (TryInlineBuiltinMethodCall(expr, known_function, map,
+                                   args_count_no_receiver)) {
+      if (FLAG_trace_inlining) {
+        PrintF("Inlining builtin ");
+        known_function->ShortPrint();
+        PrintF("\n");
+      }
+      return;
+    }
+  }
+
+  PushArgumentsFromEnvironment(arguments_count);
+  HInvokeFunction* call = New<HInvokeFunction>(
+      function, known_function, arguments_count);
+  Drop(1);  // Function
+  ast_context()->ReturnInstruction(call, expr->id());
+}
+
+
+bool HOptimizedGraphBuilder::TryIndirectCall(Call* expr) {
   ASSERT(expr->expression()->IsProperty());
 
   if (!expr->IsMonomorphic()) {
     return false;
   }
+
   Handle<Map> function_map = expr->GetReceiverTypes()->first();
   if (function_map->instance_type() != JS_FUNCTION_TYPE ||
-      !expr->target()->shared()->HasBuiltinFunctionId() ||
-      expr->target()->shared()->builtin_function_id() != kFunctionApply) {
+      !expr->target()->shared()->HasBuiltinFunctionId()) {
     return false;
   }
 
-  if (current_info()->scope()->arguments() == NULL) return false;
+  switch (expr->target()->shared()->builtin_function_id()) {
+    case kFunctionCall: {
+      BuildFunctionCall(expr);
+      return true;
+    }
+    case kFunctionApply: {
+      // For .apply, only the pattern f.apply(receiver, arguments)
+      // is supported.
+      if (current_info()->scope()->arguments() == NULL) return false;
 
-  ZoneList<Expression*>* args = expr->arguments();
-  if (args->length() != 2) return false;
+      ZoneList<Expression*>* args = expr->arguments();
+      if (args->length() != 2) return false;
+
+      VariableProxy* arg_two = args->at(1)->AsVariableProxy();
+      if (arg_two == NULL || !arg_two->var()->IsStackAllocated()) return false;
+      HValue* arg_two_value = LookupAndMakeLive(arg_two->var());
+      if (!arg_two_value->CheckFlag(HValue::kIsArguments)) return false;
+      BuildFunctionApply(expr);
+      return true;
+    }
+    default: {
+      return false;
+    }
+  }
+  UNREACHABLE();
+}
 
-  VariableProxy* arg_two = args->at(1)->AsVariableProxy();
-  if (arg_two == NULL || !arg_two->var()->IsStackAllocated()) return false;
-  HValue* arg_two_value = LookupAndMakeLive(arg_two->var());
-  if (!arg_two_value->CheckFlag(HValue::kIsArguments)) return false;
 
-  // Found pattern f.apply(receiver, arguments).
-  CHECK_ALIVE_OR_RETURN(VisitForValue(args->at(0)), true);
+void HOptimizedGraphBuilder::BuildFunctionApply(Call* expr) {
+  ZoneList<Expression*>* args = expr->arguments();
+  CHECK_ALIVE(VisitForValue(args->at(0)));
   HValue* receiver = Pop();  // receiver
   HValue* function = Pop();  // f
   Drop(1);  // apply
@@ -8572,7 +8634,6 @@ bool HOptimizedGraphBuilder::TryCallApply(Call* expr) {
                                                 length,
                                                 elements);
     ast_context()->ReturnInstruction(result, expr->id());
-    return true;
   } else {
     // We are inside inlined function and we know exactly what is inside
     // arguments object. But we need to be able to materialize at deopt.
@@ -8586,23 +8647,34 @@ bool HOptimizedGraphBuilder::TryCallApply(Call* expr) {
     for (int i = 1; i < arguments_count; i++) {
       Push(arguments_values->at(i));
     }
+    HandleIndirectCall(expr, function, arguments_count);
+  }
+}
 
-    Handle<JSFunction> known_function;
-    if (function->IsConstant() &&
-        HConstant::cast(function)->handle(isolate())->IsJSFunction()) {
-      known_function = Handle<JSFunction>::cast(
-          HConstant::cast(function)->handle(isolate()));
-      int args_count = arguments_count - 1;  // Excluding receiver.
-      if (TryInlineApply(known_function, expr, args_count)) return true;
-    }
 
-    PushArgumentsFromEnvironment(arguments_count);
-    HInvokeFunction* call = New<HInvokeFunction>(
-        function, known_function, arguments_count);
-    Drop(1);  // Function.
-    ast_context()->ReturnInstruction(call, expr->id());
-    return true;
+// f.call(...)
+void HOptimizedGraphBuilder::BuildFunctionCall(Call* expr) {
+  HValue* function = Pop();  // f
+  HValue* receiver;
+  ZoneList<Expression*>* args = expr->arguments();
+  int args_length = args->length();
+  Drop(1);  // call
+
+  if (args_length == 0) {
+    receiver = graph()->GetConstantUndefined();
+    args_length = 1;
+  } else {
+    CHECK_ALIVE(VisitForValue(args->at(0)));
+    receiver = Pop();
+  }
+  receiver = BuildWrapReceiver(receiver, function);
+
+  Push(function);
+  Push(receiver);
+  for (int i = 1; i < args_length; i++) {
+    CHECK_ALIVE(VisitForValue(args->at(i)));
   }
+  HandleIndirectCall(expr, function, args_length);
 }
 
 
@@ -8867,11 +8939,12 @@ void HOptimizedGraphBuilder::VisitCall(Call* expr) {
           HConstant::cast(function)->handle(isolate()));
       expr->set_target(known_function);
 
-      if (TryCallApply(expr)) return;
+      if (TryIndirectCall(expr)) return;
       CHECK_ALIVE(VisitExpressions(expr->arguments()));
 
       Handle<Map> map = types->length() == 1 ? types->first() : Handle<Map>();
-      if (TryInlineBuiltinMethodCall(expr, receiver, map)) {
+      if (TryInlineBuiltinMethodCall(expr, known_function, map,
+                                     expr->arguments()->length())) {
         if (FLAG_trace_inlining) {
           PrintF("Inlining builtin ");
           known_function->ShortPrint();
index 9260b58..b4e4074 100644 (file)
@@ -2321,8 +2321,13 @@ class HOptimizedGraphBuilder : public HGraphBuilder, public AstVisitor {
   void EnsureArgumentsArePushedForAccess();
   bool TryArgumentsAccess(Property* expr);
 
-  // Try to optimize fun.apply(receiver, arguments) pattern.
-  bool TryCallApply(Call* expr);
+  // Shared code for .call and .apply optimizations.
+  void HandleIndirectCall(Call* expr, HValue* function, int arguments_count);
+  // Try to optimize indirect calls such as fun.apply(receiver, arguments)
+  // or fun.call(...).
+  bool TryIndirectCall(Call* expr);
+  void BuildFunctionApply(Call* expr);
+  void BuildFunctionCall(Call* expr);
 
   bool TryHandleArrayCall(Call* expr, HValue* function);
   bool TryHandleArrayCallNew(CallNew* expr, HValue* function);
@@ -2358,12 +2363,13 @@ class HOptimizedGraphBuilder : public HGraphBuilder, public AstVisitor {
                        BailoutId id,
                        BailoutId assignment_id,
                        HValue* implicit_return_value);
-  bool TryInlineApply(Handle<JSFunction> function,
-                      Call* expr,
-                      int arguments_count);
+  bool TryInlineIndirectCall(Handle<JSFunction> function,
+                             Call* expr,
+                             int arguments_count);
   bool TryInlineBuiltinMethodCall(Call* expr,
-                                  HValue* receiver,
-                                  Handle<Map> receiver_map);
+                                  Handle<JSFunction> function,
+                                  Handle<Map> receiver_map,
+                                  int args_count_no_receiver);
   bool TryInlineBuiltinFunctionCall(Call* expr);
   enum ApiCallType {
     kCallApiFunction,
index 46661b6..9d96b50 100644 (file)
@@ -7013,6 +7013,7 @@ class Script: public Struct {
   V(Array.prototype, pop, ArrayPop)                   \
   V(Array.prototype, shift, ArrayShift)               \
   V(Function.prototype, apply, FunctionApply)         \
+  V(Function.prototype, call, FunctionCall)           \
   V(String.prototype, charCodeAt, StringCharCodeAt)   \
   V(String.prototype, charAt, StringCharAt)           \
   V(String, fromCharCode, StringFromCharCode)         \
diff --git a/test/mjsunit/compiler/inlined-call.js b/test/mjsunit/compiler/inlined-call.js
new file mode 100644 (file)
index 0000000..c71d6b3
--- /dev/null
@@ -0,0 +1,156 @@
+// Copyright 2014 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --noalways-opt
+
+var array = [];
+for (var i = 0; i < 100; ++i) {
+  array[i] = i;
+}
+
+var copy = array.slice();
+
+function unshiftsArray(num) {
+  [].unshift.call(array, num);
+}
+
+unshiftsArray(50);
+unshiftsArray(60);
+%OptimizeFunctionOnNextCall(unshiftsArray);
+unshiftsArray(80);
+unshiftsArray(50);
+unshiftsArray(60);
+
+copy.unshift(50);
+copy.unshift(60);
+copy.unshift(80);
+copy.unshift(50);
+copy.unshift(60);
+
+assertOptimized(unshiftsArray);
+assertArrayEquals(array, copy);
+
+
+var called = 0;
+var funRecv;
+// For the HConstant
+Array.prototype.fun = function() {
+  funRecv = this;
+  called++;
+  assertEquals(0, arguments.length);
+};
+
+function callNoArgs() {
+  [].fun.call();
+}
+
+callNoArgs();
+callNoArgs();
+assertEquals(this, funRecv);
+%OptimizeFunctionOnNextCall(callNoArgs);
+callNoArgs();
+assertEquals(this, funRecv);
+assertEquals(3, called);
+assertOptimized(callNoArgs);
+
+var funStrictRecv;
+called = 0;
+Array.prototype.funStrict = function() {
+  "use strict";
+  funStrictRecv = this;
+  called++;
+  assertEquals(0, arguments.length);
+};
+
+function callStrictNoArgs() {
+  [].funStrict.call();
+}
+
+callStrictNoArgs();
+callStrictNoArgs();
+assertEquals(undefined, funStrictRecv);
+%OptimizeFunctionOnNextCall(callStrictNoArgs);
+callStrictNoArgs();
+assertEquals(undefined, funStrictRecv);
+assertEquals(3, called);
+assertOptimized(callStrictNoArgs);
+
+called = 0;
+Array.prototype.manyArgs = function() {
+  "use strict";
+  assertEquals(5, arguments.length);
+  assertEquals(0, this);
+  assertEquals(5, arguments[4]);
+  called++;
+}
+
+function callManyArgs() {
+  [].manyArgs.call(0, 1, 2, 3, 4, 5);
+}
+
+callManyArgs();
+callManyArgs();
+%OptimizeFunctionOnNextCall(callManyArgs);
+callManyArgs();
+assertOptimized(callManyArgs);
+assertEquals(called, 3);
+
+called = 0;
+Array.prototype.manyArgsSloppy = function() {
+  assertTrue(this instanceof Number);
+  assertEquals(5, arguments.length);
+  assertEquals(0, this.valueOf());
+  assertEquals(5, arguments[4]);
+  called++;
+}
+
+function callManyArgsSloppy() {
+  [].manyArgsSloppy.call(0, 1, 2, 3, 4, 5);
+}
+
+callManyArgsSloppy();
+callManyArgsSloppy();
+%OptimizeFunctionOnNextCall(callManyArgsSloppy);
+callManyArgsSloppy();
+assertOptimized(callManyArgsSloppy);
+assertEquals(called, 3);
+
+var str = "hello";
+var code = str.charCodeAt(3);
+called = 0;
+function callBuiltinIndirectly() {
+  called++;
+  return "".charCodeAt.call(str, 3);
+}
+
+callBuiltinIndirectly();
+callBuiltinIndirectly();
+%OptimizeFunctionOnNextCall(callBuiltinIndirectly);
+assertEquals(code, callBuiltinIndirectly());
+assertOptimized(callBuiltinIndirectly);
+assertEquals(3, called);
+
+this.array = [1,2,3,4,5,6,7,8,9];
+var copy = this.array.slice();
+called = 0;
+
+function callInlineableBuiltinIndirectlyWhileInlined() {
+    called++;
+    return [].push.apply(array, arguments);
+}
+
+function callInlined(num) {
+    return callInlineableBuiltinIndirectlyWhileInlined(num);
+}
+
+callInlined(1);
+callInlined(2);
+%OptimizeFunctionOnNextCall(callInlineableBuiltinIndirectlyWhileInlined);
+%OptimizeFunctionOnNextCall(callInlined);
+callInlined(3);
+copy.push(1, 2, 3);
+assertOptimized(callInlined);
+assertOptimized(callInlineableBuiltinIndirectlyWhileInlined);
+assertArrayEquals(copy, this.array);
+assertEquals(3, called);