[dart2js] add helper script for faster iteration.

This script calls dart2js repeatedly with the same arguments, but
triggers a hot reload on every run (initiated by entering a new line
on stdin).

Change-Id: Ibdad8ad06c92de7e9b9c23a48e8ce5747ef426c9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/322570
Commit-Queue: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
diff --git a/pkg/compiler/tool/hot_reload_launcher.dart b/pkg/compiler/tool/hot_reload_launcher.dart
new file mode 100644
index 0000000..c16c595
--- /dev/null
+++ b/pkg/compiler/tool/hot_reload_launcher.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Helper script to run keep dart2js in memory and trigger a hot-reload between
+/// runs. This can speed up iterating on compiler changes, while still working
+/// with the compiler sources.
+///
+/// Usage: launch with a special VM flag to enable the vm service, which adds
+/// the necessary mechanism to support hot reloads, and passing all arguments
+/// that will be forwarded to dart2js:
+///
+/// ```
+/// out/ReleaseX64/dart --disable-dart-dev --enable-vm-service \
+///     pkg/compiler/tool/hot_reload_launcher.dart <dart2js-args>
+/// ```
+///
+/// This will do one compile immediately. After it completes, the process will
+/// wait for additional input. On every new-line entered (text is ignored), it
+/// will trigger a hot reload to refresh the compiler, then reexecute the
+/// compiler with the exact same args provided upfront.
+import 'dart:developer';
+import 'package:vm_service/vm_service_io.dart' as vm_service_io;
+
+import 'dart:io' as io;
+
+import 'package:compiler/src/dart2js.dart' as p;
+
+int iteration = 0;
+
+main(List<String> args) async {
+  try {
+    if (io.Platform.isLinux) {
+      // Calculate how long it took for the process to reach main, this gives us
+      // an estimate of how long it takes to compile dart2js and load it.
+      final result = io.Process.runSync(
+          'ps', ['--pid', '${io.pid}', '-D', '%F %T', '-o', 'lstart=']);
+      final startTimeString = (result.stdout as String).trim();
+      final startTime = DateTime.parse(startTimeString);
+      final currentTime = DateTime.now();
+      final diff = currentTime.difference(startTime).inMilliseconds;
+      print('${'--' * 20} (compiler loaded: ${diff}ms)');
+    }
+  } catch (e) {
+    print('Warning: couldn\'t compute load time [$e]');
+  }
+  final info =
+      await Service.controlWebServer(enable: true, silenceOutput: true);
+  final observatoryUri = info.serverUri;
+  if (observatoryUri == null) {
+    print('Error: VM service not found. Make sure to invoke the '
+        'Dart VM with the `--enable-vm-service` flag');
+    io.exit(1);
+  }
+  final wsUri = 'ws://${observatoryUri.authority}${observatoryUri.path}ws';
+  final vmService = await vm_service_io.vmServiceConnectUri(wsUri);
+  final vm = await vmService.getVM();
+  final id =
+      vm.isolates!.firstWhere((isolate) => !isolate.isSystemIsolate!).id!;
+
+  // Override exitFunc to prevent the defualt behavior (a process exit).
+  p.exitFunc = (code) {
+    throw "Exit with code $code";
+  };
+
+  Stopwatch watch = Stopwatch()..start();
+  while (true) {
+    print('${'--' * 20} (iteration: $iteration, '
+        'rebuild time: ${watch.elapsedMilliseconds}ms)');
+    iteration++;
+    try {
+      await p.compilerMain(args);
+    } catch (e, s) {
+      print('$e\n$s');
+    }
+    print('${'--' * 20} (done, please <enter> to compile again) ');
+    var line = io.stdin.readLineSync();
+    if (line == null) break;
+    watch.reset();
+    vmService.reloadSources(id);
+  }
+}