Flutter tool support for automatic saving of JIT compilation trace (#25301)

diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index f7d83d7..bb5e622 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -33,6 +33,18 @@
       ..addOption('route',
         help: 'Which route to load when running the app.',
       )
+      ..addFlag('save-compilation-trace',
+        hide: !verboseHelp,
+        negatable: false,
+        help: 'Save runtime compilation trace to a file.\n'
+              'Compilation trace will be saved to compilation.txt when \'flutter run\' exits. '
+              'This file contains a list of Dart symbols that were compiled by the runtime JIT '
+              'compiler up to that point. This file can be used in later --dynamic builds to '
+              'precompile some code by the offline compiler, thus reducing application startup '
+              'latency at the cost of larger application package.\n'
+              'This flag is only allowed when running --dynamic --profile (recommended) or '
+              '--debug mode.\n'
+      )
       ..addOption('target-platform',
         defaultsTo: 'default',
         allowed: <String>['default', 'android-arm', 'android-arm64'],
@@ -318,6 +330,11 @@
       }
     }
 
+    if (argResults['save-compilation-trace'] &&
+        getBuildMode() != BuildMode.debug && getBuildMode() != BuildMode.dynamicProfile)
+      throwToolExit('Error: --save-compilation-trace is only allowed when running '
+          '--dynamic --profile (recommended) or --debug mode.');
+
     final List<FlutterDevice> flutterDevices = devices.map<FlutterDevice>((Device device) {
       return FlutterDevice(
         device,
@@ -343,6 +360,7 @@
         projectRootPath: argResults['project-root'],
         packagesFilePath: globalResults['packages'],
         dillOutputPath: argResults['output-dill'],
+        saveCompilationTrace: argResults['save-compilation-trace'],
         stayResident: stayResident,
         ipv6: ipv6,
       );
@@ -355,6 +373,7 @@
         applicationBinary: applicationBinaryPath == null
             ? null
             : fs.file(applicationBinaryPath),
+        saveCompilationTrace: argResults['save-compilation-trace'],
         stayResident: stayResident,
         ipv6: ipv6,
       );
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 5be4191..5b3edf2 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -426,6 +426,7 @@
     this.usesTerminalUI = true,
     String projectRootPath,
     String packagesFilePath,
+    this.saveCompilationTrace,
     this.stayResident,
     this.ipv6,
   }) {
@@ -440,6 +441,7 @@
   final String target;
   final DebuggingOptions debuggingOptions;
   final bool usesTerminalUI;
+  final bool saveCompilationTrace;
   final bool stayResident;
   final bool ipv6;
   final Completer<int> _finished = Completer<int>();
@@ -487,6 +489,8 @@
 
   Future<void> stop() async {
     _stopped = true;
+    if (saveCompilationTrace)
+      await _saveCompilationTrace();
     await stopEchoingDeviceLog();
     await preStop();
     return stopApp();
@@ -591,6 +595,35 @@
     }
   }
 
+  Future<void> _saveCompilationTrace() async {
+    if (!supportsServiceProtocol)
+      return;
+
+    for (FlutterDevice device in flutterDevices) {
+      for (FlutterView view in device.views) {
+        final int index = device.views.indexOf(view);
+        printStatus('Saving compilation trace for '
+            '${device.device.name}${index == 0 ? '' :'/Isolate$index'}...');
+
+        List<int> buffer;
+        try {
+          buffer = await view.uiIsolate.flutterDumpCompilationTrace();
+          assert(buffer != null);
+        } catch (error) {
+          printError('Error communicating with Flutter on the device: $error');
+          continue;
+        }
+
+        final File outputFile = fs.currentDirectory
+          .childFile('compilation${index == 0 ? '' : index}.txt');
+
+        outputFile.parent.createSync(recursive: true);
+        outputFile.writeAsBytesSync(buffer);
+        printStatus('Compilation trace written to ${fs.path.relative(outputFile.path)}.');
+      }
+    }
+  }
+
   Future<void> _debugTogglePlatform() async {
     await refreshViews();
     final String from = await flutterDevices[0].views[0].uiIsolate.flutterPlatformOverride();
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index 12e3478..f85a53f 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -21,12 +21,14 @@
     bool usesTerminalUI = true,
     this.traceStartup = false,
     this.applicationBinary,
+    bool saveCompilationTrace = false,
     bool stayResident = true,
     bool ipv6 = false,
   }) : super(devices,
              target: target,
              debuggingOptions: debuggingOptions,
              usesTerminalUI: usesTerminalUI,
+             saveCompilationTrace: saveCompilationTrace,
              stayResident: stayResident,
              ipv6: ipv6);
 
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 07484de..d03e02f 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -61,6 +61,7 @@
     String projectRootPath,
     String packagesFilePath,
     this.dillOutputPath,
+    bool saveCompilationTrace = false,
     bool stayResident = true,
     bool ipv6 = false,
   }) : super(devices,
@@ -69,6 +70,7 @@
              usesTerminalUI: usesTerminalUI,
              projectRootPath: projectRootPath,
              packagesFilePath: packagesFilePath,
+             saveCompilationTrace: saveCompilationTrace,
              stayResident: stayResident,
              ipv6: ipv6);
 
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 4e355bf..a14f27e 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -1312,6 +1312,14 @@
     );
   }
 
+  Future<List<int>> flutterDumpCompilationTrace() async {
+    final Map<String, dynamic> result =
+      await invokeFlutterExtensionRpcRaw('ext.flutter.dumpCompilationTrace');
+    if (result != null && result['value'] is List<dynamic>)
+        return result['value'].cast<int>();
+    return null;
+  }
+
   // Application control extension methods.
   Future<Map<String, dynamic>> flutterExit() {
     return invokeFlutterExtensionRpcRaw(