Title: [208404] trunk
Revision
208404
Author
[email protected]
Date
2016-11-04 16:02:19 -0700 (Fri, 04 Nov 2016)

Log Message

EvalCodeCache should not give up in strict mode and other cases
https://bugs.webkit.org/show_bug.cgi?id=164357

Reviewed by Michael Saboff.

JSTests:

* microbenchmarks/eval-cached.js: Added. 45x faster now.
* stress/eval-cached.js: Added. Try running the same eval text in a bunch
of different scopes and verify that we access the right scope.

Source/_javascript_Core:

EvalCodeCache gives up in non-trivial cases because generated eval code
can't soundly migrate from, for example, a let scope to a non-let scope.
The number of cases has grown over time.

Instead, let's cache eval code based on the location of the call to
eval(). That way, we never relocate the code, and it's sound to make
normal assumptions about our surrounding scope.

* bytecode/EvalCodeCache.h:
(JSC::EvalCodeCache::CacheKey::CacheKey): Use CallSiteIndex to uniquely
identify the location of our call to eval().

(JSC::EvalCodeCache::CacheKey::hash):
(JSC::EvalCodeCache::CacheKey::operator==):
(JSC::EvalCodeCache::CacheKey::Hash::equal): Use CallSiteIndex instead
of lots of other flags.

(JSC::EvalCodeCache::tryGet): No need to include details that are implied
by our CallSiteIndex.

(JSC::EvalCodeCache::getSlow): No need to skip caching in complex
situations. We promise we'll never relocate the cached code.

(JSC::EvalCodeCache::isCacheableScope): Deleted.
(JSC::EvalCodeCache::isCacheable): Deleted.

* interpreter/Interpreter.cpp:
(JSC::eval): Pass through a CallSiteIndex to uniquely identify this call
to eval().

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (208403 => 208404)


--- trunk/JSTests/ChangeLog	2016-11-04 22:53:33 UTC (rev 208403)
+++ trunk/JSTests/ChangeLog	2016-11-04 23:02:19 UTC (rev 208404)
@@ -1,3 +1,14 @@
+2016-11-03  Geoffrey Garen  <[email protected]>
+
+        EvalCodeCache should not give up in strict mode and other cases
+        https://bugs.webkit.org/show_bug.cgi?id=164357
+
+        Reviewed by Michael Saboff.
+
+        * microbenchmarks/eval-cached.js: Added. 45x faster now.
+        * stress/eval-cached.js: Added. Try running the same eval text in a bunch
+        of different scopes and verify that we access the right scope.
+
 2016-11-04  JF Bastien  <[email protected]>
 
         WebAssembly JS API: implement more sections

Added: trunk/JSTests/microbenchmarks/eval-cached.js (0 => 208404)


--- trunk/JSTests/microbenchmarks/eval-cached.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/eval-cached.js	2016-11-04 23:02:19 UTC (rev 208404)
@@ -0,0 +1,14 @@
+function foo(string) {
+    let result = 42;
+    for (let i = 0; i < 1000; ++i)
+        eval(string);
+    return result;
+}
+
+noInline(foo);
+
+for (let i = 0; i < 200; ++i) {
+    let result = foo("++result");
+    if (result != 42 + 1000)
+        throw "Error: bad result: " + result;
+}

Added: trunk/JSTests/stress/eval-cached.js (0 => 208404)


--- trunk/JSTests/stress/eval-cached.js	                        (rev 0)
+++ trunk/JSTests/stress/eval-cached.js	2016-11-04 23:02:19 UTC (rev 208404)
@@ -0,0 +1,79 @@
+(function () {
+    "use strict";
+
+    function verify() {
+        for (var i = 0; i < counter; ++i) {
+            if (results[i] != i)
+                throw "strict mode verify() failed for item " + i + "."
+        }
+    }
+
+    let results = [ ];
+    let counter = 0;
+
+    let x = counter++;
+    results.push(eval("x"));
+
+    {
+        let x = counter++;
+        results.push(eval("x"));
+    }
+
+    try {
+        throw counter++;
+    } catch (x) {
+        results.push(eval("x"));
+    }
+
+    (() => {
+        var x = counter++;
+        results.push(eval("x"));
+    })();
+
+    (function (x) {
+        results.push(eval("x"));
+    })(counter++);
+
+    verify();
+})();
+
+(function () {
+    function verify() {
+        for (var i = 0; i < counter; ++i) {
+            if (results[i] != i)
+                throw "non-strict mode verify() failed for item " + i + "."
+        }
+    }
+
+    let results = [ ];
+    let counter = 0;
+
+    let x = counter++;
+    results.push(eval("x"));
+
+    {
+        let x = counter++;
+        results.push(eval("x"));
+    }
+
+    try {
+        throw counter++;
+    } catch (x) {
+        results.push(eval("x"));
+    }
+
+    (() => {
+        var x = counter++;
+        results.push(eval("x"));
+    })();
+
+    (function (x) {
+        results.push(eval("x"));
+    })(counter++);
+
+    with ({ x : counter++ }) {
+        results.push(eval("x"));
+    }
+
+    verify();
+})();

Modified: trunk/Source/_javascript_Core/ChangeLog (208403 => 208404)


--- trunk/Source/_javascript_Core/ChangeLog	2016-11-04 22:53:33 UTC (rev 208403)
+++ trunk/Source/_javascript_Core/ChangeLog	2016-11-04 23:02:19 UTC (rev 208404)
@@ -1,3 +1,40 @@
+2016-11-02  Geoffrey Garen  <[email protected]>
+
+        EvalCodeCache should not give up in strict mode and other cases
+        https://bugs.webkit.org/show_bug.cgi?id=164357
+
+        Reviewed by Michael Saboff.
+
+        EvalCodeCache gives up in non-trivial cases because generated eval code
+        can't soundly migrate from, for example, a let scope to a non-let scope.
+        The number of cases has grown over time.
+
+        Instead, let's cache eval code based on the location of the call to
+        eval(). That way, we never relocate the code, and it's sound to make
+        normal assumptions about our surrounding scope.
+
+        * bytecode/EvalCodeCache.h:
+        (JSC::EvalCodeCache::CacheKey::CacheKey): Use CallSiteIndex to uniquely
+        identify the location of our call to eval().
+
+        (JSC::EvalCodeCache::CacheKey::hash):
+        (JSC::EvalCodeCache::CacheKey::operator==):
+        (JSC::EvalCodeCache::CacheKey::Hash::equal): Use CallSiteIndex instead
+        of lots of other flags.
+
+        (JSC::EvalCodeCache::tryGet): No need to include details that are implied
+        by our CallSiteIndex.
+
+        (JSC::EvalCodeCache::getSlow): No need to skip caching in complex
+        situations. We promise we'll never relocate the cached code.
+
+        (JSC::EvalCodeCache::isCacheableScope): Deleted.
+        (JSC::EvalCodeCache::isCacheable): Deleted.
+
+        * interpreter/Interpreter.cpp:
+        (JSC::eval): Pass through a CallSiteIndex to uniquely identify this call
+        to eval().
+
 2016-11-04  Keith Miller  <[email protected]>
 
         Add support for Wasm br_table

Modified: trunk/Source/_javascript_Core/bytecode/EvalCodeCache.h (208403 => 208404)


--- trunk/Source/_javascript_Core/bytecode/EvalCodeCache.h	2016-11-04 22:53:33 UTC (rev 208403)
+++ trunk/Source/_javascript_Core/bytecode/EvalCodeCache.h	2016-11-04 23:02:19 UTC (rev 208404)
@@ -47,9 +47,9 @@
         // Specialized cache key (compared with SourceCodeKey) for eval code cache.
         class CacheKey {
         public:
-            CacheKey(const String& source, DerivedContextType derivedContextType, EvalContextType evalContextType, bool isArrowFunctionContext)
+            CacheKey(const String& source, CallSiteIndex callSiteIndex)
                 : m_source(source.impl())
-                , m_flags(SourceCodeType::EvalType, JSParserBuiltinMode::NotBuiltin, JSParserStrictMode::NotStrict, JSParserScriptMode::Classic, derivedContextType, evalContextType, isArrowFunctionContext)
+                , m_callSiteIndex(callSiteIndex)
             {
             }
 
@@ -60,13 +60,13 @@
 
             CacheKey() = default;
 
-            unsigned hash() const { return m_source->hash(); }
+            unsigned hash() const { return m_source->hash() ^ m_callSiteIndex.bits(); }
 
             bool isEmptyValue() const { return !m_source; }
 
             bool operator==(const CacheKey& other) const
             {
-                return m_source == other.m_source && m_flags == other.m_flags;
+                return m_callSiteIndex == other.m_callSiteIndex && m_source == other.m_source;
             }
 
             bool isHashTableDeletedValue() const { return m_source.isHashTableDeletedValue(); }
@@ -78,7 +78,7 @@
                 }
                 static bool equal(const CacheKey& lhs, const CacheKey& rhs)
                 {
-                    return StringHash::equal(lhs.m_source, rhs.m_source) && lhs.m_flags == rhs.m_flags;
+                    return lhs == rhs;
                 }
                 static const bool safeToCompareToEmptyOrDeleted = false;
             };
@@ -87,19 +87,15 @@
 
         private:
             RefPtr<StringImpl> m_source;
-            SourceCodeFlags m_flags;
+            CallSiteIndex m_callSiteIndex;
         };
 
-        EvalExecutable* tryGet(bool inStrictContext, const String& evalSource, DerivedContextType derivedContextType, EvalContextType evalContextType, bool isArrowFunctionContext, JSScope* scope)
+        EvalExecutable* tryGet(const String& evalSource, CallSiteIndex callSiteIndex)
         {
-            if (isCacheable(inStrictContext, evalSource, scope)) {
-                ASSERT(!inStrictContext);
-                return m_cacheMap.fastGet(CacheKey(evalSource, derivedContextType, evalContextType, isArrowFunctionContext)).get();
-            }
-            return nullptr;
+            return m_cacheMap.fastGet(CacheKey(evalSource, callSiteIndex)).get();
         }
         
-        EvalExecutable* getSlow(ExecState* exec, JSCell* owner, bool inStrictContext, DerivedContextType derivedContextType, EvalContextType evalContextType, bool isArrowFunctionContext, const String& evalSource, JSScope* scope)
+        EvalExecutable* getSlow(ExecState* exec, JSCell* owner, const String& evalSource, CallSiteIndex callSiteIndex, bool inStrictContext, DerivedContextType derivedContextType, EvalContextType evalContextType, bool isArrowFunctionContext, JSScope* scope)
         {
             VariableEnvironment variablesUnderTDZ;
             JSScope::collectVariablesUnderTDZ(scope, variablesUnderTDZ);
@@ -107,11 +103,9 @@
             if (!evalExecutable)
                 return nullptr;
 
-            if (isCacheable(inStrictContext, evalSource, scope) && m_cacheMap.size() < maxCacheEntries) {
-                ASSERT(!inStrictContext);
-                m_cacheMap.set(CacheKey(evalSource, derivedContextType, evalContextType, isArrowFunctionContext), WriteBarrier<EvalExecutable>(exec->vm(), owner, evalExecutable));
-            }
-            
+            if (m_cacheMap.size() < maxCacheEntries)
+                m_cacheMap.set(CacheKey(evalSource, callSiteIndex), WriteBarrier<EvalExecutable>(exec->vm(), owner, evalExecutable));
+
             return evalExecutable;
         }
 
@@ -125,19 +119,6 @@
         }
 
     private:
-        ALWAYS_INLINE bool isCacheableScope(JSScope* scope)
-        {
-            return scope->isGlobalLexicalEnvironment() || scope->isFunctionNameScopeObject() || scope->isVarScope();
-        }
-
-        ALWAYS_INLINE bool isCacheable(bool inStrictContext, const String& evalSource, JSScope* scope)
-        {
-            // If eval() is called and it has access to a lexical scope, we can't soundly cache it.
-            // If the eval() only has access to the "var" scope, then we can cache it.
-            return !inStrictContext 
-                && static_cast<size_t>(evalSource.length()) < Options::maximumEvalCacheableSourceLength()
-                && isCacheableScope(scope);
-        }
         static const int maxCacheEntries = 64;
 
         typedef HashMap<CacheKey, WriteBarrier<EvalExecutable>, CacheKey::Hash, CacheKey::HashTraits> EvalCacheMap;

Modified: trunk/Source/_javascript_Core/interpreter/CallFrame.h (208403 => 208404)


--- trunk/Source/_javascript_Core/interpreter/CallFrame.h	2016-11-04 22:53:33 UTC (rev 208403)
+++ trunk/Source/_javascript_Core/interpreter/CallFrame.h	2016-11-04 23:02:19 UTC (rev 208404)
@@ -57,6 +57,7 @@
 #endif
 
         explicit operator bool() const { return m_bits != UINT_MAX; }
+        bool operator==(const CallSiteIndex& other) const { return m_bits == other.m_bits; }
         
         inline uint32_t bits() const { return m_bits; }
 

Modified: trunk/Source/_javascript_Core/interpreter/Interpreter.cpp (208403 => 208404)


--- trunk/Source/_javascript_Core/interpreter/Interpreter.cpp	2016-11-04 22:53:33 UTC (rev 208403)
+++ trunk/Source/_javascript_Core/interpreter/Interpreter.cpp	2016-11-04 23:02:19 UTC (rev 208404)
@@ -109,6 +109,7 @@
     RETURN_IF_EXCEPTION(scope, JSValue());
     
     CallFrame* callerFrame = callFrame->callerFrame();
+    CallSiteIndex callerCallSiteIndex = callerFrame->callSiteIndex();
     CodeBlock* callerCodeBlock = callerFrame->codeBlock();
     JSScope* callerScopeChain = callerFrame->uncheckedR(callerCodeBlock->scopeRegister().offset()).Register::scope();
     UnlinkedCodeBlock* callerUnlinkedCodeBlock = callerCodeBlock->unlinkedCodeBlock();
@@ -130,7 +131,7 @@
     else
         evalContextType = EvalContextType::None;
 
-    EvalExecutable* eval = callerCodeBlock->evalCodeCache().tryGet(callerCodeBlock->isStrictMode(), programSource, derivedContextType, evalContextType, isArrowFunctionContext, callerScopeChain);
+    EvalExecutable* eval = callerCodeBlock->evalCodeCache().tryGet(programSource, callerCallSiteIndex);
     if (!eval) {
         if (!callerCodeBlock->isStrictMode()) {
             if (programSource.is8Bit()) {
@@ -147,7 +148,7 @@
         // If the literal parser bailed, it should not have thrown exceptions.
         ASSERT(!scope.exception());
 
-        eval = callerCodeBlock->evalCodeCache().getSlow(callFrame, callerCodeBlock, callerCodeBlock->isStrictMode(), derivedContextType, evalContextType, isArrowFunctionContext, programSource, callerScopeChain);
+        eval = callerCodeBlock->evalCodeCache().getSlow(callFrame, callerCodeBlock, programSource, callerCallSiteIndex, callerCodeBlock->isStrictMode(), derivedContextType, evalContextType, isArrowFunctionContext, callerScopeChain);
 
         if (!eval)
             return jsUndefined();
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to