Optimise multi-scope function call resolution
authoroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 18 Mar 2008 05:36:26 +0000 (05:36 +0000)
committeroliver@apple.com <oliver@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 18 Mar 2008 05:36:26 +0000 (05:36 +0000)
Reviewed by Geoff

Refactor multiscope variable resolution and use to add
optimised FunctionCallResolveNode subclasses.

2.6% gain in sunspider performance, *25%* gain in controlflow-recursive

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

JavaScriptCore/ChangeLog
JavaScriptCore/kjs/nodes.cpp
JavaScriptCore/kjs/nodes.h

index ff5b811..95f9d5a 100644 (file)
@@ -1,3 +1,37 @@
+2008-03-17  Oliver Hunt  <oliver@apple.com>
+
+        Reviewed by Geoff.
+
+        Optimise multi-scope function call resolution
+
+        Refactor multiscope variable resolution and use to add
+        optimised FunctionCallResolveNode subclasses.  
+
+        2.6% gain in sunspider performance, *25%* gain in controlflow-recursive
+
+        * kjs/nodes.cpp:
+        (KJS::getSymbolTableEntry):
+        (KJS::ResolveNode::optimizeVariableAccess):
+        (KJS::getNonLocalSymbol):
+        (KJS::ExpressionNode::resolveAndCall):
+        (KJS::FunctionCallResolveNode::optimizeVariableAccess):
+        (KJS::FunctionCallResolveNode::inlineEvaluate):
+        (KJS::ScopedVarFunctionCallNode::inlineEvaluate):
+        (KJS::ScopedVarFunctionCallNode::evaluate):
+        (KJS::ScopedVarFunctionCallNode::evaluateToNumber):
+        (KJS::ScopedVarFunctionCallNode::evaluateToBoolean):
+        (KJS::ScopedVarFunctionCallNode::evaluateToInt32):
+        (KJS::ScopedVarFunctionCallNode::evaluateToUInt32):
+        (KJS::NonLocalVarFunctionCallNode::inlineEvaluate):
+        (KJS::NonLocalVarFunctionCallNode::evaluate):
+        (KJS::NonLocalVarFunctionCallNode::evaluateToNumber):
+        (KJS::NonLocalVarFunctionCallNode::evaluateToBoolean):
+        (KJS::NonLocalVarFunctionCallNode::evaluateToInt32):
+        (KJS::NonLocalVarFunctionCallNode::evaluateToUInt32):
+        * kjs/nodes.h:
+        (KJS::ScopedVarFunctionCallNode::):
+        (KJS::NonLocalVarFunctionCallNode::):
+
 2008-03-17  David Kilzer  <ddkilzer@apple.com>
 
         Don't define PLATFORM(MIDDLE_ENDIAN) on little endian ARM.
index 2671253..25817b3 100644 (file)
@@ -593,16 +593,19 @@ uint32_t ResolveNode::evaluateToUInt32(ExecState* exec)
     return v->toUInt32(exec);
 }
 
-void ResolveNode::optimizeVariableAccess(ExecState* exec, const SymbolTable& symbolTable, const LocalStorage&, NodeStack&)
+static size_t getSymbolTableEntry(ExecState* exec, const Identifier& ident, const SymbolTable& symbolTable, size_t& stackDepth) 
 {
-    size_t index = symbolTable.get(m_ident.ustring().rep());
+    size_t index = symbolTable.get(ident.ustring().rep());
     if (index != missingSymbolMarker()) {
-        new (this) LocalVarAccessNode(index);
-        return;
+        stackDepth = 0;
+        return index;
+    }
+    
+    if (ident == exec->propertyNames().arguments) {
+        stackDepth = 0;
+        return missingSymbolMarker();
     }
     
-    if (m_ident == exec->propertyNames().arguments)
-        return;
     const ScopeChain& chain = exec->scopeChain();
     ScopeChainIterator iter = chain.begin();
     ScopeChainIterator end = chain.end();
@@ -612,16 +615,34 @@ void ResolveNode::optimizeVariableAccess(ExecState* exec, const SymbolTable& sym
         if (!currentScope->isVariableObject()) 
             break;
         JSVariableObject* currentVariableObject = static_cast<JSVariableObject*>(currentScope);
-        index = currentVariableObject->symbolTable().get(m_ident.ustring().rep());
+        index = currentVariableObject->symbolTable().get(ident.ustring().rep());
         if (index != missingSymbolMarker()) {
-            new (this) ScopedVarAccessNode(index, depth);
-            return;
+            stackDepth = depth;
+            return index;
         }
         if (currentVariableObject->isDynamicScope())
             break;
     }
-    if (depth > 0)
-        new (this) NonLocalVarAccessNode(depth);
+    stackDepth = depth;
+    return missingSymbolMarker();
+}
+
+void ResolveNode::optimizeVariableAccess(ExecState* exec, const SymbolTable& symbolTable, const LocalStorage&, NodeStack&)
+{
+    size_t depth = 0;
+    size_t index = getSymbolTableEntry(exec, m_ident, symbolTable, depth);
+    if (index != missingSymbolMarker()) {
+        if (!depth)
+            new (this) LocalVarAccessNode(index);
+        else
+            new (this) ScopedVarAccessNode(index, depth);
+        return;
+    }
+
+    if (!depth)
+        return;
+
+    new (this) NonLocalVarAccessNode(depth);
 }
 
 JSValue* LocalVarAccessNode::inlineEvaluate(ExecState* exec)
@@ -655,16 +676,21 @@ uint32_t LocalVarAccessNode::evaluateToUInt32(ExecState* exec)
     return inlineEvaluate(exec)->toUInt32(exec);
 }
 
-JSValue* ScopedVarAccessNode::inlineEvaluate(ExecState* exec)
+static inline JSValue* getNonLocalSymbol(ExecState* exec, size_t index, size_t scopeDepth)
 {
     const ScopeChain& chain = exec->scopeChain();
     ScopeChainIterator iter = chain.begin();
-    for (size_t i = 0; i < m_scopeDepth; ++iter, ++i)
+    for (size_t i = 0; i < scopeDepth; ++iter, ++i)
         ASSERT(iter != chain.end());
     JSObject* scope = *iter;
-    ASSERT(scope->isActivationObject() || scope->isGlobalObject());
+    ASSERT(scope->isVariableObject());
     JSVariableObject* variableObject = static_cast<JSVariableObject*>(scope);
-    return variableObject->localStorage()[m_index].value;
+    return variableObject->localStorage()[index].value;
+}
+
+JSValue* ScopedVarAccessNode::inlineEvaluate(ExecState* exec)
+{
+    return getNonLocalSymbol(exec, m_index, m_scopeDepth);
 }
 
 JSValue* ScopedVarAccessNode::evaluate(ExecState* exec)
@@ -1061,13 +1087,18 @@ uint32_t NewExprNode::evaluateToUInt32(ExecState* exec)
     return v->toUInt32(exec);
 }
 
-template <ExpressionNode::CallerType callerType> 
-inline JSValue* ExpressionNode::resolveAndCall(ExecState* exec, const Identifier& ident, ArgumentsNode* args)
+template <ExpressionNode::CallerType callerType, bool scopeDepthIsZero
+inline JSValue* ExpressionNode::resolveAndCall(ExecState* exec, const Identifier& ident, ArgumentsNode* args, size_t scopeDepth)
 {
     const ScopeChain& chain = exec->scopeChain();
     ScopeChainIterator iter = chain.begin();
     ScopeChainIterator end = chain.end();
 
+    if (!scopeDepthIsZero) {
+        for (size_t i = 0; i < scopeDepth; ++iter, ++i)
+            ASSERT(iter != chain.end());
+    }
+    
     // we must always have something in the scope chain
     ASSERT(iter != end);
 
@@ -1122,7 +1153,7 @@ void EvalFunctionCallNode::optimizeVariableAccess(ExecState*, const SymbolTable&
 
 JSValue* EvalFunctionCallNode::evaluate(ExecState* exec)
 {
-    return resolveAndCall<EvalOperator>(exec, exec->propertyNames().eval, m_args.get());
+    return resolveAndCall<EvalOperator, true>(exec, exec->propertyNames().eval, m_args.get());
 }
 
 void FunctionCallValueNode::optimizeVariableAccess(ExecState*, const SymbolTable&, const LocalStorage&, NodeStack& nodeStack)
@@ -1156,13 +1187,24 @@ JSValue* FunctionCallValueNode::evaluate(ExecState* exec)
     return func->call(exec, thisObj, argList);
 }
 
-void FunctionCallResolveNode::optimizeVariableAccess(ExecState*, const SymbolTable& symbolTable, const LocalStorage&, NodeStack& nodeStack)
+void FunctionCallResolveNode::optimizeVariableAccess(ExecState* exec, const SymbolTable& symbolTable, const LocalStorage&, NodeStack& nodeStack)
 {
     nodeStack.append(m_args.get());
 
-    size_t index = symbolTable.get(m_ident.ustring().rep());
-    if (index != missingSymbolMarker())
-        new (this) LocalVarFunctionCallNode(index);
+    size_t depth = 0;
+    size_t index = getSymbolTableEntry(exec, m_ident, symbolTable, depth);
+    if (index != missingSymbolMarker()) {
+        if (!depth)
+            new (this) LocalVarFunctionCallNode(index);
+        else
+            new (this) ScopedVarFunctionCallNode(index, depth);
+        return;
+    }
+    
+    if (!depth)
+        return;
+    
+    new (this) NonLocalVarFunctionCallNode(depth);
 }
 
 // ECMA 11.2.3
@@ -1171,7 +1213,7 @@ JSValue* FunctionCallResolveNode::inlineEvaluate(ExecState* exec)
     // Check for missed optimization opportunity.
     ASSERT(!canSkipLookup(exec, m_ident));
 
-    return resolveAndCall<FunctionCall>(exec, m_ident, m_args.get());
+    return resolveAndCall<FunctionCall, true>(exec, m_ident, m_args.get());
 }
 
 JSValue* FunctionCallResolveNode::evaluate(ExecState* exec)
@@ -1260,6 +1302,100 @@ uint32_t LocalVarFunctionCallNode::evaluateToUInt32(ExecState* exec)
     return v->toUInt32(exec);
 }
 
+JSValue* ScopedVarFunctionCallNode::inlineEvaluate(ExecState* exec)
+{
+    ASSERT(exec->variableObject() == exec->scopeChain().top());
+    
+    JSValue* v = getNonLocalSymbol(exec, m_index, m_scopeDepth);
+    
+    if (!v->isObject())
+        return throwError(exec, TypeError, "Value %s (result of expression %s) is not object.", v, m_ident);
+    
+    JSObject* func = static_cast<JSObject*>(v);
+    if (!func->implementsCall())
+        return throwError(exec, TypeError, "Object %s (result of expression %s) does not allow calls.", v, m_ident);
+    
+    List argList;
+    m_args->evaluateList(exec, argList);
+    KJS_CHECKEXCEPTIONVALUE
+    
+    return func->call(exec, exec->dynamicGlobalObject(), argList);
+}
+
+JSValue* ScopedVarFunctionCallNode::evaluate(ExecState* exec)
+{
+    return inlineEvaluate(exec);
+}
+
+double ScopedVarFunctionCallNode::evaluateToNumber(ExecState* exec)
+{
+    JSValue* v = inlineEvaluate(exec);
+    KJS_CHECKEXCEPTIONNUMBER
+    return v->toNumber(exec);
+}
+
+bool ScopedVarFunctionCallNode::evaluateToBoolean(ExecState* exec)
+{
+    JSValue* v = inlineEvaluate(exec);
+    KJS_CHECKEXCEPTIONBOOLEAN
+    return v->toBoolean(exec);
+}
+
+int32_t ScopedVarFunctionCallNode::evaluateToInt32(ExecState* exec)
+{
+    JSValue* v = inlineEvaluate(exec);
+    KJS_CHECKEXCEPTIONNUMBER
+    return v->toInt32(exec);
+}
+
+uint32_t ScopedVarFunctionCallNode::evaluateToUInt32(ExecState* exec)
+{
+    JSValue* v = inlineEvaluate(exec);
+    KJS_CHECKEXCEPTIONNUMBER
+    return v->toUInt32(exec);
+}
+
+JSValue* NonLocalVarFunctionCallNode::inlineEvaluate(ExecState* exec)
+{
+    // Check for missed optimization opportunity.
+    ASSERT(!canSkipLookup(exec, m_ident));
+    
+    return resolveAndCall<FunctionCall, false>(exec, m_ident, m_args.get(), m_scopeDepth);
+}
+
+JSValue* NonLocalVarFunctionCallNode::evaluate(ExecState* exec)
+{
+    return inlineEvaluate(exec);
+}
+
+double NonLocalVarFunctionCallNode::evaluateToNumber(ExecState* exec)
+{
+    JSValue* v = inlineEvaluate(exec);
+    KJS_CHECKEXCEPTIONNUMBER
+    return v->toNumber(exec);
+}
+
+bool NonLocalVarFunctionCallNode::evaluateToBoolean(ExecState* exec)
+{
+    JSValue* v = inlineEvaluate(exec);
+    KJS_CHECKEXCEPTIONBOOLEAN
+    return v->toBoolean(exec);
+}
+
+int32_t NonLocalVarFunctionCallNode::evaluateToInt32(ExecState* exec)
+{
+    JSValue* v = inlineEvaluate(exec);
+    KJS_CHECKEXCEPTIONNUMBER
+    return v->toInt32(exec);
+}
+
+uint32_t NonLocalVarFunctionCallNode::evaluateToUInt32(ExecState* exec)
+{
+    JSValue* v = inlineEvaluate(exec);
+    KJS_CHECKEXCEPTIONNUMBER
+    return v->toUInt32(exec);
+}
+
 void FunctionCallBracketNode::optimizeVariableAccess(ExecState*, const SymbolTable&, const LocalStorage&, NodeStack& nodeStack)
 {
     nodeStack.append(m_args.get());
index d7058d7..89bd2a6 100644 (file)
@@ -209,7 +209,7 @@ namespace KJS {
 
     protected:
         typedef enum { EvalOperator, FunctionCall } CallerType;
-        template <CallerType> inline JSValue* resolveAndCall(ExecState*, const Identifier&, ArgumentsNode*);
+        template <CallerType, bool> inline JSValue* resolveAndCall(ExecState*, const Identifier&, ArgumentsNode*, size_t = 0);
     };
 
     class StatementNode : public Node {
@@ -791,8 +791,9 @@ namespace KJS {
         Identifier m_ident;
         RefPtr<ArgumentsNode> m_args;
         size_t m_index; // Used by LocalVarFunctionCallNode.
+        size_t m_scopeDepth; // Used by ScopedVarFunctionCallNode and NonLocalVarFunctionCallNode
     };
-
+    
     class LocalVarFunctionCallNode : public FunctionCallResolveNode {
     public:
         LocalVarFunctionCallNode(size_t i) KJS_FAST_CALL
@@ -801,13 +802,51 @@ namespace KJS {
             ASSERT(i != missingSymbolMarker());
             m_index = i;
         }
-
+        
         virtual JSValue* evaluate(ExecState*) KJS_FAST_CALL;
         virtual bool evaluateToBoolean(ExecState*) KJS_FAST_CALL;
         virtual double evaluateToNumber(ExecState*) KJS_FAST_CALL;
         virtual int32_t evaluateToInt32(ExecState*) KJS_FAST_CALL;
         virtual uint32_t evaluateToUInt32(ExecState*) KJS_FAST_CALL;
-
+        
+    private:
+        ALWAYS_INLINE JSValue* inlineEvaluate(ExecState*);
+    };
+    
+    class ScopedVarFunctionCallNode : public FunctionCallResolveNode {
+    public:
+        ScopedVarFunctionCallNode(size_t i, size_t depth) KJS_FAST_CALL
+            : FunctionCallResolveNode(PlacementNewAdopt)
+        {
+            ASSERT(i != missingSymbolMarker());
+            m_index = i;
+            m_scopeDepth = depth;
+        }
+        
+        virtual JSValue* evaluate(ExecState*) KJS_FAST_CALL;
+        virtual bool evaluateToBoolean(ExecState*) KJS_FAST_CALL;
+        virtual double evaluateToNumber(ExecState*) KJS_FAST_CALL;
+        virtual int32_t evaluateToInt32(ExecState*) KJS_FAST_CALL;
+        virtual uint32_t evaluateToUInt32(ExecState*) KJS_FAST_CALL;
+        
+    private:
+        ALWAYS_INLINE JSValue* inlineEvaluate(ExecState*);
+    };
+    
+    class NonLocalVarFunctionCallNode : public FunctionCallResolveNode {
+    public:
+        NonLocalVarFunctionCallNode(size_t depth) KJS_FAST_CALL
+            : FunctionCallResolveNode(PlacementNewAdopt)
+        {
+            m_scopeDepth = depth;
+        }
+        
+        virtual JSValue* evaluate(ExecState*) KJS_FAST_CALL;
+        virtual bool evaluateToBoolean(ExecState*) KJS_FAST_CALL;
+        virtual double evaluateToNumber(ExecState*) KJS_FAST_CALL;
+        virtual int32_t evaluateToInt32(ExecState*) KJS_FAST_CALL;
+        virtual uint32_t evaluateToUInt32(ExecState*) KJS_FAST_CALL;
+        
     private:
         ALWAYS_INLINE JSValue* inlineEvaluate(ExecState*);
     };