Version 2.18.0-20.0.dev

Merge commit 'a823f54c9337b040cc9a0571d5a7b51248fdee23' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/pubspec.yaml b/pkg/_fe_analyzer_shared/pubspec.yaml
index e8855e1..4a30fc7 100644
--- a/pkg/_fe_analyzer_shared/pubspec.yaml
+++ b/pkg/_fe_analyzer_shared/pubspec.yaml
@@ -1,5 +1,5 @@
 name: _fe_analyzer_shared
-version: 38.0.0
+version: 39.0.0
 description: Logic that is shared between the front_end and analyzer packages.
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/_fe_analyzer_shared
 
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
index bf2dee5..689dd80 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
@@ -752,11 +752,9 @@
     );
   }
 
-  /// Add a suggestion for a [method]. If the method is being invoked with a
-  /// target of `super`, then the [containingMemberName] should be the name of
-  /// the member containing the invocation. If a [kind] is provided it will be
-  /// used as the kind for the suggestion. The [inheritanceDistance] is the
-  /// value of the inheritance distance feature computed for the method.
+  /// Add a suggestion for a [method]. If a [kind] is provided it will be used
+  /// as the kind for the suggestion. The [inheritanceDistance] is the value of
+  /// the inheritance distance feature computed for the method.
   void suggestMethod(MethodElement method,
       {required CompletionSuggestionKind kind,
       required double inheritanceDistance}) {
@@ -1047,10 +1045,8 @@
     );
   }
 
-  /// Add a suggestion for a top-level property [accessor]. If a [kind] is
-  /// provided it will be used as the kind for the suggestion. If the accessor
-  /// can only be referenced using a prefix, then the [prefix] should be
-  /// provided.
+  /// Add a suggestion for a top-level property [accessor]. If the accessor can
+  /// only be referenced using a prefix, then the [prefix] should be provided.
   void suggestTopLevelPropertyAccessor(PropertyAccessorElement accessor,
       {String? prefix}) {
     assert(
@@ -1102,8 +1098,7 @@
     }
   }
 
-  /// Add a suggestion for a top-level [variable]. If a [kind] is provided it
-  /// will be used as the kind for the suggestion. If the variable can only be
+  /// Add a suggestion for a top-level [variable]. If the variable can only be
   /// referenced using a prefix, then the [prefix] should be provided.
   void suggestTopLevelVariable(TopLevelVariableElement variable,
       {String? prefix}) {
@@ -1121,9 +1116,8 @@
     );
   }
 
-  /// Add a suggestion for a [typeAlias]. If a [kind] is provided it
-  /// will be used as the kind for the suggestion. If the alias can only be
-  /// referenced using a prefix, then the [prefix] should be provided.
+  /// Add a suggestion for a [typeAlias]. If the alias can only be referenced
+  /// using a prefix, then the [prefix] should be provided.
   void suggestTypeAlias(TypeAliasElement typeAlias, {String? prefix}) {
     var relevance = _computeTopLevelRelevance(typeAlias,
         elementType: _instantiateTypeAlias(typeAlias));
diff --git a/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart b/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart
index 639b3eb..9702d0e 100644
--- a/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart
+++ b/pkg/analysis_server/test/tool/completion_metrics/metrics_util_test.dart
@@ -161,6 +161,57 @@
     });
   });
 
+  group('PercentileComputer', () {
+    test('empty', () {
+      var computer = PercentileComputer('empty', valueLimit: 2000);
+      expect(computer.median, equals(0));
+      expect(computer.valueCount, equals(0));
+    });
+
+    test('clear', () {
+      var computer = PercentileComputer('name', valueLimit: 2000);
+      computer.addValue(4);
+      computer.addValue(5);
+      computer.addValue(6);
+      computer.addValue(3000);
+
+      expect(computer.median, equals(5));
+      expect(computer.valueCount, equals(3));
+      expect(computer.aboveValueMaxCount, equals(1));
+      computer.clear();
+
+      expect(computer.median, equals(0));
+      expect(computer.valueCount, equals(0));
+      expect(computer.aboveValueMaxCount, equals(0));
+      expect(computer.aboveValueMaxSamples, isEmpty);
+    });
+
+    test('percentiles', () {
+      var computer = PercentileComputer('name', valueLimit: 2000);
+      for (var i = 0; i < 100; i++) {
+        computer.addValue(i);
+      }
+
+      expect(computer.median, equals(50));
+      expect(computer.p90, equals(90));
+      expect(computer.p95, equals(95));
+    });
+
+    test('values above maxValue', () {
+      var computer = PercentileComputer('name', valueLimit: 2000);
+
+      computer.addValue(1);
+      computer.addValue(2);
+      computer.addValue(3);
+      computer.addValue(2500);
+      computer.addValue(3000);
+
+      expect(computer.median, equals(2));
+      expect(computer.aboveValueMaxCount, equals(2));
+      expect(computer.aboveValueMaxSamples, equals([2500, 3000]));
+    });
+  });
+
   group('Place', () {
     test('none', () {
       var place = Place.none();
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics.dart b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
index fcc4650..ed88f0c 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
@@ -66,10 +66,13 @@
 ///
 /// This approach has several drawbacks:
 ///
-/// - The AST is always complete and correct, and that's rarely the case for
-///   real completion requests. Usually the tree is incomplete and often has a
-///   completely different structure because of the way recovery works. We
-///   currently have no way of measuring completions under realistic conditions.
+/// - The options for creating an "in-progress" file are limited. In the default
+///   'overlay' mode, the AST is always complete and correct, rarely the case
+///   for real completion requests. The other 'overlay' modes generate
+///   incomplete ASTs with error recovery nodes, but neither of these quite
+///   properly emulate the act of editing the middle of a file, perhaps the
+///   middle of an expression, or the middle of an argument list. We currently
+///   have no way of measuring completions under realistic conditions.
 ///
 /// - We can't measure completions for several keywords because the presence of
 ///   the keyword in the AST causes it to not be suggested.
@@ -77,7 +80,7 @@
 /// - The time it takes to compute the suggestions doesn't include the time
 ///   required to finish analyzing the file if the analysis hasn't been
 ///   completed before suggestions are requested. While the times are accurate
-///   (within the accuracy of the `Stopwatch` class) they are the minimum
+///   (within the accuracy of the [Stopwatch] class) they are the minimum
 ///   possible time. This doesn't give us a measure of how completion will
 ///   perform in production, but does give us an optimistic approximation.
 ///
@@ -193,7 +196,7 @@
     ..addFlag(CompletionMetricsOptions.PRINT_SHADOWED_COMPLETION_DETAILS,
         defaultsTo: false,
         help: 'Print detailed information every time a completion request '
-            'produces a suggestions whose name matches the expected suggestion '
+            'produces a suggestion whose name matches the expected suggestion '
             'but that is referencing a different element',
         negatable: false)
     ..addFlag(CompletionMetricsOptions.PRINT_SLOWEST_RESULTS,
@@ -332,6 +335,11 @@
   final ArithmeticMeanComputer meanCompletionMS =
       ArithmeticMeanComputer('ms per completion');
 
+  /// A percentile computer for the ms per completion request, using 2.000
+  /// seconds as the max value to use in percentile calculations.
+  final PercentileComputer percentileCompletionMS =
+      PercentileComputer('ms per completion', valueLimit: 2000);
+
   final DistributionComputer distributionCompletionMS = DistributionComputer();
 
   final MeanReciprocalRankComputer mrrComputer =
@@ -398,6 +406,8 @@
         .fromJson(map['completionElementKindCounter'] as Map<String, dynamic>);
     metrics.meanCompletionMS
         .fromJson(map['meanCompletionMS'] as Map<String, dynamic>);
+    metrics.percentileCompletionMS
+        .fromJson(map['percentileMS'] as Map<String, dynamic>);
     metrics.distributionCompletionMS
         .fromJson(map['distributionCompletionMS'] as Map<String, dynamic>);
     metrics.mrrComputer.fromJson(map['mrrComputer'] as Map<String, dynamic>);
@@ -453,6 +463,7 @@
     completionKindCounter.addData(metrics.completionKindCounter);
     completionElementKindCounter.addData(metrics.completionElementKindCounter);
     meanCompletionMS.addData(metrics.meanCompletionMS);
+    percentileCompletionMS.addData(metrics.percentileCompletionMS);
     distributionCompletionMS.addData(metrics.distributionCompletionMS);
     mrrComputer.addData(metrics.mrrComputer);
     successfulMrrComputer.addData(metrics.successfulMrrComputer);
@@ -545,6 +556,7 @@
       'completionKindCounter': completionKindCounter.toJson(),
       'completionElementKindCounter': completionElementKindCounter.toJson(),
       'meanCompletionMS': meanCompletionMS.toJson(),
+      'percentileCompletionMS': percentileCompletionMS.toJson(),
       'distributionCompletionMS': distributionCompletionMS.toJson(),
       'mrrComputer': mrrComputer.toJson(),
       'successfulMrrComputer': successfulMrrComputer.toJson(),
@@ -617,6 +629,7 @@
   /// Record this elapsed ms count for the average ms count.
   void _recordTime(CompletionResult result) {
     meanCompletionMS.addValue(result.elapsedMS);
+    percentileCompletionMS.addValue(result.elapsedMS);
     distributionCompletionMS.addValue(result.elapsedMS);
   }
 
@@ -815,7 +828,7 @@
 
   void printComparisonOfCompletionCounts() {
     String toString(int count, int totalCount) {
-      return '$count (${printPercentage(count / totalCount, 2)})';
+      return '$count (${(count / totalCount).asPercentage(2)})';
     }
 
     var counters = targetMetrics.map((metrics) => metrics.completionCounter);
@@ -1093,25 +1106,45 @@
   }
 
   void printOtherMetrics(CompletionMetrics metrics) {
-    List<String> toRow(ArithmeticMeanComputer computer) {
-      var min = computer.min;
-      var mean = computer.mean.toStringAsFixed(6);
-      var max = computer.max;
-      return [computer.name, '$min, $mean, $max'];
+    List<String> meanComputingRow(ArithmeticMeanComputer computer) {
+      return [
+        computer.name,
+        computer.min!.toStringAsFixed(3),
+        computer.mean.toStringAsFixed(3),
+        computer.max!.toStringAsFixed(3),
+      ];
     }
 
     var table = [
-      toRow(metrics.meanCompletionMS),
-      toRow(metrics.charsBeforeTop),
-      toRow(metrics.charsBeforeTopFive),
-      toRow(metrics.insertionLengthTheoretical),
+      ['', 'min', 'mean', 'max'],
+      meanComputingRow(metrics.meanCompletionMS),
+      meanComputingRow(metrics.charsBeforeTop),
+      meanComputingRow(metrics.charsBeforeTopFive),
+      meanComputingRow(metrics.insertionLengthTheoretical),
     ];
     rightJustifyColumns(table, range(1, table[0].length));
 
     printHeading(2, 'Other metrics');
     printTable(table);
 
+    var percentileTable = [
+      ['', 'p50', 'p90', 'p95', 'count > 2s', 'max'],
+      [
+        metrics.percentileCompletionMS.name,
+        metrics.percentileCompletionMS.median.toString(),
+        metrics.percentileCompletionMS.p90.toString(),
+        metrics.percentileCompletionMS.p95.toString(),
+        metrics.percentileCompletionMS.aboveValueMaxCount.toString(),
+        metrics.percentileCompletionMS.maxValue.toString(),
+      ],
+    ];
+    rightJustifyColumns(percentileTable, range(1, percentileTable[1].length));
+
+    printHeading(3, 'Percentile metrics');
+    printTable(percentileTable);
+
     var distribution = metrics.distributionCompletionMS.displayString();
+    printHeading(3, 'Completion ms distribution');
     print('${metrics.name}: $distribution');
     print('');
   }
@@ -2083,6 +2116,12 @@
   }
 }
 
+extension on num {
+  String asPercentage([int fractionDigits = 1]) =>
+      '${(this * 100).toStringAsFixed(fractionDigits)}%'
+          .padLeft(4 + fractionDigits);
+}
+
 extension AvailableSuggestionsExtension on protocol.AvailableSuggestion {
   // TODO(jwren) I am not sure if we want CompletionSuggestionKind.INVOCATION in
   // call cases here, to iterate I need to figure out why this algorithm is
diff --git a/pkg/analysis_server/tool/code_completion/metrics_util.dart b/pkg/analysis_server/tool/code_completion/metrics_util.dart
index 673f1bb..8899842 100644
--- a/pkg/analysis_server/tool/code_completion/metrics_util.dart
+++ b/pkg/analysis_server/tool/code_completion/metrics_util.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:math' as math;
+import 'dart:typed_data';
 
 import 'package:analysis_server/src/status/pages.dart';
 
@@ -295,6 +296,136 @@
   }
 }
 
+/// A computer for calculating percentile-based metrics on a data set.
+///
+/// Specifically this tracks p50 (the median), p90, and p95.
+///
+/// See https://en.wikipedia.org/wiki/Percentile.
+class PercentileComputer {
+  final String name;
+
+  /// The value limit allowed by this computer.
+  ///
+  /// The computer can calculate percentile values for all data that range
+  /// between 0 and [valueLimit], exclusive.
+  int valueLimit;
+
+  /// An array of counts; the value at each index _i_ is the number of
+  /// occurrences of value _i_.
+  ///
+  /// Any values larger than [valueLimit] are not counted here.
+  Uint32List _counts;
+
+  /// The number of values which are less than [valueLimit].
+  int valueCount = 0;
+
+  /// The number of values greater than [valueLimit].
+  int aboveValueMaxCount = 0;
+
+  List<int> aboveValueMaxSamples = [];
+
+  int maxValue = 0;
+
+  PercentileComputer(this.name, {required this.valueLimit})
+      : _counts = Uint32List(valueLimit);
+
+  /// Calculates the median (p50) value.
+  int get median => kthPercentile(50);
+
+  /// Calculates the p90 value; the value at the 90th percentile of the data.
+  int get p90 => kthPercentile(90);
+
+  /// Calculates the p95 value; the value at the 95th percentile of the data.
+  int get p95 => kthPercentile(95);
+
+  /// Add the data from the given [computer] to this computer.
+  void addData(PercentileComputer computer) {
+    if (computer.valueLimit != valueLimit) {
+      throw UnsupportedError(
+          'Cannot combine two PercentileComputers with different valueLimit '
+          'values');
+    }
+    for (var i = 0; i < _counts.length; i++) {
+      _counts[i] += computer._counts[i];
+    }
+    valueCount += computer.valueCount;
+    aboveValueMaxCount += computer.aboveValueMaxCount;
+    for (var val in computer.aboveValueMaxSamples) {
+      if (aboveValueMaxSamples.length < 10) {
+        aboveValueMaxSamples.add(val);
+      }
+    }
+    maxValue = math.max(maxValue, computer.maxValue);
+  }
+
+  void addValue(int val) {
+    if (val > valueLimit) {
+      aboveValueMaxCount++;
+      if (aboveValueMaxSamples.length < 10) {
+        aboveValueMaxSamples.add(val);
+      }
+    } else {
+      _counts[val]++;
+      valueCount++;
+    }
+    maxValue = math.max(maxValue, val);
+  }
+
+  void clear() {
+    _counts = Uint32List(0);
+    valueCount = 0;
+    aboveValueMaxCount = 0;
+    aboveValueMaxSamples = [];
+    maxValue = 0;
+  }
+
+  /// Set the state of this computer to the state recorded in the decoded JSON
+  /// [map].
+  void fromJson(Map<String, dynamic> map) {
+    valueLimit = map['valueLimit'] as int;
+    _counts = Uint32List.fromList((map['counts'] as List<dynamic>).cast<int>());
+    valueCount = map['valueCount'] as int;
+    aboveValueMaxCount = map['aboveValueMaxCount'] as int;
+    aboveValueMaxSamples =
+        (map['aboveValueMaxSamples'] as List<dynamic>).cast<int>();
+    maxValue = map['maxValue'] as int;
+  }
+
+  /// Calculates the value at the _k_th percentile of the data.
+  int kthPercentile(int percentile) {
+    if (valueCount == 0) {
+      return 0;
+    }
+    // Linear walk through the data takes O([maxValue]) time. If this is too
+    // slow, a binary search can be implemented.
+    var targetIndex = valueCount * percentile / 100;
+    // The number of values represented by walking the counts.
+    var accumulation = 0;
+    for (var i = 0; i < _counts.length; i++) {
+      accumulation += _counts[i];
+      if (accumulation > targetIndex) {
+        // We've now accounted for [targetIndex] values, which includes the
+        // median value.
+        return i;
+      }
+    }
+    // The median value is in the very highest expected possible value.
+    return valueLimit;
+  }
+
+  /// Return a map used to represent this computer in a JSON structure.
+  Map<String, dynamic> toJson() {
+    return {
+      'counts': _counts,
+      'valueLimit': valueLimit,
+      'valueCount': valueCount,
+      'aboveValueMaxCount': aboveValueMaxCount,
+      'aboveValueMaxSamples': aboveValueMaxSamples,
+      'maxValue': maxValue,
+    };
+  }
+}
+
 /// An immutable class to represent the placement in some list, for example '2nd
 /// place out of 5'.
 class Place {
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
index 5ff3292..85f3551 100644
--- a/pkg/analyzer/CHANGELOG.md
+++ b/pkg/analyzer/CHANGELOG.md
@@ -1,4 +1,4 @@
-## 4.0.0 (Not yet released - breaking changes)
+## 4.0.0
 * Removed deprecated `UriKind` and `Source.uriKind`.
 * Removed deprecated `LibraryElement.hasExtUri`.
 * Removed deprecated `LibraryElement.hasLoadLibraryFunction`.
diff --git a/pkg/analyzer/pubspec.yaml b/pkg/analyzer/pubspec.yaml
index e0b366d..fdeb71c 100644
--- a/pkg/analyzer/pubspec.yaml
+++ b/pkg/analyzer/pubspec.yaml
@@ -1,5 +1,5 @@
 name: analyzer
-version: 4.0.0-dev
+version: 4.0.0
 description: This package provides a library that performs static analysis of Dart code.
 homepage: https://github.com/dart-lang/sdk/tree/main/pkg/analyzer
 
@@ -7,7 +7,7 @@
   sdk: '>=2.14.0 <3.0.0'
 
 dependencies:
-  _fe_analyzer_shared: ^38.0.0
+  _fe_analyzer_shared: ^39.0.0
   collection: ^1.15.0
   convert: ^3.0.0
   crypto: ^3.0.0
diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart
index 2a303b8..a2c2d4e 100644
--- a/pkg/frontend_server/lib/frontend_server.dart
+++ b/pkg/frontend_server/lib/frontend_server.dart
@@ -36,6 +36,7 @@
 import 'package:vm/incremental_compiler.dart' show IncrementalCompiler;
 import 'package:vm/kernel_front_end.dart';
 
+import 'src/binary_protocol.dart';
 import 'src/javascript_bundle.dart';
 import 'src/strong_components.dart';
 
@@ -114,6 +115,10 @@
           ' option',
       defaultsTo: 'org-dartlang-root',
       hide: true)
+  ..addOption('binary-protocol-address',
+      hide: true,
+      help: 'The server will establish TCP connection to this address, and'
+          ' will exchange binary requests and responses with the client.')
   ..addFlag('enable-http-uris',
       defaultsTo: false, hide: true, help: 'Enables support for http uris.')
   ..addFlag('verbose', help: 'Enables verbose output from the compiler.')
@@ -1421,6 +1426,12 @@
     }
   }
 
+  final binaryProtocolAddressStr = options['binary-protocol-address'];
+  if (binaryProtocolAddressStr is String) {
+    runBinaryProtocol(binaryProtocolAddressStr);
+    return 0;
+  }
+
   compiler ??= FrontendCompiler(output,
       printerFactory: binaryPrinterFactory,
       unsafePackageSerialization: options["unsafe-package-serialization"],
diff --git a/pkg/frontend_server/lib/src/binary_protocol.dart b/pkg/frontend_server/lib/src/binary_protocol.dart
new file mode 100644
index 0000000..25fc45d
--- /dev/null
+++ b/pkg/frontend_server/lib/src/binary_protocol.dart
@@ -0,0 +1,269 @@
+// Copyright (c) 2022, 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.
+
+// @dart = 2.15
+import 'dart:async';
+import 'dart:io' as io;
+import 'dart:typed_data';
+
+import 'package:_fe_analyzer_shared/src/macros/compiler/request_channel.dart';
+import 'package:front_end/src/api_prototype/compiler_options.dart' as fe;
+import 'package:front_end/src/api_prototype/file_system.dart' as fe;
+import 'package:front_end/src/api_prototype/kernel_generator.dart' as fe;
+import 'package:front_end/src/fasta/kernel/utils.dart' as fe;
+import 'package:kernel/ast.dart' as fe;
+import 'package:kernel/target/targets.dart' as fe;
+import 'package:vm/kernel_front_end.dart' as vm;
+import 'package:vm/target/vm.dart' as vm;
+
+Future<void> runBinaryProtocol(String addressStr) async {
+  final parsedAddress = _ParsedAddress.parse(addressStr);
+  final socket = await io.Socket.connect(
+    parsedAddress.host,
+    parsedAddress.port,
+  );
+
+  _Client(
+    RequestChannel(socket),
+    () {
+      socket.destroy();
+    },
+  );
+}
+
+class _Client {
+  final RequestChannel _channel;
+  final void Function() _stopHandler;
+  final Map<Uri, Uint8List> _dills = {};
+
+  _Client(this._channel, this._stopHandler) {
+    _channel.add('dill.put', _dillPut);
+    _channel.add('dill.remove', _dillRemove);
+    _channel.add('exit', _exit);
+    _channel.add('kernelForModule', _kernelForModule);
+    _channel.add('kernelForProgram', _kernelForProgram);
+    _channel.add('stop', _stop);
+  }
+
+  Future<void> _dillPut(Object? argumentObject) async {
+    final arguments = argumentObject.argumentAsMap;
+    final uriStr = arguments.required<String>('uri');
+    final bytes = arguments.required<Uint8List>('bytes');
+
+    final uri = Uri.parse(uriStr);
+    _dills[uri] = bytes;
+  }
+
+  Future<void> _dillRemove(Object? argumentObject) async {
+    final arguments = argumentObject.argumentAsMap;
+    final uriStr = arguments.required<String>('uri');
+
+    final uri = Uri.parse(uriStr);
+    _dills.remove(uri);
+  }
+
+  Future<void> _exit(Object? argument) async {
+    io.exit(0);
+  }
+
+  fe.CompilerOptions _getCompilerOptions(Map<Object?, Object?> arguments) {
+    final sdkSummaryUriStr = arguments.required<String>('sdkSummary');
+
+    final compilerOptions = fe.CompilerOptions()
+      ..environmentDefines = {}
+      ..fileSystem = _FileSystem(_channel, _dills)
+      ..sdkSummary = Uri.parse(sdkSummaryUriStr)
+      ..target = vm.VmTarget(fe.TargetFlags(enableNullSafety: true));
+
+    final additionalDills = arguments['additionalDills'].asListOf<String>();
+    if (additionalDills != null) {
+      compilerOptions.additionalDills.addAll(
+        additionalDills.map(Uri.parse),
+      );
+    }
+
+    return compilerOptions;
+  }
+
+  Future<Object?> _kernelForModule(Object? argumentObject) async {
+    final arguments = argumentObject.argumentAsMap;
+    final packagesFileUriStr = arguments.required<String>('packagesFileUri');
+
+    final compilerOptions = _getCompilerOptions(arguments)
+      ..packagesFileUri = Uri.parse(packagesFileUriStr);
+
+    final uriStrList = arguments['uris'].asListOf<String>();
+    if (uriStrList == null) {
+      throw ArgumentError('Missing field: uris');
+    }
+
+    final compilationResults = await fe.kernelForModule(
+      uriStrList.map(Uri.parse).toList(),
+      compilerOptions,
+    );
+
+    return _serializeComponentWithoutPlatform(
+      compilationResults.component!,
+    );
+  }
+
+  Future<Object?> _kernelForProgram(Object? argumentObject) async {
+    final arguments = argumentObject.argumentAsMap;
+
+    final compilerOptions = _getCompilerOptions(arguments);
+
+    final packagesFileUriStr = arguments.optional<String>('packagesFileUri');
+    if (packagesFileUriStr != null) {
+      compilerOptions.packagesFileUri = Uri.parse(packagesFileUriStr);
+    }
+
+    final uriStr = arguments.required<String>('uri');
+
+    final compilationResults = await vm.compileToKernel(
+      Uri.parse(uriStr),
+      compilerOptions,
+      environmentDefines: {},
+    );
+
+    return _serializeComponentWithoutPlatform(
+      compilationResults.component!,
+    );
+  }
+
+  Future<void> _stop(Object? argument) async {
+    _stopHandler();
+  }
+
+  static Uint8List _serializeComponentWithoutPlatform(fe.Component component) {
+    return fe.serializeComponent(
+      component,
+      filter: (library) {
+        return !library.importUri.isScheme('dart');
+      },
+      includeSources: false,
+    );
+  }
+}
+
+class _FileSystem implements fe.FileSystem {
+  final RequestChannel _channel;
+  final Map<Uri, Uint8List> _dills;
+
+  _FileSystem(this._channel, this._dills);
+
+  @override
+  fe.FileSystemEntity entityForUri(Uri uri) {
+    return _FileSystemEntity(this, uri);
+  }
+}
+
+class _FileSystemEntity implements fe.FileSystemEntity {
+  final _FileSystem _fileSystem;
+
+  @override
+  final Uri uri;
+
+  _FileSystemEntity(this._fileSystem, this.uri);
+
+  RequestChannel get _channel => _fileSystem._channel;
+
+  String get _uriStr => uri.toString();
+
+  @override
+  Future<bool> exists() async {
+    if (_fileSystem._dills.containsKey(uri)) {
+      return true;
+    }
+    return _channel.sendRequest<bool>('file.exists', _uriStr);
+  }
+
+  @override
+  Future<bool> existsAsyncIfPossible() => exists();
+
+  @override
+  Future<List<int>> readAsBytes() async {
+    final storedBytes = _fileSystem._dills[uri];
+    if (storedBytes != null) {
+      return storedBytes;
+    }
+
+    return _channel.sendRequest<Uint8List>('file.readAsBytes', _uriStr);
+  }
+
+  @override
+  Future<List<int>> readAsBytesAsyncIfPossible() => readAsBytes();
+
+  @override
+  Future<String> readAsString() async {
+    return _channel.sendRequest<String>('file.readAsString', _uriStr);
+  }
+}
+
+class _ParsedAddress {
+  final String host;
+  final int port;
+
+  factory _ParsedAddress.parse(String str) {
+    final colonOffset = str.lastIndexOf(':');
+    if (colonOffset == -1) {
+      throw FormatException("Expected ':' in: $str");
+    }
+
+    return _ParsedAddress._(
+      str.substring(0, colonOffset),
+      int.parse(str.substring(colonOffset + 1)),
+    );
+  }
+
+  _ParsedAddress._(this.host, this.port);
+}
+
+extension on Object? {
+  Map<Object?, Object?> get argumentAsMap {
+    final self = this;
+    if (self is Map<Object?, Object?>) {
+      return self;
+    }
+    throw ArgumentError('The argument must be a map.');
+  }
+}
+
+extension on Map<Object?, Object?> {
+  T? optional<T extends Object>(String name) {
+    final value = this[name];
+    if (value == null) {
+      return null;
+    }
+    if (value is T) {
+      return value;
+    }
+    throw ArgumentError('Must be null or $T: $name');
+  }
+
+  T required<T>(String name) {
+    final value = this[name];
+    if (value is T) {
+      return value;
+    }
+    throw ArgumentError('Must be $T: $name');
+  }
+}
+
+extension on Object? {
+  List<T>? asListOf<T>() {
+    final self = this;
+    if (self is List<Object?>) {
+      final result = <T>[];
+      for (final element in self) {
+        if (element is T) {
+          result.add(element);
+        } else {
+          return null;
+        }
+      }
+      return result;
+    }
+    return null;
+  }
+}
diff --git a/pkg/frontend_server/test/frontend_server_test.dart b/pkg/frontend_server/test/frontend_server_test.dart
index d34b067..46457d0 100644
--- a/pkg/frontend_server/test/frontend_server_test.dart
+++ b/pkg/frontend_server/test/frontend_server_test.dart
@@ -3,10 +3,12 @@
 import 'dart:convert';
 import 'dart:io';
 import 'dart:isolate';
+import 'dart:typed_data';
 
+import 'package:_fe_analyzer_shared/src/macros/compiler/request_channel.dart';
 import 'package:front_end/src/api_unstable/vm.dart';
-import 'package:kernel/binary/ast_to_binary.dart';
 import 'package:kernel/ast.dart' show Component;
+import 'package:kernel/binary/ast_to_binary.dart';
 import 'package:kernel/kernel.dart' show loadComponentFromBinary;
 import 'package:kernel/verifier.dart' show verifyComponent;
 import 'package:mockito/mockito.dart';
@@ -16,15 +18,6 @@
 
 import '../lib/frontend_server.dart';
 
-class _MockedCompiler extends Mock implements CompilerInterface {}
-
-class _MockedIncrementalCompiler extends Mock implements IncrementalCompiler {}
-
-class _MockedBinaryPrinterFactory extends Mock implements BinaryPrinterFactory {
-}
-
-class _MockedBinaryPrinter extends Mock implements BinaryPrinter {}
-
 void main() async {
   group('basic', () {
     final CompilerInterface compiler = _MockedCompiler();
@@ -1798,6 +1791,214 @@
       });
     });
 
+    group('binary protocol', () {
+      var fileContentMap = <Uri, String>{};
+
+      setUp(() {
+        fileContentMap = {};
+      });
+
+      void addFileCallbacks(RequestChannel requestChannel) {
+        requestChannel.add('file.exists', (uriStr) async {
+          final uri = Uri.parse(uriStr as String);
+          return fileContentMap.containsKey(uri);
+        });
+        requestChannel.add('file.readAsBytes', (uriStr) async {
+          final uri = Uri.parse(uriStr as String);
+          final content = fileContentMap[uri];
+          return content != null ? utf8.encode(content) : Uint8List(0);
+        });
+        requestChannel.add('file.readAsStringSync', (uriStr) async {
+          final uri = Uri.parse(uriStr as String);
+          return fileContentMap[uri] ?? '';
+        });
+      }
+
+      Future<ServerSocket> loopbackServerSocket() async {
+        try {
+          return await ServerSocket.bind(InternetAddress.loopbackIPv6, 0);
+        } on SocketException catch (_) {
+          return await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+        }
+      }
+
+      Uri registerKernelBlob(Uint8List bytes) {
+        bytes = Uint8List.fromList(bytes);
+        return (Isolate.current as dynamic).createUriForKernelBlob(bytes);
+      }
+
+      Future<void> runWithServer(
+        Future<void> Function(RequestChannel) f,
+      ) async {
+        final testFinished = Completer<void>();
+        final serverSocket = await loopbackServerSocket();
+
+        serverSocket.listen((socket) async {
+          final requestChannel = RequestChannel(socket);
+
+          try {
+            await f(requestChannel);
+          } finally {
+            requestChannel.sendRequest('stop', {});
+            socket.destroy();
+            serverSocket.close();
+            testFinished.complete();
+          }
+        });
+
+        final host = serverSocket.address.address;
+        final addressStr = '$host:${serverSocket.port}';
+        expect(await starter(['--binary-protocol-address=${addressStr}']), 0);
+
+        await testFinished.future;
+      }
+
+      group('dill.put', () {
+        test('not Map argument', () async {
+          await runWithServer((requestChannel) async {
+            try {
+              await requestChannel.sendRequest<Uint8List>('dill.put', 42);
+              fail('Expected RemoteException');
+            } on RemoteException {}
+          });
+        });
+
+        test('no field: uri', () async {
+          await runWithServer((requestChannel) async {
+            try {
+              await requestChannel.sendRequest<Uint8List>('dill.put', {});
+              fail('Expected RemoteException');
+            } on RemoteException {}
+          });
+        });
+
+        test('no field: bytes', () async {
+          await runWithServer((requestChannel) async {
+            try {
+              await requestChannel.sendRequest<Uint8List>('dill.put', {
+                'uri': 'vm:dill',
+              });
+              fail('Expected RemoteException');
+            } on RemoteException {}
+          });
+        });
+
+        test('OK', () async {
+          await runWithServer((requestChannel) async {
+            await requestChannel.sendRequest<Uint8List>('dill.put', {
+              'uri': 'vm:dill',
+              'bytes': Uint8List(256),
+            });
+          });
+        });
+      });
+
+      group('dill.remove', () {
+        test('not Map argument', () async {
+          await runWithServer((requestChannel) async {
+            try {
+              await requestChannel.sendRequest<Uint8List>('dill.remove', 42);
+              fail('Expected RemoteException');
+            } on RemoteException {}
+          });
+        });
+
+        test('no field: uri', () async {
+          await runWithServer((requestChannel) async {
+            try {
+              await requestChannel.sendRequest<Uint8List>('dill.remove', {});
+              fail('Expected RemoteException');
+            } on RemoteException {}
+          });
+        });
+
+        test('OK', () async {
+          await runWithServer((requestChannel) async {
+            await requestChannel.sendRequest<Uint8List>('dill.remove', {
+              'uri': 'vm:dill',
+            });
+          });
+        });
+      });
+
+      group('kernelForProgram', () {
+        test('not Map argument', () async {
+          await runWithServer((requestChannel) async {
+            try {
+              await requestChannel.sendRequest<Uint8List>(
+                'kernelForProgram',
+                42,
+              );
+              fail('Expected RemoteException');
+            } on RemoteException {}
+          });
+        });
+
+        test('no field: sdkSummary', () async {
+          await runWithServer((requestChannel) async {
+            try {
+              await requestChannel.sendRequest<Uint8List>(
+                'kernelForProgram',
+                {},
+              );
+              fail('Expected RemoteException');
+            } on RemoteException {}
+          });
+        });
+
+        test('no field: uri', () async {
+          await runWithServer((requestChannel) async {
+            try {
+              await requestChannel.sendRequest<Uint8List>('kernelForProgram', {
+                'sdkSummary': 'dill:vm',
+              });
+              fail('Expected RemoteException');
+            } on RemoteException {}
+          });
+        });
+
+        test('compiles', () async {
+          await runWithServer((requestChannel) async {
+            addFileCallbacks(requestChannel);
+
+            await requestChannel.sendRequest<void>('dill.put', {
+              'uri': 'dill:vm',
+              'bytes': File(
+                path.join(
+                  path.dirname(path.dirname(Platform.resolvedExecutable)),
+                  'lib',
+                  '_internal',
+                  'vm_platform_strong.dill',
+                ),
+              ).readAsBytesSync(),
+            });
+
+            fileContentMap[Uri.parse('file:///home/test/lib/test.dart')] = r'''
+import 'dart:isolate';
+void main(List<String> arguments, SendPort sendPort) {
+  sendPort.send(42);
+}
+''';
+
+            final kernelBytes = await requestChannel.sendRequest<Uint8List>(
+              'kernelForProgram',
+              {
+                'sdkSummary': 'dill:vm',
+                'uri': 'file:///home/test/lib/test.dart',
+              },
+            );
+
+            expect(kernelBytes, hasLength(greaterThan(200)));
+            final kernelUri = registerKernelBlob(kernelBytes);
+
+            final receivePort = ReceivePort();
+            await Isolate.spawnUri(kernelUri, [], receivePort.sendPort);
+            expect(await receivePort.first, 42);
+          });
+        });
+      });
+    });
+
     test('compile to JavaScript', () async {
       var file = File('${tempDir.path}/foo.dart')..createSync();
       file.writeAsStringSync("main() {\n}\n");
@@ -2897,30 +3098,15 @@
   }
 }
 
-class Result {
-  String status;
-  List<String> sources;
-
-  Result(this.status, this.sources);
-
-  void expectNoErrors({String filename}) {
-    var result = CompilationResult.parse(status);
-    expect(result.errorsCount, equals(0));
-    if (filename != null) {
-      expect(result.filename, equals(filename));
-    }
-  }
-}
-
 class OutputParser {
-  OutputParser(this._receivedResults);
   bool expectSources = true;
-
   StreamController<Result> _receivedResults;
-  List<String> _receivedSources;
 
+  List<String> _receivedSources;
   String _boundaryKey;
+
   bool _readingSources;
+  OutputParser(this._receivedResults);
 
   void listener(String s) {
     if (_boundaryKey == null) {
@@ -2959,3 +3145,27 @@
     }
   }
 }
+
+class Result {
+  String status;
+  List<String> sources;
+
+  Result(this.status, this.sources);
+
+  void expectNoErrors({String filename}) {
+    var result = CompilationResult.parse(status);
+    expect(result.errorsCount, equals(0));
+    if (filename != null) {
+      expect(result.filename, equals(filename));
+    }
+  }
+}
+
+class _MockedBinaryPrinter extends Mock implements BinaryPrinter {}
+
+class _MockedBinaryPrinterFactory extends Mock implements BinaryPrinterFactory {
+}
+
+class _MockedCompiler extends Mock implements CompilerInterface {}
+
+class _MockedIncrementalCompiler extends Mock implements IncrementalCompiler {}
diff --git a/runtime/vm/compiler/backend/il_arm.cc b/runtime/vm/compiler/backend/il_arm.cc
index fde11b60..3bdaac0 100644
--- a/runtime/vm/compiler/backend/il_arm.cc
+++ b/runtime/vm/compiler/backend/il_arm.cc
@@ -3691,7 +3691,7 @@
                                      instruction()->deopt_id(),
                                      instruction()->source());
     } else {
-      __ CallRuntime(kStackOverflowRuntimeEntry, kNumSlowPathArgs);
+      __ CallRuntime(kInterruptOrStackOverflowRuntimeEntry, kNumSlowPathArgs);
       compiler->EmitCallsiteMetadata(
           instruction()->source(), instruction()->deopt_id(),
           UntaggedPcDescriptors::kOther, instruction()->locs(), env);
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index 7506894..88f0e9a 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -3247,7 +3247,7 @@
                                      instruction()->deopt_id(),
                                      instruction()->source());
     } else {
-      __ CallRuntime(kStackOverflowRuntimeEntry, kNumSlowPathArgs);
+      __ CallRuntime(kInterruptOrStackOverflowRuntimeEntry, kNumSlowPathArgs);
       compiler->EmitCallsiteMetadata(
           instruction()->source(), instruction()->deopt_id(),
           UntaggedPcDescriptors::kOther, instruction()->locs(), env);
diff --git a/runtime/vm/compiler/backend/il_ia32.cc b/runtime/vm/compiler/backend/il_ia32.cc
index 4869e16..06243b2 100644
--- a/runtime/vm/compiler/backend/il_ia32.cc
+++ b/runtime/vm/compiler/backend/il_ia32.cc
@@ -2898,7 +2898,7 @@
         instruction(), /*num_slow_path_args=*/0);
     compiler->pending_deoptimization_env_ = env;
 
-    __ CallRuntime(kStackOverflowRuntimeEntry, kNumSlowPathArgs);
+    __ CallRuntime(kInterruptOrStackOverflowRuntimeEntry, kNumSlowPathArgs);
     compiler->EmitCallsiteMetadata(
         instruction()->source(), instruction()->deopt_id(),
         UntaggedPcDescriptors::kOther, instruction()->locs(), env);
diff --git a/runtime/vm/compiler/backend/il_riscv.cc b/runtime/vm/compiler/backend/il_riscv.cc
index a029815..b24fdb4 100644
--- a/runtime/vm/compiler/backend/il_riscv.cc
+++ b/runtime/vm/compiler/backend/il_riscv.cc
@@ -3494,7 +3494,7 @@
                                      instruction()->deopt_id(),
                                      instruction()->source());
     } else {
-      __ CallRuntime(kStackOverflowRuntimeEntry, kNumSlowPathArgs);
+      __ CallRuntime(kInterruptOrStackOverflowRuntimeEntry, kNumSlowPathArgs);
       compiler->EmitCallsiteMetadata(
           instruction()->source(), instruction()->deopt_id(),
           UntaggedPcDescriptors::kOther, instruction()->locs(), env);
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index 023874d..ae2531a 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -3325,7 +3325,7 @@
                                      instruction()->deopt_id(),
                                      instruction()->source());
     } else {
-      __ CallRuntime(kStackOverflowRuntimeEntry, kNumSlowPathArgs);
+      __ CallRuntime(kInterruptOrStackOverflowRuntimeEntry, kNumSlowPathArgs);
       compiler->EmitCallsiteMetadata(
           instruction()->source(), instruction()->deopt_id(),
           UntaggedPcDescriptors::kOther, instruction()->locs(), env);
diff --git a/runtime/vm/compiler/stub_code_compiler.cc b/runtime/vm/compiler/stub_code_compiler.cc
index 199ae30..33ef038 100644
--- a/runtime/vm/compiler/stub_code_compiler.cc
+++ b/runtime/vm/compiler/stub_code_compiler.cc
@@ -1037,7 +1037,8 @@
 void StubCodeCompiler::GenerateStackOverflowSharedWithoutFPURegsStub(
     Assembler* assembler) {
   GenerateSharedStub(
-      assembler, /*save_fpu_registers=*/false, &kStackOverflowRuntimeEntry,
+      assembler, /*save_fpu_registers=*/false,
+      &kInterruptOrStackOverflowRuntimeEntry,
       target::Thread::stack_overflow_shared_without_fpu_regs_stub_offset(),
       /*allow_return=*/true);
 }
@@ -1045,7 +1046,8 @@
 void StubCodeCompiler::GenerateStackOverflowSharedWithFPURegsStub(
     Assembler* assembler) {
   GenerateSharedStub(
-      assembler, /*save_fpu_registers=*/true, &kStackOverflowRuntimeEntry,
+      assembler, /*save_fpu_registers=*/true,
+      &kInterruptOrStackOverflowRuntimeEntry,
       target::Thread::stack_overflow_shared_with_fpu_regs_stub_offset(),
       /*allow_return=*/true);
 }
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 2fb6faf..a249423 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -2853,7 +2853,7 @@
 }
 #endif  // !defined(DART_PRECOMPILED_RUNTIME)
 
-DEFINE_RUNTIME_ENTRY(StackOverflow, 0) {
+DEFINE_RUNTIME_ENTRY(InterruptOrStackOverflow, 0) {
 #if defined(USING_SIMULATOR)
   uword stack_pos = Simulator::Current()->get_sp();
   // If simulator was never called it may return 0 as a value of SPREG.
diff --git a/runtime/vm/runtime_entry_list.h b/runtime/vm/runtime_entry_list.h
index 5731d55..857784b 100644
--- a/runtime/vm/runtime_entry_list.h
+++ b/runtime/vm/runtime_entry_list.h
@@ -51,7 +51,7 @@
   V(ArgumentErrorUnboxedInt64)                                                 \
   V(IntegerDivisionByZeroException)                                            \
   V(ReThrow)                                                                   \
-  V(StackOverflow)                                                             \
+  V(InterruptOrStackOverflow)                                                  \
   V(Throw)                                                                     \
   V(DeoptimizeMaterialize)                                                     \
   V(RewindPostDeopt)                                                           \
diff --git a/tools/VERSION b/tools/VERSION
index d5f67ab..c45d641 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 19
+PRERELEASE 20
 PRERELEASE_PATCH 0
\ No newline at end of file