From 06402ab1ca284cfd394a8bfb7fddbcbe242fc86e Mon Sep 17 00:00:00 2001 From: Jens Johansen Date: Wed, 30 Jul 2025 07:05:12 -0700 Subject: [PATCH] [parser] Optimize mayFollowTypeArgs Doing benchmarks before and after I get the below. Notice that this is on tokens/ms, i.e. higher is better. JIT: pkg/analyzer/lib/src/dart/ast/ast.dart: ``` Difference at 95.0% confidence 1.27922 +/- 0.393002 6.47159% +/- 1.9882% (Student's t, pooled s = 0.418268) ``` pkg/front_end/lib/src/type_inference/inference_visitor.dart: ``` Difference at 95.0% confidence 0.968609 +/- 0.287367 6.8824% +/- 2.04187% (Student's t, pooled s = 0.305841) ``` In both cases it processes a little over 6% more tokens per ms. AOT: pkg/analyzer/lib/src/dart/ast/ast.dart: ``` Difference at 95.0% confidence 2.39988 +/- 0.429312 9.50898% +/- 1.70105% (Student's t, pooled s = 0.456911) ``` pkg/front_end/lib/src/type_inference/inference_visitor.dart: ``` Difference at 95.0% confidence 2.28604 +/- 0.279366 12.9521% +/- 1.58282% (Student's t, pooled s = 0.297326) ``` It processes between 9-13% more tokens per ms. Additionally, for AOT, I ran it through the benchmarker and got this: pkg/analyzer/lib/src/dart/ast/ast.dart: ``` msec task-clock:u: -8.5488% +/- 2.1840% (-275.52 +/- 70.39) (3222.95 -> 2947.42) cycles:u: -8.6402% +/- 2.1619% (-1210657004.60 +/- 302921394.06) (14011942063.40 -> 12801285058.80) instructions:u: -9.0870% +/- 0.0000% (-2746030853.20 +/- 876.33) (30219334757.30 -> 27473303904.10) branch-misses:u: -13.3846% +/- 10.1850% (-5542357.40 +/- 4217459.09) (41408381.20 -> 35866023.80) seconds time elapsed: -8.5442% +/- 2.1816% (-0.28 +/- 0.07) (3.22 -> 2.95) seconds user: -8.5614% +/- 2.2098% (-0.27 +/- 0.07) (3.20 -> 2.93) ``` pkg/front_end/lib/src/type_inference/inference_visitor.dart ``` msec task-clock:u: -11.6033% +/- 0.9079% (-402.51 +/- 31.50) (3468.96 -> 3066.45) cycles:u: -11.6826% +/- 0.9053% (-1765194611.60 +/- 136793475.00) (15109578869.80 -> 13344384258.20) instructions:u: -11.4435% +/- 0.0000% (-3905886611.70 +/- 1150.33) (34131775222.10 -> 30225888610.40) branch-misses:u: -17.5244% +/- 4.6453% (-8196278.70 +/- 2172645.25) (46770630.60 -> 38574351.90) seconds time elapsed: -11.6004% +/- 0.9056% (-0.40 +/- 0.03) (3.47 -> 3.07) seconds user: -11.6215% +/- 0.9879% (-0.40 +/- 0.03) (3.45 -> 3.05) ``` I.e. in both cases it's 8-11% less work (instructions, time etc). Compiling the CFE with the CFE before and after (AOT) I get this change: ``` msec task-clock:u: -0.4356% +/- 0.3110% (-23.07 +/- 16.47) (5295.49 -> 5272.42) page-faults:u: 0.1135% +/- 0.0534% (123.64 +/- 58.22) (108956.82 -> 109080.46) cycles:u: -0.5004% +/- 0.3175% (-109702416.52 +/- 69596415.67) (21921571730.18 -> 21811869313.66) instructions:u: -0.3994% +/- 0.0005% (-104979706.80 +/- 138268.02) (26284569470.80 -> 26179589764.00) branch-misses:u: -1.6117% +/- 1.4644% (-1285239.90 +/- 1167813.84) (79746730.52 -> 78461490.62) seconds time elapsed: -0.4291% +/- 0.3090% (-0.02 +/- 0.02) (5.30 -> 5.28) seconds user: -0.3992% +/- 0.3801% (-0.02 +/- 0.02) (5.04 -> 5.02) ``` So this improvement to the parser reduces the runtime of compiling by ~0.4%. Change-Id: Ia89d02e8cce2655fb23ffe18ecbab63521ce75e3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/439721 Commit-Queue: Jens Johansen Reviewed-by: Johnni Winther --- .../lib/src/parser/type_info.dart | 91 +++++++++++++++---- 1 file changed, 73 insertions(+), 18 deletions(-) diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/type_info.dart b/pkg/_fe_analyzer_shared/lib/src/parser/type_info.dart index c07256117d3..af5e93e2c5a 100644 --- a/pkg/_fe_analyzer_shared/lib/src/parser/type_info.dart +++ b/pkg/_fe_analyzer_shared/lib/src/parser/type_info.dart @@ -470,7 +470,8 @@ TypeParamOrArgInfo computeTypeParamOrArg( /// possible other constructs will pass (e.g., 'a < C, D > 3'). TypeParamOrArgInfo computeMethodTypeArguments(Token token) { TypeParamOrArgInfo typeArg = computeTypeParamOrArg(token); - return mayFollowTypeArgs(typeArg.skip(token).next!) && !typeArg.recovered + return _mayFollowTypeArgs(typeArg.skip(token).next!.typeIndex) && + !typeArg.recovered ? typeArg : noTypeParamOrArg; } @@ -480,8 +481,10 @@ TypeParamOrArgInfo computeMethodTypeArguments(Token token) { /// pattern. const Set illegalPatternIdentifiers = {'when', 'as'}; -/// Indicates whether the given [token] is allowed to follow a list of type -/// arguments used as a selector after an expression. +/// Indicates whether the given [tokenTypeIndex] is allowed to follow a list of +/// type arguments used as a selector after an expression. +/// +/// Get the index from a token via `Token.typeIndex`. /// /// This is used for disambiguating constructs like `f(a(d))` and /// `f(a-d)`. In the case of `f(a(d))`, `true` will be returned, @@ -490,19 +493,71 @@ const Set illegalPatternIdentifiers = {'when', 'as'}; /// function `a`). In the case of `f(a-d)`, `false` will be returned, /// indicating that the `<` and `>` should be interpreted as operators (so two /// arguments are being passed to `f`: `a < b` and `c > -d`). -bool mayFollowTypeArgs(Token token) { - const Set continuationTokens = {'(', '.', '==', '!='}; - const Set stopTokens = {')', ']', '}', ';', ':', ','}; - const Set tokensThatMayFollowTypeArg = { - ...continuationTokens, - ...stopTokens, - }; - if (token.isA(TokenType.EOF)) { - // The spec doesn't have anything to say about this case, since an - // expression can't occur at the end of a file, but for testing it's to our - // advantage to allow EOF after type arguments, so that an isolated `f` - // can be parsed as an expression. - return true; - } - return tokensThatMayFollowTypeArg.contains(token.lexeme); +/// +// DartDocTest(() { +// for (int i = 0; i < 256; i++) { +// if (_mayFollowTypeArgs(i) != +// _mayFollowTypeArgs_helper_for_testing(i)) { +// return false; +// } +// } +// return true; +// }(), true); +@pragma("vm:prefer-inline") +bool _mayFollowTypeArgs(int tokenTypeIndex) { + // Table has size 256 to avoid bounds checks as this is called with + // `Token.typeIndex` which is know to be in [0-255]. + const List table = [ + // format hack. + true, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, true, false, false, false, false, false, + true, true, false, false, true, true, true, false, + true, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, true, false, false, false, + true, false, false, false, false, false, false, false, + false, true, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + // format hack. + ]; + + return table[tokenTypeIndex]; +} + +// ignore: unused_element +bool _mayFollowTypeArgs_helper_for_testing(int tokenTypeIndex) { + return tokenTypeIndex == TokenType.OPEN_PAREN.index || + tokenTypeIndex == TokenType.PERIOD.index || + tokenTypeIndex == TokenType.EQ_EQ.index || + tokenTypeIndex == TokenType.BANG_EQ.index || + tokenTypeIndex == TokenType.CLOSE_PAREN.index || + tokenTypeIndex == TokenType.CLOSE_SQUARE_BRACKET.index || + tokenTypeIndex == TokenType.CLOSE_CURLY_BRACKET.index || + tokenTypeIndex == TokenType.SEMICOLON.index || + tokenTypeIndex == TokenType.COLON.index || + tokenTypeIndex == TokenType.COMMA.index || + tokenTypeIndex == TokenType.EOF.index; }