Title: [282468] trunk
Revision
282468
Author
[email protected]
Date
2021-09-15 13:17:05 -0700 (Wed, 15 Sep 2021)

Log Message

[JSC] Optimize leaf object creation in JSON.parse
https://bugs.webkit.org/show_bug.cgi?id=230298

Reviewed by Keith Miller.

JSTests:

* microbenchmarks/json-parse-leaf-object.js: Added.

Source/_javascript_Core:

This patch optimizes JSON.parse.

1. Use table in isJSONWhiteSpace.
2. Extract primitive value creation as parsePrimitiveValue function to use it in different place.
3. Add leaf-object creation fast path. Previously, when creating a leaf-object from JSON.parse we
   are too generic and jumping around the code. Instead we add a fast path that does not perform
   unnecessary operations and code gets tight.

It offers 3-4% improvement in microbenchmarks.
                                                     ToT                     Patched

vanilla-es2015-babel-webpack-todomvc-json-parse
                                              104.7169+-0.1113     ^    101.4836+-0.2168        ^ definitely 1.0319x faster
flight-todomvc-json-parse                      53.9074+-0.0957     ^     52.1347+-0.0802        ^ definitely 1.0340x faster
vanilla-es2015-todomvc-json-parse             104.9373+-0.1631     ^    101.4978+-0.1073        ^ definitely 1.0339x faster
vanilla-todomvc-json-parse                     79.1330+-0.0963     ^     76.7568+-0.1606        ^ definitely 1.0310x faster

This offers 0.2% improvement in Speedometer2.

----------------------------------------------------------------------------------------------------------------------------------
|               subtest                |     ms      |     ms      |  b / a   | pValue (significance using False Discovery Rate) |
----------------------------------------------------------------------------------------------------------------------------------
| Elm-TodoMVC                          |116.860000   |116.825000   |0.999700  | 0.901070                                         |
| VueJS-TodoMVC                        |24.658333    |24.763333    |1.004258  | 0.571728                                         |
| EmberJS-TodoMVC                      |126.666667   |126.335000   |0.997382  | 0.289517                                         |
| BackboneJS-TodoMVC                   |48.435000    |48.523333    |1.001824  | 0.455638                                         |
| Preact-TodoMVC                       |17.585000    |17.368333    |0.987679  | 0.247658                                         |
| AngularJS-TodoMVC                    |129.576667   |129.398333   |0.998624  | 0.625634                                         |
| Vanilla-ES2015-TodoMVC               |62.746667    |62.241667    |0.991952  | 0.000019 (significant)                           |
| Inferno-TodoMVC                      |63.741667    |63.495000    |0.996130  | 0.448861                                         |
| Flight-TodoMVC                       |78.021667    |77.306667    |0.990836  | 0.087137                                         |
| Angular2-TypeScript-TodoMVC          |39.823333    |39.923333    |1.002511  | 0.736279                                         |
| VanillaJS-TodoMVC                    |50.073333    |49.791667    |0.994375  | 0.136495                                         |
| jQuery-TodoMVC                       |221.300000   |221.586667   |1.001295  | 0.418008                                         |
| EmberJS-Debug-TodoMVC                |340.145000   |339.965000   |0.999471  | 0.691490                                         |
| React-TodoMVC                        |85.698333    |85.650000    |0.999436  | 0.761586                                         |
| React-Redux-TodoMVC                  |140.510000   |140.785000   |1.001957  | 0.285922                                         |
| Vanilla-ES2015-Babel-Webpack-TodoMVC |60.928333    |60.500000    |0.992970  | 0.000069 (significant)                           |
----------------------------------------------------------------------------------------------------------------------------------
a mean = 262.15844
b mean = 262.72261
pValue = 0.0428052487
(Bigger means are better.)
1.002 times better
Results ARE significant

* runtime/LiteralParser.cpp:
(JSC::LiteralParser<CharType>::makeIdentifier):
(JSC::isJSONWhiteSpace):
(JSC::LiteralParser<CharType>::Lexer::lex):
(JSC::LiteralParser<CharType>::parsePrimitiveValue):
(JSC::LiteralParser<CharType>::parse):
* runtime/LiteralParser.h:

LayoutTests:

* js/dom/JSON-parse-expected.txt:

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (282467 => 282468)


--- trunk/JSTests/ChangeLog	2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/JSTests/ChangeLog	2021-09-15 20:17:05 UTC (rev 282468)
@@ -1,3 +1,12 @@
+2021-09-15  Yusuke Suzuki  <[email protected]>
+
+        [JSC] Optimize leaf object creation in JSON.parse
+        https://bugs.webkit.org/show_bug.cgi?id=230298
+
+        Reviewed by Keith Miller.
+
+        * microbenchmarks/json-parse-leaf-object.js: Added.
+
 2021-09-13  Yusuke Suzuki  <[email protected]>
 
         [JSC] Add JSON.parse microbenchmarks from Speedometer2

Added: trunk/JSTests/microbenchmarks/json-parse-leaf-object.js (0 => 282468)


--- trunk/JSTests/microbenchmarks/json-parse-leaf-object.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/json-parse-leaf-object.js	2021-09-15 20:17:05 UTC (rev 282468)
@@ -0,0 +1,187 @@
+var text = `
+[
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number": 0,
+"string": "test",
+"null": null,
+"boolean": false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+},
+{
+"number":0,
+"string":"test",
+"null":null,
+"boolean":false
+}
+]
+`;
+
+for (var i = 0; i < 1e4; ++i)
+    JSON.parse(text);

Added: trunk/JSTests/stress/json-parse-syntax.js (0 => 282468)


--- trunk/JSTests/stress/json-parse-syntax.js	                        (rev 0)
+++ trunk/JSTests/stress/json-parse-syntax.js	2021-09-15 20:17:05 UTC (rev 282468)
@@ -0,0 +1,26 @@
+function shouldThrow(func, errorMessage) {
+    var errorThrown = false;
+    var error = null;
+    try {
+        func();
+    } catch (e) {
+        errorThrown = true;
+        error = e;
+    }
+    if (!errorThrown)
+        throw new Error('not thrown');
+    if (String(error) !== errorMessage)
+        throw new Error(`bad error: ${String(error)}`);
+}
+
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+shouldThrow(() => JSON.parse(`{"object": 42`), `SyntaxError: JSON Parse error: Expected '}'`);
+shouldBe(JSON.stringify(JSON.parse(`{}`)), `{}`);
+shouldBe(JSON.stringify(JSON.parse(`{"object": {}}`)), `{"object":{}}`);
+shouldThrow(() => JSON.parse(`{"object": 42, "test":{}`), `SyntaxError: JSON Parse error: Expected '}'`);
+shouldBe(JSON.stringify(JSON.parse(`{"hello":42, "object": {}}`)), `{"hello":42,"object":{}}`);
+shouldBe(JSON.stringify(JSON.parse(`{"hello":42, "object": 42, "test": 43}`)), `{"hello":42,"object":42,"test":43}`);

Modified: trunk/LayoutTests/ChangeLog (282467 => 282468)


--- trunk/LayoutTests/ChangeLog	2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/LayoutTests/ChangeLog	2021-09-15 20:17:05 UTC (rev 282468)
@@ -1,3 +1,12 @@
+2021-09-15  Yusuke Suzuki  <[email protected]>
+
+        [JSC] Optimize leaf object creation in JSON.parse
+        https://bugs.webkit.org/show_bug.cgi?id=230298
+
+        Reviewed by Keith Miller.
+
+        * js/dom/JSON-parse-expected.txt:
+
 2021-09-15  Chris Dumez  <[email protected]>
 
         Unreviewed, skip a layout worklet test as it is not supported.

Modified: trunk/LayoutTests/js/dom/JSON-parse-expected.txt (282467 => 282468)


--- trunk/LayoutTests/js/dom/JSON-parse-expected.txt	2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/LayoutTests/js/dom/JSON-parse-expected.txt	2021-09-15 20:17:05 UTC (rev 282468)
@@ -85,7 +85,7 @@
 function (jsonObject){
         return jsonObject.parse('{"a":5,"a",}');
     }
-PASS tests[i](nativeJSON) threw exception SyntaxError: JSON Parse error: Expected ':'.
+PASS tests[i](nativeJSON) threw exception SyntaxError: JSON Parse error: Expected ':' before value in object property definition.
 function (jsonObject){
         return jsonObject.parse('{"a":(5,"a"),}');
     }

Modified: trunk/Source/_javascript_Core/ChangeLog (282467 => 282468)


--- trunk/Source/_javascript_Core/ChangeLog	2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/Source/_javascript_Core/ChangeLog	2021-09-15 20:17:05 UTC (rev 282468)
@@ -1,3 +1,64 @@
+2021-09-15  Yusuke Suzuki  <[email protected]>
+
+        [JSC] Optimize leaf object creation in JSON.parse
+        https://bugs.webkit.org/show_bug.cgi?id=230298
+
+        Reviewed by Keith Miller.
+
+        This patch optimizes JSON.parse.
+
+        1. Use table in isJSONWhiteSpace.
+        2. Extract primitive value creation as parsePrimitiveValue function to use it in different place.
+        3. Add leaf-object creation fast path. Previously, when creating a leaf-object from JSON.parse we
+           are too generic and jumping around the code. Instead we add a fast path that does not perform
+           unnecessary operations and code gets tight.
+
+        It offers 3-4% improvement in microbenchmarks.
+                                                             ToT                     Patched
+
+        vanilla-es2015-babel-webpack-todomvc-json-parse
+                                                      104.7169+-0.1113     ^    101.4836+-0.2168        ^ definitely 1.0319x faster
+        flight-todomvc-json-parse                      53.9074+-0.0957     ^     52.1347+-0.0802        ^ definitely 1.0340x faster
+        vanilla-es2015-todomvc-json-parse             104.9373+-0.1631     ^    101.4978+-0.1073        ^ definitely 1.0339x faster
+        vanilla-todomvc-json-parse                     79.1330+-0.0963     ^     76.7568+-0.1606        ^ definitely 1.0310x faster
+
+        This offers 0.2% improvement in Speedometer2.
+
+        ----------------------------------------------------------------------------------------------------------------------------------
+        |               subtest                |     ms      |     ms      |  b / a   | pValue (significance using False Discovery Rate) |
+        ----------------------------------------------------------------------------------------------------------------------------------
+        | Elm-TodoMVC                          |116.860000   |116.825000   |0.999700  | 0.901070                                         |
+        | VueJS-TodoMVC                        |24.658333    |24.763333    |1.004258  | 0.571728                                         |
+        | EmberJS-TodoMVC                      |126.666667   |126.335000   |0.997382  | 0.289517                                         |
+        | BackboneJS-TodoMVC                   |48.435000    |48.523333    |1.001824  | 0.455638                                         |
+        | Preact-TodoMVC                       |17.585000    |17.368333    |0.987679  | 0.247658                                         |
+        | AngularJS-TodoMVC                    |129.576667   |129.398333   |0.998624  | 0.625634                                         |
+        | Vanilla-ES2015-TodoMVC               |62.746667    |62.241667    |0.991952  | 0.000019 (significant)                           |
+        | Inferno-TodoMVC                      |63.741667    |63.495000    |0.996130  | 0.448861                                         |
+        | Flight-TodoMVC                       |78.021667    |77.306667    |0.990836  | 0.087137                                         |
+        | Angular2-TypeScript-TodoMVC          |39.823333    |39.923333    |1.002511  | 0.736279                                         |
+        | VanillaJS-TodoMVC                    |50.073333    |49.791667    |0.994375  | 0.136495                                         |
+        | jQuery-TodoMVC                       |221.300000   |221.586667   |1.001295  | 0.418008                                         |
+        | EmberJS-Debug-TodoMVC                |340.145000   |339.965000   |0.999471  | 0.691490                                         |
+        | React-TodoMVC                        |85.698333    |85.650000    |0.999436  | 0.761586                                         |
+        | React-Redux-TodoMVC                  |140.510000   |140.785000   |1.001957  | 0.285922                                         |
+        | Vanilla-ES2015-Babel-Webpack-TodoMVC |60.928333    |60.500000    |0.992970  | 0.000069 (significant)                           |
+        ----------------------------------------------------------------------------------------------------------------------------------
+        a mean = 262.15844
+        b mean = 262.72261
+        pValue = 0.0428052487
+        (Bigger means are better.)
+        1.002 times better
+        Results ARE significant
+
+        * runtime/LiteralParser.cpp:
+        (JSC::LiteralParser<CharType>::makeIdentifier):
+        (JSC::isJSONWhiteSpace):
+        (JSC::LiteralParser<CharType>::Lexer::lex):
+        (JSC::LiteralParser<CharType>::parsePrimitiveValue):
+        (JSC::LiteralParser<CharType>::parse):
+        * runtime/LiteralParser.h:
+
 2021-09-14  Don Olmstead  <[email protected]>
 
         Non-unified build fixes, mid September 2021 edition

Modified: trunk/Source/_javascript_Core/runtime/LiteralParser.cpp (282467 => 282468)


--- trunk/Source/_javascript_Core/runtime/LiteralParser.cpp	2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/Source/_javascript_Core/runtime/LiteralParser.cpp	2021-09-15 20:17:05 UTC (rev 282468)
@@ -59,14 +59,6 @@
 }
 
 template <typename CharType>
-static ALWAYS_INLINE bool isJSONWhiteSpace(const CharType& c)
-{
-    // The JSON RFC 4627 defines a list of allowed characters to be considered
-    // insignificant white space: http://www.ietf.org/rfc/rfc4627.txt (2. JSON Grammar).
-    return c == ' ' || c == 0x9 || c == 0xA || c == 0xD;
-}
-
-template <typename CharType>
 bool LiteralParser<CharType>::tryJSONPParse(Vector<JSONPData>& results, bool needsFullSourceInfo)
 {
     VM& vm = m_globalObject->vm();
@@ -149,6 +141,15 @@
     } while (m_lexer.currentToken()->type == TokIdentifier);
     return m_lexer.currentToken()->type == TokEnd;
 }
+
+template <typename CharType>
+ALWAYS_INLINE Identifier LiteralParser<CharType>::makeIdentifier(typename Lexer::LiteralParserTokenPtr token)
+{
+    if (token->stringIs8Bit)
+        return makeIdentifier(token->stringToken8, token->stringLength);
+    return makeIdentifier(token->stringToken16, token->stringLength);
+}
+
     
 template <typename CharType>
 template <typename LiteralCharType>
@@ -200,6 +201,8 @@
 }
 
 // 256 Latin-1 codes
+// The JSON RFC 4627 defines a list of allowed characters to be considered
+// insignificant white space: http://www.ietf.org/rfc/rfc4627.txt (2. JSON Grammar).
 static constexpr const TokenType tokenTypesOfLatin1Characters[256] = {
 /*   0 - Null               */ TokError,
 /*   1 - Start of Heading   */ TokError,
@@ -210,11 +213,11 @@
 /*   6 - Acknowledgment     */ TokError,
 /*   7 - Bell               */ TokError,
 /*   8 - Back Space         */ TokError,
-/*   9 - Horizontal Tab     */ TokError,
-/*  10 - Line Feed          */ TokError,
+/*   9 - Horizontal Tab     */ TokErrorSpace,
+/*  10 - Line Feed          */ TokErrorSpace,
 /*  11 - Vertical Tab       */ TokError,
 /*  12 - Form Feed          */ TokError,
-/*  13 - Carriage Return    */ TokError,
+/*  13 - Carriage Return    */ TokErrorSpace,
 /*  14 - Shift Out          */ TokError,
 /*  15 - Shift In           */ TokError,
 /*  16 - Data Line Escape   */ TokError,
@@ -233,7 +236,7 @@
 /*  29 - Group Separator    */ TokError,
 /*  30 - Record Separator   */ TokError,
 /*  31 - Unit Separator     */ TokError,
-/*  32 - Space              */ TokError,
+/*  32 - Space              */ TokErrorSpace,
 /*  33 - !                  */ TokError,
 /*  34 - "                  */ TokString,
 /*  35 - #                  */ TokError,
@@ -720,6 +723,12 @@
 };
 
 template <typename CharType>
+static ALWAYS_INLINE bool isJSONWhiteSpace(const CharType& c)
+{
+    return isLatin1(c) && tokenTypesOfLatin1Characters[c] == TokErrorSpace;
+}
+
+template <typename CharType>
 ALWAYS_INLINE TokenType LiteralParser<CharType>::Lexer::lex(LiteralParserToken<CharType>& token)
 {
 #if ASSERT_ENABLED
@@ -783,6 +792,7 @@
             return lexNumber(token);
 
         case TokError:
+        case TokErrorSpace:
             break;
 
         default:
@@ -1111,6 +1121,110 @@
 }
 
 template <typename CharType>
+void LiteralParser<CharType>::setErrorMessageForToken(TokenType tokenType)
+{
+    switch (tokenType) {
+    case TokRBrace:
+        m_parseErrorMessage = "Expected '}'"_s;
+        break;
+    case TokRBracket:
+        m_parseErrorMessage = "Expected ']'"_s;
+        break;
+    case TokColon:
+        m_parseErrorMessage = "Expected ':' before value in object property definition"_s;
+        break;
+    default: {
+        RELEASE_ASSERT_NOT_REACHED();
+    }
+    }
+}
+
+template <typename CharType>
+ALWAYS_INLINE JSValue LiteralParser<CharType>::parsePrimitiveValue(VM& vm)
+{
+    switch (m_lexer.currentToken()->type) {
+    case TokString: {
+        JSValue result = jsString(vm, makeIdentifier(m_lexer.currentToken()).string());
+        m_lexer.next();
+        return result;
+    }
+    case TokNumber: {
+        JSValue result = jsNumber(m_lexer.currentToken()->numberToken);
+        m_lexer.next();
+        return result;
+    }
+    case TokNull:
+        m_lexer.next();
+        return jsNull();
+    case TokTrue:
+        m_lexer.next();
+        return jsBoolean(true);
+    case TokFalse:
+        m_lexer.next();
+        return jsBoolean(false);
+    case TokRBracket:
+        m_parseErrorMessage = "Unexpected token ']'"_s;
+        return { };
+    case TokRBrace:
+        m_parseErrorMessage = "Unexpected token '}'"_s;
+        return { };
+    case TokIdentifier: {
+        auto token = m_lexer.currentToken();
+
+        auto tryMakeErrorString = [&] (unsigned length) -> String {
+            bool addEllipsis = length != token->stringLength;
+            if (token->stringIs8Bit)
+                return tryMakeString("Unexpected identifier \"", StringView { token->stringToken8, length }, addEllipsis ? "..." : "", '"');
+            return tryMakeString("Unexpected identifier \"", StringView { token->stringToken16, length }, addEllipsis ? "..." : "", '"');
+        };
+
+        constexpr unsigned maxLength = 200;
+
+        String errorString = tryMakeErrorString(std::min(token->stringLength, maxLength));
+        if (!errorString) {
+            constexpr unsigned shortLength = 10;
+            if (token->stringLength > shortLength)
+                errorString = tryMakeErrorString(shortLength);
+            if (!errorString)
+                errorString = "Unexpected identifier";
+        }
+
+        m_parseErrorMessage = errorString;
+        return { };
+    }
+    case TokColon:
+        m_parseErrorMessage = "Unexpected token ':'"_s;
+        return { };
+    case TokLParen:
+        m_parseErrorMessage = "Unexpected token '('"_s;
+        return { };
+    case TokRParen:
+        m_parseErrorMessage = "Unexpected token ')'"_s;
+        return { };
+    case TokComma:
+        m_parseErrorMessage = "Unexpected token ','"_s;
+        return { };
+    case TokDot:
+        m_parseErrorMessage = "Unexpected token '.'"_s;
+        return { };
+    case TokAssign:
+        m_parseErrorMessage = "Unexpected token '='"_s;
+        return { };
+    case TokSemi:
+        m_parseErrorMessage = "Unexpected token ';'"_s;
+        return { };
+    case TokEnd:
+        m_parseErrorMessage = "Unexpected EOF"_s;
+        return { };
+    case TokError:
+    default:
+        // Error
+        m_parseErrorMessage = "Could not parse value _expression_"_s;
+        return { };
+    }
+}
+
+template <typename CharType>
 JSValue LiteralParser<CharType>::parse(ParserState initialState)
 {
     VM& vm = m_globalObject->vm();
@@ -1123,298 +1237,257 @@
     HashSet<JSObject*> visitedUnderscoreProto;
     while (1) {
         switch(state) {
-            startParseArray:
-            case StartParseArray: {
-                JSArray* array = constructEmptyArray(m_globalObject, nullptr);
-                RETURN_IF_EXCEPTION(scope, JSValue());
-                objectStack.appendWithCrashOnOverflow(array);
-            }
-            doParseArrayStartExpression:
-            FALLTHROUGH;
-            case DoParseArrayStartExpression: {
-                TokenType lastToken = m_lexer.currentToken()->type;
-                if (m_lexer.next() == TokRBracket) {
-                    if (lastToken == TokComma) {
-                        m_parseErrorMessage = "Unexpected comma at the end of array _expression_"_s;
-                        return JSValue();
-                    }
-                    m_lexer.next();
-                    lastValue = objectStack.takeLast();
-                    break;
+        startParseArray:
+        case StartParseArray: {
+            JSArray* array = constructEmptyArray(m_globalObject, nullptr);
+            RETURN_IF_EXCEPTION(scope, { });
+            objectStack.appendWithCrashOnOverflow(array);
+        }
+        doParseArrayStartExpression:
+        FALLTHROUGH;
+        case DoParseArrayStartExpression: {
+            TokenType lastToken = m_lexer.currentToken()->type;
+            if (m_lexer.next() == TokRBracket) {
+                if (UNLIKELY(lastToken == TokComma)) {
+                    m_parseErrorMessage = "Unexpected comma at the end of array _expression_"_s;
+                    return { };
                 }
-
-                stateStack.append(DoParseArrayEndExpression);
-                goto startParseExpression;
-            }
-            case DoParseArrayEndExpression: {
-                JSArray* array = asArray(objectStack.last());
-                array->putDirectIndex(m_globalObject, array->length(), lastValue);
-                RETURN_IF_EXCEPTION(scope, JSValue());
-
-                if (m_lexer.currentToken()->type == TokComma)
-                    goto doParseArrayStartExpression;
-
-                if (m_lexer.currentToken()->type != TokRBracket) {
-                    m_parseErrorMessage = "Expected ']'"_s;
-                    return JSValue();
-                }
-                
                 m_lexer.next();
                 lastValue = objectStack.takeLast();
                 break;
             }
-            startParseObject:
-            case StartParseObject: {
-                JSObject* object = constructEmptyObject(m_globalObject);
-                objectStack.appendWithCrashOnOverflow(object);
 
-                TokenType type = m_lexer.next();
-                if (type == TokString || (m_mode != StrictJSON && type == TokIdentifier)) {
-                    typename Lexer::LiteralParserTokenPtr identifierToken = m_lexer.currentToken();
-                    if (identifierToken->stringIs8Bit)
-                        identifierStack.append(makeIdentifier(identifierToken->stringToken8, identifierToken->stringLength));
-                    else
-                        identifierStack.append(makeIdentifier(identifierToken->stringToken16, identifierToken->stringLength));
+            stateStack.append(DoParseArrayEndExpression);
+            goto startParseExpression;
+        }
+        case DoParseArrayEndExpression: {
+            JSArray* array = asArray(objectStack.last());
+            array->putDirectIndex(m_globalObject, array->length(), lastValue);
+            RETURN_IF_EXCEPTION(scope, { });
 
-                    // Check for colon
-                    if (m_lexer.next() != TokColon) {
-                        m_parseErrorMessage = "Expected ':' before value in object property definition"_s;
-                        return JSValue();
-                    }
-                    
-                    m_lexer.next();
-                    stateStack.append(DoParseObjectEndExpression);
-                    goto startParseExpression;
-                }
-                if (type != TokRBrace)  {
-                    m_parseErrorMessage = "Expected '}'"_s;
-                    return JSValue();
-                }
-                m_lexer.next();
-                lastValue = objectStack.takeLast();
-                break;
+            if (m_lexer.currentToken()->type == TokComma)
+                goto doParseArrayStartExpression;
+
+            if (UNLIKELY(m_lexer.currentToken()->type != TokRBracket)) {
+                setErrorMessageForToken(TokRBracket);
+                return { };
             }
-            doParseObjectStartExpression:
-            case DoParseObjectStartExpression: {
-                TokenType type = m_lexer.next();
-                if (type != TokString && (m_mode == StrictJSON || type != TokIdentifier)) {
-                    m_parseErrorMessage = "Property name must be a string literal"_s;
-                    return JSValue();
-                }
-                typename Lexer::LiteralParserTokenPtr identifierToken = m_lexer.currentToken();
-                if (identifierToken->stringIs8Bit)
-                    identifierStack.append(makeIdentifier(identifierToken->stringToken8, identifierToken->stringLength));
-                else
-                    identifierStack.append(makeIdentifier(identifierToken->stringToken16, identifierToken->stringLength));
+            
+            m_lexer.next();
+            lastValue = objectStack.takeLast();
+            break;
+        }
+        startParseObject:
+        case StartParseObject: {
+            JSObject* object = constructEmptyObject(m_globalObject);
 
-                // Check for colon
-                if (m_lexer.next() != TokColon) {
-                    m_parseErrorMessage = "Expected ':'"_s;
-                    return JSValue();
-                }
+            TokenType type = m_lexer.next();
+            if (type == TokString || (m_mode != StrictJSON && type == TokIdentifier)) {
+                while (true) {
+                    Identifier ident = makeIdentifier(m_lexer.currentToken());
 
-                m_lexer.next();
-                stateStack.append(DoParseObjectEndExpression);
-                goto startParseExpression;
-            }
-            case DoParseObjectEndExpression:
-            {
-                JSObject* object = asObject(objectStack.last());
-                Identifier ident = identifierStack.takeLast();
-                if (m_mode != StrictJSON && ident == vm.propertyNames->underscoreProto) {
-                    if (!visitedUnderscoreProto.add(object).isNewEntry) {
-                        m_parseErrorMessage = "Attempted to redefine __proto__ property"_s;
-                        return JSValue();
+                    if (UNLIKELY(m_lexer.next() != TokColon)) {
+                        setErrorMessageForToken(TokColon);
+                        return { };
                     }
-                    PutPropertySlot slot(object, m_nullOrCodeBlock ? m_nullOrCodeBlock->ownerExecutable()->isInStrictContext() : false);
-                    objectStack.last().put(m_globalObject, ident, lastValue, slot);
-                } else {
-                    if (std::optional<uint32_t> index = parseIndex(ident))
-                        object->putDirectIndex(m_globalObject, index.value(), lastValue);
-                    else
-                        object->putDirect(vm, ident, lastValue);
-                }
-                RETURN_IF_EXCEPTION(scope, JSValue());
-                if (m_lexer.currentToken()->type == TokComma)
-                    goto doParseObjectStartExpression;
-                if (m_lexer.currentToken()->type != TokRBrace) {
-                    m_parseErrorMessage = "Expected '}'"_s;
-                    return JSValue();
-                }
-                m_lexer.next();
-                lastValue = objectStack.takeLast();
-                break;
-            }
-            startParseExpression:
-            case StartParseExpression: {
-                switch (m_lexer.currentToken()->type) {
-                    case TokLBracket:
+
+                    TokenType nextType = m_lexer.next();
+                    if (nextType == TokLBrace || nextType == TokLBracket) {
+                        objectStack.appendWithCrashOnOverflow(object);
+                        identifierStack.append(WTFMove(ident));
+                        stateStack.append(DoParseObjectEndExpression);
+                        if (nextType == TokLBrace)
+                            goto startParseObject;
+                        ASSERT(nextType == TokLBracket);
                         goto startParseArray;
-                    case TokLBrace:
-                        goto startParseObject;
-                    case TokString: {
-                        typename Lexer::LiteralParserTokenPtr stringToken = m_lexer.currentToken();
-                        if (stringToken->stringIs8Bit)
-                            lastValue = jsString(vm, makeIdentifier(stringToken->stringToken8, stringToken->stringLength).string());
-                        else
-                            lastValue = jsString(vm, makeIdentifier(stringToken->stringToken16, stringToken->stringLength).string());
-                        m_lexer.next();
-                        break;
                     }
-                    case TokNumber: {
-                        typename Lexer::LiteralParserTokenPtr numberToken = m_lexer.currentToken();
-                        lastValue = jsNumber(numberToken->numberToken);
-                        m_lexer.next();
-                        break;
+
+                    // Leaf object construction fast path.
+                    JSValue primitive = parsePrimitiveValue(vm);
+                    if (UNLIKELY(!primitive))
+                        return { };
+
+                    if (m_mode != StrictJSON && ident == vm.propertyNames->underscoreProto) {
+                        if (UNLIKELY(!visitedUnderscoreProto.add(object).isNewEntry)) {
+                            m_parseErrorMessage = "Attempted to redefine __proto__ property"_s;
+                            return { };
+                        }
+                        PutPropertySlot slot(object, m_nullOrCodeBlock ? m_nullOrCodeBlock->ownerExecutable()->isInStrictContext() : false);
+                        JSValue(object).put(m_globalObject, ident, primitive, slot);
+                        RETURN_IF_EXCEPTION(scope, { });
+                    } else {
+                        if (std::optional<uint32_t> index = parseIndex(ident)) {
+                            object->putDirectIndex(m_globalObject, index.value(), primitive);
+                            RETURN_IF_EXCEPTION(scope, { });
+                        } else
+                            object->putDirect(vm, ident, primitive);
                     }
-                    case TokNull:
-                        m_lexer.next();
-                        lastValue = jsNull();
-                        break;
 
-                    case TokTrue:
-                        m_lexer.next();
-                        lastValue = jsBoolean(true);
+                    if (m_lexer.currentToken()->type != TokComma)
                         break;
 
-                    case TokFalse:
-                        m_lexer.next();
-                        lastValue = jsBoolean(false);
-                        break;
-                    case TokRBracket:
-                        m_parseErrorMessage = "Unexpected token ']'"_s;
-                        return JSValue();
-                    case TokRBrace:
-                        m_parseErrorMessage = "Unexpected token '}'"_s;
-                        return JSValue();
-                    case TokIdentifier: {
-                        auto token = m_lexer.currentToken();
+                    nextType = m_lexer.next();
+                    if (UNLIKELY(nextType != TokString && (m_mode == StrictJSON || nextType != TokIdentifier))) {
+                        m_parseErrorMessage = "Property name must be a string literal"_s;
+                        return { };
+                    }
+                }
 
-                        auto tryMakeErrorString = [&] (unsigned length) -> String {
-                            bool addEllipsis = length != token->stringLength;
-                            if (token->stringIs8Bit)
-                                return tryMakeString("Unexpected identifier \"", StringView { token->stringToken8, length }, addEllipsis ? "..." : "", '"');
-                            return tryMakeString("Unexpected identifier \"", StringView { token->stringToken16, length }, addEllipsis ? "..." : "", '"');
-                        };
+                if (UNLIKELY(m_lexer.currentToken()->type != TokRBrace)) {
+                    setErrorMessageForToken(TokRBrace);
+                    return { };
+                }
+                m_lexer.next();
+                lastValue = object;
+                break;
+            }
 
-                        constexpr unsigned maxLength = 200;
+            if (UNLIKELY(type != TokRBrace)) {
+                setErrorMessageForToken(TokRBrace);
+                return { };
+            }
 
-                        String errorString = tryMakeErrorString(std::min(token->stringLength, maxLength));
-                        if (!errorString) {
-                            constexpr unsigned shortLength = 10;
-                            if (token->stringLength > shortLength)
-                                errorString = tryMakeErrorString(shortLength);
-                            if (!errorString)
-                                errorString = "Unexpected identifier";
-                        }
+            m_lexer.next();
+            lastValue = object;
+            break;
+        }
+        doParseObjectStartExpression:
+        case DoParseObjectStartExpression: {
+            TokenType type = m_lexer.next();
+            if (UNLIKELY(type != TokString && (m_mode == StrictJSON || type != TokIdentifier))) {
+                m_parseErrorMessage = "Property name must be a string literal"_s;
+                return { };
+            }
+            identifierStack.append(makeIdentifier(m_lexer.currentToken()));
 
-                        m_parseErrorMessage = errorString;
-                        return JSValue();
-                    }
-                    case TokColon:
-                        m_parseErrorMessage = "Unexpected token ':'"_s;
-                        return JSValue();
-                    case TokLParen:
-                        m_parseErrorMessage = "Unexpected token '('"_s;
-                        return JSValue();
-                    case TokRParen:
-                        m_parseErrorMessage = "Unexpected token ')'"_s;
-                        return JSValue();
-                    case TokComma:
-                        m_parseErrorMessage = "Unexpected token ','"_s;
-                        return JSValue();
-                    case TokDot:
-                        m_parseErrorMessage = "Unexpected token '.'"_s;
-                        return JSValue();
-                    case TokAssign:
-                        m_parseErrorMessage = "Unexpected token '='"_s;
-                        return JSValue();
-                    case TokSemi:
-                        m_parseErrorMessage = "Unexpected token ';'"_s;
-                        return JSValue();
-                    case TokEnd:
-                        m_parseErrorMessage = "Unexpected EOF"_s;
-                        return JSValue();
-                    case TokError:
-                    default:
-                        // Error
-                        m_parseErrorMessage = "Could not parse value _expression_"_s;
-                        return JSValue();
+            // Check for colon
+            if (UNLIKELY(m_lexer.next() != TokColon)) {
+                setErrorMessageForToken(TokColon);
+                return { };
+            }
+
+            m_lexer.next();
+            stateStack.append(DoParseObjectEndExpression);
+            goto startParseExpression;
+        }
+        case DoParseObjectEndExpression:
+        {
+            JSObject* object = asObject(objectStack.last());
+            Identifier ident = identifierStack.takeLast();
+            if (m_mode != StrictJSON && ident == vm.propertyNames->underscoreProto) {
+                if (UNLIKELY(!visitedUnderscoreProto.add(object).isNewEntry)) {
+                    m_parseErrorMessage = "Attempted to redefine __proto__ property"_s;
+                    return { };
                 }
+                PutPropertySlot slot(object, m_nullOrCodeBlock ? m_nullOrCodeBlock->ownerExecutable()->isInStrictContext() : false);
+                JSValue(object).put(m_globalObject, ident, lastValue, slot);
+                RETURN_IF_EXCEPTION(scope, { });
+            } else {
+                if (std::optional<uint32_t> index = parseIndex(ident)) {
+                    object->putDirectIndex(m_globalObject, index.value(), lastValue);
+                    RETURN_IF_EXCEPTION(scope, { });
+                } else
+                    object->putDirect(vm, ident, lastValue);
+            }
+            if (m_lexer.currentToken()->type == TokComma)
+                goto doParseObjectStartExpression;
+            if (UNLIKELY(m_lexer.currentToken()->type != TokRBrace)) {
+                setErrorMessageForToken(TokRBrace);
+                return { };
+            }
+            m_lexer.next();
+            lastValue = objectStack.takeLast();
+            break;
+        }
+        startParseExpression:
+        case StartParseExpression: {
+            TokenType type = m_lexer.currentToken()->type;
+            if (type == TokLBracket)
+                goto startParseArray;
+            if (type == TokLBrace)
+                goto startParseObject;
+            lastValue = parsePrimitiveValue(vm);
+            if (UNLIKELY(!lastValue))
+                return { };
+            break;
+        }
+        case StartParseStatement: {
+            switch (m_lexer.currentToken()->type) {
+            case TokLBracket:
+            case TokNumber:
+            case TokString: {
+                lastValue = parsePrimitiveValue(vm);
+                if (UNLIKELY(!lastValue))
+                    return { };
                 break;
             }
-            case StartParseStatement: {
-                switch (m_lexer.currentToken()->type) {
-                    case TokLBracket:
-                    case TokNumber:
-                    case TokString:
-                        goto startParseExpression;
 
-                    case TokLParen: {
-                        m_lexer.next();
-                        stateStack.append(StartParseStatementEndStatement);
-                        goto startParseExpression;
-                    }
-                    case TokRBracket:
-                        m_parseErrorMessage = "Unexpected token ']'"_s;
-                        return JSValue();
-                    case TokLBrace:
-                        m_parseErrorMessage = "Unexpected token '{'"_s;
-                        return JSValue();
-                    case TokRBrace:
-                        m_parseErrorMessage = "Unexpected token '}'"_s;
-                        return JSValue();
-                    case TokIdentifier:
-                        m_parseErrorMessage = "Unexpected identifier"_s;
-                        return JSValue();
-                    case TokColon:
-                        m_parseErrorMessage = "Unexpected token ':'"_s;
-                        return JSValue();
-                    case TokRParen:
-                        m_parseErrorMessage = "Unexpected token ')'"_s;
-                        return JSValue();
-                    case TokComma:
-                        m_parseErrorMessage = "Unexpected token ','"_s;
-                        return JSValue();
-                    case TokTrue:
-                        m_parseErrorMessage = "Unexpected token 'true'"_s;
-                        return JSValue();
-                    case TokFalse:
-                        m_parseErrorMessage = "Unexpected token 'false'"_s;
-                        return JSValue();
-                    case TokNull:
-                        m_parseErrorMessage = "Unexpected token 'null'"_s;
-                        return JSValue();
-                    case TokEnd:
-                        m_parseErrorMessage = "Unexpected EOF"_s;
-                        return JSValue();
-                    case TokDot:
-                        m_parseErrorMessage = "Unexpected token '.'"_s;
-                        return JSValue();
-                    case TokAssign:
-                        m_parseErrorMessage = "Unexpected token '='"_s;
-                        return JSValue();
-                    case TokSemi:
-                        m_parseErrorMessage = "Unexpected token ';'"_s;
-                        return JSValue();
-                    case TokError:
-                    default:
-                        m_parseErrorMessage = "Could not parse statement"_s;
-                        return JSValue();
-                }
+            case TokLParen: {
+                m_lexer.next();
+                stateStack.append(StartParseStatementEndStatement);
+                goto startParseExpression;
             }
-            case StartParseStatementEndStatement: {
-                ASSERT(stateStack.isEmpty());
-                if (m_lexer.currentToken()->type != TokRParen)
-                    return JSValue();
-                if (m_lexer.next() == TokEnd)
-                    return lastValue;
-                m_parseErrorMessage = "Unexpected content at end of JSON literal"_s;
-                return JSValue();
+            case TokRBracket:
+                m_parseErrorMessage = "Unexpected token ']'"_s;
+                return { };
+            case TokLBrace:
+                m_parseErrorMessage = "Unexpected token '{'"_s;
+                return { };
+            case TokRBrace:
+                m_parseErrorMessage = "Unexpected token '}'"_s;
+                return { };
+            case TokIdentifier:
+                m_parseErrorMessage = "Unexpected identifier"_s;
+                return { };
+            case TokColon:
+                m_parseErrorMessage = "Unexpected token ':'"_s;
+                return { };
+            case TokRParen:
+                m_parseErrorMessage = "Unexpected token ')'"_s;
+                return { };
+            case TokComma:
+                m_parseErrorMessage = "Unexpected token ','"_s;
+                return { };
+            case TokTrue:
+                m_parseErrorMessage = "Unexpected token 'true'"_s;
+                return { };
+            case TokFalse:
+                m_parseErrorMessage = "Unexpected token 'false'"_s;
+                return { };
+            case TokNull:
+                m_parseErrorMessage = "Unexpected token 'null'"_s;
+                return { };
+            case TokEnd:
+                m_parseErrorMessage = "Unexpected EOF"_s;
+                return { };
+            case TokDot:
+                m_parseErrorMessage = "Unexpected token '.'"_s;
+                return { };
+            case TokAssign:
+                m_parseErrorMessage = "Unexpected token '='"_s;
+                return { };
+            case TokSemi:
+                m_parseErrorMessage = "Unexpected token ';'"_s;
+                return { };
+            case TokError:
+            default:
+                m_parseErrorMessage = "Could not parse statement"_s;
+                return { };
             }
-            default:
-                RELEASE_ASSERT_NOT_REACHED();
+            break;
         }
+        case StartParseStatementEndStatement: {
+            ASSERT(stateStack.isEmpty());
+            if (m_lexer.currentToken()->type != TokRParen)
+                return { };
+            if (m_lexer.next() == TokEnd)
+                return lastValue;
+            m_parseErrorMessage = "Unexpected content at end of JSON literal"_s;
+            return { };
+        }
+        default:
+            RELEASE_ASSERT_NOT_REACHED();
+        }
         if (stateStack.isEmpty())
             return lastValue;
         state = stateStack.takeLast();

Modified: trunk/Source/_javascript_Core/runtime/LiteralParser.h (282467 => 282468)


--- trunk/Source/_javascript_Core/runtime/LiteralParser.h	2021-09-15 20:06:42 UTC (rev 282467)
+++ trunk/Source/_javascript_Core/runtime/LiteralParser.h	2021-09-15 20:17:05 UTC (rev 282468)
@@ -52,7 +52,7 @@
     TokLBracket, TokRBracket, TokLBrace, TokRBrace,
     TokString, TokIdentifier, TokNumber, TokColon,
     TokLParen, TokRParen, TokComma, TokTrue, TokFalse,
-    TokNull, TokEnd, TokDot, TokAssign, TokSemi, TokError };
+    TokNull, TokEnd, TokDot, TokAssign, TokSemi, TokError, TokErrorSpace };
 
 struct JSONPPathEntry {
     Identifier m_pathEntryName;
@@ -138,7 +138,7 @@
         TokenType next();
         
 #if !ASSERT_ENABLED
-        typedef const LiteralParserToken<CharType>* LiteralParserTokenPtr;
+        using LiteralParserTokenPtr = const LiteralParserToken<CharType>*;
 
         LiteralParserTokenPtr currentToken()
         {
@@ -195,9 +195,14 @@
     class StackGuard;
     JSValue parse(ParserState);
 
+    JSValue parsePrimitiveValue(VM&);
+
+    ALWAYS_INLINE Identifier makeIdentifier(typename Lexer::LiteralParserTokenPtr);
     template<typename LiteralCharType>
     ALWAYS_INLINE Identifier makeIdentifier(const LiteralCharType* characters, size_t length);
 
+    void setErrorMessageForToken(TokenType);
+
     JSGlobalObject* m_globalObject;
     CodeBlock* m_nullOrCodeBlock;
     typename LiteralParser<CharType>::Lexer m_lexer;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to