Improve performance of Function.prototype.call
authoroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 28 Mar 2009 03:50:39 +0000 (03:50 +0000)
committeroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 28 Mar 2009 03:50:39 +0000 (03:50 +0000)
<https://bugs.webkit.org/show_bug.cgi?id=24907>

Reviewed by Gavin Barraclough

Optimistically assume that expression.call(..) is going to be a call to
Function.prototype.call, and handle it specially to attempt to reduce the
degree of VM reentrancy.

When everything goes right this removes the vm reentry improving .call()
by around a factor of 10.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@42065 268f45cc-cd09-0410-ab3c-d52691b4dbfc

19 files changed:
JavaScriptCore/ChangeLog
JavaScriptCore/bytecode/CodeBlock.cpp
JavaScriptCore/bytecode/Opcode.h
JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
JavaScriptCore/bytecompiler/BytecodeGenerator.h
JavaScriptCore/interpreter/Interpreter.cpp
JavaScriptCore/jit/JIT.cpp
JavaScriptCore/parser/Grammar.y
JavaScriptCore/parser/Nodes.cpp
JavaScriptCore/parser/Nodes.h
JavaScriptCore/runtime/FunctionPrototype.cpp
JavaScriptCore/runtime/FunctionPrototype.h
JavaScriptCore/runtime/JSGlobalObject.cpp
JavaScriptCore/runtime/JSGlobalObject.h
LayoutTests/ChangeLog
LayoutTests/fast/js/function-call-aliased-expected.txt [new file with mode: 0644]
LayoutTests/fast/js/function-call-aliased.html [new file with mode: 0644]
LayoutTests/fast/js/resources/function-call-aliased.js [new file with mode: 0644]
LayoutTests/fast/profiler/call-expected.txt

index 561b34d..419fbac 100644 (file)
@@ -1,3 +1,41 @@
+2009-03-27  Oliver Hunt  <oliver@apple.com>
+
+        Reviewed by Gavin Barraclough.
+
+        Improve performance of Function.prototype.call
+        <https://bugs.webkit.org/show_bug.cgi?id=24907>
+
+        Optimistically assume that expression.call(..) is going to be a call to
+        Function.prototype.call, and handle it specially to attempt to reduce the
+        degree of VM reentrancy.
+
+        When everything goes right this removes the vm reentry improving .call()
+        by around a factor of 10.
+
+        * JavaScriptCore.xcodeproj/project.pbxproj:
+        * bytecode/CodeBlock.cpp:
+        (JSC::CodeBlock::dump):
+        * bytecode/Opcode.h:
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::emitJumpIfNotFunctionCall):
+        * bytecompiler/BytecodeGenerator.h:
+        * interpreter/Interpreter.cpp:
+        (JSC::Interpreter::privateExecute):
+        * jit/JIT.cpp:
+        (JSC::JIT::privateCompileMainPass):
+        * parser/Grammar.y:
+        * parser/Nodes.cpp:
+        (JSC::CallFunctionCallDotNode::emitBytecode):
+        * parser/Nodes.h:
+        (JSC::CallFunctionCallDotNode::):
+        * runtime/FunctionPrototype.cpp:
+        (JSC::FunctionPrototype::addFunctionProperties):
+        * runtime/FunctionPrototype.h:
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::reset):
+        (JSC::JSGlobalObject::mark):
+        * runtime/JSGlobalObject.h:
+
 2009-03-27  Laszlo Gombos  <laszlo.1.gombos@nokia.com>
 
         Reviewed by Darin Adler.
index be060d0..b0cd121 100644 (file)
@@ -888,11 +888,18 @@ void CodeBlock::dump(ExecState* exec, const Vector<Instruction>::const_iterator&
             printConditionalJump(begin, it, location, "jneq_null");
             break;
         }
-        case op_jnless: {
+        case op_jneq_ptr: {
             int r0 = (++it)->u.operand;
             int r1 = (++it)->u.operand;
             int offset = (++it)->u.operand;
-            printf("[%4d] jnless\t\t %s, %s, %d(->%d)\n", location, registerName(r0).c_str(), registerName(r1).c_str(), offset, locationForOffset(begin, it, offset));
+            printf("[%4d] jneq_ptr\t\t %s, %s, %d(->%d)\n", location, registerName(r0).c_str(), registerName(r1).c_str(), offset, locationForOffset(begin, it, offset));
+            break;
+        }
+        case op_jnless: {
+            int r0 = (++it)->u.operand;
+            JSValuePtr function = JSValuePtr((++it)->u.jsCell);
+            int offset = (++it)->u.operand;
+            printf("[%4d] jnless\t\t %s, %s, %d(->%d)\n", location, registerName(r0).c_str(), valueToSourceString(exec, function).ascii(), offset, locationForOffset(begin, it, offset));
             break;
         }
         case op_loop_if_less: {
index d00178b..7b52e2f 100644 (file)
@@ -125,6 +125,7 @@ namespace JSC {
         macro(op_jfalse, 3) \
         macro(op_jeq_null, 3) \
         macro(op_jneq_null, 3) \
+        macro(op_jneq_ptr, 4) \
         macro(op_jnless, 4) \
         macro(op_jmp_scopes, 3) \
         macro(op_loop, 2) \
index c83cdc7..b0738d8 100644 (file)
@@ -695,6 +695,15 @@ PassRefPtr<Label> BytecodeGenerator::emitJumpIfFalse(RegisterID* cond, Label* ta
     return target;
 }
 
+PassRefPtr<Label> BytecodeGenerator::emitJumpIfNotFunctionCall(RegisterID* cond, Label* target)
+{
+    emitOpcode(op_jneq_ptr);
+    instructions().append(cond->index());
+    instructions().append(m_scopeChain->globalObject()->d()->callFunction);
+    instructions().append(target->offsetFrom(instructions().size()));
+    return target;
+}
+
 unsigned BytecodeGenerator::addConstant(FuncDeclNode* n)
 {
     // No need to explicitly unique function body nodes -- they're unique already.
index a6f245d..3785214 100644 (file)
@@ -298,6 +298,7 @@ namespace JSC {
         PassRefPtr<Label> emitJump(Label* target);
         PassRefPtr<Label> emitJumpIfTrue(RegisterID* cond, Label* target);
         PassRefPtr<Label> emitJumpIfFalse(RegisterID* cond, Label* target);
+        PassRefPtr<Label> emitJumpIfNotFunctionCall(RegisterID* cond, Label* target);
         PassRefPtr<Label> emitJumpScopes(Label* target, int targetScopeDepth);
 
         PassRefPtr<Label> emitJumpSubroutine(RegisterID* retAddrDst, Label*);
index 5ff3c35..431a19c 100644 (file)
@@ -2666,6 +2666,24 @@ JSValuePtr Interpreter::privateExecute(ExecutionFlag flag, RegisterFile* registe
         ++vPC;
         NEXT_INSTRUCTION();
     }
+    DEFINE_OPCODE(op_jneq_ptr) {
+        /* jneq_ptr src(r) ptr(jsCell) target(offset)
+         
+           Jumps to offset target from the current instruction, if the value r is equal
+           to ptr, using pointer equality.
+         */
+        int src = (++vPC)->u.operand;
+        JSValuePtr ptr = JSValuePtr((++vPC)->u.jsCell);
+        int target = (++vPC)->u.operand;
+        JSValuePtr srcValue = callFrame[src].jsValue(callFrame);
+        if (srcValue != ptr) {
+            vPC += target;
+            NEXT_INSTRUCTION();
+        }
+
+        ++vPC;
+        NEXT_INSTRUCTION();
+    }
     DEFINE_OPCODE(op_loop_if_less) {
         /* loop_if_less src1(r) src2(r) target(offset)
 
index e6113fc..12ef003 100644 (file)
@@ -819,6 +819,17 @@ void JIT::privateCompileMainPass()
             RECORD_JUMP_TARGET(target + 2);
             NEXT_OPCODE(op_jneq_null);
         }
+        case op_jneq_ptr: {
+            unsigned src = currentInstruction[1].u.operand;
+            JSCell* ptr = currentInstruction[2].u.jsCell;
+            unsigned target = currentInstruction[3].u.operand;
+            
+            emitGetVirtualRegister(src, regT0);
+            addJump(branchPtr(NotEqual, regT0, ImmPtr(JSValuePtr::encode(JSValuePtr(ptr)))), target + 3);            
+
+            RECORD_JUMP_TARGET(target + 3);
+            NEXT_OPCODE(op_jneq_ptr);
+        }
         case op_post_inc: {
             compileFastArith_op_post_inc(currentInstruction[1].u.operand, currentInstruction[2].u.operand);
             NEXT_OPCODE(op_post_inc);
index ae787f6..a0c2e3c 100644 (file)
@@ -1925,7 +1925,11 @@ static ExpressionNodeInfo makeFunctionCallNode(void* globalPtr, ExpressionNodeIn
     }
     ASSERT(func.m_node->isDotAccessorNode());
     DotAccessorNode* dot = static_cast<DotAccessorNode*>(func.m_node);
-    FunctionCallDotNode* node = new FunctionCallDotNode(GLOBAL_DATA, dot->base(), dot->identifier(), args.m_node, divot, divot - start, end - divot);
+    FunctionCallDotNode* node;
+    if (dot->identifier() == GLOBAL_DATA->propertyNames->call)
+        node = new CallFunctionCallDotNode(GLOBAL_DATA, dot->base(), dot->identifier(), args.m_node, divot, divot - start, end - divot);
+    else
+        node = new FunctionCallDotNode(GLOBAL_DATA, dot->base(), dot->identifier(), args.m_node, divot, divot - start, end - divot);
     node->setSubexpressionInfo(dot->divot(), dot->endOffset());
     return createNodeInfo<ExpressionNode*>(node, features, numConstants);
 }
index 52a8baf..f461bbf 100644 (file)
@@ -694,6 +694,38 @@ RegisterID* FunctionCallDotNode::emitBytecode(BytecodeGenerator& generator, Regi
     return generator.emitCall(generator.finalDestination(dst, function.get()), function.get(), thisRegister.get(), m_args.get(), divot(), startOffset(), endOffset());
 }
 
+RegisterID* CallFunctionCallDotNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
+{
+    RefPtr<Label> realCall = generator.newLabel();
+    RefPtr<Label> end = generator.newLabel();
+    RefPtr<RegisterID> base = generator.emitNode(m_base.get());
+    generator.emitExpressionInfo(divot() - m_subexpressionDivotOffset, startOffset() - m_subexpressionDivotOffset, m_subexpressionEndOffset);
+    RefPtr<RegisterID> function = generator.emitGetById(generator.tempDestination(dst), base.get(), m_ident);
+    RefPtr<RegisterID> finalDestination = generator.finalDestination(dst, function.get());
+    generator.emitJumpIfNotFunctionCall(function.get(), realCall.get());
+    {
+        RefPtr<RegisterID> realFunction = generator.emitMove(generator.tempDestination(dst), base.get());
+        RefPtr<RegisterID> thisRegister = generator.newTemporary();
+        RefPtr<ArgumentListNode> oldList = m_args->m_listNode;
+        if (m_args->m_listNode && m_args->m_listNode->m_expr) {
+            generator.emitNode(thisRegister.get(), m_args->m_listNode->m_expr.get());
+            m_args->m_listNode = m_args->m_listNode->m_next;
+        } else {
+            generator.emitLoad(thisRegister.get(), jsNull());
+        }
+        generator.emitCall(finalDestination.get(), realFunction.get(), thisRegister.get(), m_args.get(), divot(), startOffset(), endOffset());
+        generator.emitJump(end.get());
+        m_args->m_listNode = oldList;
+    }
+    generator.emitLabel(realCall.get());
+    {
+        RefPtr<RegisterID> thisRegister = generator.emitMove(generator.newTemporary(), base.get());
+        generator.emitCall(finalDestination.get(), function.get(), thisRegister.get(), m_args.get(), divot(), startOffset(), endOffset());
+    }
+    generator.emitLabel(end.get());
+    return finalDestination.get();
+}
+
 // ------------------------------ PostfixResolveNode ----------------------------------
 
 static RegisterID* emitPreIncOrDec(BytecodeGenerator& generator, RegisterID* srcDst, Operator oper)
index d84dedb..7e9a39c 100644 (file)
@@ -763,12 +763,21 @@ namespace JSC {
 
         virtual RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) JSC_FAST_CALL;
 
-    private:
+    protected:
         RefPtr<ExpressionNode> m_base;
         Identifier m_ident;
         RefPtr<ArgumentsNode> m_args;
     };
 
+    class CallFunctionCallDotNode : public FunctionCallDotNode {
+    public:
+        CallFunctionCallDotNode(JSGlobalData* globalData, ExpressionNode* base, const Identifier& ident, ArgumentsNode* args, unsigned divot, unsigned startOffset, unsigned endOffset) JSC_FAST_CALL
+            : FunctionCallDotNode(globalData, base, ident, args, divot, startOffset, endOffset)
+        {
+        }
+        virtual RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) JSC_FAST_CALL;
+    };
+
     class PrePostResolveNode : public ExpressionNode, public ThrowableExpressionData {
     public:
         PrePostResolveNode(JSGlobalData* globalData, const Identifier& ident, unsigned divot, unsigned startOffset, unsigned endOffset) JSC_FAST_CALL
index 01fc57c..40e123b 100644 (file)
@@ -43,11 +43,12 @@ FunctionPrototype::FunctionPrototype(ExecState* exec, PassRefPtr<Structure> stru
     putDirectWithoutTransition(exec->propertyNames().length, jsNumber(exec, 0), DontDelete | ReadOnly | DontEnum);
 }
 
-void FunctionPrototype::addFunctionProperties(ExecState* exec, Structure* prototypeFunctionStructure)
+void FunctionPrototype::addFunctionProperties(ExecState* exec, Structure* prototypeFunctionStructure, PrototypeFunction** callFunction)
 {
     putDirectFunctionWithoutTransition(exec, new (exec) PrototypeFunction(exec, prototypeFunctionStructure, 0, exec->propertyNames().toString, functionProtoFuncToString), DontEnum);
     putDirectFunctionWithoutTransition(exec, new (exec) PrototypeFunction(exec, prototypeFunctionStructure, 2, exec->propertyNames().apply, functionProtoFuncApply), DontEnum);
-    putDirectFunctionWithoutTransition(exec, new (exec) PrototypeFunction(exec, prototypeFunctionStructure, 1, exec->propertyNames().call, functionProtoFuncCall), DontEnum);
+    *callFunction = new (exec) PrototypeFunction(exec, prototypeFunctionStructure, 1, exec->propertyNames().call, functionProtoFuncCall);
+    putDirectFunctionWithoutTransition(exec, *callFunction, DontEnum);
 }
 
 static JSValuePtr callFunctionPrototype(ExecState*, JSObject*, JSValuePtr, const ArgList&)
index 33d68b7..613fd56 100644 (file)
 
 namespace JSC {
 
+    class PrototypeFunction;
+
     class FunctionPrototype : public InternalFunction {
     public:
         FunctionPrototype(ExecState*, PassRefPtr<Structure>);
-        void addFunctionProperties(ExecState*, Structure* prototypeFunctionStructure);
+        void addFunctionProperties(ExecState*, Structure* prototypeFunctionStructure, PrototypeFunction** callFunction);
 
         static PassRefPtr<Structure> createStructure(JSValuePtr proto)
         {
index d6ad295..61ed8c7 100644 (file)
@@ -203,7 +203,9 @@ void JSGlobalObject::reset(JSValuePtr prototype)
 
     d()->functionPrototype = new (exec) FunctionPrototype(exec, FunctionPrototype::createStructure(jsNull())); // The real prototype will be set once ObjectPrototype is created.
     d()->prototypeFunctionStructure = PrototypeFunction::createStructure(d()->functionPrototype);
-    d()->functionPrototype->addFunctionProperties(exec, d()->prototypeFunctionStructure.get());
+    PrototypeFunction* callFunction = 0;
+    d()->functionPrototype->addFunctionProperties(exec, d()->prototypeFunctionStructure.get(), &callFunction);
+    d()->callFunction = callFunction;
     d()->objectPrototype = new (exec) ObjectPrototype(exec, ObjectPrototype::createStructure(jsNull()), d()->prototypeFunctionStructure.get());
     d()->functionPrototype->structure()->setPrototypeWithoutTransition(d()->objectPrototype);
 
@@ -370,6 +372,7 @@ void JSGlobalObject::mark()
     markIfNeeded(d()->URIErrorConstructor);
 
     markIfNeeded(d()->evalFunction);
+    markIfNeeded(d()->callFunction);
 
     markIfNeeded(d()->objectPrototype);
     markIfNeeded(d()->functionPrototype);
index ceb7bc2..1ec9646 100644 (file)
@@ -40,6 +40,7 @@ namespace JSC {
     class GlobalEvalFunction;
     class NativeErrorConstructor;
     class ProgramCodeBlock;
+    class PrototypeFunction;
     class RegExpConstructor;
     class RegExpPrototype;
     class RegisterFile;
@@ -104,6 +105,7 @@ namespace JSC {
             NativeErrorConstructor* URIErrorConstructor;
 
             GlobalEvalFunction* evalFunction;
+            PrototypeFunction* callFunction;
 
             ObjectPrototype* objectPrototype;
             FunctionPrototype* functionPrototype;
index 3bcddf7..12cc658 100644 (file)
@@ -1,3 +1,14 @@
+2009-03-27  Oliver Hunt  <oliver@apple.com>
+
+        Reviewed by Gavin Barraclough.
+
+        Tests to verify behaviour of Function.prototype.call
+
+        * fast/js/function-call-aliased-expected.txt: Added.
+        * fast/js/function-call-aliased.html: Added.
+        * fast/js/resources/function-call-aliased.js: Added.
+        * fast/profiler/call-expected.txt:
+
 2009-03-27  Mark Rowe  <mrowe@apple.com>
 
         Rubber-stamped by Oliver Hunt.
diff --git a/LayoutTests/fast/js/function-call-aliased-expected.txt b/LayoutTests/fast/js/function-call-aliased-expected.txt
new file mode 100644 (file)
index 0000000..e1ec5f6
--- /dev/null
@@ -0,0 +1,21 @@
+This tests that we can correctly call Function.prototype.call
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS myObject.call() is [myObject, "myObject.call"]
+PASS myFunction('arg1') is [this, "myFunction", "arg1"]
+PASS myFunction.call(myObject, 'arg1') is [myObject, "myFunction", "arg1"]
+PASS myFunction.call() is [this, "myFunction", undefined]
+PASS myFunction.call(null) is [this, "myFunction", undefined]
+PASS myFunction.call(undefined) is [this, "myFunction", undefined]
+PASS myFunction.aliasedCall(myObject, 'arg1') is [myObject, "myFunction", "arg1"]
+PASS myFunction.aliasedCall() is [this, "myFunction", undefined]
+PASS myFunction.aliasedCall(null) is [this, "myFunction", undefined]
+PASS myFunction.aliasedCall(undefined) is [this, "myFunction", undefined]
+PASS myFunctionWithCall.call(myObject, 'arg1') is [myFunctionWithCall, "myFunctionWithCall.call", myObject]
+PASS myFunctionWithCall.aliasedCall(myObject, 'arg1') is [myObject, "myFunctionWithCall", "arg1"]
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/js/function-call-aliased.html b/LayoutTests/fast/js/function-call-aliased.html
new file mode 100644 (file)
index 0000000..cd5b6a0
--- /dev/null
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="resources/js-test-style.css">
+<script src="resources/js-test-pre.js"></script>
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script src="resources/function-call-aliased.js" type="text/javascript" charset="utf-8"></script>
+<script src="resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/js/resources/function-call-aliased.js b/LayoutTests/fast/js/resources/function-call-aliased.js
new file mode 100644 (file)
index 0000000..3f6109b
--- /dev/null
@@ -0,0 +1,24 @@
+description(
+"This tests that we can correctly call Function.prototype.call"
+);
+
+var myObject = { call: function() { return [myObject, "myObject.call"] } };
+var myFunction = function (arg1) { return [this, "myFunction", arg1] };
+var myFunctionWithCall = function (arg1) { return [this, "myFunctionWithCall", arg1] };
+myFunctionWithCall.call = function (arg1) { return [this, "myFunctionWithCall.call", arg1] };
+Function.prototype.aliasedCall = Function.prototype.call;
+
+shouldBe("myObject.call()", '[myObject, "myObject.call"]');
+shouldBe("myFunction('arg1')", '[this, "myFunction", "arg1"]');
+shouldBe("myFunction.call(myObject, 'arg1')", '[myObject, "myFunction", "arg1"]');
+shouldBe("myFunction.call()", '[this, "myFunction", undefined]');
+shouldBe("myFunction.call(null)", '[this, "myFunction", undefined]');
+shouldBe("myFunction.call(undefined)", '[this, "myFunction", undefined]');
+shouldBe("myFunction.aliasedCall(myObject, 'arg1')", '[myObject, "myFunction", "arg1"]');
+shouldBe("myFunction.aliasedCall()", '[this, "myFunction", undefined]');
+shouldBe("myFunction.aliasedCall(null)", '[this, "myFunction", undefined]');
+shouldBe("myFunction.aliasedCall(undefined)", '[this, "myFunction", undefined]');
+shouldBe("myFunctionWithCall.call(myObject, 'arg1')", '[myFunctionWithCall, "myFunctionWithCall.call", myObject]');
+shouldBe("myFunctionWithCall.aliasedCall(myObject, 'arg1')", '[myObject, "myFunctionWithCall", "arg1"]');
+
+var successfullyParsed = true;
index 961d5fb..fc2c302 100644 (file)
@@ -6,8 +6,7 @@ Profile title: Using the call() method
 Thread_1 (no file) (line 0)
    startTest call.html (line 11)
       fakeObject call.html (line 20)
-         call (no file) (line 0)
-            fakeInteriorFunction call.html (line 26)
+         fakeInteriorFunction call.html (line 26)
       endTest profiler-test-JS-resources.js (line 1)