[ddc] Add options to emit, read and diff delta dills for hot reload.

This will allow DartPad (which invokes DDC directly) to maintain delta dills across each reload.

Change-Id: I801208c6b8f50a0aa20b6f509aa3e32a827a9cdb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/405661
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
diff --git a/pkg/dev_compiler/lib/src/command/command.dart b/pkg/dev_compiler/lib/src/command/command.dart
index 5025d44..968158c 100644
--- a/pkg/dev_compiler/lib/src/command/command.dart
+++ b/pkg/dev_compiler/lib/src/command/command.dart
@@ -11,6 +11,8 @@
 import 'package:front_end/src/api_prototype/macros.dart' as macros
     show isMacroLibraryUri;
 import 'package:front_end/src/api_unstable/ddc.dart' as fe;
+import 'package:kernel/binary/ast_from_binary.dart' as kernel
+    show BinaryBuilder;
 import 'package:kernel/binary/ast_to_binary.dart' as kernel show BinaryPrinter;
 import 'package:kernel/class_hierarchy.dart';
 import 'package:kernel/core_types.dart';
@@ -28,6 +30,7 @@
 import '../js_ast/source_map_printer.dart' show SourceMapPrintingContext;
 import '../kernel/compiler.dart';
 import '../kernel/compiler_new.dart';
+import '../kernel/hot_reload_delta_inspector.dart';
 import '../kernel/module_metadata.dart';
 import '../kernel/module_symbols.dart';
 import '../kernel/module_symbols_collector.dart';
@@ -363,6 +366,33 @@
   var component = result.component;
   var compiledLibraries = result.compiledLibraries;
 
+  final reloadDeltaKernel = options.reloadDeltaKernel;
+  final reloadLastAcceptedKernel = options.reloadLastAcceptedKernel;
+  if (reloadDeltaKernel != null) {
+    if (reloadLastAcceptedKernel != null) {
+      final lastAcceptedComponent = Component();
+      kernel.BinaryBuilder((await File(reloadLastAcceptedKernel).readAsBytes()))
+          .readComponent(lastAcceptedComponent);
+      final deltaInspector = HotReloadDeltaInspector();
+      final rejectionReasons = deltaInspector.compareGenerations(
+          lastAcceptedComponent, compiledLibraries);
+      if (rejectionReasons.isNotEmpty) {
+        throw StateError(
+            'Hot reload rejected due to:\n${rejectionReasons.join('\n')}');
+      }
+    }
+    var sink = File(reloadDeltaKernel).openWrite();
+    kernel.BinaryPrinter(sink, includeSources: false, includeSourceBytes: false)
+        .writeComponentFile(compiledLibraries);
+    await sink.flush();
+    await sink.close();
+  } else {
+    if (reloadLastAcceptedKernel != null) {
+      throw ArgumentError("Must provide 'new-reload-delta-kernel' if "
+          "'old-reload-delta-kernel' provided.");
+    }
+  }
+
   // Output files can be written in parallel, so collect the futures.
   var outFiles = <Future>[];
   if (argResults['summarize'] as bool) {
diff --git a/pkg/dev_compiler/lib/src/command/options.dart b/pkg/dev_compiler/lib/src/command/options.dart
index 8000136..fdb3527 100644
--- a/pkg/dev_compiler/lib/src/command/options.dart
+++ b/pkg/dev_compiler/lib/src/command/options.dart
@@ -30,6 +30,9 @@
   /// expressions on demand in the current scope of a breakpoint.
   final bool emitFullCompiledKernel;
 
+  final String? reloadLastAcceptedKernel;
+  final String? reloadDeltaKernel;
+
   /// Whether to emit a summary file containing API signatures.
   ///
   /// This is required for a modular build process.
@@ -117,6 +120,8 @@
       this.emitDebugMetadata = false,
       this.emitDebugSymbols = false,
       this.emitFullCompiledKernel = false,
+      this.reloadLastAcceptedKernel,
+      this.reloadDeltaKernel,
       this.summaryModules = const {},
       this.moduleFormats = const [],
       required this.moduleName,
@@ -143,6 +148,9 @@
             emitDebugSymbols: args['emit-debug-symbols'] as bool,
             emitFullCompiledKernel:
                 args['experimental-output-compiled-kernel'] as bool,
+            reloadLastAcceptedKernel:
+                args['reload-last-accepted-kernel'] as String?,
+            reloadDeltaKernel: args['reload-delta-kernel'] as String?,
             summaryModules:
                 _parseCustomSummaryModules(args['summary'] as List<String>),
             moduleFormats: parseModuleFormatOption(args),
@@ -213,6 +221,17 @@
               'the .js output.',
           defaultsTo: false,
           hide: true)
+      ..addOption('reload-last-accepted-kernel',
+          help: 'Provides a file path to read a dill file. The enclosed kernel '
+              'will be diffed against the kernel produced by this compilation '
+              'as an incremental hot reload step.',
+          hide: true)
+      ..addOption('reload-delta-kernel',
+          help: 'Provides a file path to write a dill file to. The resulting '
+              'kernel can be passed to future compilations via '
+              '`reload-last-accepted-kernel` to get incremental hot reload '
+              'checks.',
+          hide: true)
       ..addMultiOption('precompiled-macro',
           help:
               'Configuration for precompiled macro binaries or kernel files.\n'
diff --git a/pkg/dev_compiler/lib/src/kernel/hot_reload_delta_inspector.dart b/pkg/dev_compiler/lib/src/kernel/hot_reload_delta_inspector.dart
index 5dae19e..ad0e3c2 100644
--- a/pkg/dev_compiler/lib/src/kernel/hot_reload_delta_inspector.dart
+++ b/pkg/dev_compiler/lib/src/kernel/hot_reload_delta_inspector.dart
@@ -23,7 +23,7 @@
   // TODO(nshahan): Annotate delta component with information for DDC.
   List<String> compareGenerations(Component lastAccepted, Component delta) {
     _partialLastAcceptedLibraryIndex = LibraryIndex(lastAccepted,
-        [for (var library in delta.libraries) '${library.fileUri}']);
+        [for (var library in delta.libraries) '${library.importUri}']);
     _rejectionMessages.clear();
 
     for (var library in delta.libraries) {