CachedScript could have a copy-free path for all-ASCII scripts.
authorakling@apple.com <akling@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 13 Dec 2015 20:03:24 +0000 (20:03 +0000)
committerakling@apple.com <akling@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 13 Dec 2015 20:03:24 +0000 (20:03 +0000)
<https://webkit.org/b/152203>

Source/JavaScriptCore:

Reviewed by Antti Koivisto.

Make SourceProvider vend a StringView instead of a String.
This relaxes the promises that providers have to make about string lifetimes.

This means that on the WebCore side, CachedScript is free to cache a String
internally, while only ever exposing it as a temporary StringView.

A few extra copies (CPU, not memory) are introduced, none of them on hot paths.

* API/JSScriptRef.cpp:
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::sourceCodeForTools):
(JSC::CodeBlock::dumpSource):
* inspector/ScriptDebugServer.cpp:
(Inspector::ScriptDebugServer::dispatchDidParseSource):
(Inspector::ScriptDebugServer::dispatchFailedToParseSource):
* interpreter/Interpreter.cpp:
(JSC::Interpreter::execute):
* jsc.cpp:
(functionFindTypeForExpression):
(functionHasBasicBlockExecuted):
(functionBasicBlockExecutionCount):
* parser/Lexer.cpp:
(JSC::Lexer<T>::setCode):
* parser/Lexer.h:
(JSC::Lexer<LChar>::setCodeStart):
(JSC::Lexer<UChar>::setCodeStart):
* parser/Parser.h:
(JSC::Parser::getToken):
* parser/SourceCode.cpp:
(JSC::SourceCode::toUTF8):
* parser/SourceCode.h:
(JSC::SourceCode::hash):
(JSC::SourceCode::view):
(JSC::SourceCode::toString): Deleted.
* parser/SourceCodeKey.h:
(JSC::SourceCodeKey::SourceCodeKey):
(JSC::SourceCodeKey::string):
* parser/SourceProvider.h:
(JSC::SourceProvider::getRange):
* runtime/Completion.cpp:
(JSC::loadAndEvaluateModule):
(JSC::loadModule):
* runtime/ErrorInstance.cpp:
(JSC::appendSourceToError):
* runtime/FunctionPrototype.cpp:
(JSC::functionProtoFuncToString):
* tools/FunctionOverrides.cpp:
(JSC::initializeOverrideInfo):
(JSC::FunctionOverrides::initializeOverrideFor):

Source/WebCore:

Reviewed by ANtti Koivisto.

Many (if not most) of script resources on the web contain nothing but ASCII characters.
Such resources, when streamed through a text decoder, will yield the exact same byte
sequence, except in anonymous heap memory instead of delicious file-backed pages.

Care is taken to ensure that the wrapper StringImpl is updated to target newly cached
resource data if an asynchronous caching notification comes in.

* loader/cache/CachedResource.cpp:
(WebCore::CachedResource::tryReplaceEncodedData):
* loader/cache/CachedResource.h:
(WebCore::CachedResource::didReplaceSharedBufferContents):
* loader/cache/CachedScript.cpp:
(WebCore::encodingMayBeAllASCII):
(WebCore::CachedScript::script):
(WebCore::CachedScript::didReplaceSharedBufferContents):
* loader/cache/CachedScript.h:
* platform/SharedBuffer.h:
* platform/cf/SharedBufferCF.cpp:
(WebCore::SharedBuffer::tryReplaceContentsWithPlatformBuffer):

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

31 files changed:
Source/JavaScriptCore/API/JSScriptRef.cpp
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/CodeBlock.cpp
Source/JavaScriptCore/inspector/ScriptDebugServer.cpp
Source/JavaScriptCore/interpreter/Interpreter.cpp
Source/JavaScriptCore/jsc.cpp
Source/JavaScriptCore/parser/Lexer.cpp
Source/JavaScriptCore/parser/Lexer.h
Source/JavaScriptCore/parser/Parser.h
Source/JavaScriptCore/parser/SourceCode.cpp
Source/JavaScriptCore/parser/SourceCode.h
Source/JavaScriptCore/parser/SourceCodeKey.h
Source/JavaScriptCore/parser/SourceProvider.h
Source/JavaScriptCore/runtime/Completion.cpp
Source/JavaScriptCore/runtime/ErrorInstance.cpp
Source/JavaScriptCore/runtime/FunctionPrototype.cpp
Source/JavaScriptCore/tools/FunctionOverrides.cpp
Source/WTF/wtf/PrintStream.cpp
Source/WTF/wtf/PrintStream.h
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/CachedScriptSourceProvider.h
Source/WebCore/bindings/js/ScriptSourceCode.h
Source/WebCore/inspector/InspectorPageAgent.cpp
Source/WebCore/loader/cache/CachedResource.cpp
Source/WebCore/loader/cache/CachedResource.h
Source/WebCore/loader/cache/CachedScript.cpp
Source/WebCore/loader/cache/CachedScript.h
Source/WebCore/platform/SharedBuffer.h
Source/WebCore/platform/cf/SharedBufferCF.cpp
Source/WebCore/platform/soup/SharedBufferSoup.cpp
Source/WebKit/mac/WebView/WebScriptDebugger.mm

index 2f37423..e6504c7 100644 (file)
@@ -46,7 +46,12 @@ public:
         return WTF::adoptRef(*new OpaqueJSScript(vm, url, startingLineNumber, source));
     }
 
-    virtual const String& source() const override
+    unsigned hash() const override
+    {
+        return m_source.impl()->hash();
+    }
+
+    StringView source() const override
     {
         return m_source;
     }
index 4a828c7..1e95d62 100644 (file)
@@ -1,3 +1,60 @@
+2015-12-13  Andreas Kling  <akling@apple.com>
+
+        CachedScript could have a copy-free path for all-ASCII scripts.
+        <https://webkit.org/b/152203>
+
+        Reviewed by Antti Koivisto.
+
+        Make SourceProvider vend a StringView instead of a String.
+        This relaxes the promises that providers have to make about string lifetimes.
+
+        This means that on the WebCore side, CachedScript is free to cache a String
+        internally, while only ever exposing it as a temporary StringView.
+
+        A few extra copies (CPU, not memory) are introduced, none of them on hot paths.
+
+        * API/JSScriptRef.cpp:
+        * bytecode/CodeBlock.cpp:
+        (JSC::CodeBlock::sourceCodeForTools):
+        (JSC::CodeBlock::dumpSource):
+        * inspector/ScriptDebugServer.cpp:
+        (Inspector::ScriptDebugServer::dispatchDidParseSource):
+        (Inspector::ScriptDebugServer::dispatchFailedToParseSource):
+        * interpreter/Interpreter.cpp:
+        (JSC::Interpreter::execute):
+        * jsc.cpp:
+        (functionFindTypeForExpression):
+        (functionHasBasicBlockExecuted):
+        (functionBasicBlockExecutionCount):
+        * parser/Lexer.cpp:
+        (JSC::Lexer<T>::setCode):
+        * parser/Lexer.h:
+        (JSC::Lexer<LChar>::setCodeStart):
+        (JSC::Lexer<UChar>::setCodeStart):
+        * parser/Parser.h:
+        (JSC::Parser::getToken):
+        * parser/SourceCode.cpp:
+        (JSC::SourceCode::toUTF8):
+        * parser/SourceCode.h:
+        (JSC::SourceCode::hash):
+        (JSC::SourceCode::view):
+        (JSC::SourceCode::toString): Deleted.
+        * parser/SourceCodeKey.h:
+        (JSC::SourceCodeKey::SourceCodeKey):
+        (JSC::SourceCodeKey::string):
+        * parser/SourceProvider.h:
+        (JSC::SourceProvider::getRange):
+        * runtime/Completion.cpp:
+        (JSC::loadAndEvaluateModule):
+        (JSC::loadModule):
+        * runtime/ErrorInstance.cpp:
+        (JSC::appendSourceToError):
+        * runtime/FunctionPrototype.cpp:
+        (JSC::functionProtoFuncToString):
+        * tools/FunctionOverrides.cpp:
+        (JSC::initializeOverrideInfo):
+        (JSC::FunctionOverrides::initializeOverrideFor):
+
 2015-12-12  Benjamin Poulain  <benjamin@webkit.org>
 
         [JSC] Add lowering for B3's Store8 opcode
index 7baf93c..879e418 100644 (file)
@@ -196,7 +196,7 @@ CString CodeBlock::sourceCodeForTools() const
     unsigned rangeEnd = delta + unlinked->startOffset() + unlinked->sourceLength();
     return toCString(
         "function ",
-        provider->source().impl()->utf8ForRange(rangeStart, rangeEnd - rangeStart));
+        provider->source().substring(rangeStart, rangeEnd - rangeStart).utf8());
 }
 
 CString CodeBlock::sourceCodeOnOneLine() const
@@ -554,14 +554,14 @@ void CodeBlock::dumpSource(PrintStream& out)
     ScriptExecutable* executable = ownerScriptExecutable();
     if (executable->isFunctionExecutable()) {
         FunctionExecutable* functionExecutable = reinterpret_cast<FunctionExecutable*>(executable);
-        String source = functionExecutable->source().provider()->getRange(
+        StringView source = functionExecutable->source().provider()->getRange(
             functionExecutable->parametersStartOffset(),
             functionExecutable->typeProfilingEndOffset() + 1); // Type profiling end offset is the character before the '}'.
         
         out.print("function ", inferredName(), source);
         return;
     }
-    out.print(executable->source().toString());
+    out.print(executable->source().view());
 }
 
 void CodeBlock::dumpBytecode()
index 89891bd..98c65de 100644 (file)
@@ -199,7 +199,7 @@ void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, Sou
 
     ScriptDebugListener::Script script;
     script.url = sourceProvider->url();
-    script.source = sourceProvider->source();
+    script.source = sourceProvider->source().toString();
     script.startLine = sourceProvider->startPosition().m_line.zeroBasedInt();
     script.startColumn = sourceProvider->startPosition().m_column.zeroBasedInt();
     script.isContentScript = isContentScript;
@@ -231,7 +231,7 @@ void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, Sou
 void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
 {
     String url = sourceProvider->url();
-    const String& data = sourceProvider->source();
+    String data = sourceProvider->source().toString();
     int firstLine = sourceProvider->startPosition().m_line.oneBasedInt();
 
     Vector<ScriptDebugListener*> copy;
index 78ffd91..fe02c41 100644 (file)
@@ -849,7 +849,7 @@ JSValue Interpreter::execute(ProgramExecutable* program, CallFrame* callFrame, J
 
     Vector<JSONPData> JSONPData;
     bool parseResult;
-    const String programSource = program->source().toString();
+    StringView programSource = program->source().view();
     if (programSource.isNull())
         return jsUndefined();
     if (programSource.is8Bit()) {
index 2e914ab..cfcec32 100644 (file)
@@ -1464,7 +1464,7 @@ EncodedJSValue JSC_HOST_CALL functionFindTypeForExpression(ExecState* exec)
 
     RELEASE_ASSERT(exec->argument(1).isString());
     String substring = exec->argument(1).getString(exec);
-    String sourceCodeText = executable->source().toString();
+    String sourceCodeText = executable->source().view().toString();
     unsigned offset = static_cast<unsigned>(sourceCodeText.find(substring) + executable->source().startOffset());
     
     String jsonString = exec->vm().typeProfiler()->typeInformationForExpressionAtOffset(TypeProfilerSearchDescriptorNormal, offset, executable->sourceID(), exec->vm());
@@ -1502,7 +1502,7 @@ EncodedJSValue JSC_HOST_CALL functionHasBasicBlockExecuted(ExecState* exec)
 
     RELEASE_ASSERT(exec->argument(1).isString());
     String substring = exec->argument(1).getString(exec);
-    String sourceCodeText = executable->source().toString();
+    String sourceCodeText = executable->source().view().toString();
     RELEASE_ASSERT(sourceCodeText.contains(substring));
     int offset = sourceCodeText.find(substring) + executable->source().startOffset();
     
@@ -1520,7 +1520,7 @@ EncodedJSValue JSC_HOST_CALL functionBasicBlockExecutionCount(ExecState* exec)
 
     RELEASE_ASSERT(exec->argument(1).isString());
     String substring = exec->argument(1).getString(exec);
-    String sourceCodeText = executable->source().toString();
+    String sourceCodeText = executable->source().view().toString();
     RELEASE_ASSERT(sourceCodeText.contains(substring));
     int offset = sourceCodeText.find(substring) + executable->source().startOffset();
     
index 763bdf1..764ee88 100644 (file)
@@ -543,10 +543,10 @@ void Lexer<T>::setCode(const SourceCode& source, ParserArena* arena)
     m_lineNumber = source.firstLine();
     m_lastToken = -1;
     
-    const String& sourceString = source.provider()->source();
+    StringView sourceString = source.provider()->source();
 
     if (!sourceString.isNull())
-        setCodeStart(sourceString.impl());
+        setCodeStart(sourceString);
     else
         m_codeStart = 0;
 
index 1862d77..c7b63b2 100644 (file)
@@ -139,7 +139,7 @@ private:
     ALWAYS_INLINE const T* currentSourcePtr() const;
     ALWAYS_INLINE void setOffsetFromSourcePtr(const T* sourcePtr, unsigned lineStartOffset) { setOffset(offsetFromSourcePtr(sourcePtr), lineStartOffset); }
 
-    ALWAYS_INLINE void setCodeStart(const StringImpl*);
+    ALWAYS_INLINE void setCodeStart(const StringView&);
 
     ALWAYS_INLINE const Identifier* makeIdentifier(const LChar* characters, size_t length);
     ALWAYS_INLINE const Identifier* makeIdentifier(const UChar* characters, size_t length);
@@ -289,17 +289,17 @@ ALWAYS_INLINE const Identifier* Lexer<T>::makeEmptyIdentifier()
 }
 
 template <>
-ALWAYS_INLINE void Lexer<LChar>::setCodeStart(const StringImpl* sourceString)
+ALWAYS_INLINE void Lexer<LChar>::setCodeStart(const StringView& sourceString)
 {
-    ASSERT(sourceString->is8Bit());
-    m_codeStart = sourceString->characters8();
+    ASSERT(sourceString.is8Bit());
+    m_codeStart = sourceString.characters8();
 }
 
 template <>
-ALWAYS_INLINE void Lexer<UChar>::setCodeStart(const StringImpl* sourceString)
+ALWAYS_INLINE void Lexer<UChar>::setCodeStart(const StringView& sourceString)
 {
-    ASSERT(!sourceString->is8Bit());
-    m_codeStart = sourceString->characters16();
+    ASSERT(!sourceString.is8Bit());
+    m_codeStart = sourceString.characters16();
 }
 
 template <typename T>
index 262ff96..e5b62a7 100644 (file)
@@ -990,7 +990,7 @@ private:
     }
 
     void printUnexpectedTokenText(WTF::PrintStream&);
-    ALWAYS_INLINE String getToken() {
+    ALWAYS_INLINE StringView getToken() {
         SourceProvider* sourceProvider = m_source->provider();
         return sourceProvider->getRange(tokenStart(), tokenEndPosition().offset);
     }
index 8ef168d..c430e97 100644 (file)
@@ -36,7 +36,7 @@ CString SourceCode::toUTF8() const
     if (!m_provider)
         return CString("", 0);
     
-    return m_provider->source().impl()->utf8ForRange(m_startChar, m_endChar - m_startChar);
+    return m_provider->source().substring(m_startChar, m_endChar - m_startChar).utf8();
 }
 
 } // namespace JSC
index 7f37ecf..73d6b54 100644 (file)
@@ -79,10 +79,16 @@ namespace JSC {
 
         bool isHashTableDeletedValue() const { return m_provider.isHashTableDeletedValue(); }
 
-        String toString() const
+        unsigned hash() const
+        {
+            ASSERT(m_provider);
+            return m_provider->hash();
+        }
+
+        StringView view() const
         {
             if (!m_provider)
-                return String();
+                return StringView();
             return m_provider->getRange(m_startChar, m_endChar);
         }
         
index 8d49a7e..09f8f91 100644 (file)
@@ -45,7 +45,7 @@ public:
         : m_sourceCode(sourceCode)
         , m_name(name)
         , m_flags((static_cast<unsigned>(codeType) << 3) | (static_cast<unsigned>(builtinMode) << 2) | (static_cast<unsigned>(strictMode) << 1) | static_cast<unsigned>(thisTDZMode))
-        , m_hash(string().impl()->hash())
+        , m_hash(sourceCode.hash())
     {
     }
 
@@ -64,7 +64,7 @@ public:
 
     // To save memory, we compute our string on demand. It's expected that source
     // providers cache their strings to make this efficient.
-    String string() const { return m_sourceCode.toString(); }
+    StringView string() const { return m_sourceCode.view(); }
 
     bool operator==(const SourceCodeKey& other) const
     {
index 4f984eb..6b478f7 100644 (file)
@@ -43,10 +43,11 @@ namespace JSC {
 
         JS_EXPORT_PRIVATE virtual ~SourceProvider();
 
-        virtual const String& source() const = 0;
-        String getRange(int start, int end) const
+        virtual unsigned hash() const = 0;
+        virtual StringView source() const = 0;
+        StringView getRange(int start, int end) const
         {
-            return source().substringSharingImpl(start, end - start);
+            return source().substring(start, end - start);
         }
 
         const String& url() const { return m_url; }
@@ -86,8 +87,13 @@ namespace JSC {
         {
             return adoptRef(*new StringSourceProvider(source, url, startPosition));
         }
+        
+        unsigned hash() const override
+        {
+            return m_source.impl()->hash();
+        }
 
-        virtual const String& source() const override
+        virtual StringView source() const override
         {
             return m_source;
         }
@@ -110,7 +116,12 @@ namespace JSC {
             return adoptRef(*new WebAssemblySourceProvider(data, url));
         }
 
-        virtual const String& source() const override
+        unsigned hash() const override
+        {
+            return m_source.impl()->hash();
+        }
+
+        virtual StringView source() const override
         {
             return m_source;
         }
index 02f395c..1550587 100644 (file)
@@ -167,7 +167,7 @@ JSInternalPromise* loadAndEvaluateModule(ExecState* exec, const SourceCode& sour
     JSGlobalObject* globalObject = exec->vmEntryGlobalObject();
 
     // Insert the given source code to the ModuleLoader registry as the fetched registry entry.
-    globalObject->moduleLoader()->provide(exec, key, ModuleLoaderObject::Status::Fetch, source.toString());
+    globalObject->moduleLoader()->provide(exec, key, ModuleLoaderObject::Status::Fetch, source.view().toString());
     if (exec->hadException())
         return rejectPromise(exec, globalObject);
 
@@ -204,7 +204,7 @@ JSInternalPromise* loadModule(ExecState* exec, const SourceCode& source)
     JSGlobalObject* globalObject = exec->vmEntryGlobalObject();
 
     // Insert the given source code to the ModuleLoader registry as the fetched registry entry.
-    globalObject->moduleLoader()->provide(exec, key, ModuleLoaderObject::Status::Fetch, source.toString());
+    globalObject->moduleLoader()->provide(exec, key, ModuleLoaderObject::Status::Fetch, source.view().toString());
     if (exec->hadException())
         return rejectPromise(exec, globalObject);
 
index c0549d8..4f14dc2 100644 (file)
@@ -60,7 +60,7 @@ static void appendSourceToError(CallFrame* callFrame, ErrorInstance* exception,
     int expressionStart = divotPoint - startOffset;
     int expressionStop = divotPoint + endOffset;
 
-    const String& sourceString = codeBlock->source()->source();
+    StringView sourceString = codeBlock->source()->source();
     if (!expressionStop || expressionStart > static_cast<int>(sourceString.length()))
         return;
     
@@ -71,24 +71,23 @@ static void appendSourceToError(CallFrame* callFrame, ErrorInstance* exception,
     
     String message = asString(jsMessage)->value(callFrame);
     if (expressionStart < expressionStop)
-        message = appender(message, codeBlock->source()->getRange(expressionStart, expressionStop) , type, ErrorInstance::FoundExactSource);
+        message = appender(message, codeBlock->source()->getRange(expressionStart, expressionStop).toString(), type, ErrorInstance::FoundExactSource);
     else {
         // No range information, so give a few characters of context.
-        const StringImpl* data = sourceString.impl();
         int dataLength = sourceString.length();
         int start = expressionStart;
         int stop = expressionStart;
         // Get up to 20 characters of context to the left and right of the divot, clamping to the line.
         // Then strip whitespace.
-        while (start > 0 && (expressionStart - start < 20) && (*data)[start - 1] != '\n')
+        while (start > 0 && (expressionStart - start < 20) && sourceString[start - 1] != '\n')
             start--;
-        while (start < (expressionStart - 1) && isStrWhiteSpace((*data)[start]))
+        while (start < (expressionStart - 1) && isStrWhiteSpace(sourceString[start]))
             start++;
-        while (stop < dataLength && (stop - expressionStart < 20) && (*data)[stop] != '\n')
+        while (stop < dataLength && (stop - expressionStart < 20) && sourceString[stop] != '\n')
             stop++;
-        while (stop > expressionStart && isStrWhiteSpace((*data)[stop - 1]))
+        while (stop > expressionStart && isStrWhiteSpace(sourceString[stop - 1]))
             stop--;
-        message = appender(message, codeBlock->source()->getRange(start, stop), type, ErrorInstance::FoundApproximateSource);
+        message = appender(message, codeBlock->source()->getRange(start, stop).toString(), type, ErrorInstance::FoundApproximateSource);
     }
     exception->putDirect(*vm, vm->propertyNames->message, jsString(vm, message));
 
index aa87561..6b7a5d9 100644 (file)
@@ -92,7 +92,7 @@ EncodedJSValue JSC_HOST_CALL functionProtoFuncToString(ExecState* exec)
         
         String functionHeader = executable->isArrowFunction() ? "" : "function ";
         
-        String source = executable->source().provider()->getRange(
+        StringView source = executable->source().provider()->getRange(
             executable->parametersStartOffset(),
             executable->parametersStartOffset() + executable->source().length());
         return JSValue::encode(jsMakeNontrivialString(exec, functionHeader, function->name(exec), source));
index 2034d60..2e328c1 100644 (file)
@@ -106,7 +106,7 @@ FunctionOverrides::FunctionOverrides(const char* overridesFileName)
 
 static void initializeOverrideInfo(const SourceCode& origCode, const String& newBody, FunctionOverrides::OverrideInfo& info)
 {
-    String origProviderStr = origCode.provider()->source();
+    String origProviderStr = origCode.provider()->source().toString();
     unsigned origBraceStart = origCode.startOffset();
     unsigned origFunctionStart = origProviderStr.reverseFind("function", origBraceStart);
     unsigned headerLength = origBraceStart - origFunctionStart;
@@ -135,7 +135,7 @@ bool FunctionOverrides::initializeOverrideFor(const SourceCode& origCode, Functi
     ASSERT(Options::functionOverrides());
     FunctionOverrides& overrides = FunctionOverrides::overrides();
 
-    auto it = overrides.m_entries.find(origCode.toString());
+    auto it = overrides.m_entries.find(origCode.view().toString());
     if (it == overrides.m_entries.end())
         return false;
 
index 068931e..3364407 100644 (file)
@@ -53,6 +53,11 @@ void printInternal(PrintStream& out, const char* string)
     out.printf("%s", string);
 }
 
+void printInternal(PrintStream& out, const StringView& string)
+{
+    out.print(string.utf8());
+}
+
 void printInternal(PrintStream& out, const CString& string)
 {
     out.print(string.data());
index 95acdfa..a5f5f03 100644 (file)
@@ -40,6 +40,7 @@ class AtomicStringImpl;
 class CString;
 class String;
 class StringImpl;
+class StringView;
 class UniquedStringImpl;
 
 class PrintStream {
@@ -70,6 +71,7 @@ public:
 };
 
 WTF_EXPORT_PRIVATE void printInternal(PrintStream&, const char*);
+WTF_EXPORT_PRIVATE void printInternal(PrintStream&, const StringView&);
 WTF_EXPORT_PRIVATE void printInternal(PrintStream&, const CString&);
 WTF_EXPORT_PRIVATE void printInternal(PrintStream&, const String&);
 WTF_EXPORT_PRIVATE void printInternal(PrintStream&, const StringImpl*);
index e9caebe..79ffcef 100644 (file)
@@ -1,3 +1,30 @@
+2015-12-13  Andreas Kling  <akling@apple.com>
+
+        CachedScript could have a copy-free path for all-ASCII scripts.
+        <https://webkit.org/b/152203>
+
+        Reviewed by ANtti Koivisto.
+
+        Many (if not most) of script resources on the web contain nothing but ASCII characters.
+        Such resources, when streamed through a text decoder, will yield the exact same byte
+        sequence, except in anonymous heap memory instead of delicious file-backed pages.
+
+        Care is taken to ensure that the wrapper StringImpl is updated to target newly cached
+        resource data if an asynchronous caching notification comes in.
+
+        * loader/cache/CachedResource.cpp:
+        (WebCore::CachedResource::tryReplaceEncodedData):
+        * loader/cache/CachedResource.h:
+        (WebCore::CachedResource::didReplaceSharedBufferContents):
+        * loader/cache/CachedScript.cpp:
+        (WebCore::encodingMayBeAllASCII):
+        (WebCore::CachedScript::script):
+        (WebCore::CachedScript::didReplaceSharedBufferContents):
+        * loader/cache/CachedScript.h:
+        * platform/SharedBuffer.h:
+        * platform/cf/SharedBufferCF.cpp:
+        (WebCore::SharedBuffer::tryReplaceContentsWithPlatformBuffer):
+
 2015-12-13  Zalan Bujtas  <zalan@apple.com>
 
         Clean up absolute positioned map properly.
index 2a228d3..fcceb8c 100644 (file)
@@ -44,7 +44,8 @@ public:
         m_cachedScript->removeClient(this);
     }
 
-    const String& source() const { return m_cachedScript->script(); }
+    unsigned hash() const override { return m_cachedScript->scriptHash(); }
+    StringView source() const override { return m_cachedScript->script(); }
 
 private:
     CachedScriptSourceProvider(CachedScript* cachedScript)
index 2d3c159..7854361 100644 (file)
@@ -61,7 +61,7 @@ public:
 
     const JSC::SourceCode& jsSourceCode() const { return m_code; }
 
-    const String& source() const { return m_provider->source(); }
+    StringView source() const { return m_provider->source(); }
 
     int startLine() const { return m_code.firstLine(); }
 
index 017b562..0e00021 100644 (file)
@@ -161,7 +161,7 @@ bool InspectorPageAgent::cachedResourceContent(CachedResource* cachedResource, S
             *result = downcast<CachedCSSStyleSheet>(*cachedResource).sheetText();
             return !result->isNull();
         case CachedResource::Script:
-            *result = downcast<CachedScript>(*cachedResource).script();
+            *result = downcast<CachedScript>(*cachedResource).script().toString();
             return true;
         case CachedResource::RawResource: {
             auto* buffer = cachedResource->resourceBuffer();
index 7fa12ee..e095e81 100644 (file)
@@ -787,7 +787,10 @@ void CachedResource::tryReplaceEncodedData(SharedBuffer& newBuffer)
     if (m_data->size() != newBuffer.size() || memcmp(m_data->data(), newBuffer.data(), m_data->size()))
         return;
 
-    m_data->tryReplaceContentsWithPlatformBuffer(newBuffer);
+    if (m_data->tryReplaceContentsWithPlatformBuffer(newBuffer)) {
+        didReplaceSharedBufferContents();
+        // FIXME: Should we call checkNotify() here to move already-decoded images to the new data source?
+    }
 }
 
 #endif
index b7bfa71..a4523d6 100644 (file)
@@ -267,6 +267,8 @@ protected:
     void setDecodedSize(unsigned);
     void didAccessDecodedData(double timeStamp);
 
+    virtual void didReplaceSharedBufferContents() { }
+
     // FIXME: Make the rest of these data members private and use functions in derived classes instead.
     HashCountedSet<CachedResourceClient*> m_clients;
     ResourceRequest m_resourceRequest;
index 2f8ae3c..e512236 100644 (file)
@@ -69,16 +69,52 @@ String CachedScript::mimeType() const
     return extractMIMETypeFromMediaType(m_response.httpHeaderField(HTTPHeaderName::ContentType)).lower();
 }
 
-const String& CachedScript::script()
+static bool encodingMayBeAllASCII(const String& encoding)
+{
+    return encoding == "UTF-8" || encoding == "ISO-8859-1" || encoding == "ASCII";
+}
+
+StringView CachedScript::script()
 {
     if (!m_script && m_data) {
+        if (m_ASCIIOptimizationState == Unknown
+            && encodingMayBeAllASCII(encoding())
+            && m_data->size()
+            && charactersAreAllASCII(reinterpret_cast<const LChar*>(m_data->data()), m_data->size())) {
+
+            m_script = StringImpl::createWithoutCopying(reinterpret_cast<const LChar*>(m_data->data()), m_data->size());
+            m_ASCIIOptimizationState = DataAndDecodedStringHaveSameBytes;
+
+            // If the encoded and decoded data are the same, there is no decoded data cost!
+            setDecodedSize(0);
+            m_decodedDataDeletionTimer.stop();
+            return m_script;
+        }
         m_script = m_decoder->decodeAndFlush(m_data->data(), encodedSize());
+        m_ASCIIOptimizationState = DataAndDecodedStringHaveDifferentBytes;
         setDecodedSize(m_script.sizeInBytes());
     }
-    m_decodedDataDeletionTimer.restart();
+    if (m_ASCIIOptimizationState == DataAndDecodedStringHaveDifferentBytes)
+        m_decodedDataDeletionTimer.restart();
     return m_script;
 }
 
+unsigned CachedScript::scriptHash()
+{
+    script();
+    return m_script.impl()->hash();
+}
+
+void CachedScript::didReplaceSharedBufferContents()
+{
+    // We receive this callback when the CachedResource's internal SharedBuffer has had its contents
+    // replaced by the memory-mapping-of-file-backed-resources optimization. If m_script is just a
+    // non-copying wrapper around the old SharedBuffer contents, we have to retarget it.
+    if (m_ASCIIOptimizationState == DataAndDecodedStringHaveSameBytes)
+        m_script = StringImpl::createWithoutCopying(reinterpret_cast<const LChar*>(m_data->data()), m_data->size());
+    CachedResource::didReplaceSharedBufferContents();
+}
+
 void CachedScript::finishLoading(SharedBuffer* data)
 {
     m_data = data;
index 938e918..4f51562 100644 (file)
@@ -37,7 +37,8 @@ public:
     CachedScript(const ResourceRequest&, const String& charset, SessionID);
     virtual ~CachedScript();
 
-    const String& script();
+    StringView script();
+    unsigned scriptHash();
 
     String mimeType() const;
 
@@ -45,6 +46,8 @@ public:
     bool mimeTypeAllowedByNosniff() const;
 #endif
 
+    void didReplaceSharedBufferContents() override;
+
 private:
     virtual bool mayTryReplaceEncodedData() const override { return true; }
 
@@ -57,6 +60,10 @@ private:
     virtual void destroyDecodedData() override;
 
     String m_script;
+
+    enum ASCIIResourceOptimizationState { Unknown, DataAndDecodedStringHaveSameBytes, DataAndDecodedStringHaveDifferentBytes };
+    ASCIIResourceOptimizationState m_ASCIIOptimizationState { Unknown };
+
     RefPtr<TextResourceDecoder> m_decoder;
 };
 
index 4030bda..aa4099b 100644 (file)
@@ -119,7 +119,7 @@ public:
     //      }
     WEBCORE_EXPORT unsigned getSomeData(const char*& data, unsigned position = 0) const;
 
-    void tryReplaceContentsWithPlatformBuffer(SharedBuffer&);
+    bool tryReplaceContentsWithPlatformBuffer(SharedBuffer&);
     WEBCORE_EXPORT bool hasPlatformData() const;
 
     struct DataBuffer : public ThreadSafeRefCounted<DataBuffer> {
index 2a75e56..b9f6e6e 100644 (file)
@@ -92,13 +92,14 @@ void SharedBuffer::clearPlatformData()
     m_cfData = 0;
 }
 
-void SharedBuffer::tryReplaceContentsWithPlatformBuffer(SharedBuffer& newContents)
+bool SharedBuffer::tryReplaceContentsWithPlatformBuffer(SharedBuffer& newContents)
 {
     if (!newContents.m_cfData)
-        return;
+        return false;
 
     clear();
     m_cfData = newContents.m_cfData;
+    return true;
 }
 
 bool SharedBuffer::maybeAppendPlatformData(SharedBuffer* newContents)
index cbdb337..ec1debf 100644 (file)
@@ -78,14 +78,15 @@ bool SharedBuffer::maybeAppendPlatformData(SharedBuffer*)
     return false;
 }
 
-void SharedBuffer::tryReplaceContentsWithPlatformBuffer(SharedBuffer& newContents)
+bool SharedBuffer::tryReplaceContentsWithPlatformBuffer(SharedBuffer& newContents)
 {
     if (!newContents.hasPlatformData())
-        return;
+        return false;
 
     clear();
     // FIXME: Use GRefPtr instead of GUniquePtr for the SoupBuffer.
     m_soupBuffer.swap(newContents.m_soupBuffer);
+    return true;
 }
 
 } // namespace WebCore
index 7b5c484..70b8e04 100644 (file)
@@ -51,7 +51,7 @@ using namespace WebCore;
 
 static NSString *toNSString(SourceProvider* sourceProvider)
 {
-    const String& sourceString = sourceProvider->source();
+    const String& sourceString = sourceProvider->source().toString();
     if (sourceString.isEmpty())
         return nil;
     return sourceString;