@@ -248,6 +248,29 @@ class LibraryBundleCompiler implements old.Compiler {
248248 }
249249}
250250
251+ /// Tracks the state of which branch the compiler is on for hot reload checks.
252+ ///
253+ /// This allows hot reload generation checks to be batched into a single
254+ /// branch statement.
255+ ///
256+ /// This batched branching allows us to avoid exponential behavior when
257+ /// recursing on deeply nested checked calls. Otherwise each branch makes 2
258+ /// copies of all the sub-branches leading to 2^n checks.
259+ enum HotReloadBranchState {
260+ /// The compiler is not along any hot reload check branch yet.
261+ none,
262+
263+ /// The compiler is along the branch with no extra checks. The check rewrite
264+ /// logic will be skipped and the normal call will be generated. The root of
265+ /// this branch will include a hot reload generation check.
266+ uncheckedBranch,
267+
268+ /// The compiler is along the branch with extra checks. The check rewrite
269+ /// logic will be applied for every call that needs it. The root of this
270+ /// branch will include a hot reload generation check.
271+ checkedBranch,
272+ }
273+
251274class LibraryCompiler extends ComputeOnceConstantVisitor <js_ast.Expression >
252275 with OnceConstantVisitorDefaultMixin <js_ast.Expression >
253276 implements
@@ -256,6 +279,8 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
256279 final Options _options;
257280 final SymbolData _symbolData;
258281
282+ HotReloadBranchState hotReloadCheckedBranch = HotReloadBranchState .none;
283+
259284 /// Maps each `Class` node compiled in the module to the `Identifier` s used to
260285 /// name the class in JavaScript.
261286 ///
@@ -5207,7 +5232,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
52075232 // the sub-expressions will have the correct mapping applied.
52085233 return jsExpression.toStatement ()..sourceInformation = continueSourceMap;
52095234 }
5210- return _visitExpression (expr) .toStatement ();
5235+ return jsExpression .toStatement ();
52115236 }
52125237
52135238 @override
@@ -6176,10 +6201,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
61766201 // Since there are no arguments (unlike methods) the dynamic get path can
61776202 // be reused for the hot reload checks on a getter.
61786203 var checkedGet = _emitCast (
6179- _emitDynamicGet (
6180- _visitExpression (receiver),
6181- _emitMemberName (memberName),
6182- ),
6204+ _emitDynamicGet (jsReceiver, _emitMemberName (memberName)),
61836205 node.resultType,
61846206 )..sourceInformation = _nodeStart (node);
61856207 return _emitHotReloadSafeInvocation (instanceGet, checkedGet);
@@ -6527,11 +6549,13 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
65276549 }
65286550 var receiver = node.receiver;
65296551 var jsReceiver = _visitExpression (receiver);
6530- var jsArguments = _emitArgumentList (node.arguments, target: target);
65316552 if (node.isNativeListInvariantAddInvocation (_coreTypes.listClass)) {
65326553 // TODO(nshahan): If this code is retained, can it become invalid after a
65336554 // hot reload?
6534- return js.call ('#.push(#)' , [jsReceiver, jsArguments]);
6555+ return js.call ('#.push(#)' , [
6556+ jsReceiver,
6557+ _emitArgumentList (node.arguments, target: target),
6558+ ]);
65356559 }
65366560 var name = node.name.text;
65376561 if (name == 'call' ) {
@@ -6548,12 +6572,23 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
65486572 // names of the Object instance members.
65496573 // TODO(nshahan): What should be checked after a hot reload. I think only
65506574 // the return type of the NSM can change.
6551- return _runtimeCall ('#(#, #)' , [name, jsReceiver, jsArguments]);
6575+ return _runtimeCall ('#(#, #)' , [
6576+ name,
6577+ jsReceiver,
6578+ _emitArgumentList (node.arguments, target: target),
6579+ ]);
65526580 }
65536581 // Otherwise generate this as a normal typed method call.
65546582 var jsName = _emitMemberName (name, member: target);
6555- var invocation = js.call ('#.#(#)' , [jsReceiver, jsName, jsArguments]);
6556- if (_shouldRewriteInvocationWithHotReloadChecks (target)) {
6583+
6584+ js_ast.Expression generateCall () {
6585+ var args = _emitArgumentList (node.arguments, target: target);
6586+ return js.call ('#.#(#)' , [jsReceiver, jsName, args]);
6587+ }
6588+
6589+ // Only consider checks if we're not in the unchecked branch.
6590+ if (_shouldRewriteInvocationWithHotReloadChecks (target) &&
6591+ hotReloadCheckedBranch != HotReloadBranchState .uncheckedBranch) {
65576592 var checkedInvocation = _rewriteInvocationWithHotReloadChecks (
65586593 jsReceiver,
65596594 jsName,
@@ -6562,10 +6597,25 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
65626597 node.getStaticType (_staticTypeContext),
65636598 _nodeStart (node),
65646599 );
6600+
6601+ // If we're within the checked branch (i.e. not at the root) then return
6602+ // the checked call as-is.
6603+ if (hotReloadCheckedBranch == HotReloadBranchState .checkedBranch) {
6604+ return checkedInvocation;
6605+ }
6606+
6607+ // We're at the root of the branch so we need to generate the unchecked
6608+ // branch as well.
6609+ hotReloadCheckedBranch = HotReloadBranchState .uncheckedBranch;
6610+ final invocation = generateCall ();
6611+ hotReloadCheckedBranch = HotReloadBranchState .none;
6612+
65656613 // As an optimization, avoid extra checks when the invocation code was
65666614 // compiled in the same generation that it is running.
65676615 return _emitHotReloadSafeInvocation (invocation, checkedInvocation);
65686616 }
6617+
6618+ final invocation = generateCall ();
65696619 return _isNullCheckableJsInterop (node.interfaceTarget)
65706620 ? _wrapWithJsInteropNullCheck (invocation)
65716621 : invocation;
@@ -7641,23 +7691,43 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
76417691 }
76427692 }
76437693
7644- var fn = _emitStaticTarget (target);
7645- var args = _emitArgumentList (node.arguments, target: target);
7646- var staticCall = js_ast.Call (fn, args)
7647- ..sourceInformation = _nodeStart (node);
7648- if (_shouldRewriteInvocationWithHotReloadChecks (target)) {
7649- var checkedCall = _rewriteInvocationWithHotReloadChecks (
7694+ js_ast.Call generateCall (js_ast.PropertyAccess fn) {
7695+ var args = _emitArgumentList (node.arguments, target: target);
7696+ return js_ast.Call (fn, args)..sourceInformation = _nodeStart (node);
7697+ }
7698+
7699+ // Only consider checks if we're not in the unchecked branch.
7700+ if (_shouldRewriteInvocationWithHotReloadChecks (target) &&
7701+ hotReloadCheckedBranch != HotReloadBranchState .uncheckedBranch) {
7702+ final fn = _emitStaticTarget (target);
7703+
7704+ var checkedInvocation = _rewriteInvocationWithHotReloadChecks (
76507705 fn.receiver,
76517706 fn.selector,
76527707 target,
76537708 node.arguments,
76547709 node.getStaticType (_staticTypeContext),
76557710 _nodeStart (node),
76567711 );
7712+
7713+ // If we're within the checked branch (i.e. not at the root) then return
7714+ // the checked call as-is.
7715+ if (hotReloadCheckedBranch == HotReloadBranchState .checkedBranch) {
7716+ return checkedInvocation;
7717+ }
7718+
7719+ // We're at the root of the branch so we need to generate the unchecked
7720+ // branch as well.
7721+ hotReloadCheckedBranch = HotReloadBranchState .uncheckedBranch;
7722+ final invocation = generateCall (fn);
7723+ hotReloadCheckedBranch = HotReloadBranchState .none;
7724+
76577725 // As an optimization, avoid extra checks when the invocation code was
76587726 // compiled in the same generation that it is running.
7659- return _emitHotReloadSafeInvocation (staticCall, checkedCall );
7727+ return _emitHotReloadSafeInvocation (invocation, checkedInvocation );
76607728 }
7729+
7730+ final staticCall = generateCall (_emitStaticTarget (target));
76617731 return _isNullCheckableJsInterop (target)
76627732 ? _wrapWithJsInteropNullCheck (staticCall)
76637733 : staticCall;
@@ -7713,6 +7783,8 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
77137783 DartType expectedReturnType,
77147784 SourceLocation ? originalCallSiteSourceLocation,
77157785 ) {
7786+ final savedCheckedBranch = hotReloadCheckedBranch;
7787+ hotReloadCheckedBranch = HotReloadBranchState .checkedBranch;
77167788 var hoistedPositionalVariables = < js_ast.Expression > [];
77177789 var hoistedNamedVariables = < String , js_ast.Expression > {};
77187790 js_ast.Expression ? letAssignments;
@@ -7799,7 +7871,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
77997871 ])..sourceInformation = originalCallSiteSourceLocation;
78007872 // Cast the result of the checked call or the value returned from a
78017873 // `NoSuchMethod` invocation.
7802- return js_ast.Binary (
7874+ final result = js_ast.Binary (
78037875 ',' ,
78047876 letAssignments,
78057877 js.call ('# == # ? # : #' , [
@@ -7809,6 +7881,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
78097881 _emitCast (checkResult, expectedReturnType),
78107882 ]),
78117883 );
7884+
7885+ hotReloadCheckedBranch = savedCheckedBranch;
7886+ return result;
78127887 }
78137888
78147889 js_ast.Expression _emitJSObjectGetPrototypeOf (
0 commit comments