Fix expression evaluation failure after hot restart

The lifetime of program compilers in JavaScriptBundler is too short,
moving the program compiler cache to FrontendCompiler instead
so the ProgramCompilers created during main compilation could be reused
for expression evaluation until the next compilation.

Closes: https://github.com/dart-lang/sdk/issues/45266
Change-Id: I7d41476c7064f16cab2783f88c49114a08a0c038
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/190301
Commit-Queue: Anna Gringauze <annagrin@google.com>
Reviewed-by: Jonah Williams <jonahwilliams@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart
index 139e557..2c45796c 100644
--- a/pkg/frontend_server/lib/frontend_server.dart
+++ b/pkg/frontend_server/lib/frontend_server.dart
@@ -10,7 +10,11 @@
 
 import 'package:args/args.dart';
 import 'package:dev_compiler/dev_compiler.dart'
-    show DevCompilerTarget, ExpressionCompiler, parseModuleFormat;
+    show
+        DevCompilerTarget,
+        ExpressionCompiler,
+        parseModuleFormat,
+        ProgramCompiler;
 
 // front_end/src imports below that require lint `ignore_for_file`
 // are a temporary state of things until frontend team builds better api
@@ -631,7 +635,7 @@
     final sourceMapsFileSink = sourceMapsFile.openWrite();
     final metadataFileSink =
         emitDebugMetadata ? metadataFile.openWrite() : null;
-    await _bundler.compile(
+    final kernel2JsCompilers = await _bundler.compile(
         results.classHierarchy,
         results.coreTypes,
         results.loadedLibraries,
@@ -639,6 +643,7 @@
         manifestFileSink,
         sourceMapsFileSink,
         metadataFileSink);
+    cachedProgramCompilers.addAll(kernel2JsCompilers);
     await Future.wait([
       sourceFileSink.close(),
       manifestFileSink.close(),
@@ -806,6 +811,12 @@
     }
   }
 
+  /// Program compilers per module.
+  ///
+  /// Produced suring initial compilation of the module to JavaScript,
+  /// cached to be used for expression compilation in [compileExpressionToJs].
+  final Map<String, ProgramCompiler> cachedProgramCompilers = {};
+
   @override
   Future<Null> compileExpressionToJs(
       String libraryUri,
@@ -822,7 +833,7 @@
       reportError('JavaScript bundler is null');
       return;
     }
-    if (!_bundler.compilers.containsKey(moduleName)) {
+    if (!cachedProgramCompilers.containsKey(moduleName)) {
       reportError('Cannot find kernel2js compiler for $moduleName.');
       return;
     }
@@ -833,13 +844,13 @@
     _processedOptions.ticker
         .logMs('Compiling expression to JavaScript in $moduleName');
 
-    var kernel2jsCompiler = _bundler.compilers[moduleName];
+    final kernel2jsCompiler = cachedProgramCompilers[moduleName];
     Component component = _generator.lastKnownGoodComponent;
     component.computeCanonicalNames();
 
     _processedOptions.ticker.logMs('Computed component');
 
-    var expressionCompiler = new ExpressionCompiler(
+    final expressionCompiler = new ExpressionCompiler(
       _compilerOptions,
       parseModuleFormat(_options['dartdevc-module-format'] as String),
       errors,
@@ -848,10 +859,10 @@
       component,
     );
 
-    var procedure = await expressionCompiler.compileExpressionToJs(
+    final procedure = await expressionCompiler.compileExpressionToJs(
         libraryUri, line, column, jsFrameValues, expression);
 
-    var result = errors.length > 0 ? errors[0] : procedure;
+    final result = errors.length > 0 ? errors[0] : procedure;
 
     // TODO(annagrin): kernelBinaryFilename is too specific
     // rename to _outputFileName?
diff --git a/pkg/frontend_server/lib/src/javascript_bundle.dart b/pkg/frontend_server/lib/src/javascript_bundle.dart
index 346b4d8..9829c57 100644
--- a/pkg/frontend_server/lib/src/javascript_bundle.dart
+++ b/pkg/frontend_server/lib/src/javascript_bundle.dart
@@ -29,8 +29,7 @@
       this.emitDebugMetadata = false,
       this.soundNullSafety = false,
       String moduleFormat})
-      : compilers = <String, ProgramCompiler>{},
-        _moduleFormat = parseModuleFormat(moduleFormat ?? 'amd') {
+      : _moduleFormat = parseModuleFormat(moduleFormat ?? 'amd') {
     _summaries = <Component>[];
     _summaryUris = <Uri>[];
     _moduleImportForSummary = <Uri, String>{};
@@ -64,7 +63,6 @@
   final PackageConfig _packageConfig;
   final bool useDebuggerModuleNames;
   final bool emitDebugMetadata;
-  final Map<String, ProgramCompiler> compilers;
   final ModuleFormat _moduleFormat;
   final bool soundNullSafety;
 
@@ -75,7 +73,7 @@
   Map<Uri, Component> _uriToComponent;
 
   /// Compile each component into a single JavaScript module.
-  Future<void> compile(
+  Future<Map<String, ProgramCompiler>> compile(
       ClassHierarchy classHierarchy,
       CoreTypes coreTypes,
       Set<Library> loadedLibraries,
@@ -88,6 +86,7 @@
     var metadataOffset = 0;
     final manifest = <String, Map<String, List<int>>>{};
     final Set<Uri> visited = <Uri>{};
+    final Map<String, ProgramCompiler> kernel2JsCompilers = {};
 
     final importToSummary = Map<Library, Component>.identity();
     final summaryToModule = Map<Component, String>.identity();
@@ -150,9 +149,8 @@
       // so it can map dart symbols to js symbols
       // [issue 40273](https://github.com/dart-lang/sdk/issues/40273)
 
-      // program compiler is used by ExpressionCompiler to evaluate expressions
-      // on demand
-      compilers[moduleName] = compiler;
+      // Save program compiler to reuse for expression evaluation.
+      kernel2JsCompilers[moduleName] = compiler;
 
       final moduleUrl = urlForComponentUri(moduleUri);
       String sourceMapBase;
@@ -201,6 +199,8 @@
       };
     }
     manifestSink.add(utf8.encode(json.encode(manifest)));
+
+    return kernel2JsCompilers;
   }
 }
 
diff --git a/pkg/frontend_server/test/src/javascript_bundle_test.dart b/pkg/frontend_server/test/src/javascript_bundle_test.dart
index 26a7c21..02cd813 100644
--- a/pkg/frontend_server/test/src/javascript_bundle_test.dart
+++ b/pkg/frontend_server/test/src/javascript_bundle_test.dart
@@ -106,8 +106,14 @@
     final metadataSink = _MemorySink();
     final coreTypes = CoreTypes(testComponent);
 
-    await javaScriptBundler.compile(ClassHierarchy(testComponent, coreTypes),
-        coreTypes, {}, codeSink, manifestSink, sourcemapSink, metadataSink);
+    final compilers = await javaScriptBundler.compile(
+        ClassHierarchy(testComponent, coreTypes),
+        coreTypes,
+        {},
+        codeSink,
+        manifestSink,
+        sourcemapSink,
+        metadataSink);
 
     final Map manifest = json.decode(utf8.decode(manifestSink.buffer));
     final String code = utf8.decode(codeSink.buffer);
@@ -122,6 +128,9 @@
 
     // verify source map url is correct.
     expect(code, contains('sourceMappingURL=c.dart.lib.js.map'));
+
+    // verify program compilers are created.
+    expect(compilers.keys, equals([urlForComponentUri(library.importUri)]));
   });
 
   test('converts package: uris into /packages/ uris', () async {