Version 2.17.0-120.0.dev

Merge commit '30195a437b689cbb99bce81f891d340e2e5e2cd5' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 012897a..225f1ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,19 @@
       throw UnsupportedError("connectionFactory not implemented");
   ```
 
+- **Breaking Change** [#48093](https://github.com/dart-lang/sdk/issues/48093):
+  `HttpClient` has a new `keyLog` property, which allows TLS keys to be logged
+  for debugging purposes. Classes that `implement HttpClient` may be broken by
+  this change. Add the following method to your classes to fix them:
+
+  ```dart
+  void set keyLog(Function(String line)? callback) =>
+      throw UnsupportedError("keyLog not implemented");
+  ```
+
+- Add a optional `keyLog` parameter to `SecureSocket.connect` and
+  `SecureSocket.startConnect`.
+
 ### Tools
 
 #### Dart command line
diff --git a/pkg/_fe_analyzer_shared/benchmark/macros/serialization_benchmark.dart b/pkg/_fe_analyzer_shared/benchmark/macros/serialization_benchmark.dart
index 550e718..01cc969 100644
--- a/pkg/_fe_analyzer_shared/benchmark/macros/serialization_benchmark.dart
+++ b/pkg/_fe_analyzer_shared/benchmark/macros/serialization_benchmark.dart
@@ -8,7 +8,7 @@
 import 'dart:isolate';
 import 'dart:typed_data';
 
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
 
 void main() async {
   for (var serializationMode in [
@@ -188,7 +188,7 @@
       import 'dart:isolate';
       import 'dart:typed_data';
 
-      import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
+      import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
 
       void main(_, [SendPort? sendPort]) {
         var mode = $mode;
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart b/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart
index 99c2ae5..ed29787 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/bootstrap.dart
@@ -2,7 +2,7 @@
 // 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.
 
-import 'executor_shared/serialization.dart'
+import 'executor/serialization.dart'
     show SerializationMode, SerializationModeHelpers;
 
 /// Generates a Dart program for a given set of macros, which can be compiled
@@ -49,72 +49,106 @@
 
 const String template = '''
 import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
 import 'dart:isolate';
 import 'dart:typed_data';
 
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/execute_macro.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/response_impls.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/protocol.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/execute_macro.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/message_grouper.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/response_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/protocol.dart';
 import 'package:_fe_analyzer_shared/src/macros/executor.dart';
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
 
 $_importMarker
 
-/// Entrypoint to be spawned with [Isolate.spawnUri].
+/// Entrypoint to be spawned with [Isolate.spawnUri] or [Process.start].
 ///
 /// Supports the client side of the macro expansion protocol.
-void main(_, SendPort sendPort) {
-  /// Local function that sends requests and returns responses using [sendPort].
-  Future<Response> sendRequest(Request request) => _sendRequest(request, sendPort);
+void main(_, [SendPort? sendPort]) {
+  // Function that sends the result of a [Serializer] using either [sendPort]
+  // or [stdout].
+  void Function(Serializer) sendResult;
 
-  /// TODO: More directly support customizable serialization types.
+  // The stream for incoming messages, could be either a ReceivePort or stdin.
+  Stream<Object?> messageStream;
+
   withSerializationMode($_modeMarker, () {
-    ReceivePort receivePort = new ReceivePort();
-    sendPort.send(receivePort.sendPort);
+    if (sendPort != null) {
+      ReceivePort receivePort = new ReceivePort();
+      messageStream = receivePort;
+      sendResult = (Serializer serializer) =>
+          _sendIsolateResult(serializer, sendPort);
+      // If using isolate communication, first send a sendPort to the parent
+      // isolate.
+      sendPort.send(receivePort.sendPort);
+    } else {
+      sendResult = _sendStdoutResult;
+      if (serializationMode == SerializationMode.byteDataClient) {
+        messageStream = MessageGrouper(stdin).messageStream;
+      } else if (serializationMode == SerializationMode.jsonClient) {
+        messageStream = stdin
+          .transform(const Utf8Decoder())
+          .transform(const LineSplitter())
+          .map((line) => jsonDecode(line)!);
+      } else {
+        throw new UnsupportedError(
+            'Unsupported serialization mode \$serializationMode for '
+            'ProcessExecutor');
+      }
+    }
 
-    receivePort.listen((message) async {
-      if (serializationMode == SerializationMode.byteDataClient
-          && message is TransferableTypedData) {
-        message = message.materialize().asUint8List();
-      }
-      var deserializer = deserializerFactory(message)
-          ..moveNext();
-      int zoneId = deserializer.expectInt();
-      deserializer..moveNext();
-      var type = MessageType.values[deserializer.expectInt()];
-      var serializer = serializerFactory();
-      switch (type) {
-        case MessageType.instantiateMacroRequest:
-          var request = new InstantiateMacroRequest.deserialize(deserializer, zoneId);
-          (await _instantiateMacro(request)).serialize(serializer);
-          break;
-        case MessageType.executeDeclarationsPhaseRequest:
-          var request = new ExecuteDeclarationsPhaseRequest.deserialize(deserializer, zoneId);
-          (await _executeDeclarationsPhase(request, sendRequest)).serialize(serializer);
-          break;
-        case MessageType.executeDefinitionsPhaseRequest:
-          var request = new ExecuteDefinitionsPhaseRequest.deserialize(deserializer, zoneId);
-          (await _executeDefinitionsPhase(request, sendRequest)).serialize(serializer);
-          break;
-        case MessageType.executeTypesPhaseRequest:
-          var request = new ExecuteTypesPhaseRequest.deserialize(deserializer, zoneId);
-          (await _executeTypesPhase(request, sendRequest)).serialize(serializer);
-          break;
-        case MessageType.response:
-          var response = new SerializableResponse.deserialize(deserializer, zoneId);
-          _responseCompleters.remove(response.requestId)!.complete(response);
-          return;
-        default:
-          throw new StateError('Unhandled event type \$type');
-      }
-      _sendResult(serializer, sendPort);
-    });
+    messageStream.listen((message) => _handleMessage(message, sendResult));
   });
 }
 
+void _handleMessage(
+    Object? message, void Function(Serializer) sendResult) async {
+  // Serializes `request` and send it using `sendResult`.
+  Future<Response> sendRequest(Request request) =>
+      _sendRequest(request, sendResult);
+
+  if (serializationMode == SerializationMode.byteDataClient
+      && message is TransferableTypedData) {
+    message = message.materialize().asUint8List();
+  }
+  var deserializer = deserializerFactory(message)
+      ..moveNext();
+  int zoneId = deserializer.expectInt();
+  deserializer..moveNext();
+  var type = MessageType.values[deserializer.expectInt()];
+  var serializer = serializerFactory();
+  switch (type) {
+    case MessageType.instantiateMacroRequest:
+      var request = new InstantiateMacroRequest.deserialize(deserializer, zoneId);
+      (await _instantiateMacro(request)).serialize(serializer);
+      break;
+    case MessageType.executeDeclarationsPhaseRequest:
+      var request = new ExecuteDeclarationsPhaseRequest.deserialize(deserializer, zoneId);
+      (await _executeDeclarationsPhase(request, sendRequest)).serialize(serializer);
+      break;
+    case MessageType.executeDefinitionsPhaseRequest:
+      var request = new ExecuteDefinitionsPhaseRequest.deserialize(deserializer, zoneId);
+      (await _executeDefinitionsPhase(request, sendRequest)).serialize(serializer);
+      break;
+    case MessageType.executeTypesPhaseRequest:
+      var request = new ExecuteTypesPhaseRequest.deserialize(deserializer, zoneId);
+      (await _executeTypesPhase(request, sendRequest)).serialize(serializer);
+      break;
+    case MessageType.response:
+      var response = new SerializableResponse.deserialize(deserializer, zoneId);
+      _responseCompleters.remove(response.requestId)!.complete(response);
+      return;
+    default:
+      throw new StateError('Unhandled event type \$type');
+  }
+  sendResult(serializer);
+}
+
 /// Maps macro identifiers to constructors.
 final _macroConstructors = <MacroClassIdentifierImpl, Map<String, Macro Function()>>{
   $_macroConstructorEntriesMarker
@@ -263,21 +297,22 @@
 /// Holds on to response completers by request id.
 final _responseCompleters = <int, Completer<Response>>{};
 
-/// Serializes [request], sends it to [sendPort], and sets up a [Completer] in
-/// [_responseCompleters] to handle the response.
-Future<Response> _sendRequest(Request request, SendPort sendPort) {
+/// Serializes [request], passes it to [sendResult], and sets up a [Completer]
+/// in [_responseCompleters] to handle the response.
+Future<Response> _sendRequest(
+    Request request, void Function(Serializer serializer) sendResult) {
   Completer<Response> completer = Completer();
   _responseCompleters[request.id] = completer;
   Serializer serializer = serializerFactory();
   serializer.addInt(request.serializationZoneId);
   request.serialize(serializer);
-  _sendResult(serializer, sendPort);
+  sendResult(serializer);
   return completer.future;
 }
 
 /// Sends [serializer.result] to [sendPort], possibly wrapping it in a
 /// [TransferableTypedData] object.
-void _sendResult(Serializer serializer, SendPort sendPort) {
+void _sendIsolateResult(Serializer serializer, SendPort sendPort) {
   if (serializationMode == SerializationMode.byteDataClient) {
     sendPort.send(
         TransferableTypedData.fromList([serializer.result as Uint8List]));
@@ -285,4 +320,27 @@
     sendPort.send(serializer.result);
   }
 }
+
+/// Sends [serializer.result] to [stdout].
+///
+/// Serializes the result to a string if using JSON.
+void _sendStdoutResult(Serializer serializer) {
+  if (serializationMode == SerializationMode.jsonClient) {
+    stdout.writeln(jsonEncode(serializer.result));
+  } else if (serializationMode == SerializationMode.byteDataClient) {
+    Uint8List result = (serializer as ByteDataSerializer).result;
+    int length = result.lengthInBytes;
+    stdout.add([
+      length >> 24 & 0xff,
+      length >> 16 & 0xff,
+      length >> 8 & 0xff,
+      length & 0xff,
+    ]);
+    stdout.add(result);
+  } else {
+    throw new UnsupportedError(
+        'Unsupported serialization mode \$serializationMode for '
+        'ProcessExecutor');
+  }
+}
 ''';
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart
index ade1e6d..3ac7616d 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor.dart
@@ -4,7 +4,7 @@
 
 import 'api.dart';
 import 'bootstrap.dart'; // For doc comments only.
-import 'executor_shared/serialization.dart';
+import 'executor/serialization.dart';
 
 /// The interface used by Dart language implementations, in order to load
 /// and execute macros, as well as produce library augmentations from those
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/augmentation_library.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/augmentation_library.dart
similarity index 100%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/augmentation_library.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/augmentation_library.dart
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/builder_impls.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/builder_impls.dart
similarity index 98%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/builder_impls.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/builder_impls.dart
index 6f70c92..7acddb2 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/builder_impls.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/builder_impls.dart
@@ -4,7 +4,7 @@
 
 import 'dart:async';
 
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
 
 import '../executor.dart';
 import '../api.dart';
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/execute_macro.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/execute_macro.dart
similarity index 98%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/execute_macro.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/execute_macro.dart
index 7488445..13ec76e 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/execute_macro.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/execute_macro.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/builder_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/builder_impls.dart';
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
 
 /// Runs [macro] in the types phase and returns a  [MacroExecutionResult].
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/isolated_executor/isolated_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/executor_base.dart
similarity index 68%
rename from pkg/_fe_analyzer_shared/lib/src/macros/isolated_executor/isolated_executor.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/executor_base.dart
index 1efa621..018b47a 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/isolated_executor/isolated_executor.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/executor_base.dart
@@ -4,157 +4,49 @@
 
 import 'dart:async';
 import 'dart:isolate';
-import 'dart:typed_data';
 
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
 
 import '../api.dart';
-import '../executor_shared/augmentation_library.dart';
-import '../executor_shared/introspection_impls.dart';
-import '../executor_shared/protocol.dart';
-import '../executor_shared/response_impls.dart';
-import '../executor_shared/serialization.dart';
+import '../executor/introspection_impls.dart';
+import '../executor/protocol.dart';
+import '../executor/serialization.dart';
 import '../executor.dart';
 
-/// Returns an instance of [_IsolatedMacroExecutor] using [serializationMode].
+/// Base implementation for macro executors which communicate with some external
+/// process to run macros.
 ///
-/// The [serializationMode] must be a `server` variant;
-///
-/// This is the only public api exposed by this library.
-Future<MacroExecutor> start(SerializationMode serializationMode) async =>
-    new _IsolatedMacroExecutor(serializationMode);
-
-/// A [MacroExecutor] implementation which spawns a separate isolate for each
-/// macro that is loaded. Each of these is wrapped in its own
-/// [_SingleIsolatedMacroExecutor] which requests are delegated to.
-///
-/// This implementation requires precompiled kernel files when loading macros,
-/// (you must pass a `precompiledKernelUri` to [loadMacro]).
-///
-/// Spawned isolates are not ran in the same isolate group, so objects are
-/// serialized between isolates.
-class _IsolatedMacroExecutor extends MacroExecutor
-    with AugmentationLibraryBuilder {
-  /// Individual executors indexed by [MacroClassIdentifier] or
-  /// [MacroInstanceIdentifier].
-  final _executors = <Object, _SingleIsolatedMacroExecutor>{};
-
-  /// The mode to use for serialization - must be a `server` variant.
-  final SerializationMode _serializationMode;
-
-  _IsolatedMacroExecutor(this._serializationMode) {
-    if (_serializationMode.isClient) {
-      throw new ArgumentError(
-          'Got $serializationMode but expected a server version.');
-    }
-  }
-
-  @override
-  void close() {
-    for (_SingleIsolatedMacroExecutor executor in _executors.values) {
-      executor.close();
-    }
-  }
-
-  @override
-  Future<MacroExecutionResult> executeDeclarationsPhase(
-          MacroInstanceIdentifier macro,
-          DeclarationImpl declaration,
-          TypeResolver typeResolver,
-          ClassIntrospector classIntrospector) =>
-      _executors[macro]!.executeDeclarationsPhase(
-          macro, declaration, typeResolver, classIntrospector);
-
-  @override
-  Future<MacroExecutionResult> executeDefinitionsPhase(
-          MacroInstanceIdentifier macro,
-          DeclarationImpl declaration,
-          TypeResolver typeResolver,
-          ClassIntrospector classIntrospector,
-          TypeDeclarationResolver typeDeclarationResolver) =>
-      _executors[macro]!.executeDefinitionsPhase(macro, declaration,
-          typeResolver, classIntrospector, typeDeclarationResolver);
-
-  @override
-  Future<MacroExecutionResult> executeTypesPhase(
-          MacroInstanceIdentifier macro, DeclarationImpl declaration) =>
-      _executors[macro]!.executeTypesPhase(macro, declaration);
-
-  @override
-  Future<MacroInstanceIdentifier> instantiateMacro(
-      MacroClassIdentifier macroClass,
-      String constructor,
-      Arguments arguments) async {
-    _SingleIsolatedMacroExecutor executor = _executors[macroClass]!;
-    MacroInstanceIdentifier instance =
-        await executor.instantiateMacro(macroClass, constructor, arguments);
-    _executors[instance] = executor;
-    return instance;
-  }
-
-  @override
-  Future<MacroClassIdentifier> loadMacro(Uri library, String name,
-      {Uri? precompiledKernelUri}) async {
-    if (precompiledKernelUri == null) {
-      throw new UnsupportedError(
-          'This environment requires a non-null `precompiledKernelUri` to be '
-          'passed when loading macros.');
-    }
-    MacroClassIdentifier identifier =
-        new MacroClassIdentifierImpl(library, name);
-    _executors.remove(identifier)?.close();
-
-    _SingleIsolatedMacroExecutor executor =
-        await _SingleIsolatedMacroExecutor.start(
-            library, name, precompiledKernelUri, _serializationMode);
-    _executors[identifier] = executor;
-    return identifier;
-  }
-}
-
-class _SingleIsolatedMacroExecutor extends MacroExecutor {
-  /// The stream on which we receive responses.
+/// Subtypes must extend this class and implement the [close] and [sendResult]
+/// apis to handle communication with the external macro program.
+abstract class ExternalMacroExecutorBase extends MacroExecutor {
+  /// The stream on which we receive messages from the external macro executor.
   final Stream<Object> messageStream;
 
-  /// The send port where we should send requests.
-  final SendPort sendPort;
-
-  /// A function that should be invoked when shutting down this executor
-  /// to perform any necessary cleanup.
-  final void Function() onClose;
-
-  /// A map of response completers by request id.
-  final responseCompleters = <int, Completer<Response>>{};
-
   /// The mode to use for serialization - must be a `server` variant.
   final SerializationMode serializationMode;
 
+  /// A map of response completers by request id.
+  final _responseCompleters = <int, Completer<Response>>{};
+
   /// We need to know which serialization zone to deserialize objects in, so
-  /// that we read them from the correct cache. Each request creates its own
-  /// zone which it stores here by ID and then responses are deserialized in
-  /// the same zone.
-  static final serializationZones = <int, Zone>{};
+  /// that we read them from the correct cache. Each macro execution creates its
+  /// own zone which it stores here by ID and then responses are deserialized in
+  /// that same zone.
+  static final _serializationZones = <int, Zone>{};
 
   /// Incrementing identifier for the serialization zone ids.
   static int _nextSerializationZoneId = 0;
 
-  _SingleIsolatedMacroExecutor(
-      {required this.onClose,
-      required this.messageStream,
-      required this.sendPort,
-      required this.serializationMode}) {
+  ExternalMacroExecutorBase(
+      {required this.messageStream, required this.serializationMode}) {
     messageStream.listen((message) {
-      if (serializationMode == SerializationMode.byteDataServer &&
-          message is TransferableTypedData) {
-        message = message.materialize().asUint8List();
-      }
       withSerializationMode(serializationMode, () {
         Deserializer deserializer = deserializerFactory(message);
         // Every object starts with a zone ID which dictates the zone in which
         // we should deserialize the message.
         deserializer.moveNext();
         int zoneId = deserializer.expectInt();
-        Zone zone = serializationZones[zoneId]!;
+        Zone zone = _serializationZones[zoneId]!;
         zone.run(() async {
           deserializer.moveNext();
           MessageType messageType =
@@ -164,7 +56,7 @@
               SerializableResponse response =
                   new SerializableResponse.deserialize(deserializer, zoneId);
               Completer<Response>? completer =
-                  responseCompleters.remove(response.requestId);
+                  _responseCompleters.remove(response.requestId);
               if (completer == null) {
                 throw new StateError(
                     'Got a response for an unrecognized request id '
@@ -192,7 +84,7 @@
                   serializationZoneId: zoneId);
               Serializer serializer = serializerFactory();
               response.serialize(serializer);
-              _sendResult(serializer);
+              sendResult(serializer);
               break;
             case MessageType.isExactlyTypeRequest:
               IsExactlyTypeRequest request =
@@ -207,7 +99,7 @@
                   serializationZoneId: zoneId);
               Serializer serializer = serializerFactory();
               response.serialize(serializer);
-              _sendResult(serializer);
+              sendResult(serializer);
               break;
             case MessageType.isSubtypeOfRequest:
               IsSubtypeOfRequest request =
@@ -222,7 +114,7 @@
                   serializationZoneId: zoneId);
               Serializer serializer = serializerFactory();
               response.serialize(serializer);
-              _sendResult(serializer);
+              sendResult(serializer);
               break;
             case MessageType.declarationOfRequest:
               DeclarationOfRequest request =
@@ -238,7 +130,7 @@
                   serializationZoneId: zoneId);
               Serializer serializer = serializerFactory();
               response.serialize(serializer);
-              _sendResult(serializer);
+              sendResult(serializer);
               break;
             case MessageType.constructorsOfRequest:
               ClassIntrospectionRequest request =
@@ -256,7 +148,7 @@
                   serializationZoneId: zoneId);
               Serializer serializer = serializerFactory();
               response.serialize(serializer);
-              _sendResult(serializer);
+              sendResult(serializer);
               break;
             case MessageType.fieldsOfRequest:
               ClassIntrospectionRequest request =
@@ -274,7 +166,7 @@
                   serializationZoneId: zoneId);
               Serializer serializer = serializerFactory();
               response.serialize(serializer);
-              _sendResult(serializer);
+              sendResult(serializer);
               break;
             case MessageType.interfacesOfRequest:
               ClassIntrospectionRequest request =
@@ -292,7 +184,7 @@
                   serializationZoneId: zoneId);
               Serializer serializer = serializerFactory();
               response.serialize(serializer);
-              _sendResult(serializer);
+              sendResult(serializer);
               break;
             case MessageType.methodsOfRequest:
               ClassIntrospectionRequest request =
@@ -310,7 +202,7 @@
                   serializationZoneId: zoneId);
               Serializer serializer = serializerFactory();
               response.serialize(serializer);
-              _sendResult(serializer);
+              sendResult(serializer);
               break;
             case MessageType.mixinsOfRequest:
               ClassIntrospectionRequest request =
@@ -328,7 +220,7 @@
                   serializationZoneId: zoneId);
               Serializer serializer = serializerFactory();
               response.serialize(serializer);
-              _sendResult(serializer);
+              sendResult(serializer);
               break;
             case MessageType.superclassOfRequest:
               ClassIntrospectionRequest request =
@@ -346,7 +238,7 @@
                   serializationZoneId: zoneId);
               Serializer serializer = serializerFactory();
               response.serialize(serializer);
-              _sendResult(serializer);
+              sendResult(serializer);
               break;
             default:
               throw new StateError('Unexpected message type $messageType');
@@ -356,35 +248,6 @@
     });
   }
 
-  static Future<_SingleIsolatedMacroExecutor> start(Uri library, String name,
-      Uri precompiledKernelUri, SerializationMode serializationMode) async {
-    ReceivePort receivePort = new ReceivePort();
-    Isolate isolate =
-        await Isolate.spawnUri(precompiledKernelUri, [], receivePort.sendPort);
-    Completer<SendPort> sendPortCompleter = new Completer();
-    StreamController<Object> messageStreamController =
-        new StreamController(sync: true);
-    receivePort.listen((message) {
-      if (!sendPortCompleter.isCompleted) {
-        sendPortCompleter.complete(message as SendPort);
-      } else {
-        messageStreamController.add(message);
-      }
-    }).onDone(messageStreamController.close);
-
-    return new _SingleIsolatedMacroExecutor(
-        onClose: () {
-          receivePort.close();
-          isolate.kill();
-        },
-        messageStream: messageStreamController.stream,
-        sendPort: await sendPortCompleter.future,
-        serializationMode: serializationMode);
-  }
-
-  @override
-  void close() => onClose();
-
   /// These calls are handled by the higher level executor.
   @override
   String buildAugmentationLibrary(Iterable<MacroExecutionResult> macroResults,
@@ -453,22 +316,28 @@
   @override
   Future<MacroClassIdentifier> loadMacro(Uri library, String name,
           {Uri? precompiledKernelUri}) =>
-      throw new StateError('Unreachable');
+      throw new StateError(
+          'This executor should be wrapped in a MultiMacroExecutor which will '
+          'handle load requests.');
+
+  /// Sends [serializer.result] to [sendPort], possibly wrapping it in a
+  /// [TransferableTypedData] object.
+  void sendResult(Serializer serializer);
 
   /// Creates a [Request] with a given serialization zone ID, and handles the
   /// response, casting it to the expected type or throwing the error provided.
   Future<T> _sendRequest<T>(Request Function(int) requestFactory) =>
       withSerializationMode(serializationMode, () async {
         int zoneId = _nextSerializationZoneId++;
-        serializationZones[zoneId] = Zone.current;
+        _serializationZones[zoneId] = Zone.current;
         Request request = requestFactory(zoneId);
         Serializer serializer = serializerFactory();
         // It is our responsibility to add the zone ID header.
         serializer.addInt(zoneId);
         request.serialize(serializer);
-        _sendResult(serializer);
+        sendResult(serializer);
         Completer<Response> completer = new Completer<Response>();
-        responseCompleters[request.id] = completer;
+        _responseCompleters[request.id] = completer;
         try {
           Response response = await completer.future;
           T? result = response.response as T?;
@@ -477,18 +346,7 @@
               response.error!.toString(), response.stackTrace);
         } finally {
           // Clean up the zone after the request is done.
-          serializationZones.remove(zoneId);
+          _serializationZones.remove(zoneId);
         }
       });
-
-  /// Sends [serializer.result] to [sendPort], possibly wrapping it in a
-  /// [TransferableTypedData] object.
-  void _sendResult(Serializer serializer) {
-    if (serializationMode == SerializationMode.byteDataServer) {
-      sendPort.send(
-          new TransferableTypedData.fromList([serializer.result as Uint8List]));
-    } else {
-      sendPort.send(serializer.result);
-    }
-  }
 }
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/introspection_impls.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart
similarity index 100%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/introspection_impls.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/introspection_impls.dart
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/isolated_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/isolated_executor.dart
new file mode 100644
index 0000000..ae15038
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/isolated_executor.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2021, 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.
+
+import 'dart:async';
+import 'dart:isolate';
+import 'dart:typed_data';
+
+import '../executor/multi_executor.dart';
+import '../executor/executor_base.dart';
+import '../executor/serialization.dart';
+import '../executor.dart';
+
+/// Returns a [MacroExecutor] which loads macros into isolates using precompiled
+/// kernel files and communicates with that isolate using [serializationMode].
+///
+/// The [serializationMode] must be a `server` variant, and any precompiled
+/// programs must use the corresponding `client` variant.
+///
+/// This is the only public api exposed by this library.
+Future<MacroExecutor> start(SerializationMode serializationMode) async =>
+    new MultiMacroExecutor((Uri library, String name,
+        {Uri? precompiledKernelUri}) {
+      if (precompiledKernelUri == null) {
+        throw new UnsupportedError(
+            'This environment requires a non-null `precompiledKernelUri` to be '
+            'passed when loading macros.');
+      }
+
+      return _SingleIsolatedMacroExecutor.start(
+          library, name, precompiledKernelUri, serializationMode);
+    });
+
+/// Actual implementation of the isolate based macro executor.
+class _SingleIsolatedMacroExecutor extends ExternalMacroExecutorBase {
+  /// The send port where we should send requests.
+  final SendPort sendPort;
+
+  /// A function that should be invoked when shutting down this executor
+  /// to perform any necessary cleanup.
+  final void Function() onClose;
+
+  _SingleIsolatedMacroExecutor(
+      {required Stream<Object> messageStream,
+      required this.onClose,
+      required this.sendPort,
+      required SerializationMode serializationMode})
+      : super(
+            messageStream: messageStream, serializationMode: serializationMode);
+
+  static Future<_SingleIsolatedMacroExecutor> start(Uri library, String name,
+      Uri precompiledKernelUri, SerializationMode serializationMode) async {
+    ReceivePort receivePort = new ReceivePort();
+    Isolate isolate =
+        await Isolate.spawnUri(precompiledKernelUri, [], receivePort.sendPort);
+    Completer<SendPort> sendPortCompleter = new Completer();
+    StreamController<Object> messageStreamController =
+        new StreamController(sync: true);
+    receivePort.listen((message) {
+      if (!sendPortCompleter.isCompleted) {
+        sendPortCompleter.complete(message as SendPort);
+      } else {
+        if (serializationMode == SerializationMode.byteDataServer) {
+          message =
+              (message as TransferableTypedData).materialize().asUint8List();
+        }
+        messageStreamController.add(message);
+      }
+    }).onDone(messageStreamController.close);
+
+    return new _SingleIsolatedMacroExecutor(
+        onClose: () {
+          receivePort.close();
+          isolate.kill();
+        },
+        messageStream: messageStreamController.stream,
+        sendPort: await sendPortCompleter.future,
+        serializationMode: serializationMode);
+  }
+
+  @override
+  void close() => onClose();
+
+  /// Sends the [Serializer.result] to [sendPort], possibly wrapping it in a
+  /// [TransferableTypedData] object.
+  void sendResult(Serializer serializer) {
+    if (serializationMode == SerializationMode.byteDataServer) {
+      sendPort.send(
+          new TransferableTypedData.fromList([serializer.result as Uint8List]));
+    } else {
+      sendPort.send(serializer.result);
+    }
+  }
+}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/message_grouper.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/message_grouper.dart
new file mode 100644
index 0000000..7e2d390
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/message_grouper.dart
@@ -0,0 +1,91 @@
+// 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.
+
+import 'dart:async';
+import 'dart:math';
+import 'dart:typed_data';
+
+/// Collects messages from an input stream of bytes.
+///
+/// Each message should start with a 32 bit big endian uint indicating its size,
+/// followed by that many bytes.
+class MessageGrouper {
+  /// The input bytes stream subscription.
+  late final StreamSubscription _inputStreamSubscription;
+
+  /// The length of the current message to read, or `-1` if we are currently
+  /// reading the length.
+  int _length = -1;
+
+  /// The buffer to store the length bytes in.
+  BytesBuilder _lengthBuffer = new BytesBuilder();
+
+  /// If reading raw data, buffer for the data.
+  Uint8List _messageBuffer = new Uint8List(0);
+
+  /// The position to write the next byte in [_messageBuffer].
+  int _messagePos = 0;
+
+  late StreamController<Uint8List> _messageStreamController =
+      new StreamController<Uint8List>(onCancel: () {
+    _inputStreamSubscription.cancel();
+  });
+  Stream<Uint8List> get messageStream => _messageStreamController.stream;
+
+  MessageGrouper(Stream<List<int>> inputStream) {
+    _inputStreamSubscription = inputStream.listen(_handleBytes, onDone: cancel);
+  }
+
+  void _handleBytes(List<int> bytes, [int offset = 0]) {
+    if (_length == -1) {
+      while (_lengthBuffer.length < 4 && offset < bytes.length) {
+        _lengthBuffer.addByte(bytes[offset++]);
+      }
+      if (_lengthBuffer.length >= 4) {
+        Uint8List lengthBytes = _lengthBuffer.takeBytes();
+        _length = lengthBytes[0] << 24 |
+            lengthBytes[1] << 16 |
+            lengthBytes[2] << 8 |
+            lengthBytes[3];
+      }
+    }
+
+    // Just pass along `bytes` without a copy if we can, and reset our state
+    if (offset == 0 && bytes.length == _length && bytes is Uint8List) {
+      _length = -1;
+      _messageStreamController.add(bytes);
+      return;
+    }
+
+    // Initialize a new buffer.
+    if (_messagePos == 0) {
+      _messageBuffer = new Uint8List(_length);
+    }
+
+    // Read the data from `bytes`.
+    int lenToRead = min(_length - _messagePos, bytes.length - offset);
+    while (lenToRead-- > 0) {
+      _messageBuffer[_messagePos++] = bytes[offset++];
+    }
+
+    // If we completed a message, add it to the output stream, reset our state,
+    // and call ourselves again if we have more data to read.
+    if (_messagePos >= _length) {
+      _messageStreamController.add(_messageBuffer);
+      _length = -1;
+      _messagePos = 0;
+
+      if (offset < bytes.length) {
+        _handleBytes(bytes, offset);
+      }
+    }
+  }
+
+  /// Stop listening to the input stream for further updates, and close the
+  /// output stream.
+  void cancel() {
+    _inputStreamSubscription.cancel();
+    _messageStreamController.close();
+  }
+}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/multi_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/multi_executor.dart
new file mode 100644
index 0000000..81b8107
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/multi_executor.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2021, 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.
+
+import 'dart:async';
+
+import '../api.dart';
+import '../executor/augmentation_library.dart';
+import '../executor/introspection_impls.dart';
+import '../executor/response_impls.dart';
+import '../executor.dart';
+
+/// A [MacroExecutor] implementation which delegates most work to other
+/// executors which are spawned through a provided callback.
+class MultiMacroExecutor extends MacroExecutor with AugmentationLibraryBuilder {
+  /// Individual executors indexed by [MacroClassIdentifier] or
+  /// [MacroInstanceIdentifier].
+  final _executors = <Object, MacroExecutor>{};
+
+  /// The function to spawn an actual macro executor for a given [loadMacro]
+  /// request.
+  final Future<MacroExecutor> Function(Uri library, String name,
+      {Uri? precompiledKernelUri}) _spawnExecutor;
+
+  MultiMacroExecutor(this._spawnExecutor);
+
+  @override
+  void close() {
+    for (MacroExecutor executor in _executors.values) {
+      executor.close();
+    }
+    _executors.clear();
+  }
+
+  @override
+  Future<MacroExecutionResult> executeDeclarationsPhase(
+          MacroInstanceIdentifier macro,
+          DeclarationImpl declaration,
+          TypeResolver typeResolver,
+          ClassIntrospector classIntrospector) =>
+      _executors[macro]!.executeDeclarationsPhase(
+          macro, declaration, typeResolver, classIntrospector);
+
+  @override
+  Future<MacroExecutionResult> executeDefinitionsPhase(
+          MacroInstanceIdentifier macro,
+          DeclarationImpl declaration,
+          TypeResolver typeResolver,
+          ClassIntrospector classIntrospector,
+          TypeDeclarationResolver typeDeclarationResolver) =>
+      _executors[macro]!.executeDefinitionsPhase(macro, declaration,
+          typeResolver, classIntrospector, typeDeclarationResolver);
+
+  @override
+  Future<MacroExecutionResult> executeTypesPhase(
+          MacroInstanceIdentifier macro, DeclarationImpl declaration) =>
+      _executors[macro]!.executeTypesPhase(macro, declaration);
+
+  @override
+  Future<MacroInstanceIdentifier> instantiateMacro(
+      MacroClassIdentifier macroClass,
+      String constructor,
+      Arguments arguments) async {
+    MacroExecutor executor = _executors[macroClass]!;
+    MacroInstanceIdentifier instance =
+        await executor.instantiateMacro(macroClass, constructor, arguments);
+    _executors[instance] = executor;
+    return instance;
+  }
+
+  @override
+  Future<MacroClassIdentifier> loadMacro(Uri library, String name,
+      {Uri? precompiledKernelUri}) async {
+    MacroClassIdentifier identifier =
+        new MacroClassIdentifierImpl(library, name);
+    _executors.remove(identifier)?.close();
+
+    MacroExecutor executor = await _spawnExecutor(library, name,
+        precompiledKernelUri: precompiledKernelUri);
+    _executors[identifier] = executor;
+    return identifier;
+  }
+}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/process_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/process_executor.dart
new file mode 100644
index 0000000..ed67219
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/process_executor.dart
@@ -0,0 +1,117 @@
+// Copyright (c) 2021, 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:_fe_analyzer_shared/src/macros/executor/protocol.dart';
+
+import '../executor/message_grouper.dart';
+import '../executor/multi_executor.dart';
+import '../executor/executor_base.dart';
+import '../executor/serialization.dart';
+import '../executor.dart';
+
+/// Returns a [MacroExecutor] which loads macros as separate processes using
+/// precompiled binaries and communicates with that program using
+/// [serializationMode].
+///
+/// The [serializationMode] must be a `server` variant, and any precompiled
+/// programs spawned must use the corresponding `client` variant.
+///
+/// This is the only public api exposed by this library.
+Future<MacroExecutor> start(SerializationMode serializationMode) async =>
+    new MultiMacroExecutor((Uri library, String name,
+        {Uri? precompiledKernelUri}) {
+      if (precompiledKernelUri == null) {
+        throw new UnsupportedError(
+            'This environment requires a non-null `precompiledKernelUri` to be '
+            'passed when loading macros.');
+      }
+
+      // TODO: We actually assume this is a full precompiled AOT binary, and not
+      // a kernel file. We launch it directly using `Process.start`.
+      return _SingleProcessMacroExecutor.start(
+          library, name, serializationMode, precompiledKernelUri.toFilePath());
+    });
+
+/// Actual implementation of the separate process based macro executor.
+class _SingleProcessMacroExecutor extends ExternalMacroExecutorBase {
+  /// The IOSink that writes to stdin of the external process.
+  final IOSink outSink;
+
+  /// A function that should be invoked when shutting down this executor
+  /// to perform any necessary cleanup.
+  final void Function() onClose;
+
+  _SingleProcessMacroExecutor(
+      {required Stream<Object> messageStream,
+      required this.onClose,
+      required this.outSink,
+      required SerializationMode serializationMode})
+      : super(
+            messageStream: messageStream, serializationMode: serializationMode);
+
+  static Future<_SingleProcessMacroExecutor> start(Uri library, String name,
+      SerializationMode serializationMode, String programPath) async {
+    Process process = await Process.start(programPath, []);
+    process.stderr
+        .transform(const Utf8Decoder())
+        .listen((content) => throw new RemoteException(content));
+
+    Stream<Object> messageStream;
+
+    if (serializationMode == SerializationMode.byteDataServer) {
+      messageStream = new MessageGrouper(process.stdout).messageStream;
+    } else if (serializationMode == SerializationMode.jsonServer) {
+      messageStream = process.stdout
+          .transform(const Utf8Decoder())
+          .transform(const LineSplitter())
+          .map((line) => jsonDecode(line)!);
+    } else {
+      throw new UnsupportedError(
+          'Unsupported serialization mode \$serializationMode for '
+          'ProcessExecutor');
+    }
+
+    return new _SingleProcessMacroExecutor(
+        onClose: () {
+          process.kill();
+        },
+        messageStream: messageStream,
+        outSink: process.stdin,
+        serializationMode: serializationMode);
+  }
+
+  @override
+  void close() => onClose();
+
+  /// Sends the [Serializer.result] to [stdin].
+  ///
+  /// Json results are serialized to a `String`, and separated by newlines.
+  void sendResult(Serializer serializer) {
+    if (serializationMode == SerializationMode.jsonServer) {
+      outSink.writeln(jsonEncode(serializer.result));
+    } else if (serializationMode == SerializationMode.byteDataServer) {
+      Uint8List result = (serializer as ByteDataSerializer).result;
+      int length = result.lengthInBytes;
+      if (length > 0xffffffff) {
+        throw new StateError('Message was larger than the allowed size!');
+      }
+      outSink.add([
+        length >> 24 & 0xff,
+        length >> 16 & 0xff,
+        length >> 8 & 0xff,
+        length & 0xff
+      ]);
+      outSink.add(result);
+    } else {
+      throw new UnsupportedError(
+          'Unsupported serialization mode $serializationMode for '
+          'ProcessExecutor');
+    }
+  }
+}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/protocol.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/protocol.dart
similarity index 99%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/protocol.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/protocol.dart
index 50a6743..fa6d608 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/protocol.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/protocol.dart
@@ -10,7 +10,7 @@
 
 import '../executor.dart';
 import '../api.dart';
-import '../executor_shared/response_impls.dart';
+import '../executor/response_impls.dart';
 import 'introspection_impls.dart';
 import 'remote_instance.dart';
 import 'serialization.dart';
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/remote_instance.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/remote_instance.dart
similarity index 100%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/remote_instance.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/remote_instance.dart
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/response_impls.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/response_impls.dart
similarity index 100%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/response_impls.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/response_impls.dart
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
similarity index 99%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
index 267a46f..50d98fd 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
@@ -68,7 +68,7 @@
   void endList();
 
   /// Returns the resulting serialized object.
-  Object? get result;
+  Object get result;
 }
 
 /// A pull based object deserialization interface.
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization_extensions.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
similarity index 99%
rename from pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization_extensions.dart
rename to pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
index cc7e8cb..cf3ff21 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor_shared/serialization_extensions.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization_extensions.dart
@@ -1,4 +1,4 @@
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
 
 import 'remote_instance.dart';
 import 'serialization.dart';
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart b/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart
deleted file mode 100644
index e45d120..0000000
--- a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright (c) 2021, 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.
-
-import 'dart:async';
-import 'dart:isolate';
-import 'dart:mirrors';
-
-import 'isolate_mirrors_impl.dart';
-import '../executor_shared/augmentation_library.dart';
-import '../executor_shared/introspection_impls.dart';
-import '../executor_shared/protocol.dart';
-import '../executor_shared/remote_instance.dart';
-import '../executor.dart';
-import '../api.dart';
-
-/// Returns an instance of [_IsolateMirrorMacroExecutor].
-///
-/// This is the only public api exposed by this library.
-Future<MacroExecutor> start() => _IsolateMirrorMacroExecutor.start();
-
-/// A [MacroExecutor] implementation which relies on [IsolateMirror.loadUri]
-/// in order to load macros libraries.
-///
-/// All actual work happens in a separate [Isolate], and this class serves as
-/// a bridge between that isolate and the language frontends.
-class _IsolateMirrorMacroExecutor extends MacroExecutor
-    with AugmentationLibraryBuilder {
-  /// The actual isolate doing macro loading and execution.
-  final Isolate _macroIsolate;
-
-  /// The channel used to send requests to the [_macroIsolate].
-  final SendPort _sendPort;
-
-  /// The stream of responses from the [_macroIsolate].
-  final Stream<Response> _responseStream;
-
-  /// A map of response completers by request id.
-  final _responseCompleters = <int, Completer<Response>>{};
-
-  /// A function that should be invoked when shutting down this executor
-  /// to perform any necessary cleanup.
-  final void Function() _onClose;
-
-  _IsolateMirrorMacroExecutor._(
-      this._macroIsolate, this._sendPort, this._responseStream, this._onClose) {
-    _responseStream.listen((event) {
-      Completer<Response>? completer =
-          _responseCompleters.remove(event.requestId);
-      if (completer == null) {
-        throw new StateError(
-            'Got a response for an unrecognized request id ${event.requestId}');
-      }
-      completer.complete(event);
-    });
-  }
-
-  /// Initialize an [IsolateMirrorMacroExecutor] and return it once ready.
-  ///
-  /// Spawns the macro isolate and sets up a communication channel.
-  static Future<MacroExecutor> start() async {
-    ReceivePort receivePort = new ReceivePort();
-    Completer<SendPort> sendPortCompleter = new Completer<SendPort>();
-    StreamController<Response> responseStreamController =
-        new StreamController<Response>(sync: true);
-    receivePort.listen((message) {
-      if (!sendPortCompleter.isCompleted) {
-        sendPortCompleter.complete(message as SendPort);
-      } else {
-        responseStreamController.add(message as Response);
-      }
-    }).onDone(responseStreamController.close);
-    Isolate macroIsolate = await Isolate.spawn(spawn, receivePort.sendPort);
-
-    return new _IsolateMirrorMacroExecutor._(
-        macroIsolate,
-        await sendPortCompleter.future,
-        responseStreamController.stream,
-        receivePort.close);
-  }
-
-  @override
-  void close() {
-    _onClose();
-    _macroIsolate.kill();
-  }
-
-  @override
-  Future<MacroExecutionResult> executeDeclarationsPhase(
-      MacroInstanceIdentifier macro,
-      Declaration declaration,
-      TypeResolver typeResolver,
-      ClassIntrospector classIntrospector) {
-    // TODO: implement executeDeclarationsPhase
-    throw new UnimplementedError();
-  }
-
-  @override
-  Future<MacroExecutionResult> executeDefinitionsPhase(
-          MacroInstanceIdentifier macro,
-          DeclarationImpl declaration,
-          TypeResolver typeResolver,
-          ClassIntrospector classIntrospector,
-          TypeDeclarationResolver typeDeclarationResolver) =>
-      _sendRequest(new ExecuteDefinitionsPhaseRequest(
-          macro,
-          declaration,
-          new RemoteInstanceImpl(
-              instance: typeResolver,
-              id: RemoteInstance.uniqueId,
-              kind: RemoteInstanceKind.typeResolver),
-          new RemoteInstanceImpl(
-              instance: classIntrospector,
-              id: RemoteInstance.uniqueId,
-              kind: RemoteInstanceKind.classIntrospector),
-          new RemoteInstanceImpl(
-              instance: typeDeclarationResolver,
-              id: RemoteInstance.uniqueId,
-              kind: RemoteInstanceKind.typeDeclarationResolver),
-          // Serialization zones are not necessary in this executor.
-          serializationZoneId: -1));
-
-  @override
-  Future<MacroExecutionResult> executeTypesPhase(
-      MacroInstanceIdentifier macro, Declaration declaration) {
-    // TODO: implement executeTypesPhase
-    throw new UnimplementedError();
-  }
-
-  @override
-  Future<MacroInstanceIdentifier> instantiateMacro(
-          MacroClassIdentifier macroClass,
-          String constructor,
-          Arguments arguments) =>
-      _sendRequest(new InstantiateMacroRequest(
-          macroClass, constructor, arguments, RemoteInstance.uniqueId,
-          // Serialization zones are not necessary in this executor.
-          serializationZoneId: -1));
-
-  @override
-  Future<MacroClassIdentifier> loadMacro(Uri library, String name,
-      {Uri? precompiledKernelUri}) {
-    if (precompiledKernelUri != null) {
-      // TODO: Implement support?
-      throw new UnsupportedError(
-          'The IsolateMirrorsExecutor does not support precompiled dill files');
-    }
-    return _sendRequest(new LoadMacroRequest(library, name,
-        // Serialization zones are not necessary in this executor.
-        serializationZoneId: -1));
-  }
-
-  /// Sends a request and returns the response, casting it to the expected
-  /// type.
-  Future<T> _sendRequest<T>(Request request) async {
-    _sendPort.send(request);
-    Completer<Response> completer = new Completer<Response>();
-    _responseCompleters[request.id] = completer;
-    Response response = await completer.future;
-    T? result = response.response as T?;
-    if (result != null) return result;
-    throw response.error!;
-  }
-}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_impl.dart b/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_impl.dart
deleted file mode 100644
index e6494f0..0000000
--- a/pkg/_fe_analyzer_shared/lib/src/macros/isolate_mirrors_executor/isolate_mirrors_impl.dart
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) 2021, 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.
-
-import 'dart:async';
-import 'dart:isolate';
-import 'dart:mirrors';
-
-import '../executor_shared/builder_impls.dart';
-import '../executor_shared/introspection_impls.dart';
-import '../executor_shared/response_impls.dart';
-import '../executor_shared/protocol.dart';
-import '../api.dart';
-
-/// Spawns a new isolate for loading and executing macros.
-void spawn(SendPort sendPort) {
-  ReceivePort receivePort = new ReceivePort();
-  sendPort.send(receivePort.sendPort);
-  receivePort.listen((message) async {
-    Response response;
-    if (message is LoadMacroRequest) {
-      response = await _loadMacro(message);
-    } else if (message is InstantiateMacroRequest) {
-      response = await _instantiateMacro(message);
-    } else if (message is ExecuteDefinitionsPhaseRequest) {
-      response = await _executeDefinitionsPhase(message);
-    } else {
-      throw new StateError('Unrecognized event type $message');
-    }
-    sendPort.send(response);
-  });
-}
-
-/// Maps macro identifiers to class mirrors.
-final _macroClasses = <MacroClassIdentifierImpl, ClassMirror>{};
-
-/// Handles [LoadMacroRequest]s.
-Future<Response> _loadMacro(LoadMacroRequest request) async {
-  try {
-    MacroClassIdentifierImpl identifier =
-        new MacroClassIdentifierImpl(request.library, request.name);
-    if (_macroClasses.containsKey(identifier)) {
-      throw new UnsupportedError(
-          'Reloading macros is not supported by this implementation');
-    }
-    LibraryMirror libMirror =
-        await currentMirrorSystem().isolate.loadUri(request.library);
-    ClassMirror macroClass =
-        libMirror.declarations[new Symbol(request.name)] as ClassMirror;
-    _macroClasses[identifier] = macroClass;
-    return new Response(
-        response: identifier,
-        requestId: request.id,
-        responseType: MessageType.macroClassIdentifier);
-  } catch (e) {
-    return new Response(
-        error: e, requestId: request.id, responseType: MessageType.error);
-  }
-}
-
-/// Maps macro instance identifiers to instances.
-final _macroInstances = <MacroInstanceIdentifierImpl, Macro>{};
-
-/// Handles [InstantiateMacroRequest]s.
-Future<Response> _instantiateMacro(InstantiateMacroRequest request) async {
-  try {
-    ClassMirror? clazz = _macroClasses[request.macroClass];
-    if (clazz == null) {
-      throw new ArgumentError('Unrecognized macro class ${request.macroClass}');
-    }
-    Macro instance = clazz.newInstance(
-        new Symbol(request.constructorName), request.arguments.positional, {
-      for (MapEntry<String, Object?> entry in request.arguments.named.entries)
-        new Symbol(entry.key): entry.value,
-    }).reflectee as Macro;
-    MacroInstanceIdentifierImpl identifier =
-        new MacroInstanceIdentifierImpl(instance, request.instanceId);
-    _macroInstances[identifier] = instance;
-    return new Response(
-        response: identifier,
-        requestId: request.id,
-        responseType: MessageType.macroInstanceIdentifier);
-  } catch (e) {
-    return new Response(
-        error: e, requestId: request.id, responseType: MessageType.error);
-  }
-}
-
-Future<Response> _executeDefinitionsPhase(
-    ExecuteDefinitionsPhaseRequest request) async {
-  try {
-    Macro? instance = _macroInstances[request.macro];
-    if (instance == null) {
-      throw new StateError('Unrecognized macro instance ${request.macro}\n'
-          'Known instances: $_macroInstances)');
-    }
-    DeclarationImpl declaration = request.declaration;
-    if (instance is FunctionDefinitionMacro &&
-        declaration is FunctionDeclarationImpl) {
-      FunctionDefinitionBuilderImpl builder = new FunctionDefinitionBuilderImpl(
-          declaration,
-          request.classIntrospector.instance as ClassIntrospector,
-          request.typeResolver.instance as TypeResolver,
-          request.typeDeclarationResolver.instance as TypeDeclarationResolver);
-      await instance.buildDefinitionForFunction(declaration, builder);
-      return new Response(
-          response: builder.result,
-          requestId: request.id,
-          responseType: MessageType.macroExecutionResult);
-    } else if (instance is MethodDefinitionMacro &&
-        declaration is MethodDeclarationImpl) {
-      FunctionDefinitionBuilderImpl builder = new FunctionDefinitionBuilderImpl(
-          declaration,
-          request.classIntrospector.instance as ClassIntrospector,
-          request.typeResolver.instance as TypeResolver,
-          request.typeDeclarationResolver.instance as TypeDeclarationResolver);
-      await instance.buildDefinitionForMethod(declaration, builder);
-      return new SerializableResponse(
-          responseType: MessageType.macroExecutionResult,
-          response: builder.result,
-          requestId: request.id,
-          serializationZoneId: request.serializationZoneId);
-    } else {
-      throw new UnsupportedError(
-          'Only Method and Function Definition Macros are supported currently');
-    }
-  } catch (e) {
-    return new Response(
-        error: e, requestId: request.id, responseType: MessageType.error);
-  }
-}
diff --git a/pkg/_fe_analyzer_shared/test/macros/executor_shared/augmentation_library_test.dart b/pkg/_fe_analyzer_shared/test/macros/executor/augmentation_library_test.dart
similarity index 94%
rename from pkg/_fe_analyzer_shared/test/macros/executor_shared/augmentation_library_test.dart
rename to pkg/_fe_analyzer_shared/test/macros/executor/augmentation_library_test.dart
index 704898a..87e6bc3 100644
--- a/pkg/_fe_analyzer_shared/test/macros/executor_shared/augmentation_library_test.dart
+++ b/pkg/_fe_analyzer_shared/test/macros/executor/augmentation_library_test.dart
@@ -2,14 +2,14 @@
 // 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.
 
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
 import 'package:test/fake.dart';
 import 'package:test/test.dart';
 
 import 'package:_fe_analyzer_shared/src/macros/executor.dart';
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/augmentation_library.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/response_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/augmentation_library.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/response_impls.dart';
 
 import '../util.dart';
 
diff --git a/pkg/_fe_analyzer_shared/test/macros/executor/executor_test.dart b/pkg/_fe_analyzer_shared/test/macros/executor/executor_test.dart
new file mode 100644
index 0000000..9f35c52
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/macros/executor/executor_test.dart
@@ -0,0 +1,496 @@
+// Copyright (c) 2021, 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.
+
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:_fe_analyzer_shared/src/macros/bootstrap.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/isolated_executor.dart'
+    as isolatedExecutor;
+import 'package:_fe_analyzer_shared/src/macros/executor/process_executor.dart'
+    as processExecutor;
+
+import 'package:test/test.dart';
+
+import '../util.dart';
+
+void main() {
+  late MacroExecutor executor;
+  late File kernelOutputFile;
+  final macroName = 'SimpleMacro';
+  late MacroInstanceIdentifier instanceId;
+  late Uri macroUri;
+  late File simpleMacroFile;
+  late Directory tmpDir;
+
+  for (var executorKind in ['Isolated', 'Process']) {
+    group('$executorKind executor', () {
+      for (var mode in [
+        SerializationMode.byteDataServer,
+        SerializationMode.jsonServer
+      ]) {
+        final clientMode = mode == SerializationMode.byteDataServer
+            ? SerializationMode.byteDataClient
+            : SerializationMode.jsonClient;
+
+        group('$mode', () {
+          setUpAll(() async {
+            simpleMacroFile =
+                File(Platform.script.resolve('simple_macro.dart').toFilePath());
+            executor = executorKind == 'Isolated'
+                ? await isolatedExecutor.start(mode)
+                : await processExecutor.start(mode);
+            tmpDir = Directory.systemTemp.createTempSync('executor_test');
+            macroUri = simpleMacroFile.absolute.uri;
+
+            var bootstrapContent = bootstrapMacroIsolate({
+              macroUri.toString(): {
+                macroName: ['', 'named']
+              }
+            }, clientMode);
+            var bootstrapFile =
+                File(tmpDir.uri.resolve('main.dart').toFilePath())
+                  ..writeAsStringSync(bootstrapContent);
+            kernelOutputFile =
+                File(tmpDir.uri.resolve('main.dart.dill').toFilePath());
+            var packageConfigPath = (await Isolate.packageConfig)!.toFilePath();
+            var buildSnapshotResult =
+                await Process.run(Platform.resolvedExecutable, [
+              if (executorKind == 'Isolated') ...[
+                '--snapshot=${kernelOutputFile.uri.toFilePath()}',
+                '--snapshot-kind=kernel',
+              ] else ...[
+                'compile',
+                'exe',
+                '-o',
+                kernelOutputFile.uri.toFilePath(),
+              ],
+              '--packages=${packageConfigPath}',
+              bootstrapFile.uri.toFilePath(),
+            ]);
+            expect(buildSnapshotResult.exitCode, 0,
+                reason: 'stdout: ${buildSnapshotResult.stdout}\n'
+                    'stderr: ${buildSnapshotResult.stderr}');
+
+            var clazzId = await executor.loadMacro(macroUri, macroName,
+                precompiledKernelUri: kernelOutputFile.uri);
+            expect(clazzId, isNotNull, reason: 'Can load a macro.');
+
+            instanceId =
+                await executor.instantiateMacro(clazzId, '', Arguments([], {}));
+            expect(instanceId, isNotNull,
+                reason: 'Can create an instance with no arguments.');
+
+            instanceId = await executor.instantiateMacro(
+                clazzId, '', Arguments([1, 2], {}));
+            expect(instanceId, isNotNull,
+                reason: 'Can create an instance with positional arguments.');
+
+            instanceId = await executor.instantiateMacro(
+                clazzId, 'named', Arguments([], {'x': 1, 'y': 2}));
+            expect(instanceId, isNotNull,
+                reason: 'Can create an instance with named arguments.');
+          });
+
+          tearDownAll(() {
+            if (tmpDir.existsSync()) {
+              try {
+                // Fails flakily on windows if a process still has the file open
+                tmpDir.deleteSync(recursive: true);
+              } catch (_) {}
+            }
+            executor.close();
+          });
+
+          group('run macros', () {
+            group('in the types phase', () {
+              test('on functions', () async {
+                var result = await executor.executeTypesPhase(
+                    instanceId, Fixtures.myFunction);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('class GeneratedByMyFunction {}'));
+              });
+
+              test('on methods', () async {
+                var result = await executor.executeTypesPhase(
+                    instanceId, Fixtures.myMethod);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('class GeneratedByMyMethod {}'));
+              });
+
+              test('on getters', () async {
+                var result = await executor.executeTypesPhase(
+                  instanceId,
+                  Fixtures.myVariableGetter,
+                );
+                expect(
+                    result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace(
+                        'class GeneratedByMyVariableGetter {}'));
+              });
+
+              test('on setters', () async {
+                var result = await executor.executeTypesPhase(
+                  instanceId,
+                  Fixtures.myVariableSetter,
+                );
+                expect(
+                    result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace(
+                        'class GeneratedByMyVariableSetter {}'));
+              });
+
+              test('on variables', () async {
+                var result = await executor.executeTypesPhase(
+                  instanceId,
+                  Fixtures.myVariable,
+                );
+                expect(
+                    result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace(
+                        'class GeneratedBy_myVariable {}'));
+              });
+
+              test('on constructors', () async {
+                var result = await executor.executeTypesPhase(
+                    instanceId, Fixtures.myConstructor);
+                expect(
+                    result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace(
+                        'class GeneratedByMyConstructor {}'));
+              });
+
+              test('on fields', () async {
+                var result = await executor.executeTypesPhase(
+                    instanceId, Fixtures.myField);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('class GeneratedByMyField {}'));
+              });
+
+              test('on classes', () async {
+                var result = await executor.executeTypesPhase(
+                    instanceId, Fixtures.myClass);
+                expect(
+                    result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace(
+                        'class MyClassBuilder implements Builder<MyClass> {}'));
+              });
+            });
+
+            group('in the declaration phase', () {
+              test('on functions', () async {
+                var result = await executor.executeDeclarationsPhase(
+                    instanceId,
+                    Fixtures.myFunction,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector);
+                expect(
+                    result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace(
+                        'String delegateMyFunction() => myFunction();'));
+              });
+
+              test('on methods', () async {
+                var result = await executor.executeDeclarationsPhase(
+                    instanceId,
+                    Fixtures.myMethod,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector);
+                expect(
+                    result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace(
+                        'String delegateMemberMyMethod() => myMethod();'));
+              });
+
+              test('on constructors', () async {
+                var result = await executor.executeDeclarationsPhase(
+                    instanceId,
+                    Fixtures.myConstructor,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('''
+              augment class MyClass {
+                factory MyClass.myConstructorDelegate() => MyClass.myConstructor();
+              }'''));
+              });
+
+              test('on getters', () async {
+                var result = await executor.executeDeclarationsPhase(
+                    instanceId,
+                    Fixtures.myVariableGetter,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('''
+                String get delegateMyVariable => myVariable;'''));
+              });
+
+              test('on setters', () async {
+                var result = await executor.executeDeclarationsPhase(
+                    instanceId,
+                    Fixtures.myVariableSetter,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('''
+                void set delegateMyVariable(String value) => myVariable = value;'''));
+              });
+
+              test('on variables', () async {
+                var result = await executor.executeDeclarationsPhase(
+                    instanceId,
+                    Fixtures.myVariable,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('''
+                String get delegate_myVariable => _myVariable;'''));
+              });
+
+              test('on fields', () async {
+                var result = await executor.executeDeclarationsPhase(
+                    instanceId,
+                    Fixtures.myField,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('''
+              augment class MyClass {
+                String get delegateMyField => myField;
+              }'''));
+              });
+
+              test('on classes', () async {
+                var result = await executor.executeDeclarationsPhase(
+                    instanceId,
+                    Fixtures.myClass,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('''
+              augment class MyClass {
+                static const List<String> fieldNames = ['myField',];
+              }'''));
+              });
+            });
+
+            group('in the definition phase', () {
+              test('on functions', () async {
+                var result = await executor.executeDefinitionsPhase(
+                    instanceId,
+                    Fixtures.myFunction,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector,
+                    Fixtures.testTypeDeclarationResolver);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('''
+                augment String myFunction() {
+                  print('isAbstract: false');
+                  print('isExternal: false');
+                  print('isGetter: false');
+                  print('isSetter: false');
+                  print('returnType: String');
+                  return augment super();
+                }'''));
+              });
+
+              test('on methods', () async {
+                var definitionResult = await executor.executeDefinitionsPhase(
+                    instanceId,
+                    Fixtures.myMethod,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector,
+                    Fixtures.testTypeDeclarationResolver);
+                expect(definitionResult.augmentations, hasLength(2));
+                var augmentationStrings = definitionResult.augmentations
+                    .map((a) => a.debugString().toString())
+                    .toList();
+                expect(augmentationStrings,
+                    unorderedEquals(methodDefinitionMatchers));
+              });
+
+              test('on constructors', () async {
+                var definitionResult = await executor.executeDefinitionsPhase(
+                    instanceId,
+                    Fixtures.myConstructor,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector,
+                    Fixtures.testTypeDeclarationResolver);
+                expect(definitionResult.augmentations, hasLength(1));
+                expect(
+                    definitionResult.augmentations.first
+                        .debugString()
+                        .toString(),
+                    constructorDefinitionMatcher);
+              });
+
+              test('on getters', () async {
+                var result = await executor.executeDefinitionsPhase(
+                    instanceId,
+                    Fixtures.myVariableGetter,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector,
+                    Fixtures.testTypeDeclarationResolver);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('''
+                augment String myVariable() {
+                  print('isAbstract: false');
+                  print('isExternal: false');
+                  print('isGetter: true');
+                  print('isSetter: false');
+                  print('returnType: String');
+                  return augment super;
+                }'''));
+              });
+
+              test('on setters', () async {
+                var result = await executor.executeDefinitionsPhase(
+                    instanceId,
+                    Fixtures.myVariableSetter,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector,
+                    Fixtures.testTypeDeclarationResolver);
+                expect(result.augmentations.single.debugString().toString(),
+                    equalsIgnoringWhitespace('''
+                augment void myVariable(String value, ) {
+                  print('isAbstract: false');
+                  print('isExternal: false');
+                  print('isGetter: false');
+                  print('isSetter: true');
+                  print('returnType: void');
+                  print('positionalParam: String value');
+                  return augment super = value;
+                }'''));
+              });
+
+              test('on variables', () async {
+                var result = await executor.executeDefinitionsPhase(
+                    instanceId,
+                    Fixtures.myVariable,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector,
+                    Fixtures.testTypeDeclarationResolver);
+                expect(
+                    result.augmentations.map((a) => a.debugString().toString()),
+                    unorderedEquals([
+                      equalsIgnoringWhitespace('''
+                augment String get _myVariable {
+                  print('parentClass: ');
+                  print('isExternal: false');
+                  print('isFinal: true');
+                  print('isLate: false');
+                  return augment super;
+                }'''),
+                      equalsIgnoringWhitespace('''
+                augment set _myVariable(String value) {
+                  augment super = value;
+                }'''),
+                      equalsIgnoringWhitespace('''
+                augment final String _myVariable = 'new initial value' + augment super;
+                '''),
+                    ]));
+              });
+
+              test('on fields', () async {
+                var definitionResult = await executor.executeDefinitionsPhase(
+                    instanceId,
+                    Fixtures.myField,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector,
+                    Fixtures.testTypeDeclarationResolver);
+                expect(definitionResult.augmentations, hasLength(1));
+                expect(
+                    definitionResult.augmentations.first
+                        .debugString()
+                        .toString(),
+                    fieldDefinitionMatcher);
+              });
+
+              test('on classes', () async {
+                var definitionResult = await executor.executeDefinitionsPhase(
+                    instanceId,
+                    Fixtures.myClass,
+                    Fixtures.testTypeResolver,
+                    Fixtures.testClassIntrospector,
+                    Fixtures.testTypeDeclarationResolver);
+                var augmentationStrings = definitionResult.augmentations
+                    .map((a) => a.debugString().toString())
+                    .toList();
+                expect(
+                    augmentationStrings,
+                    unorderedEquals([
+                      ...methodDefinitionMatchers,
+                      constructorDefinitionMatcher,
+                      fieldDefinitionMatcher
+                    ]));
+              });
+            });
+          });
+        });
+      }
+    });
+  }
+}
+
+final constructorDefinitionMatcher = equalsIgnoringWhitespace('''
+augment class MyClass {
+  augment MyClass.myConstructor() {
+    print('definingClass: MyClass');
+    print('isFactory: false');
+    print('isAbstract: false');
+    print('isExternal: false');
+    print('isGetter: false');
+    print('isSetter: false');
+    print('returnType: MyClass');
+    return augment super();
+  }
+}''');
+
+final fieldDefinitionMatcher = equalsIgnoringWhitespace('''
+augment class MyClass {
+  augment String get myField {
+    print('parentClass: MyClass');
+    print('isExternal: false');
+    print('isFinal: false');
+    print('isLate: false');
+    return augment super;
+  }
+  augment set myField(String value) {
+    augment super = value;
+  }
+  augment String myField = \'new initial value\' + augment super;
+}''');
+
+final methodDefinitionMatchers = [
+  equalsIgnoringWhitespace('''
+    augment class MyClass {
+      augment String myMethod() {
+        print('definingClass: MyClass');
+        print('isAbstract: false');
+        print('isExternal: false');
+        print('isGetter: false');
+        print('isSetter: false');
+        print('returnType: String');
+        return augment super();
+      }
+    }
+    '''),
+  equalsIgnoringWhitespace('''
+    augment class MyClass {
+      augment String myMethod() {
+        print('x: 1, y: 2');
+        print('parentClass: MyClass');
+        print('superClass: MySuperclass');
+        print('interface: MyInterface');
+        print('mixin: MyMixin');
+        print('field: myField');
+        print('method: myMethod');
+        print('constructor: myConstructor');
+        return augment super();
+      }
+    }'''),
+];
diff --git a/pkg/_fe_analyzer_shared/test/macros/executor_shared/response_impls_test.dart b/pkg/_fe_analyzer_shared/test/macros/executor/response_impls_test.dart
similarity index 97%
rename from pkg/_fe_analyzer_shared/test/macros/executor_shared/response_impls_test.dart
rename to pkg/_fe_analyzer_shared/test/macros/executor/response_impls_test.dart
index 88ef6fb..c64b7c7 100644
--- a/pkg/_fe_analyzer_shared/test/macros/executor_shared/response_impls_test.dart
+++ b/pkg/_fe_analyzer_shared/test/macros/executor/response_impls_test.dart
@@ -2,11 +2,11 @@
 // 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.
 
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
 import 'package:test/fake.dart';
 import 'package:test/test.dart';
 
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/response_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/response_impls.dart';
 import 'package:_fe_analyzer_shared/src/macros/executor.dart';
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
 
diff --git a/pkg/_fe_analyzer_shared/test/macros/executor_shared/serialization_test.dart b/pkg/_fe_analyzer_shared/test/macros/executor/serialization_test.dart
similarity index 97%
rename from pkg/_fe_analyzer_shared/test/macros/executor_shared/serialization_test.dart
rename to pkg/_fe_analyzer_shared/test/macros/executor/serialization_test.dart
index 5a9842e..eb74a8e 100644
--- a/pkg/_fe_analyzer_shared/test/macros/executor_shared/serialization_test.dart
+++ b/pkg/_fe_analyzer_shared/test/macros/executor/serialization_test.dart
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization_extensions.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization_extensions.dart';
 import 'package:test/test.dart';
 
 import '../util.dart';
diff --git a/pkg/_fe_analyzer_shared/test/macros/isolated_executor/simple_macro.dart b/pkg/_fe_analyzer_shared/test/macros/executor/simple_macro.dart
similarity index 100%
rename from pkg/_fe_analyzer_shared/test/macros/isolated_executor/simple_macro.dart
rename to pkg/_fe_analyzer_shared/test/macros/executor/simple_macro.dart
diff --git a/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/isolate_mirror_executor_test.dart b/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/isolate_mirror_executor_test.dart
deleted file mode 100644
index a2223c9..0000000
--- a/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/isolate_mirror_executor_test.dart
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (c) 2021, 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.
-
-import 'dart:io';
-
-import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
-import 'package:_fe_analyzer_shared/src/macros/isolate_mirrors_executor/isolate_mirrors_executor.dart'
-    as mirrorExecutor;
-
-import 'package:test/test.dart';
-
-import '../util.dart';
-
-void main() {
-  late MacroExecutor executor;
-  late File simpleMacroFile;
-
-  setUpAll(() {
-    // We support running from either the root of the SDK or the package root.
-    simpleMacroFile = File(
-        'pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/simple_macro.dart');
-    if (!simpleMacroFile.existsSync()) {
-      simpleMacroFile =
-          File('test/macros/isolate_mirror_executor/simple_macro.dart');
-    }
-  });
-
-  setUp(() async {
-    executor = await mirrorExecutor.start();
-  });
-
-  tearDown(() {
-    executor.close();
-  });
-
-  test('can load macros and create instances', () async {
-    var clazzId =
-        await executor.loadMacro(simpleMacroFile.absolute.uri, 'SimpleMacro');
-    expect(clazzId, isNotNull, reason: 'Can load a macro.');
-
-    var instanceId =
-        await executor.instantiateMacro(clazzId, '', Arguments([], {}));
-    expect(instanceId, isNotNull,
-        reason: 'Can create an instance with no arguments.');
-
-    instanceId =
-        await executor.instantiateMacro(clazzId, '', Arguments([1, 2], {}));
-    expect(instanceId, isNotNull,
-        reason: 'Can create an instance with positional arguments.');
-
-    instanceId = await executor.instantiateMacro(
-        clazzId, 'named', Arguments([], {'x': 1, 'y': 2}));
-    expect(instanceId, isNotNull,
-        reason: 'Can create an instance with named arguments.');
-
-    var returnType = NamedTypeAnnotationImpl(
-        id: RemoteInstance.uniqueId,
-        identifier: IdentifierImpl(id: RemoteInstance.uniqueId, name: 'String'),
-        isNullable: false,
-        typeArguments: const []);
-    var definitionResult = await executor.executeDefinitionsPhase(
-        instanceId,
-        FunctionDeclarationImpl(
-          id: RemoteInstance.uniqueId,
-          isAbstract: false,
-          isExternal: false,
-          isGetter: false,
-          isOperator: false,
-          isSetter: false,
-          identifier: IdentifierImpl(id: RemoteInstance.uniqueId, name: 'foo'),
-          namedParameters: [],
-          positionalParameters: [],
-          returnType: returnType,
-          typeParameters: [],
-        ),
-        TestTypeResolver({
-          returnType.identifier:
-              TestNamedStaticType(returnType.identifier, 'dart:core', [])
-        }),
-        FakeClassIntrospector(),
-        FakeTypeDeclarationResolver());
-    expect(definitionResult.augmentations, hasLength(1));
-    expect(definitionResult.augmentations.first.debugString().toString(),
-        equalsIgnoringWhitespace('''
-            augment String foo() {
-              print('x: 1, y: 2');
-              return augment super();
-            }'''));
-  });
-}
diff --git a/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/simple_macro.dart b/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/simple_macro.dart
deleted file mode 100644
index f23aa43..0000000
--- a/pkg/_fe_analyzer_shared/test/macros/isolate_mirror_executor/simple_macro.dart
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (c) 2021, 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.
-
-import 'dart:async';
-
-import 'package:_fe_analyzer_shared/src/macros/api.dart';
-
-/// A very simple macro that annotates functions (or getters) with no arguments
-/// and adds a print statement to the top of them.
-class SimpleMacro implements FunctionDefinitionMacro {
-  final int? x;
-  final int? y;
-
-  SimpleMacro([this.x, this.y]);
-
-  SimpleMacro.named({this.x, this.y});
-
-  @override
-  FutureOr<void> buildDefinitionForFunction(
-      FunctionDeclaration method, FunctionDefinitionBuilder builder) async {
-    if (method.namedParameters
-        .followedBy(method.positionalParameters)
-        .isNotEmpty) {
-      throw ArgumentError(
-          'This macro can only be run on functions with no arguments!');
-    }
-
-    // Test the type resolver and static type interfaces
-    var staticReturnType = await builder.resolve(method.returnType.code);
-    if (!(await staticReturnType.isExactly(staticReturnType))) {
-      throw StateError('The return type should be exactly equal to itself!');
-    }
-    if (!(await staticReturnType.isSubtypeOf(staticReturnType))) {
-      throw StateError('The return type should be a subtype of itself!');
-    }
-
-    builder.augment(FunctionBodyCode.fromString('''{
-      print('x: $x, y: $y');
-      return augment super();
-    }'''));
-  }
-}
diff --git a/pkg/_fe_analyzer_shared/test/macros/isolated_executor/isolated_executor_test.dart b/pkg/_fe_analyzer_shared/test/macros/isolated_executor/isolated_executor_test.dart
deleted file mode 100644
index ec4408f..0000000
--- a/pkg/_fe_analyzer_shared/test/macros/isolated_executor/isolated_executor_test.dart
+++ /dev/null
@@ -1,466 +0,0 @@
-// Copyright (c) 2021, 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.
-
-import 'dart:io';
-import 'dart:isolate';
-
-import 'package:_fe_analyzer_shared/src/macros/bootstrap.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
-import 'package:_fe_analyzer_shared/src/macros/isolated_executor/isolated_executor.dart'
-    as isolatedExecutor;
-
-import 'package:test/test.dart';
-
-import '../util.dart';
-
-void main() {
-  late MacroExecutor executor;
-  late File kernelOutputFile;
-  final macroName = 'SimpleMacro';
-  late MacroInstanceIdentifier instanceId;
-  late Uri macroUri;
-  late File simpleMacroFile;
-  late Directory tmpDir;
-
-  for (var mode in [
-    SerializationMode.byteDataServer,
-    SerializationMode.jsonServer
-  ]) {
-    final clientMode = mode == SerializationMode.byteDataServer
-        ? SerializationMode.byteDataClient
-        : SerializationMode.jsonClient;
-
-    group('$mode', () {
-      setUpAll(() async {
-        simpleMacroFile =
-            File(Platform.script.resolve('simple_macro.dart').toFilePath());
-        executor = await isolatedExecutor.start(mode);
-        tmpDir = Directory.systemTemp.createTempSync('isolated_executor_test');
-        macroUri = simpleMacroFile.absolute.uri;
-
-        var bootstrapContent = bootstrapMacroIsolate({
-          macroUri.toString(): {
-            macroName: ['', 'named']
-          }
-        }, clientMode);
-        var bootstrapFile = File(tmpDir.uri.resolve('main.dart').toFilePath())
-          ..writeAsStringSync(bootstrapContent);
-        kernelOutputFile =
-            File(tmpDir.uri.resolve('main.dart.dill').toFilePath());
-        var buildSnapshotResult =
-            await Process.run(Platform.resolvedExecutable, [
-          '--snapshot=${kernelOutputFile.uri.toFilePath()}',
-          '--snapshot-kind=kernel',
-          '--packages=${(await Isolate.packageConfig)!}',
-          bootstrapFile.uri.toFilePath(),
-        ]);
-        expect(buildSnapshotResult.exitCode, 0,
-            reason: 'stdout: ${buildSnapshotResult.stdout}\n'
-                'stderr: ${buildSnapshotResult.stderr}');
-
-        var clazzId = await executor.loadMacro(macroUri, macroName,
-            precompiledKernelUri: kernelOutputFile.uri);
-        expect(clazzId, isNotNull, reason: 'Can load a macro.');
-
-        instanceId =
-            await executor.instantiateMacro(clazzId, '', Arguments([], {}));
-        expect(instanceId, isNotNull,
-            reason: 'Can create an instance with no arguments.');
-
-        instanceId =
-            await executor.instantiateMacro(clazzId, '', Arguments([1, 2], {}));
-        expect(instanceId, isNotNull,
-            reason: 'Can create an instance with positional arguments.');
-
-        instanceId = await executor.instantiateMacro(
-            clazzId, 'named', Arguments([], {'x': 1, 'y': 2}));
-        expect(instanceId, isNotNull,
-            reason: 'Can create an instance with named arguments.');
-      });
-
-      tearDownAll(() {
-        if (tmpDir.existsSync()) tmpDir.deleteSync(recursive: true);
-        executor.close();
-      });
-
-      group('run macros', () {
-        group('in the types phase', () {
-          test('on functions', () async {
-            var result = await executor.executeTypesPhase(
-                instanceId, Fixtures.myFunction);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('class GeneratedByMyFunction {}'));
-          });
-
-          test('on methods', () async {
-            var result =
-                await executor.executeTypesPhase(instanceId, Fixtures.myMethod);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('class GeneratedByMyMethod {}'));
-          });
-
-          test('on getters', () async {
-            var result = await executor.executeTypesPhase(
-              instanceId,
-              Fixtures.myVariableGetter,
-            );
-            expect(
-                result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace(
-                    'class GeneratedByMyVariableGetter {}'));
-          });
-
-          test('on setters', () async {
-            var result = await executor.executeTypesPhase(
-              instanceId,
-              Fixtures.myVariableSetter,
-            );
-            expect(
-                result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace(
-                    'class GeneratedByMyVariableSetter {}'));
-          });
-
-          test('on variables', () async {
-            var result = await executor.executeTypesPhase(
-              instanceId,
-              Fixtures.myVariable,
-            );
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('class GeneratedBy_myVariable {}'));
-          });
-
-          test('on constructors', () async {
-            var result = await executor.executeTypesPhase(
-                instanceId, Fixtures.myConstructor);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('class GeneratedByMyConstructor {}'));
-          });
-
-          test('on fields', () async {
-            var result =
-                await executor.executeTypesPhase(instanceId, Fixtures.myField);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('class GeneratedByMyField {}'));
-          });
-
-          test('on classes', () async {
-            var result =
-                await executor.executeTypesPhase(instanceId, Fixtures.myClass);
-            expect(
-                result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace(
-                    'class MyClassBuilder implements Builder<MyClass> {}'));
-          });
-        });
-
-        group('in the declaration phase', () {
-          test('on functions', () async {
-            var result = await executor.executeDeclarationsPhase(
-                instanceId,
-                Fixtures.myFunction,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector);
-            expect(
-                result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace(
-                    'String delegateMyFunction() => myFunction();'));
-          });
-
-          test('on methods', () async {
-            var result = await executor.executeDeclarationsPhase(
-                instanceId,
-                Fixtures.myMethod,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector);
-            expect(
-                result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace(
-                    'String delegateMemberMyMethod() => myMethod();'));
-          });
-
-          test('on constructors', () async {
-            var result = await executor.executeDeclarationsPhase(
-                instanceId,
-                Fixtures.myConstructor,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('''
-              augment class MyClass {
-                factory MyClass.myConstructorDelegate() => MyClass.myConstructor();
-              }'''));
-          });
-
-          test('on getters', () async {
-            var result = await executor.executeDeclarationsPhase(
-                instanceId,
-                Fixtures.myVariableGetter,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('''
-                String get delegateMyVariable => myVariable;'''));
-          });
-
-          test('on setters', () async {
-            var result = await executor.executeDeclarationsPhase(
-                instanceId,
-                Fixtures.myVariableSetter,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('''
-                void set delegateMyVariable(String value) => myVariable = value;'''));
-          });
-
-          test('on variables', () async {
-            var result = await executor.executeDeclarationsPhase(
-                instanceId,
-                Fixtures.myVariable,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('''
-                String get delegate_myVariable => _myVariable;'''));
-          });
-
-          test('on fields', () async {
-            var result = await executor.executeDeclarationsPhase(
-                instanceId,
-                Fixtures.myField,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('''
-              augment class MyClass {
-                String get delegateMyField => myField;
-              }'''));
-          });
-
-          test('on classes', () async {
-            var result = await executor.executeDeclarationsPhase(
-                instanceId,
-                Fixtures.myClass,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('''
-              augment class MyClass {
-                static const List<String> fieldNames = ['myField',];
-              }'''));
-          });
-        });
-
-        group('in the definition phase', () {
-          test('on functions', () async {
-            var result = await executor.executeDefinitionsPhase(
-                instanceId,
-                Fixtures.myFunction,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector,
-                Fixtures.testTypeDeclarationResolver);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('''
-                augment String myFunction() {
-                  print('isAbstract: false');
-                  print('isExternal: false');
-                  print('isGetter: false');
-                  print('isSetter: false');
-                  print('returnType: String');
-                  return augment super();
-                }'''));
-          });
-
-          test('on methods', () async {
-            var definitionResult = await executor.executeDefinitionsPhase(
-                instanceId,
-                Fixtures.myMethod,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector,
-                Fixtures.testTypeDeclarationResolver);
-            expect(definitionResult.augmentations, hasLength(2));
-            var augmentationStrings = definitionResult.augmentations
-                .map((a) => a.debugString().toString())
-                .toList();
-            expect(
-                augmentationStrings, unorderedEquals(methodDefinitionMatchers));
-          });
-
-          test('on constructors', () async {
-            var definitionResult = await executor.executeDefinitionsPhase(
-                instanceId,
-                Fixtures.myConstructor,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector,
-                Fixtures.testTypeDeclarationResolver);
-            expect(definitionResult.augmentations, hasLength(1));
-            expect(
-                definitionResult.augmentations.first.debugString().toString(),
-                constructorDefinitionMatcher);
-          });
-
-          test('on getters', () async {
-            var result = await executor.executeDefinitionsPhase(
-                instanceId,
-                Fixtures.myVariableGetter,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector,
-                Fixtures.testTypeDeclarationResolver);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('''
-                augment String myVariable() {
-                  print('isAbstract: false');
-                  print('isExternal: false');
-                  print('isGetter: true');
-                  print('isSetter: false');
-                  print('returnType: String');
-                  return augment super;
-                }'''));
-          });
-
-          test('on setters', () async {
-            var result = await executor.executeDefinitionsPhase(
-                instanceId,
-                Fixtures.myVariableSetter,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector,
-                Fixtures.testTypeDeclarationResolver);
-            expect(result.augmentations.single.debugString().toString(),
-                equalsIgnoringWhitespace('''
-                augment void myVariable(String value, ) {
-                  print('isAbstract: false');
-                  print('isExternal: false');
-                  print('isGetter: false');
-                  print('isSetter: true');
-                  print('returnType: void');
-                  print('positionalParam: String value');
-                  return augment super = value;
-                }'''));
-          });
-
-          test('on variables', () async {
-            var result = await executor.executeDefinitionsPhase(
-                instanceId,
-                Fixtures.myVariable,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector,
-                Fixtures.testTypeDeclarationResolver);
-            expect(
-                result.augmentations.map((a) => a.debugString().toString()),
-                unorderedEquals([
-                  equalsIgnoringWhitespace('''
-                augment String get _myVariable {
-                  print('parentClass: ');
-                  print('isExternal: false');
-                  print('isFinal: true');
-                  print('isLate: false');
-                  return augment super;
-                }'''),
-                  equalsIgnoringWhitespace('''
-                augment set _myVariable(String value) {
-                  augment super = value;
-                }'''),
-                  equalsIgnoringWhitespace('''
-                augment final String _myVariable = 'new initial value' + augment super;
-                '''),
-                ]));
-          });
-
-          test('on fields', () async {
-            var definitionResult = await executor.executeDefinitionsPhase(
-                instanceId,
-                Fixtures.myField,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector,
-                Fixtures.testTypeDeclarationResolver);
-            expect(definitionResult.augmentations, hasLength(1));
-            expect(
-                definitionResult.augmentations.first.debugString().toString(),
-                fieldDefinitionMatcher);
-          });
-
-          test('on classes', () async {
-            var definitionResult = await executor.executeDefinitionsPhase(
-                instanceId,
-                Fixtures.myClass,
-                Fixtures.testTypeResolver,
-                Fixtures.testClassIntrospector,
-                Fixtures.testTypeDeclarationResolver);
-            var augmentationStrings = definitionResult.augmentations
-                .map((a) => a.debugString().toString())
-                .toList();
-            expect(
-                augmentationStrings,
-                unorderedEquals([
-                  ...methodDefinitionMatchers,
-                  constructorDefinitionMatcher,
-                  fieldDefinitionMatcher
-                ]));
-          });
-        });
-      });
-    });
-  }
-}
-
-final constructorDefinitionMatcher = equalsIgnoringWhitespace('''
-augment class MyClass {
-  augment MyClass.myConstructor() {
-    print('definingClass: MyClass');
-    print('isFactory: false');
-    print('isAbstract: false');
-    print('isExternal: false');
-    print('isGetter: false');
-    print('isSetter: false');
-    print('returnType: MyClass');
-    return augment super();
-  }
-}''');
-
-final fieldDefinitionMatcher = equalsIgnoringWhitespace('''
-augment class MyClass {
-  augment String get myField {
-    print('parentClass: MyClass');
-    print('isExternal: false');
-    print('isFinal: false');
-    print('isLate: false');
-    return augment super;
-  }
-  augment set myField(String value) {
-    augment super = value;
-  }
-  augment String myField = \'new initial value\' + augment super;
-}''');
-
-final methodDefinitionMatchers = [
-  equalsIgnoringWhitespace('''
-    augment class MyClass {
-      augment String myMethod() {
-        print('definingClass: MyClass');
-        print('isAbstract: false');
-        print('isExternal: false');
-        print('isGetter: false');
-        print('isSetter: false');
-        print('returnType: String');
-        return augment super();
-      }
-    }
-    '''),
-  equalsIgnoringWhitespace('''
-    augment class MyClass {
-      augment String myMethod() {
-        print('x: 1, y: 2');
-        print('parentClass: MyClass');
-        print('superClass: MySuperclass');
-        print('interface: MyInterface');
-        print('mixin: MyMixin');
-        print('field: myField');
-        print('method: myMethod');
-        print('constructor: myConstructor');
-        return augment super();
-      }
-    }'''),
-];
diff --git a/pkg/_fe_analyzer_shared/test/macros/util.dart b/pkg/_fe_analyzer_shared/test/macros/util.dart
index 88f02d6..0907c70 100644
--- a/pkg/_fe_analyzer_shared/test/macros/util.dart
+++ b/pkg/_fe_analyzer_shared/test/macros/util.dart
@@ -6,8 +6,8 @@
 
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
 import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart';
 
 import 'package:test/fake.dart';
 import 'package:test/test.dart';
diff --git a/pkg/compiler/lib/src/common/backend_api.dart b/pkg/compiler/lib/src/common/backend_api.dart
deleted file mode 100644
index 5c38952..0000000
--- a/pkg/compiler/lib/src/common/backend_api.dart
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2012, 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.
-
-library dart2js.backend_api;
-
-import '../common/resolution.dart' show ResolutionImpact;
-import '../universe/world_impact.dart' show WorldImpact;
-
-/// Target-specific transformation for resolution world impacts.
-///
-/// This processes target-agnostic [ResolutionImpact]s and creates [WorldImpact]
-/// in which backend/target specific impact data is added, for example: if
-/// certain feature is used that requires some helper code from the backend
-/// libraries, this will be included by the impact transformer.
-class ImpactTransformer {
-  /// Transform the [ResolutionImpact] into a [WorldImpact] adding the
-  /// backend dependencies for features used in [worldImpact].
-  WorldImpact transformResolutionImpact(ResolutionImpact worldImpact) {
-    return worldImpact;
-  }
-}
diff --git a/pkg/compiler/lib/src/js_backend/impact_transformer.dart b/pkg/compiler/lib/src/js_backend/impact_transformer.dart
index 3a92051..e340b31 100644
--- a/pkg/compiler/lib/src/js_backend/impact_transformer.dart
+++ b/pkg/compiler/lib/src/js_backend/impact_transformer.dart
@@ -8,7 +8,6 @@
 
 import '../common.dart';
 import '../common/elements.dart';
-import '../common/backend_api.dart' show ImpactTransformer;
 import '../common/codegen.dart' show CodegenImpact;
 import '../common/resolution.dart' show ResolutionImpact;
 import '../constants/values.dart';
@@ -34,7 +33,13 @@
 import 'runtime_types.dart';
 import 'runtime_types_resolution.dart';
 
-class JavaScriptImpactTransformer extends ImpactTransformer {
+/// JavaScript specific transformation for resolution world impacts.
+///
+/// This processes target-agnostic [ResolutionImpact]s and creates [WorldImpact]
+/// in which JavaScript specific impact data is added, for example: if
+/// a certain feature is used that requires some helper code from the backend
+/// libraries, this will be included by the impact transformer.
+class JavaScriptImpactTransformer {
   final ElementEnvironment _elementEnvironment;
   final CommonElements _commonElements;
   final BackendImpacts _impacts;
@@ -60,7 +65,8 @@
 
   DartTypes get _dartTypes => _commonElements.dartTypes;
 
-  @override
+  /// Transform the [ResolutionImpact] into a [WorldImpact] adding the
+  /// backend dependencies for features used in [worldImpact].
   WorldImpact transformResolutionImpact(ResolutionImpact worldImpact) {
     TransformedWorldImpact transformed = TransformedWorldImpact(worldImpact);
 
diff --git a/pkg/compiler/lib/src/kernel/kernel_strategy.dart b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
index dcd7fb2..ef176a1 100644
--- a/pkg/compiler/lib/src/kernel/kernel_strategy.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
@@ -7,7 +7,6 @@
 import 'package:kernel/ast.dart' as ir;
 
 import '../common.dart';
-import '../common/backend_api.dart';
 import '../common/elements.dart';
 import '../common/names.dart' show Uris;
 import '../common/resolution.dart';
@@ -159,7 +158,7 @@
     // before creating the resolution enqueuer.
     AnnotationsData annotationsData = AnnotationsDataImpl(
         compiler.options, annotationsDataBuilder.pragmaAnnotations);
-    ImpactTransformer impactTransformer = JavaScriptImpactTransformer(
+    final impactTransformer = JavaScriptImpactTransformer(
         elementEnvironment,
         commonElements,
         impacts,
@@ -303,7 +302,7 @@
 class KernelWorkItemBuilder implements WorkItemBuilder {
   final CompilerTask _compilerTask;
   final KernelToElementMapImpl _elementMap;
-  final ImpactTransformer _impactTransformer;
+  final JavaScriptImpactTransformer _impactTransformer;
   final NativeMemberResolver _nativeMemberResolver;
   final AnnotationsDataBuilder _annotationsDataBuilder;
   final Map<MemberEntity, ClosureScopeModel> _closureModels;
@@ -347,7 +346,7 @@
 class KernelWorkItem implements WorkItem {
   final CompilerTask _compilerTask;
   final KernelToElementMapImpl _elementMap;
-  final ImpactTransformer _impactTransformer;
+  final JavaScriptImpactTransformer _impactTransformer;
   final NativeMemberResolver _nativeMemberResolver;
   final AnnotationsDataBuilder _annotationsDataBuilder;
   @override
diff --git a/pkg/front_end/lib/src/fasta/kernel/macro.dart b/pkg/front_end/lib/src/fasta/kernel/macro.dart
index 6f6abea..c4a34b3 100644
--- a/pkg/front_end/lib/src/fasta/kernel/macro.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/macro.dart
@@ -4,9 +4,9 @@
 
 import 'package:_fe_analyzer_shared/src/macros/api.dart' as macro;
 import 'package:_fe_analyzer_shared/src/macros/executor.dart' as macro;
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/introspection_impls.dart'
+import 'package:_fe_analyzer_shared/src/macros/executor/introspection_impls.dart'
     as macro;
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/remote_instance.dart'
+import 'package:_fe_analyzer_shared/src/macros/executor/remote_instance.dart'
     as macro;
 import 'package:front_end/src/base/common.dart';
 import 'package:kernel/ast.dart' show DartType;
diff --git a/pkg/front_end/lib/src/kernel_generator_impl.dart b/pkg/front_end/lib/src/kernel_generator_impl.dart
index a20e586..8801b72 100644
--- a/pkg/front_end/lib/src/kernel_generator_impl.dart
+++ b/pkg/front_end/lib/src/kernel_generator_impl.dart
@@ -6,7 +6,7 @@
 library front_end.kernel_generator_impl;
 
 import 'package:_fe_analyzer_shared/src/macros/bootstrap.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
 import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
 import 'package:kernel/ast.dart';
 import 'package:kernel/class_hierarchy.dart';
diff --git a/pkg/front_end/test/macro_api_test.dart b/pkg/front_end/test/macro_api_test.dart
index 0938f4e..c096d36 100644
--- a/pkg/front_end/test/macro_api_test.dart
+++ b/pkg/front_end/test/macro_api_test.dart
@@ -4,8 +4,8 @@
 
 import 'dart:io' show Directory, Platform;
 
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
-import 'package:_fe_analyzer_shared/src/macros/isolated_executor/isolated_executor.dart'
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/isolated_executor.dart'
     as isolatedExecutor;
 import 'package:expect/expect.dart';
 import 'package:front_end/src/api_prototype/experimental_flags.dart';
diff --git a/pkg/front_end/test/macro_application/macro_application_test.dart b/pkg/front_end/test/macro_application/macro_application_test.dart
index 9282bac..fd317c7 100644
--- a/pkg/front_end/test/macro_application/macro_application_test.dart
+++ b/pkg/front_end/test/macro_application/macro_application_test.dart
@@ -6,8 +6,8 @@
 
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
 import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
-import 'package:_fe_analyzer_shared/src/macros/isolated_executor/isolated_executor.dart'
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/isolated_executor.dart'
     as isolatedExecutor;
 import 'package:_fe_analyzer_shared/src/testing/id.dart'
     show ActualData, ClassId, Id, LibraryId;
diff --git a/pkg/front_end/test/macros/macro_test.dart b/pkg/front_end/test/macros/macro_test.dart
index 514d77b..e583b6b 100644
--- a/pkg/front_end/test/macros/macro_test.dart
+++ b/pkg/front_end/test/macros/macro_test.dart
@@ -6,7 +6,7 @@
 
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
 import 'package:_fe_analyzer_shared/src/macros/executor.dart';
-import 'package:_fe_analyzer_shared/src/macros/executor_shared/serialization.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
 import 'package:_fe_analyzer_shared/src/testing/features.dart';
 import 'package:_fe_analyzer_shared/src/testing/id.dart' show ActualData, Id;
 import 'package:_fe_analyzer_shared/src/testing/id_testing.dart';
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index a8d2705..5669280 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -211,6 +211,8 @@
 combinations
 combinator
 combiner
+communicate
+communicates
 communication
 compared
 compares
@@ -522,6 +524,7 @@
 gradually
 granted
 graphs
+grouper
 growability
 gt
 guarantee
@@ -676,6 +679,7 @@
 lang
 largest
 lattice
+launch
 launched
 launcher
 layer
@@ -705,6 +709,7 @@
 linebreak
 linter
 linux
+listening
 lives
 ll
 llub
@@ -839,6 +844,7 @@
 orphans
 ors
 os
+ourselves
 outlined
 outputs
 outputting
@@ -924,6 +930,7 @@
 println
 prioritization
 proc
+processes
 processor
 producers
 product
diff --git a/runtime/bin/dartutils.cc b/runtime/bin/dartutils.cc
index 05a500e..5f2e056 100644
--- a/runtime/bin/dartutils.cc
+++ b/runtime/bin/dartutils.cc
@@ -658,6 +658,11 @@
   return Dart_PostCObject(port_id, &object);
 }
 
+bool DartUtils::PostString(Dart_Port port_id, const char* value) {
+  Dart_CObject* object = CObject::NewString(value);
+  return Dart_PostCObject(port_id, object);
+}
+
 Dart_Handle DartUtils::GetDartType(const char* library_url,
                                    const char* class_name) {
   return Dart_GetNonNullableType(Dart_LookupLibrary(NewString(library_url)),
diff --git a/runtime/bin/dartutils.h b/runtime/bin/dartutils.h
index bbe0a1f..ca20354 100644
--- a/runtime/bin/dartutils.h
+++ b/runtime/bin/dartutils.h
@@ -173,6 +173,7 @@
   static bool PostNull(Dart_Port port_id);
   static bool PostInt32(Dart_Port port_id, int32_t value);
   static bool PostInt64(Dart_Port port_id, int64_t value);
+  static bool PostString(Dart_Port port_id, const char* value);
 
   static Dart_Handle GetDartType(const char* library_url,
                                  const char* class_name);
diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc
index 17b9404..ba4c459 100644
--- a/runtime/bin/io_natives.cc
+++ b/runtime/bin/io_natives.cc
@@ -129,6 +129,7 @@
   V(SecureSocket_Init, 1)                                                      \
   V(SecureSocket_PeerCertificate, 1)                                           \
   V(SecureSocket_RegisterBadCertificateCallback, 2)                            \
+  V(SecureSocket_RegisterKeyLogPort, 2)                                        \
   V(SecureSocket_RegisterHandshakeCompleteCallback, 2)                         \
   V(SecureSocket_Renegotiate, 4)                                               \
   V(SecurityContext_Allocate, 1)                                               \
diff --git a/runtime/bin/secure_socket_filter.cc b/runtime/bin/secure_socket_filter.cc
index 57c5829..43e3a9d 100644
--- a/runtime/bin/secure_socket_filter.cc
+++ b/runtime/bin/secure_socket_filter.cc
@@ -207,6 +207,15 @@
   GetFilter(args)->RegisterBadCertificateCallback(callback);
 }
 
+void FUNCTION_NAME(SecureSocket_RegisterKeyLogPort)(Dart_NativeArguments args) {
+  Dart_Handle port = ThrowIfError(Dart_GetNativeArgument(args, 1));
+  ASSERT(!Dart_IsNull(port));
+
+  Dart_Port port_id;
+  ThrowIfError(Dart_SendPortGetId(port, &port_id));
+  GetFilter(args)->RegisterKeyLogPort(port_id);
+}
+
 void FUNCTION_NAME(SecureSocket_PeerCertificate)(Dart_NativeArguments args) {
   Dart_Handle cert = ThrowIfError(GetFilter(args)->PeerCertificate());
   Dart_SetReturnValue(args, cert);
@@ -465,6 +474,10 @@
   return X509Helper::WrappedX509Certificate(ca);
 }
 
+void SSLFilter::RegisterKeyLogPort(Dart_Port key_log_port) {
+  key_log_port_ = key_log_port;
+}
+
 void SSLFilter::InitializeLibrary() {
   MutexLocker locker(mutex_);
   if (!library_initialized_) {
@@ -595,10 +608,14 @@
     return SSL_ERROR_WANT_CERTIFICATE_VERIFY;
   }
   if (callback_error != NULL) {
-    // The SSL_do_handshake will try performing a handshake and might call
-    // a CertificateCallback. If the certificate validation
-    // failed the 'callback_error" will be set by the certificateCallback
-    // logic and we propagate the error"
+    // The SSL_do_handshake will try performing a handshake and might call one
+    // or both of:
+    //   SSLCertContext::KeyLogCallback
+    //   SSLCertContext::CertificateCallback
+    //
+    // If either of those functions fail, and this.callback_error has not
+    // already been set, then they will set this.callback_error to an error
+    // handle i.e. only the first error will be captured and propogated.
     Dart_PropagateError(callback_error);
   }
   if (SSL_want_write(ssl_) || SSL_want_read(ssl_)) {
diff --git a/runtime/bin/secure_socket_filter.h b/runtime/bin/secure_socket_filter.h
index 826e28f..56f7f58 100644
--- a/runtime/bin/secure_socket_filter.h
+++ b/runtime/bin/secure_socket_filter.h
@@ -90,6 +90,8 @@
                    bool require_client_certificate);
   void RegisterHandshakeCompleteCallback(Dart_Handle handshake_complete);
   void RegisterBadCertificateCallback(Dart_Handle callback);
+  void RegisterKeyLogPort(Dart_Port key_log_port);
+  Dart_Port key_log_port() { return key_log_port_; }
   Dart_Handle bad_certificate_callback() {
     return Dart_HandleFromPersistent(bad_certificate_callback_);
   }
@@ -145,6 +147,7 @@
 
   Dart_Port reply_port_ = ILLEGAL_PORT;
   Dart_Port trust_evaluate_reply_port_ = ILLEGAL_PORT;
+  Dart_Port key_log_port_ = ILLEGAL_PORT;
 
   static bool IsBufferEncrypted(int i) {
     return static_cast<BufferIndex>(i) >= kFirstEncrypted;
diff --git a/runtime/bin/secure_socket_unsupported.cc b/runtime/bin/secure_socket_unsupported.cc
index 8d2b2fc..37c2972 100644
--- a/runtime/bin/secure_socket_unsupported.cc
+++ b/runtime/bin/secure_socket_unsupported.cc
@@ -65,6 +65,11 @@
       "Secure Sockets unsupported on this platform"));
 }
 
+void FUNCTION_NAME(SecureSocket_RegisterKeyLogPort)(Dart_NativeArguments args) {
+  Dart_ThrowException(DartUtils::NewDartArgumentError(
+      "Secure Sockets unsupported on this platform"));
+}
+
 void FUNCTION_NAME(SecureSocket_ProcessBuffer)(Dart_NativeArguments args) {
   Dart_ThrowException(DartUtils::NewDartArgumentError(
       "Secure Sockets unsupported on this platform"));
diff --git a/runtime/bin/security_context.cc b/runtime/bin/security_context.cc
index 77e7ad1..8ccc6a3 100644
--- a/runtime/bin/security_context.cc
+++ b/runtime/bin/security_context.cc
@@ -74,13 +74,24 @@
         "BadCertificateCallback returned a value that was not a boolean",
         Dart_Null()));
   }
-  if (Dart_IsError(result)) {
+  // See SSLFilter::Handshake for the semantics of filter->callback_error.
+  if (Dart_IsError(result) && filter->callback_error == nullptr) {
     filter->callback_error = result;
     return 0;
   }
   return static_cast<int>(DartUtils::GetBooleanValue(result));
 }
 
+void SSLCertContext::KeyLogCallback(const SSL* ssl, const char* line) {
+  SSLFilter* filter = static_cast<SSLFilter*>(
+      SSL_get_ex_data(ssl, SSLFilter::filter_ssl_index));
+
+  Dart_Port port = filter->key_log_port();
+  if (port != ILLEGAL_PORT) {
+    DartUtils::PostString(port, line);
+  }
+}
+
 SSLCertContext* SSLCertContext::GetSecurityContext(Dart_NativeArguments args) {
   SSLCertContext* context;
   Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
@@ -807,6 +818,7 @@
   SSLFilter::InitializeLibrary();
   SSL_CTX* ctx = SSL_CTX_new(TLS_method());
   SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLCertContext::CertificateCallback);
+  SSL_CTX_set_keylog_callback(ctx, SSLCertContext::KeyLogCallback);
   SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
   SSL_CTX_set_cipher_list(ctx, "HIGH:MEDIUM");
   SSLCertContext* context = new SSLCertContext(ctx);
diff --git a/runtime/bin/security_context.h b/runtime/bin/security_context.h
index e8800e2..b5bc66b 100644
--- a/runtime/bin/security_context.h
+++ b/runtime/bin/security_context.h
@@ -30,17 +30,16 @@
   explicit SSLCertContext(SSL_CTX* context)
       : ReferenceCounted(),
         context_(context),
-        alpn_protocol_string_(NULL),
+        alpn_protocol_string_(nullptr),
         trust_builtin_(false) {}
 
   ~SSLCertContext() {
     SSL_CTX_free(context_);
-    if (alpn_protocol_string_ != NULL) {
-      free(alpn_protocol_string_);
-    }
+    free(alpn_protocol_string_);
   }
 
   static int CertificateCallback(int preverify_ok, X509_STORE_CTX* store_ctx);
+  static void KeyLogCallback(const SSL* ssl, const char* line);
 
   static SSLCertContext* GetSecurityContext(Dart_NativeArguments args);
   static const char* GetPasswordArgument(Dart_NativeArguments args,
diff --git a/runtime/tests/vm/dart/regress_big_regexp_test.dart b/runtime/tests/vm/dart/regress_big_regexp_test.dart
index 09a7aa4..8a3530d 100644
--- a/runtime/tests/vm/dart/regress_big_regexp_test.dart
+++ b/runtime/tests/vm/dart/regress_big_regexp_test.dart
@@ -2,6 +2,9 @@
 // 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.
 
+// VMOptions=--intrinsify
+// VMOptions=--no_intrinsify
+
 // Verifies that RegExp compilation doesn't crash on a huge source string.
 
 import 'package:expect/expect.dart';
diff --git a/runtime/tests/vm/dart_2/regress_big_regexp_test.dart b/runtime/tests/vm/dart_2/regress_big_regexp_test.dart
index 1ef005d..69729d1 100644
--- a/runtime/tests/vm/dart_2/regress_big_regexp_test.dart
+++ b/runtime/tests/vm/dart_2/regress_big_regexp_test.dart
@@ -2,6 +2,9 @@
 // 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.
 
+// VMOptions=--intrinsify
+// VMOptions=--no_intrinsify
+
 // @dart = 2.9
 
 // Verifies that RegExp compilation doesn't crash on a huge source string.
diff --git a/runtime/vm/compiler/asm_intrinsifier_riscv.cc b/runtime/vm/compiler/asm_intrinsifier_riscv.cc
index a800323..0e2da82 100644
--- a/runtime/vm/compiler/asm_intrinsifier_riscv.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_riscv.cc
@@ -670,8 +670,34 @@
                                                    bool sticky) {
   if (FLAG_interpret_irregexp) return;
 
-  // TODO(riscv)
-  __ Bind(normal_ir_body);
+  static const intptr_t kRegExpParamOffset = 2 * target::kWordSize;
+  static const intptr_t kStringParamOffset = 1 * target::kWordSize;
+  // start_index smi is located at offset 0.
+
+  // Incoming registers:
+  // T0: Function. (Will be reloaded with the specialized matcher function.)
+  // S4: Arguments descriptor. (Will be preserved.)
+  // S5: Unknown. (Must be GC safe on tail call.)
+
+  // Load the specialized function pointer into R0. Leverage the fact the
+  // string CIDs as well as stored function pointers are in sequence.
+  __ lx(T2, Address(SP, kRegExpParamOffset));
+  __ lx(T1, Address(SP, kStringParamOffset));
+  __ LoadClassId(T1, T1);
+  __ AddImmediate(T1, -kOneByteStringCid);
+  __ slli(T1, T1, target::kWordSizeLog2);
+  __ add(T1, T1, T2);
+  __ lx(T0, FieldAddress(T1, target::RegExp::function_offset(kOneByteStringCid,
+                                                             sticky)));
+
+  // Registers are now set up for the lazy compile stub. It expects the function
+  // in R0, the argument descriptor in R4, and IC-Data in R5.
+  __ li(S5, 0);
+
+  // Tail-call the function.
+  __ lx(CODE_REG, FieldAddress(T0, target::Function::code_offset()));
+  __ lx(T1, FieldAddress(T0, target::Function::entry_point_offset()));
+  __ jr(T1);
 }
 
 void AsmIntrinsifier::UserTag_defaultTag(Assembler* assembler,
diff --git a/runtime/vm/regexp_assembler_ir.cc b/runtime/vm/regexp_assembler_ir.cc
index 60dc2c6..36c5414 100644
--- a/runtime/vm/regexp_assembler_ir.cc
+++ b/runtime/vm/regexp_assembler_ir.cc
@@ -299,14 +299,12 @@
 
   const Object& retval =
       Object::Handle(zone, DartEntry::InvokeFunction(fun, args));
-  if (retval.IsUnwindError()) {
-    Exceptions::PropagateError(Error::Cast(retval));
+  if (retval.IsLanguageError()) {
+    Exceptions::ThrowCompileTimeError(LanguageError::Cast(retval));
+    UNREACHABLE();
   }
   if (retval.IsError()) {
-    const Error& error = Error::Cast(retval);
-    OS::PrintErr("%s\n", error.ToErrorCString());
-    // Should never happen.
-    UNREACHABLE();
+    Exceptions::PropagateError(Error::Cast(retval));
   }
 
   if (retval.IsNull()) {
diff --git a/sdk/lib/_http/http.dart b/sdk/lib/_http/http.dart
index 5503db6..6ea7e64 100644
--- a/sdk/lib/_http/http.dart
+++ b/sdk/lib/_http/http.dart
@@ -1688,6 +1688,21 @@
   void set badCertificateCallback(
       bool Function(X509Certificate cert, String host, int port)? callback);
 
+  /// Sets a callback that will be called when new TLS keys are exchanged with
+  /// the server. It will receive one line of text in
+  /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
+  /// for each call. Writing these lines to a file will allow tools (such as
+  /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
+  /// to decrypt communication between the this client and the server. This is
+  /// meant to allow network-level debugging of secure sockets and should not
+  /// be used in production code. For example:
+  ///
+  ///     final log = File('keylog.txt');
+  ///     final client = HttpClient();
+  ///     client.keyLog = (line) => log.writeAsStringSync(line,
+  ///         mode: FileMode.append);
+  void set keyLog(Function(String line)? callback);
+
   /// Shuts down the HTTP client.
   ///
   /// If [force] is `false` (the default) the [HttpClient] will be kept alive
diff --git a/sdk/lib/_http/http_impl.dart b/sdk/lib/_http/http_impl.dart
index bb38ebe..e1c0d5c 100644
--- a/sdk/lib/_http/http_impl.dart
+++ b/sdk/lib/_http/http_impl.dart
@@ -2447,7 +2447,9 @@
     } else {
       connectionTask = (isSecure && proxy.isDirect
           ? SecureSocket.startConnect(host, port,
-              context: context, onBadCertificate: callback)
+              context: context,
+              onBadCertificate: callback,
+              keyLog: client._keyLog)
           : Socket.startConnect(host, port));
     }
     _connecting++;
@@ -2526,6 +2528,7 @@
   String Function(Uri)? _findProxy = HttpClient.findProxyFromEnvironment;
   Duration _idleTimeout = const Duration(seconds: 15);
   BadCertificateCallback? _badCertificateCallback;
+  Function(String line)? _keyLog;
 
   Duration get idleTimeout => _idleTimeout;
 
@@ -2555,6 +2558,10 @@
     _badCertificateCallback = callback;
   }
 
+  void set keyLog(Function(String line)? callback) {
+    _keyLog = callback;
+  }
+
   Future<HttpClientRequest> open(
       String method, String host, int port, String path) {
     const int hashMark = 0x23;
diff --git a/sdk/lib/_internal/vm/bin/secure_socket_patch.dart b/sdk/lib/_internal/vm/bin/secure_socket_patch.dart
index cfd1d6b..a2ce0f3 100644
--- a/sdk/lib/_internal/vm/bin/secure_socket_patch.dart
+++ b/sdk/lib/_internal/vm/bin/secure_socket_patch.dart
@@ -189,6 +189,9 @@
   external void registerHandshakeCompleteCallback(
       Function handshakeCompleteHandler);
 
+  @pragma("vm:external-name", "SecureSocket_RegisterKeyLogPort")
+  external void registerKeyLogPort(SendPort port);
+
   // This is a security issue, as it exposes a raw pointer to Dart code.
   @pragma("vm:external-name", "SecureSocket_FilterPointer")
   external int _pointer();
diff --git a/sdk/lib/io/secure_socket.dart b/sdk/lib/io/secure_socket.dart
index 6150653..de267bb 100644
--- a/sdk/lib/io/secure_socket.dart
+++ b/sdk/lib/io/secure_socket.dart
@@ -27,6 +27,20 @@
   /// the connection or not.  The handler should return true
   /// to continue the [SecureSocket] connection.
   ///
+  /// [keyLog] is an optional callback that will be called when new TLS keys
+  /// are exchanged with the server. [keyLog] will receive one line of text in
+  /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
+  /// for each call. Writing these lines to a file will allow tools (such as
+  /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
+  /// to decrypt content sent through this socket. This is meant to allow
+  /// network-level debugging of secure sockets and should not be used in
+  /// production code. For example:
+  /// ```dart
+  /// final log = File('keylog.txt');
+  /// final socket = await SecureSocket.connect('www.example.com', 443,
+  ///     keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
+  /// ```
+  ///
   /// [supportedProtocols] is an optional list of protocols (in decreasing
   /// order of preference) to use during the ALPN protocol negotiation with the
   /// server.  Example values are "http/1.1" or "h2".  The selected protocol
@@ -40,11 +54,13 @@
   static Future<SecureSocket> connect(host, int port,
       {SecurityContext? context,
       bool onBadCertificate(X509Certificate certificate)?,
+      void keyLog(String line)?,
       List<String>? supportedProtocols,
       Duration? timeout}) {
     return RawSecureSocket.connect(host, port,
             context: context,
             onBadCertificate: onBadCertificate,
+            keyLog: keyLog,
             supportedProtocols: supportedProtocols,
             timeout: timeout)
         .then((rawSocket) => new SecureSocket._(rawSocket));
@@ -56,10 +72,12 @@
   static Future<ConnectionTask<SecureSocket>> startConnect(host, int port,
       {SecurityContext? context,
       bool onBadCertificate(X509Certificate certificate)?,
+      void keyLog(String line)?,
       List<String>? supportedProtocols}) {
     return RawSecureSocket.startConnect(host, port,
             context: context,
             onBadCertificate: onBadCertificate,
+            keyLog: keyLog,
             supportedProtocols: supportedProtocols)
         .then((rawState) {
       Future<SecureSocket> socket =
@@ -88,6 +106,26 @@
   /// the [socket] will be used. The [host] can be either a [String] or
   /// an [InternetAddress].
   ///
+  /// [onBadCertificate] is an optional handler for unverifiable certificates.
+  /// The handler receives the [X509Certificate], and can inspect it and
+  /// decide (or let the user decide) whether to accept
+  /// the connection or not.  The handler should return true
+  /// to continue the [SecureSocket] connection.
+  ///
+  /// [keyLog] is an optional callback that will be called when new TLS keys
+  /// are exchanged with the server. [keyLog] will receive one line of text in
+  /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
+  /// for each call. Writing these lines to a file will allow tools (such as
+  /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
+  /// to decrypt content sent through this socket. This is meant to allow
+  /// network-level debugging of secure sockets and should not be used in
+  /// production code. For example:
+  /// ```dart
+  /// final log = File('keylog.txt');
+  /// final socket = await SecureSocket.connect('www.example.com', 443,
+  ///     keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
+  /// ```
+  ///
   /// [supportedProtocols] is an optional list of protocols (in decreasing
   /// order of preference) to use during the ALPN protocol negotiation with the
   /// server.  Example values are "http/1.1" or "h2".  The selected protocol
@@ -104,6 +142,7 @@
       {host,
       SecurityContext? context,
       bool onBadCertificate(X509Certificate certificate)?,
+      void keyLog(String line)?,
       @Since("2.6") List<String>? supportedProtocols}) {
     return ((socket as dynamic /*_Socket*/)._detachRaw() as Future)
         .then<RawSecureSocket>((detachedRaw) {
@@ -112,6 +151,7 @@
           host: host,
           context: context,
           onBadCertificate: onBadCertificate,
+          keyLog: keyLog,
           supportedProtocols: supportedProtocols);
     }).then<SecureSocket>((raw) => new SecureSocket._(raw));
   }
@@ -209,6 +249,26 @@
   /// the connection or not.  The handler should return true
   /// to continue the [RawSecureSocket] connection.
   ///
+  /// [onBadCertificate] is an optional handler for unverifiable certificates.
+  /// The handler receives the [X509Certificate], and can inspect it and
+  /// decide (or let the user decide) whether to accept
+  /// the connection or not.  The handler should return true
+  /// to continue the [SecureSocket] connection.
+  ///
+  /// [keyLog] is an optional callback that will be called when new TLS keys
+  /// are exchanged with the server. [keyLog] will receive one line of text in
+  /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
+  /// for each call. Writing these lines to a file will allow tools (such as
+  /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
+  /// to decrypt content sent through this socket. This is meant to allow
+  /// network-level debugging of secure sockets and should not be used in
+  /// production code. For example:
+  /// ```dart
+  /// final log = File('keylog.txt');
+  /// final socket = await SecureSocket.connect('www.example.com', 443,
+  ///     keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
+  /// ```
+  ///
   /// [supportedProtocols] is an optional list of protocols (in decreasing
   /// order of preference) to use during the ALPN protocol negotiation with the
   /// server.  Example values are "http/1.1" or "h2".  The selected protocol
@@ -216,6 +276,7 @@
   static Future<RawSecureSocket> connect(host, int port,
       {SecurityContext? context,
       bool onBadCertificate(X509Certificate certificate)?,
+      void keyLog(String line)?,
       List<String>? supportedProtocols,
       Duration? timeout}) {
     _RawSecureSocket._verifyFields(host, port, false, false);
@@ -223,6 +284,7 @@
       return secure(socket,
           context: context,
           onBadCertificate: onBadCertificate,
+          keyLog: keyLog,
           supportedProtocols: supportedProtocols);
     });
   }
@@ -233,6 +295,7 @@
   static Future<ConnectionTask<RawSecureSocket>> startConnect(host, int port,
       {SecurityContext? context,
       bool onBadCertificate(X509Certificate certificate)?,
+      void keyLog(String line)?,
       List<String>? supportedProtocols}) {
     return RawSocket.startConnect(host, port)
         .then((ConnectionTask<RawSocket> rawState) {
@@ -240,6 +303,7 @@
         return secure(rawSocket,
             context: context,
             onBadCertificate: onBadCertificate,
+            keyLog: keyLog,
             supportedProtocols: supportedProtocols);
       });
       return new ConnectionTask<RawSecureSocket>._(socket, rawState._onCancel);
@@ -266,6 +330,26 @@
   /// the [socket] will be used. The [host] can be either a [String] or
   /// an [InternetAddress].
   ///
+  /// [onBadCertificate] is an optional handler for unverifiable certificates.
+  /// The handler receives the [X509Certificate], and can inspect it and
+  /// decide (or let the user decide) whether to accept
+  /// the connection or not.  The handler should return true
+  /// to continue the [SecureSocket] connection.
+  ///
+  /// [keyLog] is an optional callback that will be called when new TLS keys
+  /// are exchanged with the server. [keyLog] will receive one line of text in
+  /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format)
+  /// for each call. Writing these lines to a file will allow tools (such as
+  /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption))
+  /// to decrypt content sent through this socket. This is meant to allow
+  /// network-level debugging of secure sockets and should not be used in
+  /// production code. For example:
+  /// ```dart
+  /// final log = File('keylog.txt');
+  /// final socket = await SecureSocket.connect('www.example.com', 443,
+  ///     keyLog: (line) => log.writeAsStringSync(line, mode: FileMode.append));
+  /// ```
+  ///
   /// [supportedProtocols] is an optional list of protocols (in decreasing
   /// order of preference) to use during the ALPN protocol negotiation with the
   /// server.  Example values are "http/1.1" or "h2".  The selected protocol
@@ -283,6 +367,7 @@
       host,
       SecurityContext? context,
       bool onBadCertificate(X509Certificate certificate)?,
+      void keyLog(String line)?,
       List<String>? supportedProtocols}) {
     socket.readEventsEnabled = false;
     socket.writeEventsEnabled = false;
@@ -291,6 +376,7 @@
         subscription: subscription,
         context: context,
         onBadCertificate: onBadCertificate,
+        keyLog: keyLog,
         supportedProtocols: supportedProtocols);
   }
 
@@ -427,6 +513,8 @@
   final bool requestClientCertificate;
   final bool requireClientCertificate;
   final bool Function(X509Certificate certificate)? onBadCertificate;
+  final void Function(String line)? keyLog;
+  ReceivePort? keyLogPort;
 
   var _status = handshakeStatus;
   bool _writeEventsEnabled = true;
@@ -458,6 +546,7 @@
       bool requestClientCertificate = false,
       bool requireClientCertificate = false,
       bool onBadCertificate(X509Certificate certificate)?,
+      void keyLog(String line)?,
       List<String>? supportedProtocols}) {
     _verifyFields(host, requestedPort, requestClientCertificate,
         requireClientCertificate);
@@ -477,6 +566,7 @@
             requestClientCertificate,
             requireClientCertificate,
             onBadCertificate,
+            keyLog,
             supportedProtocols)
         ._handshakeComplete
         .future;
@@ -493,6 +583,7 @@
       this.requestClientCertificate,
       this.requireClientCertificate,
       this.onBadCertificate,
+      this.keyLog,
       List<String>? supportedProtocols) {
     _controller
       ..onListen = _onSubscriptionStateChange
@@ -505,6 +596,23 @@
     secureFilter.init();
     secureFilter
         .registerHandshakeCompleteCallback(_secureHandshakeCompleteHandler);
+
+    if (keyLog != null) {
+      final port = ReceivePort();
+      port.listen((line) {
+        try {
+          keyLog!((line as String) + '\n');
+        } catch (e, s) {
+          // There is no obvious place to surface exceptions from the keyLog
+          // callback so write the details to stderr.
+          stderr.writeln("Failure in keyLog callback:");
+          stderr.writeln(s);
+        }
+      });
+      secureFilter.registerKeyLogPort(port.sendPort);
+      keyLogPort = port;
+    }
+
     if (onBadCertificate != null) {
       secureFilter.registerBadCertificateCallback(_onBadCertificateWrapper);
     }
@@ -607,6 +715,7 @@
       _secureFilter!.destroy();
       _secureFilter = null;
     }
+    keyLogPort?.close();
     if (_socketSubscription != null) {
       _socketSubscription.cancel();
     }
@@ -1240,6 +1349,7 @@
   int processBuffer(int bufferIndex);
   void registerBadCertificateCallback(Function callback);
   void registerHandshakeCompleteCallback(Function handshakeCompleteHandler);
+  void registerKeyLogPort(SendPort port);
 
   // This call may cause a reference counted pointer in the native
   // implementation to be retained. It should only be called when the resulting
diff --git a/sdk/lib/web_audio/dart2js/web_audio_dart2js.dart b/sdk/lib/web_audio/dart2js/web_audio_dart2js.dart
index a7db2d5..29544d8 100644
--- a/sdk/lib/web_audio/dart2js/web_audio_dart2js.dart
+++ b/sdk/lib/web_audio/dart2js/web_audio_dart2js.dart
@@ -225,29 +225,89 @@
     }
   }
 
-  @JSName('decodeAudioData')
-  Future<AudioBuffer> _decodeAudioData(ByteBuffer audioData,
-      [DecodeSuccessCallback? successCallback,
-      DecodeErrorCallback? errorCallback]) native;
-
   Future<AudioBuffer> decodeAudioData(ByteBuffer audioData,
       [DecodeSuccessCallback? successCallback,
       DecodeErrorCallback? errorCallback]) {
-    if (successCallback != null && errorCallback != null) {
-      return _decodeAudioData(audioData, successCallback, errorCallback);
+    // Both callbacks need to be provided if they're being used.
+    assert((successCallback == null) == (errorCallback == null));
+    // `decodeAudioData` can exist either in the older callback syntax or the
+    // newer `Promise`-based syntax that also accepts callbacks. In the former,
+    // we synthesize a `Future` to be consistent.
+    // For more details:
+    // https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData
+    // https://www.w3.org/TR/webaudio/#dom-baseaudiocontext-decodeaudiodata
+    final completer = Completer<Object>();
+    var errorInCallbackIsNull = false;
+
+    void success(AudioBuffer decodedData) {
+      completer.complete(decodedData);
+      successCallback!.call(decodedData);
     }
 
-    var completer = new Completer<AudioBuffer>();
-    _decodeAudioData(audioData, (value) {
-      completer.complete(value);
-    }, (error) {
-      if (error == null) {
-        completer.completeError('');
+    final nullErrorString =
+        '[AudioContext.decodeAudioData] completed with a null error.';
+
+    void error(DomException? error) {
+      // Safari has a bug where it may return null for the error callback. In
+      // the case where the Safari version still returns a `Promise` and the
+      // error is not null after the `Promise` is finished, the error callback
+      // is called instead in the `Promise`'s `catch` block. Otherwise, and in
+      // the case where a `Promise` is not returned by the API at all, the
+      // callback never gets called (for backwards compatibility, it can not
+      // accept null). Instead, the `Future` completes with a custom string,
+      // indicating that null was given.
+      // https://github.com/mdn/webaudio-examples/issues/5
+      if (error != null) {
+        // Note that we `complete` and not `completeError`. This is to make sure
+        // that errors in the `Completer` are not thrown if the call gets back
+        // a `Promise`.
+        completer.complete(error);
+        errorCallback!.call(error);
       } else {
-        completer.completeError(error);
+        completer.complete(nullErrorString);
+        errorInCallbackIsNull = true;
       }
+    }
+
+    var decodeResult;
+    if (successCallback == null) {
+      decodeResult =
+          JS("creates:AudioBuffer;", "#.decodeAudioData(#)", this, audioData);
+    } else {
+      decodeResult = JS(
+          "creates:AudioBuffer;",
+          "#.decodeAudioData(#, #, #)",
+          this,
+          audioData,
+          convertDartClosureToJS(success, 1),
+          convertDartClosureToJS(error, 1));
+    }
+
+    if (decodeResult != null) {
+      // Promise-based syntax.
+      return promiseToFuture<AudioBuffer>(decodeResult).catchError((error) {
+        // If the error was null in the callback, but no longer is now that the
+        // `Promise` is finished, call the error callback. If it's still null,
+        // throw the error string. This is to handle the aforementioned bug in
+        // Safari.
+        if (errorInCallbackIsNull) {
+          if (error != null) {
+            errorCallback?.call(error);
+          } else {
+            throw nullErrorString;
+          }
+        }
+        throw error;
+      });
+    }
+
+    // Callback-based syntax. We use the above completer to synthesize a
+    // `Future` from the callback values. Since we don't use `completeError`
+    // above, `then` is used to simulate an error.
+    return completer.future.then((value) {
+      if (value is AudioBuffer) return value;
+      throw value;
     });
-    return completer.future;
   }
 }
 // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
diff --git a/tests/lib/html/audiocontext_test.dart b/tests/lib/html/audiocontext_test.dart
index 8316130..a091790 100644
--- a/tests/lib/html/audiocontext_test.dart
+++ b/tests/lib/html/audiocontext_test.dart
@@ -7,6 +7,7 @@
 import 'dart:typed_data';
 import 'dart:web_audio';
 
+import 'package:async_helper/async_helper.dart';
 import 'package:expect/minitest.dart';
 
 main() {
@@ -95,5 +96,74 @@
         expect(oscillator.type, equals('triangle'));
       }
     });
+
+    asyncTest(() async {
+      if (AudioContext.supported) {
+        final audioSourceUrl = "/root_dart/tests/lib/html/small.mp3";
+
+        Future<void> requestAudioDecode(
+            {bool triggerDecodeError: false,
+            DecodeSuccessCallback? successCallback,
+            DecodeErrorCallback? errorCallback}) async {
+          HttpRequest audioRequest = HttpRequest();
+          audioRequest.open("GET", audioSourceUrl, async: true);
+          audioRequest.responseType = "arraybuffer";
+          var completer = new Completer<void>();
+          audioRequest.onLoad.listen((_) {
+            ByteBuffer audioData = audioRequest.response;
+            if (triggerDecodeError) audioData = Uint8List.fromList([]).buffer;
+            context
+                .decodeAudioData(audioData, successCallback, errorCallback)
+                .then((_) {
+              completer.complete();
+            }).catchError((e) {
+              completer.completeError(e);
+            });
+          });
+          audioRequest.send();
+          return completer.future;
+        }
+
+        // Decode successfully without callback.
+        await requestAudioDecode();
+
+        // Decode successfully with callback. Use counter to make sure it's only
+        // called once.
+        var successCallbackCalled = 0;
+        await requestAudioDecode(
+            successCallback: (_) {
+              successCallbackCalled += 1;
+            },
+            errorCallback: (_) {});
+        expect(successCallbackCalled, 1);
+
+        // Fail decode without callback.
+        try {
+          await requestAudioDecode(triggerDecodeError: true);
+          fail('Expected decode failure.');
+        } catch (_) {}
+
+        // Fail decode with callback.
+        var errorCallbackCalled = 0;
+        try {
+          await requestAudioDecode(
+              triggerDecodeError: true,
+              successCallback: (_) {},
+              errorCallback: (_) {
+                errorCallbackCalled += 1;
+              });
+          fail('Expected decode failure.');
+        } catch (e) {
+          // Safari may return a null error. Assuming Safari is version >= 14.1,
+          // the Future should complete with a string error if the error
+          // callback never gets called.
+          if (errorCallbackCalled == 0) {
+            expect(e is String, true);
+          } else {
+            expect(errorCallbackCalled, 1);
+          }
+        }
+      }
+    });
   });
 }
diff --git a/tests/lib/html/small.mp3 b/tests/lib/html/small.mp3
new file mode 100644
index 0000000..3fcc88b
--- /dev/null
+++ b/tests/lib/html/small.mp3
Binary files differ
diff --git a/tests/lib_2/html/audiocontext_test.dart b/tests/lib_2/html/audiocontext_test.dart
index b66e54b..396cf39 100644
--- a/tests/lib_2/html/audiocontext_test.dart
+++ b/tests/lib_2/html/audiocontext_test.dart
@@ -1,3 +1,6 @@
+// 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.9
 import 'dart:async';
@@ -5,6 +8,7 @@
 import 'dart:typed_data';
 import 'dart:web_audio';
 
+import 'package:async_helper/async_helper.dart';
 import 'package:expect/minitest.dart';
 
 main() {
@@ -93,5 +97,74 @@
         expect(oscillator.type, equals('triangle'));
       }
     });
+
+    asyncTest(() async {
+      if (AudioContext.supported) {
+        final audioSourceUrl = "/root_dart/tests/lib_2/html/small.mp3";
+
+        Future<void> requestAudioDecode(
+            {bool triggerDecodeError: false,
+            DecodeSuccessCallback successCallback,
+            DecodeErrorCallback errorCallback}) async {
+          HttpRequest audioRequest = HttpRequest();
+          audioRequest.open("GET", audioSourceUrl, async: true);
+          audioRequest.responseType = "arraybuffer";
+          var completer = new Completer<void>();
+          audioRequest.onLoad.listen((_) {
+            ByteBuffer audioData = audioRequest.response;
+            if (triggerDecodeError) audioData = Uint8List.fromList([]).buffer;
+            context
+                .decodeAudioData(audioData, successCallback, errorCallback)
+                .then((_) {
+              completer.complete();
+            }).catchError((e) {
+              completer.completeError(e);
+            });
+          });
+          audioRequest.send();
+          return completer.future;
+        }
+
+        // Decode successfully without callback.
+        await requestAudioDecode();
+
+        // Decode successfully with callback. Use counter to make sure it's only
+        // called once.
+        var successCallbackCalled = 0;
+        await requestAudioDecode(
+            successCallback: (_) {
+              successCallbackCalled += 1;
+            },
+            errorCallback: (_) {});
+        expect(successCallbackCalled, 1);
+
+        // Fail decode without callback.
+        try {
+          await requestAudioDecode(triggerDecodeError: true);
+          fail('Expected decode failure.');
+        } catch (_) {}
+
+        // Fail decode with callback.
+        var errorCallbackCalled = 0;
+        try {
+          await requestAudioDecode(
+              triggerDecodeError: true,
+              successCallback: (_) {},
+              errorCallback: (_) {
+                errorCallbackCalled += 1;
+              });
+          fail('Expected decode failure.');
+        } catch (e) {
+          // Safari may return a null error. Assuming Safari is version >= 14.1,
+          // the Future should complete with a string error if the error
+          // callback never gets called.
+          if (errorCallbackCalled == 0) {
+            expect(e is String, true);
+          } else {
+            expect(errorCallbackCalled, 1);
+          }
+        }
+      }
+    });
   });
 }
diff --git a/tests/lib_2/html/small.mp3 b/tests/lib_2/html/small.mp3
new file mode 100644
index 0000000..3fcc88b
--- /dev/null
+++ b/tests/lib_2/html/small.mp3
Binary files differ
diff --git a/tests/standalone/io/http_key_log_test.dart b/tests/standalone/io/http_key_log_test.dart
new file mode 100644
index 0000000..0b948a2
--- /dev/null
+++ b/tests/standalone/io/http_key_log_test.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2021, 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.
+//
+// VMOptions=
+// VMOptions=--short_socket_read
+// VMOptions=--short_socket_write
+// VMOptions=--short_socket_read --short_socket_write
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+
+import "dart:async";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+
+late InternetAddress HOST;
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext = new SecurityContext()
+  ..useCertificateChain(localFile('certificates/server_chain.pem'))
+  ..usePrivateKey(localFile('certificates/server_key.pem'),
+      password: 'dartdart');
+
+Future<HttpServer> startEchoServer() {
+  return HttpServer.bindSecure(HOST, 0, serverContext).then((server) {
+    server.listen((HttpRequest req) {
+      final res = req.response;
+      res.write("Test");
+      res.close();
+    });
+    return server;
+  });
+}
+
+testSuccess(HttpServer server) async {
+  var log = "";
+  SecurityContext clientContext = new SecurityContext()
+    ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+  final client = HttpClient(context: clientContext);
+  client.keyLog = (String line) {
+    log += line;
+  };
+  final request =
+      await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
+  final response = await request.close();
+  await response.drain();
+
+  Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
+}
+
+testExceptionInKeyLogFunction(HttpServer server) async {
+  SecurityContext clientContext = new SecurityContext()
+    ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+  final client = HttpClient(context: clientContext);
+  var numCalls = 0;
+  client.keyLog = (String line) {
+    ++numCalls;
+    throw FileSystemException("Something bad happened");
+  };
+  final request =
+      await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
+  final response = await request.close();
+  await response.drain();
+
+  Expect.notEquals(0, numCalls);
+}
+
+void main() async {
+  asyncStart();
+  await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
+  final server = await startEchoServer();
+
+  await testSuccess(server);
+  await testExceptionInKeyLogFunction(server);
+
+  await server.close();
+  asyncEnd();
+}
diff --git a/tests/standalone/io/http_override_test.dart b/tests/standalone/io/http_override_test.dart
index 92270d0..fccbbe4 100644
--- a/tests/standalone/io/http_override_test.dart
+++ b/tests/standalone/io/http_override_test.dart
@@ -52,6 +52,7 @@
       String host, int port, String realm, HttpClientCredentials credentials) {}
   set badCertificateCallback(
       bool callback(X509Certificate cert, String host, int port)?) {}
+  void set keyLog(Function(String line)? callback) {}
   void close({bool force: false}) {}
 }
 
@@ -100,6 +101,7 @@
       String host, int port, String realm, HttpClientCredentials credentials) {}
   set badCertificateCallback(
       bool callback(X509Certificate cert, String host, int port)?) {}
+  void set keyLog(Function(String line)? callback) {}
   void close({bool force: false}) {}
 }
 
diff --git a/tests/standalone/io/secure_key_log_test.dart b/tests/standalone/io/secure_key_log_test.dart
new file mode 100644
index 0000000..aec2270
--- /dev/null
+++ b/tests/standalone/io/secure_key_log_test.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, 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.
+//
+// VMOptions=
+// VMOptions=--short_socket_read
+// VMOptions=--short_socket_write
+// VMOptions=--short_socket_read --short_socket_write
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+
+import "dart:async";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+
+late InternetAddress HOST;
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext = new SecurityContext()
+  ..useCertificateChain(localFile('certificates/server_chain.pem'))
+  ..usePrivateKey(localFile('certificates/server_key.pem'),
+      password: 'dartdart');
+
+Future<SecureServerSocket> startEchoServer() {
+  return SecureServerSocket.bind(HOST, 0, serverContext).then((server) {
+    server.listen((SecureSocket client) {
+      client.fold<List<int>>(
+          <int>[], (message, data) => message..addAll(data)).then((message) {
+        client.add(message);
+        client.close();
+      });
+    });
+    return server;
+  });
+}
+
+testSuccess(SecureServerSocket server) async {
+  var log = "";
+  SecurityContext clientContext = new SecurityContext()
+    ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+  await SecureSocket.connect(HOST, server.port, context: clientContext,
+      keyLog: (line) {
+    log += line;
+  }).then((socket) {
+    socket.write("Hello server.");
+    socket.close();
+    return socket.drain().then((value) {
+      Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
+      return server;
+    });
+  });
+}
+
+testExceptionInKeyLogFunction(SecureServerSocket server) async {
+  SecurityContext clientContext = new SecurityContext()
+    ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+  var numCalls = 0;
+  await SecureSocket.connect(HOST, server.port, context: clientContext,
+      keyLog: (line) {
+    ++numCalls;
+    throw FileSystemException("Something bad happened");
+  }).then((socket) {
+    socket.close();
+    return socket.drain().then((value) {
+      Expect.notEquals(0, numCalls);
+      return server;
+    });
+  });
+}
+
+void main() async {
+  asyncStart();
+  await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
+  final server = await startEchoServer();
+
+  await testSuccess(server);
+  await testExceptionInKeyLogFunction(server);
+
+  await server.close();
+  asyncEnd();
+}
diff --git a/tests/standalone_2/io/http_key_log_test.dart b/tests/standalone_2/io/http_key_log_test.dart
new file mode 100644
index 0000000..ecfc916
--- /dev/null
+++ b/tests/standalone_2/io/http_key_log_test.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, 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.
+//
+// VMOptions=
+// VMOptions=--short_socket_read
+// VMOptions=--short_socket_write
+// VMOptions=--short_socket_read --short_socket_write
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+
+// @dart = 2.9
+
+import "dart:async";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+
+InternetAddress HOST;
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext = new SecurityContext()
+  ..useCertificateChain(localFile('certificates/server_chain.pem'))
+  ..usePrivateKey(localFile('certificates/server_key.pem'),
+      password: 'dartdart');
+
+Future<HttpServer> startEchoServer() {
+  return HttpServer.bindSecure(HOST, 0, serverContext).then((server) {
+    server.listen((HttpRequest req) {
+      final res = req.response;
+      res.write("Test");
+      res.close();
+    });
+    return server;
+  });
+}
+
+testSuccess(HttpServer server) async {
+  var log = "";
+  SecurityContext clientContext = new SecurityContext()
+    ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+  final client = HttpClient(context: clientContext);
+  client.keyLog = (String line) {
+    log += line;
+  };
+  final request =
+      await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
+  final response = await request.close();
+  await response.drain();
+
+  Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
+}
+
+testExceptionInKeyLogFunction(HttpServer server) async {
+  SecurityContext clientContext = new SecurityContext()
+    ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+  final client = HttpClient(context: clientContext);
+  var numCalls = 0;
+  client.keyLog = (String line) {
+    ++numCalls;
+    throw FileSystemException("Something bad happened");
+  };
+  final request =
+      await client.getUrl(Uri.parse('https://localhost:${server.port}/test'));
+  final response = await request.close();
+  await response.drain();
+
+  Expect.notEquals(0, numCalls);
+}
+
+void main() async {
+  asyncStart();
+  await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
+  final server = await startEchoServer();
+
+  await testSuccess(server);
+  await testExceptionInKeyLogFunction(server);
+
+  await server.close();
+  asyncEnd();
+}
diff --git a/tests/standalone_2/io/http_override_test.dart b/tests/standalone_2/io/http_override_test.dart
index 72b4967..b3a8255 100644
--- a/tests/standalone_2/io/http_override_test.dart
+++ b/tests/standalone_2/io/http_override_test.dart
@@ -50,6 +50,8 @@
       String host, int port, String realm, HttpClientCredentials credentials) {}
   set badCertificateCallback(
       bool callback(X509Certificate cert, String host, int port)) {}
+  void set keyLog(Function(String line) callback) =>
+      throw UnsupportedError("keyLog not implemented");
   void close({bool force: false}) {}
 }
 
@@ -94,6 +96,8 @@
       String host, int port, String realm, HttpClientCredentials credentials) {}
   set badCertificateCallback(
       bool callback(X509Certificate cert, String host, int port)) {}
+  void set keyLog(Function(String line) callback) =>
+      throw UnsupportedError("keyLog not implemented");
   void close({bool force: false}) {}
 }
 
diff --git a/tests/standalone_2/io/secure_key_log_test.dart b/tests/standalone_2/io/secure_key_log_test.dart
new file mode 100644
index 0000000..d596122
--- /dev/null
+++ b/tests/standalone_2/io/secure_key_log_test.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, 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.
+//
+// VMOptions=
+// VMOptions=--short_socket_read
+// VMOptions=--short_socket_write
+// VMOptions=--short_socket_read --short_socket_write
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+//
+// @dart = 2.9
+
+import "dart:async";
+import "dart:io";
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+
+InternetAddress HOST;
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext = new SecurityContext()
+  ..useCertificateChain(localFile('certificates/server_chain.pem'))
+  ..usePrivateKey(localFile('certificates/server_key.pem'),
+      password: 'dartdart');
+
+Future<SecureServerSocket> startEchoServer() {
+  return SecureServerSocket.bind(HOST, 0, serverContext).then((server) {
+    server.listen((SecureSocket client) {
+      client.fold<List<int>>(
+          <int>[], (message, data) => message..addAll(data)).then((message) {
+        client.add(message);
+        client.close();
+      });
+    });
+    return server;
+  });
+}
+
+testSuccess(SecureServerSocket server) async {
+  var log = "";
+  SecurityContext clientContext = new SecurityContext()
+    ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+  await SecureSocket.connect(HOST, server.port, context: clientContext,
+      keyLog: (line) {
+    log += line;
+  }).then((socket) {
+    socket.write("Hello server.");
+    socket.close();
+    return socket.drain().then((value) {
+      Expect.contains("CLIENT_HANDSHAKE_TRAFFIC_SECRET", log);
+      return server;
+    });
+  });
+}
+
+testExceptionInKeyLogFunction(SecureServerSocket server) async {
+  SecurityContext clientContext = new SecurityContext()
+    ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+  var numCalls = 0;
+  await SecureSocket.connect(HOST, server.port, context: clientContext,
+      keyLog: (line) {
+    ++numCalls;
+    throw FileSystemException("Something bad happened");
+  }).then((socket) {
+    socket.close();
+    return socket.drain().then((value) {
+      Expect.notEquals(0, numCalls);
+      return server;
+    });
+  });
+}
+
+void main() async {
+  asyncStart();
+  await InternetAddress.lookup("localhost").then((hosts) => HOST = hosts.first);
+  final server = await startEchoServer();
+
+  await testSuccess(server);
+  await testExceptionInKeyLogFunction(server);
+
+  await server.close();
+  asyncEnd();
+}
diff --git a/tools/VERSION b/tools/VERSION
index b6083b2..cff630a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 17
 PATCH 0
-PRERELEASE 119
+PRERELEASE 120
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/dom/templates/html/impl/impl_AudioContext.darttemplate b/tools/dom/templates/html/impl/impl_AudioContext.darttemplate
index d716afd..77f860d 100644
--- a/tools/dom/templates/html/impl/impl_AudioContext.darttemplate
+++ b/tools/dom/templates/html/impl/impl_AudioContext.darttemplate
@@ -41,28 +41,88 @@
     }
   }
 
-  @JSName('decodeAudioData')
-  Future$#NULLSAFECAST(<AudioBuffer>) _decodeAudioData(ByteBuffer audioData,
-      [DecodeSuccessCallback$NULLABLE successCallback,
-      DecodeErrorCallback$NULLABLE errorCallback]) native;
-
   Future<AudioBuffer> decodeAudioData(ByteBuffer audioData,
       [DecodeSuccessCallback$NULLABLE successCallback,
       DecodeErrorCallback$NULLABLE errorCallback]) {
-    if (successCallback != null &&  errorCallback != null) {
-      return _decodeAudioData(audioData, successCallback, errorCallback);
+    // Both callbacks need to be provided if they're being used.
+    assert((successCallback == null) == (errorCallback == null));
+    // `decodeAudioData` can exist either in the older callback syntax or the
+    // newer `Promise`-based syntax that also accepts callbacks. In the former,
+    // we synthesize a `Future` to be consistent.
+    // For more details:
+    // https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData
+    // https://www.w3.org/TR/webaudio/#dom-baseaudiocontext-decodeaudiodata
+    final completer = Completer<Object>();
+    var errorInCallbackIsNull = false;
+
+    void success(AudioBuffer decodedData) {
+      completer.complete(decodedData);
+      successCallback$NULLASSERT.call(decodedData);
     }
 
-    var completer = new Completer<AudioBuffer>();
-    _decodeAudioData(audioData, (value) {
-      completer.complete(value);
-    }, (error) {
-      if (error == null) {
-        completer.completeError('');
+    final nullErrorString =
+        '[AudioContext.decodeAudioData] completed with a null error.';
+
+    void error(DomException$NULLABLE error) {
+      // Safari has a bug where it may return null for the error callback. In
+      // the case where the Safari version still returns a `Promise` and the
+      // error is not null after the `Promise` is finished, the error callback
+      // is called instead in the `Promise`'s `catch` block. Otherwise, and in
+      // the case where a `Promise` is not returned by the API at all, the
+      // callback never gets called (for backwards compatibility, it can not
+      // accept null). Instead, the `Future` completes with a custom string,
+      // indicating that null was given.
+      // https://github.com/mdn/webaudio-examples/issues/5
+      if (error != null) {
+        // Note that we `complete` and not `completeError`. This is to make sure
+        // that errors in the `Completer` are not thrown if the call gets back
+        // a `Promise`.
+        completer.complete(error);
+        errorCallback$NULLASSERT.call(error);
       } else {
-        completer.completeError(error);
+        completer.complete(nullErrorString);
+        errorInCallbackIsNull = true;
       }
+    }
+
+    var decodeResult;
+    if (successCallback == null) {
+      decodeResult =
+          JS("creates:AudioBuffer;", "#.decodeAudioData(#)", this, audioData);
+    } else {
+      decodeResult = JS(
+          "creates:AudioBuffer;",
+          "#.decodeAudioData(#, #, #)",
+          this,
+          audioData,
+          convertDartClosureToJS(success, 1),
+          convertDartClosureToJS(error, 1));
+    }
+
+    if (decodeResult != null) {
+      // Promise-based syntax.
+      return promiseToFuture<AudioBuffer>(decodeResult).catchError((error) {
+        // If the error was null in the callback, but no longer is now that the
+        // `Promise` is finished, call the error callback. If it's still null,
+        // throw the error string. This is to handle the aforementioned bug in
+        // Safari.
+        if (errorInCallbackIsNull) {
+          if (error != null) {
+            errorCallback?.call(error);
+          } else {
+            throw nullErrorString;
+          }
+        }
+        throw error;
+      });
+    }
+
+    // Callback-based syntax. We use the above completer to synthesize a
+    // `Future` from the callback values. Since we don't use `completeError`
+    // above, `then` is used to simulate an error.
+    return completer.future.then((value) {
+      if (value is AudioBuffer) return value;
+      throw value;
     });
-    return completer.future;
   }
 }