Version 2.14.0-313.0.dev

Merge commit '902709109c4befd612af76a2ed95eba6a0cdc1ed' into 'dev'
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 66ade81..ec4e5d2 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -133,17 +133,13 @@
             dart,
             'format',
             '--set-exit-if-changed',
+            '--output=none',
+            '--summary=none',
+            filename,
         ]
-        if not contents:
-            args += [
-                '--output=none',
-                '--summary=none',
-                filename,
-            ]
 
         process = subprocess.Popen(
             args, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
-        process.communicate(input=contents)
 
         # Check for exit code 1 explicitly to distinguish it from a syntax error
         # in the file (exit code 65). The repo contains many Dart files that are
diff --git a/pkg/analysis_server/lib/src/edit/edit_domain.dart b/pkg/analysis_server/lib/src/edit/edit_domain.dart
index 443a663..5513519 100644
--- a/pkg/analysis_server/lib/src/edit/edit_domain.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_domain.dart
@@ -32,7 +32,6 @@
 import 'package:analysis_server/src/services/correction/status.dart';
 import 'package:analysis_server/src/services/refactoring/refactoring.dart';
 import 'package:analysis_server/src/services/search/search_engine.dart';
-import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/analysis/session.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/error/error.dart' as engine;
@@ -575,8 +574,8 @@
       if (fixes.isNotEmpty) {
         fixes.sort(Fix.SORT_BY_RELEVANCE);
         var lineInfo = LineInfo.fromContent(content);
-        ResolvedUnitResult result = engine.ResolvedUnitResultImpl(session, file,
-            Uri.file(file), true, content, lineInfo, false, null, errors);
+        var result = engine.ErrorsResultImpl(
+            session, file, Uri.file(file), lineInfo, false, errors);
         var serverError = newAnalysisError_fromEngine(result, error);
         var errorFixes = AnalysisErrorFixes(serverError);
         errorFixesList.add(errorFixes);
@@ -674,8 +673,8 @@
       if (fixes.isNotEmpty) {
         fixes.sort(Fix.SORT_BY_RELEVANCE);
         var lineInfo = LineInfo.fromContent(content);
-        ResolvedUnitResult result = engine.ResolvedUnitResultImpl(session, file,
-            Uri.file(file), true, content, lineInfo, false, null, errors);
+        var result = engine.ErrorsResultImpl(
+            session, file, Uri.file(file), lineInfo, false, errors);
         var serverError = newAnalysisError_fromEngine(result, error);
         var errorFixes = AnalysisErrorFixes(serverError);
         errorFixesList.add(errorFixes);
@@ -723,8 +722,8 @@
       if (fixes.isNotEmpty) {
         fixes.sort(Fix.SORT_BY_RELEVANCE);
         var lineInfo = LineInfo.fromContent(content);
-        ResolvedUnitResult result = engine.ResolvedUnitResultImpl(session, file,
-            Uri.file(file), true, content, lineInfo, false, null, errors);
+        var result = engine.ErrorsResultImpl(
+            session, file, Uri.file(file), lineInfo, false, errors);
         var serverError = newAnalysisError_fromEngine(result, error);
         var errorFixes = AnalysisErrorFixes(serverError);
         errorFixesList.add(errorFixes);
diff --git a/pkg/analyzer/lib/src/context/builder.dart b/pkg/analyzer/lib/src/context/builder.dart
index 9bb60eb..e143315 100644
--- a/pkg/analyzer/lib/src/context/builder.dart
+++ b/pkg/analyzer/lib/src/context/builder.dart
@@ -5,24 +5,8 @@
 import 'dart:collection';
 import 'dart:core';
 
-import 'package:analyzer/dart/analysis/declared_variables.dart';
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
-import 'package:analyzer/src/context/context_root.dart';
 import 'package:analyzer/src/context/packages.dart';
-import 'package:analyzer/src/dart/analysis/byte_store.dart';
-import 'package:analyzer/src/dart/analysis/driver.dart'
-    show AnalysisDriver, AnalysisDriverScheduler;
-import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
-import 'package:analyzer/src/dart/analysis/performance_logger.dart';
-import 'package:analyzer/src/dart/sdk/sdk.dart';
-import 'package:analyzer/src/generated/engine.dart';
-import 'package:analyzer/src/generated/sdk.dart';
-import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/hint/sdk_constraint_extractor.dart';
-import 'package:analyzer/src/summary/package_bundle_reader.dart';
-import 'package:analyzer/src/summary/summary_sdk.dart';
-import 'package:analyzer/src/task/options.dart';
 import 'package:analyzer/src/workspace/basic.dart';
 import 'package:analyzer/src/workspace/bazel.dart';
 import 'package:analyzer/src/workspace/gn.dart';
@@ -50,246 +34,6 @@
 ///
 /// [1]: https://github.com/dart-lang/dart_enhancement_proposals/blob/master/Accepted/0005%20-%20Package%20Specification/DEP-pkgspec.md.
 class ContextBuilder {
-  /// The [ResourceProvider] by which paths are converted into [Resource]s.
-  final ResourceProvider resourceProvider;
-
-  /// The manager used to manage the DartSdk's that have been created so that
-  /// they can be shared across contexts.
-  final DartSdkManager sdkManager;
-
-  /// The options used by the context builder.
-  final ContextBuilderOptions builderOptions;
-
-  /// The scheduler used by any analysis drivers created through this interface.
-  late final AnalysisDriverScheduler analysisDriverScheduler;
-
-  /// The performance log used by any analysis drivers created through this
-  /// interface.
-  late final PerformanceLog performanceLog;
-
-  /// If `true`, additional analysis data useful for testing is stored.
-  bool retainDataForTesting = false;
-
-  /// The byte store used by any analysis drivers created through this interface.
-  late final ByteStore byteStore;
-
-  /// Whether any analysis driver created through this interface should support
-  /// indexing and search.
-  bool enableIndex = false;
-
-  /// Sometimes `BUILD` files are not preserved, and other files are created
-  /// instead. But looking for them is expensive, so we want to avoid this
-  /// in cases when `BUILD` files are always available.
-  bool lookForBazelBuildFileSubstitutes = true;
-
-  /// Initialize a newly created builder to be ready to build a context rooted in
-  /// the directory with the given [rootDirectoryPath].
-  ContextBuilder(this.resourceProvider, this.sdkManager,
-      {ContextBuilderOptions? options})
-      : builderOptions = options ?? ContextBuilderOptions();
-
-  /// Return an analysis driver that is configured correctly to analyze code in
-  /// the directory with the given [path].
-  AnalysisDriver buildDriver(
-    ContextRoot contextRoot,
-    Workspace workspace, {
-    void Function(AnalysisOptionsImpl)? updateAnalysisOptions,
-    FileContentCache? fileContentCache,
-  }) {
-    String path = contextRoot.root;
-
-    var options = getAnalysisOptions(path, workspace, contextRoot: contextRoot);
-
-    if (updateAnalysisOptions != null) {
-      updateAnalysisOptions(options);
-    }
-    //_processAnalysisOptions(context, optionMap);
-
-    SummaryDataStore? summaryData;
-    var librarySummaryPaths = builderOptions.librarySummaryPaths;
-    if (librarySummaryPaths != null) {
-      summaryData = SummaryDataStore(librarySummaryPaths);
-    }
-
-    final sf =
-        createSourceFactoryFromWorkspace(workspace, summaryData: summaryData);
-
-    AnalysisDriver driver = AnalysisDriver.tmp1(
-      scheduler: analysisDriverScheduler,
-      logger: performanceLog,
-      resourceProvider: resourceProvider,
-      byteStore: byteStore,
-      sourceFactory: sf,
-      analysisOptions: options,
-      packages: createPackageMap(
-        resourceProvider: resourceProvider,
-        options: builderOptions,
-        rootPath: path,
-      ),
-      enableIndex: enableIndex,
-      externalSummaries: summaryData,
-      retainDataForTesting: retainDataForTesting,
-      fileContentCache: fileContentCache,
-    );
-
-    declareVariablesInDriver(driver);
-    return driver;
-  }
-
-//  void _processAnalysisOptions(
-//      AnalysisContext context, Map<String, YamlNode> optionMap) {
-//    List<OptionsProcessor> optionsProcessors =
-//        AnalysisEngine.instance.optionsPlugin.optionsProcessors;
-//    try {
-//      optionsProcessors.forEach(
-//          (OptionsProcessor p) => p.optionsProcessed(context, optionMap));
-//
-//      // Fill in lint rule defaults in case lints are enabled and rules are
-//      // not specified in an options file.
-//      if (context.analysisOptions.lint && !containsLintRuleEntry(optionMap)) {
-//        setLints(context, linterPlugin.contributedRules);
-//      }
-//
-//      // Ask engine to further process options.
-//      if (optionMap != null) {
-//        configureContextOptions(context, optionMap);
-//      }
-//    } on Exception catch (e) {
-//      optionsProcessors.forEach((OptionsProcessor p) => p.onError(e));
-//    }
-//  }
-
-  SourceFactory createSourceFactory(String rootPath, Workspace workspace,
-      {SummaryDataStore? summaryData}) {
-    DartSdk sdk = findSdk(workspace);
-    if (summaryData != null && sdk is SummaryBasedDartSdk) {
-      summaryData.addBundle(null, sdk.bundle);
-    }
-    return workspace.createSourceFactory(sdk, summaryData);
-  }
-
-  SourceFactory createSourceFactoryFromWorkspace(Workspace workspace,
-      {SummaryDataStore? summaryData}) {
-    DartSdk sdk = findSdk(workspace);
-    if (summaryData != null && sdk is SummaryBasedDartSdk) {
-      summaryData.addBundle(null, sdk.bundle);
-    }
-    return workspace.createSourceFactory(sdk, summaryData);
-  }
-
-  /// Add any [declaredVariables] to the list of declared variables used by the
-  /// given analysis [driver].
-  void declareVariablesInDriver(AnalysisDriver driver) {
-    var variables = builderOptions.declaredVariables;
-    if (variables.isNotEmpty) {
-      driver.declaredVariables = DeclaredVariables.fromMap(variables);
-      driver.configure();
-    }
-  }
-
-  /// Return the SDK that should be used to analyze code. Use the given
-  /// [workspace] to locate the SDK.
-  DartSdk findSdk(Workspace? workspace) {
-    String? summaryPath = builderOptions.dartSdkSummaryPath;
-    if (summaryPath != null) {
-      return SummaryBasedDartSdk(summaryPath, true,
-          resourceProvider: resourceProvider);
-    }
-
-    DartSdk folderSdk;
-    {
-      String sdkPath = sdkManager.defaultSdkDirectory;
-      SdkDescription description = SdkDescription(sdkPath);
-      folderSdk = sdkManager.getSdk(description, () {
-        return FolderBasedDartSdk(
-          resourceProvider,
-          resourceProvider.getFolder(sdkPath),
-        );
-      });
-    }
-
-    if (workspace != null) {
-      var partialSourceFactory = workspace.createSourceFactory(null, null);
-      var embedderYamlSource = partialSourceFactory.forUri(
-        'package:sky_engine/_embedder.yaml',
-      );
-      if (embedderYamlSource != null) {
-        var embedderYamlPath = embedderYamlSource.fullName;
-        var libFolder = resourceProvider.getFile(embedderYamlPath).parent2;
-        EmbedderYamlLocator locator =
-            EmbedderYamlLocator.forLibFolder(libFolder);
-        Map<Folder, YamlMap> embedderMap = locator.embedderYamls;
-        if (embedderMap.isNotEmpty) {
-          EmbedderSdk embedderSdk = EmbedderSdk(
-            resourceProvider,
-            embedderMap,
-            languageVersion: folderSdk.languageVersion,
-          );
-          return embedderSdk;
-        }
-      }
-    }
-
-    return folderSdk;
-  }
-
-  /// Return the analysis options that should be used to analyze code in the
-  /// directory with the given [path]. Use [verbosePrint] to echo verbose
-  /// information about the analysis options selection process.
-  AnalysisOptionsImpl getAnalysisOptions(String path, Workspace workspace,
-      {void Function(String text)? verbosePrint, ContextRoot? contextRoot}) {
-    void verbose(String text) {
-      if (verbosePrint != null) {
-        verbosePrint(text);
-      }
-    }
-
-    SourceFactory sourceFactory = workspace.createSourceFactory(null, null);
-    AnalysisOptionsProvider optionsProvider =
-        AnalysisOptionsProvider(sourceFactory);
-
-    AnalysisOptionsImpl options = AnalysisOptionsImpl();
-
-    var optionsPath = builderOptions.defaultAnalysisOptionsFilePath;
-    if (optionsPath != null) {
-      var optionsFile = resourceProvider.getFile(optionsPath);
-      try {
-        contextRoot?.optionsFilePath = optionsFile.path;
-        var optionsMap = optionsProvider.getOptionsFromFile(optionsFile);
-        applyToAnalysisOptions(options, optionsMap);
-        verbose('Loaded analysis options from ${optionsFile.path}');
-      } catch (e) {
-        // Ignore exceptions thrown while trying to load the options file.
-        verbose('Exception: $e\n  when loading ${optionsFile.path}');
-      }
-    } else {
-      verbose('Using default analysis options');
-    }
-
-    var pubspecFile = _findPubspecFile(path);
-    if (pubspecFile != null) {
-      var extractor = SdkConstraintExtractor(pubspecFile);
-      var sdkVersionConstraint = extractor.constraint();
-      if (sdkVersionConstraint != null) {
-        options.sdkVersionConstraint = sdkVersionConstraint;
-      }
-    }
-
-    return options;
-  }
-
-  /// Return the `pubspec.yaml` file that should be used when analyzing code in
-  /// the directory with the given [path], possibly `null`.
-  File? _findPubspecFile(String path) {
-    var folder = resourceProvider.getFolder(path);
-    for (var current in folder.withAncestors) {
-      var file = current.getChildAssumingFile('pubspec.yaml');
-      if (file.exists) {
-        return file;
-      }
-    }
-  }
-
   /// Return [Packages] to analyze a resource with the [rootPath].
   static Packages createPackageMap({
     required ResourceProvider resourceProvider,
@@ -360,11 +104,6 @@
 
 /// Options used by a [ContextBuilder].
 class ContextBuilderOptions {
-  /// The file path of the file containing the summary of the SDK that should be
-  /// used to "analyze" the SDK. This option should only be specified by
-  /// command-line tools such as 'dartanalyzer' or 'ddc'.
-  String? dartSdkSummaryPath;
-
   /// The file path of the analysis options file that should be used in place of
   /// any file in the root directory or a parent of the root directory, or `null`
   /// if the normal lookup mechanism should be used.
@@ -378,10 +117,6 @@
   /// or `null` if the normal lookup mechanism should be used.
   String? defaultPackageFilePath;
 
-  /// A list of the paths of summary files that are to be used, or `null` if no
-  /// summary information is available.
-  List<String>? librarySummaryPaths;
-
   /// Initialize a newly created set of options
   ContextBuilderOptions();
 }
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 52d65fc..df488a6 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -8,7 +8,6 @@
 import 'package:analyzer/dart/analysis/analysis_context.dart' as api;
 import 'package:analyzer/dart/analysis/declared_variables.dart';
 import 'package:analyzer/dart/analysis/results.dart';
-import 'package:analyzer/dart/analysis/session.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/error/listener.dart';
@@ -1000,11 +999,11 @@
         }
         // Notify the completers.
         _requestedFiles.remove(path)!.forEach((completer) {
-          completer.complete(result);
+          completer.complete(result.unitResult!);
         });
         // Remove from to be analyzed and produce it now.
         _fileTracker.fileWasAnalyzed(path);
-        _resultController.add(result);
+        _resultController.add(result.unitResult!);
       } catch (exception, stackTrace) {
         _reportException(path, exception, stackTrace);
         _fileTracker.fileWasAnalyzed(path);
@@ -1129,7 +1128,7 @@
             if (result == null) {
               _partsToAnalyze.add(path);
             } else {
-              _resultController.add(result);
+              _resultController.add(result.unitResult!);
             }
           } catch (exception, stackTrace) {
             _reportException(path, exception, stackTrace);
@@ -1150,12 +1149,12 @@
             withUnit: false, skipIfSameSignature: true);
         if (result == null) {
           _partsToAnalyze.add(path);
-        } else if (result == AnalysisResult._UNCHANGED) {
+        } else if (result.isUnchangedErrors) {
           // We found that the set of errors is the same as we produced the
           // last time, so we don't need to produce it again now.
         } else {
-          _resultController.add(result);
-          _lastProducedSignatures[result.path] = result._signature;
+          _resultController.add(result.errorsResult!);
+          _lastProducedSignatures[path] = result._signature;
         }
       } catch (exception, stackTrace) {
         _reportException(path, exception, stackTrace);
@@ -1174,11 +1173,11 @@
             withUnit: true, asIsIfPartWithoutLibrary: true)!;
         // Notify the completers.
         _requestedParts.remove(path)!.forEach((completer) {
-          completer.complete(result);
+          completer.complete(result.unitResult!);
         });
         // Remove from to be analyzed and produce it now.
         _partsToAnalyze.remove(path);
-        _resultController.add(result);
+        _resultController.add(result.unitResult!);
       } catch (exception, stackTrace) {
         _reportException(path, exception, stackTrace);
         _partsToAnalyze.remove(path);
@@ -1195,10 +1194,16 @@
       String path = _partsToAnalyze.first;
       _partsToAnalyze.remove(path);
       try {
-        var result = _computeAnalysisResult(path,
-            withUnit: _priorityFiles.contains(path),
-            asIsIfPartWithoutLibrary: true)!;
-        _resultController.add(result);
+        var withUnit = _priorityFiles.contains(path);
+        if (withUnit) {
+          var result = _computeAnalysisResult(path,
+              withUnit: true, asIsIfPartWithoutLibrary: true)!;
+          _resultController.add(result.unitResult!);
+        } else {
+          var result = _computeAnalysisResult(path,
+              withUnit: false, asIsIfPartWithoutLibrary: true)!;
+          _resultController.add(result.errorsResult!);
+        }
       } catch (exception, stackTrace) {
         _reportException(path, exception, stackTrace);
         _clearLibraryContextAfterException();
@@ -1300,8 +1305,9 @@
   /// Return the cached or newly computed analysis result of the file with the
   /// given [path].
   ///
-  /// The result will have the fully resolved unit and will always be newly
-  /// compute only if [withUnit] is `true`.
+  /// The [withUnit] flag control which result will be returned.
+  /// When `true`, [AnalysisResult.unitResult] will be set.
+  /// Otherwise [AnalysisResult.errorsResult] will be set.
   ///
   /// Return `null` if the file is a part of an unknown library, so cannot be
   /// analyzed yet. But [asIsIfPartWithoutLibrary] is `true`, then the file is
@@ -1311,7 +1317,7 @@
   /// the resolved signature of the file in its library is the same as the one
   /// that was the most recently produced to the client.
   AnalysisResult? _computeAnalysisResult(String path,
-      {bool withUnit = false,
+      {required bool withUnit,
       bool asIsIfPartWithoutLibrary = false,
       bool skipIfSameSignature = false}) {
     FileState file = _fsState.getFileForPath(path);
@@ -1334,7 +1340,7 @@
     if (skipIfSameSignature) {
       assert(!withUnit);
       if (_lastProducedSignatures[path] == signature) {
-        return AnalysisResult._UNCHANGED;
+        return AnalysisResult.unchangedErrors(signature);
       }
     }
 
@@ -1394,7 +1400,7 @@
             content: withUnit ? file.content : null,
             resolvedUnit: withUnit ? resolvedUnit : null);
         if (withUnit && _priorityFiles.contains(path)) {
-          _priorityResults[path] = result;
+          _priorityResults[path] = result.unitResult!;
         }
         return result;
       } catch (exception, stackTrace) {
@@ -1416,8 +1422,7 @@
       return null;
     }
 
-    return ErrorsResultImpl(currentSession, path, analysisResult.uri,
-        analysisResult.lineInfo, analysisResult.isPart, analysisResult.errors);
+    return analysisResult.errorsResult;
   }
 
   AnalysisDriverUnitIndex _computeIndex(String path) {
@@ -1666,7 +1671,9 @@
     var unit = AnalysisDriverResolvedUnit.fromBuffer(bytes);
     List<AnalysisError> errors = _getErrorsFromSerialized(file, unit.errors);
     _updateHasErrorOrWarningFlag(file, errors);
-    return AnalysisResult(
+    var index = unit.index!;
+    if (resolvedUnit != null) {
+      var resolvedUnitResult = ResolvedUnitResultImpl(
         currentSession,
         file.path,
         file.uri,
@@ -1674,10 +1681,21 @@
         content,
         file.lineInfo,
         file.isPart,
-        signature,
         resolvedUnit,
         errors,
-        unit.index);
+      );
+      return AnalysisResult.unit(signature, resolvedUnitResult, index);
+    } else {
+      var errorsResult = ErrorsResultImpl(
+        currentSession,
+        file.path,
+        file.uri,
+        file.lineInfo,
+        file.isPart,
+        errors,
+      );
+      return AnalysisResult.errors(signature, errorsResult, index);
+    }
   }
 
   /// Return [AnalysisError]s for the given [serialized] errors.
@@ -1726,21 +1744,19 @@
   AnalysisResult _newMissingDartLibraryResult(
       FileState file, String missingUri) {
     // TODO(scheglov) Find a better way to report this.
-    return AnalysisResult(
-        currentSession,
-        file.path,
-        file.uri,
-        file.exists,
-        null,
-        file.lineInfo,
-        file.isPart,
-        'missing',
-        null,
-        [
-          AnalysisError(file.source, 0, 0,
-              CompileTimeErrorCode.MISSING_DART_LIBRARY, [missingUri])
-        ],
-        null);
+    var errorsResult = ErrorsResultImpl(
+      currentSession,
+      file.path,
+      file.uri,
+      file.lineInfo,
+      file.isPart,
+      [
+        AnalysisError(file.source, 0, 0,
+            CompileTimeErrorCode.MISSING_DART_LIBRARY, [missingUri])
+      ],
+    );
+    return AnalysisResult.errors(
+        'missing', errorsResult, AnalysisDriverUnitIndexBuilder());
   }
 
   void _reportException(String path, Object exception, StackTrace stackTrace) {
@@ -2121,38 +2137,50 @@
 
 /// The result of analyzing of a single file.
 ///
-/// These results are self-consistent, i.e. [content], [lineInfo], the
-/// resolved [unit] correspond to each other. All referenced elements, even
+/// These results are self-consistent, i.e. the file content, line info, the
+/// resolved unit correspond to each other. All referenced elements, even
 /// external ones, are also self-consistent. But none of the results is
 /// guaranteed to be consistent with the state of the files.
 ///
 /// Every result is independent, and is not guaranteed to be consistent with
 /// any previously returned result, even inside of the same library.
-class AnalysisResult extends ResolvedUnitResultImpl {
-  static final _UNCHANGED = _UnchangedAnalysisResult();
-
+class AnalysisResult {
   /// The signature of the result based on the content of the file, and the
   /// transitive closure of files imported and exported by the library of
   /// the requested file.
   final String _signature;
 
+  final bool isUnchangedErrors;
+
+  /// Is not `null` if this result is a result with errors.
+  /// Otherwise is `null`, and usually [unitResult] is set.
+  final ErrorsResultImpl? errorsResult;
+
+  /// Is not `null` if this result is a result with a resolved unit.
+  /// Otherwise is `null`, and usually [errorsResult] is set.
+  final ResolvedUnitResultImpl? unitResult;
+
   /// The index of the unit.
   final AnalysisDriverUnitIndex? _index;
 
-  AnalysisResult(
-      AnalysisSession session,
-      String path,
-      Uri uri,
-      bool exists,
-      String? content,
-      LineInfo lineInfo,
-      bool isPart,
-      this._signature,
-      CompilationUnit? unit,
-      List<AnalysisError> errors,
-      this._index)
-      : super(session, path, uri, exists, content, lineInfo, isPart, unit,
-            errors);
+  AnalysisResult.errors(
+      this._signature, this.errorsResult, AnalysisDriverUnitIndex index)
+      : isUnchangedErrors = false,
+        unitResult = null,
+        _index = index;
+
+  AnalysisResult.unchangedErrors(this._signature)
+      : isUnchangedErrors = true,
+        errorsResult = null,
+        unitResult = null,
+        _index = null;
+
+  AnalysisResult.unit(this._signature, ResolvedUnitResultImpl unitResult,
+      AnalysisDriverUnitIndex index)
+      : isUnchangedErrors = false,
+        errorsResult = null,
+        unitResult = unitResult,
+        _index = index;
 }
 
 /// An object that watches for the creation and removal of analysis drivers.
@@ -2494,8 +2522,3 @@
     return true;
   }
 }
-
-class _UnchangedAnalysisResult implements AnalysisResult {
-  @override
-  noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
-}
diff --git a/pkg/analyzer/test/src/context/builder_test.dart b/pkg/analyzer/test/src/context/builder_test.dart
index e439283..66c9590 100644
--- a/pkg/analyzer/test/src/context/builder_test.dart
+++ b/pkg/analyzer/test/src/context/builder_test.dart
@@ -4,22 +4,6 @@
 
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/context/builder.dart';
-import 'package:analyzer/src/context/context_root.dart';
-import 'package:analyzer/src/context/packages.dart';
-import 'package:analyzer/src/context/source.dart';
-import 'package:analyzer/src/generated/engine.dart';
-import 'package:analyzer/src/generated/sdk.dart';
-import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/source/package_map_resolver.dart';
-import 'package:analyzer/src/test_utilities/mock_sdk.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
-import 'package:analyzer/src/workspace/basic.dart';
-import 'package:analyzer/src/workspace/bazel.dart';
-import 'package:analyzer/src/workspace/gn.dart';
-import 'package:analyzer/src/workspace/package_build.dart';
-import 'package:analyzer/src/workspace/pub.dart';
-import 'package:analyzer/src/workspace/workspace.dart';
-import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -27,461 +11,11 @@
 
 main() {
   defineReflectiveSuite(() {
-    defineReflectiveTests(ContextBuilderTest);
     defineReflectiveTests(EmbedderYamlLocatorTest);
   });
 }
 
 @reflectiveTest
-class ContextBuilderTest with ResourceProviderMixin {
-  /// The SDK manager used by the tests;
-  late final DartSdkManager sdkManager;
-
-  /// The options passed to the context builder.
-  ContextBuilderOptions builderOptions = ContextBuilderOptions();
-
-  /// The context builder to be used in the test.
-  late ContextBuilder builder;
-
-  /// The path to the default SDK, or `null` if the test has not explicitly
-  /// invoked [createDefaultSdk].
-  late final String defaultSdkPath;
-
-  Uri convertedDirectoryUri(String directoryPath) {
-    return Uri.directory(convertPath(directoryPath),
-        windows: resourceProvider.pathContext.style == path.windows.style);
-  }
-
-  void createDefaultSdk() {
-    defaultSdkPath = convertPath(sdkRoot);
-    MockSdk(resourceProvider: resourceProvider);
-  }
-
-  void setUp() {
-    MockSdk(resourceProvider: resourceProvider);
-    sdkManager = DartSdkManager(convertPath('/sdk'));
-    builder = ContextBuilder(
-      resourceProvider,
-      sdkManager,
-      options: builderOptions,
-    );
-  }
-
-  @failingTest
-  void test_buildContext() {
-    fail('Incomplete test');
-  }
-
-  @failingTest
-  void test_cmdline_options_override_options_file() {
-    fail('No clear choice of option to override.');
-//    ArgParser argParser = new ArgParser();
-//    defineAnalysisArguments(argParser);
-//    ArgResults argResults = argParser.parse(['--$enableSuperMixinFlag']);
-//    var builder = new ContextBuilder(resourceProvider, sdkManager, contentCache,
-//        options: createContextBuilderOptions(argResults));
-//
-//    AnalysisOptionsImpl expected = new AnalysisOptionsImpl();
-//    expected.option = true;
-//
-//    String path = resourceProvider.convertPath('/some/directory/path');
-//    String filePath =
-//        join(path, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
-//    resourceProvider.newFile(filePath, '''
-//analyzer:
-//  language:
-//    option: true
-//''');
-//
-//    AnalysisOptions options = builder.getAnalysisOptions(path);
-//    _expectEqualOptions(options, expected);
-  }
-
-  void test_createPackageMap_fromPackageFile_explicit() {
-    // Use a package file that is outside the project directory's hierarchy.
-    String rootPath = convertPath('/root');
-    String projectPath = join(rootPath, 'project');
-    String packageFilePath = join(rootPath, 'child', '.packages');
-    newFolder(projectPath);
-    newFile(packageFilePath, content: '''
-foo:${toUriStr('/pkg/foo')}
-bar:${toUriStr('/pkg/bar')}
-''');
-
-    builderOptions.defaultPackageFilePath = packageFilePath;
-    Packages packages = _createPackageMap(projectPath);
-    _assertPackages(
-      packages,
-      {
-        'foo': convertPath('/pkg/foo'),
-        'bar': convertPath('/pkg/bar'),
-      },
-    );
-  }
-
-  void test_createPackageMap_fromPackageFile_inParentOfRoot() {
-    // Use a package file that is inside the parent of the project directory.
-    String rootPath = convertPath('/root');
-    String projectPath = join(rootPath, 'project');
-    String packageFilePath = join(rootPath, '.packages');
-    newFolder(projectPath);
-    newFile(packageFilePath, content: '''
-foo:${toUriStr('/pkg/foo')}
-bar:${toUriStr('/pkg/bar')}
-''');
-
-    Packages packages = _createPackageMap(projectPath);
-    _assertPackages(
-      packages,
-      {
-        'foo': convertPath('/pkg/foo'),
-        'bar': convertPath('/pkg/bar'),
-      },
-    );
-  }
-
-  void test_createPackageMap_fromPackageFile_inRoot() {
-    // Use a package file that is inside the project directory.
-    String rootPath = convertPath('/root');
-    String projectPath = join(rootPath, 'project');
-    String packageFilePath = join(projectPath, '.packages');
-    newFolder(projectPath);
-    newFile(packageFilePath, content: '''
-foo:${toUriStr('/pkg/foo')}
-bar:${toUriStr('/pkg/bar')}
-''');
-
-    Packages packages = _createPackageMap(projectPath);
-    _assertPackages(
-      packages,
-      {
-        'foo': convertPath('/pkg/foo'),
-        'bar': convertPath('/pkg/bar'),
-      },
-    );
-  }
-
-  void test_createPackageMap_none() {
-    String rootPath = convertPath('/root');
-    newFolder(rootPath);
-    Packages packages = _createPackageMap(rootPath);
-    expect(packages.packages, isEmpty);
-  }
-
-  void test_createPackageMap_rootDoesNotExist() {
-    String rootPath = convertPath('/root');
-    Packages packages = _createPackageMap(rootPath);
-    expect(packages.packages, isEmpty);
-  }
-
-  void test_createSourceFactory_bazelWorkspace_fileProvider() {
-    String projectPath = convertPath('/workspace/my/module');
-    newFile('/workspace/WORKSPACE');
-    newFolder('/workspace/bazel-bin');
-    newFolder('/workspace/bazel-genfiles');
-    newFolder(projectPath);
-
-    var factory = _createSourceFactory(projectPath);
-    expect(factory.resolvers,
-        contains(predicate((r) => r is BazelFileUriResolver)));
-    expect(factory.resolvers,
-        contains(predicate((r) => r is BazelPackageUriResolver)));
-  }
-
-  void test_createSourceFactory_bazelWorkspace_withPackagesFile() {
-    String projectPath = convertPath('/workspace/my/module');
-    newFile('/workspace/WORKSPACE');
-    newFolder('/workspace/bazel-bin');
-    newFolder('/workspace/bazel-genfiles');
-    newFolder(projectPath);
-    newFile(join(projectPath, '.packages'));
-
-    var factory = _createSourceFactory(projectPath);
-    expect(factory.resolvers,
-        contains(predicate((r) => r is ResourceUriResolver)));
-    expect(factory.resolvers,
-        contains(predicate((r) => r is PackageMapUriResolver)));
-  }
-
-  void test_createSourceFactory_noProvider_packages_embedder_noExtensions() {
-    String rootPath = convertPath('/root');
-    createDefaultSdk();
-    String projectPath = join(rootPath, 'project');
-    String packageFilePath = join(projectPath, '.packages');
-
-    String skyEnginePath = join(rootPath, 'pkgs', 'sky_engine');
-    String embedderPath = join(skyEnginePath, '_embedder.yaml');
-    String asyncPath = join(skyEnginePath, 'sdk', 'async.dart');
-    String corePath = join(skyEnginePath, 'sdk', 'core.dart');
-    newFile(embedderPath, content: '''
-embedded_libs:
-  "dart:async": ${_relativeUri(asyncPath, from: skyEnginePath)}
-  "dart:core": ${_relativeUri(corePath, from: skyEnginePath)}
-''');
-
-    String packageB = join(rootPath, 'pkgs', 'b');
-    newFile(packageFilePath, content: '''
-sky_engine:${resourceProvider.pathContext.toUri(skyEnginePath)}
-b:${resourceProvider.pathContext.toUri(packageB)}
-''');
-
-    SourceFactory factory = _createSourceFactory(projectPath);
-
-    var dartSource = factory.forUri('dart:async')!;
-    expect(dartSource, isNotNull);
-    expect(dartSource.fullName, asyncPath);
-
-    var packageSource = factory.forUri('package:b/b.dart')!;
-    expect(packageSource, isNotNull);
-    expect(packageSource.fullName, join(packageB, 'b.dart'));
-  }
-
-  @failingTest
-  void test_createSourceFactory_noProvider_packages_noEmbedder_extensions() {
-    fail('Incomplete test');
-  }
-
-  void test_createSourceFactory_noProvider_packages_noEmbedder_noExtensions() {
-    String rootPath = convertPath('/root');
-    createDefaultSdk();
-    String projectPath = join(rootPath, 'project');
-    String packageFilePath = join(projectPath, '.packages');
-    String packageA = join(rootPath, 'pkgs', 'a');
-    String packageB = join(rootPath, 'pkgs', 'b');
-    newFile(packageFilePath, content: '''
-a:${resourceProvider.pathContext.toUri(packageA)}
-b:${resourceProvider.pathContext.toUri(packageB)}
-''');
-
-    SourceFactory factory = _createSourceFactory(projectPath);
-
-    var dartSource = factory.forUri('dart:core')!;
-    expect(dartSource, isNotNull);
-    expect(
-        dartSource.fullName, join(defaultSdkPath, 'lib', 'core', 'core.dart'));
-
-    var packageSource = factory.forUri('package:a/a.dart')!;
-    expect(packageSource, isNotNull);
-    expect(packageSource.fullName, join(packageA, 'a.dart'));
-  }
-
-  void test_createWorkspace_hasPackagesFile_hasDartToolAndPubspec() {
-    newDotPackagesFile('/workspace');
-    newFolder('/workspace/.dart_tool/build/generated/project/lib');
-    newPubspecYamlFile('/workspace', 'name: project');
-    Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
-    expect(workspace, TypeMatcher<PackageBuildWorkspace>());
-  }
-
-  void test_createWorkspace_hasPackagesFile_hasPubspec() {
-    newDotPackagesFile('/workspace');
-    newPubspecYamlFile('/workspace', 'name: project');
-    Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
-    expect(workspace, TypeMatcher<PubWorkspace>());
-  }
-
-  void test_createWorkspace_hasPackagesFile_noMarkerFiles() {
-    newDotPackagesFile('/workspace');
-    Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
-    expect(workspace, TypeMatcher<BasicWorkspace>());
-  }
-
-  void test_createWorkspace_noPackagesFile_hasBazelMarkerFiles() {
-    newFile('/workspace/WORKSPACE');
-    newFolder('/workspace/bazel-genfiles');
-    Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
-    expect(workspace, TypeMatcher<BazelWorkspace>());
-  }
-
-  void test_createWorkspace_noPackagesFile_hasDartToolAndPubspec() {
-    newFolder('/workspace/.dart_tool/build/generated/project/lib');
-    newPubspecYamlFile('/workspace', 'name: project');
-    Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
-    expect(workspace, TypeMatcher<PackageBuildWorkspace>());
-  }
-
-  void test_createWorkspace_noPackagesFile_hasGnMarkerFiles() {
-    newFolder('/workspace/.jiri_root');
-    newFile(
-        '/workspace/out/debug-x87_128/dartlang/gen/project/lib/lib_package_config.json',
-        content: '''{
-  "configVersion": 2,
-  "packages": []
-}''');
-    Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
-    expect(workspace, TypeMatcher<GnWorkspace>());
-  }
-
-  void test_createWorkspace_noPackagesFile_hasPubspec() {
-    newPubspecYamlFile('/workspace', 'name: project');
-    Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
-    expect(workspace, TypeMatcher<PubWorkspace>());
-  }
-
-  void test_createWorkspace_noPackagesFile_noMarkerFiles() {
-    Workspace workspace = _createWorkspace('/workspace/project/lib/lib.dart');
-    expect(workspace, TypeMatcher<BasicWorkspace>());
-  }
-
-  @failingTest
-  void test_findSdk_embedder_extensions() {
-    // See test_createSourceFactory_noProvider_packages_embedder_extensions
-    fail('Incomplete test');
-  }
-
-  @failingTest
-  void test_findSdk_embedder_noExtensions() {
-    // See test_createSourceFactory_noProvider_packages_embedder_noExtensions
-    fail('Incomplete test');
-  }
-
-  @failingTest
-  void test_findSdk_noEmbedder_extensions() {
-    // See test_createSourceFactory_noProvider_packages_noEmbedder_extensions
-    fail('Incomplete test');
-  }
-
-  @failingTest
-  void test_findSdk_noEmbedder_noExtensions() {
-    // See test_createSourceFactory_noProvider_packages_noEmbedder_noExtensions
-    fail('Incomplete test');
-  }
-
-  void test_findSdk_noPackageMap() {
-    DartSdk sdk = builder.findSdk(null);
-    expect(sdk, isNotNull);
-  }
-
-  void test_findSdk_noPackageMap_html_strong() {
-    DartSdk sdk = builder.findSdk(null);
-    expect(sdk, isNotNull);
-    Source htmlSource = sdk.mapDartUri('dart:html')!;
-    expect(htmlSource.fullName,
-        convertPath('/sdk/lib/html/dart2js/html_dart2js.dart'));
-    expect(htmlSource.exists(), isTrue);
-  }
-
-  void test_getAnalysisOptions_gnWorkspace() {
-    String projectPath = convertPath('/workspace/some/path');
-    newFolder('/workspace/.jiri_root');
-    newFile('/workspace/out/debug/gen/dart.sources/foo_pkg',
-        content: convertPath('/workspace/foo_pkg/lib'));
-    newFolder(projectPath);
-    builder =
-        ContextBuilder(resourceProvider, sdkManager, options: builderOptions);
-    AnalysisOptionsImpl expected = AnalysisOptionsImpl();
-    var options = _getAnalysisOptions(builder, projectPath);
-    _expectEqualOptions(options, expected);
-  }
-
-  void test_getAnalysisOptions_invalid() {
-    String path = convertPath('/some/directory/path');
-    newAnalysisOptionsYamlFile(path, content: ';');
-
-    AnalysisOptions options = _getAnalysisOptions(builder, path);
-    expect(options, isNotNull);
-  }
-
-  void test_getAnalysisOptions_noDefault_noOverrides() {
-    String path = convertPath('/some/directory/path');
-    newAnalysisOptionsYamlFile(path, content: '''
-linter:
-  rules:
-    - non_existent_lint_rule
-''');
-
-    var options = _getAnalysisOptions(builder, path);
-    _expectEqualOptions(options, AnalysisOptionsImpl());
-  }
-
-  void test_getAnalysisOptions_sdkVersionConstraint() {
-    var projectPath = convertPath('/test');
-    newPubspecYamlFile(projectPath, '''
-environment:
-  sdk: ^2.1.0
-''');
-
-    var options = _getAnalysisOptions(builder, projectPath);
-    expect(options.sdkVersionConstraint.toString(), '^2.1.0');
-  }
-
-  void test_getAnalysisOptions_sdkVersionConstraint_any_noOptionsFile() {
-    var projectPath = convertPath('/test');
-    var options = _getAnalysisOptions(builder, projectPath);
-    expect(options.sdkVersionConstraint, isNull);
-  }
-
-  void _assertPackages(Packages packages, Map<String, String> nameToPath) {
-    expect(packages, isNotNull);
-    expect(packages.packages, hasLength(nameToPath.length));
-    for (var name in nameToPath.keys) {
-      var expectedPath = nameToPath[name];
-      var path = packages[name]!.libFolder.path;
-      expect(path, expectedPath, reason: 'package $name');
-    }
-  }
-
-  Packages _createPackageMap(String rootPath) {
-    return ContextBuilder.createPackageMap(
-      resourceProvider: resourceProvider,
-      options: builderOptions,
-      rootPath: rootPath,
-    );
-  }
-
-  SourceFactoryImpl _createSourceFactory(String projectPath) {
-    Workspace workspace = ContextBuilder.createWorkspace(
-      resourceProvider: resourceProvider,
-      options: builderOptions,
-      rootPath: projectPath,
-    );
-    return builder.createSourceFactory(projectPath, workspace)
-        as SourceFactoryImpl;
-  }
-
-  Workspace _createWorkspace(String posixPath) {
-    return ContextBuilder.createWorkspace(
-      resourceProvider: resourceProvider,
-      options: ContextBuilderOptions(),
-      rootPath: convertPath(posixPath),
-    );
-  }
-
-  void _expectEqualOptions(
-      AnalysisOptionsImpl actual, AnalysisOptionsImpl expected) {
-    // TODO(brianwilkerson) Consider moving this to AnalysisOptionsImpl.==.
-    expect(actual.enableTiming, expected.enableTiming);
-    expect(actual.hint, expected.hint);
-    expect(actual.lint, expected.lint);
-    expect(
-      actual.lintRules.map((l) => l.name),
-      unorderedEquals(expected.lintRules.map((l) => l.name)),
-    );
-    expect(actual.implicitCasts, expected.implicitCasts);
-    expect(actual.implicitDynamic, expected.implicitDynamic);
-    expect(actual.strictInference, expected.strictInference);
-    expect(actual.strictRawTypes, expected.strictRawTypes);
-  }
-
-  AnalysisOptionsImpl _getAnalysisOptions(ContextBuilder builder, String path,
-      {ContextRoot? contextRoot}) {
-    Workspace workspace = ContextBuilder.createWorkspace(
-      resourceProvider: resourceProvider,
-      options: builder.builderOptions,
-      rootPath: path,
-    );
-    return builder.getAnalysisOptions(path, workspace,
-        contextRoot: contextRoot);
-  }
-
-  Uri _relativeUri(String path, {String? from}) {
-    var pathContext = resourceProvider.pathContext;
-    String relativePath = pathContext.relative(path, from: from);
-    return pathContext.toUri(relativePath);
-  }
-}
-
-@reflectiveTest
 class EmbedderYamlLocatorTest extends EmbedderRelatedTest {
   void test_empty() {
     EmbedderYamlLocator locator = EmbedderYamlLocator({
diff --git a/pkg/analyzer/test/src/dart/analysis/base.dart b/pkg/analyzer/test/src/dart/analysis/base.dart
index 6b8cfd1..1bb79b1 100644
--- a/pkg/analyzer/test/src/dart/analysis/base.dart
+++ b/pkg/analyzer/test/src/dart/analysis/base.dart
@@ -33,7 +33,7 @@
   late final AnalysisDriverScheduler scheduler;
   late final AnalysisDriver driver;
   final List<AnalysisStatus> allStatuses = <AnalysisStatus>[];
-  final List<ResolvedUnitResult> allResults = <ResolvedUnitResult>[];
+  final List<AnalysisResultWithErrors> allResults = [];
   final List<ExceptionResult> allExceptions = <ExceptionResult>[];
 
   late final String testProject;
@@ -142,7 +142,7 @@
     scheduler.start();
     scheduler.status.listen(allStatuses.add);
     driver.results.listen((result) {
-      if (result is ResolvedUnitResult) {
+      if (result is AnalysisResultWithErrors) {
         allResults.add(result);
       }
     });
diff --git a/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart b/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
index af27464..5c75695 100644
--- a/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/context_builder_test.dart
@@ -5,14 +5,25 @@
 import 'package:analyzer/dart/analysis/analysis_context.dart';
 import 'package:analyzer/dart/analysis/context_root.dart';
 import 'package:analyzer/dart/analysis/declared_variables.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/context/source.dart';
 import 'package:analyzer/src/dart/analysis/context_builder.dart';
+import 'package:analyzer/src/dart/analysis/context_locator.dart';
 import 'package:analyzer/src/dart/analysis/context_root.dart';
+import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
+import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/source/package_map_resolver.dart';
 import 'package:analyzer/src/test_utilities/mock_sdk.dart';
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
 import 'package:analyzer/src/workspace/basic.dart';
+import 'package:analyzer/src/workspace/bazel.dart';
+import 'package:analyzer/src/workspace/pub.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
+import '../resolution/context_collection_resolution.dart';
+
 main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(ContextBuilderImplTest);
@@ -40,6 +51,61 @@
     contextRoot = ContextRootImpl(resourceProvider, folder, workspace);
   }
 
+  void test_analysisOptions_invalid() {
+    MockSdk(resourceProvider: resourceProvider);
+
+    var projectPath = convertPath('/home/test');
+    newAnalysisOptionsYamlFile(projectPath, content: ';');
+
+    var analysisContext = _createSingleAnalysisContext(projectPath);
+    var analysisOptions = analysisContext.analysisOptionsImpl;
+    _expectEqualOptions(analysisOptions, AnalysisOptionsImpl());
+  }
+
+  void test_analysisOptions_languageOptions() {
+    MockSdk(resourceProvider: resourceProvider);
+
+    var projectPath = convertPath('/home/test');
+    newAnalysisOptionsYamlFile(
+      projectPath,
+      content: AnalysisOptionsFileConfig(
+        strictRawTypes: true,
+      ).toContent(),
+    );
+
+    var analysisContext = _createSingleAnalysisContext(projectPath);
+    var analysisOptions = analysisContext.analysisOptionsImpl;
+    _expectEqualOptions(
+      analysisOptions,
+      AnalysisOptionsImpl()..strictRawTypes = true,
+    );
+  }
+
+  void test_analysisOptions_sdkVersionConstraint_hasPubspec_hasSdk() {
+    MockSdk(resourceProvider: resourceProvider);
+
+    var projectPath = convertPath('/home/test');
+    newPubspecYamlFile(projectPath, '''
+environment:
+  sdk: ^2.1.0
+''');
+
+    var analysisContext = _createSingleAnalysisContext(projectPath);
+    var analysisOptions = analysisContext.analysisOptionsImpl;
+    expect(analysisOptions.sdkVersionConstraint.toString(), '^2.1.0');
+  }
+
+  void test_analysisOptions_sdkVersionConstraint_noPubspec() {
+    MockSdk(resourceProvider: resourceProvider);
+
+    var projectPath = convertPath('/home/test');
+    newFile('$projectPath/lib/a.dart');
+
+    var analysisContext = _createSingleAnalysisContext(projectPath);
+    var analysisOptions = analysisContext.driver.analysisOptions;
+    expect(analysisOptions.sdkVersionConstraint, isNull);
+  }
+
   test_createContext_declaredVariables() {
     MockSdk(resourceProvider: resourceProvider);
     DeclaredVariables declaredVariables =
@@ -101,4 +167,86 @@
     expect(context.contextRoot, contextRoot);
     expect(context.sdkRoot?.path, resourceProvider.convertPath(sdkRoot));
   }
+
+  void test_sourceFactory_bazelWorkspace() {
+    MockSdk(resourceProvider: resourceProvider);
+
+    var projectPath = convertPath('/workspace/my/module');
+    newFile('/workspace/WORKSPACE');
+    newFolder('/workspace/bazel-bin');
+    newFolder('/workspace/bazel-genfiles');
+
+    var analysisContext = _createSingleAnalysisContext(projectPath);
+    expect(analysisContext.contextRoot.workspace, isA<BazelWorkspace>());
+
+    expect(
+      analysisContext.uriResolvers,
+      unorderedEquals([
+        isA<DartUriResolver>(),
+        isA<BazelPackageUriResolver>(),
+        isA<BazelFileUriResolver>(),
+      ]),
+    );
+  }
+
+  void test_sourceFactory_pubWorkspace() {
+    MockSdk(resourceProvider: resourceProvider);
+
+    var projectPath = convertPath('/home/my');
+    newFile('/home/my/pubspec.yaml');
+
+    var analysisContext = _createSingleAnalysisContext(projectPath);
+    expect(analysisContext.contextRoot.workspace, isA<PubWorkspace>());
+
+    expect(
+      analysisContext.uriResolvers,
+      unorderedEquals([
+        isA<DartUriResolver>(),
+        isA<PackageMapUriResolver>(),
+        isA<ResourceUriResolver>(),
+      ]),
+    );
+  }
+
+  /// Return a single expected analysis context at the [path].
+  DriverBasedAnalysisContext _createSingleAnalysisContext(String path) {
+    var roots = ContextLocatorImpl(
+      resourceProvider: resourceProvider,
+    ).locateRoots(includedPaths: [path]);
+
+    return ContextBuilderImpl(
+      resourceProvider: resourceProvider,
+    ).createContext(
+      contextRoot: roots.single,
+      sdkPath: sdkRoot,
+    );
+  }
+
+  static void _expectEqualOptions(
+    AnalysisOptionsImpl actual,
+    AnalysisOptionsImpl expected,
+  ) {
+    // TODO(brianwilkerson) Consider moving this to AnalysisOptionsImpl.==.
+    expect(actual.enableTiming, expected.enableTiming);
+    expect(actual.hint, expected.hint);
+    expect(actual.lint, expected.lint);
+    expect(
+      actual.lintRules.map((l) => l.name),
+      unorderedEquals(expected.lintRules.map((l) => l.name)),
+    );
+    expect(actual.implicitCasts, expected.implicitCasts);
+    expect(actual.implicitDynamic, expected.implicitDynamic);
+    expect(actual.strictInference, expected.strictInference);
+    expect(actual.strictRawTypes, expected.strictRawTypes);
+  }
+}
+
+extension on DriverBasedAnalysisContext {
+  AnalysisOptionsImpl get analysisOptionsImpl {
+    return driver.analysisOptions as AnalysisOptionsImpl;
+  }
+
+  List<UriResolver> get uriResolvers {
+    return (driver.sourceFactory as SourceFactoryImpl).resolvers;
+  }
 }
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index 998836a..0ee0dc7 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -59,7 +59,7 @@
 
   late final AnalysisDriverScheduler scheduler;
 
-  List<ResolvedUnitResult> allResults = [];
+  final List<AnalysisResultWithErrors> allResults = [];
 
   AnalysisDriver newDriver() {
     sdk = MockSdk(resourceProvider: resourceProvider);
@@ -75,7 +75,7 @@
       packages: Packages.empty,
     );
     driver.results.listen((result) {
-      if (result is ResolvedUnitResult) {
+      if (result is AnalysisResultWithErrors) {
         allResults.add(result);
       }
     });
@@ -673,7 +673,9 @@
     await waitForIdleWithoutExceptions();
     expect(allResults, hasLength(1));
     {
-      ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+      ResolvedUnitResult ar = allResults
+          .whereType<ResolvedUnitResult>()
+          .firstWhere((r) => r.path == a);
       _assertTopLevelVarType(ar.unit!, 'A', 'int');
     }
     allResults.clear();
@@ -690,7 +692,9 @@
     await waitForIdleWithoutExceptions();
     expect(allResults, hasLength(1));
     {
-      ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+      ResolvedUnitResult ar = allResults
+          .whereType<ResolvedUnitResult>()
+          .firstWhere((r) => r.path == a);
       _assertTopLevelVarType(ar.unit!, 'A', 'double');
     }
   }
@@ -744,12 +748,16 @@
     // We have results for both "a" and "b".
     expect(allResults, hasLength(2));
     {
-      ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+      ResolvedUnitResult ar = allResults
+          .whereType<ResolvedUnitResult>()
+          .firstWhere((r) => r.path == a);
       _assertTopLevelVarType(ar.unit!, 'A1', 'int');
       _assertTopLevelVarType(ar.unit!, 'A2', 'int');
     }
     {
-      ResolvedUnitResult br = allResults.firstWhere((r) => r.path == b);
+      ResolvedUnitResult br = allResults
+          .whereType<ResolvedUnitResult>()
+          .firstWhere((r) => r.path == b);
       _assertTopLevelVarType(br.unit!, 'B1', 'int');
     }
 
@@ -767,12 +775,16 @@
     await waitForIdleWithoutExceptions();
     expect(allResults, hasLength(2));
     {
-      ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+      ResolvedUnitResult ar = allResults
+          .whereType<ResolvedUnitResult>()
+          .firstWhere((r) => r.path == a);
       _assertTopLevelVarType(ar.unit!, 'A1', 'double');
       _assertTopLevelVarType(ar.unit!, 'A2', 'double');
     }
     {
-      ResolvedUnitResult br = allResults.firstWhere((r) => r.path == b);
+      ResolvedUnitResult br = allResults
+          .whereType<ResolvedUnitResult>()
+          .firstWhere((r) => r.path == b);
       _assertTopLevelVarType(br.unit!, 'B1', 'double');
     }
   }
@@ -784,7 +796,7 @@
     {
       await waitForIdleWithoutExceptions();
       expect(allResults, hasLength(1));
-      ResolvedUnitResult result = allResults[0];
+      var result = allResults[0] as ResolvedUnitResult;
       expect(result.path, testFile);
       _assertTopLevelVarType(result.unit!, 'V', 'int');
     }
@@ -807,7 +819,7 @@
     {
       await waitForIdleWithoutExceptions();
       expect(allResults, hasLength(1));
-      ResolvedUnitResult result = allResults[0];
+      var result = allResults[0] as ResolvedUnitResult;
       expect(result.path, testFile);
       _assertTopLevelVarType(result.unit!, 'V', 'double');
     }
@@ -2739,9 +2751,9 @@
 
       // c.dart was added after a.dart, so it is analyzed after a.dart,
       // so we know that a.dart is the library of c.dart, so no errors.
-      ResolvedUnitResult result = allResults.lastWhere((r) => r.path == c);
+      var result =
+          allResults.whereType<ErrorsResult>().lastWhere((r) => r.path == c);
       expect(result.errors, isEmpty);
-      expect(result.unit, isNull);
     }
 
     // Update a.dart so that c.dart is not a part.
@@ -2752,9 +2764,9 @@
 
       // Now c.dart does not have a library context, so A and B cannot be
       // resolved, so there are errors.
-      ResolvedUnitResult result = allResults.lastWhere((r) => r.path == c);
+      var result =
+          allResults.whereType<ErrorsResult>().lastWhere((r) => r.path == c);
       expect(result.errors, isNotEmpty);
-      expect(result.unit, isNull);
     }
   }
 
@@ -2788,9 +2800,9 @@
     // a.dart, but we cannot find the library for it, so we delay analysis
     // until all other files are analyzed, including a.dart, after which we
     // analyze the delayed parts.
-    ResolvedUnitResult result = allResults.lastWhere((r) => r.path == c);
+    var result =
+        allResults.whereType<ErrorsResult>().lastWhere((r) => r.path == c);
     expect(result.errors, isEmpty);
-    expect(result.unit, isNull);
   }
 
   test_part_results_noLibrary() async {
@@ -2808,9 +2820,9 @@
 
     // There is no library which c.dart is a part of, so it has unresolved
     // A and B references.
-    ResolvedUnitResult result = allResults.lastWhere((r) => r.path == c);
+    var result =
+        allResults.whereType<ErrorsResult>().lastWhere((r) => r.path == c);
     expect(result.errors, isNotEmpty);
-    expect(result.unit, isNull);
   }
 
   test_part_results_priority_beforeLibrary() async {
@@ -2844,7 +2856,9 @@
     // a.dart, but we cannot find the library for it, so we delay analysis
     // until all other files are analyzed, including a.dart, after which we
     // analyze the delayed parts.
-    ResolvedUnitResult result = allResults.lastWhere((r) => r.path == c);
+    ResolvedUnitResult result = allResults
+        .whereType<ResolvedUnitResult>()
+        .lastWhere((r) => r.path == c);
     expect(result.errors, isEmpty);
     expect(result.unit, isNotNull);
   }
@@ -2866,11 +2880,15 @@
     await waitForIdleWithoutExceptions();
     expect(allResults, hasLength(2));
     {
-      ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+      ResolvedUnitResult ar = allResults
+          .whereType<ResolvedUnitResult>()
+          .firstWhere((r) => r.path == a);
       _assertTopLevelVarType(ar.unit!, 'A', 'int');
     }
     {
-      ResolvedUnitResult br = allResults.firstWhere((r) => r.path == b);
+      ResolvedUnitResult br = allResults
+          .whereType<ResolvedUnitResult>()
+          .firstWhere((r) => r.path == b);
       _assertTopLevelVarType(br.unit!, 'B', 'int');
     }
     allResults.clear();
@@ -2886,7 +2904,9 @@
     await waitForIdleWithoutExceptions();
     expect(allResults, hasLength(1));
     {
-      ResolvedUnitResult ar = allResults.firstWhere((r) => r.path == a);
+      ResolvedUnitResult ar = allResults
+          .whereType<ResolvedUnitResult>()
+          .firstWhere((r) => r.path == a);
       _assertTopLevelVarType(ar.unit!, 'A', 'double');
     }
   }
@@ -3092,7 +3112,7 @@
     await waitForIdleWithoutExceptions();
 
     expect(allResults, hasLength(1));
-    ResolvedUnitResult result = allResults.single;
+    var result = allResults.single as ResolvedUnitResult;
     expect(result.path, testFile);
     expect(result.uri.toString(), 'package:test/test.dart');
     expect(result.content, content);
@@ -3119,7 +3139,7 @@
     await waitForIdleWithoutExceptions();
 
     expect(allResults, hasLength(3));
-    ResolvedUnitResult result = allResults[0];
+    var result = allResults[0] as ResolvedUnitResult;
     expect(result.path, b);
     expect(result.unit, isNotNull);
     expect(result.errors, hasLength(0));
@@ -3131,11 +3151,9 @@
     await waitForIdleWithoutExceptions();
 
     expect(allResults, hasLength(1));
-    ResolvedUnitResult result = allResults.single;
+    var result = allResults.single as ErrorsResult;
     expect(result.path, testFile);
     expect(result.uri.toString(), 'package:test/test.dart');
-    expect(result.content, isNull);
-    expect(result.unit, isNull);
     expect(result.errors, hasLength(0));
   }
 
diff --git a/pkg/analyzer_cli/test/options_test.dart b/pkg/analyzer_cli/test/options_test.dart
index 242c86d..8d3a0fa 100644
--- a/pkg/analyzer_cli/test/options_test.dart
+++ b/pkg/analyzer_cli/test/options_test.dart
@@ -318,7 +318,6 @@
     _parse(['a.dart']);
     var builderOptions = commandLineOptions.contextBuilderOptions;
     expect(builderOptions, isNotNull);
-    expect(builderOptions.dartSdkSummaryPath, isNull);
     expect(builderOptions.declaredVariables, isEmpty);
     expect(builderOptions.defaultAnalysisOptionsFilePath, isNull);
     expect(builderOptions.defaultPackageFilePath, isNull);
diff --git a/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart b/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart
index 5b20db1..09e4bf0 100644
--- a/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart
+++ b/pkg/compiler/test/deferred_loading/data/shadowed_types/lib_shared.dart
@@ -4,7 +4,7 @@
 
 /*class: A:
  class_unit=1{libb},
- type_unit=2{libb, liba}
+ type_unit=2{liba, libb}
 */
 /*member: A.:member_unit=1{libb}*/
 class A {}
@@ -25,7 +25,7 @@
 
 /*class: D:
  class_unit=1{libb},
- type_unit=2{libb, liba}
+ type_unit=2{liba, libb}
 */
 /*member: D.:member_unit=1{libb}*/
 class D {}
diff --git a/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart b/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart
index d32f920..9c29878 100644
--- a/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart
+++ b/pkg/compiler/test/deferred_loading/data/shadowed_types/main.dart
@@ -6,7 +6,7 @@
  a_pre_fragments=[
   p1: {units: [3{liba}], usedBy: [], needs: []},
   p2: {units: [1{libb}], usedBy: [], needs: []},
-  p3: {units: [2{libb, liba}], usedBy: [], needs: []}],
+  p3: {units: [2{liba, libb}], usedBy: [], needs: []}],
  b_finalized_fragments=[
   f1: [3{liba}],
   f2: [1{libb}]],
@@ -19,7 +19,7 @@
  a_pre_fragments=[
   p1: {units: [3{liba}], usedBy: [p3], needs: []},
   p2: {units: [1{libb}], usedBy: [p3], needs: []},
-  p3: {units: [2{libb, liba}], usedBy: [], needs: [p1, p2]}],
+  p3: {units: [2{liba, libb}], usedBy: [], needs: [p1, p2]}],
  b_finalized_fragments=[
   f1: [3{liba}],
   f2: [1{libb}]],
diff --git a/pkg/compiler/test/deferred_loading/deferred_loading_test.dart b/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
index 621ba83..7ef31f8 100644
--- a/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
+++ b/pkg/compiler/test/deferred_loading/deferred_loading_test.dart
@@ -5,24 +5,10 @@
 // @dart = 2.7
 
 import 'dart:io' hide Link;
-import 'package:_fe_analyzer_shared/src/testing/features.dart';
 import 'package:async_helper/async_helper.dart';
-import 'package:compiler/src/closure.dart';
-import 'package:compiler/src/common.dart';
-import 'package:compiler/src/compiler.dart';
 import 'package:compiler/src/deferred_load.dart';
-import 'package:compiler/src/elements/entities.dart';
-import 'package:compiler/src/ir/util.dart';
-import 'package:compiler/src/js_model/element_map.dart';
-import 'package:compiler/src/js_model/js_world.dart';
-import 'package:compiler/src/js_emitter/startup_emitter/fragment_merger.dart';
-import 'package:compiler/src/kernel/kernel_strategy.dart';
-import 'package:expect/expect.dart';
-import '../equivalence/id_equivalence.dart';
 import '../equivalence/id_equivalence_helper.dart';
-import 'package:compiler/src/constants/values.dart';
-
-import 'package:kernel/ast.dart' as ir;
+import 'deferred_loading_test_helper.dart';
 
 ///  Add in options to pass to the compiler like
 /// `Flags.disableTypeInference` or `Flags.disableInlining`
@@ -44,342 +30,3 @@
             [twoDeferredFragmentConfig, threeDeferredFragmentConfig]);
   });
 }
-
-// For ease of testing and making our tests easier to read, we impose an
-// artificial constraint of requiring every deferred import use a different
-// named prefix per test. We enforce this constraint here by checking that no
-// prefix name responds to two different libraries.
-Map<String, Uri> importPrefixes = {};
-
-String importPrefixString(OutputUnit unit) {
-  StringBuffer sb = StringBuffer();
-  bool first = true;
-  for (ImportEntity import in unit.imports) {
-    if (!first) sb.write(', ');
-    sb.write('${import.name}');
-    first = false;
-    Expect.isTrue(import.isDeferred);
-
-    if (importPrefixes.containsKey(import.name)) {
-      var existing = importPrefixes[import.name];
-      var current = import.enclosingLibraryUri;
-      Expect.equals(
-          existing,
-          current,
-          '\n    Duplicate prefix \'${import.name}\' used in both:\n'
-          '     - $existing and\n'
-          '     - $current.\n'
-          '    We require using unique prefixes on these tests to make '
-          'the expectations more readable.');
-    }
-    importPrefixes[import.name] = import.enclosingLibraryUri;
-  }
-  return sb.toString();
-}
-
-/// Create a consistent string representation of [OutputUnit]s for both
-/// KImportEntities and ImportElements.
-String outputUnitString(OutputUnit unit) {
-  if (unit == null) return 'none';
-  String sb = importPrefixString(unit);
-  return '${unit.name}{$sb}';
-}
-
-Map<String, List<PreFragment>> buildPreFragmentMap(
-    Map<String, List<FinalizedFragment>> fragmentsToLoad,
-    List<PreFragment> preDeferredFragments) {
-  Map<FinalizedFragment, PreFragment> fragmentMap = {};
-  for (var preFragment in preDeferredFragments) {
-    fragmentMap[preFragment.finalizedFragment] = preFragment;
-  }
-  Map<String, List<PreFragment>> preFragmentMap = {};
-  fragmentsToLoad.forEach((loadId, fragments) {
-    List<PreFragment> preFragments = [];
-    for (var fragment in fragments) {
-      preFragments.add(fragmentMap[fragment]);
-    }
-    preFragmentMap[loadId] = preFragments.toList();
-  });
-  return preFragmentMap;
-}
-
-class Tags {
-  static const String cls = 'class_unit';
-  static const String member = 'member_unit';
-  static const String closure = 'closure_unit';
-  static const String constants = 'constants';
-  static const String type = 'type_unit';
-  // The below tags appear in a single block comment in the main file.
-  // To keep them appearing in sequential order we prefix characters.
-  static const String preFragments = 'a_pre_fragments';
-  static const String finalizedFragments = 'b_finalized_fragments';
-  static const String steps = 'c_steps';
-}
-
-class OutputUnitDataComputer extends DataComputer<Features> {
-  const OutputUnitDataComputer();
-
-  /// OutputData for [member] as a kernel based element.
-  ///
-  /// At this point the compiler has already been run, so it is holding the
-  /// relevant OutputUnits, we just need to extract that information from it. We
-  /// fill [actualMap] with the data computed about what the resulting OutputUnit
-  /// is.
-  @override
-  void computeMemberData(Compiler compiler, MemberEntity member,
-      Map<Id, ActualData<Features>> actualMap,
-      {bool verbose: false}) {
-    JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
-    JsToElementMap elementMap = closedWorld.elementMap;
-    MemberDefinition definition = elementMap.getMemberDefinition(member);
-    OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
-            closedWorld.outputUnitData, closedWorld.closureDataLookup)
-        .run(definition.node);
-  }
-
-  @override
-  void computeClassData(Compiler compiler, ClassEntity cls,
-      Map<Id, ActualData<Features>> actualMap,
-      {bool verbose: false}) {
-    JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
-    JsToElementMap elementMap = closedWorld.elementMap;
-    ClassDefinition definition = elementMap.getClassDefinition(cls);
-    OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
-            closedWorld.outputUnitData, closedWorld.closureDataLookup)
-        .computeForClass(definition.node);
-  }
-
-  @override
-  void computeLibraryData(Compiler compiler, LibraryEntity library,
-      Map<Id, ActualData<Features>> actualMap,
-      {bool verbose}) {
-    KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy;
-    ir.Library node = frontendStrategy.elementMap.getLibraryNode(library);
-    List<PreFragment> preDeferredFragments = compiler
-        .backendStrategy.emitterTask.emitter.preDeferredFragmentsForTesting;
-    Map<String, List<FinalizedFragment>> fragmentsToLoad =
-        compiler.backendStrategy.emitterTask.emitter.finalizedFragmentsToLoad;
-    Set<OutputUnit> omittedOutputUnits =
-        compiler.backendStrategy.emitterTask.emitter.omittedOutputUnits;
-    PreFragmentsIrComputer(compiler.reporter, actualMap, preDeferredFragments,
-            fragmentsToLoad, omittedOutputUnits)
-        .computeForLibrary(node);
-  }
-
-  @override
-  DataInterpreter<Features> get dataValidator =>
-      const FeaturesDataInterpreter();
-}
-
-class PreFragmentsIrComputer extends IrDataExtractor<Features> {
-  final List<PreFragment> _preDeferredFragments;
-  final Map<String, List<FinalizedFragment>> _fragmentsToLoad;
-  final Set<OutputUnit> _omittedOutputUnits;
-
-  PreFragmentsIrComputer(
-      DiagnosticReporter reporter,
-      Map<Id, ActualData<Features>> actualMap,
-      this._preDeferredFragments,
-      this._fragmentsToLoad,
-      this._omittedOutputUnits)
-      : super(reporter, actualMap);
-
-  @override
-  Features computeLibraryValue(Id id, ir.Library library) {
-    var name = '${library.importUri.pathSegments.last}';
-    Features features = new Features();
-    if (!name.startsWith('main')) return features;
-
-    // First build a list of pre fragments and their dependencies.
-    int index = 1;
-    Map<FinalizedFragment, int> finalizedFragmentIndices = {};
-    Map<PreFragment, int> preFragmentIndices = {};
-    Map<int, PreFragment> reversePreFragmentIndices = {};
-    Map<int, FinalizedFragment> reverseFinalizedFragmentIndices = {};
-    for (var preFragment in _preDeferredFragments) {
-      if (!preFragmentIndices.containsKey(preFragment)) {
-        var finalizedFragment = preFragment.finalizedFragment;
-        preFragmentIndices[preFragment] = index;
-        finalizedFragmentIndices[finalizedFragment] = index;
-        reversePreFragmentIndices[index] = preFragment;
-        reverseFinalizedFragmentIndices[index] = finalizedFragment;
-        index++;
-      }
-    }
-
-    for (int i = 1; i < index; i++) {
-      var preFragment = reversePreFragmentIndices[i];
-      List<String> needs = [];
-      List<OutputUnit> supplied = [];
-      List<String> usedBy = [];
-      for (var dependent in preFragment.successors) {
-        if (preFragmentIndices.containsKey(dependent)) {
-          usedBy.add('p${preFragmentIndices[dependent]}');
-        }
-      }
-
-      for (var dependency in preFragment.predecessors) {
-        if (preFragmentIndices.containsKey(dependency)) {
-          needs.add('p${preFragmentIndices[dependency]}');
-        }
-      }
-
-      for (var emittedOutputUnit in preFragment.emittedOutputUnits) {
-        supplied.add(emittedOutputUnit.outputUnit);
-      }
-
-      var suppliedString = '[${supplied.map(outputUnitString).join(', ')}]';
-      features.addElement(Tags.preFragments,
-          'p$i: {units: $suppliedString, usedBy: $usedBy, needs: $needs}');
-    }
-
-    // Now dump finalized fragments and load ids.
-    for (int i = 1; i < index; i++) {
-      var finalizedFragment = reverseFinalizedFragmentIndices[i];
-      List<String> supplied = [];
-
-      for (var codeFragment in finalizedFragment.codeFragments) {
-        List<String> outputUnitStrings = [];
-        for (var outputUnit in codeFragment.outputUnits) {
-          if (!_omittedOutputUnits.contains(outputUnit)) {
-            outputUnitStrings.add(outputUnitString(outputUnit));
-          }
-        }
-        if (outputUnitStrings.isNotEmpty) {
-          supplied.add(outputUnitStrings.join('+'));
-        }
-      }
-
-      if (supplied.isNotEmpty) {
-        var suppliedString = '[${supplied.join(', ')}]';
-        features.addElement(Tags.finalizedFragments, 'f$i: $suppliedString');
-      }
-    }
-
-    _fragmentsToLoad.forEach((loadId, finalizedFragments) {
-      List<String> finalizedFragmentNeeds = [];
-      for (var finalizedFragment in finalizedFragments) {
-        assert(finalizedFragmentIndices.containsKey(finalizedFragment));
-        finalizedFragmentNeeds
-            .add('f${finalizedFragmentIndices[finalizedFragment]}');
-      }
-      features.addElement(
-          Tags.steps, '$loadId=(${finalizedFragmentNeeds.join(', ')})');
-    });
-
-    return features;
-  }
-}
-
-class OutputUnitIrComputer extends IrDataExtractor<Features> {
-  final JsToElementMap _elementMap;
-  final OutputUnitData _data;
-  final ClosureData _closureDataLookup;
-
-  Set<String> _constants = {};
-
-  OutputUnitIrComputer(
-      DiagnosticReporter reporter,
-      Map<Id, ActualData<Features>> actualMap,
-      this._elementMap,
-      this._data,
-      this._closureDataLookup)
-      : super(reporter, actualMap);
-
-  Features getMemberValue(
-      String tag, MemberEntity member, Set<String> constants) {
-    Features features = Features();
-    features.add(tag,
-        value: outputUnitString(_data.outputUnitForMemberForTesting(member)));
-    for (var constant in constants) {
-      features.addElement(Tags.constants, constant);
-    }
-    return features;
-  }
-
-  @override
-  Features computeClassValue(Id id, ir.Class node) {
-    var cls = _elementMap.getClass(node);
-    Features features = Features();
-    features.add(Tags.cls,
-        value: outputUnitString(_data.outputUnitForClassForTesting(cls)));
-    features.add(Tags.type,
-        value: outputUnitString(_data.outputUnitForClassTypeForTesting(cls)));
-    return features;
-  }
-
-  @override
-  Features computeMemberValue(Id id, ir.Member node) {
-    if (node is ir.Field && node.isConst) {
-      ir.Expression initializer = node.initializer;
-      ConstantValue constant = _elementMap.getConstantValue(node, initializer);
-      if (!constant.isPrimitive) {
-        SourceSpan span = computeSourceSpanFromTreeNode(initializer);
-        if (initializer is ir.ConstructorInvocation) {
-          // Adjust the source-span to match the AST-based location. The kernel FE
-          // skips the "const" keyword for the expression offset and any prefix in
-          // front of the constructor. The "-6" is an approximation assuming that
-          // there is just a single space after "const" and no prefix.
-          // TODO(sigmund): offsets should be fixed in the FE instead.
-          span = SourceSpan(span.uri, span.begin - 6, span.end - 6);
-        }
-        _registerValue(
-            NodeId(span.begin, IdKind.node),
-            Features.fromMap({
-              Tags.member: outputUnitString(
-                  _data.outputUnitForConstantForTesting(constant))
-            }),
-            node,
-            span,
-            actualMap,
-            reporter);
-      }
-    }
-
-    Features features =
-        getMemberValue(Tags.member, _elementMap.getMember(node), _constants);
-    _constants = {};
-    return features;
-  }
-
-  @override
-  visitConstantExpression(ir.ConstantExpression node) {
-    ConstantValue constant = _elementMap.getConstantValue(null, node);
-    if (!constant.isPrimitive) {
-      _constants.add('${constant.toStructuredText(_elementMap.types)}='
-          '${outputUnitString(_data.outputUnitForConstant(constant))}');
-    }
-    return super.visitConstantExpression(node);
-  }
-
-  @override
-  Features computeNodeValue(Id id, ir.TreeNode node) {
-    if (node is ir.FunctionExpression || node is ir.FunctionDeclaration) {
-      ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node);
-      return getMemberValue(Tags.closure, info.callMethod, const {});
-    }
-    return null;
-  }
-}
-
-/// Set [actualMap] to hold a key of [id] with the computed data [value]
-/// corresponding to [object] at location [sourceSpan]. We also perform error
-/// checking to ensure that the same [id] isn't added twice.
-void _registerValue<T>(Id id, T value, Object object, SourceSpan sourceSpan,
-    Map<Id, ActualData<T>> actualMap, CompilerDiagnosticReporter reporter) {
-  if (actualMap.containsKey(id)) {
-    ActualData<T> existingData = actualMap[id];
-    reportHere(reporter, sourceSpan,
-        "Duplicate id ${id}, value=$value, object=$object");
-    reportHere(
-        reporter,
-        sourceSpan,
-        "Duplicate id ${id}, value=${existingData.value}, "
-        "object=${existingData.object}");
-    Expect.fail("Duplicate id $id.");
-  }
-  if (value != null) {
-    actualMap[id] =
-        ActualData<T>(id, value, sourceSpan.uri, sourceSpan.begin, object);
-  }
-}
diff --git a/pkg/compiler/test/deferred_loading/deferred_loading_test_helper.dart b/pkg/compiler/test/deferred_loading/deferred_loading_test_helper.dart
new file mode 100644
index 0000000..8028c05
--- /dev/null
+++ b/pkg/compiler/test/deferred_loading/deferred_loading_test_helper.dart
@@ -0,0 +1,360 @@
+// 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.
+
+// @dart = 2.7
+
+import 'package:_fe_analyzer_shared/src/testing/features.dart';
+import 'package:compiler/src/closure.dart';
+import 'package:compiler/src/common.dart';
+import 'package:compiler/src/compiler.dart';
+import 'package:compiler/src/deferred_load.dart';
+import 'package:compiler/src/elements/entities.dart';
+import 'package:compiler/src/ir/util.dart';
+import 'package:compiler/src/js_model/element_map.dart';
+import 'package:compiler/src/js_model/js_world.dart';
+import 'package:compiler/src/js_emitter/startup_emitter/fragment_merger.dart';
+import 'package:compiler/src/kernel/kernel_strategy.dart';
+import 'package:expect/expect.dart';
+import '../equivalence/id_equivalence.dart';
+import '../equivalence/id_equivalence_helper.dart';
+import 'package:compiler/src/constants/values.dart';
+
+import 'package:kernel/ast.dart' as ir;
+
+// For ease of testing and making our tests easier to read, we impose an
+// artificial constraint of requiring every deferred import use a different
+// named prefix per test. We enforce this constraint here by checking that no
+// prefix name responds to two different libraries.
+Map<String, Uri> importPrefixes = {};
+
+String importPrefixString(OutputUnit unit) {
+  List<String> importNames = [];
+  for (ImportEntity import in unit.imports) {
+    importNames.add(import.name);
+    Expect.isTrue(import.isDeferred);
+
+    if (importPrefixes.containsKey(import.name)) {
+      var existing = importPrefixes[import.name];
+      var current = import.enclosingLibraryUri;
+      Expect.equals(
+          existing,
+          current,
+          '\n    Duplicate prefix \'${import.name}\' used in both:\n'
+          '     - $existing and\n'
+          '     - $current.\n'
+          '    We require using unique prefixes on these tests to make '
+          'the expectations more readable.');
+    }
+    importPrefixes[import.name] = import.enclosingLibraryUri;
+  }
+  importNames.sort();
+  return importNames.join(', ');
+}
+
+/// Create a consistent string representation of [OutputUnit]s for both
+/// KImportEntities and ImportElements.
+String outputUnitString(OutputUnit unit) {
+  if (unit == null) return 'none';
+  String sb = importPrefixString(unit);
+  return '${unit.name}{$sb}';
+}
+
+Map<String, List<PreFragment>> buildPreFragmentMap(
+    Map<String, List<FinalizedFragment>> fragmentsToLoad,
+    List<PreFragment> preDeferredFragments) {
+  Map<FinalizedFragment, PreFragment> fragmentMap = {};
+  for (var preFragment in preDeferredFragments) {
+    fragmentMap[preFragment.finalizedFragment] = preFragment;
+  }
+  Map<String, List<PreFragment>> preFragmentMap = {};
+  fragmentsToLoad.forEach((loadId, fragments) {
+    List<PreFragment> preFragments = [];
+    for (var fragment in fragments) {
+      preFragments.add(fragmentMap[fragment]);
+    }
+    preFragmentMap[loadId] = preFragments.toList();
+  });
+  return preFragmentMap;
+}
+
+class Tags {
+  static const String cls = 'class_unit';
+  static const String member = 'member_unit';
+  static const String closure = 'closure_unit';
+  static const String constants = 'constants';
+  static const String type = 'type_unit';
+  // The below tags appear in a single block comment in the main file.
+  // To keep them appearing in sequential order we prefix characters.
+  static const String preFragments = 'a_pre_fragments';
+  static const String finalizedFragments = 'b_finalized_fragments';
+  static const String steps = 'c_steps';
+}
+
+class OutputUnitDataComputer extends DataComputer<Features> {
+  const OutputUnitDataComputer();
+
+  /// OutputData for [member] as a kernel based element.
+  ///
+  /// At this point the compiler has already been run, so it is holding the
+  /// relevant OutputUnits, we just need to extract that information from it. We
+  /// fill [actualMap] with the data computed about what the resulting OutputUnit
+  /// is.
+  @override
+  void computeMemberData(Compiler compiler, MemberEntity member,
+      Map<Id, ActualData<Features>> actualMap,
+      {bool verbose: false}) {
+    JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
+    JsToElementMap elementMap = closedWorld.elementMap;
+    MemberDefinition definition = elementMap.getMemberDefinition(member);
+    OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
+            closedWorld.outputUnitData, closedWorld.closureDataLookup)
+        .run(definition.node);
+  }
+
+  @override
+  void computeClassData(Compiler compiler, ClassEntity cls,
+      Map<Id, ActualData<Features>> actualMap,
+      {bool verbose: false}) {
+    JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
+    JsToElementMap elementMap = closedWorld.elementMap;
+    ClassDefinition definition = elementMap.getClassDefinition(cls);
+    OutputUnitIrComputer(compiler.reporter, actualMap, elementMap,
+            closedWorld.outputUnitData, closedWorld.closureDataLookup)
+        .computeForClass(definition.node);
+  }
+
+  @override
+  void computeLibraryData(Compiler compiler, LibraryEntity library,
+      Map<Id, ActualData<Features>> actualMap,
+      {bool verbose}) {
+    KernelFrontendStrategy frontendStrategy = compiler.frontendStrategy;
+    ir.Library node = frontendStrategy.elementMap.getLibraryNode(library);
+    List<PreFragment> preDeferredFragments = compiler
+        .backendStrategy.emitterTask.emitter.preDeferredFragmentsForTesting;
+    Map<String, List<FinalizedFragment>> fragmentsToLoad =
+        compiler.backendStrategy.emitterTask.emitter.finalizedFragmentsToLoad;
+    Set<OutputUnit> omittedOutputUnits =
+        compiler.backendStrategy.emitterTask.emitter.omittedOutputUnits;
+    PreFragmentsIrComputer(compiler.reporter, actualMap, preDeferredFragments,
+            fragmentsToLoad, omittedOutputUnits)
+        .computeForLibrary(node);
+  }
+
+  @override
+  DataInterpreter<Features> get dataValidator =>
+      const FeaturesDataInterpreter();
+}
+
+class PreFragmentsIrComputer extends IrDataExtractor<Features> {
+  final List<PreFragment> _preDeferredFragments;
+  final Map<String, List<FinalizedFragment>> _fragmentsToLoad;
+  final Set<OutputUnit> _omittedOutputUnits;
+
+  PreFragmentsIrComputer(
+      DiagnosticReporter reporter,
+      Map<Id, ActualData<Features>> actualMap,
+      this._preDeferredFragments,
+      this._fragmentsToLoad,
+      this._omittedOutputUnits)
+      : super(reporter, actualMap);
+
+  @override
+  Features computeLibraryValue(Id id, ir.Library library) {
+    var name = '${library.importUri.pathSegments.last}';
+    Features features = new Features();
+    if (!name.startsWith('main')) return features;
+
+    // First build a list of pre fragments and their dependencies.
+    int index = 1;
+    Map<FinalizedFragment, int> finalizedFragmentIndices = {};
+    Map<PreFragment, int> preFragmentIndices = {};
+    Map<int, PreFragment> reversePreFragmentIndices = {};
+    Map<int, FinalizedFragment> reverseFinalizedFragmentIndices = {};
+    for (var preFragment in _preDeferredFragments) {
+      if (!preFragmentIndices.containsKey(preFragment)) {
+        var finalizedFragment = preFragment.finalizedFragment;
+        preFragmentIndices[preFragment] = index;
+        finalizedFragmentIndices[finalizedFragment] = index;
+        reversePreFragmentIndices[index] = preFragment;
+        reverseFinalizedFragmentIndices[index] = finalizedFragment;
+        index++;
+      }
+    }
+
+    for (int i = 1; i < index; i++) {
+      var preFragment = reversePreFragmentIndices[i];
+      List<String> needs = [];
+      List<OutputUnit> supplied = [];
+      List<String> usedBy = [];
+      for (var dependent in preFragment.successors) {
+        if (preFragmentIndices.containsKey(dependent)) {
+          usedBy.add('p${preFragmentIndices[dependent]}');
+        }
+      }
+
+      for (var dependency in preFragment.predecessors) {
+        if (preFragmentIndices.containsKey(dependency)) {
+          needs.add('p${preFragmentIndices[dependency]}');
+        }
+      }
+
+      for (var emittedOutputUnit in preFragment.emittedOutputUnits) {
+        supplied.add(emittedOutputUnit.outputUnit);
+      }
+
+      var suppliedString = '[${supplied.map(outputUnitString).join(', ')}]';
+      features.addElement(Tags.preFragments,
+          'p$i: {units: $suppliedString, usedBy: $usedBy, needs: $needs}');
+    }
+
+    // Now dump finalized fragments and load ids.
+    for (int i = 1; i < index; i++) {
+      var finalizedFragment = reverseFinalizedFragmentIndices[i];
+      List<String> supplied = [];
+
+      for (var codeFragment in finalizedFragment.codeFragments) {
+        List<String> outputUnitStrings = [];
+        for (var outputUnit in codeFragment.outputUnits) {
+          if (!_omittedOutputUnits.contains(outputUnit)) {
+            outputUnitStrings.add(outputUnitString(outputUnit));
+          }
+        }
+        if (outputUnitStrings.isNotEmpty) {
+          supplied.add(outputUnitStrings.join('+'));
+        }
+      }
+
+      if (supplied.isNotEmpty) {
+        var suppliedString = '[${supplied.join(', ')}]';
+        features.addElement(Tags.finalizedFragments, 'f$i: $suppliedString');
+      }
+    }
+
+    _fragmentsToLoad.forEach((loadId, finalizedFragments) {
+      List<String> finalizedFragmentNeeds = [];
+      for (var finalizedFragment in finalizedFragments) {
+        assert(finalizedFragmentIndices.containsKey(finalizedFragment));
+        finalizedFragmentNeeds
+            .add('f${finalizedFragmentIndices[finalizedFragment]}');
+      }
+      features.addElement(
+          Tags.steps, '$loadId=(${finalizedFragmentNeeds.join(', ')})');
+    });
+
+    return features;
+  }
+}
+
+class OutputUnitIrComputer extends IrDataExtractor<Features> {
+  final JsToElementMap _elementMap;
+  final OutputUnitData _data;
+  final ClosureData _closureDataLookup;
+
+  Set<String> _constants = {};
+
+  OutputUnitIrComputer(
+      DiagnosticReporter reporter,
+      Map<Id, ActualData<Features>> actualMap,
+      this._elementMap,
+      this._data,
+      this._closureDataLookup)
+      : super(reporter, actualMap);
+
+  Features getMemberValue(
+      String tag, MemberEntity member, Set<String> constants) {
+    Features features = Features();
+    features.add(tag,
+        value: outputUnitString(_data.outputUnitForMemberForTesting(member)));
+    for (var constant in constants) {
+      features.addElement(Tags.constants, constant);
+    }
+    return features;
+  }
+
+  @override
+  Features computeClassValue(Id id, ir.Class node) {
+    var cls = _elementMap.getClass(node);
+    Features features = Features();
+    features.add(Tags.cls,
+        value: outputUnitString(_data.outputUnitForClassForTesting(cls)));
+    features.add(Tags.type,
+        value: outputUnitString(_data.outputUnitForClassTypeForTesting(cls)));
+    return features;
+  }
+
+  @override
+  Features computeMemberValue(Id id, ir.Member node) {
+    if (node is ir.Field && node.isConst) {
+      ir.Expression initializer = node.initializer;
+      ConstantValue constant = _elementMap.getConstantValue(node, initializer);
+      if (!constant.isPrimitive) {
+        SourceSpan span = computeSourceSpanFromTreeNode(initializer);
+        if (initializer is ir.ConstructorInvocation) {
+          // Adjust the source-span to match the AST-based location. The kernel FE
+          // skips the "const" keyword for the expression offset and any prefix in
+          // front of the constructor. The "-6" is an approximation assuming that
+          // there is just a single space after "const" and no prefix.
+          // TODO(sigmund): offsets should be fixed in the FE instead.
+          span = SourceSpan(span.uri, span.begin - 6, span.end - 6);
+        }
+        _registerValue(
+            NodeId(span.begin, IdKind.node),
+            Features.fromMap({
+              Tags.member: outputUnitString(
+                  _data.outputUnitForConstantForTesting(constant))
+            }),
+            node,
+            span,
+            actualMap,
+            reporter);
+      }
+    }
+
+    Features features =
+        getMemberValue(Tags.member, _elementMap.getMember(node), _constants);
+    _constants = {};
+    return features;
+  }
+
+  @override
+  visitConstantExpression(ir.ConstantExpression node) {
+    ConstantValue constant = _elementMap.getConstantValue(null, node);
+    if (!constant.isPrimitive) {
+      _constants.add('${constant.toStructuredText(_elementMap.types)}='
+          '${outputUnitString(_data.outputUnitForConstant(constant))}');
+    }
+    return super.visitConstantExpression(node);
+  }
+
+  @override
+  Features computeNodeValue(Id id, ir.TreeNode node) {
+    if (node is ir.FunctionExpression || node is ir.FunctionDeclaration) {
+      ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node);
+      return getMemberValue(Tags.closure, info.callMethod, const {});
+    }
+    return null;
+  }
+}
+
+/// Set [actualMap] to hold a key of [id] with the computed data [value]
+/// corresponding to [object] at location [sourceSpan]. We also perform error
+/// checking to ensure that the same [id] isn't added twice.
+void _registerValue<T>(Id id, T value, Object object, SourceSpan sourceSpan,
+    Map<Id, ActualData<T>> actualMap, CompilerDiagnosticReporter reporter) {
+  if (actualMap.containsKey(id)) {
+    ActualData<T> existingData = actualMap[id];
+    reportHere(reporter, sourceSpan,
+        "Duplicate id ${id}, value=$value, object=$object");
+    reportHere(
+        reporter,
+        sourceSpan,
+        "Duplicate id ${id}, value=${existingData.value}, "
+        "object=${existingData.object}");
+    Expect.fail("Duplicate id $id.");
+  }
+  if (value != null) {
+    actualMap[id] =
+        ActualData<T>(id, value, sourceSpan.uri, sourceSpan.begin, object);
+  }
+}
diff --git a/runtime/lib/isolate.cc b/runtime/lib/isolate.cc
index 922ad2b..79e6f1e 100644
--- a/runtime/lib/isolate.cc
+++ b/runtime/lib/isolate.cc
@@ -20,6 +20,7 @@
 #include "vm/longjump.h"
 #include "vm/message_handler.h"
 #include "vm/object.h"
+#include "vm/object_graph_copy.h"
 #include "vm/object_store.h"
 #include "vm/port.h"
 #include "vm/resolver.h"
@@ -111,10 +112,22 @@
     PortMap::PostMessage(
         Message::New(destination_port_id, obj.ptr(), Message::kNormalPriority));
   } else {
-    MessageWriter writer(can_send_any_object);
-    // TODO(turnidge): Throw an exception when the return value is false?
-    PortMap::PostMessage(writer.WriteMessage(obj, destination_port_id,
-                                             Message::kNormalPriority));
+    const bool same_group = IsolateGroup::AreIsolateGroupsEnabled() &&
+                            PortMap::IsReceiverInThisIsolateGroup(
+                                destination_port_id, isolate->group());
+    if (same_group) {
+      const auto& copy = Object::Handle(CopyMutableObjectGraph(obj));
+      auto handle = isolate->group()->api_state()->AllocatePersistentHandle();
+      handle->set_ptr(copy.ptr());
+      std::unique_ptr<Message> message(
+          new Message(destination_port_id, handle, Message::kNormalPriority));
+      PortMap::PostMessage(std::move(message));
+    } else {
+      MessageWriter writer(can_send_any_object);
+      // TODO(turnidge): Throw an exception when the return value is false?
+      PortMap::PostMessage(writer.WriteMessage(obj, destination_port_id,
+                                               Message::kNormalPriority));
+    }
   }
   return Object::null();
 }
diff --git a/runtime/platform/growable_array.h b/runtime/platform/growable_array.h
index c568862..34ad019 100644
--- a/runtime/platform/growable_array.h
+++ b/runtime/platform/growable_array.h
@@ -188,6 +188,10 @@
   // The content is uninitialized after calling it.
   void SetLength(intptr_t new_length);
 
+  // The content (if expanded) is uninitialized after calling it.
+  // The backing store (if expanded) will grow with by a power-of-2.
+  void Resize(intptr_t new_length);
+
   // Sort the array in place.
   inline void Sort(int compare(const T*, const T*));
 
@@ -211,9 +215,6 @@
   T* data_;
   Allocator* allocator_;  // Used to (re)allocate the array.
 
-  // Used for growing the array.
-  void Resize(intptr_t new_length);
-
   DISALLOW_COPY_AND_ASSIGN(BaseGrowableArray);
 };
 
diff --git a/runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart b/runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart
new file mode 100644
index 0000000..5dce054
--- /dev/null
+++ b/runtime/tests/vm/dart/isolates/fast_object_copy2_test.dart
@@ -0,0 +1,75 @@
+// 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation
+
+// The tests in this file will only succeed when isolate groups are enabled
+// (hence the VMOptions above).
+
+import 'dart:async';
+import 'dart:isolate';
+import 'dart:typed_data';
+
+import 'package:expect/expect.dart';
+
+import 'fast_object_copy_test.dart'
+    show UserObject, SendReceiveTestBase, notAllocatableInTLAB;
+
+// When running with isolate groups enabled, we can share all of the following
+// objects.
+final sharableObjects = [
+  1,
+  0xffffffffffffffff,
+  'foobar',
+  const UserObject(1, 1.2, ''),
+  (() {
+    final rp = ReceivePort();
+    final sp = rp.sendPort;
+    rp.close();
+    return sp;
+  })(),
+  const [1, 2, 3],
+  const {1: 1, 2: 2, 3: 2},
+  const {1, 2, 3},
+  RegExp('a'),
+  Isolate.current.pauseCapability,
+  Int32x4(1, 2, 3, 4),
+];
+
+class SendReceiveTest extends SendReceiveTestBase {
+  Future runTests() async {
+    await testSharable();
+    await testSharable2();
+  }
+
+  Future testSharable() async {
+    final sharableObjectsCopy = await sendReceive([
+      ...sharableObjects,
+    ]);
+    Expect.notIdentical(sharableObjects, sharableObjectsCopy);
+    for (int i = 0; i < sharableObjects.length; ++i) {
+      Expect.identical(sharableObjects[i], sharableObjectsCopy[i]);
+    }
+  }
+
+  Future testSharable2() async {
+    final sharableObjectsCopy = await sendReceive([
+      notAllocatableInTLAB,
+      ...sharableObjects,
+    ]);
+    Expect.notIdentical(sharableObjects, sharableObjectsCopy);
+    Expect.equals(
+        notAllocatableInTLAB[0], (sharableObjectsCopy[0] as Uint8List)[0]);
+    for (int i = 0; i < sharableObjects.length; ++i) {
+      Expect.identical(sharableObjects[i], sharableObjectsCopy[i + 1]);
+    }
+  }
+}
+
+main() async {
+  await SendReceiveTest().run();
+}
diff --git a/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart b/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart
index 00297e1..447026e 100644
--- a/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart
+++ b/runtime/tests/vm/dart/isolates/fast_object_copy_test.dart
@@ -3,7 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 
 // VMOptions=
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation --verify-store-buffer
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation --verify-store-buffer
 
 // The tests in this file are particularly for an implementation that tries to
 // allocate the entire graph in BFS order using a fast new space allocation
@@ -28,7 +31,7 @@
 final Uint8List largeInternalTypedData = Uint8List(20 * 1024 * 1024)..[0] = 42;
 
 final Uint8List smallExternalTypedData =
-    File(Platform.script.toFilePath()).readAsBytesSync();
+    File(Platform.script.toFilePath()).readAsBytesSync()..[0] = 21;
 final Uint8List smallExternalTypedDataView =
     Uint8List.view(smallExternalTypedData.buffer, 1, 1);
 
@@ -110,6 +113,17 @@
     }
     return;
   }
+  if (a is Set) {
+    final cb = b as Set;
+    Expect.equals(a.length, cb.length);
+    final aKeys = a.toList();
+    final cbKeys = cb.toList();
+    for (int i = 0; i < a.length; ++i) {
+      expectGraphsMatch(aKeys[i], cbKeys[i]);
+    }
+    return;
+  }
+
   throw 'Unexpected object encountered when matching object graphs $a / $b';
 }
 
@@ -126,6 +140,8 @@
 class HashIncrementer {
   static int counter = 1;
 
+  const HashIncrementer();
+
   int get hashCode => counter++;
   bool operator ==(other) => identical(this, other);
 }
@@ -135,21 +151,39 @@
   final double unboxedDouble;
   final dynamic slot;
 
-  UserObject(this.unboxedInt, this.unboxedDouble, this.slot);
+  const UserObject(this.unboxedInt, this.unboxedDouble, this.slot);
 }
 
-class SendReceiveTest {
+abstract class SendReceiveTestBase {
   late final ReceivePort receivePort;
   late final SendPort sendPort;
   late final StreamIterator si;
 
-  SendReceiveTest();
+  SendReceiveTestBase();
 
   Future run() async {
     receivePort = ReceivePort();
     sendPort = receivePort.sendPort;
     si = StreamIterator(receivePort);
 
+    await runTests();
+
+    si.cancel();
+    receivePort.close();
+    print('done');
+  }
+
+  Future runTests();
+
+  Future<T> sendReceive<T>(T graph) async {
+    sendPort.send(graph);
+    Expect.isTrue(await si.moveNext());
+    return si.current as T;
+  }
+}
+
+class SendReceiveTest extends SendReceiveTestBase {
+  Future runTests() async {
     await testTransferrable();
     await testTransferrable2();
     await testTransferrable3();
@@ -161,6 +195,7 @@
     await testExternalTypedData3();
     await testExternalTypedData4();
     await testExternalTypedData5();
+    await testExternalTypedData6();
 
     await testInternalTypedDataView();
     await testInternalTypedDataView2();
@@ -172,16 +207,18 @@
     await testExternalTypedDataView3();
     await testExternalTypedDataView4();
 
+    await testArray();
+
     await testMapRehash();
     await testMapRehash2();
     await testMapRehash3();
 
+    await testSetRehash();
+    await testSetRehash2();
+    await testSetRehash3();
+
     await testFastOnly();
     await testSlowOnly();
-
-    si.cancel();
-    receivePort.close();
-    print('done');
   }
 
   Future testTransferrable() async {
@@ -293,6 +330,16 @@
     }
   }
 
+  Future testExternalTypedData6() async {
+    print('testExternalTypedData6');
+    final etd = await sendReceive([
+      smallExternalTypedData,
+      largeExternalTypedData,
+    ]);
+    Expect.equals(21, etd[0][0]);
+    Expect.equals(42, etd[1][0]);
+  }
+
   Future testInternalTypedDataView() async {
     print('testInternalTypedDataView');
     final graph = [
@@ -401,6 +448,18 @@
     expectGraphsMatch(graph, copiedGraph);
   }
 
+  Future testArray() async {
+    print('testArray');
+    final oldSpace = List<dynamic>.filled(1024 * 1024, null);
+    final newSpace = UserObject(1, 1.1, 'foobar');
+    oldSpace[0] = newSpace;
+    final oldSpaceCopy = await sendReceive(oldSpace);
+    final newSpaceCopy = oldSpaceCopy[0] as UserObject;
+    Expect.equals(newSpaceCopy.unboxedInt, 1);
+    Expect.equals(newSpaceCopy.unboxedDouble, 1.1);
+    Expect.equals(newSpaceCopy.slot, 'foobar');
+  }
+
   Future testMapRehash() async {
     print('testMapRehash');
     final obj = Object();
@@ -437,7 +496,7 @@
 
   Future testMapRehash3() async {
     print('testMapRehash3');
-    final obj = HashIncrementer();
+    final obj = const HashIncrementer();
     final graph = [
       {obj: 42},
       notAllocatableInTLAB,
@@ -448,6 +507,57 @@
     Expect.equals(before + 1, after);
   }
 
+  Future testSetRehash() async {
+    print('testSetRehash');
+    final obj = Object();
+    final graph = <dynamic>[
+      <dynamic>{42, obj},
+      notAllocatableInTLAB,
+    ];
+    final result = await sendReceive(graph);
+    final setCopy = result[0] as Set<dynamic>;
+    Expect.equals(2, setCopy.length);
+    Expect.equals(42, setCopy.toList()[0]);
+    Expect.equals(obj.runtimeType, setCopy.toList()[1].runtimeType);
+    Expect.notIdentical(obj, setCopy.toList()[1]);
+    Expect.notEquals(
+        identityHashCode(obj), identityHashCode(setCopy.toList()[1]));
+    Expect.isFalse(setCopy.contains(obj));
+    Expect.isTrue(setCopy.contains(setCopy.toList()[1]));
+  }
+
+  Future testSetRehash2() async {
+    print('testSetRehash2');
+    final obj = Object();
+    final graph = <dynamic>[
+      notAllocatableInTLAB,
+      <dynamic>{42, obj},
+    ];
+    final result = await sendReceive(graph);
+    final setCopy = result[1] as Set<dynamic>;
+    Expect.equals(2, setCopy.length);
+    Expect.equals(42, setCopy.toList()[0]);
+    Expect.equals(obj.runtimeType, setCopy.toList()[1].runtimeType);
+    Expect.notIdentical(obj, setCopy.toList()[1]);
+    Expect.notEquals(
+        identityHashCode(obj), identityHashCode(setCopy.toList()[1]));
+    Expect.isFalse(setCopy.contains(obj));
+    Expect.isTrue(setCopy.contains(setCopy.toList()[1]));
+  }
+
+  Future testSetRehash3() async {
+    print('testSetRehash3');
+    final obj = const HashIncrementer();
+    final graph = [
+      {42, obj},
+      notAllocatableInTLAB,
+    ];
+    final int before = HashIncrementer.counter;
+    await sendReceive(graph);
+    final int after = HashIncrementer.counter;
+    Expect.equals(before + 1, after);
+  }
+
   Future testFastOnly() async {
     print('testFastOnly');
     for (final smallPrimitive in smallPrimitives) {
@@ -469,12 +579,6 @@
           await sendReceive([notAllocatableInTLAB, smallContainer]));
     }
   }
-
-  Future<T> sendReceive<T>(T graph) async {
-    sendPort.send(graph);
-    Expect.isTrue(await si.moveNext());
-    return si.current as T;
-  }
 }
 
 main() async {
diff --git a/runtime/tests/vm/dart_2/isolates/fast_object_copy2_test.dart b/runtime/tests/vm/dart_2/isolates/fast_object_copy2_test.dart
new file mode 100644
index 0000000..5dce054
--- /dev/null
+++ b/runtime/tests/vm/dart_2/isolates/fast_object_copy2_test.dart
@@ -0,0 +1,75 @@
+// 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation
+
+// The tests in this file will only succeed when isolate groups are enabled
+// (hence the VMOptions above).
+
+import 'dart:async';
+import 'dart:isolate';
+import 'dart:typed_data';
+
+import 'package:expect/expect.dart';
+
+import 'fast_object_copy_test.dart'
+    show UserObject, SendReceiveTestBase, notAllocatableInTLAB;
+
+// When running with isolate groups enabled, we can share all of the following
+// objects.
+final sharableObjects = [
+  1,
+  0xffffffffffffffff,
+  'foobar',
+  const UserObject(1, 1.2, ''),
+  (() {
+    final rp = ReceivePort();
+    final sp = rp.sendPort;
+    rp.close();
+    return sp;
+  })(),
+  const [1, 2, 3],
+  const {1: 1, 2: 2, 3: 2},
+  const {1, 2, 3},
+  RegExp('a'),
+  Isolate.current.pauseCapability,
+  Int32x4(1, 2, 3, 4),
+];
+
+class SendReceiveTest extends SendReceiveTestBase {
+  Future runTests() async {
+    await testSharable();
+    await testSharable2();
+  }
+
+  Future testSharable() async {
+    final sharableObjectsCopy = await sendReceive([
+      ...sharableObjects,
+    ]);
+    Expect.notIdentical(sharableObjects, sharableObjectsCopy);
+    for (int i = 0; i < sharableObjects.length; ++i) {
+      Expect.identical(sharableObjects[i], sharableObjectsCopy[i]);
+    }
+  }
+
+  Future testSharable2() async {
+    final sharableObjectsCopy = await sendReceive([
+      notAllocatableInTLAB,
+      ...sharableObjects,
+    ]);
+    Expect.notIdentical(sharableObjects, sharableObjectsCopy);
+    Expect.equals(
+        notAllocatableInTLAB[0], (sharableObjectsCopy[0] as Uint8List)[0]);
+    for (int i = 0; i < sharableObjects.length; ++i) {
+      Expect.identical(sharableObjects[i], sharableObjectsCopy[i + 1]);
+    }
+  }
+}
+
+main() async {
+  await SendReceiveTest().run();
+}
diff --git a/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart b/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart
index 9737462..d27b474 100644
--- a/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart
+++ b/runtime/tests/vm/dart_2/isolates/fast_object_copy_test.dart
@@ -3,7 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 
 // VMOptions=
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation --verify-store-buffer
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation --verify-store-buffer
 
 // The tests in this file are particularly for an implementation that tries to
 // allocate the entire graph in BFS order using a fast new space allocation
@@ -28,7 +31,7 @@
 final Uint8List largeInternalTypedData = Uint8List(20 * 1024 * 1024)..[0] = 42;
 
 final Uint8List smallExternalTypedData =
-    File(Platform.script.toFilePath()).readAsBytesSync();
+    File(Platform.script.toFilePath()).readAsBytesSync()..[0] = 21;
 final Uint8List smallExternalTypedDataView =
     Uint8List.view(smallExternalTypedData.buffer, 1, 1);
 
@@ -110,6 +113,17 @@
     }
     return;
   }
+  if (a is Set) {
+    final cb = b as Set;
+    Expect.equals(a.length, cb.length);
+    final aKeys = a.toList();
+    final cbKeys = cb.toList();
+    for (int i = 0; i < a.length; ++i) {
+      expectGraphsMatch(aKeys[i], cbKeys[i]);
+    }
+    return;
+  }
+
   throw 'Unexpected object encountered when matching object graphs $a / $b';
 }
 
@@ -126,6 +140,8 @@
 class HashIncrementer {
   static int counter = 1;
 
+  const HashIncrementer();
+
   int get hashCode => counter++;
   bool operator ==(other) => identical(this, other);
 }
@@ -135,21 +151,39 @@
   final double unboxedDouble;
   final dynamic slot;
 
-  UserObject(this.unboxedInt, this.unboxedDouble, this.slot);
+  const UserObject(this.unboxedInt, this.unboxedDouble, this.slot);
 }
 
-class SendReceiveTest {
+abstract class SendReceiveTestBase {
   ReceivePort receivePort;
   SendPort sendPort;
   StreamIterator si;
 
-  SendReceiveTest();
+  SendReceiveTestBase();
 
   Future run() async {
     receivePort = ReceivePort();
     sendPort = receivePort.sendPort;
     si = StreamIterator(receivePort);
 
+    await runTests();
+
+    si.cancel();
+    receivePort.close();
+    print('done');
+  }
+
+  Future runTests();
+
+  Future<T> sendReceive<T>(T graph) async {
+    sendPort.send(graph);
+    Expect.isTrue(await si.moveNext());
+    return si.current as T;
+  }
+}
+
+class SendReceiveTest extends SendReceiveTestBase {
+  Future runTests() async {
     await testTransferrable();
     await testTransferrable2();
     await testTransferrable3();
@@ -161,6 +195,7 @@
     await testExternalTypedData3();
     await testExternalTypedData4();
     await testExternalTypedData5();
+    await testExternalTypedData6();
 
     await testInternalTypedDataView();
     await testInternalTypedDataView2();
@@ -172,16 +207,18 @@
     await testExternalTypedDataView3();
     await testExternalTypedDataView4();
 
+    await testArray();
+
     await testMapRehash();
     await testMapRehash2();
     await testMapRehash3();
 
+    await testSetRehash();
+    await testSetRehash2();
+    await testSetRehash3();
+
     await testFastOnly();
     await testSlowOnly();
-
-    si.cancel();
-    receivePort.close();
-    print('done');
   }
 
   Future testTransferrable() async {
@@ -293,6 +330,16 @@
     }
   }
 
+  Future testExternalTypedData6() async {
+    print('testExternalTypedData6');
+    final etd = await sendReceive([
+      smallExternalTypedData,
+      largeExternalTypedData,
+    ]);
+    Expect.equals(21, etd[0][0]);
+    Expect.equals(42, etd[1][0]);
+  }
+
   Future testInternalTypedDataView() async {
     print('testInternalTypedDataView');
     final graph = [
@@ -401,6 +448,18 @@
     expectGraphsMatch(graph, copiedGraph);
   }
 
+  Future testArray() async {
+    print('testArray');
+    final oldSpace = List<dynamic>.filled(1024 * 1024, null);
+    final newSpace = UserObject(1, 1.1, 'foobar');
+    oldSpace[0] = newSpace;
+    final oldSpaceCopy = await sendReceive(oldSpace);
+    final newSpaceCopy = oldSpaceCopy[0] as UserObject;
+    Expect.equals(newSpaceCopy.unboxedInt, 1);
+    Expect.equals(newSpaceCopy.unboxedDouble, 1.1);
+    Expect.equals(newSpaceCopy.slot, 'foobar');
+  }
+
   Future testMapRehash() async {
     print('testMapRehash');
     final obj = Object();
@@ -437,7 +496,7 @@
 
   Future testMapRehash3() async {
     print('testMapRehash3');
-    final obj = HashIncrementer();
+    final obj = const HashIncrementer();
     final graph = [
       {obj: 42},
       notAllocatableInTLAB,
@@ -448,6 +507,57 @@
     Expect.equals(before + 1, after);
   }
 
+  Future testSetRehash() async {
+    print('testSetRehash');
+    final obj = Object();
+    final graph = <dynamic>[
+      <dynamic>{42, obj},
+      notAllocatableInTLAB,
+    ];
+    final result = await sendReceive(graph);
+    final setCopy = result[0] as Set<dynamic>;
+    Expect.equals(2, setCopy.length);
+    Expect.equals(42, setCopy.toList()[0]);
+    Expect.equals(obj.runtimeType, setCopy.toList()[1].runtimeType);
+    Expect.notIdentical(obj, setCopy.toList()[1]);
+    Expect.notEquals(
+        identityHashCode(obj), identityHashCode(setCopy.toList()[1]));
+    Expect.isFalse(setCopy.contains(obj));
+    Expect.isTrue(setCopy.contains(setCopy.toList()[1]));
+  }
+
+  Future testSetRehash2() async {
+    print('testSetRehash2');
+    final obj = Object();
+    final graph = <dynamic>[
+      notAllocatableInTLAB,
+      <dynamic>{42, obj},
+    ];
+    final result = await sendReceive(graph);
+    final setCopy = result[1] as Set<dynamic>;
+    Expect.equals(2, setCopy.length);
+    Expect.equals(42, setCopy.toList()[0]);
+    Expect.equals(obj.runtimeType, setCopy.toList()[1].runtimeType);
+    Expect.notIdentical(obj, setCopy.toList()[1]);
+    Expect.notEquals(
+        identityHashCode(obj), identityHashCode(setCopy.toList()[1]));
+    Expect.isFalse(setCopy.contains(obj));
+    Expect.isTrue(setCopy.contains(setCopy.toList()[1]));
+  }
+
+  Future testSetRehash3() async {
+    print('testSetRehash3');
+    final obj = const HashIncrementer();
+    final graph = [
+      {42, obj},
+      notAllocatableInTLAB,
+    ];
+    final int before = HashIncrementer.counter;
+    await sendReceive(graph);
+    final int after = HashIncrementer.counter;
+    Expect.equals(before + 1, after);
+  }
+
   Future testFastOnly() async {
     print('testFastOnly');
     for (final smallPrimitive in smallPrimitives) {
@@ -469,12 +579,6 @@
           await sendReceive([notAllocatableInTLAB, smallContainer]));
     }
   }
-
-  Future<T> sendReceive<T>(T graph) async {
-    sendPort.send(graph);
-    Expect.isTrue(await si.moveNext());
-    return si.current as T;
-  }
 }
 
 main() async {
diff --git a/runtime/vm/class_table.cc b/runtime/vm/class_table.cc
index ccba236..b5491bb 100644
--- a/runtime/vm/class_table.cc
+++ b/runtime/vm/class_table.cc
@@ -519,10 +519,17 @@
 
   // This is called by snapshot reader and class finalizer.
   ASSERT(cid < capacity_);
+  UpdateClassSize(cid, raw_cls);
+  table_.load()[cid] = raw_cls;
+}
+
+void ClassTable::UpdateClassSize(intptr_t cid, ClassPtr raw_cls) {
+  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
+  ASSERT(!IsTopLevelCid(cid));  // "top-level" classes don't get instantiated
+  ASSERT(cid < capacity_);
   const intptr_t size =
       raw_cls == nullptr ? 0 : Class::host_instance_size(raw_cls);
   shared_class_table_->SetSizeAt(cid, size);
-  table_.load()[cid] = raw_cls;
 }
 
 #ifndef PRODUCT
diff --git a/runtime/vm/class_table.h b/runtime/vm/class_table.h
index 5ef0df1..e89b978 100644
--- a/runtime/vm/class_table.h
+++ b/runtime/vm/class_table.h
@@ -354,6 +354,7 @@
   }
 
   void SetAt(intptr_t index, ClassPtr raw_cls);
+  void UpdateClassSize(intptr_t cid, ClassPtr raw_cls);
 
   bool IsValidIndex(intptr_t cid) const {
     if (IsTopLevelCid(cid)) {
diff --git a/runtime/vm/clustered_snapshot.cc b/runtime/vm/clustered_snapshot.cc
index dfd02fa..b289ca1 100644
--- a/runtime/vm/clustered_snapshot.cc
+++ b/runtime/vm/clustered_snapshot.cc
@@ -5724,7 +5724,10 @@
 
   void PostLoad(Deserializer* d, const Array& refs) {
     auto isolate_group = d->isolate_group();
-    isolate_group->class_table()->CopySizesFromClassObjects();
+    {
+      SafepointWriteRwLocker ml(d->thread(), isolate_group->program_lock());
+      isolate_group->class_table()->CopySizesFromClassObjects();
+    }
     d->heap()->old_space()->EvaluateAfterLoading();
 
     const Array& units =
@@ -7800,6 +7803,7 @@
 
     {
       TIMELINE_DURATION(thread(), Isolate, "ReadFill");
+      SafepointWriteRwLocker ml(thread(), isolate_group()->program_lock());
       for (intptr_t i = 0; i < num_clusters_; i++) {
         TIMELINE_DURATION(thread(), Isolate, clusters_[i]->name());
         clusters_[i]->ReadFill(this, primary);
diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc
index 5be85a1..4bbf74e 100644
--- a/runtime/vm/isolate_reload.cc
+++ b/runtime/vm/isolate_reload.cc
@@ -1867,9 +1867,13 @@
       saved_class_table_.load(std::memory_order_relaxed);
   ClassPtr* local_saved_tlc_class_table =
       saved_tlc_class_table_.load(std::memory_order_relaxed);
-  IG->class_table()->ResetAfterHotReload(
-      local_saved_class_table, local_saved_tlc_class_table, saved_num_cids_,
-      saved_num_tlc_cids_, is_rollback);
+  {
+    auto thread = Thread::Current();
+    SafepointWriteRwLocker sl(thread, thread->isolate_group()->program_lock());
+    IG->class_table()->ResetAfterHotReload(
+        local_saved_class_table, local_saved_tlc_class_table, saved_num_cids_,
+        saved_num_tlc_cids_, is_rollback);
+  }
   saved_class_table_.store(nullptr, std::memory_order_release);
   saved_tlc_class_table_.store(nullptr, std::memory_order_release);
 }
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 7c9214a..77f8e4d 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -4023,8 +4023,11 @@
     // fields.
     const auto host_bitmap = CalculateFieldOffsets();
     if (ptr() == isolate_group->class_table()->At(id())) {
-      // Sets the new size in the class table.
-      isolate_group->class_table()->SetAt(id(), ptr());
+      if (!ClassTable::IsTopLevelCid(id())) {
+        // Unless class is top-level, which don't get instantiated,
+        // sets the new size in the class table.
+        isolate_group->class_table()->UpdateClassSize(id(), ptr());
+      }
       if (FLAG_precompiled_mode && !ClassTable::IsTopLevelCid(id())) {
         isolate_group->shared_class_table()->SetUnboxedFieldsMapAt(id(),
                                                                    host_bitmap);
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index f6d88ea..d46d8b4 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -620,6 +620,8 @@
   };
 
  protected:
+  friend ObjectPtr AllocateObject(intptr_t, intptr_t);
+
   // Used for extracting the C++ vtable during bringup.
   Object() : ptr_(null_) {}
 
@@ -1511,6 +1513,9 @@
   void set_num_native_fields(uint16_t value) const {
     StoreNonPointer(&untag()->num_native_fields_, value);
   }
+  static uint16_t NumNativeFieldsOf(ClassPtr clazz) {
+    return clazz->untag()->num_native_fields_;
+  }
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
   CodePtr allocation_stub() const { return untag()->allocation_stub(); }
@@ -8978,6 +8983,11 @@
   static ClassPtr Class();
 
   static intptr_t Value(const SmiPtr raw_smi) { return RawSmiValue(raw_smi); }
+#if defined(DART_COMPRESSED_POINTERS)
+  static intptr_t Value(const CompressedSmiPtr raw_smi) {
+    return Smi::Value(static_cast<SmiPtr>(raw_smi.DecompressSmi()));
+  }
+#endif
 
   static intptr_t RawValue(intptr_t value) {
     return static_cast<intptr_t>(New(value));
@@ -11221,6 +11231,9 @@
   static intptr_t function_offset() {
     return OFFSET_OF(UntaggedClosure, function_);
   }
+  static FunctionPtr FunctionOf(ClosurePtr closure) {
+    return closure.untag()->function();
+  }
 
 #if defined(DART_PRECOMPILER)
   FunctionTypePtr signature() const {
@@ -11237,6 +11250,9 @@
   static intptr_t context_offset() {
     return OFFSET_OF(UntaggedClosure, context_);
   }
+  static ContextPtr ContextOf(ClosurePtr closure) {
+    return closure.untag()->context();
+  }
 
   bool IsGeneric(Thread* thread) const { return NumTypeParameters(thread) > 0; }
   intptr_t NumTypeParameters(Thread* thread) const;
diff --git a/runtime/vm/object_graph_copy.cc b/runtime/vm/object_graph_copy.cc
new file mode 100644
index 0000000..96e6771
--- /dev/null
+++ b/runtime/vm/object_graph_copy.cc
@@ -0,0 +1,1677 @@
+// 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.
+
+#include "vm/object_graph_copy.h"
+#include "vm/dart_api_state.h"
+#include "vm/flags.h"
+#include "vm/heap/weak_table.h"
+#include "vm/longjump.h"
+#include "vm/object.h"
+#include "vm/snapshot.h"
+#include "vm/symbols.h"
+
+#define Z zone_
+
+// The list here contains two kinds of classes of objects
+//   * objects that will be shared and we will therefore never need to copy
+//   * objects that user object graphs should never reference
+#define FOR_UNSUPPORTED_CLASSES(V)                                             \
+  V(AbstractType)                                                              \
+  V(ApiError)                                                                  \
+  V(Bool)                                                                      \
+  V(CallSiteData)                                                              \
+  V(Capability)                                                                \
+  V(Class)                                                                     \
+  V(ClosureData)                                                               \
+  V(Code)                                                                      \
+  V(CodeSourceMap)                                                             \
+  V(CompressedStackMaps)                                                       \
+  V(Context)                                                                   \
+  V(ContextScope)                                                              \
+  V(DynamicLibrary)                                                            \
+  V(Error)                                                                     \
+  V(ExceptionHandlers)                                                         \
+  V(FfiTrampolineData)                                                         \
+  V(Field)                                                                     \
+  V(Function)                                                                  \
+  V(FunctionType)                                                              \
+  V(FutureOr)                                                                  \
+  V(ICData)                                                                    \
+  V(Instance)                                                                  \
+  V(Instructions)                                                              \
+  V(InstructionsSection)                                                       \
+  V(InstructionsTable)                                                         \
+  V(Int32x4)                                                                   \
+  V(Integer)                                                                   \
+  V(KernelProgramInfo)                                                         \
+  V(LanguageError)                                                             \
+  V(Library)                                                                   \
+  V(LibraryPrefix)                                                             \
+  V(LoadingUnit)                                                               \
+  V(LocalVarDescriptors)                                                       \
+  V(MegamorphicCache)                                                          \
+  V(Mint)                                                                      \
+  V(MirrorReference)                                                           \
+  V(MonomorphicSmiableCall)                                                    \
+  V(Namespace)                                                                 \
+  V(Number)                                                                    \
+  V(ObjectPool)                                                                \
+  V(PatchClass)                                                                \
+  V(PcDescriptors)                                                             \
+  V(Pointer)                                                                   \
+  V(ReceivePort)                                                               \
+  V(RegExp)                                                                    \
+  V(Script)                                                                    \
+  V(Sentinel)                                                                  \
+  V(SendPort)                                                                  \
+  V(SingleTargetCache)                                                         \
+  V(Smi)                                                                       \
+  V(StackTrace)                                                                \
+  V(SubtypeTestCache)                                                          \
+  V(Type)                                                                      \
+  V(TypeArguments)                                                             \
+  V(TypeParameter)                                                             \
+  V(TypeParameters)                                                            \
+  V(TypeRef)                                                                   \
+  V(TypedDataBase)                                                             \
+  V(UnhandledException)                                                        \
+  V(UnlinkedCall)                                                              \
+  V(UnwindError)                                                               \
+  V(UserTag)                                                                   \
+  V(WeakProperty)                                                              \
+  V(WeakSerializationReference)
+
+namespace dart {
+
+DEFINE_FLAG(bool,
+            enable_fast_object_copy,
+            true,
+            "Enable fast path for fast object copy.");
+DEFINE_FLAG(bool,
+            gc_on_foc_slow_path,
+            false,
+            "Cause a GC when falling off the fast path for fast object copy.");
+
+const char* kFastAllocationFailed = "fast allocation failed";
+
+struct PtrTypes {
+  using Object = ObjectPtr;
+  static const dart::UntaggedObject* UntagObject(Object arg) {
+    return arg.untag();
+  }
+  static const dart::ObjectPtr GetObjectPtr(Object arg) { return arg; }
+  static const dart::Object& HandlifyObject(ObjectPtr arg) {
+    return dart::Object::Handle(arg);
+  }
+
+#define DO(V)                                                                  \
+  using V = V##Ptr;                                                            \
+  static Untagged##V* Untag##V(V##Ptr arg) { return arg.untag(); }             \
+  static V##Ptr Get##V##Ptr(V##Ptr arg) { return arg; }                        \
+  static V##Ptr Cast##V(ObjectPtr arg) { return dart::V::RawCast(arg); }
+  CLASS_LIST_FOR_HANDLES(DO)
+#undef DO
+};
+
+struct HandleTypes {
+  using Object = const Object&;
+  static const dart::UntaggedObject* UntagObject(Object arg) {
+    return arg.ptr().untag();
+  }
+  static dart::ObjectPtr GetObjectPtr(Object arg) { return arg.ptr(); }
+  static Object HandlifyObject(Object arg) { return arg; }
+
+#define DO(V)                                                                  \
+  using V = const V&;                                                          \
+  static Untagged##V* Untag##V(V arg) { return arg.ptr().untag(); }            \
+  static V##Ptr Get##V##Ptr(V arg) { return arg.ptr(); }                       \
+  static V Cast##V(const dart::Object& arg) { return dart::V::Cast(arg); }
+  CLASS_LIST_FOR_HANDLES(DO)
+#undef DO
+};
+
+DART_FORCE_INLINE
+static ObjectPtr Marker() {
+  return Object::unknown_constant().ptr();
+}
+
+DART_FORCE_INLINE
+static bool CanShareObject(uword tags) {
+  if ((tags & UntaggedObject::CanonicalBit::mask_in_place()) != 0) {
+    return true;
+  }
+  const auto cid = UntaggedObject::ClassIdTag::decode(tags);
+  if (cid == kOneByteStringCid) return true;
+  if (cid == kTwoByteStringCid) return true;
+  if (cid == kExternalOneByteStringCid) return true;
+  if (cid == kExternalTwoByteStringCid) return true;
+  if (cid == kMintCid) return true;
+  if (cid == kImmutableArrayCid) return true;
+  if (cid == kNeverCid) return true;
+  if (cid == kSentinelCid) return true;
+#if defined(DART_PRECOMPILED_RUNTIME)
+  // In JIT mode we have field guards enabled which means
+  // double/float32x4/float64x2 boxes can be mutable and we therefore cannot
+  // share them.
+  if (cid == kDoubleCid || cid == kFloat32x4Cid || cid == kFloat64x2Cid) {
+    return true;
+  }
+#endif
+  if (cid == kInt32x4Cid) return true;  // No field guards here.
+  if (cid == kSendPortCid) return true;
+  if (cid == kCapabilityCid) return true;
+  if (cid == kRegExpCid) return true;
+
+  return false;
+}
+
+// Whether executing `get:hashCode` (possibly in a different isolate) on an
+// object with the given [tags] might return a different answer than the source
+// object (if copying is needed) or on the same object (if the object is
+// shared).
+DART_FORCE_INLINE
+static bool MightNeedReHashing(ObjectPtr object) {
+  const uword tags = TagsFromUntaggedObject(object.untag());
+  const auto cid = UntaggedObject::ClassIdTag::decode(tags);
+  // These use structural hash codes and will therefore always result in the
+  // same hash codes.
+  if (cid == kOneByteStringCid) return false;
+  if (cid == kTwoByteStringCid) return false;
+  if (cid == kExternalOneByteStringCid) return false;
+  if (cid == kExternalTwoByteStringCid) return false;
+  if (cid == kMintCid) return false;
+  if (cid == kDoubleCid) return false;
+  if (cid == kSendPortCid) return false;
+  if (cid == kCapabilityCid) return false;
+
+  // These are shared and use identity hash codes. If they are used as a key in
+  // a map or a value in a set, they will already have the identity hash code
+  // set.
+  if (cid == kImmutableArrayCid) return false;
+  if (cid == kRegExpCid) return false;
+  if (cid == kInt32x4Cid) return false;
+
+  // We copy those (instead of sharing them) - see [CanShareObjct]. They rely
+  // on the default hashCode implementation which uses identity hash codes
+  // (instead of structural hash code).
+  if (cid == kFloat32x4Cid || cid == kFloat64x2Cid) {
+    return !kDartPrecompiledRuntime;
+  }
+
+  // If the [tags] indicates this is a canonical object we'll share it instead
+  // of copying it. That would suggest we don't have to re-hash maps/sets
+  // containing this object on the receiver side.
+  //
+  // Though the object can be a constant of a user-defined class with a
+  // custom hash code that is misbehaving (e.g one that depends on global field
+  // state, ...). To be on the safe side we'll force re-hashing if such objects
+  // are encountered in maps/sets.
+  //
+  // => We might want to consider changing the implementation to avoid rehashing
+  // in such cases in the future and disambiguate the documentation.
+  return true;
+}
+
+DART_FORCE_INLINE
+uword TagsFromUntaggedObject(UntaggedObject* obj) {
+  return obj->tags_;
+}
+
+DART_FORCE_INLINE
+void SetNewSpaceTaggingWord(ObjectPtr to, classid_t cid, uint32_t size) {
+  uword tags = 0;
+
+  tags = UntaggedObject::SizeTag::update(size, tags);
+  tags = UntaggedObject::ClassIdTag::update(cid, tags);
+  tags = UntaggedObject::OldBit::update(false, tags);
+  tags = UntaggedObject::OldAndNotMarkedBit::update(false, tags);
+  tags = UntaggedObject::OldAndNotRememberedBit::update(false, tags);
+  tags = UntaggedObject::CanonicalBit::update(false, tags);
+  tags = UntaggedObject::NewBit::update(true, tags);
+#if defined(HASH_IN_OBJECT_HEADER)
+  tags = UntaggedObject::HashTag::update(0, tags);
+#endif
+  to.untag()->tags_ = tags;
+}
+
+DART_FORCE_INLINE
+ObjectPtr AllocateObject(intptr_t cid, intptr_t size) {
+#if defined(DART_COMPRESSED_POINTERS)
+  const bool compressed = true;
+#else
+  const bool compressed = false;
+#endif
+  return Object::Allocate(cid, size, Heap::kNew, compressed);
+}
+
+DART_FORCE_INLINE
+void UpdateLengthField(intptr_t cid, ObjectPtr from, ObjectPtr to) {
+  // We share these objects - never copy them.
+  ASSERT(!IsStringClassId(cid));
+  ASSERT(cid != kImmutableArrayCid);
+
+  // We update any in-heap variable sized object with the length to keep the
+  // length and the size in the object header in-sync for the GC.
+  if (cid == kArrayCid) {
+    static_cast<UntaggedArray*>(to.untag())->length_ =
+        static_cast<UntaggedArray*>(from.untag())->length_;
+  } else if (IsTypedDataClassId(cid)) {
+    static_cast<UntaggedTypedDataBase*>(to.untag())->length_ =
+        static_cast<UntaggedTypedDataBase*>(from.untag())->length_;
+  }
+}
+
+void InitializeExternalTypedData(intptr_t cid,
+                                 ExternalTypedDataPtr from,
+                                 ExternalTypedDataPtr to) {
+  auto raw_from = from.untag();
+  auto raw_to = to.untag();
+  const intptr_t length =
+      TypedData::ElementSizeInBytes(cid) * Smi::Value(raw_from->length_);
+
+  auto buffer = static_cast<uint8_t*>(malloc(length));
+  memmove(buffer, raw_from->data_, length);
+  raw_to->length_ = raw_from->length_;
+  raw_to->data_ = buffer;
+}
+
+void InitializeTypedDataView(TypedDataViewPtr obj) {
+  obj.untag()->typed_data_ = TypedDataBase::null();
+  obj.untag()->offset_in_bytes_ = 0;
+  obj.untag()->length_ = 0;
+}
+
+void FreeExternalTypedData(void* isolate_callback_data, void* buffer) {
+  free(buffer);
+}
+
+void FreeTransferablePeer(void* isolate_callback_data, void* peer) {
+  delete static_cast<TransferableTypedDataPeer*>(peer);
+}
+
+class ForwardMapBase {
+ public:
+  explicit ForwardMapBase(Thread* thread)
+      : thread_(thread), zone_(thread->zone()), isolate_(thread->isolate()) {}
+
+ protected:
+  friend class ObjectGraphCopier;
+
+  intptr_t GetObjectId(ObjectPtr object) {
+    if (object->IsNewObject()) {
+      return isolate_->forward_table_new()->GetValueExclusive(object);
+    } else {
+      return isolate_->forward_table_old()->GetValueExclusive(object);
+    }
+  }
+  void SetObjectId(ObjectPtr object, intptr_t id) {
+    if (object->IsNewObject()) {
+      isolate_->forward_table_new()->SetValueExclusive(object, id);
+    } else {
+      isolate_->forward_table_old()->SetValueExclusive(object, id);
+    }
+  }
+
+  void FinalizeTransferable(const TransferableTypedData& from,
+                            const TransferableTypedData& to) {
+    // Get the old peer.
+    auto fpeer = static_cast<TransferableTypedDataPeer*>(
+        thread_->heap()->GetPeer(from.ptr()));
+    ASSERT(fpeer != nullptr && fpeer->data() != nullptr);
+    const intptr_t length = fpeer->length();
+
+    // Allocate new peer object with (data, length).
+    auto tpeer = new TransferableTypedDataPeer(fpeer->data(), length);
+    thread_->heap()->SetPeer(to.ptr(), tpeer);
+
+    // Move the handle itself to the new object.
+    fpeer->handle()->EnsureFreedExternal(thread_->isolate_group());
+    tpeer->set_handle(FinalizablePersistentHandle::New(
+        thread_->isolate_group(), to, tpeer, FreeTransferablePeer, length,
+        /*auto_delete=*/true));
+    fpeer->ClearData();
+  }
+
+  void FinalizeExternalTypedData(const ExternalTypedData& to) {
+    to.AddFinalizer(to.DataAddr(0), &FreeExternalTypedData, to.LengthInBytes());
+  }
+
+  Thread* thread_;
+  Zone* zone_;
+  Isolate* isolate_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ForwardMapBase);
+};
+
+class FastForwardMap : public ForwardMapBase {
+ public:
+  explicit FastForwardMap(Thread* thread)
+      : ForwardMapBase(thread),
+        raw_from_to_(thread->zone(), 20),
+        raw_transferables_from_to_(thread->zone(), 0),
+        raw_objects_to_rehash_(thread->zone(), 0) {
+    raw_from_to_.Resize(2);
+    raw_from_to_[0] = Object::null();
+    raw_from_to_[1] = Object::null();
+    fill_cursor_ = 2;
+  }
+
+  ObjectPtr ForwardedObject(ObjectPtr object) {
+    const intptr_t id = GetObjectId(object);
+    if (id == 0) return Marker();
+    return raw_from_to_[id + 1];
+  }
+
+  void Insert(ObjectPtr from, ObjectPtr to) {
+    ASSERT(ForwardedObject(from) == Marker());
+    ASSERT(raw_from_to_.length() == raw_from_to_.length());
+    const auto id = raw_from_to_.length();
+    SetObjectId(from, id);
+    raw_from_to_.Resize(id + 2);
+    raw_from_to_[id] = from;
+    raw_from_to_[id + 1] = to;
+  }
+
+  void AddTransferable(TransferableTypedDataPtr from,
+                       TransferableTypedDataPtr to) {
+    raw_transferables_from_to_.Add(from);
+    raw_transferables_from_to_.Add(to);
+  }
+  void AddExternalTypedData(ExternalTypedDataPtr to) {
+    raw_external_typed_data_to_.Add(to);
+  }
+
+  void AddObjectToRehash(ObjectPtr to) { raw_objects_to_rehash_.Add(to); }
+
+ private:
+  friend class FastObjectCopy;
+  friend class ObjectGraphCopier;
+
+  GrowableArray<ObjectPtr> raw_from_to_;
+  GrowableArray<TransferableTypedDataPtr> raw_transferables_from_to_;
+  GrowableArray<ExternalTypedDataPtr> raw_external_typed_data_to_;
+  GrowableArray<ObjectPtr> raw_objects_to_rehash_;
+  intptr_t fill_cursor_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(FastForwardMap);
+};
+
+class SlowForwardMap : public ForwardMapBase {
+ public:
+  explicit SlowForwardMap(Thread* thread)
+      : ForwardMapBase(thread),
+        from_to_(thread->zone(), 20),
+        transferables_from_to_(thread->zone(), 0) {
+    from_to_.Resize(2);
+    from_to_[0] = &Object::null_object();
+    from_to_[1] = &Object::null_object();
+    fill_cursor_ = 2;
+  }
+
+  ObjectPtr ForwardedObject(ObjectPtr object) {
+    const intptr_t id = GetObjectId(object);
+    if (id == 0) return Marker();
+    return from_to_[id + 1]->ptr();
+  }
+
+  void Insert(ObjectPtr from, ObjectPtr to) {
+    ASSERT(ForwardedObject(from) == Marker());
+    const auto id = from_to_.length();
+    SetObjectId(from, id);
+    from_to_.Resize(id + 2);
+    from_to_[id] = &Object::Handle(Z, from);
+    from_to_[id + 1] = &Object::Handle(Z, to);
+  }
+
+  void AddTransferable(const TransferableTypedData& from,
+                       const TransferableTypedData& to) {
+    transferables_from_to_.Add(&TransferableTypedData::Handle(from.ptr()));
+    transferables_from_to_.Add(&TransferableTypedData::Handle(to.ptr()));
+  }
+
+  void AddExternalTypedData(ExternalTypedDataPtr to) {
+    external_typed_data_.Add(&ExternalTypedData::Handle(to));
+  }
+
+  void AddObjectToRehash(const Object& to) {
+    objects_to_rehash_.Add(&Object::Handle(to.ptr()));
+  }
+
+  void FinalizeTransferables() {
+    for (intptr_t i = 0; i < transferables_from_to_.length(); i += 2) {
+      auto from = transferables_from_to_[i];
+      auto to = transferables_from_to_[i + 1];
+      FinalizeTransferable(*from, *to);
+    }
+  }
+
+  void FinalizeExternalTypedData() {
+    for (intptr_t i = 0; i < external_typed_data_.length(); i++) {
+      auto to = external_typed_data_[i];
+      ForwardMapBase::FinalizeExternalTypedData(*to);
+    }
+  }
+
+ private:
+  friend class SlowObjectCopy;
+  friend class ObjectGraphCopier;
+
+  GrowableArray<const Object*> from_to_;
+  GrowableArray<const TransferableTypedData*> transferables_from_to_;
+  GrowableArray<const ExternalTypedData*> external_typed_data_;
+  GrowableArray<const Object*> objects_to_rehash_;
+  intptr_t fill_cursor_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(SlowForwardMap);
+};
+
+class ObjectCopyBase {
+ public:
+  explicit ObjectCopyBase(Thread* thread)
+      : thread_(thread),
+        heap_base_(thread->heap_base()),
+        zone_(thread->zone()),
+        heap_(thread->isolate_group()->heap()),
+        class_table_(thread->isolate_group()->class_table()),
+        new_space_(heap_->new_space()),
+        tmp_(Object::Handle(thread->zone())) {}
+  ~ObjectCopyBase() {}
+
+ protected:
+  static ObjectPtr LoadPointer(ObjectPtr src, intptr_t offset) {
+    return src.untag()->LoadPointer(reinterpret_cast<ObjectPtr*>(
+        reinterpret_cast<uint8_t*>(src.untag()) + offset));
+  }
+  static CompressedObjectPtr LoadCompressedPointer(ObjectPtr src,
+                                                   intptr_t offset) {
+    return src.untag()->LoadPointer(reinterpret_cast<CompressedObjectPtr*>(
+        reinterpret_cast<uint8_t*>(src.untag()) + offset));
+  }
+  static compressed_uword LoadCompressedNonPointerWord(ObjectPtr src,
+                                                       intptr_t offset) {
+    return *reinterpret_cast<compressed_uword*>(
+        reinterpret_cast<uint8_t*>(src.untag()) + offset);
+  }
+  static void StorePointerBarrier(ObjectPtr obj,
+                                  intptr_t offset,
+                                  ObjectPtr value) {
+    obj.untag()->StorePointer(
+        reinterpret_cast<ObjectPtr*>(reinterpret_cast<uint8_t*>(obj.untag()) +
+                                     offset),
+        value);
+  }
+  static void StoreCompressedPointerBarrier(ObjectPtr obj,
+                                            intptr_t offset,
+                                            ObjectPtr value) {
+    obj.untag()->StoreCompressedPointer(
+        reinterpret_cast<CompressedObjectPtr*>(
+            reinterpret_cast<uint8_t*>(obj.untag()) + offset),
+        value);
+  }
+  void StoreCompressedLargeArrayPointerBarrier(ObjectPtr obj,
+                                               intptr_t offset,
+                                               ObjectPtr value) {
+    obj.untag()->StoreCompressedArrayPointer(
+        reinterpret_cast<CompressedObjectPtr*>(
+            reinterpret_cast<uint8_t*>(obj.untag()) + offset),
+        value, thread_);
+  }
+  static void StorePointerNoBarrier(ObjectPtr obj,
+                                    intptr_t offset,
+                                    ObjectPtr value) {
+    *reinterpret_cast<ObjectPtr*>(reinterpret_cast<uint8_t*>(obj.untag()) +
+                                  offset) = value;
+  }
+  template <typename T = ObjectPtr>
+  static void StoreCompressedPointerNoBarrier(ObjectPtr obj,
+                                              intptr_t offset,
+                                              T value) {
+    *reinterpret_cast<CompressedObjectPtr*>(
+        reinterpret_cast<uint8_t*>(obj.untag()) + offset) = value;
+  }
+  static void StoreCompressedNonPointerWord(ObjectPtr obj,
+                                            intptr_t offset,
+                                            compressed_uword value) {
+    *reinterpret_cast<compressed_uword*>(
+        reinterpret_cast<uint8_t*>(obj.untag()) + offset) = value;
+  }
+
+  DART_FORCE_INLINE
+  bool CanCopyObject(uword tags, ObjectPtr object) {
+    const auto cid = UntaggedObject::ClassIdTag::decode(tags);
+    if (cid > kNumPredefinedCids) {
+      const bool has_native_fields =
+          Class::NumNativeFieldsOf(class_table_->At(cid)) != 0;
+      if (has_native_fields) {
+        exception_msg_ =
+            "Illegal argument in isolate message: (object has native fields)";
+        return false;
+      }
+      return true;
+    }
+#define HANDLE_ILLEGAL_CASE(Type)                                              \
+  case k##Type##Cid: {                                                         \
+    exception_msg_ =                                                           \
+        "Illegal argument in isolate message: "                                \
+        "(object is a" #Type ")";                                              \
+    return false;                                                              \
+  }
+
+    switch (cid) {
+      HANDLE_ILLEGAL_CASE(MirrorReference)
+      HANDLE_ILLEGAL_CASE(ReceivePort)
+      HANDLE_ILLEGAL_CASE(StackTrace)
+      HANDLE_ILLEGAL_CASE(UserTag)
+      HANDLE_ILLEGAL_CASE(DynamicLibrary)
+      HANDLE_ILLEGAL_CASE(Pointer)
+      case kClosureCid: {
+        if (!Function::IsImplicitStaticClosureFunction(
+                Closure::FunctionOf(Closure::RawCast(object)))) {
+          exception_msg_ =
+              "Illegal argument in isolate message: (object is a closure)";
+          return false;
+        }
+        ASSERT(Closure::ContextOf(Closure::RawCast(object)) == Object::null());
+        return true;
+      }
+      default:
+        return true;
+    }
+  }
+
+  Thread* thread_;
+  uword heap_base_;
+  Zone* zone_;
+  Heap* heap_;
+  ClassTable* class_table_;
+  Scavenger* new_space_;
+  Object& tmp_;
+
+  const char* exception_msg_ = nullptr;
+};
+
+class FastObjectCopyBase : public ObjectCopyBase {
+ public:
+  using Types = PtrTypes;
+
+  explicit FastObjectCopyBase(Thread* thread)
+      : ObjectCopyBase(thread), fast_forward_map_(thread) {}
+
+ protected:
+  DART_FORCE_INLINE
+  void ForwardCompressedPointers(ObjectPtr src,
+                                 ObjectPtr dst,
+                                 intptr_t offset,
+                                 intptr_t end_offset) {
+    for (; offset < end_offset; offset += kCompressedWordSize) {
+      ForwardCompressedPointer(src, dst, offset);
+    }
+  }
+
+  DART_FORCE_INLINE
+  void ForwardCompressedPointers(ObjectPtr src,
+                                 ObjectPtr dst,
+                                 intptr_t offset,
+                                 intptr_t end_offset,
+                                 UnboxedFieldBitmap bitmap) {
+    if (bitmap.IsEmpty()) {
+      ForwardCompressedPointers(src, dst, offset, end_offset);
+      return;
+    }
+    intptr_t bit = offset >> kCompressedWordSizeLog2;
+    for (; offset < end_offset; offset += kCompressedWordSize) {
+      if (bitmap.Get(bit++)) {
+        StoreCompressedNonPointerWord(
+            dst, offset, LoadCompressedNonPointerWord(src, offset));
+      } else {
+        ForwardCompressedPointer(src, dst, offset);
+      }
+    }
+  }
+
+  void ForwardCompressedArrayPointers(intptr_t array_length,
+                                      ObjectPtr src,
+                                      ObjectPtr dst,
+                                      intptr_t offset,
+                                      intptr_t end_offset) {
+    for (; offset < end_offset; offset += kCompressedWordSize) {
+      ForwardCompressedPointer(src, dst, offset);
+    }
+  }
+
+  DART_FORCE_INLINE
+  void ForwardCompressedPointer(ObjectPtr src, ObjectPtr dst, intptr_t offset) {
+    auto value = LoadCompressedPointer(src, offset);
+    if (!value.IsHeapObject()) {
+      StoreCompressedPointerNoBarrier(dst, offset, value);
+      return;
+    }
+    auto value_decompressed = value.Decompress(heap_base_);
+    const uword tags = TagsFromUntaggedObject(value_decompressed.untag());
+    if (CanShareObject(tags)) {
+      StoreCompressedPointerNoBarrier(dst, offset, value);
+      return;
+    }
+
+    ObjectPtr existing_to =
+        fast_forward_map_.ForwardedObject(value_decompressed);
+    if (existing_to != Marker()) {
+      StoreCompressedPointerNoBarrier(dst, offset, existing_to);
+      return;
+    }
+
+    if (UNLIKELY(!CanCopyObject(tags, value_decompressed))) {
+      ASSERT(exception_msg_ != nullptr);
+      StoreCompressedPointerNoBarrier(dst, offset, Object::null());
+      return;
+    }
+
+    auto to = Forward(tags, value.Decompress(heap_base_));
+    StoreCompressedPointerNoBarrier(dst, offset, to);
+  }
+
+  ObjectPtr Forward(uword tags, ObjectPtr from) {
+    const intptr_t header_size = UntaggedObject::SizeTag::decode(tags);
+    const auto cid = UntaggedObject::ClassIdTag::decode(tags);
+    const uword size =
+        header_size != 0 ? header_size : from.untag()->HeapSize();
+    if (Heap::IsAllocatableInNewSpace(size)) {
+      const uword alloc = new_space_->TryAllocate(thread_, size);
+      if (alloc != 0) {
+        ObjectPtr to(reinterpret_cast<UntaggedObject*>(alloc));
+        fast_forward_map_.Insert(from, to);
+
+        if (IsExternalTypedDataClassId(cid)) {
+          SetNewSpaceTaggingWord(to, cid, header_size);
+          InitializeExternalTypedData(cid, ExternalTypedData::RawCast(from),
+                                      ExternalTypedData::RawCast(to));
+          fast_forward_map_.AddExternalTypedData(
+              ExternalTypedData::RawCast(to));
+        } else if (IsTypedDataViewClassId(cid)) {
+          // We set the views backing store to `null` to satisfy an assertion in
+          // GCCompactor::VisitTypedDataViewPointers().
+          SetNewSpaceTaggingWord(to, cid, header_size);
+          InitializeTypedDataView(TypedDataView::RawCast(to));
+        }
+        return to;
+      }
+    }
+    exception_msg_ = kFastAllocationFailed;
+    return Marker();
+  }
+
+  void EnqueueTransferable(TransferableTypedDataPtr from,
+                           TransferableTypedDataPtr to) {
+    fast_forward_map_.AddTransferable(from, to);
+  }
+  void EnqueueObjectToRehash(ObjectPtr to) {
+    fast_forward_map_.AddObjectToRehash(to);
+  }
+
+  static void StoreCompressedArrayPointers(intptr_t array_length,
+                                           ObjectPtr src,
+                                           ObjectPtr dst,
+                                           intptr_t offset,
+                                           intptr_t end_offset) {
+    StoreCompressedPointers(src, dst, offset, end_offset);
+  }
+  static void StoreCompressedPointers(ObjectPtr src,
+                                      ObjectPtr dst,
+                                      intptr_t offset,
+                                      intptr_t end_offset) {
+    StoreCompressedPointersNoBarrier(src, dst, offset, end_offset);
+  }
+  static void StoreCompressedPointersNoBarrier(ObjectPtr src,
+                                               ObjectPtr dst,
+                                               intptr_t offset,
+                                               intptr_t end_offset) {
+    for (; offset <= end_offset; offset += kCompressedWordSize) {
+      StoreCompressedPointerNoBarrier(dst, offset,
+                                      LoadCompressedPointer(src, offset));
+    }
+  }
+
+ protected:
+  friend class ObjectGraphCopier;
+
+  FastForwardMap fast_forward_map_;
+};
+
+class SlowObjectCopyBase : public ObjectCopyBase {
+ public:
+  using Types = HandleTypes;
+
+  explicit SlowObjectCopyBase(Thread* thread)
+      : ObjectCopyBase(thread), slow_forward_map_(thread) {}
+
+ protected:
+  DART_FORCE_INLINE
+  void ForwardCompressedPointers(const Object& src,
+                                 const Object& dst,
+                                 intptr_t offset,
+                                 intptr_t end_offset) {
+    for (; offset < end_offset; offset += kCompressedWordSize) {
+      ForwardCompressedPointer(src, dst, offset);
+    }
+  }
+
+  DART_FORCE_INLINE
+  void ForwardCompressedPointers(const Object& src,
+                                 const Object& dst,
+                                 intptr_t offset,
+                                 intptr_t end_offset,
+                                 UnboxedFieldBitmap bitmap) {
+    intptr_t bit = offset >> kCompressedWordSizeLog2;
+    for (; offset < end_offset; offset += kCompressedWordSize) {
+      if (bitmap.Get(bit++)) {
+        StoreCompressedNonPointerWord(
+            dst.ptr(), offset, LoadCompressedNonPointerWord(src.ptr(), offset));
+      } else {
+        ForwardCompressedPointer(src, dst, offset);
+      }
+    }
+  }
+
+  void ForwardCompressedArrayPointers(intptr_t array_length,
+                                      const Object& src,
+                                      const Object& dst,
+                                      intptr_t offset,
+                                      intptr_t end_offset) {
+    if (Array::UseCardMarkingForAllocation(array_length)) {
+      for (; offset < end_offset; offset += kCompressedWordSize) {
+        ForwardCompressedLargeArrayPointer(src, dst, offset);
+      }
+    } else {
+      for (; offset < end_offset; offset += kCompressedWordSize) {
+        ForwardCompressedPointer(src, dst, offset);
+      }
+    }
+  }
+
+  DART_FORCE_INLINE
+  void ForwardCompressedLargeArrayPointer(const Object& src,
+                                          const Object& dst,
+                                          intptr_t offset) {
+    auto value = LoadCompressedPointer(src.ptr(), offset);
+    if (!value.IsHeapObject()) {
+      StoreCompressedPointerNoBarrier(dst.ptr(), offset, value);
+      return;
+    }
+
+    auto value_decompressed = value.Decompress(heap_base_);
+    const uword tags = TagsFromUntaggedObject(value_decompressed.untag());
+    if (CanShareObject(tags)) {
+      StoreCompressedLargeArrayPointerBarrier(dst.ptr(), offset,
+                                              value_decompressed);
+      return;
+    }
+
+    ObjectPtr existing_to =
+        slow_forward_map_.ForwardedObject(value_decompressed);
+    if (existing_to != Marker()) {
+      StoreCompressedLargeArrayPointerBarrier(dst.ptr(), offset, existing_to);
+      return;
+    }
+
+    if (UNLIKELY(!CanCopyObject(tags, value_decompressed))) {
+      ASSERT(exception_msg_ != nullptr);
+      StoreCompressedLargeArrayPointerBarrier(dst.ptr(), offset,
+                                              Object::null());
+      return;
+    }
+
+    tmp_ = value_decompressed;
+    tmp_ = Forward(tags, tmp_);  // Only this can cause allocation.
+    StoreCompressedLargeArrayPointerBarrier(dst.ptr(), offset, tmp_.ptr());
+  }
+  DART_FORCE_INLINE
+  void ForwardCompressedPointer(const Object& src,
+                                const Object& dst,
+                                intptr_t offset) {
+    auto value = LoadCompressedPointer(src.ptr(), offset);
+    if (!value.IsHeapObject()) {
+      StoreCompressedPointerNoBarrier(dst.ptr(), offset, value);
+      return;
+    }
+    auto value_decompressed = value.Decompress(heap_base_);
+    const uword tags = TagsFromUntaggedObject(value_decompressed.untag());
+    if (CanShareObject(tags)) {
+      StoreCompressedPointerBarrier(dst.ptr(), offset, value_decompressed);
+      return;
+    }
+
+    ObjectPtr existing_to =
+        slow_forward_map_.ForwardedObject(value_decompressed);
+    if (existing_to != Marker()) {
+      StoreCompressedPointerBarrier(dst.ptr(), offset, existing_to);
+      return;
+    }
+
+    if (UNLIKELY(!CanCopyObject(tags, value_decompressed))) {
+      ASSERT(exception_msg_ != nullptr);
+      StoreCompressedPointerNoBarrier(dst.ptr(), offset, Object::null());
+      return;
+    }
+
+    tmp_ = value_decompressed;
+    tmp_ = Forward(tags, tmp_);  // Only this can cause allocation.
+    StoreCompressedPointerBarrier(dst.ptr(), offset, tmp_.ptr());
+  }
+  ObjectPtr Forward(uword tags, const Object& from) {
+    const intptr_t cid = UntaggedObject::ClassIdTag::decode(tags);
+    intptr_t size = UntaggedObject::SizeTag::decode(tags);
+    if (size == 0) {
+      size = from.ptr().untag()->HeapSize();
+    }
+    ObjectPtr to = AllocateObject(cid, size);
+    slow_forward_map_.Insert(from.ptr(), to);
+    UpdateLengthField(cid, from.ptr(), to);
+    if (cid == kArrayCid && !Heap::IsAllocatableInNewSpace(size)) {
+      to.untag()->SetCardRememberedBitUnsynchronized();
+    }
+    if (IsExternalTypedDataClassId(cid)) {
+      InitializeExternalTypedData(cid, ExternalTypedData::RawCast(from.ptr()),
+                                  ExternalTypedData::RawCast(to));
+      slow_forward_map_.AddExternalTypedData(ExternalTypedData::RawCast(to));
+    } else if (IsTypedDataViewClassId(cid)) {
+      // We set the views backing store to `null` to satisfy an assertion in
+      // GCCompactor::VisitTypedDataViewPointers().
+      InitializeTypedDataView(TypedDataView::RawCast(to));
+    }
+    return to;
+  }
+  void EnqueueTransferable(const TransferableTypedData& from,
+                           const TransferableTypedData& to) {
+    slow_forward_map_.AddTransferable(from, to);
+  }
+  void EnqueueObjectToRehash(const Object& to) {
+    slow_forward_map_.AddObjectToRehash(to);
+  }
+
+  void StoreCompressedArrayPointers(intptr_t array_length,
+                                    const Object& src,
+                                    const Object& dst,
+                                    intptr_t offset,
+                                    intptr_t end_offset) {
+    auto src_ptr = src.ptr();
+    auto dst_ptr = dst.ptr();
+    if (Array::UseCardMarkingForAllocation(array_length)) {
+      for (; offset <= end_offset; offset += kCompressedWordSize) {
+        StoreCompressedLargeArrayPointerBarrier(
+            dst_ptr, offset,
+            LoadCompressedPointer(src_ptr, offset).Decompress(heap_base_));
+      }
+    } else {
+      for (; offset <= end_offset; offset += kCompressedWordSize) {
+        StoreCompressedPointerBarrier(
+            dst_ptr, offset,
+            LoadCompressedPointer(src_ptr, offset).Decompress(heap_base_));
+      }
+    }
+  }
+  void StoreCompressedPointers(const Object& src,
+                               const Object& dst,
+                               intptr_t offset,
+                               intptr_t end_offset) {
+    auto src_ptr = src.ptr();
+    auto dst_ptr = dst.ptr();
+    for (; offset <= end_offset; offset += kCompressedWordSize) {
+      StoreCompressedPointerBarrier(
+          dst_ptr, offset,
+          LoadCompressedPointer(src_ptr, offset).Decompress(heap_base_));
+    }
+  }
+  static void StoreCompressedPointersNoBarrier(const Object& src,
+                                               const Object& dst,
+                                               intptr_t offset,
+                                               intptr_t end_offset) {
+    auto src_ptr = src.ptr();
+    auto dst_ptr = dst.ptr();
+    for (; offset <= end_offset; offset += kCompressedWordSize) {
+      StoreCompressedPointerNoBarrier(dst_ptr, offset,
+                                      LoadCompressedPointer(src_ptr, offset));
+    }
+  }
+
+ protected:
+  friend class ObjectGraphCopier;
+
+  SlowForwardMap slow_forward_map_;
+};
+
+template <typename Base>
+class ObjectCopy : public Base {
+ public:
+  using Types = typename Base::Types;
+
+  explicit ObjectCopy(Thread* thread) : Base(thread) {}
+
+  void CopyPredefinedInstance(typename Types::Object from,
+                              typename Types::Object to,
+                              intptr_t cid) {
+    if (IsImplicitFieldClassId(cid)) {
+      CopyUserdefinedInstance(from, to);
+      return;
+    }
+    switch (cid) {
+#define COPY_TO(clazz)                                                         \
+  case clazz::kClassId: {                                                      \
+    typename Types::clazz casted_from = Types::Cast##clazz(from);              \
+    typename Types::clazz casted_to = Types::Cast##clazz(to);                  \
+    Copy##clazz(casted_from, casted_to);                                       \
+    return;                                                                    \
+  }
+
+      CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(COPY_TO)
+      COPY_TO(Array)
+      COPY_TO(LinkedHashMap)
+      COPY_TO(LinkedHashSet)
+#undef COPY_TO
+
+#define COPY_TO(clazz) case kTypedData##clazz##Cid:
+
+      CLASS_LIST_TYPED_DATA(COPY_TO) {
+        typename Types::TypedData casted_from = Types::CastTypedData(from);
+        typename Types::TypedData casted_to = Types::CastTypedData(to);
+        CopyTypedData(casted_from, casted_to);
+        return;
+      }
+#undef COPY_TO
+
+      case kByteDataViewCid:
+#define COPY_TO(clazz) case kTypedData##clazz##ViewCid:
+        CLASS_LIST_TYPED_DATA(COPY_TO) {
+          typename Types::TypedDataView casted_from =
+              Types::CastTypedDataView(from);
+          typename Types::TypedDataView casted_to =
+              Types::CastTypedDataView(to);
+          CopyTypedDataView(casted_from, casted_to);
+          return;
+        }
+#undef COPY_TO
+
+#define COPY_TO(clazz) case kExternalTypedData##clazz##Cid:
+
+        CLASS_LIST_TYPED_DATA(COPY_TO) {
+          typename Types::ExternalTypedData casted_from =
+              Types::CastExternalTypedData(from);
+          typename Types::ExternalTypedData casted_to =
+              Types::CastExternalTypedData(to);
+          CopyExternalTypedData(casted_from, casted_to);
+          return;
+        }
+#undef COPY_TO
+      default:
+        break;
+    }
+
+    const Object& obj = Types::HandlifyObject(from);
+    FATAL1("Unexpected object: %s\n", obj.ToCString());
+  }
+
+#if defined(DART_PRECOMPILED_RUNTIME)
+  void CopyUserdefinedInstanceAOT(typename Types::Object from,
+                                  typename Types::Object to,
+                                  UnboxedFieldBitmap bitmap) {
+    const intptr_t instance_size = UntagObject(from)->HeapSize();
+    Base::ForwardCompressedPointers(from, to, kWordSize, instance_size, bitmap);
+  }
+#endif
+
+  void CopyUserdefinedInstance(typename Types::Object from,
+                               typename Types::Object to) {
+    const intptr_t instance_size = UntagObject(from)->HeapSize();
+    Base::ForwardCompressedPointers(from, to, kWordSize, instance_size);
+  }
+
+  void CopyClosure(typename Types::Closure from, typename Types::Closure to) {
+    Base::StoreCompressedPointers(
+        from, to, OFFSET_OF(UntaggedClosure, instantiator_type_arguments_),
+        OFFSET_OF(UntaggedClosure, function_));
+    Base::ForwardCompressedPointer(from, to,
+                                   OFFSET_OF(UntaggedClosure, context_));
+    Base::StoreCompressedPointersNoBarrier(from, to,
+                                           OFFSET_OF(UntaggedClosure, hash_),
+                                           OFFSET_OF(UntaggedClosure, hash_));
+    ONLY_IN_PRECOMPILED(UntagClosure(to)->entry_point_ =
+                            UntagClosure(from)->entry_point_);
+  }
+
+  void CopyArray(typename Types::Array from, typename Types::Array to) {
+    const intptr_t length = Smi::Value(UntagArray(from)->length());
+    Base::StoreCompressedArrayPointers(
+        length, from, to, OFFSET_OF(UntaggedArray, type_arguments_),
+        OFFSET_OF(UntaggedArray, type_arguments_));
+    Base::StoreCompressedPointersNoBarrier(from, to,
+                                           OFFSET_OF(UntaggedArray, length_),
+                                           OFFSET_OF(UntaggedArray, length_));
+    Base::ForwardCompressedArrayPointers(
+        length, from, to, Array::data_offset(),
+        Array::data_offset() + kCompressedWordSize * length);
+  }
+
+  void CopyGrowableObjectArray(typename Types::GrowableObjectArray from,
+                               typename Types::GrowableObjectArray to) {
+    Base::StoreCompressedPointers(
+        from, to, OFFSET_OF(UntaggedGrowableObjectArray, type_arguments_),
+        OFFSET_OF(UntaggedGrowableObjectArray, type_arguments_));
+    Base::StoreCompressedPointersNoBarrier(
+        from, to, OFFSET_OF(UntaggedGrowableObjectArray, length_),
+        OFFSET_OF(UntaggedGrowableObjectArray, length_));
+    Base::ForwardCompressedPointer(
+        from, to, OFFSET_OF(UntaggedGrowableObjectArray, data_));
+  }
+
+  template <intptr_t one_for_set_two_for_map, typename T>
+  void CopyLinkedHashBase(T from,
+                          T to,
+                          UntaggedLinkedHashBase* from_untagged,
+                          UntaggedLinkedHashBase* to_untagged) {
+    // We have to find out whether the map needs re-hashing on the receiver side
+    // due to keys being copied and the keys therefore possibly having different
+    // hash codes (e.g. due to user-defined hashCode implementation or due to
+    // new identity hash codes of the copied objects).
+    bool needs_rehashing = false;
+    ArrayPtr data = from_untagged->data_.Decompress(Base::heap_base_);
+    if (data != Array::null()) {
+      UntaggedArray* untagged_data = data.untag();
+      const intptr_t length = Smi::Value(untagged_data->length_);
+      auto key_value_pairs = untagged_data->data();
+      for (intptr_t i = 0; i < length; i += one_for_set_two_for_map) {
+        ObjectPtr key = key_value_pairs[i].Decompress(Base::heap_base_);
+        if (key->IsHeapObject()) {
+          if (MightNeedReHashing(key)) {
+            needs_rehashing = true;
+            break;
+          }
+        }
+      }
+    }
+
+    Base::StoreCompressedPointers(
+        from, to, OFFSET_OF(UntaggedLinkedHashBase, type_arguments_),
+        OFFSET_OF(UntaggedLinkedHashBase, type_arguments_));
+
+    // Compared with the snapshot-based (de)serializer we do preserve the same
+    // backing store (i.e. used_data/deleted_keys/data) and therefore do not
+    // magically shrink backing store based on usage.
+    //
+    // We do this to avoid making assumptions about the object graph and the
+    // linked hash map (e.g. assuming there's no other references to the data,
+    // assuming the linked hashmap is in a consistent state)
+    if (needs_rehashing) {
+      to_untagged->hash_mask_ = Smi::New(0);
+      to_untagged->index_ = TypedData::RawCast(Object::null());
+      Base::EnqueueObjectToRehash(to);
+    }
+
+    // From this point on we shouldn't use the raw pointers, since GC might
+    // happen when forwarding objects.
+    from_untagged = nullptr;
+    to_untagged = nullptr;
+
+    if (!needs_rehashing) {
+      Base::ForwardCompressedPointer(from, to,
+                                     OFFSET_OF(UntaggedLinkedHashBase, index_));
+      Base::StoreCompressedPointersNoBarrier(
+          from, to, OFFSET_OF(UntaggedLinkedHashBase, hash_mask_),
+          OFFSET_OF(UntaggedLinkedHashBase, hash_mask_));
+    }
+    Base::ForwardCompressedPointer(from, to,
+                                   OFFSET_OF(UntaggedLinkedHashBase, data_));
+    Base::StoreCompressedPointersNoBarrier(
+        from, to, OFFSET_OF(UntaggedLinkedHashBase, used_data_),
+        OFFSET_OF(UntaggedLinkedHashBase, used_data_));
+    Base::StoreCompressedPointersNoBarrier(
+        from, to, OFFSET_OF(UntaggedLinkedHashMap, deleted_keys_),
+        OFFSET_OF(UntaggedLinkedHashMap, deleted_keys_));
+  }
+
+  void CopyLinkedHashMap(typename Types::LinkedHashMap from,
+                         typename Types::LinkedHashMap to) {
+    CopyLinkedHashBase<2, typename Types::LinkedHashMap>(
+        from, to, UntagLinkedHashMap(from), UntagLinkedHashMap(to));
+  }
+  void CopyLinkedHashSet(typename Types::LinkedHashSet from,
+                         typename Types::LinkedHashSet to) {
+    CopyLinkedHashBase<1, typename Types::LinkedHashSet>(
+        from, to, UntagLinkedHashSet(from), UntagLinkedHashSet(to));
+  }
+
+  void CopyDouble(typename Types::Double from, typename Types::Double to) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+    auto raw_from = UntagDouble(from);
+    auto raw_to = UntagDouble(to);
+    raw_to->value_ = raw_from->value_;
+#else
+    // Will be shared and not copied.
+    UNREACHABLE();
+#endif
+  }
+
+  void CopyFloat32x4(typename Types::Float32x4 from,
+                     typename Types::Float32x4 to) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+    auto raw_from = UntagFloat32x4(from);
+    auto raw_to = UntagFloat32x4(to);
+    raw_to->value_[0] = raw_from->value_[0];
+    raw_to->value_[1] = raw_from->value_[1];
+    raw_to->value_[2] = raw_from->value_[2];
+    raw_to->value_[3] = raw_from->value_[3];
+#else
+    // Will be shared and not copied.
+    UNREACHABLE();
+#endif
+  }
+
+  void CopyFloat64x2(typename Types::Float64x2 from,
+                     typename Types::Float64x2 to) {
+#if !defined(DART_PRECOMPILED_RUNTIME)
+    auto raw_from = UntagFloat64x2(from);
+    auto raw_to = UntagFloat64x2(to);
+    raw_to->value_[0] = raw_from->value_[0];
+    raw_to->value_[1] = raw_from->value_[1];
+#else
+    // Will be shared and not copied.
+    UNREACHABLE();
+#endif
+  }
+
+  void CopyTypedData(typename Types::TypedData from,
+                     typename Types::TypedData to) {
+    auto raw_from = UntagTypedData(from);
+    auto raw_to = UntagTypedData(to);
+    const intptr_t cid = Types::GetTypedDataPtr(from)->GetClassId();
+    raw_to->length_ = raw_from->length_;
+    raw_to->RecomputeDataField();
+    const intptr_t length =
+        TypedData::ElementSizeInBytes(cid) * Smi::Value(raw_from->length_);
+    memmove(raw_to->data_, raw_from->data_, length);
+  }
+
+  void CopyTypedDataView(typename Types::TypedDataView from,
+                         typename Types::TypedDataView to) {
+    // This will forward & initialize the typed data.
+    Base::ForwardCompressedPointer(
+        from, to, OFFSET_OF(UntaggedTypedDataView, typed_data_));
+
+    auto raw_from = UntagTypedDataView(from);
+    auto raw_to = UntagTypedDataView(to);
+    raw_to->length_ = raw_from->length_;
+    raw_to->offset_in_bytes_ = raw_from->offset_in_bytes_;
+    raw_to->data_ = nullptr;
+
+    if (raw_to->typed_data_.Decompress(Base::heap_base_) == Object::null()) {
+      ASSERT(Base::exception_msg_ != nullptr);
+      return;
+    }
+
+    const bool is_external =
+        raw_from->data_ != raw_from->DataFieldForInternalTypedData();
+    if (is_external) {
+      // The raw_to is fully initialized at this point (see handling of external
+      // typed data in [ForwardCompressedPointer])
+      raw_to->RecomputeDataField();
+    } else {
+      // The raw_to isn't initialized yet, but it's address is valid, so we can
+      // compute the data field it would use.
+      raw_to->RecomputeDataFieldForInternalTypedData();
+    }
+    const bool is_external2 =
+        raw_to->data_ != raw_to->DataFieldForInternalTypedData();
+    ASSERT(is_external == is_external2);
+  }
+
+  void CopyExternalTypedData(typename Types::ExternalTypedData from,
+                             typename Types::ExternalTypedData to) {
+    // The external typed data is initialized on the forwarding pass (where
+    // normally allocation but not initialization happens), so views on it
+    // can be initialized immediately.
+#if defined(DEBUG)
+    auto raw_from = UntagExternalTypedData(from);
+    auto raw_to = UntagExternalTypedData(to);
+    ASSERT(raw_to->data_ != nullptr);
+    ASSERT(raw_to->length_ == raw_from->length_);
+#endif
+  }
+
+  void CopyTransferableTypedData(typename Types::TransferableTypedData from,
+                                 typename Types::TransferableTypedData to) {
+    // The [TransferableTypedData] is an empty object with an associated heap
+    // peer object.
+    // -> We'll validate that there's a peer and enqueue the transferable to be
+    // transferred if the transitive copy is successful.
+    auto fpeer = static_cast<TransferableTypedDataPeer*>(
+        Base::heap_->GetPeer(Types::GetTransferableTypedDataPtr(from)));
+    ASSERT(fpeer != nullptr);
+    if (fpeer->data() == nullptr) {
+      Base::exception_msg_ =
+          "Illegal argument in isolate message"
+          " : (TransferableTypedData has been transferred already)";
+      return;
+    }
+    Base::EnqueueTransferable(from, to);
+  }
+
+#define DEFINE_UNSUPPORTED(clazz)                                              \
+  void Copy##clazz(typename Types::clazz from, typename Types::clazz to) {     \
+    FATAL("Objects of type " #clazz " should not occur in object graphs");     \
+  }
+
+  FOR_UNSUPPORTED_CLASSES(DEFINE_UNSUPPORTED)
+
+#undef DEFINE_UNSUPPORTED
+
+  UntaggedObject* UntagObject(typename Types::Object obj) {
+    return Types::GetObjectPtr(obj).Decompress(Base::heap_base_).untag();
+  }
+
+#define DO(V)                                                                  \
+  DART_FORCE_INLINE                                                            \
+  Untagged##V* Untag##V(typename Types::V obj) {                               \
+    return Types::Get##V##Ptr(obj).Decompress(Base::heap_base_).untag();       \
+  }
+  CLASS_LIST_FOR_HANDLES(DO)
+#undef DO
+};
+
+class FastObjectCopy : public ObjectCopy<FastObjectCopyBase> {
+ public:
+  explicit FastObjectCopy(Thread* thread) : ObjectCopy(thread) {}
+  ~FastObjectCopy() {}
+
+  ObjectPtr TryCopyGraphFast(ObjectPtr root) {
+    NoSafepointScope no_safepoint_scope;
+
+    ObjectPtr root_copy = Forward(TagsFromUntaggedObject(root.untag()), root);
+    if (root_copy == Marker()) {
+      return root_copy;
+    }
+    while (fast_forward_map_.fill_cursor_ <
+           fast_forward_map_.raw_from_to_.length()) {
+      const intptr_t index = fast_forward_map_.fill_cursor_;
+      ObjectPtr from = fast_forward_map_.raw_from_to_[index];
+      ObjectPtr to = fast_forward_map_.raw_from_to_[index + 1];
+      FastCopyObject(from, to);
+      if (exception_msg_ != nullptr) {
+        return root_copy;
+      }
+      fast_forward_map_.fill_cursor_ += 2;
+    }
+    if (root_copy != Marker()) {
+      TryBuildArrayOfObjectsToRehash();
+    }
+    return root_copy;
+  }
+
+  void TryBuildArrayOfObjectsToRehash() {
+    const auto& objects_to_rehash = fast_forward_map_.raw_objects_to_rehash_;
+    const intptr_t length = objects_to_rehash.length();
+    if (length == 0) return;
+
+    const intptr_t size = Array::InstanceSize(length);
+    const uword array_addr = new_space_->TryAllocate(thread_, size);
+    if (array_addr == 0) {
+      exception_msg_ = kFastAllocationFailed;
+      return;
+    }
+
+    const uword header_size =
+        UntaggedObject::SizeTag::SizeFits(size) ? size : 0;
+    ArrayPtr array(reinterpret_cast<UntaggedArray*>(array_addr));
+    SetNewSpaceTaggingWord(array, kArrayCid, header_size);
+    StoreCompressedPointerNoBarrier(array, OFFSET_OF(UntaggedArray, length_),
+                                    Smi::New(length));
+    StoreCompressedPointerNoBarrier(array,
+                                    OFFSET_OF(UntaggedArray, type_arguments_),
+                                    TypeArguments::null());
+    auto array_data = array.untag()->data();
+    for (intptr_t i = 0; i < length; ++i) {
+      array_data[i] = objects_to_rehash[i];
+    }
+    raw_objects_to_rehash_ = array;
+  }
+
+ private:
+  friend class ObjectGraphCopier;
+
+  void FastCopyObject(ObjectPtr from, ObjectPtr to) {
+    const uword tags = TagsFromUntaggedObject(from.untag());
+    const intptr_t cid = UntaggedObject::ClassIdTag::decode(tags);
+    const intptr_t size = UntaggedObject::SizeTag::decode(tags);
+
+    // Ensure the last word is GC-safe (our heap objects are 2-word aligned, the
+    // object header stores the size in multiples of kObjectAlignment, the GC
+    // uses the information from the header and therefore might visit one slot
+    // more than the actual size of the instance).
+    *reinterpret_cast<ObjectPtr*>(UntaggedObject::ToAddr(to) +
+                                  from.untag()->HeapSize() - kWordSize) = 0;
+    SetNewSpaceTaggingWord(to, cid, size);
+
+    // Fall back to virtual variant for predefined classes
+    if (cid < kNumPredefinedCids && cid != kInstanceCid) {
+      CopyPredefinedInstance(from, to, cid);
+      return;
+    }
+#if defined(DART_PRECOMPILED_RUNTIME)
+    const auto bitmap =
+        class_table_->shared_class_table()->GetUnboxedFieldsMapAt(cid);
+    CopyUserdefinedInstanceAOT(Instance::RawCast(from), Instance::RawCast(to),
+                               bitmap);
+#else
+    CopyUserdefinedInstance(Instance::RawCast(from), Instance::RawCast(to));
+#endif
+  }
+
+  ArrayPtr raw_objects_to_rehash_ = Array::null();
+};
+
+class SlowObjectCopy : public ObjectCopy<SlowObjectCopyBase> {
+ public:
+  explicit SlowObjectCopy(Thread* thread)
+      : ObjectCopy(thread), objects_to_rehash_(Array::Handle(thread->zone())) {}
+  ~SlowObjectCopy() {}
+
+  ObjectPtr ContinueCopyGraphSlow(const Object& root,
+                                  const Object& fast_root_copy) {
+    auto& root_copy = Object::Handle(Z, fast_root_copy.ptr());
+    if (root_copy.ptr() == Marker()) {
+      root_copy = Forward(TagsFromUntaggedObject(root.ptr().untag()), root);
+    }
+
+    Object& from = Object::Handle(Z);
+    Object& to = Object::Handle(Z);
+    while (slow_forward_map_.fill_cursor_ <
+           slow_forward_map_.from_to_.length()) {
+      const intptr_t index = slow_forward_map_.fill_cursor_;
+      from = slow_forward_map_.from_to_[index]->ptr();
+      to = slow_forward_map_.from_to_[index + 1]->ptr();
+      CopyObject(from, to);
+      slow_forward_map_.fill_cursor_ += 2;
+      if (exception_msg_ != nullptr) {
+        return Marker();
+      }
+    }
+    BuildArrayOfObjectsToRehash();
+    return root_copy.ptr();
+  }
+
+  void BuildArrayOfObjectsToRehash() {
+    const auto& objects_to_rehash = slow_forward_map_.objects_to_rehash_;
+    const intptr_t length = objects_to_rehash.length();
+    if (length == 0) return;
+
+    objects_to_rehash_ = Array::New(length);
+    for (intptr_t i = 0; i < length; ++i) {
+      objects_to_rehash_.SetAt(i, *objects_to_rehash[i]);
+    }
+  }
+
+ private:
+  friend class ObjectGraphCopier;
+
+  void CopyObject(const Object& from, const Object& to) {
+    const auto cid = from.GetClassId();
+
+    // Fall back to virtual variant for predefined classes
+    if (cid < kNumPredefinedCids && cid != kInstanceCid) {
+      CopyPredefinedInstance(from, to, cid);
+      return;
+    }
+#if defined(DART_PRECOMPILED_RUNTIME)
+    const auto bitmap =
+        class_table_->shared_class_table()->GetUnboxedFieldsMapAt(cid);
+    CopyUserdefinedInstanceAOT(from, to, bitmap);
+#else
+    CopyUserdefinedInstance(from, to);
+#endif
+  }
+
+  Array& objects_to_rehash_;
+};
+
+class ObjectGraphCopier {
+ public:
+  explicit ObjectGraphCopier(Thread* thread)
+      : thread_(thread),
+        zone_(thread->zone()),
+        fast_object_copy_(thread_),
+        slow_object_copy_(thread_) {
+    thread_->isolate()->set_forward_table_new(new WeakTable());
+    thread_->isolate()->set_forward_table_old(new WeakTable());
+  }
+  ~ObjectGraphCopier() {
+    thread_->isolate()->set_forward_table_new(nullptr);
+    thread_->isolate()->set_forward_table_old(nullptr);
+  }
+
+  // Result will be [<msg>, <objects-in-msg-to-rehash>]
+  ObjectPtr CopyObjectGraph(const Object& root) {
+    const char* exception_msg = nullptr;
+    auto& result = Object::Handle(zone_);
+
+    {
+      LongJumpScope jump;  // e.g. for OOMs.
+      if (setjmp(*jump.Set()) == 0) {
+        result = CopyObjectGraphInternal(root, &exception_msg);
+        // Any allocated external typed data must have finalizers attached so
+        // memory will get free()ed.
+        slow_object_copy_.slow_forward_map_.FinalizeExternalTypedData();
+      } else {
+        // Any allocated external typed data must have finalizers attached so
+        // memory will get free()ed.
+        slow_object_copy_.slow_forward_map_.FinalizeExternalTypedData();
+
+        // The copy failed due to non-application error (e.g. OOM error),
+        // propagate this error.
+        result = thread_->StealStickyError();
+        RELEASE_ASSERT(result.IsError());
+      }
+    }
+
+    if (result.IsError()) {
+      Exceptions::PropagateError(Error::Cast(result));
+      UNREACHABLE();
+    }
+    if (result.ptr() == Marker()) {
+      ASSERT(exception_msg != nullptr);
+      ThrowException(exception_msg);
+      UNREACHABLE();
+    }
+
+    // The copy was successful, then detach transferable data from the sender
+    // and attach to the copied graph.
+    slow_object_copy_.slow_forward_map_.FinalizeTransferables();
+    return result.ptr();
+  }
+
+ private:
+  ObjectPtr CopyObjectGraphInternal(const Object& root,
+                                    const char** exception_msg) {
+    const auto& result_array = Array::Handle(zone_, Array::New(2));
+    if (!root.ptr()->IsHeapObject()) {
+      result_array.SetAt(0, root);
+      return result_array.ptr();
+    }
+    const uword tags = TagsFromUntaggedObject(root.ptr().untag());
+    if (CanShareObject(tags)) {
+      result_array.SetAt(0, root);
+      return result_array.ptr();
+    }
+    if (!fast_object_copy_.CanCopyObject(tags, root.ptr())) {
+      ASSERT(fast_object_copy_.exception_msg_ != nullptr);
+      *exception_msg = fast_object_copy_.exception_msg_;
+      return Marker();
+    }
+
+    // We try a fast new-space only copy first that will not use any barriers.
+    auto& result = Object::Handle(Z, Marker());
+
+    // All allocated but non-initialized heap objects have to be made GC-visible
+    // at this point.
+    if (FLAG_enable_fast_object_copy) {
+      {
+        NoSafepointScope no_safepoint_scope;
+
+        result = fast_object_copy_.TryCopyGraphFast(root.ptr());
+        if (result.ptr() != Marker()) {
+          if (fast_object_copy_.exception_msg_ == nullptr) {
+            result_array.SetAt(0, result);
+            fast_object_copy_.tmp_ = fast_object_copy_.raw_objects_to_rehash_;
+            result_array.SetAt(1, fast_object_copy_.tmp_);
+            HandlifyExternalTypedData();
+            HandlifyTransferables();
+            return result_array.ptr();
+          }
+
+          // There are left-over uninitialized objects we'll have to make GC
+          // visible.
+          SwitchToSlowFowardingList();
+        }
+      }
+
+      if (FLAG_gc_on_foc_slow_path) {
+        // We use kLowMemory to force the GC to compact, which is more likely to
+        // discover untracked pointers (and other issues, like incorrect class
+        // table).
+        thread_->heap()->CollectAllGarbage(Heap::kLowMemory);
+      }
+
+      // Fast copy failed due to
+      //   - either failure to allocate into new space
+      //   - or failure to copy object which we cannot copy
+      ASSERT(fast_object_copy_.exception_msg_ != nullptr);
+      if (fast_object_copy_.exception_msg_ != kFastAllocationFailed) {
+        *exception_msg = fast_object_copy_.exception_msg_;
+        return Marker();
+      }
+      ASSERT(fast_object_copy_.exception_msg_ == kFastAllocationFailed);
+    }
+
+    // Use the slow copy approach.
+    result = slow_object_copy_.ContinueCopyGraphSlow(root, result);
+    ASSERT((result.ptr() == Marker()) ==
+           (slow_object_copy_.exception_msg_ != nullptr));
+    if (result.ptr() == Marker()) {
+      *exception_msg = slow_object_copy_.exception_msg_;
+      return Marker();
+    }
+
+    result_array.SetAt(0, result);
+    result_array.SetAt(1, slow_object_copy_.objects_to_rehash_);
+    return result_array.ptr();
+  }
+
+  void SwitchToSlowFowardingList() {
+    auto& fast_forward_map = fast_object_copy_.fast_forward_map_;
+    auto& slow_forward_map = slow_object_copy_.slow_forward_map_;
+
+    MakeUninitializedNewSpaceObjectsGCSafe();
+    HandlifyTransferables();
+    HandlifyExternalTypedData();
+    HandlifyObjectsToReHash();
+    HandlifyFromToObjects();
+    slow_forward_map.fill_cursor_ = fast_forward_map.fill_cursor_;
+  }
+
+  void MakeUninitializedNewSpaceObjectsGCSafe() {
+    auto& fast_forward_map = fast_object_copy_.fast_forward_map_;
+    const auto length = fast_forward_map.raw_from_to_.length();
+    const auto cursor = fast_forward_map.fill_cursor_;
+    for (intptr_t i = cursor; i < length; i += 2) {
+      auto from = fast_forward_map.raw_from_to_[i];
+      auto to = fast_forward_map.raw_from_to_[i + 1];
+      const uword tags = TagsFromUntaggedObject(from.untag());
+      const intptr_t cid = UntaggedObject::ClassIdTag::decode(tags);
+      const intptr_t size = UntaggedObject::SizeTag::decode(tags);
+      // External typed data is already initialized.
+      if (!IsExternalTypedDataClassId(cid) && !IsTypedDataViewClassId(cid)) {
+        memset(to.untag(), 0x0, from.untag()->HeapSize());
+        SetNewSpaceTaggingWord(to, cid, size);
+        UpdateLengthField(cid, from, to);
+      }
+    }
+  }
+  void HandlifyTransferables() {
+    auto& raw_transferables =
+        fast_object_copy_.fast_forward_map_.raw_transferables_from_to_;
+    const auto length = raw_transferables.length();
+    if (length > 0) {
+      auto& transferables =
+          slow_object_copy_.slow_forward_map_.transferables_from_to_;
+      transferables.Resize(length);
+      for (intptr_t i = 0; i < length; i++) {
+        transferables[i] =
+            &TransferableTypedData::Handle(Z, raw_transferables[i]);
+      }
+      raw_transferables.Clear();
+    }
+  }
+  void HandlifyExternalTypedData() {
+    auto& raw_external_typed_data =
+        fast_object_copy_.fast_forward_map_.raw_external_typed_data_to_;
+    const auto length = raw_external_typed_data.length();
+    if (length > 0) {
+      auto& external_typed_data =
+          slow_object_copy_.slow_forward_map_.external_typed_data_;
+      external_typed_data.Resize(length);
+      for (intptr_t i = 0; i < length; i++) {
+        external_typed_data[i] =
+            &ExternalTypedData::Handle(Z, raw_external_typed_data[i]);
+      }
+      raw_external_typed_data.Clear();
+    }
+  }
+  void HandlifyObjectsToReHash() {
+    auto& fast_forward_map = fast_object_copy_.fast_forward_map_;
+    auto& slow_forward_map = slow_object_copy_.slow_forward_map_;
+    const auto length = fast_forward_map.raw_transferables_from_to_.length();
+    if (length > 0) {
+      slow_forward_map.objects_to_rehash_.Resize(length);
+      for (intptr_t i = 0; i < length; i++) {
+        slow_forward_map.objects_to_rehash_[i] =
+            &Object::Handle(Z, fast_forward_map.raw_objects_to_rehash_[i]);
+      }
+      fast_forward_map.raw_objects_to_rehash_.Clear();
+    }
+  }
+  void HandlifyFromToObjects() {
+    auto& fast_forward_map = fast_object_copy_.fast_forward_map_;
+    auto& slow_forward_map = slow_object_copy_.slow_forward_map_;
+
+    const intptr_t cursor = fast_forward_map.fill_cursor_;
+    const intptr_t length = fast_forward_map.raw_from_to_.length();
+
+    slow_forward_map.from_to_.Resize(length);
+    for (intptr_t i = 2; i < length; i += 2) {
+      slow_forward_map.from_to_[i] =
+          i < cursor ? nullptr
+                     : &Object::Handle(Z, fast_forward_map.raw_from_to_[i]);
+      slow_forward_map.from_to_[i + 1] =
+          &Object::Handle(Z, fast_forward_map.raw_from_to_[i + 1]);
+    }
+    fast_forward_map.raw_from_to_.Clear();
+  }
+  void ThrowException(const char* exception_msg) {
+    const auto& msg_obj = String::Handle(Z, String::New(exception_msg));
+    const auto& args = Array::Handle(Z, Array::New(1));
+    args.SetAt(0, msg_obj);
+    Exceptions::ThrowByType(Exceptions::kArgument, args);
+    UNREACHABLE();
+  }
+
+  Thread* thread_;
+  Zone* zone_;
+  FastObjectCopy fast_object_copy_;
+  SlowObjectCopy slow_object_copy_;
+};
+
+ObjectPtr CopyMutableObjectGraph(const Object& object) {
+  auto thread = Thread::Current();
+  ObjectGraphCopier copier(thread);
+  return copier.CopyObjectGraph(object);
+}
+
+}  // namespace dart
diff --git a/runtime/vm/object_graph_copy.h b/runtime/vm/object_graph_copy.h
new file mode 100644
index 0000000..b33c3e4
--- /dev/null
+++ b/runtime/vm/object_graph_copy.h
@@ -0,0 +1,26 @@
+// 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.
+
+#ifndef RUNTIME_VM_OBJECT_GRAPH_COPY_H_
+#define RUNTIME_VM_OBJECT_GRAPH_COPY_H_
+
+namespace dart {
+
+class Object;
+class ObjectPtr;
+
+// Makes a transitive copy of the object graph referenced by [object]. Will not
+// copy objects that can be safely shared - due to being immutable.
+//
+// The result will be an array of length 2 of the format
+//
+//   [<copy-of-root>, <array-of-objects-to-rehash / null>]
+//
+// If the array of objects to rehash is not `null` the receiver should re-hash
+// those objects.
+ObjectPtr CopyMutableObjectGraph(const Object& root);
+
+}  // namespace dart
+
+#endif  // RUNTIME_VM_OBJECT_GRAPH_COPY_H_
diff --git a/runtime/vm/port.cc b/runtime/vm/port.cc
index c7312de..a671373 100644
--- a/runtime/vm/port.cc
+++ b/runtime/vm/port.cc
@@ -243,7 +243,9 @@
   MutexLocker ml(mutex_);
   auto it = ports_->TryLookup(receiver);
   if (it == ports_->end()) return false;
-  return (*it).handler->isolate()->group() == group;
+  auto isolate = (*it).handler->isolate();
+  if (isolate == nullptr) return false;
+  return isolate->group() == group;
 }
 
 void PortMap::Init() {
diff --git a/runtime/vm/port.h b/runtime/vm/port.h
index 1405362..4999ad4 100644
--- a/runtime/vm/port.h
+++ b/runtime/vm/port.h
@@ -63,6 +63,7 @@
   // Returns the owning Isolate for port 'id'.
   static Isolate* GetIsolate(Dart_Port id);
 
+  // Whether the destination port's isolate is a member of [isolate_group].
   static bool IsReceiverInThisIsolateGroup(Dart_Port receiver,
                                            IsolateGroup* group);
 
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index f16ef82..b9fd76f 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -150,6 +150,8 @@
   friend class object##DeserializationCluster;                                 \
   friend class Serializer;                                                     \
   friend class Deserializer;                                                   \
+  template <typename Base>                                                     \
+  friend class ObjectCopy;                                                     \
   friend class Pass2Visitor;
 
 // RawObject is the base class of all raw objects; even though it carries the
@@ -540,7 +542,7 @@
   void StorePointer(type const* addr, type value) {
     reinterpret_cast<std::atomic<type>*>(const_cast<type*>(addr))
         ->store(value, order);
-    if (value->IsHeapObject()) {
+    if (value.IsHeapObject()) {
       CheckHeapPointerStore(value, Thread::Current());
     }
   }
@@ -552,7 +554,7 @@
     reinterpret_cast<std::atomic<compressed_type>*>(
         const_cast<compressed_type*>(addr))
         ->store(static_cast<compressed_type>(value), order);
-    if (value->IsHeapObject()) {
+    if (value.IsHeapObject()) {
       CheckHeapPointerStore(value, Thread::Current());
     }
   }
@@ -560,7 +562,7 @@
   template <typename type>
   void StorePointer(type const* addr, type value, Thread* thread) {
     *const_cast<type*>(addr) = value;
-    if (value->IsHeapObject()) {
+    if (value.IsHeapObject()) {
       CheckHeapPointerStore(value, thread);
     }
   }
@@ -570,7 +572,7 @@
                               type value,
                               Thread* thread) {
     *const_cast<compressed_type*>(addr) = value;
-    if (value->IsHeapObject()) {
+    if (value.IsHeapObject()) {
       CheckHeapPointerStore(value, thread);
     }
   }
@@ -772,6 +774,9 @@
   friend class WriteBarrierUpdateVisitor;  // CheckHeapPointerStore
   friend class OffsetsTable;
   friend class Object;
+  friend uword TagsFromUntaggedObject(UntaggedObject*);                // tags_
+  friend void SetNewSpaceTaggingWord(ObjectPtr, classid_t, uint32_t);  // tags_
+  friend class ObjectCopyBase;  // LoadPointer/StorePointer
   friend void ReportImpossibleNullError(intptr_t cid,
                                         StackFrame* caller_frame,
                                         Thread* thread);
@@ -2808,6 +2813,12 @@
 
  private:
   friend class UntaggedTypedDataView;
+  friend void UpdateLengthField(intptr_t, ObjectPtr, ObjectPtr);  // length_
+  friend void InitializeExternalTypedData(
+      intptr_t,
+      ExternalTypedDataPtr,
+      ExternalTypedDataPtr);  // initialize fields.
+
   RAW_HEAP_OBJECT_IMPLEMENTATION(TypedDataBase);
 };
 
@@ -2899,6 +2910,7 @@
   VISIT_TO(offset_in_bytes)
   CompressedObjectPtr* to_snapshot(Snapshot::Kind kind) { return to(); }
 
+  friend void InitializeTypedDataView(TypedDataViewPtr);
   friend class Api;
   friend class Object;
   friend class ObjectPoolDeserializationCluster;
@@ -2964,6 +2976,8 @@
   template <typename Table, bool kAllCanonicalObjectsAreIncludedIntoSet>
   friend class CanonicalSetDeserializationCluster;
   friend class OldPage;
+  friend class FastObjectCopy;  // For initializing fields.
+  friend void UpdateLengthField(intptr_t, ObjectPtr, ObjectPtr);  // length_
 };
 
 class UntaggedImmutableArray : public UntaggedArray {
diff --git a/runtime/vm/vm_sources.gni b/runtime/vm/vm_sources.gni
index 8d6dcb2..12f776e 100644
--- a/runtime/vm/vm_sources.gni
+++ b/runtime/vm/vm_sources.gni
@@ -186,6 +186,8 @@
   "object.h",
   "object_graph.cc",
   "object_graph.h",
+  "object_graph_copy.cc",
+  "object_graph_copy.h",
   "object_id_ring.cc",
   "object_id_ring.h",
   "object_reload.cc",
diff --git a/tests/lib/isolate/count_test.dart b/tests/lib/isolate/count_test.dart
index 2315e12..8aeffd5 100644
--- a/tests/lib/isolate/count_test.dart
+++ b/tests/lib/isolate/count_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 library CountTest;
diff --git a/tests/lib/isolate/function_send_test.dart b/tests/lib/isolate/function_send_test.dart
index ea75c63..dc47408 100644
--- a/tests/lib/isolate/function_send_test.dart
+++ b/tests/lib/isolate/function_send_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 import "dart:isolate";
diff --git a/tests/lib/isolate/isolate_complex_messages_test.dart b/tests/lib/isolate/isolate_complex_messages_test.dart
index 606b3af..998fc7b 100644
--- a/tests/lib/isolate/isolate_complex_messages_test.dart
+++ b/tests/lib/isolate/isolate_complex_messages_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 // Dart test program for testing isolate communication with
diff --git a/tests/lib/isolate/isolate_current_test.dart b/tests/lib/isolate/isolate_current_test.dart
index b4b0baea..0dc5e59 100644
--- a/tests/lib/isolate/isolate_current_test.dart
+++ b/tests/lib/isolate/isolate_current_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 library isolate_current_test;
diff --git a/tests/lib/isolate/issue_35626_test.dart b/tests/lib/isolate/issue_35626_test.dart
index 26ee9c6..03ecce8 100644
--- a/tests/lib/isolate/issue_35626_test.dart
+++ b/tests/lib/isolate/issue_35626_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 // Tests that sets of enums can be set through ports.
diff --git a/tests/lib/isolate/large_byte_data_leak_test.dart b/tests/lib/isolate/large_byte_data_leak_test.dart
index fe2fda1..bacd42f 100644
--- a/tests/lib/isolate/large_byte_data_leak_test.dart
+++ b/tests/lib/isolate/large_byte_data_leak_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 import "dart:async";
diff --git a/tests/lib/isolate/message2_test.dart b/tests/lib/isolate/message2_test.dart
index 422b499..881e9ea 100644
--- a/tests/lib/isolate/message2_test.dart
+++ b/tests/lib/isolate/message2_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 // Dart test program for testing serialization of messages.
diff --git a/tests/lib/isolate/message3_test.dart b/tests/lib/isolate/message3_test.dart
index bb8a007fa..049499c 100644
--- a/tests/lib/isolate/message3_test.dart
+++ b/tests/lib/isolate/message3_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 // Dart test program for testing serialization of messages.
diff --git a/tests/lib/isolate/regress_34752_test.dart b/tests/lib/isolate/regress_34752_test.dart
index fc17139..d75ac0d 100644
--- a/tests/lib/isolate/regress_34752_test.dart
+++ b/tests/lib/isolate/regress_34752_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 // Verifies that large BigInt can be passed through a message port and
diff --git a/tests/lib/isolate/transferable_failed_to_send_test.dart b/tests/lib/isolate/transferable_failed_to_send_test.dart
index a8d3bcd..61297f0 100644
--- a/tests/lib/isolate/transferable_failed_to_send_test.dart
+++ b/tests/lib/isolate/transferable_failed_to_send_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 import "dart:io" show ServerSocket;
diff --git a/tests/lib/isolate/transferable_test.dart b/tests/lib/isolate/transferable_test.dart
index f8888e7..def39d4 100644
--- a/tests/lib/isolate/transferable_test.dart
+++ b/tests/lib/isolate/transferable_test.dart
@@ -2,7 +2,8 @@
 // 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=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 import "dart:async";
diff --git a/tests/lib/js/extends_test/extends_subtyping_live_test.dart b/tests/lib/js/extends_test/extends_subtyping_live_test.dart
new file mode 100644
index 0000000..b46818e
--- /dev/null
+++ b/tests/lib/js/extends_test/extends_subtyping_live_test.dart
@@ -0,0 +1,20 @@
+// 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.
+
+// Tests inheritance subtyping relationships after making package:js types live.
+
+@JS()
+library extends_subtyping_live_test;
+
+import 'package:js/js.dart';
+import 'extends_test_util.dart';
+
+@JS()
+external dynamic get externalGetter;
+
+void main() {
+  // Call to foreign function should trigger dart2js to assume types are live.
+  externalGetter;
+  testSubtyping();
+}
diff --git a/tests/lib/js/extends_test/extends_subtyping_not_live_test.dart b/tests/lib/js/extends_test/extends_subtyping_not_live_test.dart
new file mode 100644
index 0000000..3cb0fbb
--- /dev/null
+++ b/tests/lib/js/extends_test/extends_subtyping_not_live_test.dart
@@ -0,0 +1,12 @@
+// 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.
+
+// Tests inheritance subtyping relationships without making package:js types
+// live.
+
+import 'extends_test_util.dart';
+
+void main() {
+  testSubtyping();
+}
diff --git a/tests/lib/js/extends_test/extends_test_util.dart b/tests/lib/js/extends_test/extends_test_util.dart
index e39e1b9..0698ef9 100644
--- a/tests/lib/js/extends_test/extends_test_util.dart
+++ b/tests/lib/js/extends_test/extends_test_util.dart
@@ -15,6 +15,7 @@
 
 @JS()
 class JSClass {
+  external JSClass();
   external int get a;
   external int getA();
   external int getAOrB();
@@ -57,12 +58,10 @@
   external int getAOrB();
 }
 
+external AnonymousClass get anon;
 external AnonymousExtendAnonymousClass get anonExtendAnon;
 external AnonymousExtendJSClass get anonExtendJS;
 
-void useJSClass(JSClass js) {}
-void useAnonymousClass(AnonymousClass a) {}
-
 void setUpWithoutES6Syntax() {
   // Use the old way to define inheritance between JS objects.
   eval(r"""
@@ -77,6 +76,7 @@
       }
     }
     function JSClass(a) {
+      if (arguments.length == 0) a = 1;
       this.a = a;
       this.getA = function() {
         return this.a;
@@ -109,6 +109,7 @@
         return this.getB();
       }
     }
+    self.anon = new JSClass(1);
     self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
     self.anonExtendJS = new JSExtendJSClass(1, 2);
   """);
@@ -119,6 +120,7 @@
   eval(r"""
     class JSClass {
       constructor(a) {
+        if (arguments.length == 0) a = 1;
         this.a = a;
       }
       getA() {
@@ -128,6 +130,7 @@
         return this.getA();
       }
     }
+    self.JSClass = JSClass;
     class JSExtendJSClass extends JSClass {
       constructor(a, b) {
         super(a);
@@ -157,16 +160,13 @@
       }
     }
     self.JSExtendAnonymousClass = JSExtendAnonymousClass;
+    self.anon = new JSClass(1);
     self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
     self.anonExtendJS = new JSExtendJSClass(1, 2);
   """);
 }
 
 void testInheritance() {
-  // Note that for the following, there are no meaningful tests for is checks or
-  // as casts, since the web compilers should return true and succeed for all JS
-  // types.
-
   var jsExtendJS = JSExtendJSClass(1, 2);
   expect(jsExtendJS.a, 1);
   expect(jsExtendJS.b, 2);
@@ -193,18 +193,86 @@
   expect(anonExtendJS.getB(), 2);
   expect(anonExtendJS.getAOrB(), 2);
   expect((anonExtendJS as JSClass).getAOrB(), 2);
+
+  // Test type checking and casts succeeds regardless of type hierarchy.
+
+  // Test type checking at runtime by disabling inlining. We still, however, do
+  // `is` checks directly below to test those optimizations.
+  @pragma('dart2js:noInline')
+  void runtimeIsAndAs<T>(instance) {
+    expect(instance is T, true);
+    expect(() => instance as T, returnsNormally);
+  }
+
+  // Test that base JS type can be used as any subtype.
+  var js = JSClass();
+  expect(js is JSExtendJSClass, true);
+  runtimeIsAndAs<JSExtendJSClass>(js);
+  expect(js is AnonymousExtendJSClass, true);
+  runtimeIsAndAs<AnonymousExtendJSClass>(js);
+
+  // Test that base anonymous type can be use as any subtype.
+  // Conversion from external getter value to a variable is needed to coerce
+  // compile time optimization of type checks. This applies for below as well.
+  var anonVar = anon;
+  expect(anonVar is JSExtendAnonymousClass, true);
+  runtimeIsAndAs<JSExtendAnonymousClass>(anonVar);
+  expect(anonVar is AnonymousExtendAnonymousClass, true);
+  runtimeIsAndAs<AnonymousExtendAnonymousClass>(anonVar);
+
+  // Test that instance of subtypes can be used as their JS supertype.
+  expect(jsExtendJS is JSClass, true);
+  runtimeIsAndAs<JSClass>(jsExtendJS);
+  var anonExtendJSVar = anonExtendJS;
+  expect(anonExtendJSVar is JSClass, true);
+  runtimeIsAndAs<JSClass>(anonExtendJSVar);
+
+  // Test that instance of subtypes can be used as their anonymous supertype.
+  var anonExtendAnonVar = anonExtendAnon;
+  expect(anonExtendAnonVar is AnonymousClass, true);
+  runtimeIsAndAs<AnonymousClass>(anonExtendAnonVar);
+  expect(jsExtendAnon is AnonymousClass, true);
+  runtimeIsAndAs<AnonymousClass>(jsExtendAnon);
+}
+
+JSClass returnJS() => throw '';
+
+AnonymousClass returnAnon() => throw '';
+
+JSExtendJSClass returnJSExtendJS() => throw '';
+
+JSExtendAnonymousClass returnJSExtendAnon() => throw '';
+
+AnonymousExtendJSClass returnAnonExtendJS() => throw '';
+
+AnonymousExtendAnonymousClass returnAnonExtendAnon() => throw '';
+
+@pragma('dart2js:noInline')
+void isRuntimeSubtypeBothWays<T, U>() {
+  // Test T <: U and U <: T. With interop types, type checks should pass
+  // regardless of type hierarchy. Note that dart2js does these type checks at
+  // runtime. Below, we do compile-time checks using top-level functions.
+  T f1() => throw '';
+  U f2() => throw '';
+  expect(f1 is U Function(), true);
+  expect(f2 is T Function(), true);
 }
 
 void testSubtyping() {
   // Test subtyping for inheritance between JS and anonymous classes.
-  expect(useJSClass is void Function(JSExtendJSClass js), true);
-  expect(useAnonymousClass is void Function(AnonymousExtendAnonymousClass a),
-      true);
-  expect(useJSClass is void Function(AnonymousExtendJSClass a), true);
-  expect(useAnonymousClass is void Function(JSExtendAnonymousClass js), true);
+  expect(returnJS is JSExtendJSClass Function(), true);
+  expect(returnJSExtendJS is JSClass Function(), true);
+  isRuntimeSubtypeBothWays<JSClass, JSExtendJSClass>();
 
-  expect(useJSClass is void Function(AnonymousExtendAnonymousClass a), false);
-  expect(useAnonymousClass is void Function(JSExtendJSClass js), false);
-  expect(useJSClass is void Function(JSExtendAnonymousClass js), false);
-  expect(useAnonymousClass is void Function(AnonymousExtendJSClass a), false);
+  expect(returnJS is AnonymousExtendJSClass Function(), true);
+  expect(returnAnonExtendJS is JSClass Function(), true);
+  isRuntimeSubtypeBothWays<JSClass, AnonymousExtendJSClass>();
+
+  expect(returnAnon is JSExtendAnonymousClass Function(), true);
+  expect(returnJSExtendAnon is AnonymousClass Function(), true);
+  isRuntimeSubtypeBothWays<AnonymousClass, JSExtendAnonymousClass>();
+
+  expect(returnAnon is AnonymousExtendAnonymousClass Function(), true);
+  expect(returnAnonExtendAnon is AnonymousClass Function(), true);
+  isRuntimeSubtypeBothWays<AnonymousClass, AnonymousExtendAnonymousClass>();
 }
diff --git a/tests/lib/js/subtyping_test.dart b/tests/lib/js/subtyping_test.dart
deleted file mode 100644
index 2551fcd..0000000
--- a/tests/lib/js/subtyping_test.dart
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// Tests subtyping relationships between JS and anonymous classes.
-
-@JS()
-library subtyping_test;
-
-import 'package:js/js.dart';
-import 'package:expect/expect.dart' show hasUnsoundNullSafety;
-import 'package:expect/minitest.dart';
-
-@JS()
-class JSClassA {}
-
-@JS()
-class JSClassB {}
-
-@JS()
-@anonymous
-class AnonymousClassA {}
-
-@JS()
-@anonymous
-class AnonymousClassB {}
-
-class DartClass {}
-
-void useJSClassA(JSClassA _) {}
-void useAnonymousClassA(AnonymousClassA _) {}
-void useDartClass(DartClass _) {}
-
-void useNullableJSClassA(JSClassA? _) {}
-void useNullableAnonymousClassA(AnonymousClassA? _) {}
-
-// Avoid static type optimization by running all tests using this.
-@pragma('dart2js:noInline')
-@pragma('dart2js:assumeDynamic')
-confuse(x) => x;
-
-void main() {
-  // Checks subtyping with the same type and nullability subtyping.
-  expect(useJSClassA is void Function(JSClassA), true);
-  expect(useAnonymousClassA is void Function(AnonymousClassA), true);
-  expect(useJSClassA is void Function(JSClassA?), hasUnsoundNullSafety);
-  expect(useAnonymousClassA is void Function(AnonymousClassA?),
-      hasUnsoundNullSafety);
-  expect(useNullableJSClassA is void Function(JSClassA?), true);
-  expect(useNullableAnonymousClassA is void Function(AnonymousClassA?), true);
-  expect(useNullableJSClassA is void Function(JSClassA), true);
-  expect(useNullableAnonymousClassA is void Function(AnonymousClassA), true);
-
-  expect(confuse(useJSClassA) is void Function(JSClassA), true);
-  expect(confuse(useAnonymousClassA) is void Function(AnonymousClassA), true);
-  expect(
-      confuse(useJSClassA) is void Function(JSClassA?), hasUnsoundNullSafety);
-  expect(confuse(useAnonymousClassA) is void Function(AnonymousClassA?),
-      hasUnsoundNullSafety);
-  expect(confuse(useNullableJSClassA) is void Function(JSClassA?), true);
-  expect(confuse(useNullableAnonymousClassA) is void Function(AnonymousClassA?),
-      true);
-  expect(confuse(useNullableJSClassA) is void Function(JSClassA), true);
-  expect(confuse(useNullableAnonymousClassA) is void Function(AnonymousClassA),
-      true);
-
-  // No subtyping between JS and anonymous classes.
-  expect(useJSClassA is void Function(AnonymousClassA), false);
-  expect(useAnonymousClassA is void Function(JSClassA), false);
-
-  expect(confuse(useJSClassA) is void Function(AnonymousClassA), false);
-  expect(confuse(useAnonymousClassA) is void Function(JSClassA), false);
-
-  // No subtyping between separate classes even if they're both JS classes or
-  // anonymous classes.
-  expect(useJSClassA is void Function(JSClassB), false);
-  expect(useAnonymousClassA is void Function(AnonymousClassB), false);
-
-  expect(confuse(useJSClassA) is void Function(JSClassB), false);
-  expect(confuse(useAnonymousClassA) is void Function(AnonymousClassB), false);
-
-  // No subtyping between JS/anonymous classes and Dart classes.
-  expect(useJSClassA is void Function(DartClass), false);
-  expect(useAnonymousClassA is void Function(DartClass), false);
-
-  expect(confuse(useJSClassA) is void Function(DartClass), false);
-  expect(confuse(useAnonymousClassA) is void Function(DartClass), false);
-}
diff --git a/tests/lib/js/subtyping_test/subtyping_live_test.dart b/tests/lib/js/subtyping_test/subtyping_live_test.dart
new file mode 100644
index 0000000..d137ba6
--- /dev/null
+++ b/tests/lib/js/subtyping_test/subtyping_live_test.dart
@@ -0,0 +1,20 @@
+// 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.
+
+// Tests subtyping relationships after making package:js types live.
+
+@JS()
+library subtyping_live_test;
+
+import 'package:js/js.dart';
+import 'subtyping_test_util.dart';
+
+@JS()
+external dynamic get externalGetter;
+
+void main() {
+  // Call to foreign function should trigger dart2js to assume types are live.
+  externalGetter;
+  testSubtyping();
+}
diff --git a/tests/lib/js/subtyping_test/subtyping_not_live_test.dart b/tests/lib/js/subtyping_test/subtyping_not_live_test.dart
new file mode 100644
index 0000000..5344280
--- /dev/null
+++ b/tests/lib/js/subtyping_test/subtyping_not_live_test.dart
@@ -0,0 +1,12 @@
+// 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.
+
+// Tests subtyping relationships without making package:js types live.
+
+import 'package:js/js.dart';
+import 'subtyping_test_util.dart';
+
+void main() {
+  testSubtyping();
+}
diff --git a/tests/lib/js/subtyping_test/subtyping_test_util.dart b/tests/lib/js/subtyping_test/subtyping_test_util.dart
new file mode 100644
index 0000000..c8f7740
--- /dev/null
+++ b/tests/lib/js/subtyping_test/subtyping_test_util.dart
@@ -0,0 +1,92 @@
+// 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.
+
+// Tests subtyping relationships between JS and anonymous classes.
+
+@JS()
+library subtyping_test_util;
+
+import 'package:js/js.dart';
+import 'package:expect/expect.dart' show hasUnsoundNullSafety;
+import 'package:expect/minitest.dart';
+
+@JS()
+class JSClassA {}
+
+@JS()
+class JSClassB {}
+
+@JS()
+@anonymous
+class AnonymousClassA {}
+
+@JS()
+@anonymous
+class AnonymousClassB {}
+
+class DartClass {}
+
+JSClassA returnJS() => throw '';
+JSClassA? returnNullableJS() => throw '';
+
+AnonymousClassA returnAnon() => throw '';
+AnonymousClassA? returnNullableAnon() => throw '';
+
+DartClass returnDartClass() => throw '';
+
+// Avoid static type optimization by running all tests using this.
+@pragma('dart2js:noInline')
+@pragma('dart2js:assumeDynamic')
+confuse(x) => x;
+
+void testSubtyping() {
+  // Checks subtyping with the same type and nullability subtyping.
+  expect(returnJS is JSClassA Function(), true);
+  expect(returnAnon is AnonymousClassA Function(), true);
+  expect(returnJS is JSClassA? Function(), true);
+  expect(returnAnon is AnonymousClassA? Function(), true);
+  expect(returnNullableJS is JSClassA? Function(), true);
+  expect(returnNullableAnon is AnonymousClassA? Function(), true);
+  expect(returnNullableJS is JSClassA Function(), hasUnsoundNullSafety);
+  expect(
+      returnNullableAnon is AnonymousClassA Function(), hasUnsoundNullSafety);
+
+  // Subtyping between JS and anonymous classes.
+  expect(returnJS is AnonymousClassA Function(), true);
+  expect(returnAnon is JSClassA Function(), true);
+
+  // Subtyping between same type of package:js classes.
+  expect(returnJS is JSClassB Function(), true);
+  expect(returnAnon is AnonymousClassB Function(), true);
+
+  // No subtyping between JS/anonymous classes and Dart classes.
+  expect(returnJS is DartClass Function(), false);
+  expect(returnDartClass is JSClassA Function(), false);
+  expect(returnAnon is DartClass Function(), false);
+  expect(returnDartClass is AnonymousClassA Function(), false);
+
+  // Repeat the checks but using `confuse` to coerce runtime checks instead of
+  // compile-time like above.
+  expect(confuse(returnJS) is JSClassA Function(), true);
+  expect(confuse(returnAnon) is AnonymousClassA Function(), true);
+  expect(confuse(returnJS) is JSClassA? Function(), true);
+  expect(confuse(returnAnon) is AnonymousClassA? Function(), true);
+  expect(confuse(returnNullableJS) is JSClassA? Function(), true);
+  expect(confuse(returnNullableAnon) is AnonymousClassA? Function(), true);
+  expect(
+      confuse(returnNullableJS) is JSClassA Function(), hasUnsoundNullSafety);
+  expect(confuse(returnNullableAnon) is AnonymousClassA Function(),
+      hasUnsoundNullSafety);
+
+  expect(confuse(returnJS) is AnonymousClassA Function(), true);
+  expect(confuse(returnAnon) is JSClassA Function(), true);
+
+  expect(confuse(returnJS) is JSClassB Function(), true);
+  expect(confuse(returnAnon) is AnonymousClassB Function(), true);
+
+  expect(confuse(returnJS) is DartClass Function(), false);
+  expect(confuse(returnDartClass) is JSClassA Function(), false);
+  expect(confuse(returnAnon) is DartClass Function(), false);
+  expect(confuse(returnDartClass) is AnonymousClassA Function(), false);
+}
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index b931e84..412a98c 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -36,6 +36,8 @@
 [ $csp ]
 html/js_interop_constructor_name/*: SkipByDesign # Issue 42085.
 isolate/deferred_in_isolate2_test: Skip # Issue 16898. Deferred loading does not work from an isolate in CSP-mode
+js/extends_test/extends_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/extends_test/extends_with_es6_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/instanceof_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/js_util/async_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/js_util/jsify_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
diff --git a/tests/lib_2/isolate/count_test.dart b/tests/lib_2/isolate/count_test.dart
index 0d92759..d216e34 100644
--- a/tests/lib_2/isolate/count_test.dart
+++ b/tests/lib_2/isolate/count_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 library CountTest;
diff --git a/tests/lib_2/isolate/function_send_test.dart b/tests/lib_2/isolate/function_send_test.dart
index 8d24312..86eea4a 100644
--- a/tests/lib_2/isolate/function_send_test.dart
+++ b/tests/lib_2/isolate/function_send_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 import "dart:isolate";
diff --git a/tests/lib_2/isolate/isolate_complex_messages_test.dart b/tests/lib_2/isolate/isolate_complex_messages_test.dart
index 30322a1..2c10be7 100644
--- a/tests/lib_2/isolate/isolate_complex_messages_test.dart
+++ b/tests/lib_2/isolate/isolate_complex_messages_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 // Dart test program for testing isolate communication with
diff --git a/tests/lib_2/isolate/isolate_current_test.dart b/tests/lib_2/isolate/isolate_current_test.dart
index 3850591..9188c9d 100644
--- a/tests/lib_2/isolate/isolate_current_test.dart
+++ b/tests/lib_2/isolate/isolate_current_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 library isolate_current_test;
diff --git a/tests/lib_2/isolate/issue_35626_test.dart b/tests/lib_2/isolate/issue_35626_test.dart
index 9e9dbfe..cb8fe62 100644
--- a/tests/lib_2/isolate/issue_35626_test.dart
+++ b/tests/lib_2/isolate/issue_35626_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 // Tests that sets of enums can be set through ports.
diff --git a/tests/lib_2/isolate/large_byte_data_leak_test.dart b/tests/lib_2/isolate/large_byte_data_leak_test.dart
index 7db4ff5..6adcd28 100644
--- a/tests/lib_2/isolate/large_byte_data_leak_test.dart
+++ b/tests/lib_2/isolate/large_byte_data_leak_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 import "dart:async";
diff --git a/tests/lib_2/isolate/message2_test.dart b/tests/lib_2/isolate/message2_test.dart
index ccb33b33..0ca540d 100644
--- a/tests/lib_2/isolate/message2_test.dart
+++ b/tests/lib_2/isolate/message2_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 // Dart test program for testing serialization of messages.
diff --git a/tests/lib_2/isolate/message3_test.dart b/tests/lib_2/isolate/message3_test.dart
index b9813bd..0e1483b 100644
--- a/tests/lib_2/isolate/message3_test.dart
+++ b/tests/lib_2/isolate/message3_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 // Dart test program for testing serialization of messages.
diff --git a/tests/lib_2/isolate/regress_34752_test.dart b/tests/lib_2/isolate/regress_34752_test.dart
index 7390247..c034dbf 100644
--- a/tests/lib_2/isolate/regress_34752_test.dart
+++ b/tests/lib_2/isolate/regress_34752_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 // Verifies that large BigInt can be passed through a message port and
diff --git a/tests/lib_2/isolate/transferable_failed_to_send_test.dart b/tests/lib_2/isolate/transferable_failed_to_send_test.dart
index 8ee6226..d5481df 100644
--- a/tests/lib_2/isolate/transferable_failed_to_send_test.dart
+++ b/tests/lib_2/isolate/transferable_failed_to_send_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 import "dart:io" show ServerSocket;
diff --git a/tests/lib_2/isolate/transferable_test.dart b/tests/lib_2/isolate/transferable_test.dart
index aef8d97..e00cf75 100644
--- a/tests/lib_2/isolate/transferable_test.dart
+++ b/tests/lib_2/isolate/transferable_test.dart
@@ -4,7 +4,8 @@
 
 // @dart = 2.9
 
-// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --no-enable-fast-object-copy
+// VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit --enable-fast-object-copy
 // VMOptions=--no-enable-isolate-groups
 
 import "dart:async";
diff --git a/tests/lib_2/js/extends_test/extends_subtyping_live_test.dart b/tests/lib_2/js/extends_test/extends_subtyping_live_test.dart
new file mode 100644
index 0000000..b46818e
--- /dev/null
+++ b/tests/lib_2/js/extends_test/extends_subtyping_live_test.dart
@@ -0,0 +1,20 @@
+// 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.
+
+// Tests inheritance subtyping relationships after making package:js types live.
+
+@JS()
+library extends_subtyping_live_test;
+
+import 'package:js/js.dart';
+import 'extends_test_util.dart';
+
+@JS()
+external dynamic get externalGetter;
+
+void main() {
+  // Call to foreign function should trigger dart2js to assume types are live.
+  externalGetter;
+  testSubtyping();
+}
diff --git a/tests/lib_2/js/extends_test/extends_subtyping_not_live_test.dart b/tests/lib_2/js/extends_test/extends_subtyping_not_live_test.dart
new file mode 100644
index 0000000..3cb0fbb
--- /dev/null
+++ b/tests/lib_2/js/extends_test/extends_subtyping_not_live_test.dart
@@ -0,0 +1,12 @@
+// 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.
+
+// Tests inheritance subtyping relationships without making package:js types
+// live.
+
+import 'extends_test_util.dart';
+
+void main() {
+  testSubtyping();
+}
diff --git a/tests/lib/js/extends_test/extends_subtyping_test.dart b/tests/lib_2/js/extends_test/extends_test.dart
similarity index 72%
rename from tests/lib/js/extends_test/extends_subtyping_test.dart
rename to tests/lib_2/js/extends_test/extends_test.dart
index 76dc51e..4286838 100644
--- a/tests/lib/js/extends_test/extends_subtyping_test.dart
+++ b/tests/lib_2/js/extends_test/extends_test.dart
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// 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.
 
@@ -6,5 +6,5 @@
 
 void main() {
   setUpWithoutES6Syntax();
-  testSubtyping();
+  testInheritance();
 }
diff --git a/tests/lib_2/js/extends_test/extends_test_util.dart b/tests/lib_2/js/extends_test/extends_test_util.dart
new file mode 100644
index 0000000..9a21c51
--- /dev/null
+++ b/tests/lib_2/js/extends_test/extends_test_util.dart
@@ -0,0 +1,278 @@
+// 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.
+
+// Tests inheritance relationships between `JS` and `anonymous` classes/objects.
+
+@JS()
+library extends_test;
+
+import 'package:expect/minitest.dart';
+import 'package:js/js.dart';
+
+@JS()
+external void eval(String code);
+
+@JS()
+class JSClass {
+  external JSClass();
+  external int get a;
+  external int getA();
+  external int getAOrB();
+}
+
+@JS()
+@anonymous
+class AnonymousClass {
+  external int get a;
+  external int getA();
+}
+
+@JS()
+class JSExtendJSClass extends JSClass {
+  external JSExtendJSClass(int a, int b);
+  external int get b;
+  external int getB();
+  external int getAOrB();
+}
+
+@JS()
+class JSExtendAnonymousClass extends AnonymousClass {
+  external JSExtendAnonymousClass(int a, int b);
+  external int get b;
+  external int getB();
+}
+
+@JS()
+@anonymous
+class AnonymousExtendAnonymousClass extends AnonymousClass {
+  external int get b;
+  external int getB();
+}
+
+@JS()
+@anonymous
+class AnonymousExtendJSClass extends JSClass {
+  external int get b;
+  external int getB();
+  external int getAOrB();
+}
+
+external AnonymousClass get anon;
+external AnonymousExtendAnonymousClass get anonExtendAnon;
+external AnonymousExtendJSClass get anonExtendJS;
+
+void setUpWithoutES6Syntax() {
+  // Use the old way to define inheritance between JS objects.
+  eval(r"""
+    function inherits(child, parent) {
+      if (child.prototype.__proto__) {
+        child.prototype.__proto__ = parent.prototype;
+      } else {
+        function tmp() {};
+        tmp.prototype = parent.prototype;
+        child.prototype = new tmp();
+        child.prototype.constructor = child;
+      }
+    }
+    function JSClass(a) {
+      if (arguments.length == 0) a = 1;
+      this.a = a;
+      this.getA = function() {
+        return this.a;
+      }
+      this.getAOrB = function() {
+        return this.getA();
+      }
+    }
+    function JSExtendJSClass(a, b) {
+      JSClass.call(this, a);
+      this.b = b;
+      this.getB = function() {
+        return this.b;
+      }
+      this.getAOrB = function() {
+        return this.getB();
+      }
+    }
+    inherits(JSExtendJSClass, JSClass);
+    function JSExtendAnonymousClass(a, b) {
+      this.a = a;
+      this.b = b;
+      this.getA = function() {
+        return this.a;
+      }
+      this.getB = function() {
+        return this.b;
+      }
+      this.getAOrB = function() {
+        return this.getB();
+      }
+    }
+    self.anon = new JSClass(1);
+    self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
+    self.anonExtendJS = new JSExtendJSClass(1, 2);
+  """);
+}
+
+void setUpWithES6Syntax() {
+  // Use the ES6 syntax for classes to make inheritance easier.
+  eval(r"""
+    class JSClass {
+      constructor(a) {
+        if (arguments.length == 0) a = 1;
+        this.a = a;
+      }
+      getA() {
+        return this.a;
+      }
+      getAOrB() {
+        return this.getA();
+      }
+    }
+    self.JSClass = JSClass;
+    class JSExtendJSClass extends JSClass {
+      constructor(a, b) {
+        super(a);
+        this.b = b;
+      }
+      getB() {
+        return this.b;
+      }
+      getAOrB() {
+        return this.getB();
+      }
+    }
+    self.JSExtendJSClass = JSExtendJSClass;
+    class JSExtendAnonymousClass {
+      constructor(a, b) {
+        this.a = a;
+        this.b = b;
+      }
+      getA() {
+        return this.a;
+      }
+      getB() {
+        return this.b;
+      }
+      getAOrB() {
+        return this.getB();
+      }
+    }
+    self.JSExtendAnonymousClass = JSExtendAnonymousClass;
+    self.anon = new JSClass(1);
+    self.anonExtendAnon = new JSExtendAnonymousClass(1, 2);
+    self.anonExtendJS = new JSExtendJSClass(1, 2);
+  """);
+}
+
+void testInheritance() {
+  var jsExtendJS = JSExtendJSClass(1, 2);
+  expect(jsExtendJS.a, 1);
+  expect(jsExtendJS.b, 2);
+  expect(jsExtendJS.getA(), 1);
+  expect(jsExtendJS.getB(), 2);
+  // Test method overrides.
+  expect(jsExtendJS.getAOrB(), 2);
+  expect((jsExtendJS as JSClass).getAOrB(), 2);
+
+  var jsExtendAnon = JSExtendAnonymousClass(1, 2);
+  expect(jsExtendAnon.a, 1);
+  expect(jsExtendAnon.b, 2);
+  expect(jsExtendAnon.getA(), 1);
+  expect(jsExtendAnon.getB(), 2);
+
+  expect(anonExtendAnon.a, 1);
+  expect(anonExtendAnon.b, 2);
+  expect(anonExtendAnon.getA(), 1);
+  expect(anonExtendAnon.getB(), 2);
+
+  expect(anonExtendJS.a, 1);
+  expect(anonExtendJS.b, 2);
+  expect(anonExtendJS.getA(), 1);
+  expect(anonExtendJS.getB(), 2);
+  expect(anonExtendJS.getAOrB(), 2);
+  expect((anonExtendJS as JSClass).getAOrB(), 2);
+
+  // Test type checking and casts succeeds regardless of type hierarchy.
+
+  // Test type checking at runtime by disabling inlining. We still, however, do
+  // `is` checks directly below to test those optimizations.
+  @pragma('dart2js:noInline')
+  void runtimeIsAndAs<T>(instance) {
+    expect(instance is T, true);
+    expect(() => instance as T, returnsNormally);
+  }
+
+  // Test that base JS type can be used as any subtype.
+  var js = JSClass();
+  expect(js is JSExtendJSClass, true);
+  runtimeIsAndAs<JSExtendJSClass>(js);
+  expect(js is AnonymousExtendJSClass, true);
+  runtimeIsAndAs<AnonymousExtendJSClass>(js);
+
+  // Test that base anonymous type can be use as any subtype.
+  // Conversion from external getter value to a variable is needed to coerce
+  // compile time optimization of type checks. This applies for below as well.
+  var anonVar = anon;
+  expect(anonVar is JSExtendAnonymousClass, true);
+  runtimeIsAndAs<JSExtendAnonymousClass>(anonVar);
+  expect(anonVar is AnonymousExtendAnonymousClass, true);
+  runtimeIsAndAs<AnonymousExtendAnonymousClass>(anonVar);
+
+  // Test that instance of subtypes can be used as their JS supertype.
+  expect(jsExtendJS is JSClass, true);
+  runtimeIsAndAs<JSClass>(jsExtendJS);
+  var anonExtendJSVar = anonExtendJS;
+  expect(anonExtendJSVar is JSClass, true);
+  runtimeIsAndAs<JSClass>(anonExtendJSVar);
+
+  // Test that instance of subtypes can be used as their anonymous supertype.
+  var anonExtendAnonVar = anonExtendAnon;
+  expect(anonExtendAnonVar is AnonymousClass, true);
+  runtimeIsAndAs<AnonymousClass>(anonExtendAnonVar);
+  expect(jsExtendAnon is AnonymousClass, true);
+  runtimeIsAndAs<AnonymousClass>(jsExtendAnon);
+}
+
+JSClass returnJS() => throw '';
+
+AnonymousClass returnAnon() => throw '';
+
+JSExtendJSClass returnJSExtendJS() => throw '';
+
+JSExtendAnonymousClass returnJSExtendAnon() => throw '';
+
+AnonymousExtendJSClass returnAnonExtendJS() => throw '';
+
+AnonymousExtendAnonymousClass returnAnonExtendAnon() => throw '';
+
+@pragma('dart2js:noInline')
+void isRuntimeSubtypeBothWays<T, U>() {
+  // Test T <: U and U <: T. With interop types, type checks should pass
+  // regardless of type hierarchy. Note that dart2js does these type checks at
+  // runtime. Below, we do compile-time checks using top-level functions.
+  T f1() => throw '';
+  U f2() => throw '';
+  expect(f1 is U Function(), true);
+  expect(f2 is T Function(), true);
+}
+
+void testSubtyping() {
+  // Test subtyping for inheritance between JS and anonymous classes.
+  expect(returnJS is JSExtendJSClass Function(), true);
+  expect(returnJSExtendJS is JSClass Function(), true);
+  isRuntimeSubtypeBothWays<JSClass, JSExtendJSClass>();
+
+  expect(returnJS is AnonymousExtendJSClass Function(), true);
+  expect(returnAnonExtendJS is JSClass Function(), true);
+  isRuntimeSubtypeBothWays<JSClass, AnonymousExtendJSClass>();
+
+  expect(returnAnon is JSExtendAnonymousClass Function(), true);
+  expect(returnJSExtendAnon is AnonymousClass Function(), true);
+  isRuntimeSubtypeBothWays<AnonymousClass, JSExtendAnonymousClass>();
+
+  expect(returnAnon is AnonymousExtendAnonymousClass Function(), true);
+  expect(returnAnonExtendAnon is AnonymousClass Function(), true);
+  isRuntimeSubtypeBothWays<AnonymousClass, AnonymousExtendAnonymousClass>();
+}
diff --git a/tests/lib/js/extends_test/extends_with_es6_subtyping_test.dart b/tests/lib_2/js/extends_test/extends_with_es6_test.dart
similarity index 72%
rename from tests/lib/js/extends_test/extends_with_es6_subtyping_test.dart
rename to tests/lib_2/js/extends_test/extends_with_es6_test.dart
index e6185da..e0ed10d 100644
--- a/tests/lib/js/extends_test/extends_with_es6_subtyping_test.dart
+++ b/tests/lib_2/js/extends_test/extends_with_es6_test.dart
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// 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.
 
@@ -6,5 +6,5 @@
 
 void main() {
   setUpWithES6Syntax();
-  testSubtyping();
+  testInheritance();
 }
diff --git a/tests/lib_2/js/subtyping_test/subtyping_live_test.dart b/tests/lib_2/js/subtyping_test/subtyping_live_test.dart
new file mode 100644
index 0000000..d137ba6
--- /dev/null
+++ b/tests/lib_2/js/subtyping_test/subtyping_live_test.dart
@@ -0,0 +1,20 @@
+// 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.
+
+// Tests subtyping relationships after making package:js types live.
+
+@JS()
+library subtyping_live_test;
+
+import 'package:js/js.dart';
+import 'subtyping_test_util.dart';
+
+@JS()
+external dynamic get externalGetter;
+
+void main() {
+  // Call to foreign function should trigger dart2js to assume types are live.
+  externalGetter;
+  testSubtyping();
+}
diff --git a/tests/lib_2/js/subtyping_test/subtyping_not_live_test.dart b/tests/lib_2/js/subtyping_test/subtyping_not_live_test.dart
new file mode 100644
index 0000000..5344280
--- /dev/null
+++ b/tests/lib_2/js/subtyping_test/subtyping_not_live_test.dart
@@ -0,0 +1,12 @@
+// 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.
+
+// Tests subtyping relationships without making package:js types live.
+
+import 'package:js/js.dart';
+import 'subtyping_test_util.dart';
+
+void main() {
+  testSubtyping();
+}
diff --git a/tests/lib_2/js/subtyping_test/subtyping_test_util.dart b/tests/lib_2/js/subtyping_test/subtyping_test_util.dart
new file mode 100644
index 0000000..a39efb9
--- /dev/null
+++ b/tests/lib_2/js/subtyping_test/subtyping_test_util.dart
@@ -0,0 +1,74 @@
+// 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.
+
+// Tests subtyping relationships between JS and anonymous classes.
+
+@JS()
+library subtyping_test_util;
+
+import 'package:js/js.dart';
+import 'package:expect/minitest.dart';
+
+@JS()
+class JSClassA {}
+
+@JS()
+class JSClassB {}
+
+@JS()
+@anonymous
+class AnonymousClassA {}
+
+@JS()
+@anonymous
+class AnonymousClassB {}
+
+class DartClass {}
+
+JSClassA returnJS() => throw '';
+
+AnonymousClassA returnAnon() => throw '';
+
+DartClass returnDartClass() => throw '';
+
+// Avoid static type optimization by running all tests using this.
+@pragma('dart2js:noInline')
+@pragma('dart2js:assumeDynamic')
+confuse(x) => x;
+
+void testSubtyping() {
+  // Checks subtyping with the same type.
+  expect(returnJS is JSClassA Function(), true);
+  expect(returnAnon is AnonymousClassA Function(), true);
+
+  // Subtyping between JS and anonymous classes.
+  expect(returnJS is AnonymousClassA Function(), true);
+  expect(returnAnon is JSClassA Function(), true);
+
+  // Subtyping between same type of package:js classes.
+  expect(returnJS is JSClassB Function(), true);
+  expect(returnAnon is AnonymousClassB Function(), true);
+
+  // No subtyping between JS/anonymous classes and Dart classes.
+  expect(returnJS is DartClass Function(), false);
+  expect(returnDartClass is JSClassA Function(), false);
+  expect(returnAnon is DartClass Function(), false);
+  expect(returnDartClass is AnonymousClassA Function(), false);
+
+  // Repeat the checks but using `confuse` to coerce runtime checks instead of
+  // compile-time like above.
+  expect(confuse(returnJS) is JSClassA Function(), true);
+  expect(confuse(returnAnon) is AnonymousClassA Function(), true);
+
+  expect(confuse(returnJS) is AnonymousClassA Function(), true);
+  expect(confuse(returnAnon) is JSClassA Function(), true);
+
+  expect(confuse(returnJS) is JSClassB Function(), true);
+  expect(confuse(returnAnon) is AnonymousClassB Function(), true);
+
+  expect(confuse(returnJS) is DartClass Function(), false);
+  expect(confuse(returnDartClass) is JSClassA Function(), false);
+  expect(confuse(returnAnon) is DartClass Function(), false);
+  expect(confuse(returnDartClass) is AnonymousClassA Function(), false);
+}
diff --git a/tests/lib_2/lib_2.status b/tests/lib_2/lib_2.status
index 7fbd5a0..eb7d1ed 100644
--- a/tests/lib_2/lib_2.status
+++ b/tests/lib_2/lib_2.status
@@ -36,6 +36,8 @@
 [ $csp ]
 html/js_interop_constructor_name/*: SkipByDesign # Issue 42085.
 isolate/deferred_in_isolate2_test: Skip # Issue 16898. Deferred loading does not work from an isolate in CSP-mode
+js/extends_test/extends_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/extends_test/extends_with_es6_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/instanceof_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/js_util/async_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/js_util/jsify_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
diff --git a/tools/VERSION b/tools/VERSION
index d136bd2..03401a3 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 312
+PRERELEASE 313
 PRERELEASE_PATCH 0
\ No newline at end of file